1use crate::{callback::Callback, utils, APPLICATION, ORGANIZATION, QUALIFIER};
2use eframe::egui::{Key, KeyboardShortcut, Modifiers};
3use serde::{Deserialize, Serialize};
4use std::{fs, path::PathBuf, vec};
5
6const MOD_ALT: &str = "alt";
7const MOD_SHIFT: &str = "shift";
8const MOD_CTRL: &str = "ctrl";
9const MOD_MAC_CMD: &str = "mac_cmd";
10const MOD_CMD: &str = "cmd";
11
12#[derive(Deserialize, Serialize, Default)]
13pub struct Config {
14 pub image_view: ImageViewConfig,
15 pub grid_view: GridViewConfig,
16 pub general: GeneralConfig,
17 pub filter: FilterConfig,
18}
19
20#[derive(Deserialize, Serialize, Clone)]
21pub struct GeneralConfig {
22 #[serde(default = "default_limit_cached")]
23 pub limit_cached: u32,
24 #[serde(default = "default_output_icc_profile")]
25 pub output_icc_profile: String,
26 #[serde(default = "default_text_scaling")]
27 pub text_scaling: f32,
28 #[serde(default = "default_metadata_tags")]
29 pub metadata_tags: Vec<String>,
30
31 #[serde(default = "default_sc_toggle_gallery")]
32 pub sc_toggle_gallery: Shortcut,
33 #[serde(default = "default_sc_exit")]
34 pub sc_exit: Shortcut,
35 #[serde(default = "default_sc_menu")]
36 pub sc_menu: Shortcut,
37 #[serde(default = "default_sc_navigator")]
38 pub sc_navigator: Shortcut,
39 #[serde(default = "default_sc_dir_tree")]
40 pub sc_dir_tree: Shortcut,
41 #[serde(default = "default_sc_flatten_dir")]
42 pub sc_flatten_dir: Shortcut,
43 #[serde(default = "default_sc_watch_directory")]
44 pub sc_watch_directory: Shortcut,
45 #[serde(default = "default_sc_toggle_side_panel")]
46 pub sc_toggle_side_panel: Shortcut,
47}
48
49#[derive(Deserialize, Serialize, Clone)]
50pub struct ImageViewConfig {
51 #[serde(default = "default_nr_loaded_images")]
52 pub nr_loaded_images: usize,
53 #[serde(default = "default_should_wait")]
54 pub should_wait: bool,
55 #[serde(default = "default_frame_size_relative_to_image")]
56 pub frame_size_relative_to_image: f32,
57 #[serde(default = "default_scroll_navigation")]
58 pub scroll_navigation: bool,
59 #[serde(default = "default_name_format")]
60 pub name_format: String,
61 #[serde(default = "default_user_actions")]
62 pub user_actions: Vec<UserAction>,
63 #[serde(default = "default_ctx_menu")]
64 pub context_menu: Vec<ContextMenuEntry>,
65
66 #[serde(default = "default_sc_fit")]
67 pub sc_fit: Shortcut,
68 #[serde(default = "default_sc_frame")]
69 pub sc_frame: Shortcut,
70 #[serde(default = "default_sc_zoom")]
71 pub sc_zoom: Shortcut,
72 #[serde(default = "default_sc_next")]
73 pub sc_next: Shortcut,
74 #[serde(default = "default_sc_prev")]
75 pub sc_prev: Shortcut,
76 #[serde(default = "default_sc_one_to_one")]
77 pub sc_one_to_one: Shortcut,
78 #[serde(default = "default_sc_fit_horizontal")]
79 pub sc_fit_horizontal: Shortcut,
80 #[serde(default = "default_sc_fit_vertical")]
81 pub sc_fit_vertical: Shortcut,
82 #[serde(default = "default_sc_fit_maximize")]
83 pub sc_fit_maximize: Shortcut,
84 #[serde(default = "default_sc_latch_fit_maximize")]
85 pub sc_latch_fit_maximize: Shortcut,
86}
87
88#[derive(Deserialize, Serialize, Clone)]
89pub struct GridViewConfig {
90 #[serde(default = "default_images_per_row")]
91 pub images_per_row: usize,
92 #[serde(default = "default_preloaded_rows")]
93 pub preloaded_rows: usize,
94 #[serde(default = "default_simultaneous_load")]
95 pub simultaneous_load: usize,
96 #[serde(default = "default_ctx_menu")]
97 pub context_menu: Vec<ContextMenuEntry>,
98
99 #[serde(default = "default_sc_scroll")]
100 pub sc_scroll: Shortcut,
101 #[serde(default = "default_sc_more_per_row")]
102 pub sc_more_per_row: Shortcut,
103 #[serde(default = "default_sc_less_per_row")]
104 pub sc_less_per_row: Shortcut,
105}
106
107#[derive(Deserialize, Serialize, Clone)]
108pub struct FilterConfig {
109 #[serde(default = "default_exif_tags")]
110 pub exif_tags: Vec<FilterableExifTag>,
111}
112
113#[derive(Deserialize, Serialize, Clone)]
114pub struct FilterableExifTag {
115 pub name: String,
116 pub fetch_distinct: bool,
117}
118
119#[derive(Deserialize, Serialize, Clone)]
120#[serde(from = "ShortcutData")]
121pub struct Shortcut {
122 pub key: String,
123 pub modifiers: Vec<String>,
124 #[serde(skip)]
125 #[serde(default = "default_shortcut")]
126 pub kbd_shortcut: KeyboardShortcut,
127}
128
129#[derive(Deserialize, Serialize)]
130pub struct ShortcutData {
131 pub key: String,
132 pub modifiers: Vec<String>,
133}
134
135#[derive(Deserialize, Serialize, Clone)]
136pub struct UserAction {
137 pub shortcut: Shortcut,
138 pub exec: String,
139 pub callback: Option<Callback>,
140}
141
142#[derive(Deserialize, Serialize, Clone)]
143pub struct ContextMenuEntry {
144 pub description: String,
145 pub exec: String,
146 pub callback: Option<Callback>,
147}
148
149impl Shortcut {
150 fn from(key: &str, modifiers: &[&str]) -> Shortcut {
151 let modifiers: Vec<String> = modifiers.iter().map(|x| x.to_string()).collect();
152 Shortcut {
153 kbd_shortcut: build_keyboard_shortcut(&modifiers, key),
154 key: key.to_string(),
155 modifiers,
156 }
157 }
158}
159
160impl Default for GeneralConfig {
161 fn default() -> Self {
162 GeneralConfig {
163 limit_cached: default_limit_cached(),
164 output_icc_profile: default_output_icc_profile(),
165 text_scaling: default_text_scaling(),
166 metadata_tags: default_metadata_tags(),
167 sc_toggle_gallery: default_sc_toggle_gallery(),
168 sc_toggle_side_panel: default_sc_toggle_side_panel(),
169 sc_exit: default_sc_exit(),
170 sc_menu: default_sc_menu(),
171 sc_navigator: default_sc_navigator(),
172 sc_dir_tree: default_sc_dir_tree(),
173 sc_flatten_dir: default_sc_flatten_dir(),
174 sc_watch_directory: default_sc_watch_directory(),
175 }
176 }
177}
178
179impl Default for ImageViewConfig {
180 fn default() -> Self {
181 ImageViewConfig {
182 nr_loaded_images: default_nr_loaded_images(),
183 should_wait: default_should_wait(),
184 frame_size_relative_to_image: default_frame_size_relative_to_image(),
185 scroll_navigation: default_scroll_navigation(),
186 user_actions: default_user_actions(),
187 context_menu: default_ctx_menu(),
188 name_format: default_name_format(),
189
190 sc_fit: default_sc_fit(),
191 sc_frame: default_sc_frame(),
192 sc_zoom: default_sc_zoom(),
193 sc_next: default_sc_next(),
194 sc_prev: default_sc_prev(),
195 sc_one_to_one: default_sc_one_to_one(),
196 sc_fit_vertical: default_sc_fit_vertical(),
197 sc_fit_horizontal: default_sc_fit_horizontal(),
198 sc_fit_maximize: default_sc_fit_maximize(),
199 sc_latch_fit_maximize: default_sc_latch_fit_maximize(),
200 }
201 }
202}
203
204impl Default for GridViewConfig {
205 fn default() -> Self {
206 GridViewConfig {
207 images_per_row: default_images_per_row(),
208 preloaded_rows: default_preloaded_rows(),
209 simultaneous_load: default_simultaneous_load(),
210 context_menu: default_ctx_menu(),
211
212 sc_scroll: default_sc_scroll(),
213 sc_more_per_row: default_sc_more_per_row(),
214 sc_less_per_row: default_sc_less_per_row(),
215 }
216 }
217}
218
219impl Default for FilterConfig {
220 fn default() -> Self {
221 FilterConfig {
222 exif_tags: default_exif_tags(),
223 }
224 }
225}
226
227impl Config {
228 pub fn new() -> Config {
229 Self::fetch_cfg()
230 }
231
232 pub fn fetch_cfg() -> Config {
233 let config_dir = match directories::ProjectDirs::from(QUALIFIER, ORGANIZATION, APPLICATION)
234 {
235 Some(dirs) => dirs.config_dir().to_owned(),
236 None => return Config::default(),
237 };
238
239 let cfg_path = config_dir.join(PathBuf::from("config.json"));
240 println!("Reading config -> {}", cfg_path.display());
241
242 let config_json = match fs::read_to_string(cfg_path) {
243 Ok(json) => json,
244 Err(e) => {
245 println!("Failure reading config file -> {e}");
246 return Config::default();
247 }
248 };
249
250 let cfg = match serde_json::from_str(&config_json) {
251 Ok(cfg) => cfg,
252 Err(e) => {
253 println!("{e}");
254 println!("Failure parsing config json, using defaults");
255 Config::default()
256 }
257 };
258
259 println!("Using config:");
260 println!("{}", serde_json::to_string(&cfg).unwrap());
261
262 cfg
263 }
264}
265
266pub fn default_limit_cached() -> u32 {
267 1000000
268}
269
270pub fn default_output_icc_profile() -> String {
271 String::from("srgb")
272}
273
274pub fn default_text_scaling() -> f32 {
275 1.25
276}
277
278pub fn default_sc_toggle_gallery() -> Shortcut {
279 Shortcut::from("Backspace", &[])
280}
281
282pub fn default_sc_exit() -> Shortcut {
283 Shortcut::from("q", &[])
284}
285
286pub fn default_sc_menu() -> Shortcut {
287 Shortcut::from("F1", &[])
288}
289
290pub fn default_sc_navigator() -> Shortcut {
291 Shortcut::from("l", &[MOD_CTRL])
292}
293
294pub fn default_sc_dir_tree() -> Shortcut {
295 Shortcut::from("t", &[])
296}
297
298pub fn default_sc_flatten_dir() -> Shortcut {
299 Shortcut::from("f", &[MOD_CTRL])
300}
301
302pub fn default_sc_watch_directory() -> Shortcut {
303 Shortcut::from("w", &[MOD_CTRL])
304}
305
306pub fn default_nr_loaded_images() -> usize {
308 4
309}
310pub fn default_should_wait() -> bool {
311 true
312}
313pub fn default_metadata_tags() -> Vec<String> {
314 vec![
315 "Date/Time Original".to_string(),
316 "Created Date".to_string(),
317 "Camera Model Name".to_string(),
318 "Lens Model".to_string(),
319 "Focal Length".to_string(),
320 "Aperture Value".to_string(),
321 "Exposure Time".to_string(),
322 "ISO".to_string(),
323 "Image Size".to_string(),
324 "Color Space".to_string(),
325 "Directory".to_string(),
326 ]
327}
328pub fn default_frame_size_relative_to_image() -> f32 {
329 0.2
330}
331pub fn default_scroll_navigation() -> bool {
332 true
333}
334pub fn default_name_format() -> String {
335 "$(#File Name#)$( • ƒ#Aperture#)$( • #Shutter Speed#)$( • #ISO# ISO)".to_string()
336}
337pub fn default_user_actions() -> Vec<UserAction> {
338 vec![]
339}
340pub fn default_ctx_menu() -> Vec<ContextMenuEntry> {
341 vec![]
342}
343pub fn default_sc_fit() -> Shortcut {
344 Shortcut::from("f", &[])
345}
346pub fn default_sc_frame() -> Shortcut {
347 Shortcut::from("g", &[])
348}
349pub fn default_sc_toggle_side_panel() -> Shortcut {
350 Shortcut::from("i", &[])
351}
352pub fn default_sc_zoom() -> Shortcut {
353 Shortcut::from("Space", &[])
354}
355pub fn default_sc_next() -> Shortcut {
356 Shortcut::from("ArrowRight", &[])
357}
358pub fn default_sc_prev() -> Shortcut {
359 Shortcut::from("ArrowLeft", &[])
360}
361pub fn default_sc_one_to_one() -> Shortcut {
362 Shortcut::from("1", &[MOD_ALT])
363}
364pub fn default_sc_fit_vertical() -> Shortcut {
365 Shortcut::from("v", &[])
366}
367pub fn default_sc_fit_horizontal() -> Shortcut {
368 Shortcut::from("h", &[])
369}
370pub fn default_sc_fit_maximize() -> Shortcut {
371 Shortcut::from("m", &[])
372}
373pub fn default_sc_latch_fit_maximize() -> Shortcut {
374 Shortcut::from("m", &[MOD_CTRL])
375}
376
377pub fn default_images_per_row() -> usize {
379 5
380}
381pub fn default_preloaded_rows() -> usize {
382 1
383}
384pub fn default_simultaneous_load() -> usize {
385 8
386}
387pub fn default_sc_scroll() -> Shortcut {
388 Shortcut::from("Space", &[])
389}
390pub fn default_sc_more_per_row() -> Shortcut {
391 Shortcut::from("Plus", &[])
392}
393pub fn default_sc_less_per_row() -> Shortcut {
394 Shortcut::from("Minus", &[])
395}
396
397pub fn default_exif_tags() -> Vec<FilterableExifTag> {
399 vec![]
400}
401
402pub fn default_shortcut() -> KeyboardShortcut {
404 let modi = Modifiers {
408 alt: true,
409 ctrl: true,
410 shift: true,
411 command: true,
412 mac_cmd: false,
413 };
414
415 KeyboardShortcut::new(modi, Key::F20)
416}
417
418impl From<ShortcutData> for Shortcut {
419 fn from(data: ShortcutData) -> Self {
420 Shortcut {
421 kbd_shortcut: build_keyboard_shortcut(&data.modifiers, &data.key),
422 key: data.key,
423 modifiers: data.modifiers,
424 }
425 }
426}
427
428pub fn build_keyboard_shortcut(mods: &[String], key: &str) -> KeyboardShortcut {
429 let mut modifiers = Modifiers::default();
430 for modi in mods {
431 match modi.as_str() {
432 MOD_ALT => modifiers.alt = true,
433 MOD_CTRL => modifiers.ctrl = true,
434 MOD_SHIFT => modifiers.shift = true,
435 MOD_CMD => modifiers.command = true,
436 MOD_MAC_CMD => modifiers.mac_cmd = true,
437 _ => {
438 println!("Invalid modifier({}) in configuration", modi.as_str())
439 }
440 }
441 }
442
443 match Key::from_name(&utils::capitalize_first_char(key)) {
444 Some(key) => KeyboardShortcut {
445 logical_key: key,
446 modifiers,
447 },
448 None => {
449 println!("Invalid shortcut key: {key}");
450 default_shortcut()
451 } }
453}