fixed loading bar as it now shows progress

This commit is contained in:
grimsace
2026-05-06 11:08:23 -05:00
parent 6127626ea9
commit 5678a854ea
3 changed files with 115 additions and 23 deletions
+67 -4
View File
@@ -1,6 +1,7 @@
use std::collections::HashSet; use std::collections::HashSet;
use std::fmt::Write as _; use std::fmt::Write as _;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::mpsc;
use image::{DynamicImage, ImageFormat, RgbaImage, imageops::FilterType}; use image::{DynamicImage, ImageFormat, RgbaImage, imageops::FilterType};
use rayon::prelude::*; use rayon::prelude::*;
@@ -34,6 +35,19 @@ pub enum ExportTarget {
Folder(PathBuf), Folder(PathBuf),
} }
#[derive(Debug, Clone, Copy)]
pub struct ExportProgress {
pub completed: usize,
pub total: usize,
}
pub enum ExportEvent {
Progress(ExportProgress),
Finished(Result<PathBuf, String>),
}
type ProgressSender = mpsc::Sender<ExportEvent>;
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
enum DoorStyle { enum DoorStyle {
Solid, Solid,
@@ -213,6 +227,7 @@ pub fn export_to_target(
layouts: &[DungeonLayout], layouts: &[DungeonLayout],
settings: &UiSettings, settings: &UiSettings,
target: ExportTarget, target: ExportTarget,
progress_tx: &ProgressSender,
) -> Result<PathBuf, String> { ) -> Result<PathBuf, String> {
let export_indices: Vec<usize> = if settings.export_level_index == 999 { let export_indices: Vec<usize> = if settings.export_level_index == 999 {
(0..layouts.len()).collect() (0..layouts.len()).collect()
@@ -222,37 +237,79 @@ pub fn export_to_target(
.min(layouts.len().saturating_sub(1)); .min(layouts.len().saturating_sub(1));
vec![idx] vec![idx]
}; };
let total = export_image_count(settings, &target, export_indices.len());
report_progress(progress_tx, 0, total);
match target { match target {
ExportTarget::Folder(folder) => { ExportTarget::Folder(folder) => {
if settings.export_format == ExportFormat::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 { } 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) Ok(folder)
} }
ExportTarget::File(path) => { ExportTarget::File(path) => {
let idx = export_indices[0]; let idx = export_indices[0];
save_layout_to_file(&layouts[idx], settings, &path)?; save_layout_to_file(&layouts[idx], settings, &path)?;
report_progress(progress_tx, 1, total);
Ok(path) 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( fn export_level_images_to_folder(
layouts: &[DungeonLayout], layouts: &[DungeonLayout],
settings: &UiSettings, settings: &UiSettings,
folder: &std::path::Path, folder: &std::path::Path,
export_indices: &[usize], export_indices: &[usize],
progress_tx: &ProgressSender,
) -> Result<(), String> { ) -> Result<(), String> {
std::fs::create_dir_all(folder).map_err(|err| format!("Failed creating folder: {err}"))?; 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(); let ext = settings.export_format.extension();
export_indices.par_iter().try_for_each(|&idx| { export_indices.par_iter().try_for_each(|&idx| {
let filename = format!("level{}.{ext}", idx + 1); let filename = format!("level{}.{ext}", idx + 1);
let path = folder.join(filename); 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, settings: &UiSettings,
folder: &std::path::Path, folder: &std::path::Path,
export_indices: &[usize], export_indices: &[usize],
progress_tx: &ProgressSender,
) -> Result<(), String> { ) -> Result<(), String> {
let contexts = export_indices let contexts = export_indices
.iter() .iter()
@@ -479,6 +537,8 @@ fn export_composite_masks_for_levels(
.chain(std::iter::once((context_idx, CompositeTask::Composite))) .chain(std::iter::once((context_idx, CompositeTask::Composite)))
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let completed = std::sync::atomic::AtomicUsize::new(0);
let total = export_tasks.len();
export_tasks export_tasks
.into_par_iter() .into_par_iter()
@@ -490,7 +550,10 @@ fn export_composite_masks_for_levels(
save_mask_image(&context.folder, stem, &pixmap, settings) save_mask_image(&context.folder, stem, &pixmap, settings)
} }
CompositeTask::Composite => save_composite_image(context, 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(()) Ok(())
+36 -15
View File
@@ -51,7 +51,8 @@ struct DungeonApp {
undo_stack: Vec<AppSnapshot>, undo_stack: Vec<AppSnapshot>,
redo_stack: Vec<AppSnapshot>, redo_stack: Vec<AppSnapshot>,
drag_state: Option<DragState>, drag_state: Option<DragState>,
export_rx: Option<mpsc::Receiver<Result<std::path::PathBuf, String>>>, export_rx: Option<mpsc::Receiver<exporter::ExportEvent>>,
export_progress: Option<exporter::ExportProgress>,
pending_delete: bool, pending_delete: bool,
add_corridor_drag: Option<AddCorridorDrag>, add_corridor_drag: Option<AddCorridorDrag>,
resize_state: Option<ResizeState>, resize_state: Option<ResizeState>,
@@ -76,6 +77,7 @@ impl Default for DungeonApp {
redo_stack: Vec::new(), redo_stack: Vec::new(),
drag_state: None, drag_state: None,
export_rx: None, export_rx: None,
export_progress: None,
pending_delete: false, pending_delete: false,
add_corridor_drag: None, add_corridor_drag: None,
resize_state: None, resize_state: None,
@@ -108,7 +110,8 @@ impl eframe::App for DungeonApp {
let panel_result = draw_side_panel( let panel_result = draw_side_panel(
ctx, ctx,
&mut self.settings, &mut self.settings,
self.export_rx.is_some(), self.export_progress
.map(|progress| (progress.completed, progress.total)),
self.levels.len(), self.levels.len(),
); );
let undo_requested = ctx.input(|i| i.modifiers.command && i.key_pressed(egui::Key::Z)); 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 settings = self.settings.clone();
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
thread::spawn(move || { thread::spawn(move || {
let result = exporter::export_to_target(&layouts, &settings, target); let result = exporter::export_to_target(&layouts, &settings, target, &tx);
let _ = tx.send(result); let _ = tx.send(exporter::ExportEvent::Finished(result));
});
self.export_progress = Some(exporter::ExportProgress {
completed: 0,
total: 1,
}); });
self.export_rx = Some(rx); self.export_rx = Some(rx);
} }
let mut export_finished = false;
if let Some(rx) = &self.export_rx { if let Some(rx) = &self.export_rx {
match rx.try_recv() { loop {
Ok(Ok(_)) => { match rx.try_recv() {
self.export_rx = None; Ok(exporter::ExportEvent::Progress(progress)) => {
} self.export_progress = Some(progress);
Ok(Err(err)) => { ctx.request_repaint();
eprintln!("{err}"); }
self.export_rx = None; Ok(exporter::ExportEvent::Finished(Ok(_))) => {
} export_finished = true;
Err(mpsc::TryRecvError::Empty) => {} break;
Err(mpsc::TryRecvError::Disconnected) => { }
self.export_rx = None; 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); draw_legend_panel(ctx, &mut self.settings.colorblind_mode);
+12 -4
View File
@@ -261,7 +261,7 @@ pub struct SidePanelResult {
pub fn draw_side_panel( pub fn draw_side_panel(
ctx: &egui::Context, ctx: &egui::Context,
settings: &mut UiSettings, settings: &mut UiSettings,
export_in_progress: bool, export_progress: Option<(usize, usize)>,
num_levels: usize, num_levels: usize,
) -> SidePanelResult { ) -> SidePanelResult {
// Render the left side panel and return user interaction results. // Render the left side panel and return user interaction results.
@@ -310,7 +310,7 @@ pub fn draw_side_panel(
match settings.active_tab { match settings.active_tab {
Tab::Generate => { 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::Layout => draw_layout_tab(ui, settings, &mut result),
Tab::StartAndEnd => draw_start_and_end_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, ui: &mut egui::Ui,
settings: &mut UiSettings, settings: &mut UiSettings,
result: &mut SidePanelResult, result: &mut SidePanelResult,
export_in_progress: bool, export_progress: Option<(usize, usize)>,
num_levels: usize, num_levels: usize,
) { ) {
let export_in_progress = export_progress.is_some();
ui.label(RichText::new("Grid Settings").strong()); ui.label(RichText::new("Grid Settings").strong());
ui.add_space(8.0); ui.add_space(8.0);
@@ -615,7 +616,14 @@ fn draw_generate_tab(
if export_in_progress { if export_in_progress {
ui.add_space(6.0); 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")),
);
} }
} }