Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
95a24fa987 |
|||
|
63f6b6f81b |
2 changed files with 118 additions and 93 deletions
97
src/main.rs
97
src/main.rs
|
|
@ -19,11 +19,24 @@ mod utils;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
enum Message {
|
enum Message {
|
||||||
ImagePicked(Result<PathBuf, String>),
|
Open,
|
||||||
|
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, String>),
|
SavePathPicked(Result<PathBuf, Cow<'static, str>>),
|
||||||
|
|
||||||
KeyPressed(keyboard::Key, keyboard::Modifiers),
|
|
||||||
FileDropped(PathBuf),
|
FileDropped(PathBuf),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,7 +46,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<String>,
|
error: Option<Cow<'static, str>>,
|
||||||
info: Option<String>,
|
info: Option<String>,
|
||||||
infobar_shown: bool,
|
infobar_shown: bool,
|
||||||
|
|
||||||
|
|
@ -45,7 +58,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),
|
Err(e) => state.error = Some(e.into()),
|
||||||
Ok(task) => return (state, task),
|
Ok(task) => return (state, task),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -95,41 +108,38 @@ 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) => match result.and_then(|path| self.load_image(path)) {
|
Message::ImagePicked(result) => {
|
||||||
Err(e) => self.error = Some(e),
|
match result.and_then(|path| self.load_image(path).map_err(Cow::from)) {
|
||||||
|
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()),
|
|image| image.save(path).map_err(|e| e.to_string().into()),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.err();
|
.err();
|
||||||
}
|
}
|
||||||
|
|
||||||
Message::KeyPressed(key, modifiers) => match key.as_ref() {
|
Message::FocusNext => return widget::operation::focus_next(),
|
||||||
// input field cycling
|
Message::FocusPrevious => return widget::operation::focus_previous(),
|
||||||
Key::Named(key::Named::Tab) => {
|
|
||||||
if modifiers.shift() {
|
|
||||||
return widget::operation::focus_previous();
|
|
||||||
}
|
|
||||||
return widget::operation::focus_next();
|
|
||||||
}
|
|
||||||
|
|
||||||
Key::Character("o") => {
|
Message::Open => {
|
||||||
return Task::perform(utils::pick_image(), Message::ImagePicked);
|
return Task::perform(utils::pick_image(), Message::ImagePicked);
|
||||||
}
|
}
|
||||||
|
|
||||||
Key::Character("r") => match self.image.as_ref() {
|
Message::Rotate => match self.image.as_ref() {
|
||||||
None => self.error = Some("no image to rotate".into()),
|
None => self.error = Some("no image to rotate".into()),
|
||||||
Some(image) => {
|
Some(image) => {
|
||||||
self.error = None;
|
self.error = None;
|
||||||
|
|
@ -137,7 +147,7 @@ impl State {
|
||||||
return self.allocate_image();
|
return self.allocate_image();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Key::Character("i") => match self.image.as_mut() {
|
Message::Invert => match self.image.as_mut() {
|
||||||
None => self.error = Some("no image to invert".into()),
|
None => self.error = Some("no image to invert".into()),
|
||||||
Some(image) => {
|
Some(image) => {
|
||||||
self.error = None;
|
self.error = None;
|
||||||
|
|
@ -145,17 +155,17 @@ impl State {
|
||||||
return self.allocate_image();
|
return self.allocate_image();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Key::Character("f") => {
|
Message::Filter => {
|
||||||
self.image_filter = match self.image_filter {
|
self.image_filter = match self.image_filter {
|
||||||
widget::image::FilterMethod::Linear => widget::image::FilterMethod::Nearest,
|
widget::image::FilterMethod::Linear => widget::image::FilterMethod::Nearest,
|
||||||
widget::image::FilterMethod::Nearest => widget::image::FilterMethod::Linear,
|
widget::image::FilterMethod::Nearest => widget::image::FilterMethod::Linear,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Key::Character("b") => {
|
Message::Bar => {
|
||||||
self.infobar_shown = !self.infobar_shown;
|
self.infobar_shown = !self.infobar_shown;
|
||||||
}
|
}
|
||||||
|
|
||||||
Key::Character("s") => {
|
Message::Save => {
|
||||||
if self.image.is_some() {
|
if self.image.is_some() {
|
||||||
self.error = None;
|
self.error = None;
|
||||||
return Task::perform(utils::pick_save_path(), Message::SavePathPicked);
|
return Task::perform(utils::pick_save_path(), Message::SavePathPicked);
|
||||||
|
|
@ -163,9 +173,9 @@ impl State {
|
||||||
self.error = Some("no image to save".into());
|
self.error = Some("no image to save".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Key::Character("q") => return window::latest().and_then(window::close),
|
Message::Quit => return window::latest().and_then(window::close),
|
||||||
|
|
||||||
Key::Character("y") | Key::Character("c") => {
|
Message::Yank => {
|
||||||
if let Some(image) = self.image.as_ref() {
|
if let Some(image) = self.image.as_ref() {
|
||||||
match match self.clipboard.take() {
|
match match self.clipboard.take() {
|
||||||
Some(c) => Ok(c),
|
Some(c) => Ok(c),
|
||||||
|
|
@ -174,17 +184,17 @@ impl State {
|
||||||
Ok(mut clipboard) => {
|
Ok(mut clipboard) => {
|
||||||
self.error = clipboard
|
self.error = clipboard
|
||||||
.set_image(utils::arboard_from_rgbaimage(image))
|
.set_image(utils::arboard_from_rgbaimage(image))
|
||||||
.map_err(|e| e.to_string())
|
.map_err(|e| e.to_string().into())
|
||||||
.err();
|
.err();
|
||||||
self.clipboard = Some(clipboard);
|
self.clipboard = Some(clipboard);
|
||||||
}
|
}
|
||||||
Err(e) => self.error = Some(e.to_string()),
|
Err(e) => self.error = Some(e.to_string().into()),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.error = Some("no image to yank".into());
|
self.error = Some("no image to yank".into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Key::Character("p") | Key::Character("v") => {
|
Message::Put => {
|
||||||
match match self.clipboard.take() {
|
match match self.clipboard.take() {
|
||||||
Some(c) => Ok(c),
|
Some(c) => Ok(c),
|
||||||
None => Clipboard::new(),
|
None => Clipboard::new(),
|
||||||
|
|
@ -192,7 +202,7 @@ impl State {
|
||||||
Ok(mut clipboard) => {
|
Ok(mut clipboard) => {
|
||||||
let result = clipboard.get_image().map(utils::rgbaimage_from_arboard);
|
let result = clipboard.get_image().map(utils::rgbaimage_from_arboard);
|
||||||
|
|
||||||
self.error = result.as_ref().map_err(|e| e.to_string()).err();
|
self.error = result.as_ref().map_err(|e| e.to_string().into()).err();
|
||||||
self.clipboard = Some(clipboard);
|
self.clipboard = Some(clipboard);
|
||||||
|
|
||||||
if let Ok(image) = result {
|
if let Ok(image) = result {
|
||||||
|
|
@ -204,20 +214,16 @@ impl State {
|
||||||
return self.allocate_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),
|
Err(e) => self.error = Some(e.into()),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -278,7 +284,26 @@ 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, .. }) => {
|
||||||
Some(Message::KeyPressed(key, modifiers))
|
match key.as_ref() {
|
||||||
|
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))
|
||||||
|
|
|
||||||
|
|
@ -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, String> {
|
pub async fn pick_image() -> Result<PathBuf, Cow<'static, str>> {
|
||||||
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, String> {
|
||||||
Ok(filehandle.path().to_owned())
|
Ok(filehandle.path().to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn pick_save_path() -> Result<PathBuf, String> {
|
pub async fn pick_save_path() -> Result<PathBuf, Cow<'static, str>> {
|
||||||
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());
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue