feat(window): wire inactive opacity to main window focus state

This commit is contained in:
2026-05-30 16:57:59 +02:00
parent 7792b327dc
commit 336f722eef
3 changed files with 88 additions and 0 deletions
+1
View File
@@ -373,6 +373,7 @@ public sealed class Plugin : IAsyncDalamudPlugin
new SelfTests.HoverSheenAllocStep(this), new SelfTests.HoverSheenAllocStep(this),
new SelfTests.HonorificHeaderRenderStep(this), new SelfTests.HonorificHeaderRenderStep(this),
new SelfTests.PerformanceBaselineStep(this), new SelfTests.PerformanceBaselineStep(this),
new SelfTests.MainWindowFocusOpacityStep(this),
]); ]);
// Re-surface the wizard for existing users when a major UX // Re-surface the wizard for existing users when a major UX
@@ -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() { }
}
+33
View File
@@ -37,6 +37,9 @@ internal sealed class MainWindow : Window
public Vector2 LastWindowSize { get; private set; } = Vector2.Zero; public Vector2 LastWindowSize { get; private set; } = Vector2.Zero;
internal unsafe ImGuiViewport* LastViewport; internal unsafe ImGuiViewport* LastViewport;
// 1.5.6 viewport-guard input: tracked in Draw, read by PreDraw next frame.
private bool _wasDocked;
public MainWindow( public MainWindow(
Components.HonorificHeader honorific, Components.HonorificHeader honorific,
Components.Sidebar sidebar, Components.Sidebar sidebar,
@@ -70,6 +73,35 @@ internal sealed class MainWindow : Window
RespectCloseHotkey = false; 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; public Tab? ActiveTab => _activeTab;
// Internal accessors for self-tests so the probes can reach the live // 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; LastViewport = ImGui.GetWindowViewport().Handle;
} }
_wasDocked = ImGui.IsWindowDocked();
// Primary pool-reset path; InputPreview has a defensive fallback for the MainWindow-closed edge case. // Primary pool-reset path; InputPreview has a defensive fallback for the MainWindow-closed edge case.
_handlerLender.ResetCounter(); _handlerLender.ResetCounter();