From 18de039e77575f2b0233bb9957f940a9a8dbd5a5 Mon Sep 17 00:00:00 2001 From: electria Date: Fri, 31 Jul 2026 15:11:41 -0700 Subject: [PATCH] feat: make pickers async --- src/main.rs | 49 +++++++++++++++++++++++++++++++++---------------- src/utils.rs | 23 ++++++++--------------- 2 files changed, 41 insertions(+), 31 deletions(-) diff --git a/src/main.rs b/src/main.rs index c951b31..ba75666 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,9 @@ -use std::{borrow::Cow, env, ffi::OsStr, path::Path}; +use std::{ + borrow::Cow, + env, + ffi::OsStr, + path::{Path, PathBuf}, +}; use iced::{ Alignment, Color, Element, Length, Renderer, Subscription, Task, Theme, event, @@ -12,7 +17,9 @@ mod utils; #[derive(Clone, Debug)] enum Message { + ImagePicked(Result), ImageDisplayReady(Result), + SavePathPicked(Result), Event(iced::Event), } @@ -74,9 +81,26 @@ impl State { } fn update(&mut self, message: Message) -> Task { match message { + Message::ImagePicked(result) => match result.and_then(|path| self.load_image(path)) { + Err(e) => self.error = Some(e), + Ok(task) => { + self.error = None; + return task; + } + }, Message::ImageDisplayReady(result) => { self.image_display = Some(result.unwrap()); } + Message::SavePathPicked(result) => { + self.error = result + .and_then(|path| { + self.image.as_ref().map_or_else( + || Err("no image to save".into()), + |image| image.save(path).map_err(|e| e.to_string()), + ) + }) + .err(); + } Message::Event(iced::Event::Keyboard(keyboard::Event::KeyPressed { key, @@ -91,13 +115,9 @@ impl State { return widget::operation::focus_next(); } - Key::Character("o") => match self.pick_and_load_image() { - Ok(task) => { - self.error = None; - return task; - } - Err(e) => self.error = Some(e), - }, + Key::Character("o") => { + return Task::perform(utils::pick_image(), Message::ImagePicked); + } Key::Character("r") => match self.image.as_ref() { None => self.error = Some("no image to rotate".into()), @@ -126,7 +146,11 @@ impl State { } Key::Character("s") => { - self.save_image(); + if self.image.is_some() { + self.error = None; + return Task::perform(utils::pick_save_path(), Message::SavePathPicked); + } + self.error = Some("no image to save".into()); } Key::Character("q") => return window::latest().and_then(window::close), @@ -169,13 +193,6 @@ impl State { self.allocate_image() }) } - fn pick_and_load_image(&mut self) -> Result, String> { - utils::pick_image().and_then(|path| self.load_image(path)) - } - - fn save_image(&mut self) { - self.error = utils::save_image(self.image.as_ref()).err(); - } fn allocate_image(&self) -> Task { let Some(image) = self.image.as_ref() else { diff --git a/src/utils.rs b/src/utils.rs index cc523c4..fc1841d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -2,7 +2,7 @@ use std::path::{Path, PathBuf}; use iced::widget; use image::{DynamicImage, ImageDecoder, ImageReader, ImageResult, RgbaImage}; -use rfd::FileDialog; +use rfd::AsyncFileDialog; pub fn load_image(path: impl AsRef) -> Result { load_image_impl(path).map_err(|e| e.to_string()) @@ -20,8 +20,8 @@ fn load_image_impl(path: impl AsRef) -> ImageResult { Ok(decoded_image.into_rgba8()) } -pub fn pick_image() -> Result { - let Some(path) = FileDialog::new() +pub async fn pick_image() -> Result { + let Some(filehandle) = AsyncFileDialog::new() .add_filter( "image", &[ @@ -30,27 +30,20 @@ pub fn pick_image() -> Result { ], ) .pick_file() + .await else { return Err("no path to open provided".into()); }; - Ok(path) + Ok(filehandle.path().to_owned()) } -pub fn save_image(image: Option<&RgbaImage>) -> Result<(), String> { - let Some(image) = image else { - return Err("no image to save".into()); - }; - - let Some(path) = FileDialog::new().save_file() else { +pub async fn pick_save_path() -> Result { + let Some(filehandle) = AsyncFileDialog::new().save_file().await else { return Err("no path to save provided".into()); }; - if let Err(e) = image.save(&path) { - return Err(e.to_string()); - } - - Ok(()) + Ok(filehandle.path().to_owned()) } pub const fn string_from_filter_type(f: widget::image::FilterMethod) -> &'static str {