From f8e5edb8486f7c41b2d45c96944f4a110db2f5d3 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 6 Sep 2026 21:09:41 +0200 Subject: [PATCH] =?UTF-8?q?fix(vt):=20list=20column=20fix=20round=207/11?= =?UTF-8?q?=20=E2=80=94=20short=20check=20columns=20draw=20unchecked?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DrawCheckCell drew nothing for a row past its own column's bound data — inconsistent with docs/plugin-ui-markup.md's own description of VVS's HudList, which materializes every cell in a row regardless of which columns actually have data for it. Text/icon cells rightly stay no-draw past their own count (no sensible default string or icon), but a check cell always has one: unchecked. DrawCheckCell now computes isChecked defensively (false when the flags array is null or too short) instead of returning early, so a short check column draws the unchecked lamp for every row past its own data just like every other row. New test: a 3-row list (driven by a longer text column) with a check column bound to only 1 row now shows the unchecked lamp for rows 1 and 2 — shown to fail first (0 unchecked lamp quads, since DrawCheckCell returned early past index 0). Co-Authored-By: Claude Fable 5.1 --- src/AcDream.App/UI/UiMarkupList.cs | 15 ++++++-- .../UI/MarkupListColumnsTests.cs | 37 +++++++++++++++++++ 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/AcDream.App/UI/UiMarkupList.cs b/src/AcDream.App/UI/UiMarkupList.cs index 17d1d7045..ae28b1949 100644 --- a/src/AcDream.App/UI/UiMarkupList.cs +++ b/src/AcDream.App/UI/UiMarkupList.cs @@ -385,13 +385,22 @@ public sealed class UiMarkupList : UiElement /// so a check column reads exactly like every other checkbox in the /// client (contract requirement: reuse the toggle's own primitives rather /// than a bespoke box-and-tick). + /// + /// + /// Fix round item 7: unlike text/icon cells (which draw nothing past + /// their own column's row count — there is no sensible default string or + /// icon), a check cell past its own bound data still draws the + /// UNCHECKED lamp. VVS materializes every cell in the row regardless of + /// which columns actually have data for it; docs/plugin-ui-markup.md + /// already documented this ("short columns simply have nothing to draw" + /// was never meant to apply to check specifically) — this makes the code + /// agree. + /// /// private void DrawCheckCell( UiRenderContext context, IReadOnlyList? flags, int index, float cellX, float y) { - if (flags is null || index >= flags.Count) - return; - bool isChecked = flags[index]; + bool isChecked = flags is not null && index < flags.Count && flags[index]; Vector4 outer = isChecked ? UiMarkupToggle.CheckedOuter : UiMarkupToggle.UncheckedOuter; Vector4 inner = isChecked ? UiMarkupToggle.CheckedInner : UiMarkupToggle.UncheckedInner; UiMarkupToggle.DrawLamp( diff --git a/tests/AcDream.App.Tests/UI/MarkupListColumnsTests.cs b/tests/AcDream.App.Tests/UI/MarkupListColumnsTests.cs index fcacc8b9e..3c5e9b61d 100644 --- a/tests/AcDream.App.Tests/UI/MarkupListColumnsTests.cs +++ b/tests/AcDream.App.Tests/UI/MarkupListColumnsTests.cs @@ -672,6 +672,43 @@ public sealed class MarkupListColumnsTests $"expected row 1's glyph ({uncheckedY}) below row 0's ({checkedY})"); } + // ── Fix round: short check columns draw the unchecked lamp past their + // own row count (fix item 7) ──────────────────────────────────────────── + + [Fact] + public void CheckColumn_DrawsUncheckedLamp_ForRowsPastItsOwnRowCount() + { + // col0 (text) has 3 rows, driving the overall row count. col1 (last, + // check) only binds 1 row of its own — VVS materializes every cell + // regardless (docs/plugin-ui-markup.md already documented this), so + // rows 1 and 2 must STILL draw an unchecked lamp, not nothing. + var list = new UiMarkupList + { + Width = 60f, Height = 60f, RowHeight = 18f, + SelectedIndexSource = () => -1, + BackgroundColor = default, BorderColor = default, + Columns = new[] + { + UiMarkupListColumn.Text(20f, () => new[] { "a", "b", "c" }, null), + UiMarkupListColumn.Check(20f, () => new[] { true }, _ => { }), + }, + }; + var (renderer, ctx) = MakeContext(200f, 200f); + + list.DrawSelfAndChildren(ctx); + + var uncheckedQuads = renderer.DebugSpriteSegmentVerts + .SelectMany(s => Chunk(s.Verts)) + .Where(v => ColorMatches(v, UiMarkupToggle.UncheckedInner)) + .ToList(); + // One unchecked lamp for row 1 (bool value false — not this test's + // case) never applies here since col1's only row (0) is TRUE; rows 1 + // and 2 have no bound value at all and must still draw UNCHECKED. + Assert.True(uncheckedQuads.Count >= 2, + $"expected an unchecked lamp for both row 1 and row 2 (past col1's own " + + $"1-row data), got {uncheckedQuads.Count} unchecked lamp quad(s)"); + } + [Fact] public void IconColumn_DrawsResolvedRowIcon_AndSkipsAMissingOne() {