Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 109 additions & 3 deletions apps/desktop-gpui/src/editor_edits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2558,6 +2558,7 @@ pub fn set_clip_segment_timescale(

let old_duration = clip_timeline_duration(timeline);
let old_offsets = editor_timeline::clip_timeline_offsets(timeline);
let holds = timeline.hold_windows();
let incoming_duration = timeline
.effective_transition(index)
.map_or(0.0, |transition| transition.duration);
Expand All @@ -2571,9 +2572,10 @@ pub fn set_clip_segment_timescale(
let old_next_boundary = old_offsets.get(index + 1).copied().unwrap_or(old_duration);
let new_next_boundary = new_offsets.get(index + 1).copied().unwrap_or(new_duration);

let effective = |time: f64| time - held_time_before(&holds, time);
let shift = |time: f64| {
timeline_shift_after_clip_duration_change(
time,
effective(time),
old_offsets[index],
new_offsets[index],
absolute_start,
Expand All @@ -2598,14 +2600,26 @@ pub fn set_clip_segment_timescale(
segment.start += shift(segment.start);
segment.end += shift(segment.end);
}
let bends = [old_offsets[index], absolute_start, old_next_boundary];
// Track times include fullscreen-title holds, during which the clip clock
// stands still, so the remap also bends at every hold edge it spans.
let retimed = old_offsets[index]..=old_next_boundary;
let bends: Vec<f64> = [old_offsets[index], absolute_start, old_next_boundary]
.map(|bend| effective_to_output(&holds, bend))
.into_iter()
.chain(
holds
.iter()
.flat_map(|&(start, end)| [start, end])
.filter(|&edge| retimed.contains(&effective(edge))),
)
.collect();
for segment in &mut timeline.mask_segments {
let old_start = segment.start;
let start_shift = shift(old_start);
segment.start += start_shift;
segment.end += shift(segment.end);
let keyframes = &mut segment.keyframes;
let relative_bends = bends.map(|bend| bend - old_start);
let relative_bends: Vec<f64> = bends.iter().map(|bend| bend - old_start).collect();
split_keyframes_at(
&mut keyframes.position,
&relative_bends,
Expand Down Expand Up @@ -4692,6 +4706,98 @@ mod tests {
assert!(!set_clip_segment_timescale(timeline, 0, 2.0));
}

#[test]
fn clip_speed_changes_ripple_tracks_past_fullscreen_holds() {
let mut project = config(serde_json::json!({
"timeline": {
"segments": [
{ "recordingSegment": 0, "timescale": 1.0, "start": 0.0, "end": 10.0 },
{ "recordingSegment": 1, "timescale": 1.0, "start": 0.0, "end": 10.0 }
],
"zoomSegments": [
{ "start": 11.0, "end": 12.0, "amount": 1.5, "mode": "auto" },
{ "start": 20.0, "end": 21.0, "amount": 1.5, "mode": "auto" }
],
"textSegments": [
{ "start": 0.0, "end": 3.0, "track": 0, "content": "intro", "layout": "fullscreen" },
{ "start": 23.0, "end": 25.0, "track": 0, "content": "outro", "layout": "fullscreen" }
],
"maskSegments": [{
"start": 11.0, "end": 17.0, "maskType": "sensitive",
"center": { "x": 0.5, "y": 0.5 }, "size": { "x": 0.2, "y": 0.2 },
"keyframes": { "intensity": [
{ "time": 0.0, "value": 0.0 },
{ "time": 6.0, "value": 1.0 }
Comment thread
yashranaway marked this conversation as resolved.
Resolved
] }
}]
}
}));
let timeline = project.timeline.as_mut().unwrap();

assert!(set_clip_segment_timescale(timeline, 1, 2.0));

let zooms: Vec<_> = timeline
.zoom_segments
.iter()
.map(|segment| (segment.start, segment.end))
.collect();
assert_eq!(zooms, [(11.0, 12.0), (16.5, 17.0)]);
let texts: Vec<_> = timeline
.text_segments
.iter()
.map(|segment| (segment.start, segment.end))
.collect();
assert_eq!(texts, [(0.0, 3.0), (18.0, 20.0)]);
let mask = &timeline.mask_segments[0];
assert_eq!((mask.start, mask.end), (11.0, 15.0));
let intensity: Vec<_> = mask
.keyframes
.intensity
.iter()
.map(|key| (key.time, key.value))
.collect();
assert_eq!(intensity, [(0.0, 0.0), (2.0, 1.0 / 3.0), (4.0, 1.0)]);
}

#[test]
fn clip_speed_changes_split_mask_keyframes_at_hold_edges() {
let mut project = config(serde_json::json!({
"timeline": {
"segments": [
{ "recordingSegment": 0, "timescale": 1.0, "start": 0.0, "end": 10.0 },
{ "recordingSegment": 1, "timescale": 1.0, "start": 0.0, "end": 10.0 }
],
"zoomSegments": [],
"textSegments": [
{ "start": 14.0, "end": 16.0, "track": 0, "content": "title", "layout": "fullscreen" }
],
"maskSegments": [{
"start": 12.0, "end": 20.0, "maskType": "sensitive",
"center": { "x": 0.5, "y": 0.5 }, "size": { "x": 0.2, "y": 0.2 },
"keyframes": { "intensity": [
{ "time": 0.0, "value": 0.0 },
{ "time": 8.0, "value": 1.0 }
] }
}]
}
}));
let timeline = project.timeline.as_mut().unwrap();

assert!(set_clip_segment_timescale(timeline, 1, 2.0));

let text = &timeline.text_segments[0];
assert_eq!((text.start, text.end), (12.0, 14.0));
let mask = &timeline.mask_segments[0];
assert_eq!((mask.start, mask.end), (11.0, 16.0));
let intensity: Vec<_> = mask
.keyframes
.intensity
.iter()
.map(|key| (key.time, key.value))
.collect();
assert_eq!(intensity, [(0.0, 0.0), (1.0, 0.25), (3.0, 0.5), (5.0, 1.0)]);
}

#[test]
fn clip_speed_changes_remap_mask_keyframes_in_absolute_time() {
for (timescale, expected) in [
Expand Down
Loading