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 <[email protected]>
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
authored and GitHub committed 2026-10-08 10:20:04 -04:00
1 parent 9f2188bf7e
commit 879d5174c2
4 files changed
+84 -2

No files matched your search

+2
View File
@@ -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)")),
] {
+38
View File
@@ -330,6 +330,8 @@ pub struct PdfCraftApp {
pub left_open: bool,
pub right: Option<RightPanel>,
pub quick_tool: QuickTool,
/// The tool to go back to when Space, held for a temporary Hand, is released.
space_hand: Option<QuickTool>,
/// 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());
+42
View File
@@ -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<u8> {
let n = sizes.len();
+2 -2
View File
@@ -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"