From f279c3aa15fc700ecae5ff28afe50c9562fe35f9 Mon Sep 17 00:00:00 2001 From: electria Date: Thu, 30 Jul 2026 19:19:23 -0700 Subject: [PATCH 1/5] feat: f to switch FilterMethod useful to scale pixelart images nicely. This makes me consider expanding the status bar to include info; partially because I don't use window decorations usually (so I don't see the info in the title) --- src/main.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main.rs b/src/main.rs index 18d35a0..9ff87f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,6 +20,7 @@ enum Message { struct State { image: Option, image_display: Option, + image_filter: widget::image::FilterMethod, error: Option, } @@ -40,6 +41,7 @@ impl State { widget::column([ if let Some(allocation) = self.image_display.as_ref() { widget::image::viewer(allocation.handle().clone()) + .filter_method(self.image_filter) .width(Length::Fill) .height(Length::Fill) .into() @@ -107,6 +109,12 @@ impl State { } }; } + 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("s") => { self.save_image(); From 97ce6754ddf870ff6701d22dcb468421549f22e9 Mon Sep 17 00:00:00 2001 From: electria Date: Thu, 30 Jul 2026 19:21:12 -0700 Subject: [PATCH 2/5] feat: change scale limits zooming in a lot is sometimes useful, but zooming out is basically useless and annoying (when you want to put it back to covering the full window) if you really need to zoom out, you can always resize the window --- src/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main.rs b/src/main.rs index 9ff87f7..7a4cba4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,6 +42,8 @@ impl State { if let Some(allocation) = self.image_display.as_ref() { widget::image::viewer(allocation.handle().clone()) .filter_method(self.image_filter) + .max_scale(50.) + .min_scale(1.) .width(Length::Fill) .height(Length::Fill) .into() From 7ce87e2870fb907331551ceeea0265bcbb5fd1ae Mon Sep 17 00:00:00 2001 From: electria Date: Thu, 30 Jul 2026 20:17:21 -0700 Subject: [PATCH 3/5] feat: optional infobar literally the title; except on the bottom --- src/main.rs | 76 +++++++++++++++++++++++++++++++++------------------- src/utils.rs | 8 ++++++ 2 files changed, 56 insertions(+), 28 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7a4cba4..522e8c7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use std::{env, path::Path}; +use std::{borrow::Cow, env, ffi::OsStr, path::Path}; use iced::{ Alignment, Color, Element, Length, Renderer, Subscription, Task, Theme, color, event, @@ -23,6 +23,8 @@ struct State { image_filter: widget::image::FilterMethod, error: Option, + info: Option, + infobar_shown: bool, } impl State { fn new() -> (Self, Task) { @@ -38,30 +40,33 @@ impl State { (state, allocate_image) } fn view(&self) -> Element<'_, Message, Theme, Renderer> { - widget::column([ - if let Some(allocation) = self.image_display.as_ref() { - widget::image::viewer(allocation.handle().clone()) - .filter_method(self.image_filter) - .max_scale(50.) - .min_scale(1.) - .width(Length::Fill) - .height(Length::Fill) - .into() - } else { - widget::container(widget::text(include_str!("usage.txt"))) - .height(Length::Fill) - .width(Length::Fill) - .align_x(Alignment::Center) - .align_y(Alignment::Center) - .into() - }, - if let Some(error) = self.error.as_ref() { - widget::text(error).style(widget::text::danger).into() - } else { - widget::space().into() - }, - ]) - .into() + let mut main = Vec::new(); + + main.push(if let Some(allocation) = self.image_display.as_ref() { + widget::image::viewer(allocation.handle().clone()) + .filter_method(self.image_filter) + .max_scale(50.) + .min_scale(1.) + .width(Length::Fill) + .height(Length::Fill) + .into() + } else { + widget::container(widget::text(include_str!("usage.txt"))) + .height(Length::Fill) + .width(Length::Fill) + .align_x(Alignment::Center) + .align_y(Alignment::Center) + .into() + }); + + if let Some(error) = self.error.as_ref() { + main.push(widget::text(error).style(widget::text::danger).into()); + } + if self.infobar_shown && self.info.is_some() { + main.push(widget::text(self.title()).into()); + } + + widget::column(main).into() } fn update(&mut self, message: Message) -> Task { match message { @@ -117,6 +122,9 @@ impl State { widget::image::FilterMethod::Nearest => widget::image::FilterMethod::Linear, } } + Key::Character("b") => { + self.infobar_shown = !self.infobar_shown; + } Key::Character("s") => { self.save_image(); @@ -146,7 +154,16 @@ impl State { } fn load_image(&mut self, path: impl AsRef) -> Result, String> { - utils::load_image(path).map(|image| { + utils::load_image(&path).map(|image| { + self.info = Some(format!( + "{} {}x{}", + path.as_ref() + .file_name() + .map(OsStr::to_string_lossy) + .unwrap_or(Cow::Borrowed("[no file]")), + image.width(), + image.height(), + )); self.image = Some(image); self.allocate_image() }) @@ -177,8 +194,11 @@ impl State { event::listen().map(Message::Event) } fn title(&self) -> String { - match self.image.as_ref() { - Some(image) => format!("imagey {}x{}", image.width(), image.height()), + match self.info.as_ref() { + Some(info) => format!( + "imagey {info} {}", + utils::string_from_filter_type(self.image_filter), + ), None => "imagey".into(), } } diff --git a/src/utils.rs b/src/utils.rs index 1477a4e..cadc05a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,6 @@ use std::path::{Path, PathBuf}; +use iced::widget; use image::{DynamicImage, ImageDecoder, ImageReader, RgbaImage}; use rfd::FileDialog; @@ -79,3 +80,10 @@ pub fn save_image(image: Option<&RgbaImage>) -> Result<(), String> { Ok(()) } + +pub fn string_from_filter_type(f: widget::image::FilterMethod) -> &'static str { + match f { + widget::image::FilterMethod::Linear => "bilinear", + widget::image::FilterMethod::Nearest => "nearest neighbor", + } +} From 9bdd556c9df9e4fd3b5f8d9f858603dafcd42795 Mon Sep 17 00:00:00 2001 From: electria Date: Thu, 30 Jul 2026 20:26:21 -0700 Subject: [PATCH 4/5] refactor: use newer helper method in State::new this should keep it from printing "no image to allocate" each time when started without an argument --- src/main.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index 522e8c7..4de72f4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,14 +30,14 @@ impl State { fn new() -> (Self, Task) { let mut state = State::default(); - state.error = env::args().nth(1).and_then(|path| { - utils::load_image(path) - .map(|image| state.image = Some(image)) - .err() - }); - let allocate_image = state.allocate_image(); + if let Some(path) = env::args().nth(1) { + match state.load_image(path) { + Err(e) => state.error = Some(e), + Ok(task) => return (state, task), + } + } - (state, allocate_image) + (state, Task::none()) } fn view(&self) -> Element<'_, Message, Theme, Renderer> { let mut main = Vec::new(); From b1157474c9bd9d5a8ac4b7b0c4832d50f3be924a Mon Sep 17 00:00:00 2001 From: electria Date: Thu, 30 Jul 2026 20:35:45 -0700 Subject: [PATCH 5/5] feat: simplify theme --- src/main.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 4de72f4..4168550 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use std::{borrow::Cow, env, ffi::OsStr, path::Path}; use iced::{ - Alignment, Color, Element, Length, Renderer, Subscription, Task, Theme, color, event, + Alignment, Color, Element, Length, Renderer, Subscription, Task, Theme, event, keyboard::{self, Key, key}, theme, widget, window, }; @@ -213,12 +213,9 @@ fn main() -> Result<(), iced::Error> { .theme(Theme::custom( "custom", theme::Palette { - background: color!(0x080808), + background: Color::BLACK, text: Color::WHITE, - primary: color!(0xff00ff), - success: color!(0x00ff00), - warning: color!(0x880000), - danger: color!(0xff0000), + ..theme::Palette::DARK }, )) .run()