fix(ui): round-5 review polish — S1 block outline pass, S2 non-UiText outline paths, S3 citation fix
Collects the post-gate polish left uncommitted by the killed round-5 agent (S1/S3 + review fixes N1/N3/N4) and completes the missing S2 half: - S1: UiText multi-line transcript + colored-run label now submit EVERY line/run's outline pass before ANY fill pass, matching retail's UIElement_Text::DrawSelf @0x00467aa0 whole-block walk. DrawStringDatPass is exposed for block-level batching; single lines keep DrawStringDat. - S2 (completed this commit): authored outline 0x21/0x22 now reaches every text-bearing widget — UiButton, UiDatElement, UiField, UiMeter, UiMenu, UiCatalogSlot — seeded from the element's effective-default state exactly like UiText (BuildButton lifts the label-bearing Text child's authored value first, same chain as the label color). Per-STATE outline switching (dialog/character/combat buttons author 0x21 in state 0x3 only) is NOT ported — filed as register row AP-192 in this commit. - S3: ChatWindowController reconciliation comment corrects the misread indicator action ids 0x10000514-17 -> 0x10000114-17 and re-attributes the id-coincidence to the pagination widget's m_prevButton/m_nextButton, not gmFriendsUI; register + window-shell research doc corrected to match. - N1: LayoutImporter's duplicate per-state any-state-first-wins 0x21 read is deleted — ElementReader.ApplyCanonicalLegacyProjection's DirectState-then- effective-default resolution is the single source (the duplicate would have lit state-0x3-only outlines permanently once S2 widened consumption). - N3: the outline pass tints with the outline color's OWN alpha, not the fill's (retail tints m_curOutlineColor and m_curTextColor independently). - N4: the outline-inflated glyph SOURCE rect is clamped to the atlas bounds with matching dest shrink, porting CreateCharRectPair @0x00441480's edge behavior — edge glyphs crop instead of sampling a neighbour's texels. Full Release suite: 12,610 passed / 4 skipped / 0 failed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
ed0dbff90a
commit
aa6635aebf
14 changed files with 258 additions and 66 deletions
|
|
@ -263,6 +263,48 @@ public sealed class UiRenderContext
|
|||
{
|
||||
if (font is null || string.IsNullOrEmpty(text)) return;
|
||||
|
||||
if (outline)
|
||||
{
|
||||
// PASS 0 — outline, whole string (acclient 0x00467aa0, var_b0 starts at 0).
|
||||
// The outline tint uses the outline color's OWN alpha (round-5 review N3):
|
||||
// an earlier version substituted the FILL color's alpha here, which is not
|
||||
// what retail's DrawSelf does (the outline and fill passes tint independent
|
||||
// RGBA colors, m_curOutlineColor and m_curTextColor) and made a translucent
|
||||
// outline color always render at the fill's opacity instead of its own.
|
||||
DrawStringDatPass(font, text, x, y, outlineColor ?? DefaultOutlineColor, isOutlinePass: true);
|
||||
}
|
||||
|
||||
// PASS 1 (or the only pass, when outline is off) — fill, whole string.
|
||||
DrawStringDatPass(font, text, x, y, color, isOutlinePass: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// One whole-string pass of <see cref="DrawStringDat"/>'s two-pass outline+fill
|
||||
/// model: either every glyph's outline (<paramref name="isOutlinePass"/> true) or
|
||||
/// every glyph's fill. Recomputes the pen from scratch — a pure function of
|
||||
/// <paramref name="font"/>/<paramref name="text"/>/<paramref name="x"/>/
|
||||
/// <paramref name="y"/>, so repeated calls advance identically and stay in
|
||||
/// lock-step without sharing mutable state.
|
||||
///
|
||||
/// <para>
|
||||
/// <b>Exposed for BLOCK-level batching</b> (round-5 review S1): a caller that draws
|
||||
/// several lines or runs sharing ONE <c>Outline</c> flag (<see cref="AcDream.App.UI.UiText"/>'s
|
||||
/// multi-line transcript and colored-run label) must submit EVERY line's outline
|
||||
/// pass before ANY line's fill pass — retail's <c>UIElement_Text::DrawSelf</c>
|
||||
/// (0x00467aa0) walks every glyph of the whole block in the outline pass before any
|
||||
/// fill. Calling <see cref="DrawStringDat"/> once per line instead (outline+fill,
|
||||
/// outline+fill, ...) lets line N+1's outline draw AFTER line N's fill and notch a
|
||||
/// descender that pokes into the line above. A caller with several lines should
|
||||
/// call this once per line with <paramref name="isOutlinePass"/>=true for every
|
||||
/// line, THEN once per line with <paramref name="isOutlinePass"/>=false for every
|
||||
/// line. A single independent line can still call <see cref="DrawStringDat"/> directly.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public void DrawStringDatPass(
|
||||
UiDatFont font, string text, float x, float y, Vector4 tint, bool isOutlinePass)
|
||||
{
|
||||
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.
|
||||
|
|
@ -281,26 +323,6 @@ public sealed class UiRenderContext
|
|||
// line on one row and pixel-aligned.
|
||||
float baseY = System.MathF.Round(originY);
|
||||
|
||||
if (outline)
|
||||
{
|
||||
Vector4 oc = outlineColor ?? DefaultOutlineColor;
|
||||
var outlineTint = new Vector4(oc.X, oc.Y, oc.Z, color.W);
|
||||
// PASS 0 — outline, whole string (acclient 0x00467aa0, var_b0 starts at 0).
|
||||
DrawStringDatPass(font, text, originX, baseY, outlineTint, isOutlinePass: true);
|
||||
}
|
||||
|
||||
// PASS 1 (or the only pass, when outline is off) — fill, whole string.
|
||||
DrawStringDatPass(font, text, originX, baseY, color, isOutlinePass: false);
|
||||
}
|
||||
|
||||
/// <summary>One whole-string pass of <see cref="DrawStringDat"/>'s two-pass
|
||||
/// model: either every glyph's outline (<paramref name="isOutlinePass"/> true)
|
||||
/// or every glyph's fill. Recomputes the pen from scratch — a pure function of
|
||||
/// <paramref name="font"/>/<paramref name="text"/>, so both passes advance
|
||||
/// identically and stay in lock-step without sharing mutable state.</summary>
|
||||
private void DrawStringDatPass(
|
||||
UiDatFont font, string text, float originX, float baseY, Vector4 tint, bool isOutlinePass)
|
||||
{
|
||||
float pen = originX;
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
{
|
||||
|
|
@ -309,7 +331,7 @@ public sealed class UiRenderContext
|
|||
|
||||
// Horizontal: snap each glyph's dest X to a whole pixel (the pen keeps its
|
||||
// true fractional advance). Vertical: integer baseline + integer per-glyph
|
||||
// offset — never an independent per-glyph round (see DrawStringDat's baseY note).
|
||||
// offset — never an independent per-glyph round (see baseY's note above).
|
||||
float gx = System.MathF.Round(pen + g.HorizontalOffsetBefore);
|
||||
float gy = baseY + g.VerticalOffsetBefore;
|
||||
float gw = g.Width;
|
||||
|
|
@ -361,10 +383,37 @@ public sealed class UiRenderContext
|
|||
float iy = gy - by;
|
||||
float iw = gw + 2f * bx;
|
||||
float ih = gh + 2f * by;
|
||||
|
||||
// Clamp the inflated SOURCE rect to the atlas bounds (round-5 review N4):
|
||||
// a glyph whose art sits at the edge of the background atlas can inflate
|
||||
// past [0, BackgroundWidth/Height], which produced UV coordinates outside
|
||||
// [0,1] and could sample a neighbouring glyph's texels. Retail's own
|
||||
// CreateCharRectPair (0x00441480) clamps both rects the same way — when a
|
||||
// side of the SOURCE rect would fall outside the atlas, that side is pulled
|
||||
// back to the edge and the DESTINATION rect shrinks by the same amount, so
|
||||
// the visible outline is simply cropped rather than mis-sampled. A no-op
|
||||
// (byte-identical output) for every glyph that doesn't sit on the atlas edge.
|
||||
float srcX = g.OffsetX - bx, srcY = g.OffsetY - by;
|
||||
float srcW = iw, srcH = ih;
|
||||
float destX = ix, destY = iy, destW = iw, destH = ih;
|
||||
if (srcX < 0f) { destX -= srcX; destW += srcX; srcW += srcX; srcX = 0f; }
|
||||
if (srcY < 0f) { destY -= srcY; destH += srcY; srcH += srcY; srcY = 0f; }
|
||||
if (srcX + srcW > font.BackgroundWidth)
|
||||
{
|
||||
float over = srcX + srcW - font.BackgroundWidth;
|
||||
srcW -= over; destW -= over;
|
||||
}
|
||||
if (srcY + srcH > font.BackgroundHeight)
|
||||
{
|
||||
float over = srcY + srcH - font.BackgroundHeight;
|
||||
srcH -= over; destH -= over;
|
||||
}
|
||||
if (srcW <= 0f || srcH <= 0f) return;
|
||||
|
||||
var (bu0, bv0, bu1, bv1) = AtlasUv(
|
||||
g.OffsetX - bx, g.OffsetY - by, (int)iw, (int)ih,
|
||||
(int)srcX, (int)srcY, (int)srcW, (int)srcH,
|
||||
font.BackgroundWidth, font.BackgroundHeight);
|
||||
DrawSpriteAbsolute(font.BackgroundTexture, ix, iy, iw, ih, bu0, bv0, bu1, bv1, tint, applyAlpha: true);
|
||||
DrawSpriteAbsolute(font.BackgroundTexture, destX, destY, destW, destH, bu0, bv0, bu1, bv1, tint, applyAlpha: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue