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);
|
var max = BindUint((string?)el.Attribute("max"), binding);
|
||||||
panel.AddChild(new UiMeter
|
panel.AddChild(new UiMeter
|
||||||
{
|
{
|
||||||
Left = F(el, "x"),
|
Left = F(el, "x"),
|
||||||
Top = F(el, "y"),
|
Top = F(el, "y"),
|
||||||
Width = F(el, "w"),
|
Width = F(el, "w"),
|
||||||
Height = F(el, "h"),
|
Height = F(el, "h"),
|
||||||
BarColor = Color((string?)el.Attribute("color")),
|
BarColor = Color((string?)el.Attribute("color")),
|
||||||
Fill = BindFloat((string?)el.Attribute("fill"), binding),
|
Fill = BindFloat((string?)el.Attribute("fill"), binding),
|
||||||
Label = () => (cur(), max()) is (uint c, uint m) ? $"{c}/{m}" : null,
|
Label = () => (cur(), max()) is (uint c, uint m) ? $"{c}/{m}" : null,
|
||||||
Anchors = Anchor((string?)el.Attribute("anchor")),
|
Anchors = Anchor((string?)el.Attribute("anchor")),
|
||||||
|
SpriteResolve = resolve,
|
||||||
|
BackSpriteId = Hex((string?)el.Attribute("back")),
|
||||||
|
FrontSpriteId = Hex((string?)el.Attribute("front")),
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
// future element kinds (label, button, image) added here
|
// future element kinds (label, button, image) added here
|
||||||
|
|
@ -125,6 +128,15 @@ public static class MarkupDocument
|
||||||
return binding.GetType().GetProperty(expr[1..^1]);
|
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)
|
private static AnchorEdges Anchor(string? csv)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(csv)) return AnchorEdges.Left | AnchorEdges.Top;
|
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 BgColor { get; set; } = new(0f, 0f, 0f, 0.5f);
|
||||||
public Vector4 LabelColor { get; set; } = new(1f, 1f, 1f, 1f);
|
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; }
|
public UiMeter() { ClickThrough = true; }
|
||||||
|
|
||||||
/// <summary>Clamp <paramref name="pct"/> to [0,1] and return the fill rect
|
/// <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)
|
protected override void OnDraw(UiRenderContext ctx)
|
||||||
{
|
{
|
||||||
ctx.DrawRect(0, 0, Width, Height, BgColor);
|
|
||||||
|
|
||||||
float? pct = Fill();
|
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);
|
// Retail bar: empty track full width, colored fill cropped to p (left→right).
|
||||||
if (fw > 0f) ctx.DrawRect(fx, fy, fw, fh, BarColor);
|
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();
|
string? label = Label();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<panel id="acdream.vitals" x="10" y="30" w="220" h="96" resize="x">
|
<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="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"/>
|
<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"/>
|
<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>
|
</panel>
|
||||||
|
|
|
||||||
|
|
@ -56,4 +56,17 @@ public class MarkupDocumentTests
|
||||||
Assert.True(panel.ResizeX);
|
Assert.True(panel.ResizeX);
|
||||||
Assert.False(panel.ResizeY);
|
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