diff --git a/HellionChat/Plugin.cs b/HellionChat/Plugin.cs index 60952aa..9204b5c 100755 --- a/HellionChat/Plugin.cs +++ b/HellionChat/Plugin.cs @@ -373,6 +373,7 @@ public sealed class Plugin : IAsyncDalamudPlugin new SelfTests.HoverSheenAllocStep(this), new SelfTests.HonorificHeaderRenderStep(this), new SelfTests.PerformanceBaselineStep(this), + new SelfTests.MainWindowFocusOpacityStep(this), ]); // Re-surface the wizard for existing users when a major UX diff --git a/HellionChat/SelfTests/MainWindowFocusOpacityStep.cs b/HellionChat/SelfTests/MainWindowFocusOpacityStep.cs new file mode 100644 index 0000000..0076e0a --- /dev/null +++ b/HellionChat/SelfTests/MainWindowFocusOpacityStep.cs @@ -0,0 +1,54 @@ +using Dalamud.Bindings.ImGui; +using Dalamud.Plugin.SelfTest; + +namespace HellionChat.SelfTests; + +// UI-12 focus opacity. Pins the pure ResolveBgAlpha contract (focused → +// WindowOpacity, unfocused → WindowOpacityInactive). The PreDraw wiring +// (BgAlpha = ResolveBgAlpha(IsFocused) behind the main-viewport/!docked guard) +// is NOT headless-deterministic — the guard may leave BgAlpha null when +// LastViewport is stale on a /xlperf frame — so the wiring is verified by the +// reviewer grep (ResolveBgAlpha has a non-test caller: MainWindow.PreDraw) and +// the visible transparency by in-game smoke, not by driving PreDraw here. +internal sealed class MainWindowFocusOpacityStep : ISelfTestStep +{ + private readonly Plugin plugin; + + public MainWindowFocusOpacityStep(Plugin plugin) + { + this.plugin = plugin; + } + + public string Name => "Hellion Chat - MainWindow focus opacity"; + + public SelfTestStepResult RunStep() + { + var window = this.plugin.MainWindow; + if (window is null) + { + ImGui.Text("Plugin.MainWindow is null"); + return SelfTestStepResult.Fail; + } + + // Contract: focused returns the focused opacity, unfocused the inactive one. + if (window.ResolveBgAlpha(true) != Plugin.Config.WindowOpacity) + { + ImGui.Text( + $"ResolveBgAlpha(true) = {window.ResolveBgAlpha(true)}, expected {Plugin.Config.WindowOpacity}" + ); + return SelfTestStepResult.Fail; + } + + if (window.ResolveBgAlpha(false) != Plugin.Config.WindowOpacityInactive) + { + ImGui.Text( + $"ResolveBgAlpha(false) = {window.ResolveBgAlpha(false)}, expected {Plugin.Config.WindowOpacityInactive}" + ); + return SelfTestStepResult.Fail; + } + + return SelfTestStepResult.Pass; + } + + public void CleanUp() { } +} diff --git a/HellionChat/Ui/Windows/MainWindow.cs b/HellionChat/Ui/Windows/MainWindow.cs index d9251ba..cecf4d2 100644 --- a/HellionChat/Ui/Windows/MainWindow.cs +++ b/HellionChat/Ui/Windows/MainWindow.cs @@ -37,6 +37,9 @@ internal sealed class MainWindow : Window public Vector2 LastWindowSize { get; private set; } = Vector2.Zero; internal unsafe ImGuiViewport* LastViewport; + // 1.5.6 viewport-guard input: tracked in Draw, read by PreDraw next frame. + private bool _wasDocked; + public MainWindow( Components.HonorificHeader honorific, Components.Sidebar sidebar, @@ -70,6 +73,35 @@ internal sealed class MainWindow : Window RespectCloseHotkey = false; } + // UI-12: per-window focus-dependent opacity. ResolveBgAlpha stays guard-free + // and pure so the self-test can drive it directly; PreDraw owns the guard + + // wiring. 1.5.6 parity (focused → WindowOpacity, unfocused → + // WindowOpacityInactive, ChatLogWindow.PreOpenCheck 1d3b429:724). + internal float ResolveBgAlpha(bool isFocused) => + isFocused ? Plugin.Config.WindowOpacity : Plugin.Config.WindowOpacityInactive; + + public override void PreDraw() + { + // Dalamud's WindowHost turns Window.BgAlpha into SetNextWindowBgAlpha + // (WindowHost.cs:650-652), which REPLACES this one window's WindowBg + // alpha (imgui.cpp:7229). The global GlobalStyleScope clamp is left + // untouched, so Settings/DbViewer/popouts/wizard keep today's opacity. + // Viewport guard (1.5.6 parity, ChatLogWindow.PreOpenCheck 1d3b429:718): + // only drive BgAlpha while the window is on the main viewport and not + // docked. On a floated own-viewport (Dalamud multi-viewport mode) the + // WindowBg alpha would compose against the OS-layer alpha (double + // transparency), so leave BgAlpha null there and let the global scope + // govern. LastViewport/_wasDocked are last frame's values from Draw + // (one-frame latency, accepted, matches 1.5.6). + unsafe + { + if (LastViewport == ImGuiHelpers.MainViewport.Handle && !_wasDocked) + BgAlpha = ResolveBgAlpha(IsFocused); + else + BgAlpha = null; + } + } + public Tab? ActiveTab => _activeTab; // Internal accessors for self-tests so the probes can reach the live @@ -99,6 +131,7 @@ internal sealed class MainWindow : Window { LastViewport = ImGui.GetWindowViewport().Handle; } + _wasDocked = ImGui.IsWindowDocked(); // Primary pool-reset path; InputPreview has a defensive fallback for the MainWindow-closed edge case. _handlerLender.ResetCounter();