Merge pull request #70 from grovebit/export-clips-shortcut
Shift+Cmd+K exports clips as files
This commit is contained in:
commit
3e97e49bfb
|
|
@ -157,6 +157,9 @@ fn services() -> Services {
|
|||
}
|
||||
d.save_file().map(|p| p.to_string_lossy().into_owned())
|
||||
})),
|
||||
pick_folder: Some(Box::new(|start: &str| {
|
||||
rfd::FileDialog::new().set_directory(start).pick_folder().map(|p| p.to_string_lossy().into_owned())
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -113,11 +113,14 @@ pub fn specs() -> Vec<CommandSpec> {
|
|||
}
|
||||
Ok(json!({"text": text}))
|
||||
}),
|
||||
cmd!(noundo "file.export_clips", "Export Clips as Files...", [], None, "{dir, clips?, format?: wav}", always, |e, p| {
|
||||
cmd!(noundo "file.export_clips", "Export Clips as Files...", [], Some("Cmd+Shift+K"), "{dir, clips?, format?: wav}", always, |e, p| {
|
||||
let dir = str_param(p, "dir").ok_or_else(|| bad("file.export_clips", "`dir` required"))?.to_string();
|
||||
let ids = clip_ids_param(e, p);
|
||||
if ids.is_empty() {
|
||||
return Err(bad("file.export_clips", "no clips selected"));
|
||||
}
|
||||
let n = crate::io::export_clips(e, &ids, &dir)?;
|
||||
Ok(json!({"written": n}))
|
||||
Ok(json!({"written": n, "dir": dir}))
|
||||
}),
|
||||
cmd!(noundo "file.export_selected_tracks_as_session", "Selected Tracks as New Session...", ["File", "Export"], None, "{path, tracks?, replace?: false}", has_selection, |e, p| {
|
||||
let path = str_param(p, "path").ok_or_else(|| bad("file.export_selected_tracks_as_session", "`path` required"))?.to_string();
|
||||
|
|
@ -501,6 +504,20 @@ mod tests {
|
|||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exports_the_selected_clips_and_refuses_an_empty_selection() {
|
||||
let mut e = crate::demo::demo_engine();
|
||||
let dir = std::env::temp_dir().join(format!("soundcraft-clips-{}", std::process::id()));
|
||||
let p = json!({"dir": dir.to_string_lossy()});
|
||||
e.execute("edit.select_none", &json!({})).unwrap();
|
||||
assert!(e.execute("file.export_clips", &p).is_err());
|
||||
e.execute("edit.select", &json!({"tracks": ["Kick"], "start": {"seconds": 10.0}, "end": {"seconds": 11.0}})).unwrap();
|
||||
let r = e.execute("file.export_clips", &p).unwrap();
|
||||
let files = std::fs::read_dir(&dir).map_or(0, Iterator::count);
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
assert!(files >= 1 && r["written"] == files, "{r}, {files} files");
|
||||
}
|
||||
|
||||
fn midi_engine() -> Engine {
|
||||
use soundcraft_midi::{CtrlEvent, CtrlKind, Note, Sequence};
|
||||
use soundcraft_model::{ChannelFormat, Clip, TrackKind};
|
||||
|
|
|
|||
|
|
@ -291,11 +291,19 @@ pub fn show(app: &mut SoundApp, ctx: &egui::Context) {
|
|||
if let Some(pick) = &app.services.pick_open
|
||||
&& cmd != "session.save_as"
|
||||
&& cmd != "session.save_copy"
|
||||
&& key != "dir"
|
||||
&& ui.button("Browse…").clicked()
|
||||
&& let Some(p) = pick(cmd, &[])
|
||||
{
|
||||
*path = p;
|
||||
}
|
||||
if let Some(pick) = &app.services.pick_folder
|
||||
&& key == "dir"
|
||||
&& ui.button("Browse…").clicked()
|
||||
&& let Some(p) = pick(path)
|
||||
{
|
||||
*path = p;
|
||||
}
|
||||
if let Some(pick) = &app.services.pick_save
|
||||
&& (cmd == "session.save_as" || cmd == "session.save_copy")
|
||||
&& ui.button("Browse…").clicked()
|
||||
|
|
@ -420,8 +428,12 @@ pub fn show(app: &mut SoundApp, ctx: &egui::Context) {
|
|||
}
|
||||
if let Some((cmd, params)) = action {
|
||||
keep = false;
|
||||
if let Err(e) = app.run(&cmd, params) {
|
||||
app.ui.status = e;
|
||||
match app.run(&cmd, params) {
|
||||
Ok(v) if cmd == "file.export_clips" => {
|
||||
app.ui.status = format!("Exported {} clip(s) to {}", v["written"], v["dir"].as_str().unwrap_or_default());
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(e) => app.ui.status = e,
|
||||
}
|
||||
}
|
||||
if keep {
|
||||
|
|
|
|||
|
|
@ -162,6 +162,7 @@ impl Default for UiState {
|
|||
pub struct Services {
|
||||
pub pick_open: Option<Box<dyn Fn(&str, &[&str]) -> Option<String>>>,
|
||||
pub pick_save: Option<Box<dyn Fn(&str, &str) -> Option<String>>>,
|
||||
pub pick_folder: Option<Box<dyn Fn(&str) -> Option<String>>>,
|
||||
}
|
||||
|
||||
/// File-system-safe folder name for a plugin id (`clap:com.x.y` → `clap_com.x.y`).
|
||||
|
|
|
|||
|
|
@ -75,10 +75,7 @@ pub fn handle(app: &mut SoundApp, ctx: &egui::Context) {
|
|||
if fixed(app, key, mods, mac) {
|
||||
continue;
|
||||
}
|
||||
let hit =
|
||||
soundcraft_engine::command_specs().iter().find(|c| c.shortcut.and_then(parse).is_some_and(|(m, k)| k == key && mods_match(m, mods, mac)));
|
||||
if let Some(c) = hit {
|
||||
let id = c.id;
|
||||
if let Some(id) = command_for(key, mods, mac) {
|
||||
if crate::menus::invoke_shortcut_dialog(app, id) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -87,6 +84,13 @@ pub fn handle(app: &mut SoundApp, ctx: &egui::Context) {
|
|||
}
|
||||
}
|
||||
|
||||
fn command_for(key: Key, mods: Modifiers, mac: bool) -> Option<&'static str> {
|
||||
soundcraft_engine::command_specs()
|
||||
.iter()
|
||||
.find(|c| c.shortcut.and_then(parse).is_some_and(|(m, k)| k == key && mods_match(m, mods, mac)))
|
||||
.map(|c| c.id)
|
||||
}
|
||||
|
||||
/// Keys with context-dependent meaning. Returns true when handled.
|
||||
fn fixed(app: &mut SoundApp, key: Key, m: Modifiers, mac: bool) -> bool {
|
||||
let plain = !m.command && !m.alt && !m.ctrl && !m.shift;
|
||||
|
|
@ -284,8 +288,8 @@ mod label_tests {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{mods_match, parse, search_chord};
|
||||
use egui::Modifiers;
|
||||
use super::{command_for, mods_match, parse, search_chord};
|
||||
use egui::{Key, Modifiers};
|
||||
|
||||
/// Ctrl held on Windows or Linux, as egui reports it.
|
||||
const PC_CTRL: Modifiers = Modifiers { ctrl: true, command: true, ..Modifiers::NONE };
|
||||
|
|
@ -315,4 +319,11 @@ mod tests {
|
|||
assert!(!search_chord(MAC_CMD, true));
|
||||
assert!(search_chord(Modifiers { ctrl: true, ..MAC_CMD }, true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shift_cmd_k_prompts_for_a_folder_to_export_clips() {
|
||||
assert_eq!(command_for(Key::K, Modifiers { shift: true, ..MAC_CMD }, true), Some("file.export_clips"));
|
||||
assert_eq!(command_for(Key::K, Modifiers { shift: true, ..PC_CTRL }, false), Some("file.export_clips"));
|
||||
assert!(crate::dialogs::takes_path("file.export_clips"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue