mirror of
https://github.com/storytold/soundcraft.git
synced 2026-11-13 09:24:55 -06:00
@@ -156,6 +156,9 @@ fn services() -> Services {
|
||||
};
|
||||
d.pick_file()
|
||||
})),
|
||||
pick_open_many: Some(Box::new(|_purpose: &str| {
|
||||
rfd::FileDialog::new().add_filter("Audio", &FileFormat::readable_extensions()).pick_files().unwrap_or_default()
|
||||
})),
|
||||
pick_save: Some(Box::new(|purpose: &str, path: &std::path::Path| {
|
||||
let mut d = match purpose {
|
||||
"file.bounce_mix" => rfd::FileDialog::new().add_filter("Audio", craft_audio::ENCODE_EXTENSIONS),
|
||||
|
||||
@@ -331,7 +331,25 @@ fn bounce_file_row(app: &mut SoundApp, ui: &mut egui::Ui, path: &mut String, for
|
||||
fn path_prompt_form(app: &mut SoundApp, ui: &mut egui::Ui, cmd: &str, path: &mut String, key: &str, enter: bool) -> Option<(String, Value)> {
|
||||
crate::widgets::named_text_field(ui.add(egui::TextEdit::singleline(path).desired_width(420.0)), "Path");
|
||||
let mut picked = None;
|
||||
// Import Audio picks several files at once; every other open dialog takes one.
|
||||
let multi = cmd == "file.import_audio" && app.services.pick_open_many.is_some();
|
||||
if multi
|
||||
&& let Some(pick) = &app.services.pick_open_many
|
||||
&& ui.button("Browse…").clicked()
|
||||
{
|
||||
let mut files = pick(cmd);
|
||||
if files.len() > 1 {
|
||||
let texts: Result<Vec<&str>, _> = files.iter().map(|f| unicode(f)).collect();
|
||||
match texts {
|
||||
Ok(texts) => return Some((cmd.to_string(), json!({"paths": texts}))),
|
||||
Err(e) => app.ui.status = e.to_string(),
|
||||
}
|
||||
} else {
|
||||
picked = files.pop();
|
||||
}
|
||||
}
|
||||
if let Some(pick) = &app.services.pick_open
|
||||
&& !multi
|
||||
&& cmd != "session.save_as"
|
||||
&& cmd != "session.save_copy"
|
||||
&& key != "dir"
|
||||
|
||||
@@ -46,8 +46,30 @@ impl Imports {
|
||||
}
|
||||
|
||||
/// Imports the audio file `params["path"]` names (the `file.import_audio` params) in the
|
||||
/// background. Anything else (several `paths`, no path, the browser) runs the command now.
|
||||
/// background; several `paths` queue one import each. Anything else (no path, the browser) runs the command now.
|
||||
pub fn start(app: &mut SoundApp, params: Value) {
|
||||
// Several picked files: one queue entry each, so they decode one at a time and the app stays responsive.
|
||||
if !cfg!(target_arch = "wasm32")
|
||||
&& params.get("path").is_none()
|
||||
&& let Some(paths) = params.get("paths").and_then(Value::as_array)
|
||||
{
|
||||
let singles: Vec<Value> = paths
|
||||
.iter()
|
||||
.filter_map(Value::as_str)
|
||||
.map(|p| {
|
||||
let mut one = params.clone();
|
||||
if let Some(o) = one.as_object_mut() {
|
||||
o.remove("paths");
|
||||
o.insert("path".into(), Value::from(p));
|
||||
}
|
||||
one
|
||||
})
|
||||
.collect();
|
||||
if !singles.is_empty() {
|
||||
app.imports.queue.extend(singles);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if cfg!(target_arch = "wasm32") || params.get("path").and_then(Value::as_str).is_none() {
|
||||
run_now(app, params);
|
||||
return;
|
||||
@@ -213,6 +235,20 @@ mod tests {
|
||||
assert_eq!(app.engine.session().tracks.len(), 1, "the good file still imports");
|
||||
}
|
||||
|
||||
/// Issue #1089: several files picked in one Browse go to the queue, one entry each.
|
||||
#[test]
|
||||
fn several_paths_import_one_after_another() {
|
||||
let ctx = egui::Context::default();
|
||||
let mut app = SoundApp::new(soundcraft_engine::Engine::default(), None, Services::default());
|
||||
let (a, b, c) = (tone_wav("many-a.wav"), tone_wav("many-b.wav"), tone_wav("many-c.wav"));
|
||||
start(&mut app, json!({"paths": [a, b, c]}));
|
||||
assert_eq!(app.imports.queue.len(), 3);
|
||||
step(&mut app, &ctx);
|
||||
assert_eq!(app.imports.label().map(|l| l.1), Some(2), "the other two wait for the first");
|
||||
settle(&mut app, &ctx);
|
||||
assert_eq!(app.engine.session().tracks.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_failed_import_stays_on_the_status_line_while_the_next_one_starts() {
|
||||
let ctx = egui::Context::default();
|
||||
|
||||
@@ -5,6 +5,8 @@ use std::path::{Path, PathBuf};
|
||||
#[derive(Default)]
|
||||
pub struct Services {
|
||||
pub pick_open: Option<Box<dyn Fn(&str, &[&str]) -> Option<PathBuf>>>,
|
||||
/// Like `pick_open`, but the user may select several files (Import Audio). Empty when cancelled.
|
||||
pub pick_open_many: Option<Box<dyn Fn(&str) -> Vec<PathBuf>>>,
|
||||
/// The purpose and the file to start from.
|
||||
pub pick_save: Option<Box<dyn Fn(&str, &Path) -> Option<PathBuf>>>,
|
||||
/// The folder to start in.
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
//! Import Audio's Browse button lets the user select several files at once (#1089).
|
||||
use egui::vec2;
|
||||
use egui_kittest::{Harness, kittest::Queryable};
|
||||
use soundcraft_ui_egui::{Services, SoundApp, dialogs::Dialog};
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn browse(picked: Vec<PathBuf>) -> Harness<'static, SoundApp> {
|
||||
let pick_open_many: Box<dyn Fn(&str) -> Vec<PathBuf>> = Box::new(move |purpose| {
|
||||
assert_eq!(purpose, "file.import_audio");
|
||||
picked.clone()
|
||||
});
|
||||
let mut app = SoundApp::new(soundcraft_engine::Engine::default(), None, Services { pick_open_many: Some(pick_open_many), ..Default::default() });
|
||||
assert!(app.dialogs.open_for_command(&app.engine, "file.import_audio"));
|
||||
let mut h = Harness::builder().with_size(vec2(1600.0, 1000.0)).build_ui_state(
|
||||
|ui, app: &mut SoundApp| {
|
||||
app.logic(&ui.ctx().clone());
|
||||
app.ui(ui);
|
||||
},
|
||||
app,
|
||||
);
|
||||
h.run_steps(3);
|
||||
h.get_by_label("Browse…").click();
|
||||
h.run_steps(1);
|
||||
h
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn browse_with_several_files_imports_them_and_closes_the_dialog() {
|
||||
let h = browse(vec!["/nope/a.wav".into(), "/nope/b.wav".into()]);
|
||||
assert!(h.state().dialogs.open.is_none());
|
||||
assert!(h.state().imports.busy() || !h.state().ui.status.is_empty(), "the files were queued");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn browse_with_one_file_fills_the_path_field() {
|
||||
let h = browse(vec!["/nope/a.wav".into()]);
|
||||
match &h.state().dialogs.open {
|
||||
Some(Dialog::PathPrompt { path, .. }) => assert_eq!(path, "/nope/a.wav"),
|
||||
other => panic!("{other:?}"),
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ mod focused_dialog_buttons;
|
||||
mod group_dialog;
|
||||
mod hide_floating;
|
||||
mod idle;
|
||||
mod import_browse;
|
||||
mod key_families;
|
||||
mod long_menu_scroll;
|
||||
mod marker_rename;
|
||||
|
||||
Reference in New Issue
Block a user