feat: usage info on start

This commit is contained in:
electria 2026-07-01 08:56:03 -07:00
commit ab8dc257e9
Signed by: electria
SSH key fingerprint: SHA256:8LlB3ucPbBHqozqkhsNbaV5oG3SlzzqUj8FZDL6IPQs
2 changed files with 65 additions and 46 deletions

View file

@ -1,13 +1,11 @@
use std::{env, ffi::OsStr, fs}; use std::{env, ffi::OsStr, fs};
use iced::{ use iced::{
Color, Element, Length, Renderer, Subscription, Task, Theme, color, event, Alignment, Color, Element, Length, Renderer, Subscription, Task, Theme, color, event,
keyboard::{self, Key, key}, keyboard::{self, Key, key},
theme, widget, window, theme, widget, window,
}; };
use image::{ use image::{EncodableLayout, ImageReader, RgbaImage, codecs::webp::WebPEncoder, imageops};
EncodableLayout, ImageReader, Pixel, Rgba, RgbaImage, codecs::webp::WebPEncoder, imageops,
};
use rfd::FileDialog; use rfd::FileDialog;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -19,32 +17,22 @@ enum Message {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct State { struct State {
image: RgbaImage, image: Option<RgbaImage>,
image_display: Option<widget::image::Allocation>, image_display: Option<widget::image::Allocation>,
} }
impl State { impl State {
fn new() -> (Self, Task<Message>) { fn new() -> (Self, Task<Message>) {
let image = if let Some(path) = env::args().nth(1) { let state = State {
image_display: None,
image: env::args().nth(1).and_then(|path| {
Some(
ImageReader::open(path) ImageReader::open(path)
.unwrap() .unwrap()
.decode() .decode()
.unwrap() .unwrap()
.into_rgba8() .into_rgba8(),
} else { )
let mut image = RgbaImage::new(100, 100); }),
imageops::horizontal_gradient(
&mut image,
Rgba::from_slice(&[0, 0, 0, 0]),
Rgba::from_slice(&[255, 255, 255, 255]),
);
image
};
let state = State {
image_display: None,
image,
}; };
let allocate_image = state.allocate_image(); let allocate_image = state.allocate_image();
@ -57,7 +45,12 @@ impl State {
.height(Length::Fill) .height(Length::Fill)
.into() .into()
} else { } else {
widget::space().into() widget::container(widget::text(include_str!("usage.txt")))
.height(Length::Fill)
.width(Length::Fill)
.align_x(Alignment::Center)
.align_y(Alignment::Center)
.into()
} }
} }
fn update(&mut self, message: Message) -> Task<Message> { fn update(&mut self, message: Message) -> Task<Message> {
@ -80,17 +73,6 @@ impl State {
} }
} }
Key::Character("q") => return window::latest().and_then(window::close),
Key::Character("r") => {
self.image = imageops::rotate90(&self.image);
return self.allocate_image();
}
Key::Character("i") => {
imageops::invert(&mut self.image);
return self.allocate_image();
}
Key::Character("o") => { Key::Character("o") => {
let Some(path) = FileDialog::new() let Some(path) = FileDialog::new()
.add_filter( .add_filter(
@ -106,15 +88,40 @@ impl State {
return Task::none(); return Task::none();
}; };
self.image = ImageReader::open(path) self.image = Some(
ImageReader::open(path)
.unwrap() .unwrap()
.decode() .decode()
.unwrap() .unwrap()
.into_rgba8(); .into_rgba8(),
);
return self.allocate_image(); return self.allocate_image();
} }
Key::Character("r") => {
let Some(image) = self.image.as_ref() else {
eprintln!("no image to rotate");
return Task::none();
};
self.image = Some(imageops::rotate90(image));
return self.allocate_image();
}
Key::Character("i") => {
let Some(image) = self.image.as_mut() else {
eprintln!("no image to invert");
return Task::none();
};
imageops::invert(image);
return self.allocate_image();
}
Key::Character("s") => { Key::Character("s") => {
let Some(image) = self.image.as_ref() else {
eprintln!("no image to save");
return Task::none();
};
let Some(path) = FileDialog::new().save_file() else { let Some(path) = FileDialog::new().save_file() else {
eprintln!("no path to save provided"); eprintln!("no path to save provided");
return Task::none(); return Task::none();
@ -134,15 +141,17 @@ impl State {
}; };
let result = if let Some(encoder) = maybe_encoder { let result = if let Some(encoder) = maybe_encoder {
self.image.write_with_encoder(encoder) image.write_with_encoder(encoder)
} else { } else {
self.image.save(path) image.save(path)
}; };
#[allow(unused_must_use)] #[allow(unused_must_use)]
result.inspect_err(|e| eprintln!("failed to save image: {e}")); result.inspect_err(|e| eprintln!("failed to save image: {e}"));
} }
Key::Character("q") => return window::latest().and_then(window::close),
// ignore unused keys // ignore unused keys
_ => {} _ => {}
}, },
@ -155,10 +164,15 @@ impl State {
} }
fn allocate_image(&self) -> Task<Message> { fn allocate_image(&self) -> Task<Message> {
let Some(image) = self.image.as_ref() else {
eprintln!("no image to allocate");
return Task::none();
};
widget::image::allocate(widget::image::Handle::from_rgba( widget::image::allocate(widget::image::Handle::from_rgba(
self.image.width(), image.width(),
self.image.height(), image.height(),
unsafe { std::mem::transmute::<_, &'static [u8]>(self.image.as_bytes()) }, unsafe { std::mem::transmute::<_, &'static [u8]>(image.as_bytes()) },
)) ))
.map(Message::ImageDisplayReady) .map(Message::ImageDisplayReady)
} }

5
src/usage.txt Normal file
View file

@ -0,0 +1,5 @@
"o" to open a new image
"r" to rotate
"i" to invert colors
"s" to save the image
"q" to quit