feat(ui): D.2b inventory finish — UiItemList clip+scroll via UiScrollable
Grid mode now drives a shared UiScrollable from its content + clips whole rows to the panel (cells offset by -ScrollY, Visible toggled by whole-row clip, mirroring UiText). Fill mode (toolbar single cell) unchanged. LayoutCells made internal for tests; RowCount helper added. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7aba6d235c
commit
366af0c34f
2 changed files with 89 additions and 5 deletions
|
|
@ -13,6 +13,10 @@ public sealed class UiItemList : UiElement
|
|||
{
|
||||
private readonly List<UiItemSlot> _cells = new();
|
||||
|
||||
/// <summary>Vertical scroll model for grid mode (clip+scroll). Bound to the gutter
|
||||
/// UiScrollbar by the panel controller. Inert in fill mode (single toolbar cell).</summary>
|
||||
public UiScrollable Scroll { get; } = new();
|
||||
|
||||
public UiItemList(Func<uint, (uint tex, int w, int h)>? spriteResolve = null)
|
||||
{
|
||||
SpriteResolve = spriteResolve;
|
||||
|
|
@ -63,6 +67,14 @@ public sealed class UiItemList : UiElement
|
|||
/// <summary>Fixed cell height in grid mode (pairs with CellWidth).</summary>
|
||||
public float CellHeight { get; set; }
|
||||
|
||||
/// <summary>Whole rows needed for <paramref name="cellCount"/> cells in a grid of
|
||||
/// <paramref name="columns"/> columns.</summary>
|
||||
public static int RowCount(int cellCount, int columns)
|
||||
{
|
||||
int cols = columns < 1 ? 1 : columns;
|
||||
return (cellCount + cols - 1) / cols;
|
||||
}
|
||||
|
||||
/// <summary>Row-major pixel offset of cell <paramref name="index"/> in a grid of
|
||||
/// <paramref name="columns"/> columns at the given cell pitch.</summary>
|
||||
internal static (float x, float y) CellOffset(int index, int columns, float cellW, float cellH)
|
||||
|
|
@ -72,24 +84,42 @@ public sealed class UiItemList : UiElement
|
|||
}
|
||||
|
||||
/// <summary>Position every cell per the current mode: fill (CellWidth<=0) sizes the single
|
||||
/// cell to the list; grid (CellWidth>0) tiles cells row-major at the cell pitch.</summary>
|
||||
private void LayoutCells()
|
||||
/// cell to the list; grid (CellWidth>0) tiles cells row-major, offset by the scroll position
|
||||
/// with whole-row vertical clipping (cells fully outside the view are hidden).</summary>
|
||||
internal void LayoutCells()
|
||||
{
|
||||
if (CellWidth <= 0f)
|
||||
{
|
||||
// Fill mode (the toolbar single cell): size the one cell to the list.
|
||||
if (_cells.Count > 0)
|
||||
{
|
||||
var c = _cells[0];
|
||||
c.Left = 0; c.Top = 0; c.Width = Width; c.Height = Height;
|
||||
c.Left = 0; c.Top = 0; c.Width = Width; c.Height = Height; c.Visible = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int cols = Columns < 1 ? 1 : Columns;
|
||||
int cellH = (int)MathF.Round(CellHeight);
|
||||
|
||||
// Drive the shared scroll model from the current geometry, then re-clamp the offset to
|
||||
// the (possibly changed) max. ContentHeight/ViewHeight must be set BEFORE reading ScrollY
|
||||
// so the clamp uses the right max.
|
||||
Scroll.LineHeight = cellH > 0 ? cellH : 1;
|
||||
Scroll.ContentHeight = RowCount(_cells.Count, cols) * cellH;
|
||||
Scroll.ViewHeight = (int)MathF.Floor(Height);
|
||||
Scroll.SetScrollY(Scroll.ScrollY); // re-clamp to the new max
|
||||
float scrollY = Scroll.ScrollY;
|
||||
|
||||
for (int i = 0; i < _cells.Count; i++)
|
||||
{
|
||||
var (x, y) = CellOffset(i, cols, CellWidth, CellHeight);
|
||||
var (x, baseY) = CellOffset(i, cols, CellWidth, CellHeight);
|
||||
float top = baseY - scrollY;
|
||||
var cell = _cells[i];
|
||||
cell.Left = x; cell.Top = y; cell.Width = CellWidth; cell.Height = CellHeight;
|
||||
cell.Left = x; cell.Top = top; cell.Width = CellWidth; cell.Height = CellHeight;
|
||||
// Whole-row vertical clip (no scissor — mirrors UiText.cs:198). A row fully inside
|
||||
// [0, Height] draws; a partially-scrolled row is hidden.
|
||||
cell.Visible = top >= -0.5f && top + CellHeight <= Height + 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
54
tests/AcDream.App.Tests/UI/UiItemListScrollTests.cs
Normal file
54
tests/AcDream.App.Tests/UI/UiItemListScrollTests.cs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
using AcDream.App.UI;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.App.Tests.UI;
|
||||
|
||||
public sealed class UiItemListScrollTests
|
||||
{
|
||||
private static UiItemList Grid(int items)
|
||||
{
|
||||
var list = new UiItemList { Columns = 6, CellWidth = 32, CellHeight = 32, Width = 192, Height = 96 };
|
||||
list.Flush(); // drop the default single cell
|
||||
for (int i = 0; i < items; i++) list.AddItem(new UiItemSlot());
|
||||
list.LayoutCells(); // drive the scroll model + position cells
|
||||
return list;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RowCount_ceils_to_whole_rows()
|
||||
{
|
||||
Assert.Equal(0, UiItemList.RowCount(0, 6));
|
||||
Assert.Equal(1, UiItemList.RowCount(1, 6));
|
||||
Assert.Equal(7, UiItemList.RowCount(41, 6));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Grid_drives_scroll_model_from_content()
|
||||
{
|
||||
var list = Grid(30); // 5 rows × 32 = 160 content, 96 view
|
||||
Assert.Equal(160, list.Scroll.ContentHeight);
|
||||
Assert.Equal(96, list.Scroll.ViewHeight);
|
||||
Assert.Equal(64, list.Scroll.MaxScroll);
|
||||
Assert.True(list.Scroll.HasOverflow);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void At_top_first_three_rows_visible_rest_clipped()
|
||||
{
|
||||
var list = Grid(30);
|
||||
Assert.True(list.GetItem(0)!.Visible); // row 0, top 0
|
||||
Assert.True(list.GetItem(12)!.Visible); // row 2, top 64 (+32 == 96)
|
||||
Assert.False(list.GetItem(18)!.Visible); // row 3, top 96 (off the bottom)
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Scrolled_one_row_shifts_window_and_clips_top_row()
|
||||
{
|
||||
var list = Grid(30);
|
||||
list.Scroll.SetScrollY(32);
|
||||
list.LayoutCells();
|
||||
Assert.False(list.GetItem(0)!.Visible); // row 0 scrolled above
|
||||
Assert.Equal(0f, list.GetItem(6)!.Top); // row 1 now at the view top
|
||||
Assert.True(list.GetItem(18)!.Visible); // row 3 now visible
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue