fix(vt): list column fix round 1/11 — text column optional onclick
None of VTank's eight HudList instances relies on row selection
(PluginCore.cs Monsters/Items/Meta/Route/Buffs/Consumables cell
handlers) — every real text cell is an action target. Add an optional
onclick="{Action<int>}" to <column type="text">: when present, a click
fires it with the row index INSTEAD of selecting; when absent, the
original select-the-row behavior is unchanged. BuildListColumn now
takes the column's own index (used for its onclick throw message and
threaded through for the rest of the fix round's column-context
messages); the unknown-<column type> throw also picked up the index
as a side effect of that signature change.
New tests (MarkupListColumnsTests): the onclick attribute binds and
falls back correctly when absent, a malformed onclick throws at Build,
and a click actually reaches the bound callback instead of firing
SelectionChanged — each shown to fail first (missing TextClicked
member / no 4-arg Text() overload before the production change).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
959a694823
commit
fd521f7816
4 changed files with 119 additions and 9 deletions
|
|
@ -553,7 +553,7 @@ public static class MarkupDocument
|
|||
if (listUsesColumns)
|
||||
{
|
||||
list.Columns = listChildren
|
||||
.Select(columnEl => BuildListColumn(columnEl, binding, icons))
|
||||
.Select((columnEl, index) => BuildListColumn(columnEl, binding, icons, index))
|
||||
.ToList();
|
||||
}
|
||||
else
|
||||
|
|
@ -994,7 +994,7 @@ public static class MarkupDocument
|
|||
/// stays null (draws nothing) rather than ever pointing at a null resolver.
|
||||
/// </summary>
|
||||
private static UiMarkupListColumn BuildListColumn(
|
||||
XElement columnEl, object binding, IMarkupIconResolver? icons)
|
||||
XElement columnEl, object binding, IMarkupIconResolver? icons, int index)
|
||||
{
|
||||
string? type = (string?)columnEl.Attribute("type");
|
||||
float width = F(columnEl, "width");
|
||||
|
|
@ -1008,7 +1008,21 @@ public static class MarkupDocument
|
|||
Func<IReadOnlyList<uint>>? colorsSource = colorsAttr is null
|
||||
? null
|
||||
: BindUintList(colorsAttr, binding, "column colors");
|
||||
return UiMarkupListColumn.Text(width, textSource, colorsSource);
|
||||
// Fix round finding 1: optional onclick — a text cell that
|
||||
// declares one fires it with the row index instead of
|
||||
// selecting; one that doesn't keeps the original
|
||||
// select-on-click behavior. Same "resolve if binding-shaped,
|
||||
// throw only if malformed" rule as the list's own onchange
|
||||
// above — omitting the attribute entirely is fine.
|
||||
string? textOnClickAttr = (string?)columnEl.Attribute("onclick");
|
||||
Action<int>? textOnClick = BindIntAction(textOnClickAttr, binding);
|
||||
if (textOnClickAttr is not null && textOnClick is null)
|
||||
{
|
||||
throw new FormatException(
|
||||
$"{ColumnContext(index, "text", "onclick")} did not resolve to an "
|
||||
+ $"Action<int> property on {binding.GetType().Name}");
|
||||
}
|
||||
return UiMarkupListColumn.Text(width, textSource, colorsSource, textOnClick);
|
||||
}
|
||||
case "check":
|
||||
{
|
||||
|
|
@ -1033,10 +1047,21 @@ public static class MarkupDocument
|
|||
}
|
||||
default:
|
||||
throw new FormatException(
|
||||
$"unknown <column type=\"{type}\"> (expected text, check, or icon)");
|
||||
$"column[{index}] has unknown type=\"{type}\" (expected text, check, or icon)");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fix round finding 5: every column-attribute throw message identifies
|
||||
/// the offending column by position and declared type
|
||||
/// (<c>column[2] type="check" values</c>) rather than the generic
|
||||
/// <c>"column values"</c> the initial slice used — a plugin author with
|
||||
/// several columns of the same <c>type</c> needs the index to find which
|
||||
/// one is wrong.
|
||||
/// </summary>
|
||||
private static string ColumnContext(int index, string type, string attribute) =>
|
||||
$"column[{index}] type=\"{type}\" {attribute}";
|
||||
|
||||
private static bool IsBinding(string value) =>
|
||||
value.Length > 2 && value[0] == '{' && value[^1] == '}';
|
||||
|
||||
|
|
|
|||
|
|
@ -360,10 +360,16 @@ public sealed class UiMarkupList : UiElement
|
|||
switch (columns[c].Kind)
|
||||
{
|
||||
case UiMarkupListColumnKind.Text:
|
||||
// Text-column click selects — the list's own
|
||||
// selected/onchange, unchanged in meaning from the
|
||||
// single-column list.
|
||||
SelectionChanged?.Invoke(index);
|
||||
// Fix round finding 1: a text column with its own
|
||||
// onclick fires THAT instead of selecting — none of
|
||||
// VTank's eight lists actually uses row selection, every
|
||||
// real text cell is an action target. A text column
|
||||
// without onclick keeps the original select-the-row
|
||||
// behavior (the list's own selected/onchange).
|
||||
if (columns[c].TextClicked is { } onTextClick)
|
||||
onTextClick(index);
|
||||
else
|
||||
SelectionChanged?.Invoke(index);
|
||||
break;
|
||||
case UiMarkupListColumnKind.Check:
|
||||
// A click in a check/icon column fires that column's own
|
||||
|
|
|
|||
|
|
@ -46,6 +46,18 @@ public sealed class UiMarkupListColumn
|
|||
/// column draws with the list's <see cref="UiMarkupList.TextColor"/>.
|
||||
/// </summary>
|
||||
public Func<IReadOnlyList<uint>>? ColorsSource { get; init; }
|
||||
/// <summary>
|
||||
/// Fix round finding 1: optional <c>onclick="{Action<int>}"</c> on a
|
||||
/// text column. Fired with the ROW INDEX on a click anywhere in the cell
|
||||
/// INSTEAD of selecting the row, when present. Null (the default — no
|
||||
/// <c>onclick</c> attribute at all) keeps today's original behavior: a
|
||||
/// click in this cell selects the row and fires the list's own
|
||||
/// <c>onchange</c>, exactly as before this fix. None of VTank's eight
|
||||
/// lists actually uses row selection — every real text cell is an action
|
||||
/// target — but the select-on-click default stays for any acdream markup
|
||||
/// that already relies on it.
|
||||
/// </summary>
|
||||
public Action<int>? TextClicked { get; init; }
|
||||
|
||||
// ── check ─────────────────────────────────────────────────────────────
|
||||
/// <summary><c><column type="check" values="{IReadOnlyList<bool>}"></c>.</summary>
|
||||
|
|
@ -79,12 +91,14 @@ public sealed class UiMarkupListColumn
|
|||
public static UiMarkupListColumn Text(
|
||||
float width,
|
||||
Func<IReadOnlyList<string>> textSource,
|
||||
Func<IReadOnlyList<uint>>? colorsSource) => new()
|
||||
Func<IReadOnlyList<uint>>? colorsSource,
|
||||
Action<int>? onClick = null) => new()
|
||||
{
|
||||
Kind = UiMarkupListColumnKind.Text,
|
||||
Width = width,
|
||||
TextSource = textSource,
|
||||
ColorsSource = colorsSource,
|
||||
TextClicked = onClick,
|
||||
};
|
||||
|
||||
public static UiMarkupListColumn Check(
|
||||
|
|
|
|||
|
|
@ -324,6 +324,71 @@ public sealed class MarkupListColumnsTests
|
|||
Assert.Throws<FormatException>(() => MarkupDocument.Build(xml, binding, Sprite));
|
||||
}
|
||||
|
||||
// ── Fix round: text column optional onclick (fix item 1) ────────────────
|
||||
|
||||
[Fact]
|
||||
public void TextColumn_OptionalOnclickAttribute_BindsAndDoesNotBreakWithoutIt()
|
||||
{
|
||||
var binding = new ThreeColumnBinding();
|
||||
const string withOnclick =
|
||||
"<panel x=\"0\" y=\"0\" w=\"200\" h=\"100\">" +
|
||||
"<list x=\"0\" y=\"0\" w=\"180\" h=\"60\" selected=\"{Selected}\">" +
|
||||
" <column type=\"text\" width=\"80\" items=\"{Names}\" onclick=\"{ClickIcon}\"/>" +
|
||||
"</list></panel>";
|
||||
var panelWith = MarkupDocument.Build(withOnclick, binding, Sprite);
|
||||
var listWith = Assert.IsType<UiMarkupList>(panelWith.Children[0]);
|
||||
Assert.NotNull(listWith.Columns![0].TextClicked);
|
||||
|
||||
const string withoutOnclick =
|
||||
"<panel x=\"0\" y=\"0\" w=\"200\" h=\"100\">" +
|
||||
"<list x=\"0\" y=\"0\" w=\"180\" h=\"60\" selected=\"{Selected}\">" +
|
||||
" <column type=\"text\" width=\"80\" items=\"{Names}\"/>" +
|
||||
"</list></panel>";
|
||||
var panelWithout = MarkupDocument.Build(withoutOnclick, binding, Sprite);
|
||||
var listWithout = Assert.IsType<UiMarkupList>(panelWithout.Children[0]);
|
||||
Assert.Null(listWithout.Columns![0].TextClicked);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TextColumn_MalformedOnclickAttribute_ThrowsAtBuild()
|
||||
{
|
||||
var binding = new ThreeColumnBinding();
|
||||
const string xml =
|
||||
"<panel x=\"0\" y=\"0\" w=\"200\" h=\"100\">" +
|
||||
"<list x=\"0\" y=\"0\" w=\"180\" h=\"60\" selected=\"{Selected}\">" +
|
||||
" <column type=\"text\" width=\"80\" items=\"{Names}\" onclick=\"notabinding\"/>" +
|
||||
"</list></panel>";
|
||||
|
||||
var ex = Assert.Throws<FormatException>(() => MarkupDocument.Build(xml, binding, Sprite));
|
||||
Assert.Contains("column[0] type=\"text\" onclick", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClickInTextColumn_WithOnclick_FiresItInsteadOfSelecting()
|
||||
{
|
||||
var clicked = new List<int>();
|
||||
var selections = new List<int>();
|
||||
var list = new UiMarkupList
|
||||
{
|
||||
Width = 60f, Height = 200f, RowHeight = 20f,
|
||||
SelectedIndexSource = () => -1,
|
||||
SelectionChanged = row => selections.Add(row),
|
||||
Columns = new[]
|
||||
{
|
||||
UiMarkupListColumn.Text(
|
||||
60f, () => new[] { "a", "b", "c" }, null, row => clicked.Add(row)),
|
||||
},
|
||||
};
|
||||
var (_, ctx) = MakeContext(200f, 200f);
|
||||
list.DrawSelfAndChildren(ctx);
|
||||
|
||||
// Row 1 (y = 20..40).
|
||||
list.OnEvent(new UiEvent { Type = UiEventType.MouseDown, Data1 = 10, Data2 = 25 });
|
||||
|
||||
Assert.Equal(new[] { 1 }, clicked);
|
||||
Assert.Empty(selections);
|
||||
}
|
||||
|
||||
// ── Draw-level: column x-offsets, check glyph, icon, clipping ────────────
|
||||
|
||||
private static (TextRenderer renderer, UiRenderContext ctx) MakeContext(float w, float h)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue