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));
}