diff --git a/src/AcDream.App/UI/UiChatView.cs b/src/AcDream.App/UI/UiChatView.cs index a2039c08..1392c26f 100644 --- a/src/AcDream.App/UI/UiChatView.cs +++ b/src/AcDream.App/UI/UiChatView.cs @@ -34,6 +34,11 @@ public sealed class UiChatView : UiElement /// Font for the transcript; falls back to the context default. public BitmapFont? Font { get; set; } + /// Retail dat font (0x40000000) for the transcript. When set, glyphs + /// render via the two-pass dat-font blit and measure/hit-test use the dat glyph + /// advance; when null, the debug BitmapFont path is used. Set by the controller. + public UiDatFont? DatFont { get; set; } + /// Keyboard device for clipboard (Ctrl+C) + modifier state. Wired by /// the host from . public Silk.NET.Input.IKeyboard? Keyboard { get; set; } @@ -49,11 +54,12 @@ public sealed class UiChatView : UiElement // Pixels the transcript is scrolled UP from the newest line (0 = pinned to bottom). private float _scroll; - private const float WheelLines = 3f; // lines advanced per wheel notch + private const float WheelLines = 1f; // lines advanced per wheel notch (retail = 1 line per notch) // ── Cached layout from the last OnDraw, so OnEvent hit-tests the SAME geometry ── private IReadOnlyList _lastLines = Array.Empty(); private BitmapFont? _lastFont; + private UiDatFont? _lastDatFont; private float _lastLineHeight = 16f; private float _lastBaseY; // top Y of line 0 in local space private float _lastPadding = 4f; @@ -85,21 +91,24 @@ public sealed class UiChatView : UiElement { ctx.DrawRect(0, 0, Width, Height, BackgroundColor); - var font = Font ?? ctx.DefaultFont; - if (font is null) return; + // Prefer the retail dat font when set; fall back to BitmapFont. + var datFont = DatFont; + var bitmapFont = datFont is null ? (Font ?? ctx.DefaultFont) : null; + if (datFont is null && bitmapFont is null) return; var lines = LinesProvider(); // Cache the geometry OnEvent will hit-test against. Even when there are no // lines we record the font/padding so a stray hit-test is harmless. _lastLines = lines; - _lastFont = font; - _lastLineHeight = font.LineHeight; + _lastDatFont = datFont; + _lastFont = bitmapFont; + _lastLineHeight = datFont is not null ? datFont.LineHeight : bitmapFont!.LineHeight; _lastPadding = Padding; if (lines.Count == 0) return; - float lh = font.LineHeight; + float lh = _lastLineHeight; float top = Padding, bottom = Height - Padding; float innerH = bottom - top; float contentH = lines.Count * lh; @@ -129,13 +138,25 @@ public sealed class UiChatView : UiElement c1 = Math.Clamp(c1, 0, text.Length); if (c1 > c0) { - float hx = Padding + font.MeasureWidth(text.Substring(0, c0)); - float hw = font.MeasureWidth(text.Substring(c0, c1 - c0)); + float hx, hw; + if (datFont is not null) + { + hx = Padding + datFont.MeasureWidth(text.Substring(0, c0)); + hw = datFont.MeasureWidth(text.Substring(c0, c1 - c0)); + } + else + { + hx = Padding + bitmapFont!.MeasureWidth(text.Substring(0, c0)); + hw = bitmapFont.MeasureWidth(text.Substring(c0, c1 - c0)); + } ctx.DrawRect(hx, y, hw, lh, SelectionColor); } } - ctx.DrawString(text, Padding, y, lines[i].Color, font); + if (datFont is not null) + ctx.DrawStringDat(datFont, text, Padding, y, lines[i].Color); + else + ctx.DrawString(text, Padding, y, lines[i].Color, bitmapFont); } } @@ -145,7 +166,7 @@ public sealed class UiChatView : UiElement { case UiEventType.Scroll: { - float lh = (Font ?? _lastFont)?.LineHeight ?? 16f; + float lh = DatFont?.LineHeight ?? (Font ?? _lastFont)?.LineHeight ?? 16f; // Silk wheel +Y = scroll up = reveal older = shift content down = larger _scroll. _scroll += e.Data0 * WheelLines * lh; // re-clamped next OnDraw against live content return true; @@ -316,11 +337,13 @@ public sealed class UiChatView : UiElement line = Math.Clamp(line, 0, lines.Count - 1); string text = lines[line].Text; - var font = _lastFont; - int col = font is null - ? 0 - : CharIndexAt(text, ch => font.TryGetGlyph(ch, out var g) ? g.Advance : 0f, - localX - _lastPadding); + int col = _lastDatFont is { } df + ? CharIndexAt(text, ch => df.TryGetGlyph(ch, out var g) ? UiDatFont.GlyphAdvance(g) : 0f, + localX - _lastPadding) + : (_lastFont is { } bf + ? CharIndexAt(text, ch => bf.TryGetGlyph(ch, out var bg) ? bg.Advance : 0f, + localX - _lastPadding) + : 0); return new Pos(line, col); } diff --git a/tests/AcDream.App.Tests/UI/UiChatViewDatFontTests.cs b/tests/AcDream.App.Tests/UI/UiChatViewDatFontTests.cs new file mode 100644 index 00000000..c00c9544 --- /dev/null +++ b/tests/AcDream.App.Tests/UI/UiChatViewDatFontTests.cs @@ -0,0 +1,30 @@ +using AcDream.App.UI; +using DatReaderWriter.Types; +using Xunit; + +namespace AcDream.App.Tests.UI; + +public class UiChatViewDatFontTests +{ + // Synthetic per-char advance: each glyph 10px (Before=2,Width=6,After=2). + private static FontCharDesc Glyph(char c) => new() + { + Unicode = c, HorizontalOffsetBefore = 2, Width = 6, HorizontalOffsetAfter = 2, + OffsetX = 0, OffsetY = 0, Height = 12, VerticalOffsetBefore = 0, + }; + + [Fact] + public void CharIndexAt_UsesDatGlyphAdvance() + { + float Adv(char c) => UiDatFont.GlyphAdvance(Glyph(c)); + Assert.Equal(0, UiChatView.CharIndexAt("abc", Adv, 4f)); + Assert.Equal(1, UiChatView.CharIndexAt("abc", Adv, 12f)); + Assert.Equal(3, UiChatView.CharIndexAt("abc", Adv, 100f)); + } + + [Fact] + public void GlyphAdvance_MatchesRetailFormula() + { + Assert.Equal(10f, UiDatFont.GlyphAdvance(Glyph('x'))); + } +}