diff --git a/HellionChat/SelfTests/MainWindowFlagsStep.cs b/HellionChat/SelfTests/MainWindowFlagsStep.cs index 7a96ab9..9462c39 100644 --- a/HellionChat/SelfTests/MainWindowFlagsStep.cs +++ b/HellionChat/SelfTests/MainWindowFlagsStep.cs @@ -34,7 +34,11 @@ internal sealed class MainWindowFlagsStep : ISelfTestStep // 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); + var expected = MainWindow.ResolveFlags( + Plugin.Config.CanMove, + Plugin.Config.CanResize, + Plugin.Config.ShowTitleBar + ); if (window.Flags != expected) { ImGui.Text($"PreDraw set Flags {window.Flags}, expected ResolveFlags = {expected}"); @@ -43,7 +47,7 @@ internal sealed class MainWindowFlagsStep : ISelfTestStep } // Fresh-base contract: locked window carries NoMove|NoResize ... - var locked = MainWindow.ResolveFlags(false, false); + var locked = MainWindow.ResolveFlags(false, false, true); if ( !locked.HasFlag(ImGuiWindowFlags.NoMove) || !locked.HasFlag(ImGuiWindowFlags.NoResize) @@ -51,17 +55,35 @@ internal sealed class MainWindowFlagsStep : ISelfTestStep ) { ImGui.Text( - $"ResolveFlags(false,false) = {locked}, missing NoMove/NoResize/NoScrollbar" + $"ResolveFlags(false,false,true) = {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); + var free = MainWindow.ResolveFlags(true, true, true); if (free.HasFlag(ImGuiWindowFlags.NoMove) || free.HasFlag(ImGuiWindowFlags.NoResize)) { - ImGui.Text($"ResolveFlags(true,true) = {free}, NoMove/NoResize stuck after re-enable"); + ImGui.Text( + $"ResolveFlags(true,true,true) = {free}, NoMove/NoResize stuck after re-enable" + ); + window.Flags = savedFlags; + return SelfTestStepResult.Fail; + } + + // P7 title-bar contract: ShowTitleBar=false adds NoTitleBar from the + // fresh base, true clears it (same no-accumulation guarantee). + var barHidden = MainWindow.ResolveFlags(true, true, false); + var barShown = MainWindow.ResolveFlags(true, true, true); + if ( + !barHidden.HasFlag(ImGuiWindowFlags.NoTitleBar) + || barShown.HasFlag(ImGuiWindowFlags.NoTitleBar) + ) + { + ImGui.Text( + $"NoTitleBar wiring wrong: hidden={barHidden} (want NoTitleBar), shown={barShown} (want none)" + ); window.Flags = savedFlags; return SelfTestStepResult.Fail; } diff --git a/HellionChat/Ui/Components/InputBar.cs b/HellionChat/Ui/Components/InputBar.cs index 1552b66..f3c13e4 100644 --- a/HellionChat/Ui/Components/InputBar.cs +++ b/HellionChat/Ui/Components/InputBar.cs @@ -540,15 +540,21 @@ internal sealed class InputBar ImGui.SetTooltip("Settings"); } - ImGui.SameLine(); - var hidden = Plugin.Config.HideChat; - var visIcon = hidden ? FontAwesomeIcon.EyeSlash : FontAwesomeIcon.Eye; - if (ImGui.Button(visIcon.ToIconString())) - Plugin.Config.HideChat = !hidden; - if (ImGui.IsItemHovered()) + // Hide button gated on ShowHideButton (1.5.6 parity). It is the last + // button in the row, so skipping it (with its leading SameLine) leaves + // no dangling SameLine. Shared by main + pop-out InputBars. + if (Plugin.Config.ShowHideButton) { - using (ImRaii.DefaultFont()) - ImGui.SetTooltip(hidden ? "Unhide chat" : "Hide chat"); + ImGui.SameLine(); + var hidden = Plugin.Config.HideChat; + var visIcon = hidden ? FontAwesomeIcon.EyeSlash : FontAwesomeIcon.Eye; + if (ImGui.Button(visIcon.ToIconString())) + Plugin.Config.HideChat = !hidden; + if (ImGui.IsItemHovered()) + { + using (ImRaii.DefaultFont()) + ImGui.SetTooltip(hidden ? "Unhide chat" : "Hide chat"); + } } } } diff --git a/HellionChat/Ui/Components/Settings/Tabs/ChatTab.cs b/HellionChat/Ui/Components/Settings/Tabs/ChatTab.cs index 96e8e01..647d11a 100644 --- a/HellionChat/Ui/Components/Settings/Tabs/ChatTab.cs +++ b/HellionChat/Ui/Components/Settings/Tabs/ChatTab.cs @@ -38,6 +38,11 @@ internal sealed class ChatTab () => Plugin.Config.HideSameTimestamps, v => Plugin.Config.HideSameTimestamps = v ); + DrawToggle( + "24-hour clock", + () => Plugin.Config.Use24HourClock, + v => Plugin.Config.Use24HourClock = v + ); DrawWorldSuffixCombo(); DrawNameFormCombo(); } diff --git a/HellionChat/Ui/Components/Settings/Tabs/WindowTab.cs b/HellionChat/Ui/Components/Settings/Tabs/WindowTab.cs index 17638e0..e108486 100644 --- a/HellionChat/Ui/Components/Settings/Tabs/WindowTab.cs +++ b/HellionChat/Ui/Components/Settings/Tabs/WindowTab.cs @@ -28,6 +28,25 @@ internal sealed class WindowTab } } + if (ImGui.CollapsingHeader("Window style", ImGuiTreeNodeFlags.DefaultOpen)) + { + DrawToggle( + "Show title bar", + () => Plugin.Config.ShowTitleBar, + v => Plugin.Config.ShowTitleBar = v + ); + DrawToggle( + "Show title bar for pop-outs", + () => Plugin.Config.ShowPopOutTitleBar, + v => Plugin.Config.ShowPopOutTitleBar = v + ); + DrawToggle( + "Show hide button", + () => Plugin.Config.ShowHideButton, + v => Plugin.Config.ShowHideButton = v + ); + } + if (ImGui.CollapsingHeader("Opacity", ImGuiTreeNodeFlags.DefaultOpen)) { DrawSlider( diff --git a/HellionChat/Ui/Windows/ChannelPopoutWindow.cs b/HellionChat/Ui/Windows/ChannelPopoutWindow.cs index 0a37d21..606f82c 100644 --- a/HellionChat/Ui/Windows/ChannelPopoutWindow.cs +++ b/HellionChat/Ui/Windows/ChannelPopoutWindow.cs @@ -75,6 +75,17 @@ internal sealed class ChannelPopoutWindow : Window IsOpen = false; } + public override void PreDraw() + { + // Gate the native title bar on the user toggle (1.5.6 parity). DrawHeader + // carries the close button in-body regardless, so hiding the title bar + // never strands the pop-out. Reset from a fresh base each frame so + // toggling the bar back on clears NoTitleBar. + Flags = Plugin.Config.ShowPopOutTitleBar + ? ImGuiWindowFlags.None + : ImGuiWindowFlags.NoTitleBar; + } + public override void Draw() { if (Bound is null) diff --git a/HellionChat/Ui/Windows/MainWindow.cs b/HellionChat/Ui/Windows/MainWindow.cs index 90b1032..310c8e0 100644 --- a/HellionChat/Ui/Windows/MainWindow.cs +++ b/HellionChat/Ui/Windows/MainWindow.cs @@ -77,19 +77,21 @@ 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) + // B1-2 / P7: rebuild flags from a fresh base every frame so toggling + // CanMove/CanResize/ShowTitleBar back on actually CLEARS NoMove/NoResize/ + // NoTitleBar (not accumulating). Move/resize/title-bar logic as 1.5.6 + // (ChatLogWindow.PreOpenCheck 1d3b429:703-710); base flags = today's + // MainWindow set (NoScrollbar|NoScrollWithMouse — the message list owns its + // own scroll; 1.5.6's NoFocusOnAppearing is deliberately not restored). + internal static ImGuiWindowFlags ResolveFlags(bool canMove, bool canResize, bool showTitleBar) { var flags = ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse; if (!canMove) flags |= ImGuiWindowFlags.NoMove; if (!canResize) flags |= ImGuiWindowFlags.NoResize; + if (!showTitleBar) + flags |= ImGuiWindowFlags.NoTitleBar; return flags; } @@ -114,7 +116,11 @@ internal sealed class MainWindow : Window BgAlpha = null; } - Flags = ResolveFlags(Plugin.Config.CanMove, Plugin.Config.CanResize); + Flags = ResolveFlags( + Plugin.Config.CanMove, + Plugin.Config.CanResize, + Plugin.Config.ShowTitleBar + ); } public Tab? ActiveTab => _activeTab;