feat(chat): Campaign CH slice CH6c — window opacity + transparency setting
Retail's ChatInterface::SetOpacity (0x004F3120) fades the WHOLE composited
window surface with one alpha; UiRenderContext.ApplyAlpha already gated
DrawSprite/DrawRect/DrawFill (since 1da697ec, pre-CH6) but DrawStringDat and
DrawString still passed applyAlpha:false, so text stayed sharp over a
translucent window. Both now route through the same chokepoint.
RetailWindowOpacityController (new) subscribes to a new
RetailWindowManager.WindowRegistered event and drives every registered
window's live Opacity from keyboard-focus state, applied to EVERY window
(chat, floaties, vitals, toolbar, ...) rather than retail's ChatInterface-only
scope — register row AP-190, retiring the stale AP-40 "fixed 0.75, no focus
transition" row in the same commit.
Verified retail's shipped opacity defaults from the decomp (constructor
literals, no cdb needed): the base ChatInterface ctor sets
DefaultOpacity=0.5/ActiveOpacity=1.0, kept unmodified by the four floating
windows; gmMainChatUI's own ctor overrides the main window to 1.0/1.0
(always fully opaque). acdream ships one shared global default (0.5/1.0)
rather than replicating the per-class override — also AP-190. The linking
invariant (raising default above active drags active UP; lowering active
below default drags default DOWN — never a clamp) is ported verbatim as
ChatOpacityLink in AcDream.UI.Abstractions, shared by the live controller
and the new Settings -> Chat tab's two linked opacity sliders.
Persistence: ChatSettings.DefaultOpacity/ActiveOpacity round-trip through
SettingsStore; Save pushes both through IRuntimeSettingsTargets.SetChatOpacity
into the live controller, no restart required.
Rider (CH6a/b re-review): strengthened the grip-media regression guard past
a bare SpriteFile != 0 check — ChatLayoutConformanceTests now drives each
live grip through a real UiRenderContext/TextRenderer (backed by the
in-memory RecordingGpuDevice test double) and asserts the draw call chain
actually queued sprite geometry, via a new TextRenderer.DebugSpriteSegments
test-only accessor.
Full Release suite 12,459 passed / 4 skipped / 0 failed (baseline
12,420/4/0). No subagents, no client launches (session hard constraints);
pending the next connected user gate for visual confirmation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
ccab53d9a1
commit
a819687cf0
23 changed files with 1174 additions and 38 deletions
|
|
@ -474,6 +474,70 @@ public sealed class SettingsPanelTests
|
|||
Assert.Equal(1f, (float)masterCall.Args[3]!);
|
||||
}
|
||||
|
||||
// -- Campaign CH slice CH6c: chat tab opacity sliders -----------------
|
||||
|
||||
[Fact]
|
||||
public void Chat_tab_when_active_renders_two_linked_opacity_sliders()
|
||||
{
|
||||
var (panel, vm, _, _) = Build();
|
||||
var r = new FakePanelRenderer { ActiveTabLabel = "Chat" };
|
||||
|
||||
panel.Render(new PanelContext(0.016f, new NullBus()), r);
|
||||
|
||||
var sliders = r.Calls.Where(c => c.Method == "SliderFloat")
|
||||
.Select(c => (string)c.Args[0]!).ToList();
|
||||
Assert.Contains("Background opacity (unfocused)", sliders);
|
||||
Assert.Contains("Active opacity (typing / focused)", sliders);
|
||||
|
||||
var bgCall = r.Calls.First(
|
||||
c => c.Method == "SliderFloat" && (string)c.Args[0]! == "Background opacity (unfocused)");
|
||||
Assert.Equal(vm.ChatDraft.DefaultOpacity, (float)bgCall.Args[1]!);
|
||||
Assert.Equal(0f, (float)bgCall.Args[2]!);
|
||||
Assert.Equal(1f, (float)bgCall.Args[3]!);
|
||||
|
||||
var activeCall = r.Calls.First(
|
||||
c => c.Method == "SliderFloat" && (string)c.Args[0]! == "Active opacity (typing / focused)");
|
||||
Assert.Equal(vm.ChatDraft.ActiveOpacity, (float)activeCall.Args[1]!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Chat_tab_opacity_sliders_do_not_render_when_a_different_tab_is_active()
|
||||
{
|
||||
var (panel, _, _, _) = Build();
|
||||
var r = new FakePanelRenderer { ActiveTabLabel = "Display" };
|
||||
|
||||
panel.Render(new PanelContext(0.016f, new NullBus()), r);
|
||||
|
||||
var sliders = r.Calls.Where(c => c.Method == "SliderFloat")
|
||||
.Select(c => (string)c.Args[0]!).ToList();
|
||||
Assert.DoesNotContain("Background opacity (unfocused)", sliders);
|
||||
Assert.DoesNotContain("Active opacity (typing / focused)", sliders);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Chat_tab_dragging_active_opacity_below_background_drags_background_down_in_draft()
|
||||
{
|
||||
// FakePanelRenderer applies ONE injected value to every SliderFloat call
|
||||
// in the same Render pass and each branch's `_vm.SetChat` starts from the
|
||||
// ORIGINAL pre-render draft — so with SliderFloatNextReturn set, the
|
||||
// LAST-rendered opacity slider ("Active", rendered after "Background")
|
||||
// determines the final draft. That exercises ChatOpacityLink.SetActive's
|
||||
// drag-background-down path end-to-end through the real panel code,
|
||||
// starting from ChatSettings.Default (DefaultOpacity=0.5, ActiveOpacity=1.0).
|
||||
var (panel, vm, _, _) = Build();
|
||||
var r = new FakePanelRenderer
|
||||
{
|
||||
ActiveTabLabel = "Chat",
|
||||
SliderFloatNextReturn = true,
|
||||
SliderFloatNextValue = 0.2f,
|
||||
};
|
||||
|
||||
panel.Render(new PanelContext(0.016f, new NullBus()), r);
|
||||
|
||||
Assert.Equal(0.2f, vm.ChatDraft.DefaultOpacity);
|
||||
Assert.Equal(0.2f, vm.ChatDraft.ActiveOpacity);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Save_Cancel_buttons_render_outside_the_tab_bar()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue