feat(D.2b): UiField (Type 3) — editable input as a generic field; remove the stray Type-12 input placeholder (widget-generalization Task 6)

- Rename UiChatInput → UiField (UIElement_Field, RegisterElementClass(3) @ :126190);
  update doc to cite retail's CatchDroppedItem/MouseOverTop drag-drop hooks for
  future item windows. BackgroundColor default → transparent (controller sets
  the translucent 0.35α value explicitly, matching UiText pattern).
- Register Type 3 in DatWidgetFactory.Create: `3 => new UiField()`.
- ChatWindowController.Bind (Variant B): factory now builds 0x10000016 as an
  invisible UiText placeholder (Type 12); Bind removes that placeholder via
  FindElement(InputId).Parent.RemoveChild and places a UiField at the same rect.
  Result: exactly ONE input widget in the input bar, no stray UiText duplicate.
- Input property type changed from UiChatInput to UiField; GameWindow.cs:1861
  UiField.Keyboard assignment compiles unchanged (field exists).
- Tests: UiChatInputTests → UiFieldTests (class + all ctor refs renamed);
  DatWidgetFactoryTests: new Type3_Field_MakesUiField test; ChatWindowControllerTests:
  updated stale "skipped by factory" comments; LayoutConformanceTests: updated
  VitalsTree_ChromeCornerHasExpectedSprite — Type-3 chrome-corner elements are
  now UiField (sprite rendering for Type-3 dat image elements is a known
  limitation, tracked for post-Task-8 UiField.BackgroundSprite follow-up).
- Full suite: 404 passed, 2 skipped, 0 failed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-16 17:48:51 +02:00
parent cb082b59e4
commit e059a3f6ef
7 changed files with 72 additions and 44 deletions

View file

@ -14,15 +14,14 @@ namespace AcDream.App.UI.Layout;
/// analogue of retail <c>ChatInterface</c> + <c>gmMainChatUI::PostInit @0x4ce130</c>.
///
/// <para>
/// The transcript (<c>0x10000011</c>) and input (<c>0x10000016</c>) are Type-0
/// elements whose base is a Type-12 prototype, so the importer factory skips them
/// (returns null). This controller reads their rects from the raw
/// <see cref="ElementInfo"/> tree (which contains everything) and adds the behavioral
/// widgets as children of their parent container widgets (transcript panel
/// <c>0x10000010</c> / input bar <c>0x10000013</c>) which ARE created as
/// <see cref="UiDatElement"/> nodes. The scrollbar track (<c>0x10000012</c>) is built
/// directly as a <see cref="UiScrollbar"/> by the factory (Type 11) and is bound in place
/// here. The channel menu (<c>0x10000014</c>) is still replaced with its behavioral counterpart.
/// The transcript (<c>0x10000011</c>) is Type-12 and is built as a <see cref="UiText"/>
/// by the factory; this controller binds its live data provider in place. The input
/// (<c>0x10000016</c>) is also Type-12, so the factory builds it as an invisible
/// <see cref="UiText"/> placeholder; this controller removes that placeholder and adds
/// a <see cref="UiField"/> at the same rect. The scrollbar track (<c>0x10000012</c>) is
/// built directly as a <see cref="UiScrollbar"/> by the factory (Type 11) and bound in
/// place. The channel menu (<c>0x10000014</c>) is built as <see cref="UiMenu"/> (Type 6)
/// and bound in place.
/// </para>
/// </summary>
public sealed class ChatWindowController
@ -37,7 +36,7 @@ public sealed class ChatWindowController
private const uint TrackId = 0x10000012u;
private const uint InputBarId = 0x10000013u;
private const uint MenuId = 0x10000014u;
private const uint InputId = 0x10000016u; // Type-12 prototype — skipped by factory
private const uint InputId = 0x10000016u; // Type-12 Text — factory builds UiText placeholder; Bind removes + replaces with UiField
private const uint SendId = 0x10000019u;
private const uint MaxMinId = 0x1000046Fu;
@ -68,7 +67,7 @@ public sealed class ChatWindowController
public UiText Transcript { get; private set; } = null!;
/// <summary>Editable chat input widget. Null until <see cref="Bind"/> succeeds.</summary>
public UiChatInput Input { get; private set; } = null!;
public UiField Input { get; private set; } = null!;
/// <summary>Scrollbar widget, driven by <see cref="Transcript"/>'s scroll model.</summary>
public UiScrollbar Scrollbar { get; private set; } = null!;
@ -160,9 +159,9 @@ public sealed class ChatWindowController
BitmapFont? debugFont,
Func<uint, (uint tex, int w, int h)> resolve)
{
// The transcript is now built as a UiText by the factory (Type 12 is no longer skipped).
// The input node (0x10000016) is still Type-12 based; find it in the raw ElementInfo
// tree to read its rect for the behavioral UiChatInput widget.
// The transcript is built as a UiText by the factory (Type 12).
// The input node (0x10000016) is also Type-12 → UiText, but the controller replaces
// it with a UiField. Read its rect from the raw ElementInfo tree first.
var iInfo = FindInfo(rootInfo, InputId);
// Their parent panels must exist as real widgets in the layout tree.
@ -214,8 +213,12 @@ public sealed class ChatWindowController
c.Transcript.LinesProvider = () => BuildLines(vm, c.Transcript, datFont, debugFont);
// ── Input ────────────────────────────────────────────────────────
// Place the behavioral input widget inside the input bar.
c.Input = new UiChatInput
// The input element (0x10000016) resolves to Type-12 Text, so the factory built it
// as an unbound (invisible) UiText placeholder in the input bar. The editable entry
// is a controller-placed UiField at the same rect — drop the placeholder, add the field.
if (layout.FindElement(InputId) is { Parent: { } inParent } inputPlaceholder)
inParent.RemoveChild(inputPlaceholder);
c.Input = new UiField
{
Left = iInfo.X,
Top = iInfo.Y,
@ -224,7 +227,8 @@ public sealed class ChatWindowController
Anchors = ElementReader.ToAnchors(iInfo.Left, iInfo.Top, iInfo.Right, iInfo.Bottom),
DatFont = datFont,
Font = debugFont,
SpriteResolve = resolve,
BackgroundColor = new Vector4(0f, 0f, 0f, 0.35f), // retail translucent unfocused field
SpriteResolve = resolve,
FocusFieldSprite = InputFocusField,
};
inputBar.AddChild(c.Input);

View file

@ -52,6 +52,7 @@ public static class DatWidgetFactory
UiElement e = info.Type switch
{
1 => new UiButton(info, resolve), // UIElement_Button (reg :125828)
3 => new UiField(), // UIElement_Field (reg :126190)
6 => new UiMenu(), // UIElement_Menu (reg :120163)
7 => BuildMeter(info, resolve, datFont), // UIElement_Meter
11 => new UiScrollbar(), // UIElement_Scrollbar (reg :124137)