Skip to content

Phase 5 slice D1 — Dope sheet multi-select + copy/paste/bulk-move #374

Description

@fernandotonon

Overview

First chunk of slice D from #260 — the polish that makes the dope sheet (slice C, #372/#373) actually usable for editing more than one keyframe at a time. Defers per-channel rows (D2) and the curve editor + Bezier handles (D3) to their own follow-up issues so this PR stays reviewable.

The original audit comment for slice D bundled everything together; this issue splits it into three sub-slices:

  • D1 (this issue): multi-select, copy/paste, bulk move. Data model unchanged — uses Ogre's existing TransformKeyFrame.
  • D2 (follow-up): per-channel rows (T.X / T.Y / R.W / etc.) under each bone.
  • D3 (follow-up): curve editor + Bezier handles (requires a tangent side-table since TransformKeyFrame has no handle data).

Scope (this slice)

Selection state

  • New selectedKeyframes set on the dope sheet's QML view: { bone, time } pairs.
  • Click a diamond → replaces selection with just that one (existing behavior preserved).
  • Ctrl+click → toggles a diamond in the selection.
  • Shift+click → range-select on the same bone (from last clicked to this one).
  • Drag a rectangle on empty timeline area → marquee select all diamonds inside the rect (across rows).
  • Esc → clear selection.
  • Selected diamonds render with a thicker red outline + larger size.

Bulk move

  • Dragging any selected diamond moves the whole selection by the same dt.
  • Constraint: clamp the bulk delta so no member of the selection lands < 0 or > animation length, and no member collides with an unselected keyframe at the target time.
  • Single MoveKeyframesCommand per release — undo brings the entire selection back as one step.

Copy / paste

  • New commands/CopyPasteKeyframesCommand — pure paste side; copy itself is just a clipboard string.
  • Copy (Ctrl+C while hovering the dope sheet): serialize selected keyframes (bone + relative time + TRS) to JSON in the system clipboard. The earliest selected time becomes t0 so paste is relative.
  • Paste (Ctrl+V): parse JSON; for each entry, create a new keyframe at currentSliderTime + (entryTime - t0). Skip entries whose target time would collide with an existing keyframe (warn via Sentry breadcrumb, don't fail). One PasteKeyframesCommand for the batch — undo removes everything pasted.

Controller (C++) additions

  • Q_INVOKABLE bool moveKeyframes(QVariantList selection, double dt) — atomic bulk move, undoable.
  • Q_INVOKABLE QString serializeKeyframes(QVariantList selection) — clipboard payload (JSON).
  • Q_INVOKABLE int pasteKeyframesAt(QString json, double atTime) — returns count actually pasted (skipping collisions).
  • New keyframeSelectionChanged() signal so the dope sheet's selection-overlay redraws.

Tests

Pure-data:

  • serializeKeyframes round-trip (parse what we wrote and verify shape).
  • pasteKeyframesAt rejects when the target collides with existing keyframes.

Ogre fixture:

  • moveKeyframes shifts every member by dt; one command on the undo stack.
  • MoveKeyframesCommand undo restores all keyframes; redo re-applies.
  • Bulk move that would push one selection member past length is clamped (entire selection moves by the maximum legal dt).
  • PasteKeyframesCommand round-trips: paste, undo, paste again — count is consistent.

Out of scope (separate issues)

  • Per-channel rows (slice D2)
  • Bezier handles + curve editor (slice D3)

Acceptance criteria

  • Ctrl+click toggles a diamond in/out of the selection
  • Shift+click range-selects within a row
  • Drag-rectangle on empty timeline marquee-selects across rows
  • Esc clears the selection
  • Selected diamonds render distinctly (thicker outline / different color)
  • Dragging any selected diamond moves the whole selection by the same dt
  • Bulk move clamps at clip boundaries; collisions with unselected keyframes block the move
  • Ctrl+C with a selection serializes to clipboard (visible via paste in another text app)
  • Ctrl+V at a different scrub time pastes the selection at offset, skipping collisions
  • Ctrl+Z undoes a bulk move / paste in one step
  • Linux CI: 4+ new tests pass; Sonar quality gate stays green

Plan (#260)

Phase 5 audit comment

Follows: #373 (slice C — dope sheet view).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions