fix(D.2b): Slice 2 — buttons inside a whole-window-Draggable frame get their Click

Visual gate 2 (user): the "Slots" toggle caption was visible but unclickable.

Root cause (UiRoot.OnMouseDown/OnMouseUp): a left-press on a non-drag-source
widget inside a whole-window-Draggable frame (the inventory window's IA-12
drag) set _windowDragTarget; OnMouseUp then early-returned before emitting the
Click. So the paperdoll Slots button (the first plain button inside the
draggable inventory frame) never received its click. Chat/toolbar buttons
escape this — their frames aren't whole-window-draggable.

Fix (toolkit, root cause not band-aid): add UiElement.HandlesClick (a virtual
opt-out parallel to IsDragSource); UiButton overrides it true; OnMouseDown
routes a HandlesClick press to the widget (like CapturesPointerDrag) instead of
the window-drag, so OnMouseUp emits the Click. 2 regression tests lock it
(HandlesClick widget in a Draggable frame emits Click; a plain one doesn't).

Build + full App suite green (596, +2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-23 10:08:59 +02:00
parent fe319bd2aa
commit 8ee3d89feb
4 changed files with 67 additions and 5 deletions

View file

@ -14,6 +14,53 @@ public class UiRootInputTests
Assert.Equal(AnchorEdges.None, panel.Anchors);
}
private sealed class ClickRecorder : UiElement
{
private readonly bool _handlesClick;
public bool Clicked;
public ClickRecorder(bool handlesClick) => _handlesClick = handlesClick;
public override bool HandlesClick => _handlesClick;
public override bool OnEvent(in UiEvent e)
{
if (e.Type == UiEventType.Click) { Clicked = true; return true; }
return false;
}
}
[Fact]
public void HandlesClickWidget_insideDraggableWindow_stillEmitsClick()
{
// Regression (paperdoll "Slots" toggle): a HandlesClick widget (e.g. a UiButton) inside a
// whole-window-Draggable frame (the inventory window, IA-12 whole-window-drag) must still
// receive its Click. Before the fix the window-move branch captured the press and OnMouseUp
// returned before emitting Click, so the toggle button did nothing.
var root = new UiRoot { Width = 800, Height = 600 };
var frame = new UiPanel { Left = 10, Top = 300, Width = 200, Height = 60, Draggable = true };
var btn = new ClickRecorder(handlesClick: true) { Left = 5, Top = 5, Width = 120, Height = 14 };
frame.AddChild(btn);
root.AddChild(frame);
root.OnMouseDown(UiMouseButton.Left, 20, 310); // press over the button (screen rect 15,305..135,319)
root.OnMouseUp(UiMouseButton.Left, 20, 310); // release same spot → Click
Assert.True(btn.Clicked);
}
[Fact]
public void PlainWidget_insideDraggableWindow_doesNotEmitClick()
{
// Contrast that locks the distinction: a NON-HandlesClick child inside the Draggable frame is
// captured as a whole-window-drag, so no Click is emitted (the frame is draggable by it).
var root = new UiRoot { Width = 800, Height = 600 };
var frame = new UiPanel { Left = 10, Top = 300, Width = 200, Height = 60, Draggable = true };
var plain = new ClickRecorder(handlesClick: false) { Left = 5, Top = 5, Width = 120, Height = 14 };
frame.AddChild(plain);
root.AddChild(frame);
root.OnMouseDown(UiMouseButton.Left, 20, 310);
root.OnMouseUp(UiMouseButton.Left, 20, 310);
Assert.False(plain.Clicked);
}
private sealed class CoordRecorder : UiElement
{
public (int x, int y)? Down, Move;