merge(vt): resizable plugin panels (resizable/minw/minh) and anchor markup (owner: larger default, resizable)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
commit
2e63391cc4
5 changed files with 605 additions and 27 deletions
|
|
@ -36,8 +36,9 @@ in-memory KSML string instead of a file path — the route to reach for when a
|
|||
panel is small enough not to need its own shipped `.xml` asset.
|
||||
|
||||
Every registered window gets a stable persisted key
|
||||
(`plugin:{pluginId}:{windowId}`), drag, resize (where the markup opts in),
|
||||
the global UI lock, and a button in the shared plugin shelf
|
||||
(`plugin:{pluginId}:{windowId}`), drag, resize (where the markup opts in —
|
||||
`<panel resizable="true">`, see "Resizable panels and anchors" below), the
|
||||
global UI lock, and a button in the shared plugin shelf
|
||||
(`ShowInSidePanel = true`, the default). Hiding or minimizing a window never
|
||||
disables the plugin or pauses its `Tick`.
|
||||
|
||||
|
|
@ -98,18 +99,18 @@ vanishing from the built tree.
|
|||
|
||||
| Element | Purpose | Key attributes |
|
||||
|---|---|---|
|
||||
| `panel` (root) | The window itself | `x y w h title resize visible` |
|
||||
| `group` | Transparent layout container | `x y w h background border visible` |
|
||||
| `label` | Static or bound text | `x y text color` |
|
||||
| `button` | Clickable rect + caption (+ Slice B icon) | `x y w h text color background border onclick icon iconkind` |
|
||||
| `icon` | Slice B: a standalone DAT icon | `x y w h did spell item tooltip` |
|
||||
| `panel` (root) | The window itself | `x y w h title resize resizable minw minh visible` |
|
||||
| `group` | Transparent layout container | `x y w h background border visible anchor` |
|
||||
| `label` | Static or bound text | `x y text color anchor` |
|
||||
| `button` | Clickable rect + caption (+ Slice B icon) | `x y w h text color background border onclick icon iconkind anchor` |
|
||||
| `icon` | Slice B: a standalone DAT icon | `x y w h did spell item tooltip anchor` |
|
||||
| `meter` | Retail-style nine-slice bar | `x y w h fill cur max color anchor backleft/backtile/backright frontleft/fronttile/frontright` |
|
||||
| `tab` | Selectable tab button | `x y w h text selected onclick` |
|
||||
| `toggle` | Lamp-style checkbox | `x y w h text checked onclick color` |
|
||||
| `slider` | Horizontal scalar | `x y w h value onchange` |
|
||||
| `field` | Single-line editable text | `x y w h text maxlength clearonsubmit onchange onsubmit color background` |
|
||||
| `menu` | Dropdown selector | `x y w h items selected onchange rows rowheight openupward style` |
|
||||
| `list` | Scrollable row list (+ Slice B icon column, + Campaign VT slice 1 multi-column) | `x y w h selected onchange rowheight` + either the single-column `items colors icons iconkind`, or one-to-many `<column>` children (see "Columns" below) — never both |
|
||||
| `tab` | Selectable tab button | `x y w h text selected onclick anchor` |
|
||||
| `toggle` | Lamp-style checkbox | `x y w h text checked onclick color anchor` |
|
||||
| `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 |
|
||||
|
||||
`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
|
||||
|
|
@ -139,12 +140,15 @@ 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),
|
||||
`enabled` (same rule), and `tooltip` (a literal string or `{Binding}` shown
|
||||
`enabled` (same rule), `tooltip` (a literal string or `{Binding}` shown
|
||||
through retail's own runtime tooltip popup, empty/whitespace treated as no
|
||||
tooltip). The root `<panel>` is the one exception: it does **not** go
|
||||
through `ApplyCommon` (no `name`/`enabled`/`tooltip`), and its `visible`
|
||||
attribute accepts a `{Binding}` only — a literal `visible="true"` on the
|
||||
root is not parsed (unlike every child element, where a literal is fine).
|
||||
tooltip), and `anchor` (which edges of the element's PARENT it keeps a fixed
|
||||
margin to on resize — see "Resizable panels and anchors" below). The root
|
||||
`<panel>` is the one exception: it does **not** go through `ApplyCommon` (no
|
||||
`name`/`enabled`/`tooltip`/`anchor` — a top-level window is never anchored to
|
||||
its own parent, only dragged/resized directly), and its `visible` attribute
|
||||
accepts a `{Binding}` only — a literal `visible="true"` on the root is not
|
||||
parsed (unlike every child element, where a literal is fine).
|
||||
|
||||
Multi-column lists are real (Campaign VT slice 1 Part B, below) — a `<list>`
|
||||
with `<column>` children is no longer limited to one padded text column. A
|
||||
|
|
@ -161,6 +165,67 @@ the `0x` prefix to parse as hex; an all-digit string with no prefix
|
|||
(`did="165"`) parses as **decimal**, not hex — `did="165"` and `did="0x165"`
|
||||
are different ids.
|
||||
|
||||
## Resizable panels and anchors
|
||||
|
||||
A plugin panel is **fixed-size by default** — this matches every panel
|
||||
shipped before 2026-09-07 (e.g. `mosstank.xml`'s `resize="none"`). A window
|
||||
opts into real user drag-resize with `<panel resizable="true">`, and every
|
||||
non-root element opts its OWN geometry into following that resize with
|
||||
`anchor="..."`. The two attributes are independent: a resizable panel whose
|
||||
children have no `anchor` just gets bigger/smaller with empty space at the
|
||||
bottom-right (today's default placement, `Left|Top`); a panel with anchored
|
||||
children but `resizable` left at its default `false` never actually resizes,
|
||||
so the anchors never have anything to react to.
|
||||
|
||||
| Attribute | Element | Meaning |
|
||||
|---|---|---|
|
||||
| `resizable` | `panel` (root) | `"true"` arms the window for user drag-resize on both axes (edges + corners, same mechanism chat windows use); default `false` — fixed size, exactly as before this attribute existed |
|
||||
| `minw` / `minh` | `panel` (root) | The floor a drag-resize (and a persisted-layout restore) will not shrink below. Default: the panel's own authored `w`/`h` — a resizable panel never shrinks past the layout its author actually tested. Only meaningful when `resizable="true"` |
|
||||
| `resize` | `panel` (root) | Pre-existing per-axis lock (`x`/`y`/`both`/`none`) that narrows `resizable="true"` to one axis; has no effect on its own now that `resizable` (default `false`) is the master switch |
|
||||
| `anchor` | `group` `list` `menu` `field` `label` `button` `icon` (and `meter`/`tab`/`toggle`/`slider`) | Space-separated subset of `left top right bottom` (case-insensitive), naming which edges of the element's **direct parent** it keeps a fixed margin to as that parent resizes. Default (attribute absent) is `left top` — today's fixed placement, unchanged |
|
||||
|
||||
`anchor` semantics are exactly `AcDream.App.UI.UiElement.Anchors`/
|
||||
`AnchorEdges`/`ApplyAnchor` (already used by every retail-imported window):
|
||||
|
||||
- `left top` (the default) — pinned top-left at a fixed size; never stretches.
|
||||
- `left right` — stretches WIDTH to track the parent (both side margins stay
|
||||
fixed).
|
||||
- `top bottom` — stretches HEIGHT the same way, vertically.
|
||||
- `left top right bottom` — stretches on both axes.
|
||||
- `right` alone (no `left`) — pins to the parent's right edge at a FIXED
|
||||
width, moving as the parent resizes rather than stretching. `bottom` alone
|
||||
is the same, vertically.
|
||||
|
||||
An element's parent is whatever markup element directly contains it — for a
|
||||
`<group>`'s children, that is the GROUP, not the panel. This is how a group
|
||||
propagates resize to its own contents: give the group
|
||||
`anchor="left top right bottom"` so it stretches with the panel, and give a
|
||||
`<list>` inside it `anchor="left right"` so the list stretches with the
|
||||
GROUP's width in turn. An unrecognized token (a typo like
|
||||
`anchor="left rihgt"`) throws `FormatException` at `Build`, naming the
|
||||
offending element by its `name`/`id` — the same "malformed markup throws"
|
||||
rule every other attribute in this grammar follows.
|
||||
|
||||
No other markup or host wiring is needed to make a panel resizable: once
|
||||
`resizable="true"` sets the window's `Resizable`/`ResizeX`/`ResizeY`/
|
||||
`MinWidth`/`MinHeight`, the SAME drag-resize, persistence (save/restore
|
||||
across sessions, clamped to `minw`/`minh`), and UI-lock behavior every other
|
||||
retained window already has just applies.
|
||||
|
||||
```xml
|
||||
<panel x="0" y="0" w="420" h="320" title="MossTank" resizable="true" minw="360" minh="260">
|
||||
<group anchor="left top right bottom" x="8" y="8" w="404" h="304" border="#FF4A3A14">
|
||||
<label x="4" y="4" text="Monsters"/>
|
||||
<list anchor="left right top bottom" x="4" y="24" w="396" h="276"
|
||||
items="{MonsterNames}" selected="{SelectedMonster}" onchange="{SelectMonster}"/>
|
||||
</group>
|
||||
</panel>
|
||||
```
|
||||
|
||||
Here the outer `<group>` stretches with the panel on every edge, and the
|
||||
`<list>` inside it stretches with the GROUP on every edge in turn — dragging
|
||||
the window's corner grows the whole list, not just empty panel background.
|
||||
|
||||
## The icon-id grammar (Slice B)
|
||||
|
||||
Decal/VirindiViewService plugins (the reference usage this ported:
|
||||
|
|
@ -554,4 +619,14 @@ 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 `<list>` is unaffected,
|
||||
and two full `MarkupDocument.Build` end-to-end tests transcribing VTank's
|
||||
real Monsters- and Meta-tab column shapes.
|
||||
real Monsters- and Meta-tab column shapes. `MarkupResizableAnchorTests`
|
||||
covers `resizable`/`minw`/`minh` parsing, the `anchor` grammar (default,
|
||||
every token combination, the unknown-token throw) across every element
|
||||
listed above, live re-layout against the same recording-renderer apparatus
|
||||
(a stretching list, a right-anchored button that moves, a group whose resize
|
||||
propagates to its own anchored children), and a golden proving a panel with
|
||||
none of these attributes draws byte-identically to itself across repeated
|
||||
builds. `RetailWindowManagerTests`/`RetailWindowLayoutPersistenceTests`
|
||||
cover a resizable markup panel through the real `ResizeTo`/save-restore
|
||||
paths (accepts within `minw`/`minh`, a fixed panel refuses, a restored size
|
||||
below the CURRENT floor clamps up to it).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue