fix(ui): render live component counts

Port retail text-surface clipping so the 15px component count field remains visible under the 16px DAT font. Cover SetStackSize decrements and final component removal while preserving desired restock values.

Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-15 18:11:02 +02:00
parent a1175e6aac
commit 0527325b25
7 changed files with 217 additions and 36 deletions

View file

@ -0,0 +1,51 @@
namespace AcDream.App.Rendering;
/// <summary>
/// Clips one screen-space textured quad and remaps its UV rectangle to the surviving
/// pixels. Shared by retained DAT sprites and fallback bitmap-font glyphs so every UI
/// draw path applies the same edge semantics.
/// </summary>
internal static class QuadClipper
{
public static bool TryClip(
float clipLeft,
float clipTop,
float clipRight,
float clipBottom,
ref float x,
ref float y,
ref float w,
ref float h,
ref float u0,
ref float v0,
ref float u1,
ref float v1)
{
if (clipRight <= clipLeft || clipBottom <= clipTop || w <= 0f || h <= 0f)
return false;
float left = MathF.Max(x, clipLeft);
float top = MathF.Max(y, clipTop);
float right = MathF.Min(x + w, clipRight);
float bottom = MathF.Min(y + h, clipBottom);
if (right <= left || bottom <= top)
return false;
float oldX = x, oldY = y, oldW = w, oldH = h;
float oldU0 = u0, oldV0 = v0, oldU1 = u1, oldV1 = v1;
float tx0 = (left - oldX) / oldW;
float tx1 = (right - oldX) / oldW;
float ty0 = (top - oldY) / oldH;
float ty1 = (bottom - oldY) / oldH;
x = left;
y = top;
w = right - left;
h = bottom - top;
u0 = oldU0 + (oldU1 - oldU0) * tx0;
u1 = oldU0 + (oldU1 - oldU0) * tx1;
v0 = oldV0 + (oldV1 - oldV0) * ty0;
v1 = oldV0 + (oldV1 - oldV0) * ty1;
return true;
}
}

View file

@ -144,6 +144,41 @@ public sealed unsafe class TextRenderer : IDisposable
/// typographic block. Handles '\n' as a line break.
/// </summary>
public void DrawString(BitmapFont font, string text, float x, float y, Vector4 color)
=> DrawStringCore(
font, text, x, y, color,
clip: false, 0f, 0f, 0f, 0f);
/// <summary>
/// Draw a bitmap-font string clipped to an absolute screen-space rectangle.
/// The retained UI uses this overload when a text element intersects its authored
/// surface edge. Retail <c>UIElement_Text::DrawSelf @ 0x00467AA0</c> clips the
/// individual glyph blits instead of discarding the whole line.
/// </summary>
internal void DrawStringClipped(
BitmapFont font,
string text,
float x,
float y,
Vector4 color,
float clipLeft,
float clipTop,
float clipRight,
float clipBottom)
=> DrawStringCore(
font, text, x, y, color,
clip: true, clipLeft, clipTop, clipRight, clipBottom);
private void DrawStringCore(
BitmapFont font,
string text,
float x,
float y,
Vector4 color,
bool clip,
float clipLeft,
float clipTop,
float clipRight,
float clipBottom)
{
float cursorX = x;
// The caller provides top-y; shift to baseline for glyph offset math.
@ -170,11 +205,19 @@ public sealed unsafe class TextRenderer : IDisposable
float gy = baseline + g.OffsetY;
float gw = g.Width;
float gh = g.Height;
float u0 = g.UvMinX;
float v0 = g.UvMinY;
float u1 = g.UvMaxX;
float v1 = g.UvMaxY;
if (gw > 0 && gh > 0)
if (gw > 0 && gh > 0
&& (!clip || QuadClipper.TryClip(
clipLeft, clipTop, clipRight, clipBottom,
ref gx, ref gy, ref gw, ref gh,
ref u0, ref v0, ref u1, ref v1)))
{
if (OverlayMode) { AppendQuad(_overlayTextBuf, gx, gy, gw, gh, g.UvMinX, g.UvMinY, g.UvMaxX, g.UvMaxY, color); _overlayTextVerts += 6; }
else { AppendQuad(_textBuf, gx, gy, gw, gh, g.UvMinX, g.UvMinY, g.UvMaxX, g.UvMaxY, color); _textVerts += 6; }
if (OverlayMode) { AppendQuad(_overlayTextBuf, gx, gy, gw, gh, u0, v0, u1, v1, color); _overlayTextVerts += 6; }
else { AppendQuad(_textBuf, gx, gy, gw, gh, u0, v0, u1, v1, color); _textVerts += 6; }
}
cursorX += g.Advance;
}