avis_imgv/
config.rs

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
306//Gallery
307pub 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
377//Multi Gallery
378pub 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
397//Filter
398pub fn default_exif_tags() -> Vec<FilterableExifTag> {
399    vec![]
400}
401
402//Shortcuts
403pub fn default_shortcut() -> KeyboardShortcut {
404    //Bogus shortcut as default so we don't have to use option
405    //Easier when implementing the shortcuts
406    //We use F20 as most users don't have it and all modifiers
407    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        } //uses default unreachable shortcut
452    }
453}