feat(D.2b): MarkupDocument (XML -> UiElement tree); vitals panel from vitals.xml

Implements Task 8 of the D.2b retail-UI plan. MarkupDocument.Build() parses
KSML-style panel markup into a live UiNineSlicePanel subtree, resolving
{Binding} attribute expressions against a supplied object via reflection.
Color format is #AARRGGBB (alpha-first, matching controls.ini). Handles
<panel> root (geometry + optional title label) and <meter> children (fill,
label, bar color). Future element kinds (label, button, image) extend the
switch without touching existing code.

vitals.xml encodes the just-approved vitals panel layout (health red #FFC70D0D,
stamina gold #FFD49E1F, mana blue #FF1F33D9); ships next to the binary via
PreserveNewest csproj rule. GameWindow.cs drops the 35-line hand-built panel
block in favour of a 4-line File.ReadAllText + MarkupDocument.Build call —
identical tree, identical render, now data-driven.

2 new tests (Build_CreatesPanelWithMeterFillLabelAndGeometry,
Build_NullBindingValuesYieldNullFillAndLabel) + 11 total targeted green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-14 17:38:07 +02:00
parent 97bd1d2f09
commit 07bf6cbf60
5 changed files with 182 additions and 35 deletions

View file

@ -1753,42 +1753,11 @@ public sealed class GameWindow : IDisposable
var controls = _options.AcDir is { } acDir
? AcDream.App.UI.ControlsIni.Load(System.IO.Path.Combine(acDir, "controls", "controls.ini"))
: AcDream.App.UI.ControlsIni.Parse(string.Empty);
var titleColor = controls.TryColor("title", "color", out var tc)
? tc : new System.Numerics.Vector4(1f, 1f, 1f, 1f);
var panel = new AcDream.App.UI.UiNineSlicePanel(ResolveChrome)
{ Left = 10, Top = 30, Width = 220, Height = 96 };
panel.AddChild(new AcDream.App.UI.UiLabel
{
Text = "Vitals", Left = 8, Top = 4,
TextColor = titleColor,
});
var vm = _vitalsVm!;
panel.AddChild(new AcDream.App.UI.UiMeter
{
Left = 8, Top = 24, Width = 200, Height = 14,
BarColor = new System.Numerics.Vector4(0.78f, 0.05f, 0.05f, 1f), // health red
Fill = () => vm.HealthPercent,
Label = () => (vm.HealthCurrent, vm.HealthMax) is (uint c, uint m) ? $"{c}/{m}" : null,
});
panel.AddChild(new AcDream.App.UI.UiMeter
{
Left = 8, Top = 44, Width = 200, Height = 14,
BarColor = new System.Numerics.Vector4(0.83f, 0.62f, 0.12f, 1f), // stamina gold (retail; not cyan)
Fill = () => vm.StaminaPercent,
Label = () => (vm.StaminaCurrent, vm.StaminaMax) is (uint c, uint m) ? $"{c}/{m}" : null,
});
panel.AddChild(new AcDream.App.UI.UiMeter
{
Left = 8, Top = 64, Width = 200, Height = 14,
BarColor = new System.Numerics.Vector4(0.12f, 0.20f, 0.85f, 1f), // mana blue
Fill = () => vm.ManaPercent,
Label = () => (vm.ManaCurrent, vm.ManaMax) is (uint c, uint m) ? $"{c}/{m}" : null,
});
string vitalsXml = System.IO.File.ReadAllText(
System.IO.Path.Combine(AppContext.BaseDirectory, "UI", "assets", "vitals.xml"));
var panel = AcDream.App.UI.MarkupDocument.Build(vitalsXml, _vitalsVm!, ResolveChrome, controls);
_uiHost.Root.AddChild(panel);
Console.WriteLine("[D.2b] retail UI active — vitals panel wired (render-only).");
Console.WriteLine("[D.2b] retail UI active — vitals panel from vitals.xml markup.");
}
// Phase N.4+N.5 — WB rendering pipeline foundation. The modern path is