diff --git a/WineFix/Patches/SubToolColumnsPatch.cs b/WineFix/Patches/SubToolColumnsPatch.cs
new file mode 100644
index 0000000..959cbfb
--- /dev/null
+++ b/WineFix/Patches/SubToolColumnsPatch.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Collections;
+using System.Linq;
+using System.Reflection;
+using System.Windows;
+using HarmonyLib;
+using AffinityPluginLoader.Core;
+
+namespace WineFix.Patches
+{
+ ///
+ /// Keeps the sub-tool panel's column count across a studio switch.
+ ///
+ /// The sub-tool panel is a ToolTray like the main tool pane. SubToolPanel.ToggleVisibility
+ /// gives it two columns when the current tool has more than ten sub-tools (the shape tools)
+ /// and one otherwise, and runs whenever the tool changes. A studio switch also raises
+ /// CurrentWorkspaceChanged, and every ToolTray handles that by setting Columns to
+ /// ToolSettings.ToolPaneColumns -- the main tool pane's setting. With a one-column tool pane
+ /// the shape tools came back as a single column running the height of the window, until
+ /// the next tool change put two columns back.
+ ///
+ /// After that handler, the sub-tool panel's tray gets its own rule back. The main tool pane
+ /// is left alone.
+ ///
+ public static class SubToolColumnsPatch
+ {
+ private static PropertyInfo _toolhostProperty;
+ private static PropertyInfo _subToolRootProperty;
+ private static PropertyInfo _columnsProperty;
+ private static PropertyInfo _currentToolProperty;
+
+ public static void ApplyPatches(Harmony harmony)
+ {
+ Logger.Info("Applying SubToolColumns patch...");
+
+ var serifAssembly = AppDomain.CurrentDomain.GetAssemblies()
+ .FirstOrDefault(a => a.GetName().Name == "Serif.Affinity");
+ if (serifAssembly == null)
+ {
+ Logger.Error("Serif.Affinity assembly not found");
+ return;
+ }
+
+ var trayType = serifAssembly.GetType("Serif.Affinity.UI.Controls.Tools.ToolTray");
+ var hostType = serifAssembly.GetType("Serif.Affinity.UI.Controls.Tools.ToolHost");
+ var windowType = serifAssembly.GetType("Serif.Affinity.UI.AffinityWindow");
+ var handler = trayType == null ? null
+ : AccessTools.Method(trayType, "WorkspaceService_CurrentWorkspaceChanged");
+
+ _toolhostProperty = windowType == null ? null : AccessTools.Property(windowType, "Toolhost");
+ _subToolRootProperty = hostType == null ? null : AccessTools.Property(hostType, "SubToolRoot");
+ _columnsProperty = trayType == null ? null : AccessTools.Property(trayType, "Columns");
+ _currentToolProperty = trayType == null ? null : AccessTools.Property(trayType, "CurrentTool");
+
+ if (handler == null || _toolhostProperty == null || _subToolRootProperty == null
+ || _columnsProperty == null || _currentToolProperty == null)
+ {
+ Logger.Error("ToolTray/ToolHost members not found; sub-tool columns left as they are");
+ return;
+ }
+
+ harmony.Patch(handler, postfix: new HarmonyMethod(typeof(SubToolColumnsPatch), nameof(CurrentWorkspaceChanged_Postfix)));
+ Logger.Info("Patched ToolTray.WorkspaceService_CurrentWorkspaceChanged");
+ }
+
+ public static void CurrentWorkspaceChanged_Postfix(object __instance)
+ {
+ try
+ {
+ var window = Application.Current?.MainWindow;
+ if (window == null || !_toolhostProperty.DeclaringType.IsInstanceOfType(window))
+ return;
+ var toolhost = _toolhostProperty.GetValue(window);
+ if (toolhost == null || !ReferenceEquals(_subToolRootProperty.GetValue(toolhost), __instance))
+ return;
+
+ // SubToolPanel.ToggleVisibility's rule.
+ var tool = _currentToolProperty.GetValue(__instance);
+ if (tool == null)
+ return;
+ var children = AccessTools.Property(tool.GetType(), "Children")?.GetValue(tool) as ICollection;
+ if (children == null)
+ return;
+ object columns = children.Count > 10 ? 2 : 1;
+ if (_columnsProperty.PropertyType == typeof(int?))
+ columns = (int?)(int)columns;
+ _columnsProperty.SetValue(__instance, columns);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error("SubToolColumns: " + ex.Message);
+ }
+ }
+ }
+}
diff --git a/WineFix/WineFixPlugin.cs b/WineFix/WineFixPlugin.cs
index d05ea2c..cf73a77 100644
--- a/WineFix/WineFixPlugin.cs
+++ b/WineFix/WineFixPlugin.cs
@@ -22,6 +22,7 @@ public class WineFixPlugin : AffinityPlugin
public const string SettingWidenStubFix = "widen_stub_fix";
public const string SettingBezierSplitGuard = "bezier_split_guard";
public const string SettingCommandLineFileOpen = "command_line_file_open";
+ public const string SettingSubToolColumns = "sub_tool_columns_fix";
public override PluginSettingsDefinition DefineSettings()
{
@@ -69,6 +70,10 @@ public override PluginSettingsDefinition DefineSettings()
defaultValue: true,
restartRequired: true,
description: "Fix opening files from the command line or desktop file manager.")
+ .AddBool(SettingSubToolColumns, "Sub-tool panel: Keep its columns across studio switches",
+ defaultValue: true,
+ restartRequired: true,
+ description: "A studio switch gives the sub-tool panel the main tool pane's column count, so with a one-column tool pane the shape tools come back as one column running the height of the window until the next tool change. Put back the sub-tool panel's own rule (two columns for more than ten sub-tools) after a studio switch.")
.AddSection("Crash Fixes")
.AddBool(SettingForceSyncFontEnum, "Force synchronous font enumeration",
defaultValue: true,
@@ -111,6 +116,16 @@ public override void OnPatch(Harmony harmony, IPluginContext context)
context.Patch("MainWindowLoaded fix",
h => Patches.MainWindowLoadedPatch.ApplyPatches(h));
+ if (context.Settings.GetEffectiveValue(SettingSubToolColumns))
+ {
+ context.Patch("SubToolColumns fix",
+ h => Patches.SubToolColumnsPatch.ApplyPatches(h));
+ }
+ else
+ {
+ Logger.Info("Skipping SubToolColumns fix (setting: disabled)");
+ }
+
if (context.Settings.GetEffectiveValue(SettingForceSyncFontEnum))
{
context.Patch("FontEnumeration fix",
diff --git a/docs/winefix/configuration.md b/docs/winefix/configuration.md
index de2a45a..22bf894 100644
--- a/docs/winefix/configuration.md
+++ b/docs/winefix/configuration.md
@@ -21,6 +21,7 @@ WineFix uses plugin ID `winefix`. Settings are stored in `apl/config/winefix.tom
| `color_picker_sampling_mode` | enum | `native` | No | Controls how the color picker samples color values. See [Sampling Modes](index.md#color-picker-sampling-modes). |
| `canva_sign_in_helper` | bool | `true` | Yes | Patch the Canva sign-in dialog to include a helper textbox and instructions to complete sign-in without a protocol URL handler. |
| `command_line_file_open` | bool | `true` | Yes | Fix opening files from the command line or desktop file manager. Bypasses Affinity's broken `ProcessCommandLineArguments` which references a WinRT type missing in Wine. |
+| `sub_tool_columns_fix` | bool | `true` | Yes | Keep the sub-tool panel's own column count (two for more than ten sub-tools) after a studio switch, which otherwise gives it the main tool pane's. |
#### `color_picker_magnifier_fix`