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
|
|
@ -1,3 +1,7 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.App.Rendering.Gpu;
|
||||
using AcDream.App.Tests.Rendering.Gpu;
|
||||
using AcDream.App.UI;
|
||||
using AcDream.App.UI.Layout;
|
||||
using AcDream.Core.Chat;
|
||||
|
|
@ -360,6 +364,53 @@ public class ChatLayoutConformanceTests
|
|||
Assert.NotEqual(0u, grip.SpriteFile);
|
||||
}
|
||||
|
||||
private sealed class NullGpuFrameSource : ICurrentGpuFrameSource
|
||||
{
|
||||
public IGpuFrame? CurrentFrame => null;
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0x1000069Bu)] // TL corner
|
||||
[InlineData(0x1000069Du)] // TR corner
|
||||
[InlineData(0x1000069Eu)] // left edge
|
||||
[InlineData(0x1000069Fu)] // BL corner
|
||||
[InlineData(0x100006A0u)] // bottom edge
|
||||
[InlineData(0x100006A1u)] // BR corner
|
||||
[InlineData(0x100006A2u)] // right edge
|
||||
public void MountedChatWindow_LiveGrip_ActuallyEmitsASpriteDraw_NotJustResolvesSpriteFile(uint elementId)
|
||||
{
|
||||
// CH6a/b re-review rider: MountedChatWindow_LiveGrip_ResolvesNonZeroSprite
|
||||
// (above) only proves the ElementInfo carries a non-zero DirectState
|
||||
// sprite id — it never calls OnDraw, so a media-less regression (a grip
|
||||
// constructed WITHOUT its resolve delegate, or one whose resolve always
|
||||
// returns a zero handle/dimension — exactly the CH6a/b BLOCKER 1 bug)
|
||||
// would still pass it. This drives the grip through a REAL
|
||||
// UiRenderContext over a REAL TextRenderer (backed by the in-memory
|
||||
// RecordingGpuDevice test double, no live GPU) and asserts the draw
|
||||
// call chain actually queued sprite geometry for that grip's texture.
|
||||
var infos = FixtureLoader.LoadChatInfos();
|
||||
// Distinct from FixtureLoader's own null-returning resolver: echoes the
|
||||
// sprite id as a nonzero fake texture handle with nonzero dimensions,
|
||||
// so UiResizeGrip.OnDraw's `tex == 0 || tw == 0 || th == 0` guard does
|
||||
// not short-circuit before reaching ctx.DrawSprite.
|
||||
var layout = LayoutImporter.Build(infos, id => (id, 8, 8), null);
|
||||
var grip = Assert.IsType<UiResizeGrip>(layout.FindElement(elementId));
|
||||
Assert.NotEqual(0u, grip.SpriteFile);
|
||||
Assert.True(grip.Visible);
|
||||
|
||||
var device = new RecordingGpuDevice();
|
||||
var renderer = new TextRenderer(device, new NullGpuFrameSource(), "unused");
|
||||
renderer.Begin(new Vector2(800f, 600f));
|
||||
var ctx = new UiRenderContext(renderer, new Vector2(800f, 600f));
|
||||
|
||||
grip.DrawSelfAndChildren(ctx);
|
||||
|
||||
var seg = Assert.Single(
|
||||
renderer.DebugSpriteSegments,
|
||||
s => s.Texture == grip.SpriteFile);
|
||||
Assert.True(seg.VertexCount > 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MountedChatWindow_BottomRightGrip_GrowsBothAxes_NotOnlyShrinks()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue