From 6b98c21fedac5063a5676aa472df4ae754eb714c Mon Sep 17 00:00:00 2001 From: grovebit <337789029+grovebit@users.noreply.github.com> Date: Sat, 10 Oct 2026 14:20:55 +0100 Subject: [PATCH] Make the Window menu's shortcuts work The Window menu's keys never fired: Cmd+1 Transport, Cmd+3 Big Counter, Cmd+4 Automation, Cmd+5 Memory Locations, Cmd+9 Video, Cmd+Alt+M Narrow Mix, Cmd+W Close Window, Cmd+Ctrl+W Hide All Floating Windows and Cmd+Ctrl+= Score Editor. The shortcut handler only looked at engine commands, and these are window commands. It now falls back to them, and the menus and the Keyboard Shortcuts window show their keys. On a Mac, Cmd+Ctrl+= toggled Mix/Edit instead of opening the Score Editor. The Mix/Edit toggle now needs Cmd+= without Control there. The two Control chords still have no key on Windows and Linux. --- crates/ui-egui/src/menus.rs | 7 +++- crates/ui-egui/src/shortcuts.rs | 6 +++- crates/ui-egui/src/windows.rs | 11 +++--- crates/ui-egui/tests/window_shortcuts.rs | 43 ++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 crates/ui-egui/tests/window_shortcuts.rs diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 5b8f079..bbee052 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -261,7 +261,7 @@ fn menu_node(app: &mut SoundApp, ui: &mut egui::Ui, n: &MenuNode, extra: &[(&str let (enabled, checked) = item_state(app, &n.path, id.as_deref()); let label = live_label(app, id.as_deref()).unwrap_or_else(|| n.label.clone()); let label = if checked { format!("✔ {label}") } else { label }; - let shortcut = id.as_deref().and_then(soundcraft_engine::find_command).and_then(|c| c.shortcut).unwrap_or(""); + let shortcut = id.as_deref().and_then(shortcut_for).unwrap_or(""); let btn = egui::Button::new(label).shortcut_text(crate::shortcuts::shortcut_label(shortcut, ui.ctx().os().is_mac())); if ui.add_enabled(enabled, btn).clicked() && let Some(id) = id @@ -388,6 +388,11 @@ const MENU_WINDOWS: &[(&str, &str)] = &[ ("Event > MIDI Real-Time Properties", "window.rtp"), ]; +/// The shortcut of an engine or window command. +pub fn shortcut_for(id: &str) -> Option<&'static str> { + soundcraft_engine::find_command(id).map_or_else(|| UI_COMMANDS.iter().find(|c| c.0 == id).and_then(|c| c.3), |c| c.shortcut) +} + /// Undo and Redo name the step they would undo or redo; other items keep their catalog label. pub fn live_label(app: &SoundApp, id: Option<&str>) -> Option { let e = &app.engine; diff --git a/crates/ui-egui/src/shortcuts.rs b/crates/ui-egui/src/shortcuts.rs index 90a4e08..8438d3a 100644 --- a/crates/ui-egui/src/shortcuts.rs +++ b/crates/ui-egui/src/shortcuts.rs @@ -81,6 +81,10 @@ pub fn handle(app: &mut SoundApp, ctx: &egui::Context) { continue; } let _ = app.run(id, json!({})); + } else if let Some(c) = + crate::menus::UI_COMMANDS.iter().find(|c| c.3.and_then(parse).is_some_and(|(m, k)| k == key && mods_match(m, mods, mac))) + { + let _ = app.run(c.0, json!({})); } } } @@ -102,7 +106,7 @@ fn fixed(app: &mut SoundApp, key: Key, m: Modifiers, mac: bool) -> bool { let _ = app.run("window.search", json!({})); true } - Key::Equals if m.command => { + Key::Equals if m.command && !(mac && m.ctrl) => { let _ = app.run("window.toggle_mix_edit", json!({})); true } diff --git a/crates/ui-egui/src/windows.rs b/crates/ui-egui/src/windows.rs index b7b34e5..b4145e9 100644 --- a/crates/ui-egui/src/windows.rs +++ b/crates/ui-egui/src/windows.rs @@ -488,10 +488,13 @@ pub fn shortcuts_window(app: &mut SoundApp, ctx: &egui::Context) { win(ctx, &mut open, "Keyboard Shortcuts", vec2(520.0, 480.0), |ui| { egui::ScrollArea::vertical().show(ui, |ui| { egui::Grid::new("sc").num_columns(3).striped(true).show(ui, |ui| { - for c in soundcraft_engine::command_specs().iter().filter(|c| c.shortcut.is_some()) { - ui.label(egui::RichText::new(crate::shortcuts::shortcut_label(c.shortcut.unwrap_or(""), mac)).font(mono(11.0))); - ui.label(c.label); - ui.label(egui::RichText::new(c.menu.join(" › ")).small()); + let engine = soundcraft_engine::command_specs().iter().map(|c| (c.shortcut, c.label, c.menu.join(" › "))); + let windows = crate::menus::UI_COMMANDS.iter().map(|c| (c.3, c.1, c.2.replace(" > ", " › "))); + for (shortcut, label, menu) in engine.chain(windows) { + let Some(shortcut) = shortcut else { continue }; + ui.label(egui::RichText::new(crate::shortcuts::shortcut_label(shortcut, mac)).font(mono(11.0))); + ui.label(label); + ui.label(egui::RichText::new(menu).small()); ui.end_row(); } }); diff --git a/crates/ui-egui/tests/window_shortcuts.rs b/crates/ui-egui/tests/window_shortcuts.rs new file mode 100644 index 0000000..11e591e --- /dev/null +++ b/crates/ui-egui/tests/window_shortcuts.rs @@ -0,0 +1,43 @@ +use egui::os::OperatingSystem; +use egui::{Key, Modifiers}; +use soundcraft_ui_egui::{MainWindow, Services, SoundApp, shortcuts}; + +const MAC_CMD: Modifiers = Modifiers { mac_cmd: true, command: true, ..Modifiers::NONE }; + +fn press(ctx: &egui::Context, app: &mut SoundApp, key: Key, modifiers: Modifiers) { + let events = [true, false].map(|pressed| egui::Event::Key { key, physical_key: None, pressed, repeat: false, modifiers }).to_vec(); + ctx.run_ui(egui::RawInput { events, ..Default::default() }, |ui| shortcuts::handle(app, ui.ctx())).textures_delta.clear(); +} + +#[test] +fn window_shortcuts_open_and_close_windows() { + let pc_ctrl = Modifiers { ctrl: true, command: true, ..Modifiers::NONE }; + for (os, cmd) in [(OperatingSystem::Mac, MAC_CMD), (OperatingSystem::Windows, pc_ctrl), (OperatingSystem::Nix, pc_ctrl)] { + let mut app = SoundApp::new(soundcraft_engine::Engine::default(), None, Services::default()); + let ctx = egui::Context::default(); + ctx.set_os(os); + for key in [Key::Num1, Key::Num3, Key::Num4, Key::Num5, Key::Num9] { + press(&ctx, &mut app, key, cmd); + } + press(&ctx, &mut app, Key::M, Modifiers { alt: true, ..cmd }); + let ui = &app.ui; + assert!( + ui.show_transport && ui.show_big_counter && ui.show_automation && ui.show_memory_locations && ui.show_video && ui.narrow_mix, + "{os:?}" + ); + press(&ctx, &mut app, Key::W, cmd); + assert!(!app.ui.show_transport && !app.ui.show_big_counter && !app.ui.show_memory_locations, "{os:?}"); + } +} + +#[test] +fn cmd_ctrl_equals_opens_the_score_editor_on_a_mac() { + let mut app = SoundApp::new(soundcraft_engine::Engine::default(), None, Services::default()); + let ctx = egui::Context::default(); + ctx.set_os(OperatingSystem::Mac); + press(&ctx, &mut app, Key::Equals, Modifiers { ctrl: true, ..MAC_CMD }); + assert!(app.ui.show_score); + assert_eq!(app.ui.window, MainWindow::Edit); + press(&ctx, &mut app, Key::Equals, MAC_CMD); + assert_eq!(app.ui.window, MainWindow::Mix); +}