From 064ef41ce4f5a77cfc01295746db374a52aff1e1 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 14 Jun 2026 16:38:07 +0200 Subject: [PATCH] feat(D.2b): UiMeter vital bar + fill-geometry tests Adds UiMeter, the horizontal vital-bar widget for the D.2b retail-look UI toolkit. Solid-color fill for Spec 1; the retail orb sprite + scissor crop path is reserved for a later sub-phase. Five unit tests (1 Fact + 4 Theory) cover half-fill geometry and clamping at -1/0/1/2 fractions. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/AcDream.App/UI/UiMeter.cs | 40 ++++++++++++++++++++++ tests/AcDream.App.Tests/UI/UiMeterTests.cs | 25 ++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 src/AcDream.App/UI/UiMeter.cs create mode 100644 tests/AcDream.App.Tests/UI/UiMeterTests.cs diff --git a/src/AcDream.App/UI/UiMeter.cs b/src/AcDream.App/UI/UiMeter.cs new file mode 100644 index 00000000..9fcdd5d6 --- /dev/null +++ b/src/AcDream.App/UI/UiMeter.cs @@ -0,0 +1,40 @@ +using System.Numerics; + +namespace AcDream.App.UI; + +/// +/// A horizontal vital bar: an empty background rect with a partial-width +/// fill. returns 0..1 (or null = no data → empty bar). +/// Solid-color for Spec 1; the retail orb sprite + scissor crop is a later +/// sub-phase. +/// +public sealed class UiMeter : UiElement +{ + /// Fill fraction provider; a null result draws an empty bar. + public Func Fill { get; set; } = () => 0f; + public Vector4 BarColor { get; set; } = new(1f, 0f, 0f, 1f); + public Vector4 BgColor { get; set; } = new(0f, 0f, 0f, 0.5f); + + public UiMeter() { ClickThrough = true; } + + /// Clamp to [0,1] and return the fill rect + /// (local px) for a bar of x . + public static (float x, float y, float w, float h) ComputeFillRect( + float pct, float w, float h) + { + if (pct < 0f) pct = 0f; + if (pct > 1f) pct = 1f; + return (0f, 0f, w * pct, h); + } + + protected override void OnDraw(UiRenderContext ctx) + { + ctx.DrawRect(0, 0, Width, Height, BgColor); + float? pct = Fill(); + if (pct is float p) + { + var (fx, fy, fw, fh) = ComputeFillRect(p, Width, Height); + if (fw > 0f) ctx.DrawRect(fx, fy, fw, fh, BarColor); + } + } +} diff --git a/tests/AcDream.App.Tests/UI/UiMeterTests.cs b/tests/AcDream.App.Tests/UI/UiMeterTests.cs new file mode 100644 index 00000000..9e7637e9 --- /dev/null +++ b/tests/AcDream.App.Tests/UI/UiMeterTests.cs @@ -0,0 +1,25 @@ +using AcDream.App.UI; + +namespace AcDream.App.Tests.UI; + +public class UiMeterTests +{ + [Fact] + public void ComputeFillRect_HalfFillIsHalfWidth() + { + var (x, y, w, h) = UiMeter.ComputeFillRect(0.5f, 200f, 12f); + Assert.Equal(0f, x); Assert.Equal(0f, y); + Assert.Equal(100f, w); Assert.Equal(12f, h); + } + + [Theory] + [InlineData(-1f, 0f)] // clamps below 0 + [InlineData(2f, 200f)] // clamps above 1 + [InlineData(0f, 0f)] + [InlineData(1f, 200f)] + public void ComputeFillRect_ClampsFraction(float pct, float expectedW) + { + var (_, _, w, _) = UiMeter.ComputeFillRect(pct, 200f, 12f); + Assert.Equal(expectedW, w); + } +}