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
|
|
@ -131,6 +131,15 @@ internal interface IRuntimeSettingsTargets
|
|||
/// <c>CharacterOption</c> id (e.g. <c>ListenToGeneralChat = 0x23</c>).
|
||||
/// </summary>
|
||||
void SetSingleCharacterOption(uint optionId, bool value);
|
||||
|
||||
/// <summary>
|
||||
/// Campaign CH slice CH6c (2026-08-10): pushes the Chat tab's two linked
|
||||
/// transparency sliders into the live <c>RetailWindowOpacityController</c> —
|
||||
/// local-only (no server round-trip, unlike <see cref="SetSingleCharacterOption"/>),
|
||||
/// applies with no restart, matches retail's own
|
||||
/// <c>UpdateFromPlayerModule</c> call order (default before active).
|
||||
/// </summary>
|
||||
void SetChatOpacity(float defaultOpacity, float activeOpacity);
|
||||
}
|
||||
|
||||
internal interface IRuntimeSettingsPreviewSource
|
||||
|
|
@ -541,6 +550,12 @@ internal sealed class RuntimeSettingsController :
|
|||
PublishHearOptionChange(
|
||||
previous.HearSocietyChat, chat.HearSocietyChat,
|
||||
(uint)CharacterOptionId.ListenToSocietyChat);
|
||||
|
||||
// CH6c: local-only live apply — unlike the Hear* options above, this never
|
||||
// touches the wire (retail's 0x1000008C blob remains unparsed, per the
|
||||
// window-shell research doc §4.4/§6.1). Always pushed (not diffed) so the
|
||||
// linking invariant self-heals even if only one field nominally changed.
|
||||
_runtimeTargets?.SetChatOpacity(chat.DefaultOpacity, chat.ActiveOpacity);
|
||||
}
|
||||
|
||||
private void PublishHearOptionChange(bool previous, bool current, uint optionId)
|
||||
|
|
|
|||
|
|
@ -35,6 +35,15 @@ internal interface IRuntimeUiLockTarget
|
|||
void Apply(bool locked);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign CH slice CH6c target seam for the Chat tab's transparency sliders,
|
||||
/// mirroring <see cref="IRuntimeUiLockTarget"/>'s shape.
|
||||
/// </summary>
|
||||
internal interface IRuntimeChatOpacityTarget
|
||||
{
|
||||
void Apply(float defaultOpacity, float activeOpacity);
|
||||
}
|
||||
|
||||
internal sealed class SilkRuntimeDisplayWindowTarget : IRuntimeDisplayWindowTarget
|
||||
{
|
||||
private readonly IWindow _window;
|
||||
|
|
@ -196,6 +205,29 @@ internal sealed class NullRuntimeUiLockTarget : IRuntimeUiLockTarget
|
|||
}
|
||||
}
|
||||
|
||||
internal sealed class RuntimeChatOpacityTarget(RetailWindowOpacityController controller)
|
||||
: IRuntimeChatOpacityTarget
|
||||
{
|
||||
private readonly RetailWindowOpacityController _controller =
|
||||
controller ?? throw new ArgumentNullException(nameof(controller));
|
||||
|
||||
public void Apply(float defaultOpacity, float activeOpacity) =>
|
||||
_controller.SetOpacity(defaultOpacity, activeOpacity);
|
||||
}
|
||||
|
||||
internal sealed class NullRuntimeChatOpacityTarget : IRuntimeChatOpacityTarget
|
||||
{
|
||||
public static NullRuntimeChatOpacityTarget Instance { get; } = new();
|
||||
|
||||
private NullRuntimeChatOpacityTarget()
|
||||
{
|
||||
}
|
||||
|
||||
public void Apply(float defaultOpacity, float activeOpacity)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Complete late-bound target for changes made after startup. Construction and
|
||||
/// binding are inert; only an explicit controller command mutates borrowers.
|
||||
|
|
@ -205,6 +237,7 @@ internal sealed class RuntimeSettingsTargets : IRuntimeSettingsTargets
|
|||
private readonly IRuntimeDisplayWindowTarget _displayWindow;
|
||||
private readonly IRuntimeQualityApplicationTarget _quality;
|
||||
private readonly IRuntimeUiLockTarget _uiLock;
|
||||
private readonly IRuntimeChatOpacityTarget _chatOpacity;
|
||||
private readonly ICommandBus _commands;
|
||||
private readonly Action<string> _log;
|
||||
|
||||
|
|
@ -216,6 +249,7 @@ internal sealed class RuntimeSettingsTargets : IRuntimeSettingsTargets
|
|||
WorldRenderRangeState renderRange,
|
||||
UiRoot? uiRoot,
|
||||
ICommandBus commands,
|
||||
RetailWindowOpacityController? chatOpacity = null,
|
||||
Action<string>? log = null)
|
||||
: this(
|
||||
displayWindow,
|
||||
|
|
@ -228,7 +262,10 @@ internal sealed class RuntimeSettingsTargets : IRuntimeSettingsTargets
|
|||
? NullRuntimeUiLockTarget.Instance
|
||||
: new RuntimeUiLockTarget(uiRoot),
|
||||
commands,
|
||||
log)
|
||||
log,
|
||||
chatOpacity is null
|
||||
? NullRuntimeChatOpacityTarget.Instance
|
||||
: new RuntimeChatOpacityTarget(chatOpacity))
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -237,12 +274,14 @@ internal sealed class RuntimeSettingsTargets : IRuntimeSettingsTargets
|
|||
IRuntimeQualityApplicationTarget quality,
|
||||
IRuntimeUiLockTarget uiLock,
|
||||
ICommandBus commands,
|
||||
Action<string>? log = null)
|
||||
Action<string>? log = null,
|
||||
IRuntimeChatOpacityTarget? chatOpacity = null)
|
||||
{
|
||||
_displayWindow = displayWindow
|
||||
?? throw new ArgumentNullException(nameof(displayWindow));
|
||||
_quality = quality ?? throw new ArgumentNullException(nameof(quality));
|
||||
_uiLock = uiLock ?? throw new ArgumentNullException(nameof(uiLock));
|
||||
_chatOpacity = chatOpacity ?? NullRuntimeChatOpacityTarget.Instance;
|
||||
_commands = commands ?? throw new ArgumentNullException(nameof(commands));
|
||||
_log = log ?? Console.WriteLine;
|
||||
}
|
||||
|
|
@ -281,4 +320,7 @@ internal sealed class RuntimeSettingsTargets : IRuntimeSettingsTargets
|
|||
/// </summary>
|
||||
public void SetSingleCharacterOption(uint optionId, bool value) =>
|
||||
_commands.Publish(new SetSingleCharacterOptionRuntimeCmd(optionId, value));
|
||||
|
||||
public void SetChatOpacity(float defaultOpacity, float activeOpacity) =>
|
||||
_chatOpacity.Apply(defaultOpacity, activeOpacity);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue