collective

Browse / github.com/storytold/vectorcraft / patch

Fix #4283: a wheel notch zooms at once without Animated Zoom

Zooming with mouse wheel is always animated regardless of settings Zooming with mouse wheel disregards "Animate Zoom" setting under `Preferences > Performance` and is always animated. That setting should be respected even when zooming is done with mouse wheel. It now only affects zooming with keyboard & Zoom tool. https://github.com/user-attachments/assets/87da597f-cae0-4316-bc97-a8a37c23f14c Possibly linked to #888 which fixed the opposite behavior (mouse wheel zoom NOT animating)

Status
open
Score
0.00
Adopters
0 other operators
Author
Hassan1305 (imported from pull request #4313, open)
Base
eb0e99a5531cd6eca6222d149442c2f5ac0b3977
Size
+71 −11 in 3 files
Review
pending
Shared
2026-10-28
Id
5ce02190f9f09ce148222865

How it went

No other operator has reported using it yet.

Diff

raw · crates/ui-egui/src/canvas/input.rs crates/ui-egui/src/canvas/tests.rs crates/ui-egui/src/canvas/view.rs

diff --git a/crates/ui-egui/src/canvas/input.rs b/crates/ui-egui/src/canvas/input.rs--- a/crates/ui-egui/src/canvas/input.rs+++ b/crates/ui-egui/src/canvas/input.rs@@ -13,11 +13,12 @@ use super::*; pub(super) fn handle_input(app: &mut VectorcraftApp, ui: &Ui, resp: &egui::Response, rect: egui::Rect) {     let line = ui.ctx().options(|o| o.input_options.line_scroll_speed);     let wheel_zooms = app.session.prefs.zoom_with_mouse_wheel;+    let animate = animated_zoom(&app.session.prefs);     let turn_id = egui::Id::unique("canvas-wheel-turn");     let turn_before: WheelTurn = ui.data(|d| d.get_temp(turn_id)).unwrap_or_default();     let mut turn = turn_before;     let (pointer, m, space, (factor, scroll)) =-        ui.input(|i| (i.pointer.clone(), i.modifiers, i.key_down(egui::Key::Space), wheel(i, wheel_zooms, line, rect.height(), &mut turn)));+        ui.input(|i| (i.pointer.clone(), i.modifiers, i.key_down(egui::Key::Space), wheel(i, wheel_zooms, animate, line, rect.height(), &mut turn)));     if turn != turn_before {         ui.data_mut(|d| d.insert_temp(turn_id, turn));     }diff --git a/crates/ui-egui/src/canvas/tests.rs b/crates/ui-egui/src/canvas/tests.rs--- a/crates/ui-egui/src/canvas/tests.rs+++ b/crates/ui-egui/src/canvas/tests.rs@@ -3765,6 +3765,49 @@ fn wheel_notches_zoom_in_a_glide() {     assert_eq!(app.view().unwrap().zoom / before, done, "and it stops"); } +/// #4283: with Performance › Animated Zoom off, a wheel notch zooms the whole step on the frame+/// it arrives, both the plain wheel with Zoom with Mouse Wheel and Alt-wheel without it.+#[test]+fn wheel_notches_zoom_at_once_without_animated_zoom() {+    let mut app = VectorcraftApp::new(Session::new(), Default::default());+    app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap();+    app.session.execute("prefs.set", &json!({"key": "animatedZoom", "value": false})).unwrap();+    let ctx = egui::Context::default();+    let run = |app: &mut VectorcraftApp| {+        let mut raw = egui::RawInput { screen_rect: Some(egui::Rect::from_min_size(Pos2::ZERO, vec2(800.0, 600.0))), ..Default::default() };+        app.raw_input_hook(&ctx, &mut raw);+        let mut out = ctx.run_ui(raw, |ui| show(app, ui));+        out.textures_delta.clear();+    };+    run(&mut app);+    let at = app.canvas_rect.unwrap().center();+    for (wheel_zooms, alt) in [(true, false), (false, true)] {+        app.session.execute("prefs.set", &json!({"key": "zoomWithMouseWheel", "value": wheel_zooms})).unwrap();+        let before = app.view().unwrap().zoom;+        crate::tests_synthetic::control(&mut app, &ctx, "ui.wheel", json!({"x": at.x, "y": at.y, "dy": 1, "alt": alt}));+        // One line notch's whole zoom.+        let expected = ctx.options(|o| f64::from(o.input_options.line_scroll_speed) * 0.01).exp();+        // The first frame the zoom moves (the event may land a frame later).+        let mut first = 1.0;+        for _ in 0..3 {+            run(&mut app);+            first = app.view().unwrap().zoom / before;+            if first != 1.0 {+                break;+            }+        }+        assert!(+            (first - expected).abs() < 1e-9,+            "the whole notch arrives at once (wheel zooms {wheel_zooms}, alt {alt}: expected {expected}, got {first})"+        );+        for _ in 0..30 {+            run(&mut app);+        }+        let done = app.view().unwrap().zoom / before;+        assert!((done - first).abs() < 1e-9, "all of it on the first frame (wheel zooms {wheel_zooms}, alt {alt}: {first} then {done})");+    }+}+ /// In each frame where a wheel scroll moves the view, the hover point (read by the rulers' /// pointer marks, the Info panel, the hover highlight and the tool cursor) is the document point /// under the pointer in the view the canvas draws in that frame.diff --git a/crates/ui-egui/src/canvas/view.rs b/crates/ui-egui/src/canvas/view.rs--- a/crates/ui-egui/src/canvas/view.rs+++ b/crates/ui-egui/src/canvas/view.rs@@ -64,7 +64,8 @@ pub(super) const HOLD_DELAY: f32 = 0.3; pub(super) const HOLD_ZOOM: f64 = std::f64::consts::LN_2;  /// Performance › Animated Zoom, which needs GPU Performance as in Illustrator: the Zoom tool zooms-/// as it is dragged sideways or held, instead of zooming to the area dragged across.+/// as it is dragged sideways or held, instead of zooming to the area dragged across, and a wheel+/// notch's zoom glides in ([`wheel`]). pub(crate) fn animated_zoom(p: &vectorcraft_engine::Prefs) -> bool {     p.animated_zoom && p.gpu_performance }@@ -336,11 +337,19 @@ struct ThumbDrag { /// a zoom factor (about the pointer) and a scroll (screen points the content moves). The wheel scrolls, Cmd- and Alt-wheel (Option /// on the Mac) zoom. With General › Zoom with Mouse Wheel (`wheel_zooms`) the wheel and Alt-wheel /// zoom, Shift-wheel scrolls up and down and Cmd/Ctrl-wheel sideways. `line` and `page`: points-/// per wheel line and page. `turn`: kept by the caller across frames. A mouse wheel's notches zoom-/// in a glide over a few frames, as scrolling does (#888); a trackpad's fine steps at once.-pub(super) fn wheel(i: &egui::InputState, wheel_zooms: bool, line: f32, page: f32, turn: &mut WheelTurn) -> (f64, egui::Vec2) {+/// per wheel line and page. `turn`: kept by the caller across frames. With Performance › Animated+/// Zoom (`animate`) a mouse wheel's notches zoom in a glide over a few frames, as scrolling does+/// (#888); off, each notch zooms on the frame it arrives (#4283). A trackpad's fine steps zoom at+/// once either way.+pub(super) fn wheel(i: &egui::InputState, wheel_zooms: bool, animate: bool, line: f32, page: f32, turn: &mut WheelTurn) -> (f64, egui::Vec2) {     // Fingers on a touch screen pan the content with them (a trackpad sends scrolls instead).     let pan = i.multi_touch().map_or(egui::Vec2::ZERO, |t| t.translation_delta);+    // A wheel event's motion in points.+    let points = |unit: &egui::MouseWheelUnit, delta: egui::Vec2| match unit {+        egui::MouseWheelUnit::Point => delta,+        egui::MouseWheelUnit::Line => delta * line,+        egui::MouseWheelUnit::Page => delta * page,+    };     if !wheel_zooms {         // egui makes Cmd-wheel (and a pinch) its zoom and the rest a scroll it spreads over a few         // frames: the rest of an Alt-wheel turn zooms too, however soon Alt is let go.@@ -348,6 +357,17 @@ pub(super) fn wheel(i: &egui::InputState, wheel_zooms: bool, line: f32, page: f3             turn.alt = m.alt && !m.command;         }         let (zoom, scroll) = (f64::from(i.zoom_delta()), i.smooth_scroll_delta);+        if turn.alt && !animate {+            // Without Animated Zoom, Alt-wheel zooms by the wheel events themselves, on the frame+            // they arrive; the rest of egui's spread scroll is dropped.+            let d: f32 = i+                .events+                .iter()+                .filter_map(|e| if let egui::Event::MouseWheel { unit, delta, .. } = e { Some(points(unit, *delta)) } else { None })+                .map(|d| d.x + d.y)+                .sum();+            return (zoom * (f64::from(d) * WHEEL_ZOOM).exp(), pan);+        }         return if turn.alt && scroll != egui::Vec2::ZERO {             (zoom * (f64::from(scroll.x + scroll.y) * WHEEL_ZOOM).exp(), pan)         } else {@@ -360,16 +380,12 @@ pub(super) fn wheel(i: &egui::InputState, wheel_zooms: bool, line: f32, page: f3     for e in &i.events {         match e {             egui::Event::MouseWheel { unit, delta, modifiers, .. } => {-                let d = match unit {-                    egui::MouseWheelUnit::Point => *delta,-                    egui::MouseWheelUnit::Line => *delta * line,-                    egui::MouseWheelUnit::Page => *delta * page,-                };+                let d = points(unit, *delta);                 if modifiers.command {                     scroll.x += d.x + d.y;                 } else if modifiers.shift {                     scroll.y += d.x + d.y;-                } else if *unit == egui::MouseWheelUnit::Point {+                } else if *unit == egui::MouseWheelUnit::Point || !animate {                     zoom *= (f64::from(d.y) * WHEEL_ZOOM).exp();                     scroll.x += d.x;                 } else {