feat(D.2b): vital bars use retail dat sprites (back track + fill-cropped front)
UiMeter gains SpriteResolve/BackSpriteId/FrontSpriteId; when both are set, OnDraw draws the empty-track sprite full-width then the colored-fill sprite UV-cropped to the live fill fraction (left-to-right drain). Falls back to solid rects when sprite ids are absent, keeping existing behavior and tests intact. MarkupDocument.Build() parses `back`/`front` hex attrs on <meter> and passes `resolve` into every UiMeter. vitals.xml wires the authoritative LayoutDesc 0x21000014 sprites (Health 0x06005F3C/3D, Stamina 3E/3F, Mana 40/41). The bar prove-out block in GameWindow.cs was already gone. If the sprites decode as 1x1 magenta at runtime they are paletted (INDEX16/P8) — the solid-color fallback will display instead and can be investigated separately. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
56ee5eff60
commit
84630517e3
4 changed files with 68 additions and 16 deletions
|
|
@ -58,14 +58,17 @@ public static class MarkupDocument
|
|||
var max = BindUint((string?)el.Attribute("max"), binding);
|
||||
panel.AddChild(new UiMeter
|
||||
{
|
||||
Left = F(el, "x"),
|
||||
Top = F(el, "y"),
|
||||
Width = F(el, "w"),
|
||||
Height = F(el, "h"),
|
||||
BarColor = Color((string?)el.Attribute("color")),
|
||||
Fill = BindFloat((string?)el.Attribute("fill"), binding),
|
||||
Label = () => (cur(), max()) is (uint c, uint m) ? $"{c}/{m}" : null,
|
||||
Anchors = Anchor((string?)el.Attribute("anchor")),
|
||||
Left = F(el, "x"),
|
||||
Top = F(el, "y"),
|
||||
Width = F(el, "w"),
|
||||
Height = F(el, "h"),
|
||||
BarColor = Color((string?)el.Attribute("color")),
|
||||
Fill = BindFloat((string?)el.Attribute("fill"), binding),
|
||||
Label = () => (cur(), max()) is (uint c, uint m) ? $"{c}/{m}" : null,
|
||||
Anchors = Anchor((string?)el.Attribute("anchor")),
|
||||
SpriteResolve = resolve,
|
||||
BackSpriteId = Hex((string?)el.Attribute("back")),
|
||||
FrontSpriteId = Hex((string?)el.Attribute("front")),
|
||||
});
|
||||
break;
|
||||
// future element kinds (label, button, image) added here
|
||||
|
|
@ -125,6 +128,15 @@ public static class MarkupDocument
|
|||
return binding.GetType().GetProperty(expr[1..^1]);
|
||||
}
|
||||
|
||||
private static uint Hex(string? s)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(s)) return 0;
|
||||
var t = s.Trim();
|
||||
if (t.StartsWith("0x", System.StringComparison.OrdinalIgnoreCase)) t = t[2..];
|
||||
return uint.TryParse(t, System.Globalization.NumberStyles.HexNumber,
|
||||
System.Globalization.CultureInfo.InvariantCulture, out var v) ? v : 0u;
|
||||
}
|
||||
|
||||
private static AnchorEdges Anchor(string? csv)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(csv)) return AnchorEdges.Left | AnchorEdges.Top;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,14 @@ public sealed class UiMeter : UiElement
|
|||
public Vector4 BgColor { get; set; } = new(0f, 0f, 0f, 0.5f);
|
||||
public Vector4 LabelColor { get; set; } = new(1f, 1f, 1f, 1f);
|
||||
|
||||
/// <summary>Resolver from a RenderSurface DataId to (GL handle, w, h). When set
|
||||
/// with Back/Front sprite ids, the bar draws the retail sprites instead of solid color.</summary>
|
||||
public Func<uint, (uint tex, int w, int h)>? SpriteResolve { get; set; }
|
||||
/// <summary>Empty-track sprite (drawn full width). 0 = none.</summary>
|
||||
public uint BackSpriteId { get; set; }
|
||||
/// <summary>Colored-fill sprite (drawn cropped to the fill fraction). 0 = none.</summary>
|
||||
public uint FrontSpriteId { get; set; }
|
||||
|
||||
public UiMeter() { ClickThrough = true; }
|
||||
|
||||
/// <summary>Clamp <paramref name="pct"/> to [0,1] and return the fill rect
|
||||
|
|
@ -38,13 +46,32 @@ public sealed class UiMeter : UiElement
|
|||
|
||||
protected override void OnDraw(UiRenderContext ctx)
|
||||
{
|
||||
ctx.DrawRect(0, 0, Width, Height, BgColor);
|
||||
|
||||
float? pct = Fill();
|
||||
if (pct is float p)
|
||||
float p = pct is float pf ? (pf < 0f ? 0f : pf > 1f ? 1f : pf) : 0f;
|
||||
|
||||
if (SpriteResolve is { } resolve && (BackSpriteId != 0 || FrontSpriteId != 0))
|
||||
{
|
||||
var (fx, fy, fw, fh) = ComputeFillRect(p, Width, Height);
|
||||
if (fw > 0f) ctx.DrawRect(fx, fy, fw, fh, BarColor);
|
||||
// Retail bar: empty track full width, colored fill cropped to p (left→right).
|
||||
if (BackSpriteId != 0)
|
||||
{
|
||||
var (bt, _, _) = resolve(BackSpriteId);
|
||||
if (bt != 0) ctx.DrawSprite(bt, 0, 0, Width, Height, 0, 0, 1, 1, Vector4.One);
|
||||
}
|
||||
if (FrontSpriteId != 0 && pct is not null && p > 0f)
|
||||
{
|
||||
var (ft, _, _) = resolve(FrontSpriteId);
|
||||
if (ft != 0) ctx.DrawSprite(ft, 0, 0, Width * p, Height, 0, 0, p, 1, Vector4.One);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Placeholder solid-color fallback.
|
||||
ctx.DrawRect(0, 0, Width, Height, BgColor);
|
||||
if (pct is not null && p > 0f)
|
||||
{
|
||||
var (fx, fy, fw, fh) = ComputeFillRect(p, Width, Height);
|
||||
if (fw > 0f) ctx.DrawRect(fx, fy, fw, fh, BarColor);
|
||||
}
|
||||
}
|
||||
|
||||
string? label = Label();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<panel id="acdream.vitals" x="10" y="30" w="220" h="96" resize="x">
|
||||
<meter id="health" x="8" y="24" w="200" h="14" fill="{HealthPercent}" cur="{HealthCurrent}" max="{HealthMax}" color="#FFC70D0D" anchor="left,top,right"/>
|
||||
<meter id="stamina" x="8" y="44" w="200" h="14" fill="{StaminaPercent}" cur="{StaminaCurrent}" max="{StaminaMax}" color="#FFD49E1F" anchor="left,top,right"/>
|
||||
<meter id="mana" x="8" y="64" w="200" h="14" fill="{ManaPercent}" cur="{ManaCurrent}" max="{ManaMax}" color="#FF1F33D9" anchor="left,top,right"/>
|
||||
<meter id="health" x="8" y="24" w="200" h="14" fill="{HealthPercent}" cur="{HealthCurrent}" max="{HealthMax}" color="#FFC70D0D" anchor="left,top,right" back="0x06005F3C" front="0x06005F3D"/>
|
||||
<meter id="stamina" x="8" y="44" w="200" h="14" fill="{StaminaPercent}" cur="{StaminaCurrent}" max="{StaminaMax}" color="#FFD49E1F" anchor="left,top,right" back="0x06005F3E" front="0x06005F3F"/>
|
||||
<meter id="mana" x="8" y="64" w="200" h="14" fill="{ManaPercent}" cur="{ManaCurrent}" max="{ManaMax}" color="#FF1F33D9" anchor="left,top,right" back="0x06005F40" front="0x06005F41"/>
|
||||
</panel>
|
||||
|
|
|
|||
|
|
@ -56,4 +56,17 @@ public class MarkupDocumentTests
|
|||
Assert.True(panel.ResizeX);
|
||||
Assert.False(panel.ResizeY);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_ParsesBackFrontSpriteIds()
|
||||
{
|
||||
const string xml = "<panel id=\"v\" x=\"0\" y=\"0\" w=\"100\" h=\"50\" title=\"V\">" +
|
||||
"<meter id=\"h\" x=\"0\" y=\"0\" w=\"100\" h=\"14\" fill=\"{HealthPercent}\" back=\"0x06005F3C\" front=\"0x06005F3D\"/>" +
|
||||
"</panel>";
|
||||
var panel = MarkupDocument.Build(xml, new FakeBinding(), _ => ((uint)7, 32, 32));
|
||||
var meter = Assert.IsType<UiMeter>(panel.Children[1]);
|
||||
Assert.Equal(0x06005F3Cu, meter.BackSpriteId);
|
||||
Assert.Equal(0x06005F3Du, meter.FrontSpriteId);
|
||||
Assert.NotNull(meter.SpriteResolve);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue