diff --git a/src/AcDream.App/Studio/MockupDesktop.cs b/src/AcDream.App/Studio/MockupDesktop.cs index ed2fe1ab..8ef57909 100644 --- a/src/AcDream.App/Studio/MockupDesktop.cs +++ b/src/AcDream.App/Studio/MockupDesktop.cs @@ -5,6 +5,7 @@ using AcDream.Core.Chat; using AcDream.UI.Abstractions; using AcDream.UI.Abstractions.Panels.Chat; using DatReaderWriter; +using System.Numerics; namespace AcDream.App.Studio; @@ -19,24 +20,32 @@ internal static class MockupDesktop public static void Load(DatCollection dats, RenderStack stack) { var objects = SampleData.BuildObjectTable(); + var windows = new List(); - MountVitals(dats, stack); - MountToolbar(dats, stack, objects); - MountCharacter(dats, stack, objects); - MountInventory(dats, stack, objects); - MountChat(dats, stack); + if (MountVitals(dats, stack, objects) is { } vitals) + RegisterWindow(stack, windows, WindowNames.Vitals, "Vitals", vitals, zOrder: 10); + if (MountToolbar(dats, stack, objects) is { } toolbar) + RegisterWindow(stack, windows, WindowNames.Toolbar, "Toolbar", toolbar, zOrder: 20); + if (MountCharacter(dats, stack, objects) is { } character) + RegisterWindow(stack, windows, WindowNames.Character, "Character", character, zOrder: 30); + if (MountInventory(dats, stack, objects) is { } inventory) + RegisterWindow(stack, windows, WindowNames.Inventory, "Inventory", inventory, zOrder: 40); + if (MountChat(dats, stack) is { } chat) + RegisterWindow(stack, windows, WindowNames.Chat, "Chat", chat, zOrder: 50); + + MountControls(stack, windows); } private static ImportedLayout? Import(DatCollection dats, uint layoutId, RenderStack stack) => LayoutImporter.Import(dats, layoutId, stack.ResolveChrome, stack.VitalsDatFont, fontResolve: stack.ResolveDatFont); - private static void MountVitals(DatCollection dats, RenderStack stack) + private static UiElement? MountVitals(DatCollection dats, RenderStack stack, AcDream.Core.Items.ClientObjectTable objects) { var layout = Import(dats, 0x2100006Cu, stack); - if (layout is null) return; + if (layout is null) return null; - FixtureProvider.Populate(0x2100006Cu, layout, stack, SampleData.BuildObjectTable(), dats); + FixtureProvider.Populate(0x2100006Cu, layout, stack, objects, dats); var root = layout.Root; root.Left = 12f; @@ -48,13 +57,13 @@ internal static class MockupDesktop root.ResizeX = true; root.ResizeY = false; root.MinWidth = 40f; - stack.UiHost.Root.AddChild(root); + return root; } - private static void MountToolbar(DatCollection dats, RenderStack stack, AcDream.Core.Items.ClientObjectTable objects) + private static UiElement? MountToolbar(DatCollection dats, RenderStack stack, AcDream.Core.Items.ClientObjectTable objects) { var layout = Import(dats, 0x21000016u, stack); - if (layout is null) return; + if (layout is null) return null; FixtureProvider.Populate(0x21000016u, layout, stack, objects, dats); @@ -82,7 +91,7 @@ internal static class MockupDesktop frame.AddChild(toolbarRoot); ConfigureToolbarCollapse(layout, frame, contentH, border); - stack.UiHost.Root.AddChild(frame); + return frame; } private static void ConfigureToolbarCollapse(ImportedLayout layout, UiCollapsibleFrame frame, float contentH, int border) @@ -117,10 +126,10 @@ internal static class MockupDesktop frame.MaxHeight = expandedH; } - private static void MountCharacter(DatCollection dats, RenderStack stack, AcDream.Core.Items.ClientObjectTable objects) + private static UiElement? MountCharacter(DatCollection dats, RenderStack stack, AcDream.Core.Items.ClientObjectTable objects) { var layout = Import(dats, 0x2100002Eu, stack); - if (layout is null) return; + if (layout is null) return null; FixtureProvider.Populate(0x2100002Eu, layout, stack, objects, dats); @@ -133,13 +142,13 @@ internal static class MockupDesktop root.Resizable = true; root.MinWidth = root.Width; root.MinHeight = root.Height; - stack.UiHost.Root.AddChild(root); + return root; } - private static void MountInventory(DatCollection dats, RenderStack stack, AcDream.Core.Items.ClientObjectTable objects) + private static UiElement? MountInventory(DatCollection dats, RenderStack stack, AcDream.Core.Items.ClientObjectTable objects) { var layout = Import(dats, 0x21000023u, stack); - if (layout is null) return; + if (layout is null) return null; FixtureProvider.Populate(0x21000023u, layout, stack, objects, dats); @@ -179,8 +188,7 @@ internal static class MockupDesktop PinTopLeft(layout, 0x100001CDu); PinTopLeft(layout, 0x100001CEu); - stack.UiHost.Root.AddChild(frame); - stack.UiHost.RegisterWindow(WindowNames.Inventory, frame); + return frame; } private static void StretchV(ImportedLayout layout, uint id) @@ -195,10 +203,10 @@ internal static class MockupDesktop element.Anchors = AnchorEdges.Left | AnchorEdges.Top; } - private static void MountChat(DatCollection dats, RenderStack stack) + private static UiElement? MountChat(DatCollection dats, RenderStack stack) { var rootInfo = LayoutImporter.ImportInfos(dats, ChatWindowController.LayoutId); - if (rootInfo is null) return; + if (rootInfo is null) return null; var layout = LayoutImporter.Build(rootInfo, stack.ResolveChrome, stack.VitalsDatFont, fontResolve: stack.ResolveDatFont); @@ -219,7 +227,7 @@ internal static class MockupDesktop stack.VitalsDatFont, debugFont: null, stack.ResolveChrome); - if (controller is null) return; + if (controller is null) return null; const int border = RetailChromeSprites.Border; var chatRoot = controller.Root; @@ -245,7 +253,221 @@ internal static class MockupDesktop chatRoot.Resizable = false; frame.AddChild(chatRoot); - stack.UiHost.Root.AddChild(frame); stack.UiHost.Root.DefaultTextInput = controller.Input; + return frame; + } + + private static void RegisterWindow(RenderStack stack, List windows, + string name, string title, UiElement element, int zOrder) + { + element.ZOrder = zOrder; + stack.UiHost.Root.AddChild(element); + stack.UiHost.RegisterWindow(name, element); + windows.Add(new MockupWindow( + name, + title, + element, + new MockupRect(element.Left, element.Top, element.Width, element.Height), + zOrder)); + } + + private static void MountControls(RenderStack stack, IReadOnlyList windows) + { + if (windows.Count == 0) return; + + var font = stack.VitalsDatFont; + var panel = new MockupControlPanel + { + Name = "mockup-controls", + Left = 1010f, + Top = 430f, + Width = 258f, + Height = 242f, + Anchors = AnchorEdges.None, + Draggable = true, + Resizable = false, + BackgroundColor = new Vector4(0.03f, 0.035f, 0.04f, 0.82f), + BorderColor = new Vector4(0.55f, 0.45f, 0.27f, 0.95f), + BorderThickness = 1f, + ZOrder = 1_000, + }; + + panel.AddChild(new MockupLabel(font) + { + Left = 12f, + Top = 10f, + Width = 220f, + Height = 18f, + Text = "Mockup", + TextColor = new Vector4(0.95f, 0.86f, 0.62f, 1f), + }); + + var toggles = new List<(MockupWindow Window, MockupButton Button)>(); + panel.AddChild(MakeButton(font, "Arrange", 12f, 34f, 108f, 24f, () => + { + Arrange(windows, showAll: false); + RefreshToggles(toggles); + })); + panel.AddChild(MakeButton(font, "Reset", 132f, 34f, 108f, 24f, () => + { + Arrange(windows, showAll: true); + RefreshToggles(toggles); + })); + + float y = 70f; + foreach (var window in windows) + { + var button = MakeButton(font, "", 12f, y, 228f, 24f, () => + { + stack.UiHost.ToggleWindow(window.Name); + RefreshToggles(toggles); + }); + toggles.Add((window, button)); + panel.AddChild(button); + y += 30f; + } + + RefreshToggles(toggles); + stack.UiHost.Root.AddChild(panel); + } + + private static MockupButton MakeButton(UiDatFont? font, string text, + float left, float top, float width, float height, Action onClick) + { + var button = new MockupButton(font) + { + Left = left, + Top = top, + Width = width, + Height = height, + Text = text, + }; + button.Click += onClick; + return button; + } + + private static void Arrange(IReadOnlyList windows, bool showAll) + { + foreach (var window in windows) + { + var element = window.Element; + element.Left = window.DefaultRect.Left; + element.Top = window.DefaultRect.Top; + element.Width = window.DefaultRect.Width; + element.Height = window.DefaultRect.Height; + element.ZOrder = window.DefaultZOrder; + if (showAll) + element.Visible = true; + } + } + + private static void RefreshToggles(IReadOnlyList<(MockupWindow Window, MockupButton Button)> toggles) + { + foreach (var (window, button) in toggles) + { + bool visible = window.Element.Visible; + button.Text = visible ? $"{window.Title} on" : $"{window.Title} off"; + button.TextColor = visible + ? new Vector4(0.96f, 0.93f, 0.82f, 1f) + : new Vector4(0.62f, 0.62f, 0.62f, 1f); + button.BackgroundColor = visible + ? new Vector4(0.14f, 0.13f, 0.10f, 0.94f) + : new Vector4(0.06f, 0.065f, 0.07f, 0.82f); + } + } + + private sealed record MockupWindow( + string Name, + string Title, + UiElement Element, + MockupRect DefaultRect, + int DefaultZOrder); + + private readonly record struct MockupRect(float Left, float Top, float Width, float Height); + + private sealed class MockupControlPanel : UiPanel + { + protected override void OnDraw(UiRenderContext ctx) + { + if (BackgroundColor.W > 0f) + ctx.DrawFill(0f, 0f, Width, Height, BackgroundColor); + if (BorderColor.W > 0f && BorderThickness > 0f) + ctx.DrawRectOutline(0f, 0f, Width, Height, BorderColor, BorderThickness); + } + + protected override void OnTick(double deltaSeconds) + { + base.OnTick(deltaSeconds); + if (Parent is null) return; + + int top = ZOrder; + foreach (var sibling in Parent.Children) + { + if (!ReferenceEquals(sibling, this)) + top = Math.Max(top, sibling.ZOrder + 1); + } + ZOrder = top; + } + } + + private sealed class MockupLabel : UiElement + { + private readonly UiDatFont? _font; + + public string Text { get; set; } = string.Empty; + public Vector4 TextColor { get; set; } = Vector4.One; + + public MockupLabel(UiDatFont? font) + { + _font = font; + ClickThrough = true; + } + + protected override void OnDraw(UiRenderContext ctx) + { + if (_font is null || Text.Length == 0) return; + ctx.DrawStringDat(_font, Text, 0f, 0f, TextColor); + } + } + + private sealed class MockupButton : UiPanel + { + private readonly UiDatFont? _font; + + public event Action? Click; + public string Text { get; set; } = string.Empty; + public Vector4 TextColor { get; set; } = new(0.96f, 0.93f, 0.82f, 1f); + + public MockupButton(UiDatFont? font) + { + _font = font; + BackgroundColor = new Vector4(0.14f, 0.13f, 0.10f, 0.94f); + BorderColor = new Vector4(0.45f, 0.38f, 0.24f, 1f); + BorderThickness = 1f; + } + + public override bool HandlesClick => true; + + public override bool OnEvent(in UiEvent e) + { + if (e.Type != UiEventType.Click || !Enabled) return false; + Click?.Invoke(); + return true; + } + + protected override void OnDraw(UiRenderContext ctx) + { + if (BackgroundColor.W > 0f) + ctx.DrawFill(0f, 0f, Width, Height, BackgroundColor); + if (BorderColor.W > 0f && BorderThickness > 0f) + ctx.DrawRectOutline(0f, 0f, Width, Height, BorderColor, BorderThickness); + + if (_font is null || Text.Length == 0) return; + + float textW = _font.MeasureWidth(Text); + float tx = MathF.Max(4f, (Width - textW) * 0.5f); + float ty = (Height - _font.LineHeight) * 0.5f; + ctx.DrawStringDat(_font, Text, tx, ty, TextColor); + } } } diff --git a/src/AcDream.App/UI/UiHost.cs b/src/AcDream.App/UI/UiHost.cs index 8e4c66f3..1840817d 100644 --- a/src/AcDream.App/UI/UiHost.cs +++ b/src/AcDream.App/UI/UiHost.cs @@ -108,6 +108,12 @@ public sealed class UiHost : System.IDisposable /// Register a top-level window for Show/Hide/Toggle. See . public void RegisterWindow(string name, UiElement window) => Root.RegisterWindow(name, window); + /// Show a registered window; returns false if the name is unknown. + public bool ShowWindow(string name) => Root.ShowWindow(name); + + /// Hide a registered window; returns false if the name is unknown. + public bool HideWindow(string name) => Root.HideWindow(name); + /// Toggle a registered window's visibility; returns the new IsVisible. public bool ToggleWindow(string name) => Root.ToggleWindow(name); diff --git a/src/AcDream.App/UI/WindowNames.cs b/src/AcDream.App/UI/WindowNames.cs index 34cf5c43..6f2447e6 100644 --- a/src/AcDream.App/UI/WindowNames.cs +++ b/src/AcDream.App/UI/WindowNames.cs @@ -4,5 +4,9 @@ namespace AcDream.App.UI; /// mount, the window registry, and the toggle keybind all agree on one literal. public static class WindowNames { + public const string Vitals = "vitals"; + public const string Toolbar = "toolbar"; + public const string Character = "character"; public const string Inventory = "inventory"; + public const string Chat = "chat"; }