From 5678a854ea41bdb867b83ebc5ce5031a38bd7d35 Mon Sep 17 00:00:00 2001 From: grimsace Date: Wed, 6 May 2026 11:08:23 -0500 Subject: [PATCH] fixed loading bar as it now shows progress --- src/exporter.rs | 71 ++++++++++++++++++++++++++++++++++++++++++++++--- src/main.rs | 51 ++++++++++++++++++++++++----------- src/ui.rs | 16 ++++++++--- 3 files changed, 115 insertions(+), 23 deletions(-) diff --git a/src/exporter.rs b/src/exporter.rs index 9a4642b..2333bb9 100644 --- a/src/exporter.rs +++ b/src/exporter.rs @@ -1,6 +1,7 @@ use std::collections::HashSet; use std::fmt::Write as _; use std::path::PathBuf; +use std::sync::mpsc; use image::{DynamicImage, ImageFormat, RgbaImage, imageops::FilterType}; use rayon::prelude::*; @@ -34,6 +35,19 @@ pub enum ExportTarget { Folder(PathBuf), } +#[derive(Debug, Clone, Copy)] +pub struct ExportProgress { + pub completed: usize, + pub total: usize, +} + +pub enum ExportEvent { + Progress(ExportProgress), + Finished(Result), +} + +type ProgressSender = mpsc::Sender; + #[derive(Debug, Clone, Copy)] enum DoorStyle { Solid, @@ -213,6 +227,7 @@ pub fn export_to_target( layouts: &[DungeonLayout], settings: &UiSettings, target: ExportTarget, + progress_tx: &ProgressSender, ) -> Result { let export_indices: Vec = if settings.export_level_index == 999 { (0..layouts.len()).collect() @@ -222,37 +237,79 @@ pub fn export_to_target( .min(layouts.len().saturating_sub(1)); vec![idx] }; + let total = export_image_count(settings, &target, export_indices.len()); + report_progress(progress_tx, 0, total); match target { ExportTarget::Folder(folder) => { if settings.export_format == ExportFormat::Folder { - export_composite_masks_for_levels(layouts, settings, &folder, &export_indices)?; + export_composite_masks_for_levels( + layouts, + settings, + &folder, + &export_indices, + progress_tx, + )?; } else { - export_level_images_to_folder(layouts, settings, &folder, &export_indices)?; + export_level_images_to_folder( + layouts, + settings, + &folder, + &export_indices, + progress_tx, + )?; } Ok(folder) } ExportTarget::File(path) => { let idx = export_indices[0]; save_layout_to_file(&layouts[idx], settings, &path)?; + report_progress(progress_tx, 1, total); Ok(path) } } } +fn export_image_count(settings: &UiSettings, target: &ExportTarget, level_count: usize) -> usize { + match target { + ExportTarget::File(_) => 1, + ExportTarget::Folder(_) if settings.export_format == ExportFormat::Folder => { + level_count * composite_images_per_level(settings) + } + ExportTarget::Folder(_) => level_count, + } +} + +fn composite_images_per_level(settings: &UiSettings) -> usize { + let base_masks = 12; + let grid_mask = usize::from(settings.export_show_grid); + let composite = 1; + base_masks + grid_mask + composite +} + +fn report_progress(progress_tx: &ProgressSender, completed: usize, total: usize) { + let _ = progress_tx.send(ExportEvent::Progress(ExportProgress { completed, total })); +} + fn export_level_images_to_folder( layouts: &[DungeonLayout], settings: &UiSettings, folder: &std::path::Path, export_indices: &[usize], + progress_tx: &ProgressSender, ) -> Result<(), String> { std::fs::create_dir_all(folder).map_err(|err| format!("Failed creating folder: {err}"))?; + let completed = std::sync::atomic::AtomicUsize::new(0); + let total = export_indices.len(); let ext = settings.export_format.extension(); export_indices.par_iter().try_for_each(|&idx| { let filename = format!("level{}.{ext}", idx + 1); let path = folder.join(filename); - save_layout_to_file(&layouts[idx], settings, &path) + save_layout_to_file(&layouts[idx], settings, &path)?; + let done = completed.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1; + report_progress(progress_tx, done, total); + Ok(()) }) } @@ -424,6 +481,7 @@ fn export_composite_masks_for_levels( settings: &UiSettings, folder: &std::path::Path, export_indices: &[usize], + progress_tx: &ProgressSender, ) -> Result<(), String> { let contexts = export_indices .iter() @@ -479,6 +537,8 @@ fn export_composite_masks_for_levels( .chain(std::iter::once((context_idx, CompositeTask::Composite))) }) .collect::>(); + let completed = std::sync::atomic::AtomicUsize::new(0); + let total = export_tasks.len(); export_tasks .into_par_iter() @@ -490,7 +550,10 @@ fn export_composite_masks_for_levels( save_mask_image(&context.folder, stem, &pixmap, settings) } CompositeTask::Composite => save_composite_image(context, settings), - } + }?; + let done = completed.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1; + report_progress(progress_tx, done, total); + Ok::<(), String>(()) })?; Ok(()) diff --git a/src/main.rs b/src/main.rs index f85cafa..32f9978 100644 --- a/src/main.rs +++ b/src/main.rs @@ -51,7 +51,8 @@ struct DungeonApp { undo_stack: Vec, redo_stack: Vec, drag_state: Option, - export_rx: Option>>, + export_rx: Option>, + export_progress: Option, pending_delete: bool, add_corridor_drag: Option, resize_state: Option, @@ -76,6 +77,7 @@ impl Default for DungeonApp { redo_stack: Vec::new(), drag_state: None, export_rx: None, + export_progress: None, pending_delete: false, add_corridor_drag: None, resize_state: None, @@ -108,7 +110,8 @@ impl eframe::App for DungeonApp { let panel_result = draw_side_panel( ctx, &mut self.settings, - self.export_rx.is_some(), + self.export_progress + .map(|progress| (progress.completed, progress.total)), self.levels.len(), ); let undo_requested = ctx.input(|i| i.modifiers.command && i.key_pressed(egui::Key::Z)); @@ -194,27 +197,45 @@ impl eframe::App for DungeonApp { let settings = self.settings.clone(); let (tx, rx) = mpsc::channel(); thread::spawn(move || { - let result = exporter::export_to_target(&layouts, &settings, target); - let _ = tx.send(result); + let result = exporter::export_to_target(&layouts, &settings, target, &tx); + let _ = tx.send(exporter::ExportEvent::Finished(result)); + }); + self.export_progress = Some(exporter::ExportProgress { + completed: 0, + total: 1, }); self.export_rx = Some(rx); } + let mut export_finished = false; 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; + loop { + match rx.try_recv() { + Ok(exporter::ExportEvent::Progress(progress)) => { + self.export_progress = Some(progress); + ctx.request_repaint(); + } + Ok(exporter::ExportEvent::Finished(Ok(_))) => { + export_finished = true; + break; + } + Ok(exporter::ExportEvent::Finished(Err(err))) => { + eprintln!("{err}"); + export_finished = true; + break; + } + Err(mpsc::TryRecvError::Empty) => break, + Err(mpsc::TryRecvError::Disconnected) => { + export_finished = true; + break; + } } } } + if export_finished { + self.export_rx = None; + self.export_progress = None; + } draw_legend_panel(ctx, &mut self.settings.colorblind_mode); diff --git a/src/ui.rs b/src/ui.rs index 8d9a70f..0930856 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -261,7 +261,7 @@ pub struct SidePanelResult { pub fn draw_side_panel( ctx: &egui::Context, settings: &mut UiSettings, - export_in_progress: bool, + export_progress: Option<(usize, usize)>, num_levels: usize, ) -> SidePanelResult { // Render the left side panel and return user interaction results. @@ -310,7 +310,7 @@ pub fn draw_side_panel( match settings.active_tab { Tab::Generate => { - draw_generate_tab(ui, settings, &mut result, export_in_progress, num_levels) + draw_generate_tab(ui, settings, &mut result, export_progress, num_levels) } Tab::Layout => draw_layout_tab(ui, settings, &mut result), Tab::StartAndEnd => draw_start_and_end_tab(ui, settings, &mut result), @@ -410,9 +410,10 @@ fn draw_generate_tab( ui: &mut egui::Ui, settings: &mut UiSettings, result: &mut SidePanelResult, - export_in_progress: bool, + export_progress: Option<(usize, usize)>, num_levels: usize, ) { + let export_in_progress = export_progress.is_some(); ui.label(RichText::new("Grid Settings").strong()); ui.add_space(8.0); @@ -615,7 +616,14 @@ fn draw_generate_tab( if export_in_progress { ui.add_space(6.0); - ui.add(egui::ProgressBar::new(0.0).animate(true)); + let (completed, total) = export_progress.unwrap_or((0, 1)); + let total = total.max(1); + let fraction = (completed as f32 / total as f32).clamp(0.0, 1.0); + ui.add( + egui::ProgressBar::new(fraction) + .show_percentage() + .text(format!("{completed}/{total} images")), + ); } }