diff --git a/crates/ui-egui/src/tools/prepare.rs b/crates/ui-egui/src/tools/prepare.rs index c5fa2b684e..e54bdd0468 100644 --- a/crates/ui-egui/src/tools/prepare.rs +++ b/crates/ui-egui/src/tools/prepare.rs @@ -618,30 +618,34 @@ pub(crate) fn delete_selected(view: &mut DocView) -> Option { /// as the object nudge does (`object_ui::nudge_keys`) and as dragging it does (#2608). The keys /// are consumed before the view turns the page with them; one press is one undo step. pub(crate) fn nudge_keys(ctx: &egui::Context, view: &mut DocView, doc: &Document) { - // `consume_key` lets an extra Alt through, and Alt+↑/↓ belongs to Calculation order's move. - if ctx.egui_wants_keyboard_input() || view.prepare.grab.is_some() || ctx.input(|i| i.modifiers.alt) { + if ctx.egui_wants_keyboard_input() || view.prepare.grab.is_some() { return; } let Some((name, wi)) = view.prepare.selected.clone() else { return }; let Some(w) = doc.form.iter().find(|f| f.name == name).and_then(|f| f.widgets.get(wi)) else { return }; let Some(page) = w.page else { return }; let Some(xf) = view.page_xform(page) else { return }; - use egui::{Key, Modifiers}; + use egui::Key; let mut step = egui::Vec2::ZERO; ctx.input_mut(|i| { - for (key, direction) in [ - (Key::ArrowLeft, egui::vec2(-1.0, 0.0)), - (Key::ArrowRight, egui::vec2(1.0, 0.0)), - (Key::ArrowUp, egui::vec2(0.0, -1.0)), - (Key::ArrowDown, egui::vec2(0.0, 1.0)), - ] { - // Shift first: a plain key pattern also matches a Shift press. - if i.consume_key(Modifiers::SHIFT, key) { - step += direction * 10.0; - } else if i.consume_key(Modifiers::NONE, key) { - step += direction; + // Only an arrow pressed alone or with Shift, judged by each press's own modifiers (the + // frame's can differ when a key is let go in the same frame). Alt+← / Alt+→ go back and + // forward through the view history (#3632) and Alt+↑ / ↓ is Calculation order's move. + i.events.retain(|e| { + let egui::Event::Key { key, pressed: true, modifiers, .. } = e else { return true }; + if modifiers.alt || modifiers.ctrl || modifiers.command || modifiers.mac_cmd { + return true; } - } + let direction = match key { + Key::ArrowLeft => egui::vec2(-1.0, 0.0), + Key::ArrowRight => egui::vec2(1.0, 0.0), + Key::ArrowUp => egui::vec2(0.0, -1.0), + Key::ArrowDown => egui::vec2(0.0, 1.0), + _ => return true, + }; + step += direction * if modifiers.shift { 10.0 } else { 1.0 }; + false + }); }); if step == egui::Vec2::ZERO { return; diff --git a/crates/ui-egui/tests/it/prepare.rs b/crates/ui-egui/tests/it/prepare.rs index 5c7850c2c0..838e04f85f 100644 --- a/crates/ui-egui/tests/it/prepare.rs +++ b/crates/ui-egui/tests/it/prepare.rs @@ -156,6 +156,40 @@ fn arrow_keys_nudge_the_selected_field_instead_of_turning_the_page() { assert_eq!(h.state().views[0].current, 0, "the arrows moved the field, not the page"); } +/// #3632: Alt+← / Alt+→ go back and forward through the view history with a field selected, and +/// leave the field where it is, even when Alt is let go in the same frame as the arrow, so the +/// frame has no modifiers and only the press carries Alt. +#[test] +fn alt_arrows_navigate_instead_of_nudging_the_selected_field() { + let mut h = harness(); + assert!(h.state_mut().apply_edit(pdfcraft_engine::Edit::InsertBlankPage { at: 1, width: 612.0, height: 792.0 })); + h.run_steps(2); + h.state_mut().execute("form.add.text"); + h.run_steps(1); + let p = at(&h, 40.0, 250.0); + h.hover_at(p); + h.run_steps(1); + h.drag_at(p); + h.run_steps(1); + h.drop_at(p); + h.run_steps(4); + let name = h.state().views[0].prepare.selected.clone().expect("the placed field is selected").0; + let before = rect_of(&h, &name); + h.state_mut().views[0].go_to_page(1); + h.run_steps(2); + h.state_mut().views[0].go_to_page(0); + h.run_steps(2); + assert!(h.state().views[0].prepare.selected.is_some(), "still selected, so a nudge could take the keys"); + let arrow = |key, modifiers| egui::Event::Key { key, physical_key: None, pressed: true, repeat: false, modifiers }; + h.event(arrow(egui::Key::ArrowLeft, egui::Modifiers::ALT)); + h.run_steps(4); + assert_eq!(h.state().views[0].current, 1, "an Alt+← press went back"); + h.event(arrow(egui::Key::ArrowRight, egui::Modifiers::ALT)); + h.run_steps(4); + assert_eq!(h.state().views[0].current, 0, "an Alt+→ press went forward"); + assert_eq!(rect_of(&h, &name), before, "the field didn't move"); +} + #[test] fn format_validate_and_calculate_tabs() { use pdfcraft_engine::form_scripts::{Calculate, Format, Validate};