diff --git a/src/exporter.rs b/src/exporter.rs index 3a103bb..da20ad1 100644 --- a/src/exporter.rs +++ b/src/exporter.rs @@ -19,6 +19,11 @@ const LOCKED_DOOR: (u8, u8, u8, u8) = (80, 200, 120, 255); const ARCHWAY: (u8, u8, u8, u8) = (70, 130, 220, 255); const WHITE: (u8, u8, u8, u8) = (255, 255, 255, 255); +pub enum ExportTarget { + File(PathBuf), + Folder(PathBuf), +} + #[derive(Debug, Clone, Copy)] enum DoorStyle { Solid, @@ -111,17 +116,13 @@ fn collect_scene_data(layout: &DungeonLayout, settings: &UiSettings) -> SceneDat scene } -pub fn export_with_dialog( - layout: &DungeonLayout, - settings: &UiSettings, -) -> Result { +pub fn select_export_target(settings: &UiSettings) -> Result { if settings.export_format == ExportFormat::Folder { let folder = FileDialog::new() .set_title("Export composite masks folder") .pick_folder() .ok_or_else(|| "Export canceled".to_string())?; - export_composite_masks(layout, settings, &folder)?; - return Ok(folder); + return Ok(ExportTarget::Folder(folder)); } let ext = settings.export_format.extension(); @@ -141,27 +142,42 @@ pub fn export_with_dialog( path.set_extension(ext); } - match settings.export_format { - ExportFormat::Svg => { - let svg = build_svg(layout, settings); - std::fs::write(&path, svg).map_err(|e| format!("Failed writing SVG: {e}"))?; - } - ExportFormat::Png | ExportFormat::Jpeg | ExportFormat::Webp => { - let pixmap = render_pixmap(layout, settings)?; - let img = raster_image_from_pixmap(&pixmap, settings)?; - let format = match settings.export_format { - ExportFormat::Png => ImageFormat::Png, - ExportFormat::Jpeg => ImageFormat::Jpeg, - ExportFormat::Webp => ImageFormat::WebP, - ExportFormat::Svg | ExportFormat::Folder => unreachable!(), - }; - img.save_with_format(&path, format) - .map_err(|e| format!("Failed writing image: {e}"))?; - } - ExportFormat::Folder => unreachable!(), - } + Ok(ExportTarget::File(path)) +} - Ok(path) +pub fn export_to_target( + layout: &DungeonLayout, + settings: &UiSettings, + target: ExportTarget, +) -> Result { + match target { + ExportTarget::Folder(folder) => { + export_composite_masks(layout, settings, &folder)?; + Ok(folder) + } + ExportTarget::File(path) => { + match settings.export_format { + ExportFormat::Svg => { + let svg = build_svg(layout, settings); + std::fs::write(&path, svg).map_err(|e| format!("Failed writing SVG: {e}"))?; + } + ExportFormat::Png | ExportFormat::Jpeg | ExportFormat::Webp => { + let pixmap = render_pixmap(layout, settings)?; + let img = raster_image_from_pixmap(&pixmap, settings)?; + let format = match settings.export_format { + ExportFormat::Png => ImageFormat::Png, + ExportFormat::Jpeg => ImageFormat::Jpeg, + ExportFormat::Webp => ImageFormat::WebP, + ExportFormat::Svg | ExportFormat::Folder => unreachable!(), + }; + img.save_with_format(&path, format) + .map_err(|e| format!("Failed writing image: {e}"))?; + } + ExportFormat::Folder => unreachable!(), + } + Ok(path) + } + } } fn render_pixmap(layout: &DungeonLayout, settings: &UiSettings) -> Result { diff --git a/src/main.rs b/src/main.rs index 1cbb2f3..a43316d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,8 @@ mod settings; mod ui; use std::collections::HashSet; +use std::sync::mpsc; +use std::thread; use eframe::egui; use egui::{Color32, Stroke}; @@ -25,6 +27,7 @@ struct DungeonApp { settings: UiSettings, layout: DungeonLayout, drag_state: Option, + export_rx: Option>>, } impl Default for DungeonApp { @@ -48,6 +51,7 @@ impl Default for DungeonApp { settings, layout, drag_state: None, + export_rx: None, } } } @@ -62,7 +66,7 @@ impl Drop for DungeonApp { impl eframe::App for DungeonApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - let panel_result = draw_side_panel(ctx, &mut self.settings); + let panel_result = draw_side_panel(ctx, &mut self.settings, self.export_rx.is_some()); if self.settings.min_room_size > self.settings.max_room_size { self.settings.max_room_size = self.settings.min_room_size; @@ -77,9 +81,37 @@ impl eframe::App for DungeonApp { if panel_result.reset_clicked || panel_result.settings_changed { self.regenerate_layout(); } - if panel_result.export_clicked { - if let Err(err) = exporter::export_with_dialog(&self.layout, &self.settings) { - eprintln!("{err}"); + if panel_result.export_clicked && self.export_rx.is_none() { + let target = match exporter::select_export_target(&self.settings) { + Ok(target) => target, + Err(err) => { + eprintln!("{err}"); + return; + } + }; + let layout = self.layout.clone(); + let settings = self.settings.clone(); + let (tx, rx) = mpsc::channel(); + thread::spawn(move || { + let result = exporter::export_to_target(&layout, &settings, target); + let _ = tx.send(result); + }); + self.export_rx = Some(rx); + } + + if let Some(rx) = &self.export_rx { + match rx.try_recv() { + Ok(Ok(_)) => { + self.export_rx = None; + } + Ok(Err(err)) => { + eprintln!("{err}"); + self.export_rx = None; + } + Err(mpsc::TryRecvError::Empty) => {} + Err(mpsc::TryRecvError::Disconnected) => { + self.export_rx = None; + } } } diff --git a/src/ui.rs b/src/ui.rs index f7df825..2bd5cdd 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -152,7 +152,11 @@ pub struct SidePanelResult { pub export_clicked: bool, } -pub fn draw_side_panel(ctx: &egui::Context, settings: &mut UiSettings) -> SidePanelResult { +pub fn draw_side_panel( + ctx: &egui::Context, + settings: &mut UiSettings, + export_in_progress: bool, +) -> SidePanelResult { let mut result = SidePanelResult::default(); egui::SidePanel::left("options_panel") @@ -175,7 +179,7 @@ pub fn draw_side_panel(ctx: &egui::Context, settings: &mut UiSettings) -> SidePa ui.separator(); match settings.active_tab { - Tab::Generate => draw_generate_tab(ui, settings, &mut result), + Tab::Generate => draw_generate_tab(ui, settings, &mut result, export_in_progress), Tab::Layout => draw_layout_tab(ui, settings, &mut result), } }); @@ -183,7 +187,12 @@ pub fn draw_side_panel(ctx: &egui::Context, settings: &mut UiSettings) -> SidePa result } -fn draw_generate_tab(ui: &mut egui::Ui, settings: &mut UiSettings, result: &mut SidePanelResult) { +fn draw_generate_tab( + ui: &mut egui::Ui, + settings: &mut UiSettings, + result: &mut SidePanelResult, + export_in_progress: bool, +) { ui.label(RichText::new("Grid Settings").strong()); ui.add_space(8.0); @@ -339,6 +348,11 @@ fn draw_generate_tab(ui: &mut egui::Ui, settings: &mut UiSettings, result: &mut if ui.button("Export Image").clicked() { result.export_clicked = true; } + + if export_in_progress { + ui.add_space(6.0); + ui.add(egui::ProgressBar::new(0.0).animate(true)); + } } fn draw_layout_tab(ui: &mut egui::Ui, settings: &mut UiSettings, result: &mut SidePanelResult) {