From 879d5174c2f8d7d962cae904d9352bc876c148e6 Mon Sep 17 00:00:00 2001 From: wstock Date: Thu, 8 Oct 2026 15:20:04 +0100 Subject: [PATCH] V, H and Space pick the Select and Hand tools (#281) The quick toolbar's tooltips read "Select (V)" and "Hand (H)", but nothing listened for either key. V now selects, H picks the Hand, and holding Space pans with the Hand until it is released, as in Acrobat. They are plain letters, so not while a text field, a form field, a dialog or the palette has the keyboard, and not with a modifier held. The shortcuts dialog lists them (reusing the translated tooltips) and the view.hand-tool parity entry cites the new test. Claude-Session: https://claude.ai/code/session_01NH3itd85jPuM6wNjgcGfEH Co-authored-by: wstock <1394687+wstock@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 (1M context) --- crates/ui-egui/src/dialogs.rs | 2 ++ crates/ui-egui/src/lib.rs | 38 ++++++++++++++++++++++++++++++ crates/ui-egui/tests/viewer.rs | 42 ++++++++++++++++++++++++++++++++++ parity/acrobat-features.toml | 4 ++-- 4 files changed, 84 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index abf11ea1..50aca72d 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -1031,6 +1031,8 @@ pub fn show(app: &mut PdfCraftApp, ctx: &egui::Context) { ("⇧⌘+ / ⇧⌘−", tl!("Rotate view")), ("Home / End", tl!("First / last page")), ("← / →, ⌘← / ⌘→", tl!("Previous / next page")), + ("V", tl!("Select (V)")), + ("H / Space (hold)", tl!("Hand (H)")), ("Delete", tl!("Delete selected pages (Organize)")), ("⌘A", tl!("Select all pages (Organize)")), ] { diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 147bebb5..606a9d5a 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -330,6 +330,8 @@ pub struct PdfCraftApp { pub left_open: bool, pub right: Option, pub quick_tool: QuickTool, + /// The tool to go back to when Space, held for a temporary Hand, is released. + space_hand: Option, /// Comment author, per-tool colours and widths, pin. pub comment_prefs: comments::CommentPrefs, /// Resolved colours, including the current OS theme when following the system. @@ -558,6 +560,7 @@ impl PdfCraftApp { left_open: true, right: None, quick_tool: QuickTool::Select, + space_hand: None, comment_prefs: Default::default(), theme: ThemeKind::Light, theme_preference: ThemePreference::Light, @@ -1366,11 +1369,46 @@ impl PdfCraftApp { { self.views[i].select_all(); } + self.tool_keys(i, ctx); canvas::shortcuts(&mut self.views[i], ctx); } } } +impl PdfCraftApp { + /// The quick tools' keys, as in Acrobat: V selects, H pans, and holding Space pans until it + /// is released. Plain letters, so not while a text field, a form field, a dialog or the + /// palette has the keyboard. + fn tool_keys(&mut self, i: usize, ctx: &egui::Context) { + use egui::Key; + let free = self.dialog.is_none() && !self.palette_open && !ctx.egui_wants_keyboard_input() && self.views[i].forms.focus.is_none(); + let (plain, space) = ctx.input(|input| (input.modifiers.is_none(), input.key_down(Key::Space))); + if let Some(previous) = self.space_hand + && !space + { + self.space_hand = None; + if self.quick_tool == QuickTool::Hand { + self.quick_tool = previous; + } + } + if !free || !plain { + return; + } + if ctx.input(|input| input.key_pressed(Key::V)) { + self.quick_tool = QuickTool::Select; + self.space_hand = None; + } + if ctx.input(|input| input.key_pressed(Key::H)) { + self.quick_tool = QuickTool::Hand; + self.space_hand = None; + } + if space && self.space_hand.is_none() && self.quick_tool != QuickTool::Hand { + self.space_hand = Some(self.quick_tool); + self.quick_tool = QuickTool::Hand; + } + } +} + impl eframe::App for PdfCraftApp { fn save(&mut self, storage: &mut dyn eframe::Storage) { storage.set_string("pdfcraft", self.persist()); diff --git a/crates/ui-egui/tests/viewer.rs b/crates/ui-egui/tests/viewer.rs index 6a143c23..39802d6f 100644 --- a/crates/ui-egui/tests/viewer.rs +++ b/crates/ui-egui/tests/viewer.rs @@ -472,6 +472,48 @@ fn arrow_and_page_keys_move_through_a_scrolling_document() { assert_eq!(press(&mut h, Key::PageDown, 1), 3); } +#[test] +fn v_h_and_space_pick_the_quick_tools() { + // The toolbar's tooltips promise "Select (V)" and "Hand (H)", but the keys did nothing. + use egui::{Key, Modifiers}; + use pdfcraft_ui_egui::QuickTool; + let mut h = harness(); + h.state_mut().active = Some(0); + h.run_steps(2); + let press = |h: &mut Harness<'static, PdfCraftApp>, key| { + h.key_press(key); + h.run_steps(2); + h.state().quick_tool + }; + assert_eq!(press(&mut h, Key::H), QuickTool::Hand); + assert_eq!(press(&mut h, Key::V), QuickTool::Select); + // V leaves any other tool too, as the toolbar button does. + h.state_mut().quick_tool = QuickTool::Crop; + assert_eq!(press(&mut h, Key::V), QuickTool::Select); + // Holding Space pans with the Hand for as long as it is held, then gives the tool back. + h.state_mut().quick_tool = QuickTool::Crop; + h.key_down(Key::Space); + h.run_steps(4); + assert_eq!(h.state().quick_tool, QuickTool::Hand, "Space held"); + h.key_up(Key::Space); + h.run_steps(2); + assert_eq!(h.state().quick_tool, QuickTool::Crop, "Space released"); + // With a modifier the letters are someone else's shortcut. + h.key_press_modifiers(Modifiers::SHIFT, Key::H); + h.run_steps(2); + assert_eq!(h.state().quick_tool, QuickTool::Crop); + // They are letters to a text field: typing in the find bar keeps the tool. + h.state_mut().quick_tool = QuickTool::Select; + h.state_mut().views[0].open_find(); + h.run_steps(4); + assert_eq!(press(&mut h, Key::H), QuickTool::Select, "typing H in the find bar"); + h.key_down(Key::Space); + h.run_steps(2); + h.key_up(Key::Space); + h.run_steps(2); + assert_eq!(h.state().quick_tool, QuickTool::Select, "typing a space in the find bar"); +} + /// Like [`fixture`], with each page's size given. fn sized_fixture(sizes: &[(u32, u32)]) -> Vec { let n = sizes.len(); diff --git a/parity/acrobat-features.toml b/parity/acrobat-features.toml index c1f228d4..2d478e79 100644 --- a/parity/acrobat-features.toml +++ b/parity/acrobat-features.toml @@ -1034,8 +1034,8 @@ milestone = "M3" status = "shipped" commands = [] tools = [] -evidence = ["crates/ui-egui/tests/view.rs::the_hand_tool_pans_by_dragging"] -notes = "drag to pan; Grab/Grabbing cursors" +evidence = ["crates/ui-egui/tests/view.rs::the_hand_tool_pans_by_dragging", "crates/ui-egui/tests/viewer.rs::v_h_and_space_pick_the_quick_tools"] +notes = "drag to pan; Grab/Grabbing cursors; H picks it, V goes back to Select, Space held pans temporarily" [[feature]] id = "view.rotate-view"