Merge pull request #300 from grovebit/bounce-browse

Bounce Mix: Browse for the file, and start in the session folder
This commit is contained in:
Brandon Thomas authored and GitHub committed 2026-10-13 06:30:09 -04:00
commit 2a675b7ae4
3 files changed
+82 -4

No files matched your search

+8 -2
View File
@@ -168,9 +168,15 @@ fn services() -> Services {
};
d.pick_file().map(|p| p.to_string_lossy().into_owned())
})),
pick_save: Some(Box::new(|_purpose: &str, default: &str| {
pick_save: Some(Box::new(|purpose: &str, default: &str| {
let path = std::path::Path::new(default);
let mut d = rfd::FileDialog::new().add_filter("SoundCraft Session", &["scraft"]);
let mut d = match purpose {
"file.bounce_mix" => rfd::FileDialog::new().add_filter("Audio", &["wav", "aif", "aiff", "flac"]),
_ => rfd::FileDialog::new().add_filter("SoundCraft Session", &["scraft"]),
};
if let Some(dir) = path.parent().filter(|dir| dir.is_dir()) {
d = d.set_directory(dir);
}
if let Some(n) = path.file_name().and_then(|n| n.to_str()) {
d = d.set_file_name(n);
}
+20 -2
View File
@@ -91,7 +91,13 @@ impl Dialogs {
}
"session.new" => Dialog::NewSession { name: "Untitled".into(), sample_rate: 48_000, demo: false },
"file.bounce_mix" => {
Dialog::Bounce { path: format!("{home}/{name} Bounce.wav"), format: "wav".into(), bit_depth: "24".into(), normalize: false }
let dir = e
.path
.as_deref()
.and_then(|p| std::path::Path::new(p).parent())
.filter(|d| !d.as_os_str().is_empty())
.map_or(home.clone(), |d| d.display().to_string());
Dialog::Bounce { path: format!("{dir}/{name} Bounce.wav"), format: "wav".into(), bit_depth: "24".into(), normalize: false }
}
"file.import_audio" => Dialog::PathPrompt { cmd: id.into(), title: "Import Audio".into(), path: home, key: "path".into() },
"file.import_midi" => Dialog::PathPrompt { cmd: id.into(), title: "Import MIDI".into(), path: home, key: "path".into() },
@@ -346,7 +352,19 @@ pub fn show(app: &mut SoundApp, ctx: &egui::Context) {
Dialog::Bounce { path, format, bit_depth, normalize } => {
ui.horizontal(|ui| {
ui.label("File");
ui.add(egui::TextEdit::singleline(path).desired_width(360.0));
ui.add(egui::TextEdit::singleline(path).desired_width(420.0));
if let Some(pick) = &app.services.pick_save
&& ui.button("Browse…").clicked()
&& let Some(p) = pick("file.bounce_mix", path)
{
*path = p;
match std::path::Path::new(path.as_str()).extension().map(|x| x.to_string_lossy().to_lowercase()).as_deref() {
Some("wav") => *format = "wav".into(),
Some("aif" | "aiff") => *format = "aiff".into(),
Some("flac") => *format = "flac".into(),
_ => {}
}
}
});
ui.horizontal(|ui| {
egui::ComboBox::from_label("Format").selected_text(format.to_uppercase()).show_ui(ui, |ui| {
+54
View File
@@ -0,0 +1,54 @@
//! Bounce Mix starts in the session's folder and has a Browse button for the file (#186).
use egui::vec2;
use egui_kittest::{Harness, kittest::Queryable};
use soundcraft_ui_egui::{Services, SoundApp, dialogs::Dialog};
use std::cell::RefCell;
use std::rc::Rc;
#[test]
fn bounce_starts_in_the_session_folder_and_browse_picks_the_file_and_format() {
let asked = Rc::new(RefCell::new(Vec::new()));
let log = Rc::clone(&asked);
let pick_save: Box<dyn Fn(&str, &str) -> Option<String>> = Box::new(move |purpose, default| {
log.borrow_mut().push((purpose.to_string(), default.to_string()));
Some("/Mixes/Final Mix.FLAC".into())
});
let mut engine = soundcraft_engine::demo::demo_engine();
engine.path = Some("/Songs/Midnight Groove/Midnight Groove.scraft".into());
let mut app = SoundApp::new(engine, None, Services { pick_save: Some(pick_save), ..Default::default() });
assert!(app.dialogs.open_for_command(&app.engine, "file.bounce_mix"));
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(3);
let default = "/Songs/Midnight Groove/Midnight Groove Bounce.wav".to_string();
assert_eq!(*asked.borrow(), vec![("file.bounce_mix".to_string(), default)]);
match &h.state().dialogs.open {
Some(Dialog::Bounce { path, format, .. }) => assert_eq!((path.as_str(), format.as_str()), ("/Mixes/Final Mix.FLAC", "flac")),
other => panic!("{other:?}"),
}
}
#[test]
fn bounce_of_a_session_with_a_relative_path_starts_in_the_default_folder() {
let mut engine = soundcraft_engine::demo::demo_engine();
engine.path = Some("Midnight Groove.scraft".into());
let name = engine.session().name.clone();
let mut app = SoundApp::new(engine, None, Services::default());
assert!(app.dialogs.open_for_command(&app.engine, "file.bounce_mix"));
let mut fresh = SoundApp::new(soundcraft_engine::demo::demo_engine(), None, Services::default());
assert!(fresh.dialogs.open_for_command(&fresh.engine, "file.bounce_mix"));
match (&app.dialogs.open, &fresh.dialogs.open) {
(Some(Dialog::Bounce { path, .. }), Some(Dialog::Bounce { path: unsaved, .. })) => {
assert_ne!(path.as_str(), format!("/{name} Bounce.wav"));
assert_eq!(path, unsaved, "the same folder as a session never saved");
}
other => panic!("{other:?}"),
}
}