collective

Browse / github.com/storytold/deckcraft / patch

feat(ui): support Alt+1..9 shortcuts for Quick Access Toolbar items

[Feature Request] Quick Access Toolbar: support Alt+1..9 keyboard shortcuts for pinned items ### Summary In the desktop presentation editor, items pinned to the Quick Access Toolbar (QAT) should be triggerable via sequential `Alt+1` through `Alt+9` keyboard shortcuts matching their position on the toolbar (position 1 = Alt+1, position 2 = Alt+2, ..., position 9 = Alt+9). Currently, DeckCraft allows pinning and unpinning commands to the Quick Access Toolbar (`crates/ui-egui/src/qat.rs`), but pressing `Alt+1`..`Alt+9` does not execute the pinned tools. ### Expected Behavior - When the presentation editor has focus and no modal dialog or text edit is active, pressing `Alt+1` through `A

Status
open
Score
0.00
Adopters
0 other operators
Author
piyushgoel2808 (imported from pull request #1382, open)
Base
9ec39267931c0163dd5648c879043edcb9603cb8
Size
+69 −11 in 3 files
Review
pending
Shared
2026-10-28
Id
323d8f606faa033350263e73

How it went

No other operator has reported using it yet.

Diff

raw · crates/ui-egui/src/lib.rs crates/ui-egui/src/menus.rs crates/ui-egui/src/qat.rs

diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs--- a/crates/ui-egui/src/lib.rs+++ b/crates/ui-egui/src/lib.rs@@ -2270,6 +2270,27 @@ mod tests {         assert_eq!(app.ui.qat.len(), 6);         app.run("app.qat", json!({"reset": true})).unwrap();         assert_eq!(ids(app), ["app.home", "file.save", "app.exportDialog", "edit.undo", "edit.redo", "show.fromStart"]);+        // Alt+1 runs position 0 (app.home) when on Windows/Linux, opening Backstage.+        app.session.mac_keys = false;+        assert!(!app.backstage_open());+        assert!(crate::menus::ui_shortcut(app, egui::Key::Num1, deckcraft_engine::Mods { shift: false, alt: true, cmd: false }));+        assert!(app.backstage_open());+        // Disabled while Backstage is open.+        assert!(!crate::menus::ui_shortcut(app, egui::Key::Num1, deckcraft_engine::Mods { shift: false, alt: true, cmd: false }));+        app.close_backstage();+        // Alt+9 is unassigned when there are only 6 items.+        assert!(!crate::menus::ui_shortcut(app, egui::Key::Num9, deckcraft_engine::Mods { shift: false, alt: true, cmd: false }));+        // Disabled on macOS so Option+digit types characters.+        app.session.mac_keys = true;+        assert!(!crate::menus::ui_shortcut(app, egui::Key::Num1, deckcraft_engine::Mods { shift: false, alt: true, cmd: false }));+        app.session.mac_keys = false;+        // Disabled when text is being edited.+        let shape_id = deckcraft_model::ShapeId(app.session.active().unwrap().selection.shapes.first().map(|s| s.0).unwrap_or(0));+        app.session.active_mut().unwrap().selection.text = Some(deckcraft_engine::TextSel { shape: shape_id, ..Default::default() });+        assert!(!crate::menus::ui_shortcut(app, egui::Key::Num1, deckcraft_engine::Mods { shift: false, alt: true, cmd: false }));+        app.session.active_mut().unwrap().selection.text = None;+        // Disabled QAT items (e.g. edit.undo when stack is empty) cannot run.+        assert!(!crate::qat::run_index(app, 3));         // An older settings file without the toolbar gets the defaults.         let old: UiState = serde_json::from_value(json!({"tab": "Home"})).unwrap();         assert_eq!(old.qat.len(), 6);diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs--- a/crates/ui-egui/src/menus.rs+++ b/crates/ui-egui/src/menus.rs@@ -72,6 +72,24 @@ pub fn ui_shortcut(app: &mut SlideApp, key: egui::Key, m: Mods) -> bool {         }         (F, true, false, false) => "app.find",         (H, true, false, false) => "app.replace",+        (Num1 | Num2 | Num3 | Num4 | Num5 | Num6 | Num7 | Num8 | Num9, false, false, true) => {+            if app.session.mac_keys || app.backstage.is_some() || app.session.active().is_some_and(|d| d.selection.text.is_some()) {+                return false;+            }+            let idx = match key {+                Num1 => 0,+                Num2 => 1,+                Num3 => 2,+                Num4 => 3,+                Num5 => 4,+                Num6 => 5,+                Num7 => 6,+                Num8 => 7,+                Num9 => 8,+                _ => return false,+            };+            return crate::qat::run_index(app, idx);+        }         _ => return false,     };     let _ = app.run(id, json!({}));diff --git a/crates/ui-egui/src/qat.rs b/crates/ui-egui/src/qat.rs--- a/crates/ui-egui/src/qat.rs+++ b/crates/ui-egui/src/qat.rs@@ -101,26 +101,45 @@ fn run(app: &mut SlideApp, q: &QatItem) {     } } -fn tip(q: &QatItem) -> String {+/// Whether a Quick Access Toolbar item can be run right now.+pub fn is_enabled(app: &SlideApp, id: &str) -> bool {+    match id {+        "app.home" => true,+        "file.save" | "show.fromStart" | "review.spelling" | "app.find" => app.session.active().is_some(),+        id => crate::ribbon::enabled(app, id),+    }+}++/// Runs the Quick Access Toolbar item at `index` (0-based) if it exists and is enabled. Returns `true` if run.+pub fn run_index(app: &mut SlideApp, index: usize) -> bool {+    let Some(q) = app.ui.qat.get(index).cloned() else { return false };+    if !is_enabled(app, &q.id) {+        return false;+    }+    run(app, &q);+    true+}++fn tip(q: &QatItem, index: usize, mac: bool) -> String {     let sc =         deckcraft_engine::find_command(&q.id).and_then(|c| c.shortcut).or_else(|| crate::UI_COMMANDS.iter().find(|c| c.0 == q.id).and_then(|c| c.2));-    match sc {-        Some(s) => format!("{} ({})", q.label, crate::ribbon::pretty_shortcut(s)),-        None => q.label.clone(),+    let qat_sc = if !mac && index < 9 { Some(format!("Alt+{}", index + 1)) } else { None };+    match (sc, qat_sc) {+        (Some(s), Some(qsc)) => format!("{} ({}, {})", q.label, crate::ribbon::pretty_shortcut(s), qsc),+        (Some(s), None) => format!("{} ({})", q.label, crate::ribbon::pretty_shortcut(s)),+        (None, Some(qsc)) => format!("{} ({})", q.label, qsc),+        (None, None) => q.label.clone(),     } }  /// Draw the toolbar and its Customize menu into a left-to-right row of the title bar. pub fn show(app: &mut SlideApp, ui: &mut Ui) {     let items = app.ui.qat.clone();-    for q in &items {+    let mac = app.session.mac_keys;+    for (i, q) in items.iter().enumerate() {         let on = q.id == "app.home" && app.backstage_open();-        let en = match q.id.as_str() {-            "app.home" => true,-            "file.save" | "show.fromStart" | "review.spelling" | "app.find" => app.session.active().is_some(),-            id => crate::ribbon::enabled(app, id),-        };-        let r = crate::widgets::icon_toggle(ui, q.icon(), &tip(q), en, on);+        let en = is_enabled(app, &q.id);+        let r = crate::widgets::icon_toggle(ui, q.icon(), &tip(q, i, mac), en, on);         if r.clicked() {             run(app, q);         }