diff --git a/src/AcDream.App/UI/UiNineSlicePanel.cs b/src/AcDream.App/UI/UiNineSlicePanel.cs index 576da3e1..2e4465a1 100644 --- a/src/AcDream.App/UI/UiNineSlicePanel.cs +++ b/src/AcDream.App/UI/UiNineSlicePanel.cs @@ -29,6 +29,11 @@ public sealed class UiNineSlicePanel : UiPanel BorderColor = Vector4.Zero; Draggable = true; // retail windows are movable Resizable = true; // retail windows are resizable + // A top-level window is USER-positioned: it must NOT be anchor-managed + // by its parent (UiRoot), or the per-frame anchor pass would reset its + // Left/Top/Width/Height every frame and undo move/resize. Children + // INSIDE the window still anchor to it (the bars stretch with width). + Anchors = AnchorEdges.None; } /// diff --git a/tests/AcDream.App.Tests/UI/UiRootInputTests.cs b/tests/AcDream.App.Tests/UI/UiRootInputTests.cs index d3b3cc0b..1adbffcd 100644 --- a/tests/AcDream.App.Tests/UI/UiRootInputTests.cs +++ b/tests/AcDream.App.Tests/UI/UiRootInputTests.cs @@ -5,6 +5,26 @@ 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); + } + + [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() {