#1284: Easy Ease, interpolation, Toggle Hold, labels and Time-Reverse skip a locked layer's keys and keep them selected

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDvxCS8xcR28VArjzn1x7U
This commit is contained in:
Claude
2026-10-29 15:53:52 +00:00
parent c4ca4dc0e9
commit 1dc5d2db69
3 changed files with 37 additions and 2 deletions
+7 -1
View File
@@ -498,7 +498,13 @@ fn time_reverse(s: &mut Session, _: &Value) -> Result<Value> {
}
let mut new_sel = vec![];
for ((lid, uid), times) in groups {
let Some(pr) = comp.layer_mut(lid).and_then(|l| l.props.find_mut(uid)) else { continue };
let Some(layer) = comp.layer_mut(lid) else { continue };
// A locked layer's keys stay as they are, and stay selected.
if layer.switches.locked {
new_sel.extend(times.iter().map(|time| KeyRef { layer: lid, prop: uid, time: *time }));
continue;
}
let Some(pr) = layer.props.find_mut(uid) else { continue };
let (mut picked, rest): (Vec<Keyframe>, Vec<Keyframe>) = pr.keys.drain(..).partition(|k| times.contains(&k.time));
effectcraft_keyframe::time_reverse(&mut picked);
new_sel.extend(picked.iter().map(|k| KeyRef { layer: lid, prop: uid, time: k.time }));
+10 -1
View File
@@ -895,7 +895,16 @@ pub(crate) fn edit_keys(s: &mut Session, label: &str, merge: Option<&str>, f: im
groups.entry((k.layer, k.prop)).or_default().push(k.time);
}
for ((lid, uid), times) in groups {
let Some(pr) = comp.layer_mut(lid).and_then(|l| l.props.find_mut(uid)) else { continue };
let Some(layer) = comp.layer_mut(lid) else { continue };
// A locked layer's keys stay as they are, and stay selected.
if layer.switches.locked {
for time in times {
let r = KeyRef { layer: lid, prop: uid, time };
remapped.insert(r, r);
}
continue;
}
let Some(pr) = layer.props.find_mut(uid) else { continue };
// Process each stored key once, in an order that avoids collisions when moving.
let mut idx: Vec<(usize, Tick)> = times.iter().filter_map(|t| key_at(&pr.keys, *t).map(|i| (i, *t))).collect();
idx.sort_by_key(|x| x.0);
+20
View File
@@ -64,3 +64,23 @@ fn a_locked_layers_keys_cannot_be_deleted_or_edited() {
s.execute_checked("keys.set", json!({"layer": layer, "prop": prop, "time": 0, "value": 90})).unwrap();
assert_eq!(s.execute_checked("keys.delete", json!({})).unwrap(), json!(3));
}
/// Easy Ease, Toggle Hold and Time-Reverse leave a locked layer's keys as they are and keep them
/// selected; unlocked, they change them.
#[test]
fn selection_based_key_edits_skip_a_locked_layer() {
let (mut s, layer, prop) = setup();
let keys = keys_of(&s, layer, prop);
let stored = |s: &Session| s.active_comp().unwrap().layer(LayerId(layer)).unwrap().props.find(prop).unwrap().keys.clone();
let before = stored(&s);
s.state.selected_keys = keys.clone();
s.execute_checked("layer.setSwitch", json!({"layers": [layer], "switch": "locked", "value": true})).unwrap();
for command in ["keys.easyEase", "keys.toggleHold", "keys.timeReverse"] {
s.execute_checked(command, json!({})).unwrap();
assert_eq!(stored(&s), before, "{command} changed a locked layer's keys");
assert_eq!(s.state.selected_keys, keys, "{command} dropped the selection of a locked layer's keys");
}
s.execute_checked("layer.setSwitch", json!({"layers": [layer], "switch": "locked", "value": false})).unwrap();
s.execute_checked("keys.easyEase", json!({})).unwrap();
assert_ne!(stored(&s), before, "unlocked, Easy Ease changes the keys");
}