fix(ui): D.2b inventory finish — grid cells exempt from the anchor pass

Root cause of the "grid escapes the window when scrolled" bug: UiElement.
DrawSelfAndChildren runs ApplyAnchor on every child AFTER OnDraw, which captured
each cell's scroll-0 position and reset Top to it every frame, fighting
LayoutCells' scroll offset. Cells are laid out procedurally by the list, so they
must be exempt — AddItem now sets cell.Anchors = None, making LayoutCells the
sole authority. Regression test reproduces the anchor-pass interaction (the unit
tests missed it by calling LayoutCells in isolation, never the draw traversal).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-21 20:51:08 +02:00
parent 4112a53683
commit 14ea938c7f
2 changed files with 24 additions and 0 deletions

View file

@ -53,6 +53,12 @@ public sealed class UiItemList : UiElement
public void AddItem(UiItemSlot cell) public void AddItem(UiItemSlot cell)
{ {
cell.SpriteResolve ??= SpriteResolve; cell.SpriteResolve ??= SpriteResolve;
// The list lays cells out procedurally (grid offset + scroll clip), so cells must be
// EXEMPT from the parent anchor pass — UiElement.DrawSelfAndChildren runs ApplyAnchor
// on every child after OnDraw, which would otherwise reset the scroll offset to each
// cell's captured base position (the escaping-grid bug). Anchors=None makes LayoutCells
// the sole authority over cell rects.
cell.Anchors = AnchorEdges.None;
_cells.Add(cell); _cells.Add(cell);
AddChild(cell); AddChild(cell);
LayoutCells(); LayoutCells();

View file

@ -52,6 +52,24 @@ public sealed class UiItemListScrollTests
Assert.True(list.GetItem(18)!.Visible); // row 3 now visible Assert.True(list.GetItem(18)!.Visible); // row 3 now visible
} }
[Fact]
public void Scroll_survives_the_per_frame_anchor_pass()
{
// Regression (the escaping-grid bug): UiElement.DrawSelfAndChildren runs ApplyAnchor
// on every child AFTER OnDraw/LayoutCells. Grid cells must be exempt (Anchors=None) so
// the anchor pass can't reset the scroll offset LayoutCells applied — otherwise the
// grid translates out of the view when scrolled.
var list = Grid(30); // cells added (Anchors=None) + laid out
for (int i = 0; i < list.GetNumUIItems(); i++) // first anchor pass (would capture base)
list.GetItem(i)!.ApplyAnchor(list.Width, list.Height);
list.Scroll.SetScrollY(32); // scroll one row
list.LayoutCells();
for (int i = 0; i < list.GetNumUIItems(); i++) // second anchor pass (the draw traversal)
list.GetItem(i)!.ApplyAnchor(list.Width, list.Height);
Assert.Equal(0f, list.GetItem(6)!.Top); // row 1 stays at the view top
Assert.False(list.GetItem(0)!.Visible); // row 0 stays clipped above
}
[Fact] [Fact]
public void Wheel_down_scrolls_toward_the_bottom() public void Wheel_down_scrolls_toward_the_bottom()
{ {