mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-01 05:58:19 -06:00
#1284: preserve locked keys during group transforms and cover unselected menus
This commit is contained in:
@@ -72,6 +72,10 @@ fn transform(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let mut new_sel = vec![];
|
||||
for ((lid, uid), times) in groups {
|
||||
let Some(layer) = comp.layer_mut(lid) else { continue };
|
||||
if layer.switches.locked {
|
||||
new_sel.extend(times.into_iter().map(|time| KeyRef { layer: lid, prop: uid, time }));
|
||||
continue;
|
||||
}
|
||||
let (start, stretch) = (layer.start_time, layer.stretch);
|
||||
let to_comp = |lt: Tick| if (stretch - 100.0).abs() < 1e-9 { start + lt } else { start + Tick((lt.0 as f64 * stretch / 100.0) as i64) };
|
||||
let to_layer = |ct: Tick| {
|
||||
|
||||
@@ -84,3 +84,42 @@ fn selection_based_key_edits_skip_a_locked_layer() {
|
||||
s.execute_checked("keys.easyEase", json!({})).unwrap();
|
||||
assert_ne!(stored(&s), before, "unlocked, Easy Ease changes the keys");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn key_transform_preserves_locked_keys_in_a_mixed_selection() {
|
||||
let (mut s, locked, locked_prop) = setup();
|
||||
let unlocked = s.execute("layer.newSolid", json!({"name": "Unlocked", "color": "#fff"})).unwrap()["layer"].as_u64().unwrap();
|
||||
for t in 0..3 {
|
||||
s.execute("prop.addKey", json!({"layer": unlocked, "path": "transform/opacity", "time": t, "value": t * 25})).unwrap();
|
||||
}
|
||||
let unlocked_prop = s.active_comp().unwrap().layer(LayerId(unlocked)).unwrap().props.prop("transform/opacity").unwrap().uid;
|
||||
let locked_keys = keys_of(&s, locked, locked_prop);
|
||||
s.state.selected_keys = locked_keys.iter().chain(keys_of(&s, unlocked, unlocked_prop).iter()).copied().collect();
|
||||
s.execute("layer.setSwitch", json!({"layers": [locked], "switch": "locked", "value": true})).unwrap();
|
||||
let stored = |s: &Session, layer, prop| s.active_comp().unwrap().layer(LayerId(layer)).unwrap().props.find(prop).unwrap().keys.clone();
|
||||
let before = stored(&s, locked, locked_prop);
|
||||
let undo = s.history.undo.len();
|
||||
for offset in [0.5, 1.0] {
|
||||
s.execute("keys.transform", json!({"timeOffset": offset, "valueOffset": offset * 10.0, "merge": "mixed-transform", "fromStart": true})).unwrap();
|
||||
assert_eq!(stored(&s, locked, locked_prop), before);
|
||||
assert_eq!(s.state.selected_keys.len(), 6);
|
||||
assert!(locked_keys.iter().all(|k| s.state.selected_keys.contains(k)), "locked selections stay at their original times");
|
||||
assert!(keys_of(&s, unlocked, unlocked_prop).iter().all(|k| s.state.selected_keys.contains(k)), "unlocked selections follow the keys");
|
||||
let moved = stored(&s, unlocked, unlocked_prop);
|
||||
for (i, key) in moved.iter().enumerate() {
|
||||
assert_eq!(key.time.seconds(), i as f64 + offset);
|
||||
assert_eq!(key.value.as_f64(), i as f64 * 25.0 + offset * 10.0);
|
||||
}
|
||||
}
|
||||
assert_eq!(s.history.undo.len(), undo + 1, "one merged drag");
|
||||
let after = stored(&s, unlocked, unlocked_prop);
|
||||
s.execute("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(stored(&s, locked, locked_prop), before);
|
||||
assert_eq!(stored(&s, unlocked, unlocked_prop), before);
|
||||
s.execute("edit.redo", json!({})).unwrap();
|
||||
assert_eq!(stored(&s, locked, locked_prop), before);
|
||||
assert_eq!(stored(&s, unlocked, unlocked_prop), after);
|
||||
s.execute("layer.setSwitch", json!({"layers": [locked], "switch": "locked", "value": false})).unwrap();
|
||||
s.execute("keys.transform", json!({"timeOffset": 0.5, "valueOffset": 10})).unwrap();
|
||||
assert_ne!(stored(&s, locked, locked_prop), before, "unlocking makes the keys editable again");
|
||||
}
|
||||
|
||||
@@ -552,29 +552,34 @@ fn the_key_menu_is_translated_and_cannot_edit_a_locked_layers_keys() {
|
||||
let layer = app.session.active_comp().unwrap().layer(LayerId(id)).unwrap().clone();
|
||||
let prop = layer.props.prop("transform/opacity").unwrap().clone();
|
||||
let key = prop.keys[0].clone();
|
||||
let enabled = |locked: bool, label: &str| {
|
||||
let enabled = |locked: bool, selected: bool, label: &str| {
|
||||
let mut layer = layer.clone();
|
||||
layer.switches.locked = locked;
|
||||
let mut actions = vec![];
|
||||
let mut h = Harness::new_ui(|ui| key_menu(ui, &app, &layer, &prop, &key, true, &mut actions));
|
||||
let mut h = Harness::new_ui(|ui| key_menu(ui, &app, &layer, &prop, &key, selected, &mut actions));
|
||||
h.run();
|
||||
!h.get_by_label(label).accesskit_node().is_disabled()
|
||||
};
|
||||
for label in
|
||||
["Copy", "Select Equal Keyframes", "Select All Keyframes", "Delete", "Edit Value…", "Easy Ease", "Time-Reverse Keyframes", "Keyframe Velocity…"]
|
||||
{
|
||||
assert!(enabled(false, label), "{label} on an unlocked layer");
|
||||
assert!(enabled(false, true, label), "{label} on an unlocked layer");
|
||||
}
|
||||
for label in ["Copy", "Select Equal Keyframes", "Select All Keyframes"] {
|
||||
assert!(enabled(true, label), "{label} is still available on a locked layer");
|
||||
assert!(enabled(true, true, label), "{label} is still available on a locked layer");
|
||||
}
|
||||
for label in ["Delete", "Edit Value…", "Easy Ease", "Time-Reverse Keyframes", "Keyframe Velocity…", "Linear"] {
|
||||
assert!(!enabled(true, label), "{label} is off on a locked layer");
|
||||
assert!(!enabled(true, true, label), "{label} is off on a locked layer");
|
||||
assert!(!enabled(true, false, label), "{label} is off on an unselected locked key");
|
||||
}
|
||||
for label in ["Copy", "Select Equal Keyframes", "Select Previous Keyframes", "Select Following Keyframes"] {
|
||||
assert!(!enabled(true, false, label), "{label} requires selecting the clicked key");
|
||||
}
|
||||
assert!(enabled(true, false, "Select All Keyframes"));
|
||||
// The labels follow the interface language.
|
||||
crate::i18n::set_current("es");
|
||||
let copy = crate::i18n::context_item("keys.copy", &json!({}), "Copy");
|
||||
assert_ne!(copy, "Copy", "the catalog translates Copy");
|
||||
assert!(enabled(false, ©));
|
||||
assert!(enabled(false, true, ©));
|
||||
crate::i18n::set_current("en");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user