feat(D.2b): chat text selection + Ctrl-C copy

Windows-like selection in the retail chat window: left-click-drag selects
characters, Ctrl-C copies, Ctrl-A selects all. The selected span paints a
translucent highlight behind the text.

- UiElement.CapturesPointerDrag: a per-element opt-out so an interior drag is
  delivered to the widget (text selection) instead of moving/resizing the host
  window. UiRoot.OnMouseDown honours it AFTER edge-resize (a resizable window
  is still resizable from its frame) and BEFORE window-move.
- UiChatView: AcceptsFocus + IsEditControl + CapturesPointerDrag; caches the
  OnDraw layout so OnEvent hit-tests the same geometry; HitChar maps a local
  point to (line,col) with glyph-midpoint caret snapping; SelectedText joins a
  multi-line span with \n; Ctrl-C writes to IKeyboard.ClipboardText (only when
  non-empty, so an empty copy never clobbers the clipboard).
- UiHost exposes the wired IKeyboard (clipboard + Ctrl modifier state).

Adversarial-review fix (the 99 tests would have stayed green without it): a
coordinate-frame mismatch between MouseDown and MouseMove. UiRoot.OnMouseDown
dispatched HitTestTopDown's coords, which are relative to the TOP-LEVEL child,
while MouseMove/MouseUp use target.ScreenPosition. For the chat view inset at
(8,8) inside its window the anchor landed ~8px off the click. OnMouseDown now
delivers target-LOCAL coords like the other mouse events. Added a UiRoot
regression test asserting MouseDown and MouseMove share the target-local frame
for a nested child.

Decomp ref: SurfaceWindow text/selection model; clipboard via Silk.NET
IKeyboard.ClipboardText. Built with the chat-select-copy implement->review
workflow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-14 23:21:28 +02:00
parent 36bd3522f4
commit 4e60c03a74
7 changed files with 507 additions and 12 deletions

View file

@ -1812,6 +1812,9 @@ public sealed class GameWindow : IDisposable
| AcDream.App.UI.AnchorEdges.Right | AcDream.App.UI.AnchorEdges.Bottom,
Font = _debugFont,
LinesProvider = () => BuildRetailChatLines(retailChatVm),
// Drag-select + Ctrl+C copy need the keyboard for clipboard +
// modifier state. UiHost.Keyboard is set during WireKeyboard above.
Keyboard = _uiHost.Keyboard,
};
chatWindow.AddChild(chatView);
_uiHost.Root.AddChild(chatWindow);