Compare commits

..
2 changed files with 93 additions and 118 deletions

View file

@ -19,24 +19,11 @@ mod utils;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
enum Message { enum Message {
Open, ImagePicked(Result<PathBuf, String>),
Rotate,
Invert,
Filter,
Bar,
Save,
Quit,
Yank,
Put,
FocusNext,
FocusPrevious,
ImagePicked(Result<PathBuf, Cow<'static, str>>),
ImageDisplayReady(Result<widget::image::Allocation, widget::image::Error>), ImageDisplayReady(Result<widget::image::Allocation, widget::image::Error>),
SavePathPicked(Result<PathBuf, Cow<'static, str>>), SavePathPicked(Result<PathBuf, String>),
KeyPressed(keyboard::Key, keyboard::Modifiers),
FileDropped(PathBuf), FileDropped(PathBuf),
} }
@ -46,7 +33,7 @@ struct State {
image_display: Option<widget::image::Allocation>, image_display: Option<widget::image::Allocation>,
image_filter: widget::image::FilterMethod, image_filter: widget::image::FilterMethod,
error: Option<Cow<'static, str>>, error: Option<String>,
info: Option<String>, info: Option<String>,
infobar_shown: bool, infobar_shown: bool,
@ -58,7 +45,7 @@ impl State {
if let Some(path) = env::args().nth(1) { if let Some(path) = env::args().nth(1) {
match state.load_image(path) { match state.load_image(path) {
Err(e) => state.error = Some(e.into()), Err(e) => state.error = Some(e),
Ok(task) => return (state, task), Ok(task) => return (state, task),
} }
} }
@ -108,122 +95,129 @@ impl State {
} }
fn update(&mut self, message: Message) -> Task<Message> { fn update(&mut self, message: Message) -> Task<Message> {
match message { match message {
Message::ImagePicked(result) => { Message::ImagePicked(result) => match result.and_then(|path| self.load_image(path)) {
match result.and_then(|path| self.load_image(path).map_err(Cow::from)) { Err(e) => self.error = Some(e),
Err(e) => self.error = Some(e.into()), Ok(task) => {
Ok(task) => { self.error = None;
self.error = None; return task;
return task;
}
} }
} },
Message::ImageDisplayReady(result) => { Message::ImageDisplayReady(result) => {
self.image_display = Some(result.unwrap()); self.image_display = Some(result.unwrap());
} }
Message::SavePathPicked(result) => { Message::SavePathPicked(result) => {
self.error = result self.error = result
.map_err(Cow::from)
.and_then(|path| { .and_then(|path| {
self.image.as_ref().map_or_else( self.image.as_ref().map_or_else(
|| Err("no image to save".into()), || Err("no image to save".into()),
|image| image.save(path).map_err(|e| e.to_string().into()), |image| image.save(path).map_err(|e| e.to_string()),
) )
}) })
.err(); .err();
} }
Message::FocusNext => return widget::operation::focus_next(), Message::KeyPressed(key, modifiers) => match key.as_ref() {
Message::FocusPrevious => return widget::operation::focus_previous(), // input field cycling
Key::Named(key::Named::Tab) => {
Message::Open => { if modifiers.shift() {
return Task::perform(utils::pick_image(), Message::ImagePicked); return widget::operation::focus_previous();
} }
return widget::operation::focus_next();
Message::Rotate => match self.image.as_ref() {
None => self.error = Some("no image to rotate".into()),
Some(image) => {
self.error = None;
self.image = Some(imageops::rotate90(image));
return self.allocate_image();
} }
},
Message::Invert => match self.image.as_mut() {
None => self.error = Some("no image to invert".into()),
Some(image) => {
self.error = None;
imageops::invert(image);
return self.allocate_image();
}
},
Message::Filter => {
self.image_filter = match self.image_filter {
widget::image::FilterMethod::Linear => widget::image::FilterMethod::Nearest,
widget::image::FilterMethod::Nearest => widget::image::FilterMethod::Linear,
}
}
Message::Bar => {
self.infobar_shown = !self.infobar_shown;
}
Message::Save => { Key::Character("o") => {
if self.image.is_some() { return Task::perform(utils::pick_image(), Message::ImagePicked);
self.error = None;
return Task::perform(utils::pick_save_path(), Message::SavePathPicked);
} }
self.error = Some("no image to save".into());
}
Message::Quit => return window::latest().and_then(window::close), Key::Character("r") => match self.image.as_ref() {
None => self.error = Some("no image to rotate".into()),
Some(image) => {
self.error = None;
self.image = Some(imageops::rotate90(image));
return self.allocate_image();
}
},
Key::Character("i") => match self.image.as_mut() {
None => self.error = Some("no image to invert".into()),
Some(image) => {
self.error = None;
imageops::invert(image);
return self.allocate_image();
}
},
Key::Character("f") => {
self.image_filter = match self.image_filter {
widget::image::FilterMethod::Linear => widget::image::FilterMethod::Nearest,
widget::image::FilterMethod::Nearest => widget::image::FilterMethod::Linear,
}
}
Key::Character("b") => {
self.infobar_shown = !self.infobar_shown;
}
Message::Yank => { Key::Character("s") => {
if let Some(image) = self.image.as_ref() { 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),
Key::Character("y") | Key::Character("c") => {
if let Some(image) = self.image.as_ref() {
match match self.clipboard.take() {
Some(c) => Ok(c),
None => Clipboard::new(),
} {
Ok(mut clipboard) => {
self.error = clipboard
.set_image(utils::arboard_from_rgbaimage(image))
.map_err(|e| e.to_string())
.err();
self.clipboard = Some(clipboard);
}
Err(e) => self.error = Some(e.to_string()),
}
} else {
self.error = Some("no image to yank".into());
}
}
Key::Character("p") | Key::Character("v") => {
match match self.clipboard.take() { match match self.clipboard.take() {
Some(c) => Ok(c), Some(c) => Ok(c),
None => Clipboard::new(), None => Clipboard::new(),
} { } {
Ok(mut clipboard) => { Ok(mut clipboard) => {
self.error = clipboard let result = clipboard.get_image().map(utils::rgbaimage_from_arboard);
.set_image(utils::arboard_from_rgbaimage(image))
.map_err(|e| e.to_string().into()) self.error = result.as_ref().map_err(|e| e.to_string()).err();
.err();
self.clipboard = Some(clipboard); self.clipboard = Some(clipboard);
}
Err(e) => self.error = Some(e.to_string().into()),
}
} else {
self.error = Some("no image to yank".into());
}
}
Message::Put => {
match match self.clipboard.take() {
Some(c) => Ok(c),
None => Clipboard::new(),
} {
Ok(mut clipboard) => {
let result = clipboard.get_image().map(utils::rgbaimage_from_arboard);
self.error = result.as_ref().map_err(|e| e.to_string().into()).err(); if let Ok(image) = result {
self.clipboard = Some(clipboard); self.info = Some(format!("{}x{}", image.width(), image.height(),));
if image.dimensions() < (100, 100) {
if let Ok(image) = result { self.image_filter = widget::image::FilterMethod::Nearest;
self.info = Some(format!("{}x{}", image.width(), image.height(),)); }
if image.dimensions() < (100, 100) { self.image = Some(image);
self.image_filter = widget::image::FilterMethod::Nearest; return self.allocate_image();
} }
self.image = Some(image);
return self.allocate_image();
} }
Err(e) => self.error = Some(e.to_string()),
} }
Err(e) => self.error = Some(e.to_string().into()),
} }
}
// ignore unused keys
_ => {}
},
Message::FileDropped(path) => match self.load_image(path) { Message::FileDropped(path) => match self.load_image(path) {
Ok(task) => { Ok(task) => {
self.error = None; self.error = None;
return task; return task;
} }
Err(e) => self.error = Some(e.into()), Err(e) => self.error = Some(e),
}, },
} }
@ -284,26 +278,7 @@ impl State {
fn subscription(&self) -> Subscription<Message> { fn subscription(&self) -> Subscription<Message> {
event::listen().filter_map(|event| match event { event::listen().filter_map(|event| match event {
iced::Event::Keyboard(keyboard::Event::KeyPressed { key, modifiers, .. }) => { iced::Event::Keyboard(keyboard::Event::KeyPressed { key, modifiers, .. }) => {
match key.as_ref() { Some(Message::KeyPressed(key, modifiers))
Key::Named(key::Named::Tab) => Some(if modifiers.shift() {
Message::FocusPrevious
} else {
Message::FocusNext
}),
Key::Character("o") => Some(Message::Open),
Key::Character("r") => Some(Message::Rotate),
Key::Character("i") => Some(Message::Invert),
Key::Character("f") => Some(Message::Filter),
Key::Character("b") => Some(Message::Bar),
Key::Character("s") => Some(Message::Save),
Key::Character("q") => Some(Message::Quit),
Key::Character("y") | Key::Character("c") => Some(Message::Yank),
Key::Character("p") | Key::Character("v") => Some(Message::Put),
_ => None,
}
} }
iced::Event::Window(window::Event::FileDropped(path)) => { iced::Event::Window(window::Event::FileDropped(path)) => {
Some(Message::FileDropped(path)) Some(Message::FileDropped(path))

View file

@ -34,7 +34,7 @@ fn load_image_impl(path: impl AsRef<Path>) -> ImageResult<RgbaImage> {
Ok(decoded_image.into_rgba8()) Ok(decoded_image.into_rgba8())
} }
pub async fn pick_image() -> Result<PathBuf, Cow<'static, str>> { pub async fn pick_image() -> Result<PathBuf, String> {
let Some(filehandle) = AsyncFileDialog::new() let Some(filehandle) = AsyncFileDialog::new()
.add_filter( .add_filter(
"image", "image",
@ -52,7 +52,7 @@ pub async fn pick_image() -> Result<PathBuf, Cow<'static, str>> {
Ok(filehandle.path().to_owned()) Ok(filehandle.path().to_owned())
} }
pub async fn pick_save_path() -> Result<PathBuf, Cow<'static, str>> { pub async fn pick_save_path() -> Result<PathBuf, String> {
let Some(filehandle) = AsyncFileDialog::new().save_file().await else { let Some(filehandle) = AsyncFileDialog::new().save_file().await else {
return Err("no path to save provided".into()); return Err("no path to save provided".into());
}; };