From eab641ac8f27a964e32d6b5eb4c3707d9d87ee7d Mon Sep 17 00:00:00 2001 From: Pratham Bhanushali <134723366+Morpheus004@users.noreply.github.com> Date: Mon, 12 Oct 2026 18:08:47 +0530 Subject: [PATCH] feat(ui): add command palette arrow-key navigation (#1419) --- crates/ui-egui/src/palette.rs | 24 +++++++++++++++++++++--- crates/ui-egui/tests/commands.rs | 16 ++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/crates/ui-egui/src/palette.rs b/crates/ui-egui/src/palette.rs index cb99ca99..f4d0b617 100644 --- a/crates/ui-egui/src/palette.rs +++ b/crates/ui-egui/src/palette.rs @@ -109,6 +109,24 @@ pub fn show(app: &mut PdfCraftApp, ctx: &egui::Context) { } hits.sort_by_key(|(s, h)| (*s, !h.ready)); hits.truncate(12); + let (down, up) = ctx.input(|i| (i.key_pressed(egui::Key::ArrowDown), i.key_pressed(egui::Key::ArrowUp))); + let selected = ctx.data_mut(|data| { + let id = egui::Id::new("palette_selected"); + let query_id = egui::Id::new("palette_selected_query"); + let previous_query = data.get_temp::(query_id); + let selected = + if previous_query.as_deref() == Some(&q) { data.get_temp::(id).unwrap_or(0) } else { 0 }.min(hits.len().saturating_sub(1)); + data.insert_temp(query_id, q.clone()); + let selected = if down { + selected.saturating_add(1).min(hits.len().saturating_sub(1)) + } else if up { + selected.saturating_sub(1) + } else { + selected + }; + data.insert_temp(id, selected); + selected + }); let screen = ctx.content_rect(); @@ -145,17 +163,17 @@ pub fn show(app: &mut PdfCraftApp, ctx: &egui::Context) { // check while it is focused as well as when focus is lost. if (r.has_focus() || r.lost_focus()) && ui.input(|i| i.key_pressed(egui::Key::Enter)) - && let Some((_, h)) = hits.first() + && let Some((_, h)) = hits.get(selected) { chosen = Some((h.command, h.group)); } r.request_focus(); }); ui.separator(); - for (_, h) in &hits { + for (index, (_, h)) in hits.iter().enumerate() { let (rect, resp) = ui.allocate_exact_size(vec2(ui.available_width(), 36.0), Sense::click()); let press = theme::Press::track(ui, &resp); - press.wash(ui, rect, 6, false); + press.wash(ui, rect, 6, index == selected); let body = rect.translate(press.offset()); icons::paint(ui, Rect::from_min_size(body.min + vec2(8.0, 9.0), vec2(18.0, 18.0)), h.icon, 17.0, t.icon); // Display is translated; matching above already considered both languages. diff --git a/crates/ui-egui/tests/commands.rs b/crates/ui-egui/tests/commands.rs index f4e7bf85..a7023875 100644 --- a/crates/ui-egui/tests/commands.rs +++ b/crates/ui-egui/tests/commands.rs @@ -233,6 +233,22 @@ fn the_palette_lists_commands_with_shortcuts_and_runs_them() { assert_eq!(doc.can_undo(), Some("Rotate page")); } +#[test] +fn palette_arrow_keys_select_results_and_enter_runs_the_selection() { + let mut h = harness(); + h.state_mut().set_option("palette", "rotate pages").unwrap(); + h.run_steps(2); + h.get_by_label("Rotate pages clockwise"); + h.get_by_label("Rotate pages counterclockwise"); + h.key_press(Key::ArrowDown); + h.run_steps(2); + h.key_press(Key::Enter); + h.run_steps(2); + let app = h.state(); + assert!(!app.palette_open); + assert_eq!(app.session.get(app.views[0].id).unwrap().info.pages[0].rotation, 270); +} + #[test] fn the_pages_menu_comes_from_the_registry() { let mut h = harness();