diff --git a/docs/plugin-ui-markup.md b/docs/plugin-ui-markup.md index 4752d8a7..ee601902 100644 --- a/docs/plugin-ui-markup.md +++ b/docs/plugin-ui-markup.md @@ -80,10 +80,13 @@ check those four against the markup by eye. | `list onchange` | Throws | `Action` | | `column items` (`type="text"`) | Throws — REQUIRED, unlike the single-column list's own `items` sugar it mirrors | `IReadOnlyList` | | `column colors` (`type="text"`) | **Silent** if omitted (no per-row override, same rule as `list colors`); throws if present but mistyped | `IReadOnlyList` **or** `IReadOnlyList` | +| `column onclick` (`type="text"`) | **Silent** if omitted (keeps the original select-the-row behavior); throws if present but mistyped | `Action` (row index) | | `column values` (`type="check"`) | Throws — REQUIRED (there is no "no check column" fallback the way `list icons` has "no icon column") | `IReadOnlyList` | | `column values` (`type="icon"`) | Throws — REQUIRED | `IReadOnlyList` **or** `IReadOnlyList` | | `column onchange` (`type="check"`) | Throws — REQUIRED (unlike the list's own optional `onchange`) | `Action` (row index) | | `column onclick` (`type="icon"`) | Throws — REQUIRED | `Action` (row index) | +| `column width` (any type, NOT the list's last column) | Throws if missing, unparseable, or `<= 0` — UNLESS it is the literal `"*"` | `float`, or the literal `"*"` for auto | +| `column width` (the list's LAST column) | **Silent** — never validated, never used for layout (it always absorbs the remainder), UNLESS it is the literal `"*"` (then it joins the auto-sharing group instead of taking 100% of the remainder alone) | `float`, or the literal `"*"` | The icon-id row is the one binding here whose failure mode depends on WHEN you look: a typo'd property name is caught immediately at `Build`, but a property that exists yet holds the wrong kind of value at runtime is only ever discovered later, from inside a live draw. @@ -308,27 +311,33 @@ col)`. acdream's `` matches this by letting a `` declare attributes: ```xml - - - - + + + ``` -This mirrors VTank's own Monsters tab (a name column plus several boolean -flag columns plus an icon-button column) — see +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 -columns" gap and its proposed extension, which this implements verbatim. +columns" gap and its proposed extension, which this implements verbatim. The +`rowheight="18"` above is the widget's own default (`UiMarkupList.RowHeight`), +chosen to match VVS's own row pitch exactly: `Padding*2 + ControlHeight` = +`1*2 + 16` = `18` (`docs/research/vtank-kb/08-ui-views.md`'s `HudList` row: +`Padding=1px`, `ControlHeight=16px`) — an author who omits `rowheight` +entirely already gets VVS's pitch for free. ### `` attribute grammar | Attribute | Applies to | Required | Meaning | |---|---|---|---| | `type` | every column | yes | `text`, `check`, or `icon` — any other value throws `FormatException` at `Build` | -| `width` | every column | no (defaults to `0`) | Column width in px. **The LAST column in a `` ignores its own declared width and always absorbs whatever room remains** after every earlier column — recomputed every frame off the list's live width, so a resizable list re-flows its last column like every other retained widget | +| `width` | every column | see below | Column width in px, or `"*"` for auto. See "Width semantics" below — the rules differ for the LAST column in a `` vs. every other column | | `items` | `type="text"` | yes | `{IReadOnlyList}` — one row of text per index | | `colors` | `type="text"` | no | `{IReadOnlyList}`, `0xRRGGBB` per row (same grammar as the single-column list's own `colors`); omitted rows (or the whole attribute) fall back to the list's `TextColor` | +| `onclick` | `type="text"` | no | `{Action}` — fired with the ROW INDEX on a click anywhere in the cell INSTEAD of selecting the row. Omitted (the default) keeps the original select-the-row behavior; present but malformed throws `FormatException` at `Build`. None of VTank's eight lists actually relies on row selection — every real text cell in `mainView.xml` is wired as an action target — so a new column is usually written WITH an `onclick` | | `values` | `type="check"` | yes | `{IReadOnlyList}` — the checked state per row | | `onchange` | `type="check"` | yes | `{Action}` — fired with the ROW INDEX on a click anywhere in the cell; the plugin flips its own bool, the column never mutates `values`' backing collection itself | | `values` | `type="icon"` | yes | `{IReadOnlyList}` (or `IReadOnlyList`) — one icon id per row, same id-space rules as `list icons` | @@ -342,20 +351,62 @@ error, not a silently-inert control. A `` with an unrecognized `type`, a missing required binding for its type, or any `` child element that isn't `` at all, throws `FormatException` at `Build`. A `` with `` children cannot ALSO use the single-column `items`/`colors`/`icons` -attributes on the `` element itself — pick one form per list. +attributes on the `` element itself — pick one form per list. Every +column-attribute throw message identifies the offending column by position +and declared type — `column[2] type="check" values`, not just `"column +values"` — so a list with several columns of the same type still points at +the right one. + +### Width semantics + +`width="*"` means AUTO: this column shares the list's remaining width +EQUALLY with every other auto column, VVS's own "0-width column auto-sizes" +rule (`docs/research/vtank-kb/08-ui-views.md`'s `HudList` row: "a 0-width +text/button/edit/list/fixedlayout/notebook column auto-sizes... share the +remaining width equally"). `width="*"` is legal on ANY column, including the +last. + +The LAST column in a `` is special: it is ALWAYS treated as auto — +sharing the remaining width like every other auto column when one or more +earlier columns also declare `width="*"`, or absorbing 100% of the remainder +by itself when no other column does (the original, still-default behavior). +Its own declared `width` (or omitting `width` entirely) is never validated +and never used for layout — only an explicit `width="*"` on the last column +actually changes anything (it makes the last column share evenly with +earlier auto columns instead of taking the whole remainder alone). When two +or more columns end up sharing, integer-division remainder goes to the LAST +one — e.g. three columns sharing 100px split 33/33/34, not 33/33/33 with 1px +unaccounted for. + +Every OTHER (non-last) column's declared `width` is validated at `Build`: **a +missing, unparseable, or non-positive `width` throws `FormatException`** +naming the column's index and declared type (`column[0] type="text" width +must be a positive number or "*", got (missing)`) — UNLESS it is `width="*"`. + +At layout time (recomputed every frame off the list's live width, so a +resizable list re-flows like every other retained widget), a declared width +that would overflow the list's total width is CLAMPED to whatever room is +actually left, walked left to right — every column after the overflow point +gets `0` width and draws nothing (a `<=0`-width cell is skipped entirely, the +same as today). ### Row count, selection, and clicks Row count is the longest bound column (a text column with 20 rows next to a -check column with only 5 simply draws 15 rows of empty checkboxes — short -columns never truncate the whole list). The list's own `selected`/`onchange` -attributes keep exactly their single-column meaning: a click in a **text** -column selects that row (and fires the list's `onchange` with the row index, -same as today). A click in a **check** or **icon** column instead fires that +check column with only 5 simply draws 15 rows past its own data — see "Short +columns past their own row count" below for what each column kind does +there). The list's own `selected`/`onchange` attributes keep exactly their +single-column meaning: a click in a **text** column without its own +`onclick` selects that row (and fires the list's `onchange` with the row +index, same as today). A click in a **check** or **icon** column, or a +**text** column that DOES declare its own `onclick`, instead fires that column's own `onchange`/`onclick` and does **not** change the list's selection — VVS's per-cell `Click(row, col)` folded into a per-column callback, since acdream's binding model is per-attribute rather than -per-cell. Scrolling works exactly as the single-column list already does. +per-cell. A click landing on a row past that SPECIFIC column's own bound +data (even though the row is valid for the list overall, because some OTHER +column has more rows) fires nothing — no callback, no crash. Scrolling works +exactly as the single-column list already does. ### No header row @@ -369,9 +420,44 @@ is no dedicated header markup to learn. ### Check-column glyph A `type="check"` cell draws with the exact same five-band lamp glyph as -`` (`UiMarkupToggle`'s checked/unchecked colors and `DrawLamp` -primitive), so a column checkbox reads identically to every other checkbox -in the client rather than a bespoke box-and-tick. +`` (both now share one `UiCheckLamp` primitive — its checked/ +unchecked colors and `Draw` method), so a column checkbox reads identically +to every other checkbox in the client rather than a bespoke box-and-tick. +The glyph is centered horizontally in its cell (matching how an icon cell +already centers its sprite) — a check column is routinely declared wider +than the glyph itself under the PITCH convention below. + +### Short columns past their own row count + +A **text** or **icon** cell past its own column's row count draws nothing — +there is no sensible default string or icon to show. A **check** cell past +its own column's row count still draws the lamp, UNCHECKED — VVS +materializes every cell in a row regardless of which columns actually have +data for it, and there is always a sensible default for a boolean (false). + +### The PITCH convention for transcribing a VTank column table + +VVS's own `HudList` reserves geometry acdream's column model doesn't have a +separate concept for: `WPaddingOuter=3px` (the list's own left/right +margin), `WPadding=7px` (a gap BETWEEN columns), and a themed +`VScrollBarButtonSize=16px` (scrollbar width, reserved on the right). It +also forces every `CheckColumn` to a fixed 13px regardless of its declared +`fixedwidth`. acdream's column model has no separate gap/scrollbar/ +forced-width concept — every column's declared `width` is its full cell +width, columns sit directly adjacent with no gap, and a check column uses +whatever `width` it's given like any other column. + +To transcribe a real VTank column table (as in +`refs/vtank/uTank2.ViewXML.mainView.xml`) faithfully, declare each column's +**PITCH** instead of its raw `fixedwidth`: `pitch = fixedwidth + 7` (baking +VVS's inter-column `WPadding` into the cell width itself, since acdream has +no separate gap). For a `CheckColumn`, use VVS's forced 13px as the +`fixedwidth` regardless of whatever `fixedwidth` the source XML declares +(`16 -> 13 + 7 = 20`, not `16 + 7 = 23`). Reserve VVS's 16px scrollbar width +on the LAST column specifically (add it to that column's own pitch, or fold +it into the list's total declared `w`) — acdream's list draws no scrollbar +of its own today, but reserving the space keeps the transcribed proportions +matching what a real VVS `HudList` would show once one exists. ### Backward compatibility @@ -417,7 +503,12 @@ in-test class recording which id/kind it was asked to resolve) rather than a live DAT — see `tests/AcDream.App.Tests/UI/`. `PluginSidePanelTests` exercises the shelf's drag/collapse/hide/persistence behavior against a bare `UiRoot`. `MarkupListColumnsTests` covers the Columns extension above: per-column -binding-type validation, draw-level column-offset/clipping/check-glyph pins -against the same recording-renderer apparatus, hit-test routing (text -selects; check/icon fire their own callback and never touch selection), and -a backward-compatibility proof that a column-less `` is unaffected. +binding-type validation (every throw naming its column by index and type), +width semantics (`"*"` sharing, the last-column-always-auto rule, the +overflow clamp), the per-column row-bound click guard, draw-level +column-offset/clipping/check-glyph pins against the same recording-renderer +apparatus, hit-test routing (text selects unless it has its own `onclick`; +check/icon/onclick-text fire their own callback and never touch selection), +a backward-compatibility proof that a column-less `` is unaffected, +and two full `MarkupDocument.Build` end-to-end tests transcribing VTank's +real Monsters- and Meta-tab column shapes.