M5.11: Graph Editor: drags move every selected key, Shift keeps an axis, Ctrl / Ctrl+Alt clicks; drags and the transform box never merge or lose keys

- `keys.transform {fromStart}`: each step of a merged drag gives the whole transform since the
  drag started and applies to the keys as they were then. Keys squeezed onto one frame on the
  way (Alt-drag scaling, the transform box) are no longer merged for good, and a slow
  transform-box move, whose per-frame offsets rounded to nothing, now moves the keys.
- Dragging a key in the Graph Editor moves every selected key in time and value (it moved only
  the grabbed one), by how far the pointer went from where the button went down (the key no
  longer jumps under the pointer); Shift keeps the drag to the time or the value axis.
- Shift+click toggles a key in or out of the selection; Ctrl+click switches Linear ↔ Auto
  Bezier and Ctrl+Alt+click toggles Hold, as in the Timeline.
- The transform box and the Timeline's Alt-drag scale send whole-gesture values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
bflatastic
2026-10-05 07:52:20 +07:00
co-authored by Claude Opus 5.5
parent 9929b5f9ac
commit daed03305a
5 changed files with 130 additions and 33 deletions
+19
View File
@@ -938,3 +938,22 @@ fn key_selection_toggles_and_skips_locked_layers_and_hold_restores_bezier() {
s.execute("keys.selectAll", json!({"layers": [a]})).unwrap();
assert!(s.state.selected_keys.is_empty());
}
#[test]
fn transform_drags_apply_their_whole_offset_to_the_keys_they_started_with() {
let mut s = comp();
let a = solid(&mut s, "#ff0000");
opacity_keys(&mut s, a, &[0.0, 1.0, 2.0]);
s.execute("keys.selectAll", json!({"layers": [a]})).unwrap();
// Squeezed onto one frame on the way, then back: the three keys survive.
for k in [0.5, 0.01, 0.0001, 1.0] {
s.execute("keys.transform", json!({"timeScale": k, "timeAnchor": 0.0, "merge": "box", "fromStart": true})).unwrap();
}
assert_eq!(key_times(&s, a), vec![0.0, 1.0, 2.0]);
// A slow move in tiny steps still arrives (each step is the whole offset).
s.history.merge_key = None;
for i in 1..=30 {
s.execute("keys.transform", json!({"timeOffset": i as f64 * 0.1 / 30.0, "merge": "move", "fromStart": true})).unwrap();
}
assert_eq!(key_times(&s, a), vec![0.1, 1.1, 2.1]);
}