From 2996c8ae83ec1954715ec624f77c3ecf5b05b560 Mon Sep 17 00:00:00 2001 From: jfacemyer <1776340+jfacemyer@users.noreply.github.com> Date: Fri, 16 Oct 2026 09:01:41 -0400 Subject: [PATCH] WineFix: keep the sub-tool panel's columns across a studio switch The sub-tool panel is a ToolTray like the main tool pane. Its column count comes from SubToolPanel.ToggleVisibility on every tool change (two for more than ten sub-tools), but a studio switch raises CurrentWorkspaceChanged, and every ToolTray handles that by setting Columns to the main tool pane's ToolPaneColumns. 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. After that handler, the sub-tool panel's tray gets its own rule back; the main tool pane is left alone. Checked with three Pixel/Layout round trips: two columns each time, one before. --- WineFix/Patches/SubToolColumnsPatch.cs | 95 ++++++++++++++++++++++++++ WineFix/WineFixPlugin.cs | 15 ++++ docs/winefix/configuration.md | 1 + 3 files changed, 111 insertions(+) create mode 100644 WineFix/Patches/SubToolColumnsPatch.cs 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`