diff --git a/HellionChat/Plugin.cs b/HellionChat/Plugin.cs index 9204b5c..865b3dd 100755 --- a/HellionChat/Plugin.cs +++ b/HellionChat/Plugin.cs @@ -374,6 +374,7 @@ public sealed class Plugin : IAsyncDalamudPlugin new SelfTests.HonorificHeaderRenderStep(this), new SelfTests.PerformanceBaselineStep(this), new SelfTests.MainWindowFocusOpacityStep(this), + new SelfTests.MainWindowFlagsStep(this), ]); // Re-surface the wizard for existing users when a major UX diff --git a/HellionChat/SelfTests/MainWindowFlagsStep.cs b/HellionChat/SelfTests/MainWindowFlagsStep.cs new file mode 100644 index 0000000..7a96ab9 --- /dev/null +++ b/HellionChat/SelfTests/MainWindowFlagsStep.cs @@ -0,0 +1,74 @@ +using Dalamud.Bindings.ImGui; +using Dalamud.Plugin.SelfTest; +using HellionChat.Ui.Windows; + +namespace HellionChat.SelfTests; + +// B1-2 window flags. Drives the REAL MainWindow.PreDraw and asserts it wired +// Window.Flags to ResolveFlags(CanMove, CanResize), then pins the pure +// fresh-base contract: false/false adds NoMove|NoResize, true/true clears them +// (the masterplan's "flags must rebuild from a fresh base, else NoMove sticks +// after toggling back" risk). NoScrollbar|NoScrollWithMouse always present. +// Non-test caller of ResolveFlags: MainWindow.PreDraw. +internal sealed class MainWindowFlagsStep : ISelfTestStep +{ + private readonly Plugin plugin; + + public MainWindowFlagsStep(Plugin plugin) + { + this.plugin = plugin; + } + + public string Name => "Hellion Chat - MainWindow flags"; + + public SelfTestStepResult RunStep() + { + var window = this.plugin.MainWindow; + if (window is null) + { + ImGui.Text("Plugin.MainWindow is null"); + return SelfTestStepResult.Fail; + } + + // Wiring proof: drive the real PreDraw and confirm Flags == the helper's + // value for the live config. No state mutation needed. + var savedFlags = window.Flags; + window.PreDraw(); + var expected = MainWindow.ResolveFlags(Plugin.Config.CanMove, Plugin.Config.CanResize); + if (window.Flags != expected) + { + ImGui.Text($"PreDraw set Flags {window.Flags}, expected ResolveFlags = {expected}"); + window.Flags = savedFlags; + return SelfTestStepResult.Fail; + } + + // Fresh-base contract: locked window carries NoMove|NoResize ... + var locked = MainWindow.ResolveFlags(false, false); + if ( + !locked.HasFlag(ImGuiWindowFlags.NoMove) + || !locked.HasFlag(ImGuiWindowFlags.NoResize) + || !locked.HasFlag(ImGuiWindowFlags.NoScrollbar) + ) + { + ImGui.Text( + $"ResolveFlags(false,false) = {locked}, missing NoMove/NoResize/NoScrollbar" + ); + window.Flags = savedFlags; + return SelfTestStepResult.Fail; + } + + // ... and re-enabling both CLEARS NoMove|NoResize (no accumulation). + var free = MainWindow.ResolveFlags(true, true); + if (free.HasFlag(ImGuiWindowFlags.NoMove) || free.HasFlag(ImGuiWindowFlags.NoResize)) + { + ImGui.Text($"ResolveFlags(true,true) = {free}, NoMove/NoResize stuck after re-enable"); + window.Flags = savedFlags; + return SelfTestStepResult.Fail; + } + + window.Flags = savedFlags; + return SelfTestStepResult.Pass; + } + + public void CleanUp() { } +} diff --git a/HellionChat/Ui/Components/Settings/Tabs/GeneralTab.cs b/HellionChat/Ui/Components/Settings/Tabs/GeneralTab.cs index 55af53a..740ada3 100644 --- a/HellionChat/Ui/Components/Settings/Tabs/GeneralTab.cs +++ b/HellionChat/Ui/Components/Settings/Tabs/GeneralTab.cs @@ -15,16 +15,6 @@ internal sealed class GeneralTab { if (ImGui.CollapsingHeader("Behavior", ImGuiTreeNodeFlags.DefaultOpen)) { - DrawToggle( - "Allow window movement", - () => Plugin.Config.CanMove, - v => Plugin.Config.CanMove = v - ); - DrawToggle( - "Allow window resize", - () => Plugin.Config.CanResize, - v => Plugin.Config.CanResize = v - ); DrawToggle( "Reduce motion (no theme crossfade)", () => Plugin.Config.ReduceMotion, diff --git a/HellionChat/Ui/Components/Settings/Tabs/WindowTab.cs b/HellionChat/Ui/Components/Settings/Tabs/WindowTab.cs index 0da2b9c..17638e0 100644 --- a/HellionChat/Ui/Components/Settings/Tabs/WindowTab.cs +++ b/HellionChat/Ui/Components/Settings/Tabs/WindowTab.cs @@ -48,6 +48,11 @@ internal sealed class WindowTab if (ImGui.CollapsingHeader("Resize behavior", ImGuiTreeNodeFlags.DefaultOpen)) { + DrawToggle( + "Allow movement", + () => Plugin.Config.CanMove, + v => Plugin.Config.CanMove = v + ); DrawToggle( "Allow resize", () => Plugin.Config.CanResize, diff --git a/HellionChat/Ui/Windows/MainWindow.cs b/HellionChat/Ui/Windows/MainWindow.cs index cecf4d2..8df6df8 100644 --- a/HellionChat/Ui/Windows/MainWindow.cs +++ b/HellionChat/Ui/Windows/MainWindow.cs @@ -66,9 +66,6 @@ internal sealed class MainWindow : Window MinimumSize = new Vector2(MinWidth, MinHeight), MaximumSize = new Vector2(float.MaxValue, float.MaxValue), }; - // The message list owns its own scroll inside the body child; - // the outer window must not show a second scrollbar. - Flags = ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse; IsOpen = Plugin.Config.MainWindowOpen; RespectCloseHotkey = false; } @@ -80,6 +77,22 @@ internal sealed class MainWindow : Window internal float ResolveBgAlpha(bool isFocused) => isFocused ? Plugin.Config.WindowOpacity : Plugin.Config.WindowOpacityInactive; + // B1-2: rebuild flags from a fresh base every frame so toggling CanMove/ + // CanResize back on actually CLEARS NoMove/NoResize (not accumulating). + // Move/resize toggle logic as 1.5.6 (ChatLogWindow.PreOpenCheck + // 1d3b429:703-707); base flags = today's MainWindow set (NoScrollbar| + // NoScrollWithMouse — the message list owns its own scroll; 1.5.6's + // NoFocusOnAppearing/NoTitleBar are deliberately not restored). + internal static ImGuiWindowFlags ResolveFlags(bool canMove, bool canResize) + { + var flags = ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse; + if (!canMove) + flags |= ImGuiWindowFlags.NoMove; + if (!canResize) + flags |= ImGuiWindowFlags.NoResize; + return flags; + } + public override void PreDraw() { // Dalamud's WindowHost turns Window.BgAlpha into SetNextWindowBgAlpha @@ -100,6 +113,8 @@ internal sealed class MainWindow : Window else BgAlpha = null; } + + Flags = ResolveFlags(Plugin.Config.CanMove, Plugin.Config.CanResize); } public Tab? ActiveTab => _activeTab;