using AcDream.App.UI; using AcDream.App.UI.Layout; namespace AcDream.App.Tests.UI.Layout; /// /// The retail vitals numeric/graphical click toggle, against the committed /// vitals fixture (vitals_2100006C.json) — no dats, no GL. /// /// Retail mechanism (fully derived 2026-08-17): /// /// gmVitalsUI::ListenToElementMessage @0x004BFC00 — /// mouse press (msg 0x1C) with dwParam1 7 (left) or 0xA (right) flips /// SetState(m_state == HideDetail ? ShowDetail : HideDetail). /// UIElement::SetState @0x00464E70 — the state /// cascades through the authored PassToChildren chain (root and meters /// author media-less HideDetail/ShowDetail with PassToChildren=true). /// The cur/max labels author per-state 0x3B (invisible — /// UIElement::OnSetAttribute case 8 @0x00462DAE is /// SetVisible(value == 0)): HideDetail={0x3B:false} (numbers shown), /// ShowDetail={0x3B:true} (numbers hidden). /// The 0x100004A9 overlay children author icon media only /// for ShowDetail (heart 0x06007490/91, sword 0x06007492/93, scepter /// 0x06007494/95) — dim back icon unclipped, bright front icon fill-clipped. /// UIElement_Dragbar @0x0046C850 / /// UIElement_Resizebar @0x0046B930 consume presses (return 2) — a /// window move/resize press never toggles. /// Not persisted: gmGamePlayUI::SaveScreenLayout /// @0x004EAD50 writes window rects only; each window resets to the /// authored Undef default per session. /// /// [Trait("Category", "Conformance")] public class VitalsDetailToggleTests { private const uint DragBarTop = 0x1000063Cu; // Type 2 — WindowMoveHandle private const uint GripTopLeft = 0x1000063Bu; // Type 9 — UiResizeGrip // ── Import shape ───────────────────────────────────────────────────────── [Fact] public void VitalsTree_RootIsVitalsRootWidget() { var layout = FixtureLoader.LoadVitals(); Assert.IsType(layout.Root); } [Fact] public void VitalsTree_MetersAbsorbDetailIconOverlays() { var layout = FixtureLoader.LoadVitals(); // MeterId → (dim back icon, bright front icon) from the authored // 0x100004A9 ShowDetail media (format doc §11 + installed-DAT probe). (uint MeterId, uint Back, uint Front)[] cases = [ (VitalsController.Health, 0x06007490u, 0x06007491u), // heart (VitalsController.Stamina, 0x06007492u, 0x06007493u), // sword (VitalsController.Mana, 0x06007494u, 0x06007495u), // scepter ]; foreach (var (meterId, back, front) in cases) { var m = Assert.IsType(layout.FindElement(meterId)); Assert.True(m.HasDetailOverlay); Assert.Equal(back, m.DetailBackSprite); Assert.Equal(front, m.DetailFrontSprite); } // Health's authored overlay rect: 18x16 at x=66 (the heart). var health = Assert.IsType(layout.FindElement(VitalsController.Health)); Assert.Equal((66f, 0f, 18f, 16f), health.DetailBackRect); } // ── The press toggle ───────────────────────────────────────────────────── [Fact] public void Press_TogglesUndefThenHideDetailThenShowDetail() { var layout = FixtureLoader.LoadVitals(); var root = Assert.IsType(layout.Root); var stamina = Assert.IsType(layout.FindElement(VitalsController.Stamina)); var staminaText = Assert.IsType(layout.FindElement(VitalsController.StaminaText)); // Login default: authored DefaultState is Undef and nothing calls // SetState — numbers visible, no icons. Assert.True(staminaText.Visible); // Press 1 (from Undef): retail lands on HideDetail — visually identical // (labels author {0x3B:false} there), never back to Undef afterwards. Press(root, root); Assert.Equal(RetailUiStateIds.HideDetail, root.ActiveRetailStateId); Assert.Equal(RetailUiStateIds.HideDetail, stamina.ActiveRetailStateId); Assert.True(staminaText.Visible); // Press 2: ShowDetail — the graphical mode. Numbers hidden, icons on. Press(root, root); Assert.Equal(RetailUiStateIds.ShowDetail, root.ActiveRetailStateId); Assert.Equal(RetailUiStateIds.ShowDetail, stamina.ActiveRetailStateId); Assert.False(staminaText.Visible); // Press 3: back to numeric. Press(root, root); Assert.Equal(RetailUiStateIds.HideDetail, root.ActiveRetailStateId); Assert.True(staminaText.Visible); } [Fact] public void RightPress_TogglesLikeLeftPress() { // Retail accepts dwParam1 7 (left) OR 0xA (right) — @0x004BFC19. var layout = FixtureLoader.LoadVitals(); var root = Assert.IsType(layout.Root); Press(root, root, UiEventType.RightDown); Assert.Equal(RetailUiStateIds.HideDetail, root.ActiveRetailStateId); Press(root, root, UiEventType.RightDown); Assert.Equal(RetailUiStateIds.ShowDetail, root.ActiveRetailStateId); } [Fact] public void Press_CascadesToAllThreeLabels() { var layout = FixtureLoader.LoadVitals(); var root = Assert.IsType(layout.Root); Press(root, root); // Undef → HideDetail Press(root, root); // HideDetail → ShowDetail foreach (uint textId in new[] { VitalsController.HealthText, VitalsController.StaminaText, VitalsController.ManaText, }) { var text = Assert.IsType(layout.FindElement(textId)); Assert.False(text.Visible); } } // ── Chrome exclusions ──────────────────────────────────────────────────── [Fact] public void Press_OnDragBar_DoesNotToggle() { // Retail UIElement_Dragbar::ListenToElementMessage returns 2 (consumed) // for every message — the press never reaches gmVitalsUI. var layout = FixtureLoader.LoadVitals(); var root = Assert.IsType(layout.Root); var dragBar = layout.FindElement(DragBarTop); Assert.NotNull(dragBar); Assert.True(dragBar!.WindowMoveHandle); uint before = root.ActiveRetailStateId; Press(root, dragBar); Assert.Equal(before, root.ActiveRetailStateId); } [Fact] public void Press_OnResizeGrip_DoesNotToggle() { // Retail UIElement_Resizebar::ListenToElementMessage returns 2 likewise. var layout = FixtureLoader.LoadVitals(); var root = Assert.IsType(layout.Root); var grip = layout.FindElement(GripTopLeft); Assert.NotNull(grip); Assert.IsType(grip); uint before = root.ActiveRetailStateId; Press(root, grip!); Assert.Equal(before, root.ActiveRetailStateId); } // ── Independence: each window keeps its own state ───────────────────────── [Fact] public void TwoWindows_ToggleIndependently() { // Retail: gmFloatyVitalsUI and gmFloatySideVitalsUI each carry their // own m_state; toggling one never touches the other. var a = FixtureLoader.LoadVitals(); var b = FixtureLoader.LoadVitals(); var rootA = Assert.IsType(a.Root); var rootB = Assert.IsType(b.Root); Press(rootA, rootA); Press(rootA, rootA); Assert.Equal(RetailUiStateIds.ShowDetail, rootA.ActiveRetailStateId); Assert.NotEqual(RetailUiStateIds.ShowDetail, rootB.ActiveRetailStateId); } // ── End-to-end: the REAL mouse path (hit test + bubble) ────────────────── [Fact] public void MouseDown_OnWindowBody_TogglesThroughTheRealHitPath() { var (root, vitalsRoot) = MountedWindow(); // Body press: the stamina meter's center (meters are ClickThrough, so // the hit lands on the window content and bubbles to the vitals root). root.OnMouseDown(UiMouseButton.Left, 90, 59); root.OnMouseUp(UiMouseButton.Left, 90, 59); Assert.Equal(RetailUiStateIds.HideDetail, vitalsRoot.ActiveRetailStateId); // Right press toggles too (retail dwParam1 0xA). root.OnMouseDown(UiMouseButton.Right, 90, 59); root.OnMouseUp(UiMouseButton.Right, 90, 59); Assert.Equal(RetailUiStateIds.ShowDetail, vitalsRoot.ActiveRetailStateId); } [Fact] public void MouseDown_OnTopDragBar_MovesArmWithoutToggling() { var (root, vitalsRoot) = MountedWindow(); // The authored top drag bar (0x1000063C) spans content-local // (5,0)-(155,5) → canvas y just below the window top. Retail's // Dragbar consumes the press; the toggle must not fire. root.OnMouseDown(UiMouseButton.Left, 90, 32); root.OnMouseUp(UiMouseButton.Left, 90, 32); Assert.NotEqual(RetailUiStateIds.HideDetail, vitalsRoot.ActiveRetailStateId); Assert.NotEqual(RetailUiStateIds.ShowDetail, vitalsRoot.ActiveRetailStateId); } private static (UiRoot Root, UiVitalsRoot VitalsRoot) MountedWindow() { var root = new UiRoot { Width = 800, Height = 600 }; var layout = FixtureLoader.LoadVitals(); RetailWindowFrame.Mount(root, layout.Root, static _ => (0u, 0, 0), new RetailWindowFrame.Options { WindowName = WindowNames.Vitals, Chrome = RetailWindowChrome.Imported, Left = 10f, Top = 30f, ResizeX = true, ResizeY = false, MinWidth = 40f, ContentClickThrough = false, }); return (root, Assert.IsType(layout.Root)); } // ── Helpers ────────────────────────────────────────────────────────────── /// Delivers a press to as UiRoot's bubble /// walk would: the hit target rides in . private static void Press(UiVitalsRoot root, UiElement target, int type = UiEventType.MouseDown) => root.OnEvent(new UiEvent(target.EventId, target, type)); }