use crate::{
callback::Callback, config::GridViewConfig, thumbnail_image::ThumbnailImage,
user_action::show_context_menu, utils,
};
use eframe::{
egui::{self, Ui},
epaint::Vec2,
};
use std::path::{Path, PathBuf};
pub struct GridView {
imgs: Vec<ThumbnailImage>,
config: GridViewConfig,
output_profile: String,
selected_image_name: Option<String>,
prev_img_size: f32,
prev_scroll_offset: f32,
total_rows: usize,
images_per_row: usize,
prev_images_per_row: usize,
prev_row_range_start: usize,
reset_scroll: bool,
callback: Option<Callback>,
}
impl GridView {
pub fn new(
image_paths: &[PathBuf],
config: GridViewConfig,
output_profile: &String,
) -> GridView {
let imgs = ThumbnailImage::from_paths(image_paths, output_profile);
let mut mg = GridView {
total_rows: 0,
imgs,
selected_image_name: None,
images_per_row: config.images_per_row,
prev_images_per_row: config.images_per_row,
config,
prev_img_size: 0.,
prev_scroll_offset: 0.,
prev_row_range_start: 0,
output_profile: output_profile.to_owned(),
reset_scroll: false,
callback: None,
};
mg.set_total_rows();
mg
}
pub fn set_images(&mut self, img_paths: &[PathBuf]) {
self.imgs = ThumbnailImage::from_paths(img_paths, &self.output_profile);
self.reset_scroll = true;
self.set_total_rows();
}
pub fn ui(&mut self, ctx: &egui::Context, jump_to_index: &mut Option<usize>) {
self.handle_input(ctx);
egui::CentralPanel::default().show(ctx, |ui| {
ui.spacing_mut().item_spacing = Vec2::new(0., 0.);
ui.set_min_width(ui.available_width());
let mut loading_imgs = self.imgs.iter().filter(|i| i.is_loading()).count();
let mut img_size = ui.available_width() / self.images_per_row as f32;
let prev_img_size = img_size;
if img_size % 6. != 0. {
img_size -= img_size % 6.; }
let remainder = (prev_img_size - img_size) * self.images_per_row as f32;
let mut scroll_area = egui::ScrollArea::vertical().drag_to_scroll(true);
if img_size != self.prev_img_size {
scroll_area = scroll_area.scroll_offset(Vec2 {
x: 0.,
y: img_size * self.prev_scroll_offset / self.prev_img_size,
});
}
if self.images_per_row != self.prev_images_per_row {
let target_row =
(self.prev_row_range_start * self.prev_images_per_row) / self.images_per_row;
scroll_area = scroll_area.scroll_offset(Vec2 {
x: 0.,
y: img_size * target_row as f32,
});
}
if let Some(mut i) = jump_to_index.take() {
i = i - (i % self.images_per_row);
let scroll_offset = ((i as f32) / self.images_per_row as f32) * img_size;
scroll_area = scroll_area.scroll_offset(Vec2 {
x: 0.,
y: scroll_offset,
})
};
if self.reset_scroll {
scroll_area = scroll_area.scroll_offset(Vec2 { x: 0., y: 0. });
self.reset_scroll = false;
}
let scroll_area_response =
scroll_area.show_rows(ui, img_size, self.total_rows, |ui, row_range| {
ui.spacing_mut().item_spacing = Vec2::new(0., 0.);
let preload_from = row_range.start.saturating_sub(self.config.preloaded_rows);
let preload_to = if row_range.end + self.config.preloaded_rows > self.total_rows
{
self.total_rows
} else {
row_range.end + self.config.preloaded_rows
};
for r in row_range.start..row_range.end {
for i in r * self.images_per_row..(r + 1) * self.images_per_row {
self.load_unload_image(
i,
row_range.start,
row_range.end,
&mut loading_imgs,
img_size,
);
}
}
for r in row_range.end..self.total_rows {
for i in r * self.images_per_row..(r + 1) * self.images_per_row {
self.load_unload_image(
i,
preload_from,
preload_to,
&mut loading_imgs,
img_size,
);
}
}
for r in 0..row_range.start {
for i in r * self.images_per_row..(r + 1) * self.images_per_row {
self.load_unload_image(
i,
preload_from,
preload_to,
&mut loading_imgs,
img_size,
);
}
}
for r in row_range.clone() {
ui.horizontal(|ui| {
ui.spacing_mut().item_spacing = Vec2::new(0., 0.);
ui.add_space(remainder / 2.0);
for j in r * self.images_per_row..(r + 1) * self.images_per_row {
if let Some(img) = &mut self.imgs.get_mut(j) {
Self::show_image(
img,
ui,
ctx,
img_size,
&mut self.selected_image_name,
&self.config,
&mut self.callback,
);
}
}
});
}
if !utils::are_inputs_muted(ctx)
&& ui.input_mut(|i| i.consume_shortcut(&self.config.sc_scroll.kbd_shortcut))
{
ui.scroll_with_delta(Vec2::new(0., -(img_size * 0.5)));
}
self.prev_row_range_start = row_range.start;
});
self.prev_scroll_offset = scroll_area_response.state.offset.y;
self.prev_img_size = img_size;
self.prev_images_per_row = self.images_per_row;
});
}
fn load_unload_image(
&mut self,
i: usize,
preload_from: usize,
preload_to: usize,
loading_imgs: &mut usize,
image_size: f32,
) {
let img = &mut match self.imgs.get_mut(i) {
Some(img) => img,
None => return,
};
if i >= preload_from * self.images_per_row && i <= preload_to * self.images_per_row {
if loading_imgs != &self.config.simultaneous_load {
if img.load((image_size * 2.) as u32) {
*loading_imgs += 1;
}
}
} else {
img.unload_delayed();
img.unload(i);
}
}
fn show_image(
image: &mut ThumbnailImage,
ui: &mut Ui,
ctx: &egui::Context,
max_size: f32,
select_image_name: &mut Option<String>,
config: &GridViewConfig,
callback: &mut Option<Callback>,
) {
if let Some(resp) = image.ui(ui, [max_size, max_size]) {
if resp.clicked() {
*select_image_name = Some(image.name.clone());
}
if resp.hovered() {
ctx.set_cursor_icon(egui::CursorIcon::PointingHand);
}
let return_callback = show_context_menu(&config.context_menu, resp, &image.path);
if let Some(return_callback) = return_callback {
*callback = Some(Callback::from_callback(
return_callback,
Some(image.path.clone()),
));
println!("{callback:?}");
}
}
}
pub fn handle_input(&mut self, ctx: &egui::Context) {
if utils::are_inputs_muted(ctx) {
return;
}
if (ctx.input_mut(|i| i.consume_shortcut(&self.config.sc_more_per_row.kbd_shortcut))
|| (ctx.input(|i| i.raw_scroll_delta.y) < 0. && ctx.input(|i| i.zoom_delta() != 1.)))
&& self.images_per_row <= 15
{
self.images_per_row += 1;
self.set_total_rows();
}
if (ctx.input_mut(|i| i.consume_shortcut(&self.config.sc_less_per_row.kbd_shortcut))
|| (ctx.input(|i| i.raw_scroll_delta.y) > 0. && ctx.input(|i| i.zoom_delta() != 1.)))
&& self.images_per_row != 1
{
self.images_per_row -= 1;
self.set_total_rows();
}
}
pub fn selected_image_name(&mut self) -> Option<String> {
self.selected_image_name.take()
}
pub fn set_total_rows(&mut self) {
self.total_rows = (self.imgs.len() as f32 / self.images_per_row as f32).ceil() as usize
}
pub fn pop(&mut self, path: &Path) {
if let Some(pos) = self.imgs.iter().position(|x| x.path == path) {
self.imgs.remove(pos);
self.set_total_rows();
}
}
pub fn take_callback(&mut self) -> Option<Callback> {
self.callback.take()
}
pub fn reload_at(&mut self, path: &Path) {
if let Some(pos) = self.imgs.iter().position(|x| x.path == path) {
if let Some(img) = self.imgs.get_mut(pos) {
img.unload_delayed();
img.unload(pos);
}
}
}
}