feat: lazy load images

this keeps the images loaded, eventually causing an OOM

weird that dropping the handles doesn't clear memory
This commit is contained in:
electria 2026-08-04 14:34:46 -07:00
commit 4cb52996e2
Signed by: electria
SSH key fingerprint: SHA256:8LlB3ucPbBHqozqkhsNbaV5oG3SlzzqUj8FZDL6IPQs

View file

@ -1,18 +1,16 @@
use std::{ use std::{borrow::Cow, collections::HashMap, env, path::PathBuf};
borrow::Cow,
collections::HashMap,
env,
path::{Path, PathBuf},
};
use iced::{Color, Element, Length, Renderer, Task, Theme, application, theme, widget}; use iced::{Color, Element, Renderer, Task, Theme, application, theme, widget};
use ignore::{WalkBuilder, types::TypesBuilder}; use ignore::{WalkBuilder, types::TypesBuilder};
use image::{DynamicImage, EncodableLayout, RgbaImage};
use rayon::iter::{ParallelBridge, ParallelIterator}; use rayon::iter::{ParallelBridge, ParallelIterator};
use crate::SUPPORTED_TYPES; use crate::SUPPORTED_TYPES;
enum Message {} #[derive(Clone, Debug)]
enum Message {
LoadImage(PathBuf),
UnloadImage(PathBuf),
}
#[derive(Default)] #[derive(Default)]
enum Mode { enum Mode {
@ -23,7 +21,7 @@ enum Mode {
#[derive(Default)] #[derive(Default)]
struct State { struct State {
mode: Mode, mode: Mode,
images: HashMap<PathBuf, Option<(RgbaImage, widget::image::Handle)>>, images: HashMap<PathBuf, Option<widget::image::Handle>>,
} }
impl State { impl State {
@ -54,32 +52,40 @@ impl State {
.map(|entry| entry.into_path()) .map(|entry| entry.into_path())
}) })
.filter(|path| path.is_file()) .filter(|path| path.is_file())
.map(|path| { .map(|path| (path, None))
let image = image::open(&path)
.inspect_err(|e| eprintln!("{e}"))
.ok()
.map(DynamicImage::into_rgba8);
let handle = image.as_ref().map(|image| {
widget::image::Handle::from_rgba(image.width(), image.height(), unsafe {
std::mem::transmute::<&[u8], &'static [u8]>(image.as_bytes())
})
});
(path, image.map(|image| (image, handle.unwrap())))
})
.collect(), .collect(),
} }
} }
fn update(&mut self, message: Message) -> Task<Message> { fn update(&mut self, message: Message) -> Task<Message> {
match message {
Message::LoadImage(path) => {
self.images
.entry(path.clone())
.and_modify(|e| *e = Some(widget::image::Handle::from_path(path)));
}
Message::UnloadImage(path) => {
self.images.entry(path).and_modify(|e| *e = None);
}
}
Task::none() Task::none()
} }
fn view(&self) -> Element<'_, Message, Theme, Renderer> { fn view(&self) -> Element<'_, Message, Theme, Renderer> {
widget::scrollable( widget::scrollable(
widget::grid( widget::grid(self.images.iter().map(|(path, opt_handle)| {
self.images opt_handle.as_ref().map_or_else(
.iter() || {
.filter_map(|(_k, v)| v.as_ref()) widget::sensor(widget::space())
.map(|(_image, handle)| widget::image(handle).into()), .on_show(|_size| Message::LoadImage(path.clone()))
.into()
},
|handle| {
widget::sensor(widget::image(handle))
.on_hide(Message::UnloadImage(path.clone()))
.into()
},
) )
}))
.fluid(500) .fluid(500)
.spacing(0), .spacing(0),
) )