using System;
using System.Numerics;
using AcDream.App.UI;
namespace AcDream.App.UI.Layout;
///
/// Per-window controller for the vitals layout (LayoutDesc 0x2100006C).
/// Mirrors retail gmVitalsUI::PostInit: grab the three meter elements
/// by their dat element ids and bind live data providers (fill fraction + cur/max
/// text) to each. This is the ONLY per-window code in the whole importer — pure
/// data wiring, not graphics.
///
/// The slice sprites + dat font on each are already
/// set by during tree construction; this controller
/// only binds the dynamic vitals data. Do not touch meter rendering fields here.
///
/// Element ids confirmed from
/// docs/research/2026-06-15-layoutdesc-format.md §11
/// (vitals window 0x2100006C dump).
///
public static class VitalsController
{
/// Dat element id for the Health meter (0x100000E6).
public const uint Health = 0x100000E6;
/// Dat element id for the Stamina meter (0x100000EC).
public const uint Stamina = 0x100000EC;
/// Dat element id for the Mana meter (0x100000EE).
public const uint Mana = 0x100000EE;
public const uint HealthText = 0x100000EB;
public const uint StaminaText = 0x100000ED;
public const uint ManaText = 0x100000EF;
///
/// Bind live vitals data providers to the Health, Stamina, and Mana meter
/// elements found in . Any meter whose id is absent
/// from the layout is silently skipped — partial layouts (e.g. test fakes)
/// do not cause errors.
///
/// Imported vitals layout tree.
/// Provider returning Health fill fraction [0..1].
/// Provider returning Stamina fill fraction [0..1].
/// Provider returning Mana fill fraction [0..1].
/// Provider returning Health "cur/max" overlay text.
/// Provider returning Stamina "cur/max" overlay text.
/// Provider returning Mana "cur/max" overlay text.
public static void Bind(
ImportedLayout layout,
Func healthPct,
Func staminaPct,
Func manaPct,
Func healthText,
Func staminaText,
Func manaText)
{
BindMeter(layout, Health, HealthText, healthPct, healthText);
BindMeter(layout, Stamina, StaminaText, staminaPct, staminaText);
BindMeter(layout, Mana, ManaText, manaPct, manaText);
}
/// White cur/max numbers — matches the former UiMeter.LabelColor default.
private static readonly Vector4 NumberColor = new(1f, 1f, 1f, 1f);
private static void BindMeter(
ImportedLayout layout, uint id, uint textId,
Func pct,
Func text)
{
// Silently skip if the id is absent — missing meters are not an error (partial layouts).
if (layout.FindElement(id) is not UiMeter m) return;
m.Fill = () => pct();
// Retail gmVitalsUI binds the authored UIElement_Text over each meter. The
// importer already built and registered that node; do not synthesize a duplicate.
m.Label = () => null;
if (layout.FindElement(textId) is not UiText number) return;
number.Centered = true;
number.RightAligned = false;
number.OneLine = true;
number.Selectable = false;
number.DatFont ??= m.DatFont;
number.LinesProvider = () =>
{
var s = text();
return string.IsNullOrEmpty(s)
? Array.Empty()
: new[] { new UiText.Line(s, NumberColor) };
};
}
}