From 6c740fb579dd456fa7e8b4d57a13e100d2546889 Mon Sep 17 00:00:00 2001 From: Aditya Garud <153842990+yashranaway@users.noreply.github.com> Date: Mon, 26 Oct 2026 05:36:50 +0000 Subject: [PATCH 1/5] fix(rendering): ignore cursor movement in timeline cuts --- crates/rendering/src/zoom_spring.rs | 195 +++++++++++++++++++++++++++- 1 file changed, 192 insertions(+), 3 deletions(-) diff --git a/crates/rendering/src/zoom_spring.rs b/crates/rendering/src/zoom_spring.rs index 5f66c11806a..2d697ad8037 100644 --- a/crates/rendering/src/zoom_spring.rs +++ b/crates/rendering/src/zoom_spring.rs @@ -463,6 +463,7 @@ struct TimeMapSegment { timeline_start: f64, timeline_end: f64, recording_start: f64, + recording_end: f64, timescale: f64, recording_clip: u32, } @@ -488,6 +489,7 @@ fn build_time_map(timeline: Option<&TimelineConfiguration>) -> Vec) -> Vec) -> Vec) -> Vec { - let recording_start = map_timeline_to_recording_secs( + let mut recording_start = map_timeline_to_recording_secs( &time_map, segment.start, recording_clip, prefer_outgoing, ); - let recording_end = map_timeline_to_recording_secs( + let mut recording_end = map_timeline_to_recording_secs( &time_map, segment.end, recording_clip, prefer_outgoing, ) .max(recording_start); + let mut spans: Vec<_> = time_map + .iter() + .filter(|span| { + span.timeline_start < segment.end && span.timeline_end > segment.start + }) + .collect(); + let has_cut = spans.windows(2).any(|pair| { + pair[0].recording_clip != pair[1].recording_clip + || pair[0].recording_end != pair[1].recording_start + }); + spans.retain(|span| { + recording_clip.is_none_or(|clip| span.recording_clip == clip) + }); + let cut_cursor_events = if has_cut && !spans.is_empty() { + recording_start = f64::INFINITY; + recording_end = f64::NEG_INFINITY; + for span in &spans { + let start = segment.start.max(span.timeline_start); + let end = segment.end.min(span.timeline_end); + recording_start = recording_start.min( + span.recording_start + + (start - span.timeline_start) * span.timescale, + ); + recording_end = recording_end.max( + span.recording_start + (end - span.timeline_start) * span.timescale, + ); + } + Some(CursorEvents { + moves: cursor_events + .moves + .iter() + .filter(|event| { + spans.iter().any(|span| { + event.time_ms >= span.recording_start * 1000.0 + && event.time_ms < span.recording_end * 1000.0 + }) + }) + .cloned() + .collect(), + clicks: Vec::new(), + }) + } else { + None + }; Some(ZoomClusters::new( - cursor_events, + cut_cursor_events.as_ref().unwrap_or(cursor_events), moves_time_ordered, recording_start, recording_end, @@ -1358,6 +1407,7 @@ mod tests { timeline_start: start, timeline_end: end, recording_start: -0.0, + recording_end: (end - start) * 1.5, timescale: 1.5, recording_clip: clip, }; @@ -2717,6 +2767,145 @@ mod tests { } } + #[test] + fn auto_zoom_ignores_cursor_moves_in_timeline_cuts() { + for (first, second, timescale) in [(0.0, 10.0, 1.0), (10.0, 0.0, 2.0)] { + let mut project = + project_with_crop_styles(vec![auto_segment(0.5, 7.9 / timescale, 2.0)], Vec::new()); + project.timeline.as_mut().unwrap().segments = [first, second] + .into_iter() + .map(|start| TimelineSegment { + start, + end: start + 4.0, + timescale, + ..Default::default() + }) + .collect(); + let retained = vec![move_event(0.0, 0.2, 0.2), move_event(10000.0, 0.8, 0.8)]; + let mut polluted = retained.clone(); + polluted.extend([move_event(4000.0, 0.9, 0.9), move_event(7000.0, 0.1, 0.1)]); + for sorted in [false, true] { + let build = |mut moves: Vec| { + if sorted { + moves.sort_by(|a, b| a.time_ms.total_cmp(&b.time_ms)); + } + let mut timeline = ZoomTransformTimeline::from_project_for_clip( + &project, + &CursorEvents { + moves, + clicks: Vec::new(), + }, + 8.0 / timescale, + XY::new(1920, 1080), + 0, + ); + timeline.precompute(); + timeline + }; + let expected = build(retained.clone()); + assert_samples_identical(&expected, &build(polluted.clone())); + for (time, position) in [(2.0 / timescale, first), (6.0 / timescale, second)] { + let focus = if position == 0.0 { 0.2 } else { 0.8 }; + assert_eq!( + expected.targets_at(time, XY::new(0.5, 0.5)).center, + follow_center((focus, focus)) + ); + } + } + } + } + + #[test] + fn auto_zoom_without_internal_cuts_matches_original_clusters() { + let cursor = CursorEvents { + moves: vec![ + move_event(0.0, 0.1, 0.1), + move_event(2000.0, 0.3, 0.2), + move_event(4000.0, 0.4, 0.3), + move_event(6000.0, 0.9, 0.9), + move_event(10000.0, 0.2, 0.2), + ], + clicks: Vec::new(), + }; + for split in [None, Some(3.0), Some(3.754321)] { + for (timescale, held) in [(1.0, false), (1.5, false), (1.5, true)] { + let mut project = project_with_crop_styles( + vec![auto_segment(0.5, 6.0 / timescale, 2.0)], + vec![crop_style( + 1.0, + 3.0, + 0, + Some(Crop { + position: XY::new(100, 100), + size: XY::new(1000, 800), + }), + )], + ); + let config = project.timeline.as_mut().unwrap(); + config.segments = vec![TimelineSegment { + start: 0.0, + end: split.unwrap_or(8.0), + timescale, + ..Default::default() + }]; + if let Some(split) = split { + config.segments.push(TimelineSegment { + start: split, + end: 8.0, + timescale, + ..Default::default() + }); + } + config.segments.push(TimelineSegment { + start: 12.0, + end: 16.0, + timescale: 1.0, + ..Default::default() + }); + if held { + config.text_segments.push( + serde_json::from_value(serde_json::json!({ + "start": 2.0, "end": 3.0, "layout": "fullscreen" + })) + .unwrap(), + ); + } + let build = || { + ZoomTransformTimeline::from_project_for_clip( + &project, + &cursor, + 12.0, + XY::new(1920, 1080), + 0, + ) + }; + let mut actual = build(); + let mut original = build(); + let segment = &original.zoom_segments[0]; + let start = map_timeline_to_recording_secs( + &original.time_map, + segment.start, + Some(0), + false, + ); + let end = + map_timeline_to_recording_secs(&original.time_map, segment.end, Some(0), false) + .max(start); + original.clusters[0] = Some(ZoomClusters::new( + &cursor, + true, + start, + end, + segment, + &CursorCropMaps::from_project(&project, XY::new(1920, 1080)), + )); + actual.precompute(); + original.precompute(); + assert_samples_identical(&actual, &original); + } + } + } + #[test] fn timeline_mapping_filters_clusters_in_recording_time() { // Timeline: single segment showing recording range [10s, 20s] at 1x, From c8beaaec3f69b1708bc6da2f99ea64fc20c31a2e Mon Sep 17 00:00:00 2001 From: Aditya Garud <153842990+yashranaway@users.noreply.github.com> Date: Mon, 26 Oct 2026 06:02:08 +0000 Subject: [PATCH 2/5] fix(rendering): keep carried cursor and cut endpoints in auto zoom --- crates/rendering/src/zoom_spring.rs | 200 +++++++++++++++++++++------- 1 file changed, 153 insertions(+), 47 deletions(-) diff --git a/crates/rendering/src/zoom_spring.rs b/crates/rendering/src/zoom_spring.rs index 2d697ad8037..7c78cb5dd64 100644 --- a/crates/rendering/src/zoom_spring.rs +++ b/crates/rendering/src/zoom_spring.rs @@ -622,6 +622,92 @@ fn map_timeline_to_recording_secs( last.recording_start + (last.timeline_end - last.timeline_start) * last.timescale } +/// When an auto zoom spans a timeline cut, returns the cursor moves the viewer +/// actually sees during it and the recording range they cover. +fn cursor_events_across_cut( + time_map: &[TimeMapSegment], + segment: &ZoomSegment, + recording_clip: Option, + cursor_events: &CursorEvents, +) -> Option<(CursorEvents, f64, f64)> { + // A zoom ending exactly at a cut maps its end into the incoming range, so + // the span starting there still counts as a cut even though it adds no time. + let mut spans: Vec<_> = time_map + .iter() + .filter(|span| span.timeline_start <= segment.end && span.timeline_end > segment.start) + .collect(); + let has_cut = spans.windows(2).any(|pair| { + pair[0].recording_clip != pair[1].recording_clip + || pair[0].recording_end != pair[1].recording_start + }); + spans.retain(|span| { + span.timeline_start < segment.end + && recording_clip.is_none_or(|clip| span.recording_clip == clip) + }); + if !has_cut || spans.is_empty() { + return None; + } + let ranges: Vec<_> = spans + .iter() + .map(|span| { + let to_recording_ms = |timeline_secs: f64| { + (span.recording_start + (timeline_secs - span.timeline_start) * span.timescale) + * 1000.0 + }; + ( + to_recording_ms(segment.start.max(span.timeline_start)), + to_recording_ms(segment.end.min(span.timeline_end)), + ) + }) + .collect(); + let recording_start = ranges + .iter() + .map(|range| range.0) + .fold(f64::INFINITY, f64::min) + / 1000.0; + let recording_end = ranges + .iter() + .map(|range| range.1) + .fold(f64::NEG_INFINITY, f64::max) + / 1000.0; + let finite_moves = cursor_events + .moves + .iter() + .filter(|event| event.x.is_finite() && event.y.is_finite() && event.time_ms.is_finite()); + // The cursor shown when a kept range starts is the last position before + // it, even if that move was cut out. + let carried = ranges.iter().filter_map(|&(start_ms, _)| { + finite_moves + .clone() + .filter(|event| event.time_ms <= start_ms) + .max_by(|a, b| a.time_ms.total_cmp(&b.time_ms)) + .filter(|event| event.time_ms < start_ms) + .map(|event| cap_project::CursorMoveEvent { + time_ms: start_ms, + ..event.clone() + }) + }); + let mut moves: Vec<_> = finite_moves + .clone() + .filter(|event| { + ranges + .iter() + .any(|&(start_ms, end_ms)| event.time_ms >= start_ms && event.time_ms <= end_ms) + }) + .cloned() + .chain(carried) + .collect(); + moves.sort_by(|a, b| a.time_ms.total_cmp(&b.time_ms)); + Some(( + CursorEvents { + moves, + clicks: Vec::new(), + }, + recording_start, + recording_end, + )) +} + /// One precomputed step. Sample times are implicit: `samples[i]` is the state /// at `i * STEP_MS`, so lookup is pure index math. #[derive(Clone, Copy, Debug)] @@ -786,66 +872,28 @@ impl ZoomTransformTimeline { .iter() .map(|segment| match segment.mode { ZoomMode::Auto => { - let mut recording_start = map_timeline_to_recording_secs( + let recording_start = map_timeline_to_recording_secs( &time_map, segment.start, recording_clip, prefer_outgoing, ); - let mut recording_end = map_timeline_to_recording_secs( + let recording_end = map_timeline_to_recording_secs( &time_map, segment.end, recording_clip, prefer_outgoing, ) .max(recording_start); - let mut spans: Vec<_> = time_map - .iter() - .filter(|span| { - span.timeline_start < segment.end && span.timeline_end > segment.start - }) - .collect(); - let has_cut = spans.windows(2).any(|pair| { - pair[0].recording_clip != pair[1].recording_clip - || pair[0].recording_end != pair[1].recording_start - }); - spans.retain(|span| { - recording_clip.is_none_or(|clip| span.recording_clip == clip) - }); - let cut_cursor_events = if has_cut && !spans.is_empty() { - recording_start = f64::INFINITY; - recording_end = f64::NEG_INFINITY; - for span in &spans { - let start = segment.start.max(span.timeline_start); - let end = segment.end.min(span.timeline_end); - recording_start = recording_start.min( - span.recording_start - + (start - span.timeline_start) * span.timescale, - ); - recording_end = recording_end.max( - span.recording_start + (end - span.timeline_start) * span.timescale, - ); - } - Some(CursorEvents { - moves: cursor_events - .moves - .iter() - .filter(|event| { - spans.iter().any(|span| { - event.time_ms >= span.recording_start * 1000.0 - && event.time_ms < span.recording_end * 1000.0 - }) - }) - .cloned() - .collect(), - clicks: Vec::new(), - }) - } else { - None + let cut = + cursor_events_across_cut(&time_map, segment, recording_clip, cursor_events); + let (events, recording_start, recording_end) = match &cut { + Some((events, start, end)) => (events, *start, *end), + None => (cursor_events, recording_start, recording_end), }; Some(ZoomClusters::new( - cut_cursor_events.as_ref().unwrap_or(cursor_events), - moves_time_ordered, + events, + moves_time_ordered || cut.is_some(), recording_start, recording_end, segment, @@ -2815,6 +2863,64 @@ mod tests { } } + fn cut_zoom_timeline( + zoom: ZoomSegment, + kept: [f64; 2], + moves: Vec, + ) -> ZoomTransformTimeline { + let mut project = project_with_crop_styles(vec![zoom], Vec::new()); + project.timeline.as_mut().unwrap().segments = kept + .into_iter() + .map(|start| TimelineSegment { + start, + end: start + 4.0, + timescale: 1.0, + ..Default::default() + }) + .collect(); + let mut timeline = ZoomTransformTimeline::from_project_for_clip( + &project, + &CursorEvents { + moves, + clicks: Vec::new(), + }, + 8.0, + XY::new(1920, 1080), + 0, + ); + timeline.precompute(); + timeline + } + + #[test] + fn auto_zoom_across_cut_keeps_cursor_carried_into_each_range() { + let timeline = cut_zoom_timeline( + auto_segment(0.5, 7.9, 2.0), + [10.0, 20.0], + vec![move_event(9000.0, 0.2, 0.2), move_event(19000.0, 0.8, 0.8)], + ); + for (time, focus) in [(2.0, 0.2), (6.0, 0.8)] { + assert_eq!( + timeline.targets_at(time, XY::new(0.5, 0.5)).center, + follow_center((focus, focus)) + ); + } + } + + #[test] + fn auto_zoom_ending_at_cut_ignores_cut_cursor_moves() { + let build = |moves| cut_zoom_timeline(auto_segment(0.5, 4.0, 2.0), [0.0, 10.0], moves); + let kept = vec![move_event(1000.0, 0.2, 0.2), move_event(11000.0, 0.8, 0.8)]; + let mut with_cut_moves = kept.clone(); + with_cut_moves.push(move_event(7000.0, 0.9, 0.9)); + let expected = build(kept); + assert_samples_identical(&expected, &build(with_cut_moves)); + assert_eq!( + expected.targets_at(2.0, XY::new(0.5, 0.5)).center, + follow_center((0.2, 0.2)) + ); + } + #[test] fn auto_zoom_without_internal_cuts_matches_original_clusters() { let cursor = CursorEvents { From 288cbdf8bfd5731c0d642dd6451c77d38eeb2358 Mon Sep 17 00:00:00 2001 From: Aditya Garud <153842990+yashranaway@users.noreply.github.com> Date: Mon, 26 Oct 2026 06:10:05 +0000 Subject: [PATCH 3/5] fix(rendering): drop auto zoom moves at the cut instant --- crates/rendering/src/zoom_spring.rs | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/crates/rendering/src/zoom_spring.rs b/crates/rendering/src/zoom_spring.rs index 7c78cb5dd64..a990ca71883 100644 --- a/crates/rendering/src/zoom_spring.rs +++ b/crates/rendering/src/zoom_spring.rs @@ -657,6 +657,9 @@ fn cursor_events_across_cut( ( to_recording_ms(segment.start.max(span.timeline_start)), to_recording_ms(segment.end.min(span.timeline_end)), + // A span's own end is the cut-out instant; only a zoom ending + // inside the span keeps its end inclusive. + segment.end < span.timeline_end, ) }) .collect(); @@ -674,14 +677,13 @@ fn cursor_events_across_cut( .moves .iter() .filter(|event| event.x.is_finite() && event.y.is_finite() && event.time_ms.is_finite()); - // The cursor shown when a kept range starts is the last position before - // it, even if that move was cut out. - let carried = ranges.iter().filter_map(|&(start_ms, _)| { + // The cursor shown when a kept range starts is the last position at or + // before it, even if that move was cut out. + let carried = ranges.iter().filter_map(|&(start_ms, ..)| { finite_moves .clone() .filter(|event| event.time_ms <= start_ms) .max_by(|a, b| a.time_ms.total_cmp(&b.time_ms)) - .filter(|event| event.time_ms < start_ms) .map(|event| cap_project::CursorMoveEvent { time_ms: start_ms, ..event.clone() @@ -690,9 +692,10 @@ fn cursor_events_across_cut( let mut moves: Vec<_> = finite_moves .clone() .filter(|event| { - ranges - .iter() - .any(|&(start_ms, end_ms)| event.time_ms >= start_ms && event.time_ms <= end_ms) + ranges.iter().any(|&(start_ms, end_ms, closed)| { + event.time_ms > start_ms + && (event.time_ms < end_ms || closed && event.time_ms == end_ms) + }) }) .cloned() .chain(carried) @@ -2921,6 +2924,15 @@ mod tests { ); } + #[test] + fn auto_zoom_across_cut_ignores_moves_at_the_cut_instant() { + let build = |moves| cut_zoom_timeline(auto_segment(0.5, 7.9, 2.0), [0.0, 10.0], moves); + let kept = vec![move_event(1000.0, 0.4, 0.4), move_event(10000.0, 0.8, 0.8)]; + let mut with_cut_move = kept.clone(); + with_cut_move.push(move_event(4000.0, 0.6, 0.4)); + assert_samples_identical(&build(kept), &build(with_cut_move)); + } + #[test] fn auto_zoom_without_internal_cuts_matches_original_clusters() { let cursor = CursorEvents { From f634205942c8f5dc79a63279bd5c2afb3a294ad3 Mon Sep 17 00:00:00 2001 From: Aditya Garud <153842990+yashranaway@users.noreply.github.com> Date: Mon, 26 Oct 2026 06:17:44 +0000 Subject: [PATCH 4/5] fix(rendering): keep auto zoom end inclusive at continuous splits --- crates/rendering/src/zoom_spring.rs | 75 +++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 21 deletions(-) diff --git a/crates/rendering/src/zoom_spring.rs b/crates/rendering/src/zoom_spring.rs index a990ca71883..2a36c491d9e 100644 --- a/crates/rendering/src/zoom_spring.rs +++ b/crates/rendering/src/zoom_spring.rs @@ -632,24 +632,24 @@ fn cursor_events_across_cut( ) -> Option<(CursorEvents, f64, f64)> { // A zoom ending exactly at a cut maps its end into the incoming range, so // the span starting there still counts as a cut even though it adds no time. - let mut spans: Vec<_> = time_map + let spans: Vec<_> = time_map .iter() .filter(|span| span.timeline_start <= segment.end && span.timeline_end > segment.start) .collect(); - let has_cut = spans.windows(2).any(|pair| { - pair[0].recording_clip != pair[1].recording_clip - || pair[0].recording_end != pair[1].recording_start - }); - spans.retain(|span| { - span.timeline_start < segment.end - && recording_clip.is_none_or(|clip| span.recording_clip == clip) - }); - if !has_cut || spans.is_empty() { + let continues = |span: &TimeMapSegment, next: &TimeMapSegment| { + span.recording_clip == next.recording_clip && span.recording_end == next.recording_start + }; + if spans.windows(2).all(|pair| continues(pair[0], pair[1])) { return None; } let ranges: Vec<_> = spans .iter() - .map(|span| { + .enumerate() + .filter(|(_, span)| { + span.timeline_start < segment.end + && recording_clip.is_none_or(|clip| span.recording_clip == clip) + }) + .map(|(index, span)| { let to_recording_ms = |timeline_secs: f64| { (span.recording_start + (timeline_secs - span.timeline_start) * span.timescale) * 1000.0 @@ -657,12 +657,17 @@ fn cursor_events_across_cut( ( to_recording_ms(segment.start.max(span.timeline_start)), to_recording_ms(segment.end.min(span.timeline_end)), - // A span's own end is the cut-out instant; only a zoom ending - // inside the span keeps its end inclusive. - segment.end < span.timeline_end, + // The end is inclusive unless it is the cut-out instant. + segment.end < span.timeline_end + || spans + .get(index + 1) + .is_some_and(|next| continues(span, next)), ) }) .collect(); + if ranges.is_empty() { + return None; + } let recording_start = ranges .iter() .map(|range| range.0) @@ -2868,15 +2873,15 @@ mod tests { fn cut_zoom_timeline( zoom: ZoomSegment, - kept: [f64; 2], + kept: &[(f64, f64)], moves: Vec, ) -> ZoomTransformTimeline { let mut project = project_with_crop_styles(vec![zoom], Vec::new()); project.timeline.as_mut().unwrap().segments = kept - .into_iter() - .map(|start| TimelineSegment { + .iter() + .map(|&(start, end)| TimelineSegment { start, - end: start + 4.0, + end, timescale: 1.0, ..Default::default() }) @@ -2899,7 +2904,7 @@ mod tests { fn auto_zoom_across_cut_keeps_cursor_carried_into_each_range() { let timeline = cut_zoom_timeline( auto_segment(0.5, 7.9, 2.0), - [10.0, 20.0], + &[(10.0, 14.0), (20.0, 24.0)], vec![move_event(9000.0, 0.2, 0.2), move_event(19000.0, 0.8, 0.8)], ); for (time, focus) in [(2.0, 0.2), (6.0, 0.8)] { @@ -2912,7 +2917,13 @@ mod tests { #[test] fn auto_zoom_ending_at_cut_ignores_cut_cursor_moves() { - let build = |moves| cut_zoom_timeline(auto_segment(0.5, 4.0, 2.0), [0.0, 10.0], moves); + let build = |moves| { + cut_zoom_timeline( + auto_segment(0.5, 4.0, 2.0), + &[(0.0, 4.0), (10.0, 14.0)], + moves, + ) + }; let kept = vec![move_event(1000.0, 0.2, 0.2), move_event(11000.0, 0.8, 0.8)]; let mut with_cut_moves = kept.clone(); with_cut_moves.push(move_event(7000.0, 0.9, 0.9)); @@ -2926,13 +2937,35 @@ mod tests { #[test] fn auto_zoom_across_cut_ignores_moves_at_the_cut_instant() { - let build = |moves| cut_zoom_timeline(auto_segment(0.5, 7.9, 2.0), [0.0, 10.0], moves); + let build = |moves| { + cut_zoom_timeline( + auto_segment(0.5, 7.9, 2.0), + &[(0.0, 4.0), (10.0, 14.0)], + moves, + ) + }; let kept = vec![move_event(1000.0, 0.4, 0.4), move_event(10000.0, 0.8, 0.8)]; let mut with_cut_move = kept.clone(); with_cut_move.push(move_event(4000.0, 0.6, 0.4)); assert_samples_identical(&build(kept), &build(with_cut_move)); } + #[test] + fn auto_zoom_ending_at_a_split_keeps_its_end_move() { + let moves = vec![ + move_event(1000.0, 0.4, 0.4), + move_event(11000.0, 0.4, 0.4), + move_event(14000.0, 0.6, 0.4), + ]; + let build = |kept: &[(f64, f64)]| { + cut_zoom_timeline(auto_segment(0.5, 8.0, 2.0), kept, moves.clone()) + }; + assert_samples_identical( + &build(&[(0.0, 4.0), (10.0, 18.0)]), + &build(&[(0.0, 4.0), (10.0, 14.0), (14.0, 18.0)]), + ); + } + #[test] fn auto_zoom_without_internal_cuts_matches_original_clusters() { let cursor = CursorEvents { From f883592cd70290f5050ad4273eab767d676fb021 Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Mon, 26 Oct 2026 19:13:27 +0000 Subject: [PATCH 5/5] chore(rendering): drop narrating doc comment on cursor_events_across_cut --- crates/rendering/src/zoom_spring.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/rendering/src/zoom_spring.rs b/crates/rendering/src/zoom_spring.rs index 47c9aef389c..f8df92f8ac5 100644 --- a/crates/rendering/src/zoom_spring.rs +++ b/crates/rendering/src/zoom_spring.rs @@ -622,8 +622,6 @@ fn map_timeline_to_recording_secs( last.recording_start + (last.timeline_end - last.timeline_start) * last.timescale } -/// When an auto zoom spans a timeline cut, returns the cursor moves the viewer -/// actually sees during it and the recording range they cover. fn cursor_events_across_cut( time_map: &[TimeMapSegment], segment: &ZoomSegment,