Merge pull request #207 from carlitosteso3/fix/ripple-trim-targets-164

Q/W ripple trim only the selected clips' tracks (#164)
This commit is contained in:
Brandon Thomas
2026-10-07 20:35:37 -04:00
committed by GitHub
2 changed files with 32 additions and 8 deletions
+20
View File
@@ -415,3 +415,23 @@ fn fps_validation_keeps_real_rates_and_refuses_degenerate_ones() {
assert_eq!(s.sequence_rate(), before);
assert_eq!(s.execute("project.inspect", json!({})).unwrap().to_string().len(), n, "no sequence was created");
}
/// #164: with a clip selected, Q/W trim only its tracks (and its linked sound), not every
/// targeted track under the playhead (the music on A2 used to be cut too).
#[test]
fn ripple_trim_to_playhead_follows_the_selection() {
let mut s = demo_unlocked();
let rate = s.sequence_rate();
let a2 = |s: &Session| s.active_sequence().unwrap().audio_tracks[1].items.iter().map(|i| (i.start, i.duration)).collect::<Vec<_>>();
let music = a2(&s);
assert!(!music.is_empty(), "the demo has music on A2");
let (id, start, _) = v1(&s)[1];
s.execute("timeline.select", json!({"clips": [id]})).unwrap();
let ph = start + rate.tick_of(10);
s.execute("playhead.set", json!({"time": ph.0})).unwrap();
s.execute("trim.rippleNext", json!({})).unwrap();
assert_eq!(v1(&s).into_iter().find(|c| c.0 == id).unwrap().2, ph, "the selected clip ends at the playhead");
assert_eq!(a2(&s), music, "the music on A2 is untouched");
let linked = s.active_sequence().unwrap().audio_tracks[0].items.iter().find(|i| i.start == start).map(|i| i.end());
assert_eq!(linked, Some(ph), "its linked sound on A1 is trimmed with it");
}
+12 -8
View File
@@ -239,14 +239,18 @@ pub fn to_playhead(s: &mut Session, p: &Value) -> Result<Value> {
let ripple = p.get("ripple").and_then(Value::as_bool).unwrap_or(true);
let ph = s.playhead();
let seq = s.active_sequence().ok_or(EngineError::NoSequence)?;
let targeted = s.targeting().targeted;
let tracks: Vec<TrackId> = seq
.video_tracks
.iter()
.chain(seq.audio_tracks.iter())
.filter(|t| targeted.contains(&t.id) && !t.locked && t.items.iter().any(|i| i.start < ph && ph < i.end()))
.map(|t| t.id)
.collect();
let under = |t: &filmcraft_project::Track, keep: &dyn Fn(ClipId) -> bool| t.items.iter().any(|i| i.start < ph && ph < i.end() && keep(i.id));
// Like Add Edit: selected clips under the playhead (and their linked partners) are trimmed,
// and only their tracks (#164); with none, the targeted tracks are.
let sel = crate::commands::with_links(s, &s.state.selection);
let selected: Vec<TrackId> =
seq.video_tracks.iter().chain(seq.audio_tracks.iter()).filter(|t| !t.locked && under(t, &|c| sel.contains(&c))).map(|t| t.id).collect();
let tracks = if selected.is_empty() {
let targeted = s.targeting().targeted;
seq.video_tracks.iter().chain(seq.audio_tracks.iter()).filter(|t| targeted.contains(&t.id) && !t.locked && under(t, &|_| true)).map(|t| t.id).collect()
} else {
selected
};
if tracks.is_empty() {
return Err(EngineError::Other("no clip under the playhead on the targeted tracks".into()));
}