diff --git a/crates/ui-egui/src/panels.rs b/crates/ui-egui/src/panels.rs index 003f2b2e78..13881c5bdc 100644 --- a/crates/ui-egui/src/panels.rs +++ b/crates/ui-egui/src/panels.rs @@ -1627,6 +1627,20 @@ fn navigator(app: &mut PhotocraftApp, ui: &mut egui::Ui) { app.ui.views[idx].center = [d.x.clamp(0.0, size.width as f32), d.y.clamp(0.0, size.height as f32)]; app.ui.views[idx].fit_pending = false; } + // The Navigator has its own zoom target: an unmodified wheel over the preview + // should zoom even when the document canvas is configured to scroll on wheel. + // Use the canvas wheel classifier to honour one ×1.1 step per notch without + // applying egui's smoothed wheel tail a second time. + if resp.hovered() + && let Some(crate::wheel_nav::Wheel::Zoom(factor)) = crate::wheel_nav::read(&ctx, true) + { + let view = &mut app.ui.views[idx]; + let next = crate::zoom_levels::clamp(view.zoom * factor, view.doc_size); + if next != view.zoom { + view.zoom = next; + view.fit_pending = false; + } + } // Visible-area rectangle. let v = app.ui.views[idx].clone(); let canvas = app.last_canvas_rect; @@ -1660,6 +1674,43 @@ fn navigator(app: &mut PhotocraftApp, ui: &mut egui::Ui) { } } +#[cfg(test)] +mod navigator_wheel_tests { + use super::*; + use egui::{Event, Modifiers, MouseWheelUnit, RawInput, TouchPhase}; + + fn frame(app: &mut PhotocraftApp, ctx: &egui::Context, pointer: Pos2, wheel: f32) { + let mut events = vec![Event::PointerMoved(pointer)]; + if wheel != 0.0 { + events.push(Event::MouseWheel { unit: MouseWheelUnit::Line, delta: vec2(0.0, wheel), phase: TouchPhase::Move, modifiers: Modifiers::NONE }); + } + let mut out = ctx + .run_ui(RawInput { screen_rect: Some(Rect::from_min_size(Pos2::ZERO, vec2(430.0, 460.0))), events, ..Default::default() }, |ui| navigator(app, ui)); + out.textures_delta.clear(); + } + + #[test] + fn navigator_wheel_changes_zoom_only_when_over_preview() { + let mut app = PhotocraftApp::new(photocraft_engine::Session::new(), crate::Services::default()); + app.run("file.new", json!({"width": 200, "height": 100})).unwrap(); + app.sync_views(); + app.ui.views[0].zoom = 1.0; + app.ui.views[0].fit_pending = false; + let ctx = egui::Context::default(); + PhotocraftApp::setup_context(&ctx, crate::theme::ThemeKind::ALL[0]); + crate::wheel_nav::configure(&ctx); + frame(&mut app, &ctx, pos2(50.0, 55.0), 0.0); + let center = app.ui.views[0].center; + frame(&mut app, &ctx, pos2(50.0, 55.0), 1.0); + assert!((app.ui.views[0].zoom - 1.1).abs() < 1e-4, "one wheel notch zooms once"); + assert_eq!(app.ui.views[0].center, center, "Navigator wheel must not pan"); + frame(&mut app, &ctx, pos2(50.0, 55.0), -1.0); + assert!((app.ui.views[0].zoom - 1.0).abs() < 1e-4); + frame(&mut app, &ctx, pos2(50.0, 400.0), 1.0); + assert!((app.ui.views[0].zoom - 1.0).abs() < 1e-4, "wheel outside preview leaves zoom unchanged"); + } +} + fn empty(ui: &mut egui::Ui, s: &str) { let t = Tokens::get(ui.ctx()); ui.add_space(6.0);