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; /// /// 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, healthPct, healthText); BindMeter(layout, Stamina, staminaPct, staminaText); BindMeter(layout, Mana, 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, 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 renders the cur/max as a real UIElement_Text centered over the // bar — NOT a meter-internal label. Attach a centered UiText (non-interactive // decoration) that fills + stretches with the meter, and stop the meter drawing its // own label. UiText.Centered uses the SAME centering formula the meter's overlay did, // so the numbers stay pixel-identical (locked by the visual gate). m.Label = () => null; var number = new UiText { Left = 0f, Top = 0f, Width = m.Width, Height = m.Height, Anchors = AnchorEdges.Left | AnchorEdges.Top | AnchorEdges.Right | AnchorEdges.Bottom, Centered = true, DatFont = m.DatFont, // the same dat font the meter used for its label ClickThrough = true, // decoration: no focus / selection / drag AcceptsFocus = false, IsEditControl = false, CapturesPointerDrag = false, LinesProvider = () => { var s = text(); return string.IsNullOrEmpty(s) ? Array.Empty() : new[] { new UiText.Line(s, NumberColor) }; }, }; m.AddChild(number); } }