Skip to content

Commit e26f954

Browse files
Merge pull request #2679 from yashranaway/fix/auto-zoom-idle-focus
fix(rendering): keep an idle cursor in view when auto zoom starts
2 parents 0c00839 + 324fddc commit e26f954

1 file changed

Lines changed: 77 additions & 13 deletions

File tree

‎crates/rendering/src/zoom_spring.rs‎

Lines changed: 77 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -378,18 +378,19 @@ pub(crate) fn build_clusters(
378378
.filter(|m| m.time_ms >= start_ms && m.time_ms <= end_ms)
379379
.collect();
380380

381+
let previous = moves_until_start
382+
.iter()
383+
.filter(finite)
384+
.rev()
385+
.find(|m| m.time_ms <= start_ms);
386+
381387
if events_in_range.is_empty() {
382-
let fallback = moves_until_start
383-
.iter()
384-
.filter(finite)
385-
.rev()
386-
.find(|m| m.time_ms <= start_ms)
387-
.or_else(|| {
388-
moves_from_start
389-
.iter()
390-
.filter(finite)
391-
.find(|m| m.time_ms >= start_ms)
392-
});
388+
let fallback = previous.or_else(|| {
389+
moves_from_start
390+
.iter()
391+
.filter(finite)
392+
.find(|m| m.time_ms >= start_ms)
393+
});
393394

394395
if let Some(evt) = fallback {
395396
let (x, y) = map_uv(evt.x, evt.y);
@@ -398,12 +399,16 @@ pub(crate) fn build_clusters(
398399
return vec![];
399400
}
400401

402+
let mut events = previous
403+
.filter(|_| events_in_range[0].time_ms > start_ms)
404+
.into_iter()
405+
.chain(events_in_range);
401406
let mut clusters = Vec::new();
402-
let first = events_in_range[0];
407+
let first = events.next().unwrap();
403408
let (first_x, first_y) = map_uv(first.x, first.y);
404409
let mut current = ClickCluster::new(first_x, first_y, first.time_ms);
405410

406-
for evt in &events_in_range[1..] {
411+
for evt in events {
407412
let (x, y) = map_uv(evt.x, evt.y);
408413
if current.can_add(x, y, cluster_w, cluster_h) {
409414
current.add(x, y, evt.time_ms);
@@ -1836,6 +1841,65 @@ mod tests {
18361841
)
18371842
}
18381843

1844+
#[test]
1845+
fn auto_zoom_uses_position_at_segment_start_over_earlier_movement() {
1846+
let cursor = CursorEvents {
1847+
moves: vec![
1848+
move_event(0.0, 0.9, 0.9),
1849+
move_event(2000.0, 0.1, 0.1),
1850+
move_event(6000.0, 0.9, 0.9),
1851+
],
1852+
clicks: Vec::new(),
1853+
};
1854+
for ordered in [false, true] {
1855+
let clusters = build_clusters(&cursor, ordered, 2.0, 10.0, 2.0, None);
1856+
assert_eq!(cluster_center_at_time(&clusters, 2000.0), Some((0.1, 0.1)));
1857+
}
1858+
}
1859+
1860+
#[test]
1861+
fn auto_zoom_keeps_idle_cursor_visible_until_movement_resumes() {
1862+
for (recording_start, timescale) in [(0.0, 1.0), (10.0, 2.0)] {
1863+
let segment = auto_segment(2.0, 10.0, 2.0);
1864+
let mut project = project_with_crop_styles(vec![segment], Vec::new());
1865+
project.timeline.as_mut().unwrap().segments = vec![TimelineSegment {
1866+
recording_clip: 0,
1867+
start: recording_start,
1868+
end: recording_start + 12.0 * timescale,
1869+
timescale,
1870+
name: None,
1871+
speed_audio_mode: None,
1872+
hide_cursor: None,
1873+
volume: None,
1874+
}];
1875+
let cursor = CursorEvents {
1876+
moves: vec![
1877+
move_event(recording_start * 1000.0, 0.1, 0.1),
1878+
move_event((recording_start + 6.0 * timescale) * 1000.0, 0.9, 0.9),
1879+
],
1880+
clicks: Vec::new(),
1881+
};
1882+
let mut timeline = ZoomTransformTimeline::from_project_for_clip(
1883+
&project,
1884+
&cursor,
1885+
12.0,
1886+
XY::new(1920, 1080),
1887+
0,
1888+
);
1889+
timeline.precompute();
1890+
1891+
for (time, position) in [(4.0, 0.1), (8.0, 0.9)] {
1892+
let zoom = timeline.sample(time);
1893+
let (left, top, size) = visible_viewport(&zoom);
1894+
assert!(
1895+
(left..=left + size).contains(&position)
1896+
&& (top..=top + size).contains(&position),
1897+
"cursor {position} outside viewport at {time}s: {zoom:?}, source start={recording_start}, speed={timescale}"
1898+
);
1899+
}
1900+
}
1901+
}
1902+
18391903
#[test]
18401904
fn hovering_into_a_corner_re_aims_without_a_click() {
18411905
// Regression for the real-recording report: clicks early near the

0 commit comments

Comments
 (0)