ROOT CAUSE (proven, not guessed): the lazily-created row viewport was constructed 0x0 with Left|Top|Right|Bottom fill-anchors. Its first ApplyAnchor captured mR = parentW - (0+0) = parentW, so ComputeAnchoredRect's l&&r branch (w = parentW - mR - mL) kept it 0x0 forever. A 0-tall viewport makes UiScrollablePanel.LayoutScrollableChildren cull every row, so Character/Chat/Config rendered blank while Gameplay (no viewport — authored static children sized at Build) worked. This is the exact Gameplay-vs-rest split the user's first connected gate found. FIX: seed the viewport to the ListBox's current extent at creation, so the fill-anchor baseline is mR = parentW - parentW = 0 and the viewport tracks the parent. The ListBox is a static dat child sized at Build, so its extent is authored by the time the viewport is lazily created during Bind. Dormancy preserved — the viewport is still created only on the first row. Reproduced RED then GREEN by UiTemplateListBoxViewportTests (viewport fills; rows stay visible after the anchor+cull layout pass) — the layout path the whole fixture conformance suite structurally never drove, which is why every OP2-OP6 test was green over a live-only blank-tab failure. Full Release suite 13,131 / 4 skips / 0 failed. Still owed (NOT fixed here, no evidence yet): the 'only Exit Game worked' Gameplay-buttons observation needs a re-gate (two buttons are correctly INERT; the other four have dialog/chat effects that may have gone unnoticed); and the 13 ID_ChatOption_TextFilter_* labels fail to resolve (blank captions, behaviour unaffected). See #372. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
79 lines
3.3 KiB
C#
79 lines
3.3 KiB
C#
using AcDream.App.UI;
|
||
using AcDream.App.UI.Layout;
|
||
|
||
namespace AcDream.App.Tests.UI;
|
||
|
||
/// <summary>
|
||
/// #372 regression: the Options panel's Character/Chat/Config tabs rendered
|
||
/// BLANK at the first connected gate while Gameplay (which has no ListBox)
|
||
/// worked. Root cause: <see cref="UiTemplateListBox"/> creates its row
|
||
/// viewport at 0×0 with Left|Top|Right|Bottom fill-anchors; the anchor
|
||
/// baseline captured from that 0-size rect makes
|
||
/// <c>ComputeAnchoredRect</c> keep it 0×0 forever (mR=parentW, so
|
||
/// w=parentW-mR-mL=0), and <see cref="UiScrollablePanel.LayoutScrollableChildren"/>
|
||
/// then culls every row (top+height ≤ 0 is false). These tests pin that the
|
||
/// viewport fills its ListBox and the rows stay visible after a layout pass.
|
||
/// </summary>
|
||
public sealed class UiTemplateListBoxViewportTests
|
||
{
|
||
private static UiTemplateListBox MakeListBox(float width, float height)
|
||
{
|
||
// A ListBox element authoring one template entry, sized like the
|
||
// Options-panel Character ListBox (0x100001FA, 276×560 authored).
|
||
var info = new ElementInfo { Id = 0x100001FAu, Type = 5u };
|
||
var box = new UiTemplateListBox(
|
||
info,
|
||
_ => (0u, 0, 0),
|
||
new[] { new UiTemplateListEntry(0x2100002Bu, 0x10000218u) },
|
||
scrollbarElementId: 0x100001FBu)
|
||
{
|
||
Width = width,
|
||
Height = height,
|
||
};
|
||
return box;
|
||
}
|
||
|
||
[Fact]
|
||
public void Viewport_FillsTheListBox_NotCollapsedToZero()
|
||
{
|
||
var box = MakeListBox(276f, 560f);
|
||
// Add a couple of rows via the prebuilt path (no DAT needed).
|
||
box.AddPrebuiltRow(new UiText { Width = 260f, Height = 20f });
|
||
box.AddPrebuiltRow(new UiText { Width = 260f, Height = 20f });
|
||
|
||
UiScrollablePanel? viewport = box.ViewportForTest;
|
||
Assert.NotNull(viewport);
|
||
|
||
// Drive the per-frame layout the draw traversal runs: the parent
|
||
// ListBox re-anchors its children (UiElement.DrawSelfAndChildren
|
||
// line ~500), sizing the fill-anchored viewport.
|
||
viewport!.ApplyAnchor(box.Width, box.Height);
|
||
|
||
// The whole point of #372: the viewport must FILL the ListBox, not
|
||
// collapse. Pre-fix this was 0×0.
|
||
Assert.Equal(276f, viewport.Width, 3);
|
||
Assert.Equal(560f, viewport.Height, 3);
|
||
}
|
||
|
||
[Fact]
|
||
public void Rows_StayVisible_AfterLayout_WhenTheyFitTheViewport()
|
||
{
|
||
var box = MakeListBox(276f, 560f);
|
||
var row0 = new UiText { Width = 260f, Height = 20f };
|
||
var row1 = new UiText { Width = 260f, Height = 20f };
|
||
var row2 = new UiText { Width = 260f, Height = 20f };
|
||
box.AddPrebuiltRow(row0);
|
||
box.AddPrebuiltRow(row1);
|
||
box.AddPrebuiltRow(row2);
|
||
|
||
UiScrollablePanel viewport = box.ViewportForTest!;
|
||
viewport.ApplyAnchor(box.Width, box.Height); // size the viewport
|
||
viewport.LayoutScrollableChildren(); // the cull pass (runs each OnDraw)
|
||
|
||
// All three rows sit at y 0/20/40 inside a 560px viewport → visible.
|
||
// Pre-fix the viewport was 0px tall and every row was culled → blank tab.
|
||
Assert.True(row0.Visible, "row 0 culled — the #372 blank-tab bug");
|
||
Assert.True(row1.Visible, "row 1 culled — the #372 blank-tab bug");
|
||
Assert.True(row2.Visible, "row 2 culled — the #372 blank-tab bug");
|
||
}
|
||
}
|