using System.Numerics; using AcDream.App.Rendering; namespace AcDream.App.UI; /// /// Per-frame drawing context passed through the /// tree. Wraps a (our 2D sprite batcher) and a /// transform stack so elements can draw in local coordinates. /// /// Retail equivalent: the implicit context FUN_005da8f0 walks with /// when iterating the UI tree. Our version is explicit so it plugs /// cleanly into Silk.NET. /// public sealed class UiRenderContext { public TextRenderer TextRenderer { get; } public BitmapFont? DefaultFont { get; set; } public Vector2 ScreenSize { get; } // Transform stack — simple 2D translate (no rotation/scale for UI). private readonly System.Collections.Generic.List _stack = new(); private Vector2 _current; public UiRenderContext(TextRenderer tr, Vector2 screenSize, BitmapFont? defaultFont = null) { TextRenderer = tr; ScreenSize = screenSize; DefaultFont = defaultFont; } /// Push a relative translate. Must be paired with . public void PushTransform(float dx, float dy) { _stack.Add(_current); _current += new Vector2(dx, dy); } public void PopTransform() { if (_stack.Count == 0) return; _current = _stack[^1]; _stack.RemoveAt(_stack.Count - 1); } public Vector2 CurrentOrigin => _current; // ── Pass-through draw helpers (add current translate) ────────────── public void DrawRect(float x, float y, float w, float h, Vector4 color) => TextRenderer.DrawRect(_current.X + x, _current.Y + y, w, h, color); public void DrawRectOutline(float x, float y, float w, float h, Vector4 color, float thickness = 1f) => TextRenderer.DrawRectOutline(_current.X + x, _current.Y + y, w, h, color, thickness); public void DrawSprite(uint texture, float x, float y, float w, float h, float u0, float v0, float u1, float v1, Vector4 tint) => TextRenderer.DrawSprite(texture, _current.X + x, _current.Y + y, w, h, u0, v0, u1, v1, tint); public void DrawString(string text, float x, float y, Vector4 color, BitmapFont? font = null) { var f = font ?? DefaultFont; if (f is null) return; TextRenderer.DrawString(f, text, _current.X + x, _current.Y + y, color); } /// /// Draw a single line of text with a retail dat font (), /// at , = the top-left of the /// typographic block (in this element's local space). Mirrors retail's /// SurfaceWindow::DrawCharacter (acclient 0x00442bd0): for each glyph /// the BACKGROUND atlas sub-rect is blitted first tinted black (the outline), /// then the FOREGROUND atlas sub-rect tinted (the /// fill). The pen advances by /// HorizontalOffsetBefore + Width + HorizontalOffsetAfter and each /// glyph is positioned at pen + HorizontalOffsetBefore on the X axis /// and at baseline + VerticalOffsetBefore - (BaselineOffset) via the /// glyph's OffsetY into the atlas. If the font has no background atlas the /// outline pass is skipped. /// public void DrawStringDat(UiDatFont font, string text, float x, float y, Vector4 color) { if (font is null || string.IsNullOrEmpty(text)) return; // Baseline of this line in local space; retail draws glyphs whose // descriptor OffsetY already places them relative to the line top, so we // anchor each glyph's quad at the line top (y) plus its VerticalOffsetBefore. float originX = _current.X + x; float originY = _current.Y + y; float pen = originX; var outline = new Vector4(0f, 0f, 0f, color.W); for (int i = 0; i < text.Length; i++) { if (!font.TryGetGlyph(text[i], out var g)) continue; float gx = pen + g.HorizontalOffsetBefore; float gy = originY + g.VerticalOffsetBefore; float gw = g.Width; float gh = g.Height; if (gw > 0f && gh > 0f) { // Background (outline) atlas pass, tinted black — drawn behind. if (font.BackgroundTexture != 0) { var (bu0, bv0, bu1, bv1) = AtlasUv( g.OffsetX, g.OffsetY, g.Width, g.Height, font.BackgroundWidth, font.BackgroundHeight); TextRenderer.DrawSprite(font.BackgroundTexture, gx, gy, gw, gh, bu0, bv0, bu1, bv1, outline); } // Foreground (fill) atlas pass, tinted with the requested color. var (fu0, fv0, fu1, fv1) = AtlasUv( g.OffsetX, g.OffsetY, g.Width, g.Height, font.ForegroundWidth, font.ForegroundHeight); TextRenderer.DrawSprite(font.ForegroundTexture, gx, gy, gw, gh, fu0, fv0, fu1, fv1, color); } pen += UiDatFont.GlyphAdvance(g); } } /// Convert an (OffsetX,OffsetY,Width,Height) atlas pixel sub-rect to /// normalized UVs for an atlas of x /// . Guards against a zero-sized atlas. private static (float u0, float v0, float u1, float v1) AtlasUv( int offsetX, int offsetY, int width, int height, int atlasW, int atlasH) { if (atlasW <= 0 || atlasH <= 0) return (0f, 0f, 0f, 0f); float u0 = offsetX / (float)atlasW; float v0 = offsetY / (float)atlasH; float u1 = (offsetX + width) / (float)atlasW; float v1 = (offsetY + height) / (float)atlasH; return (u0, v0, u1, v1); } }