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.
This commit is contained in:
grovebit 2026-10-10 14:20:55 +01:00
parent bd00c3855c
commit 6b98c21fed
4 changed files with 61 additions and 6 deletions

View File

@ -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<String> {
let e = &app.engine;

View File

@ -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
}

View File

@ -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();
}
});

View File

@ -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);
}