Root cause: a live-DAT probe found retail authors NO backing element behind the Character/Chat/Config tabs' Apply/Reset/Defaults footer — each page root has exactly five children (the row ListBox, its scrollbar, and the three buttons) with zero direct-state media on the root itself. Scrolled row content therefore bled through visibly between/behind the three buttons; the bleed-through is a rendering gap in our own composition, not a missing import. Fix: new minimal widget UiSolidSpriteFill tiles RetailChromeSprites.CenterFill (the SAME panel-background sprite the Options window's own chrome already draws behind everything, not an invented color) across the footer strip's rect, derived from the three buttons' own resolved Top/Height and z-ordered strictly behind every other child so it can never intercept input or occlude the buttons. Register row AP-205 records the synthesis. Regressed by OptionsPanelControllerTests. Bind_SynthesizesOneOpaqueFooterBacking_PerPageWithApplyResetDefaults, which pins exactly one backing field per page, sized from the live button rects, z-ordered behind every sibling. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
63 lines
2.6 KiB
C#
63 lines
2.6 KiB
C#
using System;
|
|
using System.Numerics;
|
|
|
|
namespace AcDream.App.UI;
|
|
|
|
/// <summary>
|
|
/// #381 (Campaign OP gate 4, 2026-08-11): a minimal, purely-synthesized
|
|
/// leaf that tiles ONE opaque sprite across its own rect — nothing more.
|
|
/// Used for the Apply/Reset/Defaults footer's opaque backing field: a
|
|
/// live-DAT probe found the Character/Chat/Config page roots author
|
|
/// EXACTLY five children each (the row ListBox, its scrollbar, and the
|
|
/// three physical buttons) with zero direct-state media on the root itself
|
|
/// — retail authors NO backdrop element behind the footer strip at all, so
|
|
/// scrolled row content bled through between/behind the three buttons.
|
|
///
|
|
/// <para>
|
|
/// This is a genuine synthesis, not a retail port — register row AP-205
|
|
/// records it. The fill sprite is <see cref="RetailChromeSprites.CenterFill"/>,
|
|
/// the SAME authored panel-background media the whole Options window's own
|
|
/// <see cref="UiNineSlicePanel"/> chrome already tiles behind everything —
|
|
/// not an invented color, the panel's own background family reused for
|
|
/// visual consistency with the rest of the window.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// <see cref="UiNineSlicePanel"/> itself was not reused here — its
|
|
/// constructor forces <c>Draggable</c>/<c>Resizable</c>/<c>Anchors.None</c>
|
|
/// (top-level-window machinery this footer strip must never have) and
|
|
/// draws a full 8-piece bevel + resize grip this borderless field
|
|
/// explicitly does not want (the issue calls for "no border").
|
|
/// </para>
|
|
/// </summary>
|
|
public sealed class UiSolidSpriteFill : UiElement
|
|
{
|
|
public uint SpriteId { get; init; }
|
|
public Func<uint, (uint tex, int w, int h)>? SpriteResolve { get; set; }
|
|
|
|
public UiSolidSpriteFill()
|
|
{
|
|
// A backing field must never intercept pointer events meant for
|
|
// whatever's stacked in front of/behind it (the buttons draw ON
|
|
// TOP via their own later ReadOrder; nothing should route to this
|
|
// leaf at all).
|
|
ClickThrough = true;
|
|
}
|
|
|
|
protected override void OnDraw(UiRenderContext ctx)
|
|
{
|
|
var resolve = SpriteResolve;
|
|
if (resolve is null || SpriteId == 0u) return;
|
|
var (tex, tw, th) = resolve(SpriteId);
|
|
if (tex == 0 || tw == 0 || th == 0) return;
|
|
// Force OPAQUE — an occluding backing field must read solid even
|
|
// when it sits inside a window whose own chrome/content is
|
|
// translucent (same "force opaque" pattern UiMenu's popup uses).
|
|
ctx.PushAlphaAbsolute(1f);
|
|
try
|
|
{
|
|
ctx.DrawSprite(tex, 0f, 0f, Width, Height, 0f, 0f, Width / tw, Height / th, Vector4.One);
|
|
}
|
|
finally { ctx.PopAlpha(); }
|
|
}
|
|
}
|