using System.Numerics; using AcDream.App.UI; namespace AcDream.App.Tests.UI; public class UiRootInputTests { [Fact] public void UiNineSlicePanel_IsNotAnchorManaged_SoUserMoveResizeSticks() { // Regression: the per-frame anchor pass must NOT reset a window's rect, // or move/resize get undone every frame. Windows are user-positioned. var panel = new UiNineSlicePanel(_ => ((uint)1, 32, 32)); Assert.Equal(AnchorEdges.None, panel.Anchors); } private sealed class CoordRecorder : UiElement { public (int x, int y)? Down, Move; public CoordRecorder() { CapturesPointerDrag = true; } public override bool OnEvent(in UiEvent e) { if (e.Type == UiEventType.MouseDown) { Down = (e.Data1, e.Data2); return true; } if (e.Type == UiEventType.MouseMove) { Move = (e.Data1, e.Data2); return true; } return false; } } [Fact] public void MouseDown_And_MouseMove_DeliverSameTargetLocalFrame_ForNestedChild() { // Regression (adversarial review): a nested child must receive target-LOCAL // coords on MouseDown AND MouseMove for the same physical point — otherwise // drag-select anchors ~(child offset) px off from where you click. Before the // fix MouseDown used HitTestTopDown's window-relative coords (50,40) while // MouseMove used target-local (42,32). var root = new UiRoot { Width = 800, Height = 600 }; var panel = new UiPanel { Left = 50, Top = 60, Width = 200, Height = 100 }; var child = new CoordRecorder { Left = 8, Top = 8, Width = 150, Height = 80 }; panel.AddChild(child); root.AddChild(panel); // child ScreenPosition = (58,68). Click screen (100,100) -> local (42,32). root.OnMouseDown(UiMouseButton.Left, 100, 100); Assert.Equal((42, 32), child.Down); // drag to (120,110) -> local (62,42); MUST share the MouseDown frame. root.OnMouseMove(120, 110); Assert.Equal((62, 42), child.Move); } [Fact] public void ApplyAnchor_None_IsNoOp() { var e = new UiPanel { Left = 50, Top = 60, Width = 100, Height = 40, Anchors = AnchorEdges.None }; e.ApplyAnchor(800, 600); Assert.Equal(50f, e.Left); Assert.Equal(60f, e.Top); Assert.Equal(100f, e.Width); Assert.Equal(40f, e.Height); } [Fact] public void WantsMouse_TrueOverWidget_FalseOverEmptySpace() { var root = new UiRoot { Width = 800, Height = 600 }; var panel = new UiPanel { Left = 10, Top = 10, Width = 100, Height = 50 }; root.AddChild(panel); root.OnMouseMove(50, 30); // inside the panel Assert.True(root.WantsMouse); root.OnMouseMove(500, 400); // empty space Assert.False(root.WantsMouse); } [Fact] public void WindowDrag_RepositionsDraggablePanel_StopsOnRelease() { var root = new UiRoot { Width = 800, Height = 600 }; var panel = new UiPanel { Left = 10, Top = 10, Width = 100, Height = 50, Draggable = true }; root.AddChild(panel); root.OnMouseDown(UiMouseButton.Left, 20, 20); // grab at (10,10) into the panel root.OnMouseMove(120, 90); // drag Assert.Equal(110f, panel.Left); // 120 - 10 Assert.Equal(80f, panel.Top); // 90 - 10 root.OnMouseUp(UiMouseButton.Left, 120, 90); root.OnMouseMove(300, 300); // released — must not move Assert.Equal(110f, panel.Left); Assert.Equal(80f, panel.Top); } [Fact] public void NonDraggablePanel_DoesNotMoveOnDrag() { var root = new UiRoot { Width = 800, Height = 600 }; var panel = new UiPanel { Left = 10, Top = 10, Width = 100, Height = 50 }; // Draggable defaults false root.AddChild(panel); root.OnMouseDown(UiMouseButton.Left, 20, 20); root.OnMouseMove(120, 90); Assert.Equal(10f, panel.Left); Assert.Equal(10f, panel.Top); } [Fact] public void CapturesPointerDragChild_DoesNotMoveDraggableAncestor_OnInteriorDrag() { // A child that captures pointer drags (text selection) must NOT move its // draggable ancestor window when the user drags inside it. var root = new UiRoot { Width = 800, Height = 600 }; var window = new UiPanel { Left = 10, Top = 10, Width = 200, Height = 100, Draggable = true }; var child = new UiPanel { Left = 20, Top = 20, Width = 120, Height = 60, CapturesPointerDrag = true }; window.AddChild(child); root.AddChild(window); // Press deep inside the child, then drag. root.OnMouseDown(UiMouseButton.Left, 60, 60); root.OnMouseMove(160, 160); // Window stays put; the captured child receives the drag itself. Assert.Equal(10f, window.Left); Assert.Equal(10f, window.Top); Assert.Same(child, root.Captured); root.OnMouseUp(UiMouseButton.Left, 160, 160); Assert.Equal(10f, window.Left); Assert.Equal(10f, window.Top); } [Fact] public void CapturesPointerDragChild_StillAllowsEdgeResizeOfResizableWindow() { // Edge resize must still win even when a CapturesPointerDrag child covers // the frame: a resizable chat window can be resized from its border. var root = new UiRoot { Width = 800, Height = 600 }; var window = new UiPanel { Left = 100, Top = 100, Width = 200, Height = 100, Draggable = true, Resizable = true, MinWidth = 40, MinHeight = 40 }; // Child fills the whole window (anchored) and captures interior drags. var child = new UiPanel { Left = 0, Top = 0, Width = 200, Height = 100, CapturesPointerDrag = true, Anchors = AnchorEdges.Left | AnchorEdges.Top | AnchorEdges.Right | AnchorEdges.Bottom }; window.AddChild(child); root.AddChild(window); // Grab within ResizeGrip(5) of the right edge (x=298 of right edge x=300) → resize. root.OnMouseDown(UiMouseButton.Left, 298, 150); root.OnMouseMove(338, 150); Assert.Equal(240f, window.Width); Assert.Equal(100f, window.Left); root.OnMouseUp(UiMouseButton.Left, 338, 150); } [Fact] public void ResizeRect_RightBottom_GrowsSizeOnly() { var (x, y, w, h) = UiRoot.ResizeRect(10, 20, 100, 50, ResizeEdges.Right | ResizeEdges.Bottom, dx: 30, dy: 15, minW: 40, minH: 40); Assert.Equal(10f, x); Assert.Equal(20f, y); Assert.Equal(130f, w); Assert.Equal(65f, h); } [Fact] public void ResizeRect_LeftTop_MovesOriginAndClampsToMin() { // Drag left edge right by 80 on a 100-wide / min-40 window: width clamps to 40, // origin shifts so the RIGHT edge (110) stays put → x = 70. var (x, _, w, _) = UiRoot.ResizeRect(10, 20, 100, 50, ResizeEdges.Left, dx: 80, dy: 0, minW: 40, minH: 40); Assert.Equal(40f, w); Assert.Equal(70f, x); } [Fact] public void HitEdges_DetectsCornerAndInteriorNone() { var panel = new UiPanel { Left = 100, Top = 100, Width = 200, Height = 100 }; // bottom-right corner (300,200) Assert.Equal(ResizeEdges.Right | ResizeEdges.Bottom, UiRoot.HitEdges(panel, 300, 200, 5)); // deep interior → no edges Assert.Equal(ResizeEdges.None, UiRoot.HitEdges(panel, 200, 150, 5)); } [Fact] public void EdgeDrag_ResizesPanel_InteriorDragMoves() { var root = new UiRoot { Width = 800, Height = 600 }; var panel = new UiPanel { Left = 100, Top = 100, Width = 200, Height = 100, Draggable = true, Resizable = true, MinWidth = 40, MinHeight = 40 }; root.AddChild(panel); // grab just inside the right edge (x=298, within ResizeGrip=5 of x=300) and drag right → wider, same origin root.OnMouseDown(UiMouseButton.Left, 298, 150); root.OnMouseMove(338, 150); Assert.Equal(240f, panel.Width); Assert.Equal(100f, panel.Left); root.OnMouseUp(UiMouseButton.Left, 338, 150); // grab the interior and drag → moves root.OnMouseDown(UiMouseButton.Left, 200, 150); root.OnMouseMove(220, 170); Assert.Equal(120f, panel.Left); Assert.Equal(120f, panel.Top); root.OnMouseUp(UiMouseButton.Left, 220, 170); } [Fact] public void HitEdges_RespectsResizeAxisLock() { var panel = new UiPanel { Left = 100, Top = 100, Width = 200, Height = 100, ResizeY = false }; // right edge still detected (X allowed) Assert.True((UiRoot.HitEdges(panel, 300, 150, 5) & ResizeEdges.Right) != 0); // bottom edge masked out (Y locked) Assert.True((UiRoot.HitEdges(panel, 200, 200, 5) & ResizeEdges.Bottom) == 0); } [Fact] public void ComputeAnchoredRect_LeftRight_StretchesWidth() { // bar at x=8,w=200 in a 220-wide parent (right margin 12). Parent grows to 300. var (x, _, w, _) = UiElement.ComputeAnchoredRect( AnchorEdges.Left | AnchorEdges.Right | AnchorEdges.Top, mL: 8, mT: 24, mR: 12, mB: 58, w0: 200, h0: 14, parentW: 300, parentH: 96); Assert.Equal(8f, x); Assert.Equal(280f, w); // 300 - 12 - 8 } [Fact] public void ComputeAnchoredRect_LeftTopOnly_KeepsFixedSizeAndOrigin() { var (x, y, w, h) = UiElement.ComputeAnchoredRect( AnchorEdges.Left | AnchorEdges.Top, mL: 8, mT: 24, mR: 12, mB: 58, w0: 200, h0: 14, parentW: 300, parentH: 96); Assert.Equal(8f, x); Assert.Equal(24f, y); Assert.Equal(200f, w); Assert.Equal(14f, h); } }