merge(vt): plugin <list> draws no selection band by default (selectionband="true" opts in)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
commit
7b7e5e579a
4 changed files with 158 additions and 3 deletions
|
|
@ -110,7 +110,7 @@ vanishing from the built tree.
|
|||
| `slider` | Horizontal scalar | `x y w h value onchange anchor` |
|
||||
| `field` | Single-line editable text | `x y w h text maxlength clearonsubmit onchange onsubmit color background anchor` |
|
||||
| `menu` | Dropdown selector | `x y w h items selected onchange rows rowheight openupward style anchor` |
|
||||
| `list` | Scrollable row list (+ Slice B icon column, + Campaign VT slice 1 multi-column) | `x y w h selected onchange rowheight anchor` + either the single-column `items colors icons iconkind`, or one-to-many `<column>` children (see "Columns" below) — never both |
|
||||
| `list` | Scrollable row list (+ Slice B icon column, + Campaign VT slice 1 multi-column) | `x y w h selected onchange rowheight selectionband anchor` + either the single-column `items colors icons iconkind`, or one-to-many `<column>` children (see "Columns" below) — never both |
|
||||
|
||||
`menu style` is `plain` (the default) or `retail`: retail's gold pushbutton
|
||||
art read as an out-of-place "big yellow button" next to a plugin's own dark
|
||||
|
|
@ -410,6 +410,11 @@ attributes:
|
|||
</list>
|
||||
```
|
||||
|
||||
VVS lists draw no persistent row-selection fill, so `<list>` matches that by
|
||||
default in both the single-column and `<column>` forms — a plugin that wants
|
||||
a visible band anyway sets `selectionband="true"` (row selection itself,
|
||||
including scroll-into-view, is unaffected either way).
|
||||
|
||||
This mirrors VTank's own Monsters tab (several boolean flag columns, a name
|
||||
column, and icon-button columns) — see
|
||||
`docs/research/vtank-kb/08-ui-views.md` §3's "Multi-column lists with typed
|
||||
|
|
|
|||
|
|
@ -594,6 +594,12 @@ public static class MarkupDocument
|
|||
binding,
|
||||
"list selected"),
|
||||
SelectionChanged = listChanged,
|
||||
// Campaign VT slice 7 resemblance re-check: VVS lists draw
|
||||
// no persistent row-selection fill by default (matches
|
||||
// both single-column and <column> mode now — see
|
||||
// UiMarkupList.SelectionBandEnabled). A plugin that wants
|
||||
// one back opts in with <list selectionband="true">.
|
||||
SelectionBandEnabled = B(el, "selectionband", false),
|
||||
};
|
||||
|
||||
if (listUsesColumns)
|
||||
|
|
|
|||
|
|
@ -73,6 +73,25 @@ public sealed class UiMarkupList : UiElement
|
|||
public Vector4 TextColor { get; set; } = new(0.91f, 0.87f, 0.76f, 1f);
|
||||
public Vector4 SelectedColor { get; set; } = new(0.28f, 0.23f, 0.08f, 0.95f);
|
||||
|
||||
/// <summary>
|
||||
/// Campaign VT slice 7 resemblance re-check (2026-09-07): real VVS lists
|
||||
/// (VTank's own <c>HudList</c>) draw no persistent row-selection fill at
|
||||
/// all — before this fix the column-less <c>items=</c> mode drew
|
||||
/// <see cref="SelectedColor"/> under the selected row while the
|
||||
/// <c><column></c> mode did the same, so a plugin's Buffs lists
|
||||
/// highlighted a row while the Monsters/Meta grids happened not to (or
|
||||
/// vice versa, depending on which mode a given list used) — same-looking
|
||||
/// widgets, inconsistent behavior. Default false now suppresses the fill
|
||||
/// in BOTH <see cref="OnDraw"/>'s legacy branch and
|
||||
/// <see cref="DrawColumns"/>, matching VVS. <c><list
|
||||
/// selectionband="true"></c> (parsed in <see cref="MarkupDocument"/>'s
|
||||
/// <c>case "list"</c>) opts a single list back into a visible band for
|
||||
/// plugins that want one. This gates ONLY the fill — <see cref="SelectedIndexSource"/>,
|
||||
/// <see cref="SelectionChanged"/>, and the selected-row scroll-into-view
|
||||
/// logic in <see cref="OnDraw"/>/<see cref="DrawColumns"/> are unchanged.
|
||||
/// </summary>
|
||||
public bool SelectionBandEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Owner live-client report 2026-09-07 ("For scrollable dropdown or the
|
||||
/// meta window we use the same assets as we do in for example chat or
|
||||
|
|
@ -163,7 +182,7 @@ public sealed class UiMarkupList : UiElement
|
|||
for (int index = _topRow; index < end; index++)
|
||||
{
|
||||
float y = (index - _topRow) * RowHeight;
|
||||
if (index == selected)
|
||||
if (index == selected && SelectionBandEnabled)
|
||||
context.DrawFill(1f, y + 1f, contentWidth - 2f, RowHeight - 1f, SelectedColor);
|
||||
|
||||
if (iconIds is not null && index < iconIds.Count && IconResolve is { } resolve)
|
||||
|
|
@ -380,7 +399,7 @@ public sealed class UiMarkupList : UiElement
|
|||
for (int index = _topRow; index < end; index++)
|
||||
{
|
||||
float y = (index - _topRow) * RowHeight;
|
||||
if (index == selected)
|
||||
if (index == selected && SelectionBandEnabled)
|
||||
context.DrawFill(1f, y + 1f, contentWidth - 2f, RowHeight - 1f, SelectedColor);
|
||||
|
||||
for (int c = 0; c < columns.Count; c++)
|
||||
|
|
|
|||
|
|
@ -1126,6 +1126,18 @@ public sealed class MarkupListColumnsTests
|
|||
/// entirely — i.e. against the widget's own unmodified
|
||||
/// <c>OnDraw</c>/legacy branch, not a snapshot captured from a different
|
||||
/// commit. Every vertex float must match exactly.
|
||||
///
|
||||
/// <para>
|
||||
/// Deliberately updated for the slice-7 resemblance re-check's
|
||||
/// <see cref="UiMarkupList.SelectionBandEnabled"/> fix: <c>SelectedIndex
|
||||
/// = 1</c> (a real selected row) previously meant this byte-for-byte
|
||||
/// comparison implicitly included the <see cref="UiMarkupList.SelectedColor"/>
|
||||
/// fill quad on BOTH sides. Neither the markup XML nor the hand-built
|
||||
/// widget below sets <c>selectionband</c>/<see cref="UiMarkupList.SelectionBandEnabled"/>,
|
||||
/// so that fill is now absent from both — the explicit assertion at the
|
||||
/// end locks in that the new no-band default applies here too, rather
|
||||
/// than leaving it to accidentally fall out of the byte-for-byte diff.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ColumnLessList_ProducesTheIdenticalDrawRecordToTheHandBuiltWidget()
|
||||
|
|
@ -1181,6 +1193,119 @@ public sealed class MarkupListColumnsTests
|
|||
}
|
||||
|
||||
Assert.Null(viaMarkup.Columns);
|
||||
|
||||
// Neither side opted into selectionband="true" — row 1 IS selected
|
||||
// (LegacyBinding.SelectedIndex = 1) but the new default draws no
|
||||
// SelectedColor fill for it, on either the markup or the hand-built
|
||||
// path.
|
||||
Assert.False(viaMarkup.SelectionBandEnabled);
|
||||
Assert.False(handBuilt.SelectionBandEnabled);
|
||||
Assert.DoesNotContain(markupVerts, s => s.Texture == 0u
|
||||
&& Chunk(s.Verts).Any(v => ColorMatches(v, viaMarkup.SelectedColor)));
|
||||
}
|
||||
|
||||
// ── Slice 7 resemblance re-check: selectionband default + opt-in ────────
|
||||
|
||||
private sealed class SelectionBandBinding
|
||||
{
|
||||
public IReadOnlyList<string> Choices => new[] { "First", "Second" };
|
||||
public int Selected { get; set; } = 1;
|
||||
public Action<int> SelectIndex => _ => { };
|
||||
}
|
||||
|
||||
/// <summary>Any untextured (fill) quad in <paramref name="segs"/> whose vertex
|
||||
/// color matches <paramref name="color"/> — used to detect the selection-band
|
||||
/// fill regardless of its exact geometry.</summary>
|
||||
private static bool HasFillOfColor(
|
||||
IEnumerable<(uint Texture, IReadOnlyList<float> Verts)> segs, Vector4 color)
|
||||
=> segs.Where(s => s.Texture == 0u)
|
||||
.SelectMany(s => Chunk(s.Verts))
|
||||
.Any(v => ColorMatches(v, color));
|
||||
|
||||
/// <summary>
|
||||
/// Slice-7 resemblance re-check finding: VVS lists (VTank's real
|
||||
/// <c>HudList</c>) draw no persistent row-selection fill, so a
|
||||
/// column-less <c><list items="..."></c> must match that by
|
||||
/// default — <see cref="UiMarkupList.SelectionBandEnabled"/> defaults
|
||||
/// false. <c>selectionband="true"</c> opts a single list back into the
|
||||
/// visible <see cref="UiMarkupList.SelectedColor"/> band.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public void SingleColumnList_SelectionBand_DefaultsOffAndAttributeOptsIn(bool enabled)
|
||||
{
|
||||
var binding = new SelectionBandBinding();
|
||||
string attr = enabled ? " selectionband=\"true\"" : "";
|
||||
string xml =
|
||||
"<panel x=\"0\" y=\"0\" w=\"200\" h=\"100\">" +
|
||||
"<list x=\"0\" y=\"0\" w=\"180\" h=\"60\" rowheight=\"18\" " +
|
||||
$"items=\"{{Choices}}\" selected=\"{{Selected}}\" onchange=\"{{SelectIndex}}\"{attr}/>" +
|
||||
"</panel>";
|
||||
|
||||
var panel = MarkupDocument.Build(xml, binding, Sprite);
|
||||
var list = Assert.IsType<UiMarkupList>(panel.Children[0]);
|
||||
Assert.Equal(enabled, list.SelectionBandEnabled);
|
||||
|
||||
var (renderer, ctx) = MakeContext(200f, 200f);
|
||||
list.DrawSelfAndChildren(ctx);
|
||||
|
||||
Assert.Equal(enabled, HasFillOfColor(renderer.DebugSpriteSegmentVerts, list.SelectedColor));
|
||||
}
|
||||
|
||||
/// <summary>Same fix, exercised through <c><column></c> mode
|
||||
/// (<see cref="UiMarkupList.DrawColumns"/>) — the gate applies to both
|
||||
/// draw branches of the shared widget, not just the legacy one.</summary>
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public void ColumnModeList_SelectionBand_DefaultsOffAndAttributeOptsIn(bool enabled)
|
||||
{
|
||||
var binding = new SelectionBandBinding();
|
||||
string attr = enabled ? " selectionband=\"true\"" : "";
|
||||
string xml =
|
||||
"<panel x=\"0\" y=\"0\" w=\"200\" h=\"100\">" +
|
||||
$"<list x=\"0\" y=\"0\" w=\"180\" h=\"60\" rowheight=\"18\" selected=\"{{Selected}}\" onchange=\"{{SelectIndex}}\"{attr}>" +
|
||||
" <column type=\"text\" width=\"*\" items=\"{Choices}\"/>" +
|
||||
"</list></panel>";
|
||||
|
||||
var panel = MarkupDocument.Build(xml, binding, Sprite);
|
||||
var list = Assert.IsType<UiMarkupList>(panel.Children[0]);
|
||||
Assert.Equal(enabled, list.SelectionBandEnabled);
|
||||
|
||||
var (renderer, ctx) = MakeContext(200f, 200f);
|
||||
list.DrawSelfAndChildren(ctx);
|
||||
|
||||
Assert.Equal(enabled, HasFillOfColor(renderer.DebugSpriteSegmentVerts, list.SelectedColor));
|
||||
}
|
||||
|
||||
/// <summary>Parse-only pin: <c>selectionband</c> follows the same silent
|
||||
/// literal-bool convention as <c>openupward</c>/<c>clearonsubmit</c>
|
||||
/// (<see cref="MarkupDocument"/>'s private <c>B</c> helper) — omitted
|
||||
/// defaults false, and <c>"true"</c> sets the property, with no draw
|
||||
/// involved at all.</summary>
|
||||
[Fact]
|
||||
public void ListSelectionBandAttribute_ParsesToProperty()
|
||||
{
|
||||
var binding = new SelectionBandBinding();
|
||||
const string xmlDefault =
|
||||
"<panel x=\"0\" y=\"0\" w=\"200\" h=\"100\">" +
|
||||
"<list x=\"0\" y=\"0\" w=\"180\" h=\"60\" items=\"{Choices}\" " +
|
||||
"selected=\"{Selected}\" onchange=\"{SelectIndex}\"/>" +
|
||||
"</panel>";
|
||||
const string xmlEnabled =
|
||||
"<panel x=\"0\" y=\"0\" w=\"200\" h=\"100\">" +
|
||||
"<list x=\"0\" y=\"0\" w=\"180\" h=\"60\" items=\"{Choices}\" " +
|
||||
"selected=\"{Selected}\" onchange=\"{SelectIndex}\" selectionband=\"true\"/>" +
|
||||
"</panel>";
|
||||
|
||||
var defaultList = Assert.IsType<UiMarkupList>(
|
||||
MarkupDocument.Build(xmlDefault, binding, Sprite).Children[0]);
|
||||
var enabledList = Assert.IsType<UiMarkupList>(
|
||||
MarkupDocument.Build(xmlEnabled, binding, Sprite).Children[0]);
|
||||
|
||||
Assert.False(defaultList.SelectionBandEnabled);
|
||||
Assert.True(enabledList.SelectionBandEnabled);
|
||||
}
|
||||
|
||||
// ── Fix round: end-to-end MarkupDocument builds (fix item 10) ───────────
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue