From ceef739e1d5f9a5721584894a4b81d5fffd4ddc0 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 17 Jun 2026 16:05:50 +0200 Subject: [PATCH] fix(D.5.1): draw window-frame border over content (OnDrawAfterChildren) UiNineSlicePanel drew its full chrome in OnDraw, before children, so content painted OVER the frame. The toolbar's row-2 right cap (0x100006C0, W=8) extends 2px past the 300px content and was poking over the frame's bottom-right border (the 'missing frame' the user circled). Split the panel: center fill stays in OnDraw (background, under content); the bevel border + grip move to a new UiElement.OnDrawAfterChildren hook (foreground, over content edges) so the frame is the outermost layer. Chat is unaffected (its content is inset 5px, so the border never overlaps it). Co-Authored-By: Claude Opus 4.8 (1M context) --- src/AcDream.App/UI/UiElement.cs | 13 +++++++++++++ src/AcDream.App/UI/UiNineSlicePanel.cs | 17 ++++++++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/AcDream.App/UI/UiElement.cs b/src/AcDream.App/UI/UiElement.cs index 7e1df4ad..b22da24e 100644 --- a/src/AcDream.App/UI/UiElement.cs +++ b/src/AcDream.App/UI/UiElement.cs @@ -167,6 +167,15 @@ public abstract class UiElement /// protected virtual void OnDraw(UiRenderContext ctx) { } + /// + /// Draw AFTER this element's own children, but still within this element's + /// transform/alpha (NOT a global pass like ). Use for a + /// window FRAME border, which must be the outermost layer drawn OVER its content's + /// edges (so content can't poke through the frame), while the frame's center fill + /// stays a background in . Default: nothing. + /// + protected virtual void OnDrawAfterChildren(UiRenderContext ctx) { } + /// /// Draw content that must sit ON TOP of the ENTIRE UI, regardless of this /// element's position in the tree — open menus, dropdowns, tooltips. Called in @@ -228,6 +237,10 @@ public abstract class UiElement for (int i = 0; i < ordered.Length; i++) ordered[i].DrawSelfAndChildren(ctx); } + + // Foreground pass for this element (e.g. a window frame's border drawn + // OVER its content's edges). Default no-op for ordinary elements. + OnDrawAfterChildren(ctx); } finally { diff --git a/src/AcDream.App/UI/UiNineSlicePanel.cs b/src/AcDream.App/UI/UiNineSlicePanel.cs index 9c18f095..f407f07b 100644 --- a/src/AcDream.App/UI/UiNineSlicePanel.cs +++ b/src/AcDream.App/UI/UiNineSlicePanel.cs @@ -61,9 +61,18 @@ public sealed class UiNineSlicePanel : UiPanel protected override void OnDraw(UiRenderContext ctx) { + // Center fill is the window BACKGROUND — it must sit UNDER the content, so it + // draws here (before children). The bevel border + grip is the OUTERMOST layer + // and draws in OnDrawAfterChildren (over the content's edges) so content can + // never poke through the frame (e.g. the toolbar's 2px bottom-right cap overhang). var r = ComputeFrameRects(Width, Height, RetailChromeSprites.Border); - // center + edges tile (UV repeat); corners stretch 1:1. DrawTiled(ctx, RetailChromeSprites.CenterFill, r.Center); + } + + protected override void OnDrawAfterChildren(UiRenderContext ctx) + { + var r = ComputeFrameRects(Width, Height, RetailChromeSprites.Border); + // 8-piece bevel: edges tile (UV repeat); corners stretch 1:1. DrawTiled(ctx, RetailChromeSprites.TopEdge, r.Top); DrawTiled(ctx, RetailChromeSprites.BottomEdge, r.Bottom); DrawTiled(ctx, RetailChromeSprites.LeftEdge, r.Left); @@ -73,10 +82,8 @@ public sealed class UiNineSlicePanel : UiPanel DrawStretched(ctx, RetailChromeSprites.CornerBL, r.BL); DrawStretched(ctx, RetailChromeSprites.CornerBR, r.BR); - // Resize-grip overlay (gold ridged edges + square corner studs) drawn on - // top of the bevel — the second border layer the vitals LayoutDesc carries - // (0x1000063B–0x10000642). Edges tile; the corner stud is the same sprite - // at all four corners. + // Resize-grip overlay (gold ridged edges + square corner studs) on top of the + // bevel — the second border layer the vitals LayoutDesc carries (0x1000063B–0x10000642). DrawTiled(ctx, RetailChromeSprites.GripTop, r.Top); DrawTiled(ctx, RetailChromeSprites.GripBottom, r.Bottom); DrawTiled(ctx, RetailChromeSprites.GripLeft, r.Left);