diff --git a/crates/engine/src/cmd/prop.rs b/crates/engine/src/cmd/prop.rs index 17dee1898..3a8419765 100644 --- a/crates/engine/src/cmd/prop.rs +++ b/crates/engine/src/cmd/prop.rs @@ -267,8 +267,13 @@ fn twin_params(p: &Value, lid: LayerId, uid: Uid) -> Value { /// The value of property `uid` of `lid` at the CTI as numbers (Position with Separate /// Dimensions: its X / Y / Z Position). fn current_components(s: &Session, cid: ItemId, lid: LayerId, uid: Uid) -> Option> { + components_at(s, cid, lid, uid, None) +} + +/// [`current_components`] at layer time `at` (the CTI when `None`). +fn components_at(s: &Session, cid: ItemId, lid: LayerId, uid: Uid, at: Option) -> Option> { let l = s.project.comp(cid)?.layer(lid)?; - let lt = l.layer_time(s.time_of(cid)); + let lt = at.unwrap_or_else(|| l.layer_time(s.time_of(cid))); if let Some(tr) = l.transform() && tr.get("position").is_some_and(|q| q.uid == uid) && tr.get("positionX").is_some() @@ -329,9 +334,10 @@ fn set_selected(s: &mut Session, p: &Value) -> Result { }; let follow: Vec = p.get("follow").and_then(Value::as_array).into_iter().flatten().filter_map(Value::as_u64).filter_map(|d| usize::try_from(d).ok()).collect(); + // Each layer's own value where `set` writes it (a `time` other than the CTI keys there). let typed = |s: &Session, q: &mut Value, olid: LayerId, ouid: Uid| { if let (Some(dims), Some(n)) = (&dims, &new) - && let Some(mut c) = current_components(s, cid, olid, ouid) + && let Some(mut c) = components_at(s, cid, olid, ouid, snapped_key_time(s, cid, olid, q, "prop.set").ok().flatten()) { let change = dims.first().and_then(|&d| Some(n.get(d)? - c.get(d)?)).unwrap_or(0.0); for &d in &follow { diff --git a/crates/engine/src/tests/timeline.rs b/crates/engine/src/tests/timeline.rs index fa9117e37..75d1b71be 100644 --- a/crates/engine/src/tests/timeline.rs +++ b/crates/engine/src/tests/timeline.rs @@ -1449,4 +1449,10 @@ fn a_typed_component_moves_its_follow_components_by_each_layers_own_change() { assert_eq!(prop(&s, a, "audio/levels").value.components(), vec![-3.0, -9.0]); assert_eq!(prop(&s, b, "audio/levels").value.components(), vec![-3.0, -13.0], "B rises 17 dB on both channels"); assert_eq!(s.history.undo.len(), undo + 1, "one undo step"); + + // Keyed at a time other than the CTI: B's change is measured on its key there. + animate(&mut s, b, "audio/levels", &[(0.0, json!([-20.0, -30.0])), (1.0, json!([-10.0, -40.0]))]); + s.execute("prop.set", json!({"layer": a, "prop": uid, "value": [-4.0, -10.0], "time": 1.0, "selected": true, "dims": [0], "follow": [1]})).unwrap(); + let keys: Vec> = prop(&s, b, "audio/levels").keys.iter().map(|k| k.value.components()).collect(); + assert_eq!(keys, vec![vec![-20.0, -30.0], vec![-4.0, -34.0]], "B's 1 s key rises 6 dB on both channels"); }