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