fix(chat): CH6a/b rework — grip media, retail window-id model, floaty fixture
Applies docs/research/2026-08-10-ch6ab-review-findings.md in full:
- BLOCKER 1: UiResizeGrip now carries its ElementInfo/resolve pair and
draws its own authored DirectState media (a synthetic parameterless
grip still draws nothing, preserving existing resize-drag tests).
DatWidgetFactory.BuildResizeGrip threads resolve through. All seven
live grips on the main chat window now resolve a non-zero sprite,
restoring the visible borders/corners CH6a silently dropped.
- SHOULD-FIX 2: ChatWindowState gains BroadcastTargetWindow, a sentinel
distinct from every real window id (0-4), fixing the bug where the
main window's explicit-addressing branch coincided with the broadcast
check (both were literal 0). SetFilter's main-window no-op is dropped
— the main window's filter is now genuinely settable. ChatWindowController
.Bind takes a ChatWindowState (the same canonical instance the floating
windows already share) and GetTranscriptLines builds a real accept
predicate instead of accept:null. Verified safe: ClientLocal (0x1A)
never reaches ChatLog (AddText routes it to the SpewBox and returns),
so nothing observable regresses.
- SHOULD-FIX 3: UiButton.SuppressSelfToggle stops the four chat-window
indicator buttons (DAT property 0x0B=true, no retail click handler)
from flipping their own Selected mirror on a stray click.
- SHOULD-FIX 4: generated and committed chat_floaty_2100005b.json from
the real installed dats; added the permanent RetailLayoutFixtureGenerator
entry. All three flagged FloatingChatWindowController assumptions
(input field, title bar, close button) are confirmed correct against
real data — no controller code changes needed. New finding: unlike the
main window, ALL EIGHT floaty border/corner elements are live Type-9
grips (the floaty's own title bar is its move handle), so a floaty
window resizes from every edge and corner.
- SHOULD-FIX 5: register row AP-189 documents the shared-500-entry/
200-line-tail vs retail's per-window 10,000-line scrollback depth gap.
- NITs 1-5: documented the filter-persistence-only-on-/saveautoui
asymmetry and the reconnect-preserves-filters intent; corrected the
research doc's modifier-mask mislabel and the "ONLY function" false
superlative; moved WrapText off ChatWindowController onto
ChatTranscriptRenderer, closing the circular dependency.
Full Release suite: 12,420 passed / 4 skipped / 0 failed (baseline
12,392/4/0 at 22020ef2; net +28 tests, zero regressions).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
56b84deeab
commit
1aa7709988
23 changed files with 6284 additions and 248 deletions
|
|
@ -1,3 +1,7 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using AcDream.App.UI.Layout;
|
||||
|
||||
namespace AcDream.App.UI;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -23,10 +27,19 @@ namespace AcDream.App.UI;
|
|||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// A grip is a small (5px) leaf with no children and no authored background
|
||||
/// media beyond its own cursor-feedback sprite; it exists purely as a precise
|
||||
/// hit region. <see cref="AcDream.App.UI.UiRoot"/> gives a directly-hit grip's
|
||||
/// own <see cref="Edges"/> priority over the generic proximity-based
|
||||
/// A grip is a small (5px) leaf with no children; it exists primarily as a
|
||||
/// precise hit region, but it IS authored with real border/corner media (the
|
||||
/// <c>0x06006129</c>-family sprites — CH6a/b REJECT-review BLOCKER 1,
|
||||
/// <c>docs/research/2026-08-10-ch6ab-review-findings.md</c>: every one of the
|
||||
/// seven live grips on the main chat window carries a non-zero DirectState
|
||||
/// sprite, and drawing nothing left the window with only its 400×5 top strip
|
||||
/// visible). A factory-built grip (<see cref="AcDream.App.UI.Layout.DatWidgetFactory.Create"/>,
|
||||
/// Type 9) therefore draws its own DirectState media exactly like
|
||||
/// <see cref="AcDream.App.UI.Layout.UiDatElement"/> would; a synthetic grip
|
||||
/// built with the parameterless constructor (unit tests exercising only the
|
||||
/// resize-drag behavior) carries no media and draws nothing, matching its
|
||||
/// prior behavior. <see cref="AcDream.App.UI.UiRoot"/> gives a directly-hit
|
||||
/// grip's own <see cref="Edges"/> priority over the generic proximity-based
|
||||
/// <see cref="AcDream.App.UI.UiRoot.HitEdges"/> heuristic, so e.g. the chat
|
||||
/// window's top-left/top-right CORNER grips resize (including the Y/top axis)
|
||||
/// while the plain top EDGE strip in between remains a pure move handle.
|
||||
|
|
@ -34,6 +47,57 @@ namespace AcDream.App.UI;
|
|||
/// </summary>
|
||||
public sealed class UiResizeGrip : UiElement
|
||||
{
|
||||
private readonly ElementInfo? _info;
|
||||
private readonly Func<uint, (uint tex, int w, int h)>? _resolve;
|
||||
|
||||
/// <summary>Synthetic grip with no authored media — used by unit tests that
|
||||
/// exercise only the resize-drag hit-testing/edge behavior.</summary>
|
||||
public UiResizeGrip()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factory-built grip (CH6a/b REJECT-review BLOCKER 1): carries its resolved
|
||||
/// <see cref="ElementInfo"/> and sprite resolver so it draws its own
|
||||
/// authored DirectState media, the same way every other dat-imported leaf
|
||||
/// does. <see cref="UiElement.ClickThrough"/> is left at the base
|
||||
/// <see langword="false"/> default — unlike <see cref="UiDatElement"/>'s
|
||||
/// decoration default of <see langword="true"/> — because a grip must stay
|
||||
/// hit-testable to capture the resize drag.
|
||||
/// </summary>
|
||||
public UiResizeGrip(ElementInfo info, Func<uint, (uint tex, int w, int h)> resolve)
|
||||
{
|
||||
_info = info;
|
||||
_resolve = resolve;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This grip's authored DirectState sprite file id (0 for a synthetic grip,
|
||||
/// or an authored grip with no media). Exposed for conformance tests —
|
||||
/// BLOCKER 1's regression guard is that every one of the seven live Type-9
|
||||
/// grips resolves a non-zero value here.
|
||||
/// </summary>
|
||||
public uint SpriteFile => _info is not null && _info.StateMedia.TryGetValue("", out var media)
|
||||
? media.File
|
||||
: 0u;
|
||||
|
||||
/// <summary>
|
||||
/// Draws this grip's own DirectState media exactly like
|
||||
/// <see cref="UiDatElement.OnDraw"/> — tiled at native size (Normal), same
|
||||
/// blend for Overlay/Alphablend since the sprite shader already
|
||||
/// alpha-blends. A synthetic (parameterless-constructed) grip has no
|
||||
/// resolver and draws nothing, matching its pre-BLOCKER-1 behavior.
|
||||
/// </summary>
|
||||
protected override void OnDraw(UiRenderContext ctx)
|
||||
{
|
||||
if (_resolve is null) return;
|
||||
uint file = SpriteFile;
|
||||
if (file == 0u) return;
|
||||
var (tex, tw, th) = _resolve(file);
|
||||
if (tex == 0 || tw == 0 || th == 0) return;
|
||||
ctx.DrawSprite(tex, 0, 0, Width, Height, 0, 0, Width / tw, Height / th, Vector4.One);
|
||||
}
|
||||
|
||||
/// <summary>Retail <c>BorderLocation</c> (<c>acclient.h:4327</c>):
|
||||
/// <c>BORDER_NONE=0, BORDER_UL=1, BORDER_TOP=2, BORDER_UR=3, BORDER_RIGHT=4,
|
||||
/// BORDER_LR=5, BORDER_BOTTOM=6, BORDER_LL=7, BORDER_LEFT=8</c>.</summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue