feat(D.2b): vitals numbers as UiText (widget-generalization Task 8)
The vitals cur/max numbers now render through the generic UiText widget — retail gmVitalsUI uses UIElement_Text for them, not a meter-internal label. VitalsController attaches a centered, non-interactive UiText child to each meter and stops the meter drawing its own label (UiMeter.Label -> null). New UiText.Centered draws the first line centered H+V with the SAME formula UiMeter's overlay used, so the numbers are pixel-identical — user-confirmed in the live client. This completes the D.2b widget-generalization pass: every chat + vitals widget is now built generically and registered to its retail Type (Button/Field*/Menu/Meter/Scrollbar/ Text), with thin find-by-id controllers. (*Field is controller-placed; Type 3 stays UiDatElement for chrome.) Divergence register: AP-37 vitals-numbers-via-UiMeter.Label clause retired. Full suite: 404 passed, 2 skipped, 0 failed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d7002552bc
commit
89626cd400
4 changed files with 83 additions and 10 deletions
|
|
@ -1,4 +1,6 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using AcDream.App.UI;
|
||||
|
||||
namespace AcDream.App.UI.Layout;
|
||||
|
||||
|
|
@ -53,16 +55,44 @@ public static class VitalsController
|
|||
BindMeter(layout, Mana, manaPct, manaText);
|
||||
}
|
||||
|
||||
/// <summary>White cur/max numbers — matches the former <c>UiMeter.LabelColor</c> default.</summary>
|
||||
private static readonly Vector4 NumberColor = new(1f, 1f, 1f, 1f);
|
||||
|
||||
private static void BindMeter(
|
||||
ImportedLayout layout, uint id,
|
||||
Func<float> pct,
|
||||
Func<string> text)
|
||||
{
|
||||
if (layout.FindElement(id) is UiMeter m)
|
||||
// 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
|
||||
{
|
||||
m.Fill = () => pct();
|
||||
m.Label = () => text();
|
||||
}
|
||||
// Silently skip if the id is absent — missing meters are not an error.
|
||||
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<UiText.Line>()
|
||||
: new[] { new UiText.Line(s, NumberColor) };
|
||||
},
|
||||
};
|
||||
m.AddChild(number);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,6 +63,14 @@ public sealed class UiText : UiElement
|
|||
/// <summary>Inner text inset from the view edges, px.</summary>
|
||||
public float Padding { get; set; } = 4f;
|
||||
|
||||
/// <summary>Static centered single-line mode (retail <c>UIElement_Text</c> center
|
||||
/// justification): draws the FIRST line centered horizontally AND vertically in the
|
||||
/// element rect, with NO scroll/selection machinery. Used for static labels such as
|
||||
/// the vitals cur/max numbers. The centering formula is IDENTICAL to
|
||||
/// <see cref="UiMeter"/>'s former number overlay so those numbers stay pixel-identical
|
||||
/// after the rewire. Pair with <c>ClickThrough = true</c> for non-interactive labels.</summary>
|
||||
public bool Centered { get; set; }
|
||||
|
||||
/// <summary>The scroll model — also read by the linked UiScrollbar.</summary>
|
||||
public UiScrollable Scroll { get; } = new();
|
||||
|
||||
|
|
@ -122,6 +130,29 @@ public sealed class UiText : UiElement
|
|||
// submitted first → text on top.
|
||||
ctx.DrawFill(0, 0, Width, Height, BackgroundColor);
|
||||
|
||||
// Static centered single-line mode (vitals cur/max numbers etc.): draw the first
|
||||
// line centered H+V with the SAME formula UIElement_Meter used for its label, then
|
||||
// skip the scroll/selection machinery entirely.
|
||||
if (Centered)
|
||||
{
|
||||
var cLines = LinesProvider();
|
||||
if (cLines.Count == 0) return;
|
||||
var line0 = cLines[0];
|
||||
if (DatFont is { } cdf)
|
||||
{
|
||||
float cx = (Width - cdf.MeasureWidth(line0.Text)) * 0.5f;
|
||||
float cy = (Height - cdf.LineHeight) * 0.5f;
|
||||
ctx.DrawStringDat(cdf, line0.Text, cx, cy, line0.Color);
|
||||
}
|
||||
else if ((Font ?? ctx.DefaultFont) is { } cbf)
|
||||
{
|
||||
float cx = (Width - cbf.MeasureWidth(line0.Text)) * 0.5f;
|
||||
float cy = (Height - cbf.LineHeight) * 0.5f;
|
||||
ctx.DrawString(line0.Text, cx, cy, line0.Color, cbf);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Prefer the retail dat font when set; fall back to BitmapFont.
|
||||
var datFont = DatFont;
|
||||
var bitmapFont = datFont is null ? (Font ?? ctx.DefaultFont) : null;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue