fix(CT-GF1): UiLabel opts out of the self-clip — plugin markup text restored
CT7 gate regression (owner report): all MossTank plugin text vanished except button captions. A markup <label> authors position only, so UiLabel's box was degenerate (0x0) and CT-GF1's completed self-clip (UIRegion::DrawHere @0x0069FA30 shape) cropped its glyphs to nothing; markup buttons author w/h, which is why their captions survived. UiLabel now opts out of the self-clip — it is ClickThrough pure text whose real containment is its ancestors (the plugin panel/window, which are properly sized), the effective retail behavior for a text region whose box hugs its glyphs — and keeps a truthful box by measuring its current text each draw. Mechanism pin: an unsized label's subtree must render inside its sized parent (probe-child draw-capture test). Gate note recorded by the owner in the same round: the Titles-page divider IS visible inside the window in retail while scrolling — a retail quirk our clipped rendering now reproduces exactly. CT7 gate PASSED apart from this regression. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
9e85d82325
commit
752782d0a9
2 changed files with 68 additions and 0 deletions
|
|
@ -117,9 +117,31 @@ public class UiLabel : UiElement
|
||||||
|
|
||||||
public UiLabel() { ClickThrough = true; }
|
public UiLabel() { ClickThrough = true; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CT7 gate regression (2026-08-25, MossTank plugin text vanished): a
|
||||||
|
/// markup <c><label></c> authors position only, so this element's
|
||||||
|
/// box is degenerate (0x0) — under CT-GF1's completed
|
||||||
|
/// <c>UIRegion::DrawHere @0x0069FA30</c> port the self-clip cropped the
|
||||||
|
/// glyphs to nothing. A label must not self-clip: it is
|
||||||
|
/// <see cref="UiElement.ClickThrough"/> pure text whose real clip is
|
||||||
|
/// its ancestors (the plugin panel/window, which ARE properly sized) —
|
||||||
|
/// the same effective containment retail gives a text region whose box
|
||||||
|
/// hugs its glyphs. <see cref="OnDraw"/> below also keeps the box
|
||||||
|
/// truthful by measuring the current text, so layout/hit consumers see
|
||||||
|
/// real extents (converges the frame after a bound text change).
|
||||||
|
/// </summary>
|
||||||
|
protected override bool ClipsChildren => false;
|
||||||
|
|
||||||
protected override void OnDraw(UiRenderContext ctx)
|
protected override void OnDraw(UiRenderContext ctx)
|
||||||
{
|
{
|
||||||
string text = TextSource?.Invoke() ?? Text;
|
string text = TextSource?.Invoke() ?? Text;
|
||||||
|
float w = DatFont is { } df
|
||||||
|
? df.MeasureWidth(text)
|
||||||
|
: (ctx.DefaultFont?.MeasureWidth(text) ?? text.Length * 7f);
|
||||||
|
float h = DatFont?.LineHeight
|
||||||
|
?? ctx.DefaultFont?.LineHeight ?? 14f;
|
||||||
|
if (w != Width) Width = w;
|
||||||
|
if (h != Height) Height = h;
|
||||||
if (DatFont is { } dat)
|
if (DatFont is { } dat)
|
||||||
ctx.DrawStringDat(dat, text, 0, 0, TextColor, Outline);
|
ctx.DrawStringDat(dat, text, 0, 0, TextColor, Outline);
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -376,4 +376,50 @@ public sealed class UiAncestorClipTests
|
||||||
root.DrawOverlays(ctx);
|
root.DrawOverlays(ctx);
|
||||||
Assert.NotEmpty(renderer.DebugSpriteSegmentVerts);
|
Assert.NotEmpty(renderer.DebugSpriteSegmentVerts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CT7 gate regression (2026-08-25): the MossTank plugin's markup
|
||||||
|
/// labels lost ALL text under the completed self-clip — a markup
|
||||||
|
/// <label> authors position only, so UiLabel's box was 0x0 and the
|
||||||
|
/// element's own clip cropped every glyph. UiLabel now opts out of the
|
||||||
|
/// self-clip (its ancestors clip — the retail containment for a text
|
||||||
|
/// region whose box hugs its glyphs) and keeps a truthful measured box.
|
||||||
|
/// Button captions never regressed because markup buttons author w/h.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void UnsizedMarkupLabel_InsideASizedPanel_DoesNotSelfClipItsSubtree()
|
||||||
|
{
|
||||||
|
var (device, renderer, ctx) = MakeContext(800f, 600f);
|
||||||
|
var panel = new UiPanel
|
||||||
|
{
|
||||||
|
Left = 50f, Top = 50f, Width = 200f, Height = 100f,
|
||||||
|
BackgroundColor = default, BorderColor = default,
|
||||||
|
};
|
||||||
|
// Exactly what MarkupDocument builds for <label x=".." y=".."> —
|
||||||
|
// position only, no Width/Height (the box is degenerate at draw
|
||||||
|
// time). Pre-fix, UiLabel inherited the ambient self-clip and its
|
||||||
|
// 0x0 push cropped everything it painted to nothing — the MossTank
|
||||||
|
// vanished-text regression. The mechanism pin below hangs a
|
||||||
|
// sprite-drawing probe child under the label: with the self-clip
|
||||||
|
// the probe cannot render; with UiLabel's opt-out it must.
|
||||||
|
var label = new UiLabel { Left = 10f, Top = 10f, Text = "MossTank status line" };
|
||||||
|
var probe = new UiPanel
|
||||||
|
{
|
||||||
|
Left = 0f, Top = 0f, Width = 100f, Height = 10f,
|
||||||
|
BackgroundSprite = 42u,
|
||||||
|
SpriteResolve = id => id == 42u ? (42u, 8, 8) : (0u, 0, 0),
|
||||||
|
BackgroundColor = default, BorderColor = default,
|
||||||
|
};
|
||||||
|
label.AddChild(probe);
|
||||||
|
panel.AddChild(label);
|
||||||
|
|
||||||
|
panel.DrawSelfAndChildren(ctx);
|
||||||
|
|
||||||
|
Assert.True(
|
||||||
|
AnyQuadAt(renderer, (x, y) => x >= 60f && x <= 160f && y >= 60f && y <= 70f),
|
||||||
|
"the unsized label self-clipped its subtree away (MossTank regression)");
|
||||||
|
// The box became truthful (fallback-measured without a font), not degenerate.
|
||||||
|
Assert.True(label.Width > 0f && label.Height > 0f,
|
||||||
|
$"label box stayed degenerate: {label.Width}x{label.Height}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue