diff --git a/crates/ui-egui/src/shortcuts.rs b/crates/ui-egui/src/shortcuts.rs index 4d4417b3..516d6c4b 100644 --- a/crates/ui-egui/src/shortcuts.rs +++ b/crates/ui-egui/src/shortcuts.rs @@ -175,6 +175,20 @@ fn fixed(app: &mut SoundApp, key: Key, m: Modifiers, mac: bool) -> bool { } true } + // The registry's key families "Cmd+Alt+[ / ]", "Cmd+Alt+0-5" and "Cmd+Ctrl+1-5", which `parse` can't read. + Key::OpenBracket | Key::CloseBracket if mods_match(Modifiers::COMMAND | Modifiers::ALT, m, mac) => { + let factor = if key == Key::CloseBracket { 2.0 } else { 0.5 }; + let _ = app.run("view.waveform_zoom", json!({"factor": factor})); + true + } + Key::Num0 | Key::Num1 | Key::Num2 | Key::Num3 | Key::Num4 | Key::Num5 if mods_match(Modifiers::COMMAND | Modifiers::ALT, m, mac) => { + let _ = app.run("clip.rating", json!({"rating": digit(key)})); + true + } + Key::Num1 | Key::Num2 | Key::Num3 | Key::Num4 | Key::Num5 if mods_match(Modifiers::COMMAND | Modifiers::CTRL, m, mac) => { + let _ = app.run("view.zoom_preset", json!({"preset": digit(key)})); + true + } Key::ArrowUp | Key::ArrowDown if m.ctrl && !m.shift => { let id = if key == Key::ArrowUp { "edit.extend_selection_up" } else { "edit.extend_selection_down" }; let _ = app.run(id, json!({})); @@ -211,6 +225,11 @@ fn commands_focus(app: &mut SoundApp, key: Key) -> bool { Key::C => "edit.copy", Key::V => "edit.paste", Key::Z => "edit.undo", + Key::N => "options.insertion_follows_playback", + Key::Num1 | Key::Num2 | Key::Num3 | Key::Num4 | Key::Num5 => { + let _ = app.run("view.zoom_preset", json!({"preset": digit(key)})); + return true; + } Key::P => { move_selection_track(app, -1); return true; @@ -255,6 +274,10 @@ fn tab_clip(app: &mut SoundApp, back: bool) { } } +fn digit(key: Key) -> u8 { + key.name().parse().unwrap_or(0) +} + /// Shortcut text for display. The registry stores macOS-style strings ("Cmd+N"); off macOS the Cmd /// modifier is the Ctrl key. Mac-only Control chords ("Cmd+Ctrl+S") have no key there, so they show nothing. pub fn shortcut_label(s: &str, mac: bool) -> String { diff --git a/crates/ui-egui/tests/key_families.rs b/crates/ui-egui/tests/key_families.rs new file mode 100644 index 00000000..b3d728ba --- /dev/null +++ b/crates/ui-egui/tests/key_families.rs @@ -0,0 +1,63 @@ +//! Shortcuts written as key families ("Cmd+Ctrl+1-5", "Cmd+Alt+0-5", "Cmd+Alt+[ / ]") fire, and Commands Focus +//! has Pro Tools' 1–5 zoom presets and N for Insertion Follows Playback. + +use egui::os::OperatingSystem; +use egui::{Key, Modifiers}; +use serde_json::json; +use soundcraft_ui_egui::{Services, SoundApp, shortcuts}; + +const MAC_CMD: Modifiers = Modifiers { mac_cmd: true, command: true, ..Modifiers::NONE }; +const PC_CTRL: Modifiers = Modifiers { ctrl: true, command: true, ..Modifiers::NONE }; +const OSES: [(OperatingSystem, Modifiers); 3] = + [(OperatingSystem::Mac, MAC_CMD), (OperatingSystem::Windows, PC_CTRL), (OperatingSystem::Nix, PC_CTRL)]; + +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(); +} + +fn rig(os: OperatingSystem) -> (egui::Context, SoundApp) { + let ctx = egui::Context::default(); + ctx.set_os(os); + (ctx, SoundApp::new(soundcraft_engine::demo::demo_engine(), None, Services::default())) +} + +#[test] +fn cmd_alt_brackets_zoom_waveforms_and_cmd_alt_digits_rate_clips() { + for (os, cmd) in OSES { + let (ctx, mut app) = rig(os); + let cmd_alt = Modifiers { alt: true, ..cmd }; + press(&ctx, &mut app, Key::CloseBracket, cmd_alt); + press(&ctx, &mut app, Key::CloseBracket, cmd_alt); + press(&ctx, &mut app, Key::OpenBracket, cmd_alt); + assert_eq!(app.engine.session().edit.zoom.waveform_zoom, 2.0, "{os:?}"); + let kick = app.engine.session().track_by_name("Kick").unwrap().clips()[0].id; + app.engine.execute("edit.select", &json!({"clips": [kick.0]})).unwrap(); + press(&ctx, &mut app, Key::Num4, cmd_alt); + assert_eq!(app.engine.session().find_clip(kick).unwrap().1.rating, 4, "{os:?}"); + press(&ctx, &mut app, Key::Num9, cmd_alt); + assert_eq!(app.engine.session().find_clip(kick).unwrap().1.rating, 4, "{os:?}: 9 is outside 0-5"); + } +} + +#[test] +fn cmd_ctrl_digits_recall_zoom_presets_on_macos() { + let (ctx, mut app) = rig(OperatingSystem::Mac); + press(&ctx, &mut app, Key::Num2, Modifiers { ctrl: true, ..MAC_CMD }); + assert_eq!(app.engine.session().edit.zoom.samples_per_px, 256.0); + press(&ctx, &mut app, Key::Num6, Modifiers { ctrl: true, ..MAC_CMD }); + assert_eq!(app.engine.session().edit.zoom.samples_per_px, 256.0, "6 is outside 1-5"); +} + +#[test] +fn commands_focus_digits_recall_zoom_presets_and_n_toggles_insertion_follows_playback() { + for (os, _) in OSES { + let (ctx, mut app) = rig(os); + app.engine.execute("options.keyboard_focus", &json!({"focus": "commands"})).unwrap(); + let follows = app.engine.session().edit.insertion_follows_playback; + press(&ctx, &mut app, Key::Num5, Modifiers::NONE); + press(&ctx, &mut app, Key::N, Modifiers::NONE); + let edit = &app.engine.session().edit; + assert_eq!((edit.zoom.samples_per_px, edit.insertion_follows_playback), (16384.0, !follows), "{os:?}"); + } +}