diff --git a/src/AcDream.App/UI/UiPanel.cs b/src/AcDream.App/UI/UiPanel.cs index 5b5e81df..926eb1e8 100644 --- a/src/AcDream.App/UI/UiPanel.cs +++ b/src/AcDream.App/UI/UiPanel.cs @@ -117,9 +117,31 @@ public class UiLabel : UiElement public UiLabel() { ClickThrough = true; } + /// + /// CT7 gate regression (2026-08-25, MossTank plugin text vanished): a + /// markup <label> authors position only, so this element's + /// box is degenerate (0x0) — under CT-GF1's completed + /// UIRegion::DrawHere @0x0069FA30 port the self-clip cropped the + /// glyphs to nothing. A label must not self-clip: it is + /// 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. 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). + /// + protected override bool ClipsChildren => false; + protected override void OnDraw(UiRenderContext ctx) { 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) ctx.DrawStringDat(dat, text, 0, 0, TextColor, Outline); else diff --git a/tests/AcDream.App.Tests/UI/UiAncestorClipTests.cs b/tests/AcDream.App.Tests/UI/UiAncestorClipTests.cs index 22f976f8..d0ba8eff 100644 --- a/tests/AcDream.App.Tests/UI/UiAncestorClipTests.cs +++ b/tests/AcDream.App.Tests/UI/UiAncestorClipTests.cs @@ -376,4 +376,50 @@ public sealed class UiAncestorClipTests root.DrawOverlays(ctx); Assert.NotEmpty(renderer.DebugSpriteSegmentVerts); } + + /// + /// 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. + /// + [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