Add multi-selection clipboard commands
This commit is contained in:
parent
987e289f89
commit
edca82cd5d
10 changed files with 839 additions and 36 deletions
139
docs/clipboard-plan.md
Normal file
139
docs/clipboard-plan.md
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
# 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.
|
||||
|
||||
Targeting remains separate. Clicking a timeline label both selects it and aims
|
||||
it. Shift-click changes the selection set and makes the clicked row the one
|
||||
target. Clicking a bar/cel or the stage changes selection without silently
|
||||
changing the target. A target is singular even when selection is plural.
|
||||
|
||||
## 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 always uses the explicit insertion target and the playhead:
|
||||
|
||||
- no target: paste at the top of the open symbol;
|
||||
- aimed ordinary node: paste beside it in the symbol that owns it;
|
||||
- aimed instance, including an instance whose source is drawn as a lane: paste
|
||||
inside the symbol it places;
|
||||
- the open lane's own row: paste directly into that lane.
|
||||
|
||||
The destination is resolved structurally from the target. It does not require
|
||||
the target instance to be visible at the playhead. Being outside the target
|
||||
symbol's authored window is legal: content can exist there and be cropped by
|
||||
the window. Lane paste may grow the lane/window because a sequence cannot store
|
||||
an overlapping or unreachable tail and pretend the edit succeeded.
|
||||
|
||||
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, aiming, 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 destination and all remapped references at commit time, so a stale
|
||||
target 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 resolution, pasting beyond a symbol window,
|
||||
and one undo plus redo of each mutation. Browser tests cover mirrored stage and
|
||||
timeline selection, Shift-toggle on labels/bars/cels, singular aiming, and the
|
||||
keyboard commands.
|
||||
Loading…
Add table
Add a link
Reference in a new issue