From 0b253d72c1fad6fde254deedbe97efe42054b83b Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Wed, 21 Oct 2026 10:15:35 +0200 Subject: [PATCH 1/4] Allow Ctrl+wheel zoom in an unfocused window (#3736) --- crates/ui-egui/src/canvas.rs | 39 ++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index eb9244a284..ee40339df3 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -2468,8 +2468,10 @@ 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); - if zoom_delta.is_finite() && zoom_delta > 0.0 && zoom_delta != 1.0 && navigation_enabled && ui.rect_contains_pointer(navigation_rect) { + // Wheel zoom works in a visible window without focus, as wheel scrolling does; the other gestures need focus. + let zoom_enabled = app.dialog.is_none() && app.close_request.is_none() && !app.palette_open && ui.is_enabled(); + let navigation_enabled = zoom_enabled && ui.input(|i| i.focused); + if zoom_delta.is_finite() && zoom_delta > 0.0 && zoom_delta != 1.0 && zoom_enabled && ui.rect_contains_pointer(navigation_rect) { let z = view.zoom * zoom_delta; match ui.input(|i| i.pointer.hover_pos()) { Some(p) => view.zoom_at(z, p), @@ -6204,6 +6206,39 @@ trailer << /Root 1 0 R >> assert_eq!(app.views[0].edit_lines.get(&0).map(|(g, _)| *g), Some(generation), "the boxes follow the edit while input is blocked"); } + /// 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, + }; + 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; From d6650acab10f74fce530fdb1b2576e822cdc0968 Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Wed, 4 Nov 2026 08:48:52 +0100 Subject: [PATCH 2/4] Block unfocused Ctrl+wheel zoom behind every modal prompt (#3736) --- crates/ui-egui/src/canvas.rs | 6 ++++-- crates/ui-egui/src/canvas/tests.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 49d2c44b03..a6f3d55bc8 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -3583,9 +3583,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 461ae954c2..5c8b57e4ae 100644 --- a/crates/ui-egui/src/canvas/tests.rs +++ b/crates/ui-egui/src/canvas/tests.rs @@ -1619,6 +1619,34 @@ fn edit_text_boxes_stay_drawn_while_page_input_is_blocked() { assert_eq!(app.views[0].edit_lines.get(&0).map(|(g, _)| *g), Some(generation), "the boxes follow the edit while input is blocked"); } +/// 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 }; + 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; From 8314fa12a162751dd339909863cd614718341551 Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Wed, 4 Nov 2026 08:49:03 +0100 Subject: [PATCH 3/4] Wrap the Ctrl+wheel test event (#3736) --- crates/ui-egui/src/canvas/tests.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/ui-egui/src/canvas/tests.rs b/crates/ui-egui/src/canvas/tests.rs index 5c8b57e4ae..7c7b470353 100644 --- a/crates/ui-egui/src/canvas/tests.rs +++ b/crates/ui-egui/src/canvas/tests.rs @@ -1634,7 +1634,12 @@ fn ctrl_wheel_zooms_an_unfocused_window() { 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 }; + let ctrl_wheel = egui::Event::MouseWheel { + unit: egui::MouseWheelUnit::Line, + delta: vec2(0.0, 1.0), + phase: egui::TouchPhase::Move, + modifiers: egui::Modifiers::COMMAND, + }; h.state_mut().palette_open = true; let before = h.state().views[0].zoom; h.event(ctrl_wheel.clone()); From 41d819e2d3bc59ee9fddaa3789f371724772dda3 Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Wed, 4 Nov 2026 08:50:45 +0100 Subject: [PATCH 4/4] Give the Ctrl+wheel test event its wheel source (#3736) --- crates/ui-egui/src/canvas/tests.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/ui-egui/src/canvas/tests.rs b/crates/ui-egui/src/canvas/tests.rs index 7c7b470353..f11ebd125e 100644 --- a/crates/ui-egui/src/canvas/tests.rs +++ b/crates/ui-egui/src/canvas/tests.rs @@ -1639,6 +1639,7 @@ fn ctrl_wheel_zooms_an_unfocused_window() { 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;