diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index f4ad716137..5568d07743 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -3130,9 +3130,11 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) { let zoom_delta = crate::motion::zoom_delta(ui.ctx()); // Exclude the previous frame's scrollbar tracks from gesture activation. let navigation_rect = if view.viewport_screen.is_positive() { avail.intersect(view.viewport_screen) } else { avail }; - let navigation_enabled = app.dialog.is_none() && app.close_request.is_none() && !app.palette_open && ui.is_enabled() && ui.input(|i| i.focused); + // Wheel zoom works in a visible window without focus, as wheel scrolling does; the other gestures need focus. + let zoom_enabled = unobstructed && ui.is_enabled(); + let navigation_enabled = zoom_enabled && ui.input(|i| i.focused); let present = presentation_click_state(ui, view, navigation_rect, navigation_enabled && app.quick_tool == QuickTool::Select); - pinch_zoom(ui, view, zoom_delta, navigation_rect, navigation_enabled); + pinch_zoom(ui, view, zoom_delta, navigation_rect, zoom_enabled); view.viewport_screen = avail; let (auto_delta, edge_delta) = auto_scroll_deltas(ui, view, avail, unobstructed, invert_middle); diff --git a/crates/ui-egui/src/canvas/tests.rs b/crates/ui-egui/src/canvas/tests.rs index c05a03bb7b..d6cb1f7f33 100644 --- a/crates/ui-egui/src/canvas/tests.rs +++ b/crates/ui-egui/src/canvas/tests.rs @@ -2134,6 +2134,40 @@ fn a_page_found_cut_after_a_dismissal_is_still_named() { h.get_by_label_contains("2 pages hold more content than PdfCraft can read, page 1 first"); } +/// Ctrl+wheel zooms a visible window that another application has focus over, as plain +/// wheel scrolling does; the palette still blocks it. +#[test] +fn ctrl_wheel_zooms_an_unfocused_window() { + let mut h = egui_kittest::Harness::builder().with_size(vec2(1400.0, 900.0)).build_eframe(|_cc| { + let mut app = PdfCraftApp::new(); + let bytes = app.session.create_blank(300.0, 400.0, 1).unwrap(); + app.open_bytes("blank.pdf", None, bytes.as_ref().clone()).unwrap(); + app + }); + h.run_steps(3); + let center = h.state().views[0].viewport_rect().center(); + h.hover_at(center); + h.input_mut().focused = false; + h.run_steps(2); + let ctrl_wheel = egui::Event::MouseWheel { + unit: egui::MouseWheelUnit::Line, + delta: vec2(0.0, 1.0), + phase: egui::TouchPhase::Move, + modifiers: egui::Modifiers::COMMAND, + source: egui::MouseWheelSource::Unknown, + }; + h.state_mut().palette_open = true; + let before = h.state().views[0].zoom; + h.event(ctrl_wheel.clone()); + h.run_steps(10); + assert_eq!(h.state().views[0].zoom, before, "the palette still blocks zoom"); + h.state_mut().palette_open = false; + h.run_steps(2); + h.event(ctrl_wheel); + h.run_steps(10); + assert!(h.state().views[0].zoom > before, "Ctrl+wheel zooms without window focus"); +} + #[test] fn virtualized_organizer_keeps_insert_gaps_at_document_positions() { use egui_kittest::kittest::Queryable;