Added filters, scroll thumbnails
This commit is contained in:
parent
b41554c608
commit
b00bbbadc0
102
src/app.rs
102
src/app.rs
@ -1,11 +1,11 @@
|
|||||||
use crate::egui_tools::EguiRenderer;
|
use crate::egui_tools::EguiRenderer;
|
||||||
use egui::load::{ImageLoadResult, ImageLoader};
|
use egui::load::{ImageLoadResult, ImageLoader};
|
||||||
use egui::{Align2, Color32, ColorImage, Event, ImageSource, Key, PointerButton};
|
use egui::{Align2, Color32, ColorImage, Event, Image, ImageSource, Key, PointerButton, Sense};
|
||||||
use egui_wgpu::wgpu::SurfaceError;
|
use egui_wgpu::wgpu::SurfaceError;
|
||||||
use egui_wgpu::{ScreenDescriptor, wgpu};
|
use egui_wgpu::{ScreenDescriptor, wgpu};
|
||||||
use image::metadata::Orientation;
|
use image::metadata::Orientation;
|
||||||
use imflow::image::swap_wh;
|
use imflow::image::{ImageFormat, swap_wh};
|
||||||
use imflow::store::ImageStore;
|
use imflow::store::{FileFilters, ImageStore};
|
||||||
use std::cmp::{max, min};
|
use std::cmp::{max, min};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@ -223,6 +223,7 @@ pub struct AppState {
|
|||||||
pub render_pipeline: wgpu::RenderPipeline,
|
pub render_pipeline: wgpu::RenderPipeline,
|
||||||
pub transform_buffer: wgpu::Buffer,
|
pub transform_buffer: wgpu::Buffer,
|
||||||
pub transform_data: TransformData,
|
pub transform_data: TransformData,
|
||||||
|
pub filters: FileFilters,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
@ -315,6 +316,7 @@ impl AppState {
|
|||||||
render_pipeline,
|
render_pipeline,
|
||||||
transform_buffer,
|
transform_buffer,
|
||||||
transform_data,
|
transform_data,
|
||||||
|
filters: FileFilters::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -629,18 +631,23 @@ impl App {
|
|||||||
render_pass.draw_indexed(0..6, 0, 0..1);
|
render_pass.draw_indexed(0..6, 0, 0..1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut rating_filter = [false; 6];
|
||||||
|
|
||||||
|
// let mut file_filters;
|
||||||
let rating;
|
let rating;
|
||||||
let path;
|
let path;
|
||||||
let current_id;
|
let current_id;
|
||||||
let image_count;
|
let image_count;
|
||||||
let filename;
|
let filename;
|
||||||
let window;
|
let window;
|
||||||
|
let filtered_images;
|
||||||
{
|
{
|
||||||
let store = state.store.read().unwrap();
|
let store = state.store.read().unwrap();
|
||||||
rating = store.get_current_rating();
|
rating = store.get_current_rating();
|
||||||
path = store.current_image_path.clone();
|
path = store.current_image_path.clone();
|
||||||
current_id = store.current_image_id;
|
current_id = store.current_image_id;
|
||||||
image_count = store.available_images.len();
|
image_count = store.available_images.len();
|
||||||
|
filtered_images = store.get_filtered_images(&state.filters);
|
||||||
filename = path.path.file_name().unwrap();
|
filename = path.path.file_name().unwrap();
|
||||||
window = self.window.as_ref().unwrap();
|
window = self.window.as_ref().unwrap();
|
||||||
}
|
}
|
||||||
@ -665,59 +672,45 @@ impl App {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
egui::Window::new("Id")
|
|
||||||
.collapsible(false)
|
egui::TopBottomPanel::bottom("Thumbnails")
|
||||||
.resizable(false)
|
.exact_height(120.0)
|
||||||
.default_width(5.0)
|
.show(state.egui_renderer.context(), |panel_ui| {
|
||||||
.anchor(Align2::RIGHT_TOP, [-5.0, 5.0])
|
egui::ScrollArea::horizontal().show(panel_ui, |ui| {
|
||||||
.pivot(Align2::RIGHT_TOP)
|
ui.horizontal_centered(|horizontal| {
|
||||||
.show(state.egui_renderer.context(), |ui| {
|
for image in filtered_images {
|
||||||
ui.vertical_centered(|ui| {
|
let source = ImageSource::Bytes {
|
||||||
ui.label(
|
uri: std::borrow::Cow::Owned(image.get_hash_str()),
|
||||||
egui::RichText::new(format!("{}/{}", current_id, image_count))
|
bytes: egui::load::Bytes::Static(&[]),
|
||||||
.size(22.0)
|
};
|
||||||
.strong(),
|
|
||||||
);
|
let image_widget = horizontal.add(
|
||||||
|
egui::Image::new(source)
|
||||||
|
.fit_to_original_size(0.8)
|
||||||
|
.corner_radius(10)
|
||||||
|
.sense(Sense::click()),
|
||||||
|
);
|
||||||
|
if image_widget.clicked() {
|
||||||
|
image_widget.scroll_to_me(None);
|
||||||
|
// ui.scroll_to_rect(image_widget.rect, None);
|
||||||
|
println!("{}", image.get_hash_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
egui::Window::new("Images")
|
egui::SidePanel::right("Filters").show(state.egui_renderer.context(), |ui| {
|
||||||
.collapsible(false)
|
for (i, mut rating) in state.filters.rating.iter_mut().enumerate().rev() {
|
||||||
.resizable(false)
|
ui.checkbox(&mut rating, format!("{} stars", i));
|
||||||
.default_width(500.0)
|
}
|
||||||
.default_height(300.0)
|
|
||||||
.anchor(Align2::CENTER_BOTTOM, [0.0, 10.0])
|
|
||||||
.pivot(Align2::CENTER_BOTTOM)
|
|
||||||
.show(state.egui_renderer.context(), |ui| {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
// ui.label(
|
|
||||||
// egui::RichText::new(format!("{}/{}", current_id, image_count))
|
|
||||||
// .size(22.0)
|
|
||||||
// .strong(),
|
|
||||||
// );
|
|
||||||
|
|
||||||
const NUM: i32 = 5;
|
ui.text_edit_singleline(&mut state.filters.name);
|
||||||
for i in max((current_id as i32) - NUM, 0)
|
|
||||||
..min((current_id as i32) + NUM + 1, image_count as i32)
|
|
||||||
{
|
|
||||||
let source = ImageSource::Bytes {
|
|
||||||
uri: std::borrow::Cow::Owned(i.to_string()),
|
|
||||||
bytes: egui::load::Bytes::Static(&[]),
|
|
||||||
};
|
|
||||||
|
|
||||||
ui.add(
|
for (format, mut value) in state.filters.file_format.iter_mut() {
|
||||||
egui::Image::new(source)
|
ui.checkbox(&mut value, format!("{}", format));
|
||||||
// .sca
|
}
|
||||||
// .load_for_size(ctx, available_size)
|
});
|
||||||
// .fit_to_fraction(Vec2::new(10.0, 10.0))
|
|
||||||
// .max_width(200.0)
|
|
||||||
.fit_to_original_size(1.0)
|
|
||||||
.corner_radius(10),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// ui.image(source);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
state.egui_renderer.end_frame_and_draw(
|
state.egui_renderer.end_frame_and_draw(
|
||||||
&state.device,
|
&state.device,
|
||||||
@ -845,7 +838,7 @@ impl ApplicationHandler for App {
|
|||||||
pub struct ImflowEguiLoader {
|
pub struct ImflowEguiLoader {
|
||||||
store: Arc<RwLock<ImageStore>>,
|
store: Arc<RwLock<ImageStore>>,
|
||||||
// stored: Option<ImageLoadResult>,
|
// stored: Option<ImageLoadResult>,
|
||||||
cache: egui::mutex::Mutex<HashMap<usize, ImageLoadResult>>,
|
cache: egui::mutex::Mutex<HashMap<String, ImageLoadResult>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImflowEguiLoader {
|
impl ImflowEguiLoader {
|
||||||
@ -870,13 +863,14 @@ impl ImageLoader for ImflowEguiLoader {
|
|||||||
) -> egui::load::ImageLoadResult {
|
) -> egui::load::ImageLoadResult {
|
||||||
let mut cache = self.cache.lock();
|
let mut cache = self.cache.lock();
|
||||||
|
|
||||||
let id = uri.parse::<usize>().unwrap();
|
// let id = uri.parse::<usize>().unwrap();
|
||||||
|
let id = uri.to_string();
|
||||||
if let Some(handle) = cache.get(&id) {
|
if let Some(handle) = cache.get(&id) {
|
||||||
handle.clone()
|
handle.clone()
|
||||||
} else {
|
} else {
|
||||||
let imbuf = {
|
let imbuf = {
|
||||||
let binding = self.store.read().unwrap();
|
let binding = self.store.read().unwrap();
|
||||||
binding.get_thumbnail_id(id).clone()
|
binding.get_thumbnail_hash(id.clone()).clone()
|
||||||
};
|
};
|
||||||
let mut image = ColorImage::new([imbuf.width, imbuf.height], Color32::BLACK);
|
let mut image = ColorImage::new([imbuf.width, imbuf.height], Color32::BLACK);
|
||||||
let image_buffer = image.as_raw_mut();
|
let image_buffer = image.as_raw_mut();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use egui::Context;
|
use egui::{Color32, Context, Visuals};
|
||||||
use egui_wgpu::wgpu::{CommandEncoder, Device, Queue, StoreOp, TextureFormat, TextureView};
|
use egui_wgpu::wgpu::{CommandEncoder, Device, Queue, StoreOp, TextureFormat, TextureView};
|
||||||
use egui_wgpu::{Renderer, ScreenDescriptor, wgpu};
|
use egui_wgpu::{Renderer, ScreenDescriptor, wgpu};
|
||||||
use egui_winit::State;
|
use egui_winit::State;
|
||||||
@ -24,6 +24,14 @@ impl EguiRenderer {
|
|||||||
window: &Window,
|
window: &Window,
|
||||||
) -> EguiRenderer {
|
) -> EguiRenderer {
|
||||||
let egui_context = Context::default();
|
let egui_context = Context::default();
|
||||||
|
egui_context.options_mut(|o| o.line_scroll_speed = 200.0);
|
||||||
|
egui_context.set_visuals_of(
|
||||||
|
egui::Theme::Dark,
|
||||||
|
Visuals {
|
||||||
|
panel_fill: Color32::BLACK,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let egui_state = egui_winit::State::new(
|
let egui_state = egui_winit::State::new(
|
||||||
egui_context,
|
egui_context,
|
||||||
|
19
src/image.rs
19
src/image.rs
@ -18,6 +18,8 @@ use zune_image::codecs::qoi::zune_core::colorspace::ColorSpace;
|
|||||||
use zune_image::codecs::qoi::zune_core::options::DecoderOptions;
|
use zune_image::codecs::qoi::zune_core::options::DecoderOptions;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::fmt::Display;
|
||||||
|
// use std::fmt::Write;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::fs::read;
|
use std::fs::read;
|
||||||
@ -37,6 +39,16 @@ pub enum ImageFormat {
|
|||||||
Heif,
|
Heif,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for ImageFormat {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
ImageFormat::Jpg => f.write_str("JPG"),
|
||||||
|
ImageFormat::Jxl => f.write_str("JXL"),
|
||||||
|
ImageFormat::Heif => f.write_str("HEIF"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ImageData {
|
pub struct ImageData {
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
@ -44,6 +56,7 @@ pub struct ImageData {
|
|||||||
pub embedded_thumbnail: bool,
|
pub embedded_thumbnail: bool,
|
||||||
pub orientation: Orientation,
|
pub orientation: Orientation,
|
||||||
pub hash: GenericArray<u8, U32>,
|
pub hash: GenericArray<u8, U32>,
|
||||||
|
pub rating: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImageData {
|
impl ImageData {
|
||||||
@ -56,6 +69,10 @@ impl ImageData {
|
|||||||
let hash_hex = format!("{:x}", self.hash);
|
let hash_hex = format!("{:x}", self.hash);
|
||||||
return cache_dir.join(hash_hex).to_path_buf();
|
return cache_dir.join(hash_hex).to_path_buf();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_hash_str(&self) -> String {
|
||||||
|
format!("{:x}", self.hash)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for ImageData {
|
impl Hash for ImageData {
|
||||||
@ -253,12 +270,14 @@ pub fn load_available_images(dir: PathBuf) -> Vec<ImageData> {
|
|||||||
let orientation = Orientation::from_exif(meta.get_orientation() as u8)
|
let orientation = Orientation::from_exif(meta.get_orientation() as u8)
|
||||||
.unwrap_or(Orientation::NoTransforms);
|
.unwrap_or(Orientation::NoTransforms);
|
||||||
let hash = get_file_hash(&path);
|
let hash = get_file_hash(&path);
|
||||||
|
let rating = meta.get_tag_numeric("Xmp.xmp.Rating");
|
||||||
Some(ImageData {
|
Some(ImageData {
|
||||||
path,
|
path,
|
||||||
format,
|
format,
|
||||||
embedded_thumbnail,
|
embedded_thumbnail,
|
||||||
orientation,
|
orientation,
|
||||||
hash,
|
hash,
|
||||||
|
rating,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
47
src/store.rs
47
src/store.rs
@ -1,4 +1,4 @@
|
|||||||
use crate::image::{ImageData, load_thumbnail};
|
use crate::image::{ImageData, ImageFormat, load_thumbnail};
|
||||||
use crate::image::{ImflowImageBuffer, load_available_images, load_image};
|
use crate::image::{ImflowImageBuffer, load_available_images, load_image};
|
||||||
use crossbeam_channel::{Receiver, Sender, unbounded};
|
use crossbeam_channel::{Receiver, Sender, unbounded};
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
@ -11,6 +11,26 @@ use threadpool::ThreadPool;
|
|||||||
|
|
||||||
const PRELOAD_NEXT_IMAGE_N: usize = 16;
|
const PRELOAD_NEXT_IMAGE_N: usize = 16;
|
||||||
|
|
||||||
|
pub struct FileFilters {
|
||||||
|
pub rating: [bool; 6],
|
||||||
|
pub name: String,
|
||||||
|
pub file_format: HashMap<ImageFormat, bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FileFilters {
|
||||||
|
fn default() -> Self {
|
||||||
|
let mut formats = HashMap::new();
|
||||||
|
formats.insert(ImageFormat::Jpg, true);
|
||||||
|
formats.insert(ImageFormat::Jxl, true);
|
||||||
|
formats.insert(ImageFormat::Heif, true);
|
||||||
|
FileFilters {
|
||||||
|
rating: [true; 6],
|
||||||
|
name: "".to_string(),
|
||||||
|
file_format: formats,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ImageStore {
|
pub struct ImageStore {
|
||||||
pub current_image_id: usize,
|
pub current_image_id: usize,
|
||||||
pub(crate) loaded_images: HashMap<ImageData, ImflowImageBuffer>,
|
pub(crate) loaded_images: HashMap<ImageData, ImflowImageBuffer>,
|
||||||
@ -176,6 +196,14 @@ impl ImageStore {
|
|||||||
self.loaded_images_thumbnails.get(path).unwrap()
|
self.loaded_images_thumbnails.get(path).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_thumbnail_hash(&self, hash: String) -> &ImflowImageBuffer {
|
||||||
|
self.loaded_images_thumbnails
|
||||||
|
.iter()
|
||||||
|
.find(|f| f.0.get_hash_str() == hash)
|
||||||
|
.unwrap()
|
||||||
|
.1
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_thumbnail(&mut self) -> &ImflowImageBuffer {
|
pub fn get_thumbnail(&mut self) -> &ImflowImageBuffer {
|
||||||
if self
|
if self
|
||||||
.loaded_images_thumbnails
|
.loaded_images_thumbnails
|
||||||
@ -196,4 +224,21 @@ impl ImageStore {
|
|||||||
.get(&self.current_image_path)
|
.get(&self.current_image_path)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_filtered_images(&self, filter: &FileFilters) -> Vec<ImageData> {
|
||||||
|
self.available_images
|
||||||
|
.iter()
|
||||||
|
.filter(|f| filter.rating[f.rating.clamp(0, 5) as usize])
|
||||||
|
.filter(|f| filter.file_format[&f.format])
|
||||||
|
.filter(|f| {
|
||||||
|
f.path
|
||||||
|
.file_name()
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
.contains(&filter.name)
|
||||||
|
})
|
||||||
|
.map(|f| f.clone())
|
||||||
|
.collect::<Vec<ImageData>>()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user