From 4b06407daeac87e2f9c69edd9e4f31a1e1dfafcf Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Mon, 2 Nov 2026 15:57:40 +0100 Subject: [PATCH] Keep the edit cursor as the zoom reference when it is off-screen Fixes #1260 Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Sonnet 5.5 --- crates/engine/src/cmd/view.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/cmd/view.rs b/crates/engine/src/cmd/view.rs index b214a81f..869bb7b6 100644 --- a/crates/engine/src/cmd/view.rs +++ b/crates/engine/src/cmd/view.rs @@ -96,14 +96,22 @@ pub fn specs() -> Vec { const MIN_SPP: f64 = 0.25; const MAX_SPP: f64 = 1_000_000.0; +/// Pixel column an off-screen edit cursor is brought to when zooming (the engine does not know the viewport width). +const OFFSCREEN_CURSOR_PX: f64 = 500.0; fn zoom(e: &mut Engine, f: f64, x: Option) -> Result { let insertion = e.session().edit.selection.start as f64; let z = e.zoom_mut(); let (old, scroll) = (z.samples_per_px, z.scroll as f64); let new = (old * f).clamp(MIN_SPP, MAX_SPP); - if let Some(x) = x.or_else(|| Some((insertion - scroll) / old).filter(|x| (0.0..4000.0).contains(x))) { + // Without a pointer, the edit cursor is the reference: held in place when visible, centred when off-screen. + let cursor_x = (insertion - scroll) / old; + if let Some(x) = x { z.scroll = soundcraft_time::to_samples(scroll + x * (old - new)).clamp(0, MAX_POSITION); + } else if (0.0..4000.0).contains(&cursor_x) { + z.scroll = soundcraft_time::to_samples(scroll + cursor_x * (old - new)).clamp(0, MAX_POSITION); + } else { + z.scroll = soundcraft_time::to_samples(insertion - OFFSCREEN_CURSOR_PX * new).clamp(0, MAX_POSITION); } z.samples_per_px = new; Ok(json!({"samples_per_px": new, "scroll": z.scroll})) @@ -186,6 +194,17 @@ mod tests { assert_eq!(view(&e), (100.0, 48_000)); } + #[test] + fn zoom_in_brings_an_offscreen_edit_cursor_into_view() { + let mut e = Engine::default(); + e.execute("view.zoom_set", &json!({"samples_per_px": 100.0})).unwrap(); + e.execute("edit.select", &json!({"start": 1_000_000, "end": 1_000_000})).unwrap(); + e.execute("view.zoom_in", &json!({})).unwrap(); + let (spp, scroll) = view(&e); + let x = (1_000_000.0 - scroll as f64) / spp; + assert!((0.0..2000.0).contains(&x), "cursor at {x}px"); + } + #[test] fn zoom_and_scroll_stay_in_range() { let mut e = Engine::default();