135 lines
6.7 KiB
Markdown
135 lines
6.7 KiB
Markdown
# Selection and clipboard
|
|
|
|
This is the implementation contract for multi-selection, copy, cut, paste,
|
|
duplicate, and duplicate unique. It deliberately replaces any incidental older
|
|
behavior. The document model is the authority; the stage and timeline are two
|
|
views of the same editor state.
|
|
|
|
## One selection
|
|
|
|
`[:ui :selections]` is the ordered selection set. Its last member is the primary
|
|
selection in `[:ui :selection]`, used by the inspector and single-subject tools.
|
|
Every member is an occurrence address:
|
|
|
|
```clojure
|
|
[:node owner-symbol-id node-id row-path]
|
|
```
|
|
|
|
The row path distinguishes two occurrences of shared content. Commands that
|
|
write the document canonicalize those addresses before acting:
|
|
|
|
- invalid and non-node addresses are ignored;
|
|
- the same owned node, `[owner-symbol-id node-id]`, is acted on once;
|
|
- when one selected row path is below another selected row path, only the
|
|
ancestor is a clipboard root. Its ordinary parent-pointer subtree comes with
|
|
it, and an instance already displays the symbol it references, so also
|
|
materializing the visibly nested selection would duplicate it twice.
|
|
|
|
Plain click replaces the selection. Shift-click toggles membership, on both the
|
|
stage and timeline. A stage marquee replaces, or with Shift adds to, the same
|
|
set. Timeline rows, bars, and cel blocks render membership from that same set;
|
|
the primary member gets the inspector/focus treatment.
|
|
|
|
Creation targeting is derived rather than stored. The primary (last-selected)
|
|
occurrence is the preferred row; the playhead validates it and, when necessary,
|
|
walks outward to the nearest valid containing occurrence. A target is singular
|
|
even when selection is plural. See `docs/creating-in.md` for the resolver
|
|
contract.
|
|
|
|
## Clipboard value
|
|
|
|
The clipboard is editor state, not document state and not history. Copy records
|
|
a detached snapshot of each canonical root and its complete parent-pointer
|
|
subtree. It records source occurrence paths and authored node data, but normal
|
|
copy deliberately keeps referenced symbol identities. Therefore a pasted
|
|
instance is another use of the same symbol. The clipboard survives cutting its
|
|
nodes because it contains the node snapshot, not merely their addresses.
|
|
|
|
This first implementation is the application's clipboard, not the operating
|
|
system clipboard. It is consequently project-local and has no serialization or
|
|
cross-project identity collision policy hidden inside it.
|
|
|
|
## Paste
|
|
|
|
Paste resolves one destination from the primary selection and playhead. An
|
|
ordinary occurrence is usable only while the playhead maps through every
|
|
enclosing occurrence and lies within its extent. Otherwise resolution walks
|
|
outward, with the open symbol as the total fallback. A selected lane is an
|
|
insertion surface only while all occurrences enclosing its parent symbol are
|
|
valid. Multi-selection supplies one ordered payload, not several destinations;
|
|
only its primary member anchors this resolution.
|
|
|
|
The earliest finite start among the copied roots is aligned with the playhead
|
|
in the destination. All other root starts retain their offset from it. Roots
|
|
without a finite span remain timeless; paste does not invent a span for a shape
|
|
that was authored for the whole symbol. Parent/child timing, transforms,
|
|
channels, corrections, playback, stencil links, and relative root order are
|
|
otherwise copied exactly. Every node receives a new identity, and all internal
|
|
parent and stencil references are remapped.
|
|
|
|
In an ordinary composition overlap is valid. In a lane the pasted finite spans
|
|
claim their intervals using the lane's existing overwrite rule: covered cels
|
|
are removed, crossing cels are trimmed or split, and the pasted roots do not
|
|
overlap one another. Pasting a timeless root into a lane is refused. Validation
|
|
is all-or-nothing.
|
|
|
|
After paste, the new roots are the selection, in clipboard order, and the last
|
|
one is primary.
|
|
|
|
## Cut
|
|
|
|
Cut first takes exactly the same snapshot as copy, then deletes every canonical
|
|
root and its parent-pointer subtree. The clipboard write is editor state; the
|
|
whole document deletion is one history transaction. The selection is cleared.
|
|
Undo restores the deleted document nodes. It does not roll back the clipboard,
|
|
which matches ordinary editor behavior.
|
|
|
|
## Duplicate and Duplicate Unique
|
|
|
|
Duplicate does not read the insertion target or playhead. It is a local
|
|
operation beside the selected material:
|
|
|
|
- in an ordinary composition, copies keep the originals' parent, transform,
|
|
timing, and span, and are stacked immediately in front;
|
|
- for direct children of a lane, the selected temporal envelope is repeated
|
|
immediately after itself. Relative timing and gaps inside the selected set are
|
|
preserved, and later cels ripple forward by the envelope duration. This is
|
|
the lane's useful "duplicate forward" behavior; it is not a second command.
|
|
|
|
Selections in several owners are handled per owner in one command. Thus two
|
|
lane selections repeat in their respective lanes, while a selected composition
|
|
node duplicates in place, all as one history step.
|
|
|
|
Normal Duplicate preserves symbol references, just like normal copy/paste.
|
|
Duplicate Unique performs the same placement but deep-copies the complete graph
|
|
of every referenced symbol. One shared remap table is used for the whole batch,
|
|
so two duplicated instances that shared a nested part still share one new copy
|
|
with each other, while sharing nothing mutable with the originals. Immutable
|
|
media/store blocks may remain shared.
|
|
|
|
After either duplicate command, the new roots replace the selection.
|
|
|
|
## History, refusal, and stale state
|
|
|
|
Cut, paste, duplicate, and duplicate unique each call one domain command and
|
|
commit through one `edit/transaction`; each is exactly one undo/redo step no
|
|
matter how many nodes or symbols it touches. Copy and selection do not
|
|
touch history. A refusal changes no document leaves and creates no history step.
|
|
|
|
Undo/redo filters the complete selection set against the restored document and
|
|
repairs the primary selection. Clipboard payloads remain snapshots. Paste
|
|
validates the resolved destination and all remapped references at commit time,
|
|
so a stale selection or a newly impossible symbol cycle refuses rather than
|
|
partially editing.
|
|
|
|
## Required tests
|
|
|
|
Domain tests cover canonical ancestor/descendant selection, subtree ID remaps,
|
|
normal shared references, deep unique graph remaps, multi-root relative timing,
|
|
composition overlap, lane overwrite, lane forward duplication and ripple,
|
|
mixed-owner duplication, cycle refusal, stale targets, and all-or-nothing
|
|
failure. Event tests cover copy without history, atomic cut/paste/duplicates,
|
|
resulting multi-selection, target fallback at the playhead,
|
|
and one undo plus redo of each mutation. Browser tests cover mirrored stage and
|
|
timeline selection, Shift-toggle on labels/bars/cels, singular derived creation
|
|
targeting, and the keyboard commands.
|