M3: reorder paints, effects and layout grids in the Design panel; grid and instance property variable bindings (#81)

Fill, stroke, effect and layout grid rows drag to reorder by their grip (a handle shows over the
row, an insertion line where it lands, the row's label follows the pointer like a dragged layer;
Escape cancels; lists that differ across the selection have nothing to drag). New commands
`fill.move`, `stroke.move` and `grid.move {ids?, from, to}` join `effect.move` (lists bottom first,
one undo step; items keep everything, linked list styles are unlinked).

Variable bindings on list items follow their items when a list is reordered or shrinks:
paint colours bind per paint (`fills/<i>/color`, `strokes/<i>/color`; `fill`/`stroke` stay the
first paint's), so a moved bound paint keeps its variable and its neighbours are never recoloured.
`fill.remove`, `stroke.remove` and `grid.remove` refuse negative indices.

Layout grids get settings in the Design panel (size; count, type, width or height, margin or
offset, gutter; colour) whose numbers bind to number variables (`layoutGrids/<i>/<field>`),
re-applied on variable and mode changes. Instance boolean and text properties bind to boolean and
string variables (`componentProperties/<name>`, from the instance's properties panel), kept across
instance rebuilds, following property renames and dropped on delete, reset and detach.
Inspect names the variable of each paint.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
BFlat
2026-10-10 15:21:05 +07:00
committed by GitHub
co-authored by Claude Opus 5.5
parent d16878588f
commit 4565c4ab93
22 changed files with 1277 additions and 216 deletions
+28
View File
@@ -419,6 +419,34 @@ and a file keeps the newest 20 autosaves (user versions are never dropped for th
order: `{items: [{id, name, page, status}]}`. The Inspect tab sets the status of the selected
frames and, with nothing selected, lists them with a status filter (click one to go to it).
### Paint, effect and layout grid lists
A layer's fills, strokes, effects and layout grids are lists kept **bottom first**: index 0 paints
first, the last one is on top (the Design panel lists them the other way round, topmost first).
- **Reorder:** `fill.move`, `stroke.move`, `effect.move` and `grid.move {ids?, from, to}` move one
item (one undo step for every layer given; out-of-range or equal indices change nothing, negative
or fractional ones are refused). The item keeps everything (an image fill its image, scale mode
and adjustments, a gradient its stops and handles), and a paint, effect or grid style linked to
the list is unlinked (the list no longer matches it). In the app, drag a row by its grip (the
empty strip left of its fields); a line shows where it lands.
- **Variable bindings follow their items** when a list is reordered or an item removed
(`fill.remove`, `stroke.remove`, `effect.remove`, `grid.remove`); a removed item's bindings go
with it. Binding fields (`variable.bind {field}`): `fill` / `stroke` (the first paint's colour) or
`fills/<i>/color` / `strokes/<i>/color`, `effects/<i>/<offsetX|offsetY|radius|spread|color>`,
`layoutGrids/<i>/<size|count|gutter|offset>` (number variables; count is kept to 1–1000, size and
gutter are never negative). Moving a bound paint rewrites its key (`fill` becomes `fills/2/color`).
- **Instance properties:** `variable.bind {ids: [instance], field: "componentProperties/<name>",
variable}` drives a boolean property from a boolean variable and a text property from a string
variable (other types are refused); the value follows the variable's modes like any binding, the
binding follows renames of the property, and deleting the property, resetting the instance or
detaching it drops it. The instance's properties panel offers the same with its variable button.
```json
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"run_command","arguments":{"command":"fill.move","params":{"ids":[2],"from":0,"to":2}}}}
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"run_command","arguments":{"command":"variable.bind","params":{"ids":[5],"field":"layoutGrids/0/gutter","variable":"gutter"}}}}
```
### Ruler guides
`guide.add {vertical, offset, frame? | page?}` (returns `{index}`), `guide.move {index, offset, …}`,