1use crate::{
2 callback::Callback, config::GridViewConfig, thumbnail_image::ThumbnailImage,
3 user_action::show_context_menu, utils,
4};
5use eframe::{
6 egui::{self, Ui},
7 epaint::Vec2,
8};
9use std::path::{Path, PathBuf};
10
11pub struct GridView {
12 imgs: Vec<ThumbnailImage>,
13 config: GridViewConfig,
14 output_profile: String,
15 selected_image_name: Option<String>,
16 prev_img_size: f32,
17 prev_scroll_offset: f32,
18 total_rows: usize,
19 images_per_row: usize,
20 prev_images_per_row: usize,
21 prev_row_range_start: usize,
22 reset_scroll: bool,
23 callback: Option<Callback>,
24}
25
26impl GridView {
27 pub fn new(
28 image_paths: &[PathBuf],
29 config: GridViewConfig,
30 output_profile: &String,
31 ) -> GridView {
32 let imgs = ThumbnailImage::from_paths(image_paths, output_profile);
33 let mut mg = GridView {
34 total_rows: 0,
35 imgs,
36 selected_image_name: None,
37 images_per_row: config.images_per_row,
38 prev_images_per_row: config.images_per_row,
39 config,
40 prev_img_size: 0.,
41 prev_scroll_offset: 0.,
42 prev_row_range_start: 0,
43 output_profile: output_profile.to_owned(),
44 reset_scroll: false,
45 callback: None,
46 };
47
48 mg.set_total_rows();
49
50 mg
51 }
52
53 pub fn set_images(&mut self, img_paths: &[PathBuf]) {
54 self.imgs = ThumbnailImage::from_paths(img_paths, &self.output_profile);
55 self.reset_scroll = true;
56 self.set_total_rows();
57 }
58
59 pub fn ui(&mut self, ctx: &egui::Context, jump_to_index: &mut Option<usize>) {
60 self.handle_input(ctx);
61
62 egui::CentralPanel::default().show(ctx, |ui| {
63 ui.spacing_mut().item_spacing = Vec2::new(0., 0.);
64 ui.set_min_width(ui.available_width());
65
66 let mut loading_imgs = self.imgs.iter().filter(|i| i.is_loading()).count();
67 let mut img_size = ui.available_width() / self.images_per_row as f32;
68 let prev_img_size = img_size;
69
70 if img_size % 6. != 0. {
71 img_size -= img_size % 6.; }
73
74 let remainder = (prev_img_size - img_size) * self.images_per_row as f32;
75
76 let mut scroll_area = egui::ScrollArea::vertical().drag_to_scroll(true);
77
78 if img_size != self.prev_img_size {
81 scroll_area = scroll_area.scroll_offset(Vec2 {
82 x: 0.,
83 y: img_size * self.prev_scroll_offset / self.prev_img_size,
84 });
85 }
86
87 if self.images_per_row != self.prev_images_per_row {
88 let target_row =
89 (self.prev_row_range_start * self.prev_images_per_row) / self.images_per_row;
90
91 scroll_area = scroll_area.scroll_offset(Vec2 {
92 x: 0.,
93 y: img_size * target_row as f32,
94 });
95 }
96
97 if let Some(mut i) = jump_to_index.take() {
98 i = i - (i % self.images_per_row);
100 let scroll_offset = ((i as f32) / self.images_per_row as f32) * img_size;
101 scroll_area = scroll_area.scroll_offset(Vec2 {
102 x: 0.,
103 y: scroll_offset,
104 })
105 };
106
107 if self.reset_scroll {
108 scroll_area = scroll_area.scroll_offset(Vec2 { x: 0., y: 0. });
109 self.reset_scroll = false;
110 }
111
112 let scroll_area_response =
113 scroll_area.show_rows(ui, img_size, self.total_rows, |ui, row_range| {
114 ui.spacing_mut().item_spacing = Vec2::new(0., 0.);
115
116 let preload_from = row_range.start.saturating_sub(self.config.preloaded_rows);
117
118 let preload_to = if row_range.end + self.config.preloaded_rows > self.total_rows
119 {
120 self.total_rows
121 } else {
122 row_range.end + self.config.preloaded_rows
123 };
124
125 for r in row_range.start..row_range.end {
127 for i in r * self.images_per_row..(r + 1) * self.images_per_row {
128 self.load_unload_image(
129 i,
130 row_range.start,
131 row_range.end,
132 &mut loading_imgs,
133 img_size,
134 );
135 }
136 }
137
138 for r in row_range.end..self.total_rows {
140 for i in r * self.images_per_row..(r + 1) * self.images_per_row {
141 self.load_unload_image(
142 i,
143 preload_from,
144 preload_to,
145 &mut loading_imgs,
146 img_size,
147 );
148 }
149 }
150
151 for r in 0..row_range.start {
153 for i in r * self.images_per_row..(r + 1) * self.images_per_row {
154 self.load_unload_image(
155 i,
156 preload_from,
157 preload_to,
158 &mut loading_imgs,
159 img_size,
160 );
161 }
162 }
163
164 for r in row_range.clone() {
165 ui.horizontal(|ui| {
166 ui.spacing_mut().item_spacing = Vec2::new(0., 0.);
167 ui.add_space(remainder / 2.0);
168
169 for j in r * self.images_per_row..(r + 1) * self.images_per_row {
170 if let Some(img) = &mut self.imgs.get_mut(j) {
171 Self::show_image(
172 img,
173 ui,
174 ctx,
175 img_size,
176 &mut self.selected_image_name,
177 &self.config,
178 &mut self.callback,
179 );
180 }
181 }
182 });
183 }
184
185 if !utils::are_inputs_muted(ctx)
186 && ui.input_mut(|i| i.consume_shortcut(&self.config.sc_scroll.kbd_shortcut))
187 {
188 ui.scroll_with_delta(Vec2::new(0., -(img_size * 0.5)));
189 }
190
191 self.prev_row_range_start = row_range.start;
192 });
193
194 self.prev_scroll_offset = scroll_area_response.state.offset.y;
195 self.prev_img_size = img_size;
196 self.prev_images_per_row = self.images_per_row;
197 });
198 }
199
200 fn load_unload_image(
201 &mut self,
202 i: usize,
203 preload_from: usize,
204 preload_to: usize,
205 loading_imgs: &mut usize,
206 image_size: f32,
207 ) {
208 let img = &mut match self.imgs.get_mut(i) {
209 Some(img) => img,
210 None => return,
211 };
212
213 if i >= preload_from * self.images_per_row && i <= preload_to * self.images_per_row {
214 if loading_imgs != &self.config.simultaneous_load {
215 if img.load((image_size * 2.) as u32) {
218 *loading_imgs += 1;
219 }
220 }
221 } else {
222 img.unload_delayed();
223 img.unload(i);
224 }
225 }
226
227 fn show_image(
228 image: &mut ThumbnailImage,
229 ui: &mut Ui,
230 ctx: &egui::Context,
231 max_size: f32,
232 select_image_name: &mut Option<String>,
233 config: &GridViewConfig,
234 callback: &mut Option<Callback>,
235 ) {
236 if let Some(resp) = image.ui(ui, [max_size, max_size]) {
237 if resp.clicked() {
238 *select_image_name = Some(image.name.clone());
239 }
240 if resp.hovered() {
241 ctx.set_cursor_icon(egui::CursorIcon::PointingHand);
242 }
243
244 let return_callback = show_context_menu(&config.context_menu, resp, &image.path);
245
246 if let Some(return_callback) = return_callback {
247 *callback = Some(Callback::from_callback(
248 return_callback,
249 Some(image.path.clone()),
250 ));
251
252 println!("{callback:?}");
253 }
254 }
255 }
256
257 pub fn handle_input(&mut self, ctx: &egui::Context) {
258 if utils::are_inputs_muted(ctx) {
259 return;
260 }
261
262 if (ctx.input_mut(|i| i.consume_shortcut(&self.config.sc_more_per_row.kbd_shortcut))
263 || (ctx.input(|i| i.raw_scroll_delta.y) < 0. && ctx.input(|i| i.zoom_delta() != 1.)))
264 && self.images_per_row <= 15
265 {
266 self.images_per_row += 1;
267 self.set_total_rows();
268 }
269
270 if (ctx.input_mut(|i| i.consume_shortcut(&self.config.sc_less_per_row.kbd_shortcut))
271 || (ctx.input(|i| i.raw_scroll_delta.y) > 0. && ctx.input(|i| i.zoom_delta() != 1.)))
272 && self.images_per_row != 1
273 {
274 self.images_per_row -= 1;
275 self.set_total_rows();
276 }
277 }
278
279 pub fn selected_image_name(&mut self) -> Option<String> {
280 self.selected_image_name.take()
282 }
283
284 pub fn set_total_rows(&mut self) {
285 self.total_rows = (self.imgs.len() as f32 / self.images_per_row as f32).ceil() as usize
287 }
288
289 pub fn pop(&mut self, path: &Path) {
290 if let Some(pos) = self.imgs.iter().position(|x| x.path == path) {
291 self.imgs.remove(pos);
292 self.set_total_rows();
293 }
294 }
295
296 pub fn take_callback(&mut self) -> Option<Callback> {
297 self.callback.take()
298 }
299
300 pub fn reload_at(&mut self, path: &Path) {
301 if let Some(pos) = self.imgs.iter().position(|x| x.path == path) {
302 if let Some(img) = self.imgs.get_mut(pos) {
303 img.unload_delayed();
304 img.unload(pos);
305 }
306 }
307 }
308}