From c4fdbd60649413694d41a60610d758cf5bacec6a Mon Sep 17 00:00:00 2001 From: Elias Date: Fri, 9 Oct 2026 22:17:27 +0100 Subject: [PATCH] ui: Graph Editor fit goes through the timeline zoom; AZERTY zoom keys in the graph Co-Authored-By: Claude (cherry picked from commit 6277a98f3ff6207bd215213af514c5f6fb481532) (cherry picked from commit 646f4055b180d98ccc6ac6898b64ba97f9e2ad7d) (cherry picked from commit b21932ed7058cec68da01d337ba46521501ebc49) (cherry picked from commit 0d6b93962e123b2e0179622f2abd472cfa9015ac) --- crates/ui-egui/src/panels/graph.rs | 9 ++++--- crates/ui-egui/src/panels/timeline.rs | 4 +-- crates/ui-egui/tests/ui_time_zoom_controls.rs | 25 ++++++++++++++----- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/crates/ui-egui/src/panels/graph.rs b/crates/ui-egui/src/panels/graph.rs index ea3e1431..734c642d 100644 --- a/crates/ui-egui/src/panels/graph.rs +++ b/crates/ui-egui/src/panels/graph.rs @@ -676,9 +676,12 @@ fn fit_selection(app: &mut EffectcraftApp, cs: &[Curve], tm: TMap, plot: Rect) { } } let span = (t1 - t0).max(0.2); - let pps = (plot.width() as f64 * 0.8 / span).clamp(5.0, 4000.0); - app.ui.timeline.pps = Some(pps); - app.ui.timeline.start = (t0 - span * 0.1).max(0.0); + let pps = plot.width() as f64 * 0.8 / span; + // Through the timeline's zoom, so the fit never zooms out past the whole comp. + if let Some(comp) = app.session.active_comp_arc() { + let fit = super::timeline::fit_pps(plot.width(), &comp); + super::timeline::set_zoom(&mut app.ui.timeline, &comp, pps, fit, (t0 - span * 0.1).max(0.0), 0.0); + } let pad = ((v1 - v0) * 0.15).max(1.0); app.ui.timeline.graph_auto_zoom = false; app.ui.timeline.graph_range = Some((v0 - pad, v1 + pad)); diff --git a/crates/ui-egui/src/panels/timeline.rs b/crates/ui-egui/src/panels/timeline.rs index b2b00b10..5e81bd68 100644 --- a/crates/ui-egui/src/panels/timeline.rs +++ b/crates/ui-egui/src/panels/timeline.rs @@ -517,14 +517,14 @@ const MAX_PPS: f64 = 4000.0; pub(crate) const ZOOM_STEP: f64 = 1.5; /// Pixels per second that fit `comp` in a time graph `width` points wide. -fn fit_pps(width: f32, comp: &Comp) -> f64 { +pub(crate) fn fit_pps(width: f32, comp: &Comp) -> f64 { (width - 10.0).max(1.0) as f64 / comp.duration.seconds().max(0.01) } /// Zoom the time ruler to `pps` pixels per second, keeping comp time `at_t` at `at_px` pixels /// from the ruler's start. Zooming out stops at `fit`, where the whole comp shows (and the /// ruler keeps fitting it). -fn set_zoom(tl: &mut crate::state::TimelineState, comp: &Comp, pps: f64, fit: f64, at_t: f64, at_px: f64) { +pub(crate) fn set_zoom(tl: &mut crate::state::TimelineState, comp: &Comp, pps: f64, fit: f64, at_t: f64, at_px: f64) { let npps = pps.min(MAX_PPS.max(fit)).max(fit); if npps <= fit { tl.pps = None; diff --git a/crates/ui-egui/tests/ui_time_zoom_controls.rs b/crates/ui-egui/tests/ui_time_zoom_controls.rs index 0bdeae1b..4dfc8fa6 100644 --- a/crates/ui-egui/tests/ui_time_zoom_controls.rs +++ b/crates/ui-egui/tests/ui_time_zoom_controls.rs @@ -142,17 +142,30 @@ fn graph_curves_follow_the_time_zoom_and_the_navigator() { } #[test] -fn switching_between_layer_bars_and_graph_keeps_the_time_zoom() { +fn leaving_the_graph_keeps_its_time_zoom() { let mut h = harness(false); - send(&mut h, "ui.key", json!({"key": "="})); - send(&mut h, "ui.key", json!({"key": "="})); - let before = view(&h); + // Opening the Graph Editor fits the graphs (ui_graph_fit); the zoom set there stays. send(&mut h, "ui.click", json!({"id": "timeline.switch.graph"})); assert!(h.state().ui.timeline.graph_editor); - assert_eq!(view(&h), before); - send(&mut h, "ui.key", json!({"key": "-"})); + send(&mut h, "ui.key", json!({"key": "="})); let graph = view(&h); send(&mut h, "ui.click", json!({"id": "timeline.switch.graph"})); assert!(!h.state().ui.timeline.graph_editor); assert_eq!(view(&h), graph); } + +#[test] +fn azerty_physical_zoom_keys_zoom_the_graph() { + let mut h = harness(true); + // The "Elias AE"-style bindings: AZERTY's - (US 6 position) zooms in, ) (US minus) out. + for (command, keys) in [("timeline.zoomIn", "Physical+6"), ("timeline.zoomOut", "Physical+Minus")] { + h.state_mut().session.execute("shortcuts.set", json!({"command": command, "keys": keys})).unwrap(); + } + let (_, span0) = view(&h); + send(&mut h, "ui.key", json!({"key": "Physical+6"})); + let (_, span1) = view(&h); + assert!(span1 < span0 * 0.8, "Physical+6 zooms the graph in: {span0} -> {span1}"); + send(&mut h, "ui.key", json!({"key": "Physical+Minus"})); + let (_, span2) = view(&h); + assert!(span2 > span1 * 1.2, "Physical+Minus zooms the graph out: {span1} -> {span2}"); +}