From 21c580a3c071eef38ba36a4ba83c26ec9230fb29 Mon Sep 17 00:00:00 2001 From: grimsace Date: Tue, 21 Apr 2026 14:49:45 -0500 Subject: [PATCH] copying now uses it's own thread to avoid freezing the ui for larger images --- src/app.rs | 72 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 59 insertions(+), 13 deletions(-) diff --git a/src/app.rs b/src/app.rs index a955bbd..2eeb479 100644 --- a/src/app.rs +++ b/src/app.rs @@ -37,6 +37,8 @@ pub struct TerrainApp { generated: Option, generation_receiver: Option>>, generation_in_progress: bool, + copy_receiver: Option>>, + copy_in_progress: bool, status_message: String, } @@ -51,6 +53,8 @@ impl TerrainApp { generated: None, generation_receiver: None, generation_in_progress: false, + copy_receiver: None, + copy_in_progress: false, status_message: String::from("Adjust parameters and click Generate."), } } @@ -108,6 +112,33 @@ impl TerrainApp { } } + fn poll_copy(&mut self, ctx: &Context) { + let Some(receiver) = &self.copy_receiver else { + return; + }; + + match receiver.try_recv() { + Ok(Ok(message)) => { + self.status_message = message; + self.copy_receiver = None; + self.copy_in_progress = false; + } + Ok(Err(error)) => { + self.status_message = error; + self.copy_receiver = None; + self.copy_in_progress = false; + } + Err(TryRecvError::Empty) => { + ctx.request_repaint(); + } + Err(TryRecvError::Disconnected) => { + self.status_message = String::from("Copy worker disconnected."); + self.copy_receiver = None; + self.copy_in_progress = false; + } + } + } + fn install_textures(&mut self, ctx: &Context, images: &GeneratedImages) { let heightmap = gray_to_color_image(&images.heightmap_image); let bumpmap = gray_to_color_image(&images.bumpmap_image); @@ -119,27 +150,37 @@ impl TerrainApp { } fn copy_preview(&mut self, preview: PreviewFocus) { + if self.copy_in_progress { + self.status_message = String::from("Copy already in progress..."); + return; + } + let Some(generated) = &self.generated else { self.status_message = String::from("No generated image is available to copy."); return; }; let image = match preview { - PreviewFocus::Heightmap => &generated.heightmap_image, - PreviewFocus::Bumpmap => &generated.bumpmap_image, + PreviewFocus::Heightmap => generated.heightmap_image.clone(), + PreviewFocus::Bumpmap => generated.bumpmap_image.clone(), + }; + let success_message = match preview { + PreviewFocus::Heightmap => String::from("Heightmap copied to clipboard."), + PreviewFocus::Bumpmap => String::from("Bumpmap copied to clipboard."), }; - match copy_gray_image_to_clipboard(image) { - Ok(()) => { - self.status_message = match preview { - PreviewFocus::Heightmap => String::from("Heightmap copied to clipboard."), - PreviewFocus::Bumpmap => String::from("Bumpmap copied to clipboard."), - }; - } - Err(error) => { - self.status_message = format!("Failed to copy image: {error}"); - } - } + let (tx, rx) = mpsc::channel(); + self.copy_receiver = Some(rx); + self.copy_in_progress = true; + self.status_message = match preview { + PreviewFocus::Heightmap => String::from("Copying heightmap to clipboard..."), + PreviewFocus::Bumpmap => String::from("Copying bumpmap to clipboard..."), + }; + + thread::spawn(move || { + let result = copy_gray_image_to_clipboard(&image).map(|_| success_message); + let _ = tx.send(result); + }); } fn preview_context_menu(response: &Response) -> bool { @@ -236,6 +277,10 @@ impl TerrainApp { ui.add_space(8.0); ui.spinner(); } + if self.copy_in_progress { + ui.add_space(4.0); + ui.spinner(); + } ui.add_space(8.0); ui.label(&self.status_message); @@ -392,6 +437,7 @@ impl eframe::App for TerrainApp { fn update(&mut self, ctx: &Context, _frame: &mut eframe::Frame) { self.hovered_preview = None; self.poll_generation(ctx); + self.poll_copy(ctx); self.ui_left_panel(ctx); self.ui_right_panel(ctx);