diff --git a/src/AcDream.App/UI/UiRoot.cs b/src/AcDream.App/UI/UiRoot.cs index b43201dc..381ceea5 100644 --- a/src/AcDream.App/UI/UiRoot.cs +++ b/src/AcDream.App/UI/UiRoot.cs @@ -474,6 +474,42 @@ public sealed class UiRoot : UiElement public void SetCapture(UiElement e) => Captured = e; public void ReleaseCapture() => Captured = null; + // ── Window manager (named top-level windows: Show / Hide / Toggle) ─── + + private readonly Dictionary _windows = new(); + + /// Register a top-level window under a name for Show/Hide/Toggle. + /// Does NOT add it to the tree — the caller mounts via AddChild and controls + /// initial Visible. Idempotent (re-register replaces). The dict is the source + /// of truth — independent of UiElement.Name (init-only, not set here). + public void RegisterWindow(string name, UiElement window) => _windows[name] = window; + + /// Make the named window visible. No-op (returns false) if unknown. + public bool ShowWindow(string name) + { + if (!_windows.TryGetValue(name, out var w)) return false; + w.Visible = true; + return true; + } + + /// Hide the named window. No-op (returns false) if unknown. + public bool HideWindow(string name) + { + if (!_windows.TryGetValue(name, out var w)) return false; + w.Visible = false; + return true; + } + + /// Flip the named window's visibility (Show if hidden, Hide if shown). + /// Returns the new IsVisible state (false for an unknown name). + public bool ToggleWindow(string name) + { + if (!_windows.TryGetValue(name, out var w)) return false; + if (w.Visible) { HideWindow(name); return false; } + ShowWindow(name); + return true; + } + // ── Drag-drop (retail event chain 0x15 → 0x21 → 0x1C → 0x3E) ──────── private void BeginDrag(UiElement source) diff --git a/src/AcDream.App/UI/WindowNames.cs b/src/AcDream.App/UI/WindowNames.cs new file mode 100644 index 00000000..34cf5c43 --- /dev/null +++ b/src/AcDream.App/UI/WindowNames.cs @@ -0,0 +1,8 @@ +namespace AcDream.App.UI; + +/// Canonical registry names for top-level retail-UI windows, so the +/// mount, the window registry, and the toggle keybind all agree on one literal. +public static class WindowNames +{ + public const string Inventory = "inventory"; +} diff --git a/tests/AcDream.App.Tests/UI/UiRootInputTests.cs b/tests/AcDream.App.Tests/UI/UiRootInputTests.cs index 9f326919..55048e6a 100644 --- a/tests/AcDream.App.Tests/UI/UiRootInputTests.cs +++ b/tests/AcDream.App.Tests/UI/UiRootInputTests.cs @@ -257,4 +257,36 @@ public class UiRootInputTests Assert.Equal(8f, x); Assert.Equal(24f, y); Assert.Equal(200f, w); Assert.Equal(14f, h); } + + [Fact] + public void ToggleWindow_FlipsVisible_AndReturnsNewState() + { + var root = new UiRoot { Width = 800, Height = 600 }; + var win = new UiPanel { Width = 100, Height = 100, Visible = false }; + root.AddChild(win); + root.RegisterWindow("inventory", win); + + Assert.True(root.ToggleWindow("inventory")); // hidden -> shown + Assert.True(win.Visible); + Assert.False(root.ToggleWindow("inventory")); // shown -> hidden + Assert.False(win.Visible); + } + + [Fact] + public void ShowHideWindow_SetVisibility_UnknownNameIsNoOp() + { + var root = new UiRoot { Width = 800, Height = 600 }; + var win = new UiPanel { Width = 100, Height = 100, Visible = false }; + root.AddChild(win); + root.RegisterWindow("inventory", win); + + Assert.True(root.ShowWindow("inventory")); + Assert.True(win.Visible); + Assert.True(root.HideWindow("inventory")); + Assert.False(win.Visible); + + Assert.False(root.ShowWindow("nope")); + Assert.False(root.HideWindow("nope")); + Assert.False(root.ToggleWindow("nope")); + } }