Objects: one selection for charts, pictures, shapes and slicers, with eight resize handles

The sheet tracked the selected object as a bare id in `selected_chart`, so
only charts and text boxes drew a selection frame, only the bottom-right
corner resized, and the kind of the selected object had to be guessed from
the id. The selection is now `SheetApp::selected_object: Option<ObjRef>`,
with `selected_chart()` kept for the Chart Design tab and the Format Chart
pane.

- Model: `ObjKind` (chart, image, shape, slicer) and `ObjRef { kind, id }`;
  `Sheet` helpers find, move and remove an object by `ObjRef` and walk every
  object's anchor in a fixed order. `next_object_id` saturates instead of
  overflowing on an id at `u32::MAX` read from a file.
- Commands: `object.delete`, `object.move` and `object.setAnchorMode` accept
  the four kinds through one parameter check, refuse an unknown kind or id
  instead of succeeding silently, and clamp hostile offsets. `object.delete`
  replies with the deleted object. Arrange and the Selection Pane include
  slicers, which stack above the other objects.
- Sheet: the selected object of any kind draws a frame with eight handles
  over every object. Each handle resizes only its own edges, which never
  cross; the body moves the object, and the first drag frame already applies
  the pointer's movement. The pointer shows the handle's resize cursor or a
  move cursor, with the diagonals swapped on a right-to-left sheet, where the
  frame moves with its object.
- Keys: Escape drops the object selection; Delete and Backspace keep
  deleting the selected object (#213). A selection whose object disappears
  (undo, redo, delete, sheet switch) is cleared.
- Double-click: `open_object` routes by kind (a text box edits in place, a
  chart opens Format Chart).
- Control protocol: `ui.inspect` reports `selectedObject {kind, id}` and
  keeps `selectedChart`; `ui.object.select` selects any object and replies
  with its rect. The snapshot example takes `--object <id>`.

Tests: engine tests for every kind by kind and id, the Selection Pane and
hostile parameters; UI tests for selecting each kind (a slicer over a chart
takes the click), all eight handles on four kinds, cursors, Delete,
Backspace and Escape, undo and redo, double-click routing, and resizing on a
right-to-left sheet. Existing tests use the new selection API.
This commit is contained in:
prxnvt
2026-10-31 05:51:02 -04:00
parent 981fad14e0
commit e7591de2ec
40 changed files with 1010 additions and 201 deletions
+9 -1
View File
@@ -58,7 +58,8 @@ connection; nothing sent after it runs. Blank lines are skipped.
| `document.inspect` | | Workbook summary: sheets, used ranges, tables, charts, names, selection, undo labels |
| `sheet.read` | `{range?, sheet?, formulas?, formatted?}` | Values (or formulas / displayed text) of a range |
| `cell.get` | `{cell?}` | One cell: value, formula, displayed text, style, merge, comment, link |
| `ui.inspect` | | UI state: ribbon tab, editor, open dialog, Backstage page, message, grid rect, scroll, perf, `windows` (as `window.list`) and `activeWindow` |
| `ui.inspect` | | UI state: ribbon tab, editor, open dialog, Backstage page, message, selected object, grid rect, scroll, perf, `windows` (as `window.list`) and `activeWindow` |
| `ui.object.select` | `{id?, kind?}` | Selects a floating object on the active sheet (no `id` clears the selection); replies `{selected, rect}`, the rect in window points |
| `ui.click` / `ui.drag` / `ui.move` | `{x, y, toX?, toY?, button?, count?, shift?, cmd?, alt?}` | Real pointer input in window points; refused with an error if it would leave more than 10,000 synthetic events or 8 MiB of synthetic input queued (pointer, key, text and move events share this budget) |
| `ui.cell.click` / `ui.cell.drag` | `{cell}` / `{from, to}` | Pointer input at a cell's centre (works in point mode while editing); same shared queue limits |
| `ui.key` / `ui.text` | `{key, shift?, cmd?, alt?, ctrl?}` / `{text}` | Keyboard input (typing in the grid starts editing); same shared queue limits, and one `ui.text` string may be at most 1 MiB |
@@ -90,6 +91,13 @@ include `window` or `doc` to run it in that window, as with `engine.execute`; th
(`window.*`, `view.newWindow`, `view.hideWindow`, `view.unhideWindow`, `view.switchWindows`) read
them as their own parameters.
`ui.inspect` reports the selected floating object as `selectedObject: {kind, id}` (or `null`),
where `kind` is `chart`, `image`, `shape` or `slicer`; the same names go in the `kind` parameter
of `object.delete`, `object.move`, `object.setAnchorMode` and the `arrange.*` commands.
`selectedChart` remains for existing clients: the id when the selection is a chart, otherwise
`null`. With an object selected, `ui.key {key: "Delete"}` (or Backspace) deletes the object
instead of clearing cells, and Escape clears the selection.
### Windows
Every open workbook has one or more windows; View › New Window (`view.newWindow`) adds another