From f6f242d6ddb9b6637dfbf864046b4aa5bb289554 Mon Sep 17 00:00:00 2001 From: bflatastic Date: Fri, 30 Oct 2026 12:20:30 +0700 Subject: [PATCH] layer.slip reports the requested shift and whether it was clamped (#2371) A slip on a layer with no source handles succeeded with `delta: 0` and nothing else, which read like a no-op bug. The result now carries `requested` (seconds) and, per layer, `clamped` when the source could not move that far. Co-Authored-By: Claude Opus 5.5 --- crates/engine/src/cmd/layer.rs | 10 +++++++++- crates/engine/src/cmd/layer/timing.rs | 10 +++++++--- crates/engine/src/tests/fidelity.rs | 5 ++++- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/crates/engine/src/cmd/layer.rs b/crates/engine/src/cmd/layer.rs index 6b6a0ecd9..97fb0f160 100644 --- a/crates/engine/src/cmd/layer.rs +++ b/crates/engine/src/cmd/layer.rs @@ -148,7 +148,15 @@ pub fn specs() -> Vec { cmd!("layer.moveLater", "Move Layers 1 Frame Later", [], Some("Alt+PageDown"), "{layers?}", has_layers, |s, p| move_frames(s, p, 1)), cmd!("layer.moveEarlier10", "Move Layers 10 Frames Earlier", [], Some("Alt+Shift+PageUp"), "{layers?}", has_layers, |s, p| move_frames(s, p, -10)), cmd!("layer.moveLater10", "Move Layers 10 Frames Later", [], Some("Alt+Shift+PageDown"), "{layers?}", has_layers, |s, p| move_frames(s, p, 10)), - cmd!("layer.slip", "Slip Edit", [], None, "{layers?, delta? (seconds) | frames?, merge?}", has_layers, slip), + cmd!( + "layer.slip", + "Slip Edit", + [], + None, + "{layers?, delta? (seconds) | frames?, merge?} → {requested, slipped: [{layer, delta, clamped}]}", + has_layers, + slip + ), cmd!("layer.slipBack", "Slip Edit 1 Frame Earlier", [], None, "{layers?}", has_layers, |s, p| { let mut p = p.clone(); p["frames"] = json!(-1); diff --git a/crates/engine/src/cmd/layer/timing.rs b/crates/engine/src/cmd/layer/timing.rs index 0427dcf0d..650544d17 100644 --- a/crates/engine/src/cmd/layer/timing.rs +++ b/crates/engine/src/cmd/layer/timing.rs @@ -84,8 +84,9 @@ pub(super) fn move_frames(s: &mut Session, p: &Value, frames: i64) -> Result Result { let (cid, ids) = layers_p(s, p)?; let comp = s.project.comp(cid).ok_or(EngineError::NoComp)?; @@ -130,7 +131,10 @@ pub(super) fn slip(s: &mut Session, p: &Value) -> Result { } Ok(()) })?; - Ok(json!({"slipped": shifts.iter().map(|(id, d)| json!({"layer": id.0, "delta": d.seconds()})).collect::>()})) + Ok(json!({ + "requested": delta.seconds(), + "slipped": shifts.iter().map(|(id, d)| json!({"layer": id.0, "delta": d.seconds(), "clamped": *d != delta})).collect::>(), + })) } pub(super) fn arrange(s: &mut Session, p: &Value) -> Result { diff --git a/crates/engine/src/tests/fidelity.rs b/crates/engine/src/tests/fidelity.rs index ebe375601..4fd8a68b8 100644 --- a/crates/engine/src/tests/fidelity.rs +++ b/crates/engine/src/tests/fidelity.rs @@ -58,7 +58,8 @@ fn slip_moves_the_source_under_fixed_in_and_out() { let before = get(&s); assert_eq!((before.start_time, before.in_point.seconds(), before.out_point.seconds()), (Tick::ZERO, 1.0, 3.0)); // Slip the source 0.5 s later: in/out stay, the source (start) moves. - s.execute("layer.slip", json!({"layers": [id], "delta": 0.5})).unwrap(); + let r = s.execute("layer.slip", json!({"layers": [id], "delta": 0.5})).unwrap(); + assert_eq!(r["slipped"][0]["clamped"], json!(false)); let l = get(&s); assert_eq!((l.start_time.seconds(), l.in_point, l.out_point), (0.5, before.in_point, before.out_point)); // The source time at comp 2 s is now 1.5 s. @@ -67,6 +68,8 @@ fn slip_moves_the_source_under_fixed_in_and_out() { let r = s.execute("layer.slip", json!({"layers": [id], "delta": 5.0})).unwrap(); assert_eq!(get(&s).start_time.seconds(), 1.0); assert_eq!(r["slipped"][0]["delta"], json!(0.5)); + // The response says the 5 s asked for was cut short (#2371). + assert_eq!((r["requested"].clone(), r["slipped"][0]["clamped"].clone()), (json!(5.0), json!(true))); // Slip by a frame. s.execute("layer.slipBack", json!({"layers": [id]})).unwrap(); assert!((get(&s).start_time.seconds() - (1.0 - 1.0 / 30.0)).abs() < 1e-9);