fixed loading bar as it now shows progress
This commit is contained in:
+67
-4
@@ -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(())
|
||||||
|
|||||||
+32
-11
@@ -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,26 +197,44 @@ 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 {
|
||||||
|
loop {
|
||||||
match rx.try_recv() {
|
match rx.try_recv() {
|
||||||
Ok(Ok(_)) => {
|
Ok(exporter::ExportEvent::Progress(progress)) => {
|
||||||
self.export_rx = None;
|
self.export_progress = Some(progress);
|
||||||
|
ctx.request_repaint();
|
||||||
}
|
}
|
||||||
Ok(Err(err)) => {
|
Ok(exporter::ExportEvent::Finished(Ok(_))) => {
|
||||||
|
export_finished = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Ok(exporter::ExportEvent::Finished(Err(err))) => {
|
||||||
eprintln!("{err}");
|
eprintln!("{err}");
|
||||||
self.export_rx = None;
|
export_finished = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
Err(mpsc::TryRecvError::Empty) => {}
|
Err(mpsc::TryRecvError::Empty) => break,
|
||||||
Err(mpsc::TryRecvError::Disconnected) => {
|
Err(mpsc::TryRecvError::Disconnected) => {
|
||||||
|
export_finished = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if export_finished {
|
||||||
self.export_rx = None;
|
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);
|
||||||
|
|||||||
@@ -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")),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user