feat: retail scrollbar chrome for markup <menu> overflow and <list> overflow

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 inventory
window."

<menu> markup wiring (MarkupDocument.cs): a plugin <menu> is now always
Scrollable (single-column, VTank HudCombo shape) instead of wrapping
overflow into more grid columns, with PopupScrollbarHideWhenDisabled=true
so the bar is entirely absent while the item count fits the "rows"
window. RetailScrollbarChrome.ApplyToMenuPopup wires the same chrome ids
the previous commit taught DrawScrollablePopupPlain to draw, for both
style="plain" and style="retail" markup menus.

<list> markup (UiMarkupList.cs / MarkupDocument.cs): a plugin <list>
(single-column or <column> multi-column) that overflows its own row
viewport now draws the retail scrollbar chrome at its right edge (VVS's
own placement, 16px wide) instead of being wheel-scroll-only with no
visible bar. The reserved 16px column only exists while rows actually
overflow, in both column-layout modes (ComputeColumnLayout receives the
already-shrunk width so the last/auto column absorbs the remainder
correctly); the bar is fully interactive (up/down arrows, track paging,
thumb drag) via a small UiScrollable projection kept in sync with the
list's own _topRow, which stays the single source of truth. Wheel
scrolling and a no-resolver hand-built list (draws nothing, no crash) are
unchanged.

Mutation shown to fail first: new
UiMarkupListScrollbarTests/MarkupDocumentTests cases were written against
pre-change UiMarkupList/MarkupDocument and failed (no scrollbar sprites
ever emitted since UiMarkupList had no SpriteResolve property at all, and
<menu> markup never set Scrollable) before the implementation landed;
after: SingleColumn_Overflowing_DrawsRetailScrollbarChromeAtRightEdge and
Columns_Overflowing_ReservesSixteenPixels_LastColumnShrinksAccordingly
pin sprite ids + exact reserved-width geometry,
*_ContentFits_DrawsNo(Scrollbar|ReservationLastColumnKeepsFullRemainder)
pin the no-overflow/no-bar case, *_UpArrowClick_ScrollsUpByOneRow and
ThumbDrag_MovesTopRowAndIsReadableByASubsequentClick pin interactivity via
a following row click resolving to the moved position (mirroring
MarkupListColumnsTests' own wheel-scroll pin), and the four new
MarkupDocumentTests menu cases pin Scrollable/PopupScrollbarHideWhenDisabled/
the six chrome-id properties plus an end-to-end open-popup draw for both
the overflowing (draws chrome) and non-overflowing (draws none) cases.
Every pre-existing MarkupListColumnsTests/MarkupDocumentTests case stays
green unchanged (none of their fixtures overflow their own viewport).

docs/plugin-ui-markup.md updated: the <menu> style paragraph and a new
<list> "Scrollbar" section describe the new chrome + auto-reservation, and
the PITCH-transcription guidance is corrected to say the 16px scrollbar
column is now automatic (no more manual fold-in/double-reservation advice).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-09-07 15:43:00 +02:00
parent 67aba8c386
commit e9108277c4
5 changed files with 691 additions and 21 deletions

View file

@ -125,10 +125,17 @@ scrollbar, and a baked checkmark glyph on the current entry even with
`style="plain"`. The open popup now matches `<list>`'s own chrome too: a
flat fill + 1px border, one row per entry in the list text color, the
current entry filled like a list selection, the hovered entry a slightly
lighter fill, and no checkmark; more entries than the row cap show a plain
1px-bordered scrollbar track with a flat thumb, no DAT scrollbar art.
`style="retail"` keeps the sprite popup (gradient panel, checkmark-bearing
row art, ornate scrollbar) exactly as before, unchanged.
lighter fill, and no checkmark. A `<menu>` popup always scrolls a single
column (rather than wrapping into more grid columns) once its item count
exceeds `rows`; a further owner directive (still 2026-09-07 — "For
scrollable dropdown or the meta window we use the same assets as we do in
for example chat or inventory window") moved that overflow scrollbar to
retail's own chrome — the exact sprites the chat window's transcript and
the inventory list already use — while the rows themselves stay the flat
style described above; a menu with too few items to overflow shows no bar
at all. `style="retail"` keeps the sprite popup rows (gradient panel,
checkmark-bearing row art) exactly as before, unchanged — only the
scrollbar chrome is shared between the two styles.
Common to every element via `ApplyCommon`: `name`/`id` (a stable control
name), `visible` (literal `true`/`false` or a bound `bool` property),
@ -461,10 +468,12 @@ 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.
`fixedwidth`. acdream's column model has no separate gap/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. The 16px scrollbar column IS now
automatic (owner directive, 2026-09-07 — see "Scrollbar" below): a plugin
author never reserves it by hand.
To transcribe a real VTank column table (as in
`refs/vtank/uTank2.ViewXML.mainView.xml`) faithfully, declare each column's
@ -472,11 +481,26 @@ To transcribe a real VTank column table (as in
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.
(`16 -> 13 + 7 = 20`, not `16 + 7 = 23`). Do NOT also fold VVS's 16px
scrollbar width into the last column's pitch or the list's total `w` — the
list reserves that width itself, automatically, only while its rows
actually overflow (see "Scrollbar" below); doing both would double-reserve
it and starve the last column once the list has few enough rows to hide
the bar.
### Scrollbar
Once a `<list>`'s rows overflow its own height (either the single-column or
the `<column>` form), it reserves a 16px column at its right edge — VVS's
own `VScrollBarButtonSize` placement — and draws retail's scrollbar chrome
there: the same sprite ids the chat window's transcript and the inventory
list already draw through (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 inventory window"). Mouse wheel keeps working as
before; the bar itself is also fully interactive (up/down arrow clicks,
track paging, and thumb drag). A list whose rows all fit reserves no width
and draws no bar at all — the reservation and the chrome both come and go
together with actual overflow, never present "just in case."
### Backward compatibility