diff --git a/Cargo.lock b/Cargo.lock index f79acac..245dbb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -609,6 +609,7 @@ dependencies = [ "rfd", "serde", "serde_json", + "strum", "thiserror", "wasm-bindgen-futures", "web-sys", @@ -2900,6 +2901,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + [[package]] name = "ryu" version = "1.0.18" @@ -3113,6 +3120,28 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.48", +] + [[package]] name = "syn" version = "1.0.109" diff --git a/Cargo.toml b/Cargo.toml index 4795cdc..2aa4c42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ rfd = "0.14.1" thiserror = "1.0.63" dirs = "5.0.1" serde_json = "1.0.120" +strum = { version = "0.26.3", features = ["derive"] } # native: [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/src/app.rs b/src/app.rs index 5dd0e28..c5875cc 100644 --- a/src/app.rs +++ b/src/app.rs @@ -24,6 +24,7 @@ mod files; mod files_wasm; #[cfg(target_arch = "wasm32")] use files_wasm as files; +use strum::{Display, EnumIter, IntoEnumIterator}; #[cfg(target_arch = "wasm32")] type FilePickerSharedResult = Arc>>>>; @@ -34,6 +35,12 @@ fn input_file() -> &'static FilePickerSharedResult { INPUT_FILE.get_or_init(Default::default) } +#[cfg(target_arch = "wasm32")] +fn output_file() -> &'static FilePickerSharedResult { + static OUTPUT_FILE: OnceLock = OnceLock::new(); + OUTPUT_FILE.get_or_init(Default::default) +} + /// We derive Deserialize/Serialize so we can persist app state on shutdown. #[derive(Default, Deserialize, Serialize)] #[serde(default)] // if we add new fields, give them default values when deserializing old state @@ -46,6 +53,8 @@ pub struct TemplateApp { #[cfg(target_arch = "wasm32")] #[serde(skip)] input_file_asked: bool, + #[serde(skip)] + output_file_asked: bool, } impl TemplateApp { @@ -115,16 +124,18 @@ impl eframe::App for TemplateApp { ui.heading("Bent"); ui.separator(); - ui.label("Input file:"); - self.browse(ui); - - ui.horizontal(|ui| { - if ui.button("Alert").clicked() { - self.toasts.warning("Warning!"); - } + FileDirection::iter().for_each(|direction| { + ui.label(format!("{direction} file:")); + self.browse(ui, direction); }); - ui.separator(); + // ui.horizontal(|ui| { + // if ui.button("Alert").clicked() { + // self.toasts.warning("Warning!"); + // } + // }); + + // ui.separator(); ui.with_layout(egui::Layout::bottom_up(egui::Align::LEFT), |ui| { ui.add(egui::github_link_file!( @@ -177,31 +188,62 @@ pub fn spawn_and_collect> + UnwindSa }); } +#[derive(Display, Clone, Copy, EnumIter)] +enum FileDirection { + Input, + Output, +} + impl TemplateApp { #[cfg(target_arch = "wasm32")] - fn browse(&mut self, ui: &mut egui::Ui) { + fn browse(&mut self, ui: &mut egui::Ui, direction: FileDirection) { ui.horizontal(|ui| { if ui.button("Browse").clicked() { - debug!("user asked to open a file"); - TemplateApp::prompt(); - self.input_file_asked = true; + debug!("user asked to open a file for {direction}"); + TemplateApp::prompt(direction); + match direction { + FileDirection::Input => { + self.input_file_asked = true; + } + FileDirection::Output => { + self.output_file_asked = true; + } + } } - if self.input_file_asked { - let mut guard = input_file().lock(); + if match direction { + FileDirection::Input => self.input_file_asked, + FileDirection::Output => self.output_file_asked, + } { + let mut guard = match direction { + FileDirection::Input => input_file(), + FileDirection::Output => output_file(), + } + .lock(); if guard.as_ref().is_some() { - self.input_file_asked = false; + match direction { + FileDirection::Input => { + self.input_file_asked = false; + } + FileDirection::Output => { + self.output_file_asked = false; + } + } trace!("just got the file picker result"); - match { + let res = { let guard = guard.as_ref(); unsafe { guard.unwrap_unchecked() } - } { + }; + match res { Ok(maybe_path) => { trace!("it performed successfuly"); if let Some(path) = maybe_path { match ImageFile::try_new(path) { Ok(file) => { trace!("file is correct"); - self.files[0] = Some(file); + self.files[match direction { + FileDirection::Input => 0, + FileDirection::Output => 1, + }] = Some(file); } Err(e) => { trace!("file is incorrect, reason: {e}"); @@ -224,56 +266,78 @@ impl TemplateApp { } } ui.label( - self.files[0] - .as_ref() - .map(|file| file.to_string()) - .unwrap_or_default(), + self.files[match direction { + FileDirection::Input => 0, + FileDirection::Output => 1, + }] + .as_ref() + .map(|file| file.to_string()) + .unwrap_or_default(), ); }); } #[cfg(target_arch = "wasm32")] - fn prompt() { + fn prompt(direction: FileDirection) { use std::panic::AssertUnwindSafe; use rfd::AsyncFileDialog; - spawn_and_collect( - AssertUnwindSafe( - AsyncFileDialog::new() - .set_title("Input image") - .set_directory(working_dir()) - .add_filter( - "images", - &ImageFormat::all() - .flat_map(ImageFormat::extensions_str) - .collect::>(), - ) - .pick_file() - .map(|x| x.map(|file| file.file_name())), + let fd = AsyncFileDialog::new() + .set_title(format!("{direction} image")) + .set_directory(working_dir()) + .add_filter( + "images", + &ImageFormat::all() + .flat_map(ImageFormat::extensions_str) + .collect::>(), + ); + + match direction { + FileDirection::Input => spawn_and_collect( + AssertUnwindSafe(fd.pick_file().map(|x| x.map(|file| file.file_name()))), + Arc::downgrade(match direction { + FileDirection::Input => input_file(), + FileDirection::Output => output_file(), + }), ), - Arc::downgrade(input_file()), - ); + FileDirection::Output => { + spawn_and_collect( + AssertUnwindSafe(fd.save_file().map(|x| x.map(|file| file.file_name()))), + Arc::downgrade(match direction { + FileDirection::Input => input_file(), + FileDirection::Output => output_file(), + }), + ); + } + } } #[cfg(not(target_arch = "wasm32"))] - fn browse(&mut self, ui: &mut egui::Ui) { + fn browse(&mut self, ui: &mut egui::Ui, direction: FileDirection) { use rfd::FileDialog; ui.horizontal(|ui| { if ui.button("Browse").clicked() { - if let Some(path) = FileDialog::new() - .set_title("Input image") - .set_directory(working_dir()) - .add_filter( - "images", - &ImageFormat::all() - .flat_map(ImageFormat::extensions_str) - .collect::>(), - ) - .pick_file() - { + if let Some(path) = { + let fd = FileDialog::new() + .set_title(format!("{direction} image")) + .set_directory(working_dir()) + .add_filter( + "images", + &ImageFormat::all() + .flat_map(ImageFormat::extensions_str) + .collect::>(), + ); + match direction { + FileDirection::Input => fd.pick_file(), + FileDirection::Output => fd.save_file(), + } + } { match ImageFile::try_new(&path) { Ok(file) => { - self.files[0] = Some(file); + self.files[match direction { + FileDirection::Input => 0, + FileDirection::Output => 1, + }] = Some(file); } Err(e) => { self.toasts.error(e.to_string()); @@ -282,10 +346,13 @@ impl TemplateApp { } } ui.label( - self.files[0] - .clone() - .map(|file| file.display().to_string()) - .unwrap_or_default(), + self.files[match direction { + FileDirection::Input => 0, + FileDirection::Output => 1, + }] + .clone() + .map(|file| file.display().to_string()) + .unwrap_or_default(), ); }); }