From 036db8b8ab1eb7062a44fffa79bd8de0b40be183 Mon Sep 17 00:00:00 2001 From: Erik Date: Sat, 20 Jun 2026 21:31:29 +0200 Subject: [PATCH] =?UTF-8?q?feat(ui):=20D.2b-A=20=E2=80=94=20BringToFront?= =?UTF-8?q?=20+=20raise=20on=20show/click?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BringToFront sets a window's ZOrder one past the max among its peers. ShowWindow now raises on open; OnMouseDown raises any pressed top-level window (retail-faithful stacking). Existing drag/resize tests unaffected (raise only touches ZOrder, not geometry). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/AcDream.App/UI/UiRoot.cs | 15 +++++++ .../AcDream.App.Tests/UI/UiRootInputTests.cs | 41 +++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/src/AcDream.App/UI/UiRoot.cs b/src/AcDream.App/UI/UiRoot.cs index 381ceea5..11d35b55 100644 --- a/src/AcDream.App/UI/UiRoot.cs +++ b/src/AcDream.App/UI/UiRoot.cs @@ -248,6 +248,8 @@ public sealed class UiRoot : UiElement // A left-drag starting near an edge resizes; interior drag repositions; // otherwise it's a normal drag-drop candidate. var window = FindWindow(target); + // Retail-faithful: pressing on a window raises it above its peers. + if (window is not null) BringToFront(window); if (btn == UiMouseButton.Left && window is not null) { var edges = window.Resizable ? HitEdges(window, x, y, ResizeGrip) : ResizeEdges.None; @@ -489,6 +491,7 @@ public sealed class UiRoot : UiElement { if (!_windows.TryGetValue(name, out var w)) return false; w.Visible = true; + BringToFront(w); return true; } @@ -510,6 +513,18 @@ public sealed class UiRoot : UiElement return true; } + /// Raise a top-level window above its siblings by setting its ZOrder + /// one past the current max among the OTHER top-level children. Used on Show + /// and on click. Leaves ZOrder unchanged if it is the only / already-topmost child. + public void BringToFront(UiElement window) + { + int top = window.ZOrder; + foreach (var c in Children) + if (!ReferenceEquals(c, window)) + top = System.Math.Max(top, c.ZOrder + 1); + window.ZOrder = top; + } + // ── Drag-drop (retail event chain 0x15 → 0x21 → 0x1C → 0x3E) ──────── private void BeginDrag(UiElement source) diff --git a/tests/AcDream.App.Tests/UI/UiRootInputTests.cs b/tests/AcDream.App.Tests/UI/UiRootInputTests.cs index 55048e6a..e31da6c7 100644 --- a/tests/AcDream.App.Tests/UI/UiRootInputTests.cs +++ b/tests/AcDream.App.Tests/UI/UiRootInputTests.cs @@ -289,4 +289,45 @@ public class UiRootInputTests Assert.False(root.HideWindow("nope")); Assert.False(root.ToggleWindow("nope")); } + + [Fact] + public void ShowWindow_RaisesAbovePeers() + { + var root = new UiRoot { Width = 800, Height = 600 }; + var a = new UiPanel { Width = 100, Height = 100 }; + var b = new UiPanel { Width = 100, Height = 100 }; + var win = new UiPanel { Width = 100, Height = 100, Visible = false }; + root.AddChild(a); root.AddChild(b); root.AddChild(win); + root.RegisterWindow("inventory", win); + + root.ShowWindow("inventory"); + Assert.True(win.ZOrder > a.ZOrder); + Assert.True(win.ZOrder > b.ZOrder); + } + + [Fact] + public void BringToFront_SetsStrictlyGreatestZOrder() + { + var root = new UiRoot { Width = 800, Height = 600 }; + var a = new UiPanel { Width = 100, Height = 100, ZOrder = 5 }; + var b = new UiPanel { Width = 100, Height = 100, ZOrder = 9 }; + root.AddChild(a); root.AddChild(b); + + root.BringToFront(a); + Assert.True(a.ZOrder > b.ZOrder); // 10 > 9 + } + + [Fact] + public void MouseDown_OnWindow_RaisesItAbovePeers() + { + var root = new UiRoot { Width = 800, Height = 600 }; + // 'peer' has a higher ZOrder but does NOT cover (50,50); 'clicked' does. + var peer = new UiPanel { Left = 300, Top = 0, Width = 100, Height = 100, ZOrder = 9 }; + var clicked = new UiPanel { Left = 0, Top = 0, Width = 100, Height = 100, ZOrder = 0, Draggable = true }; + root.AddChild(peer); root.AddChild(clicked); + + root.OnMouseDown(UiMouseButton.Left, 50, 50); // only 'clicked' occupies (50,50) + Assert.True(clicked.ZOrder > peer.ZOrder); + root.OnMouseUp(UiMouseButton.Left, 50, 50); + } }