Skip to main content

arama_env/config/
settings.rs

1use arama_i18n::Locale;
2use serde::{Deserialize, Serialize};
3
4pub mod cache_lookup_strategy;
5pub mod target_media_type;
6
7use crate::{DEFAULT_THUMBNAIL_SIZE, MIN_IMAGE_SIMILARITY};
8use cache_lookup_strategy::CacheLookupStrategy;
9use target_media_type::TargetMediaType;
10
11/// User-selectable application theme preset. Maps to the four Snora Design
12/// token presets (and, for the base iced theme, to `Theme::Light` / `Dark`).
13/// Pure data — no GUI dependency, so it lives in `arama-env` alongside the
14/// other persisted setting enums. The token / iced-`Theme` mapping lives in
15/// `arama-theme`.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
17#[serde(rename_all = "snake_case")]
18pub enum ThemePreset {
19    #[default]
20    Light,
21    Dark,
22    HighContrastLight,
23    HighContrastDark,
24}
25
26impl ThemePreset {
27    /// All presets in display order.
28    pub fn all() -> &'static [ThemePreset] {
29        &[
30            ThemePreset::Light,
31            ThemePreset::Dark,
32            ThemePreset::HighContrastLight,
33            ThemePreset::HighContrastDark,
34        ]
35    }
36}
37
38#[derive(Debug, Serialize, Deserialize)]
39pub struct Settings {
40    pub root_dir_path: String,
41    pub target_media_type: TargetMediaType,
42    pub sub_dir_depth_limit: u8,
43    pub thumbnail_size: u16,
44    pub cache_lookup_strategy: CacheLookupStrategy,
45    /// Cosine-similarity threshold for the focus view and similarity pairs
46    /// finder. Defaults to [`MIN_IMAGE_SIMILARITY`] (0.86).
47    /// `serde(default)` ensures existing settings files load cleanly.
48    #[serde(default = "default_similarity_threshold")]
49    pub similarity_threshold: f32,
50    #[serde(default)]
51    pub locale: Locale,
52    #[serde(default)]
53    pub theme: ThemePreset,
54}
55
56fn default_similarity_threshold() -> f32 {
57    MIN_IMAGE_SIMILARITY
58}
59
60impl Default for Settings {
61    fn default() -> Self {
62        Self {
63            root_dir_path: String::default(),
64            target_media_type: TargetMediaType::default(),
65            sub_dir_depth_limit: 0,
66            thumbnail_size: DEFAULT_THUMBNAIL_SIZE,
67            cache_lookup_strategy: CacheLookupStrategy::default(),
68            similarity_threshold: default_similarity_threshold(),
69            locale: Locale::default(),
70            theme: ThemePreset::default(),
71        }
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::ThemePreset;
78
79    #[test]
80    fn theme_preset_discriminants() {
81        // The u8 discriminants are relied on by arama-theme's atomic global
82        // (THEME_ID encode/decode), so pin them here.
83        assert_eq!(ThemePreset::Light as u8, 0);
84        assert_eq!(ThemePreset::Dark as u8, 1);
85        assert_eq!(ThemePreset::HighContrastLight as u8, 2);
86        assert_eq!(ThemePreset::HighContrastDark as u8, 3);
87        assert_eq!(ThemePreset::all().len(), 4);
88        assert_eq!(ThemePreset::default(), ThemePreset::Light);
89    }
90
91    #[test]
92    fn theme_preset_serde_round_trip() {
93        for (preset, json) in [
94            (ThemePreset::Light, "\"light\""),
95            (ThemePreset::Dark, "\"dark\""),
96            (ThemePreset::HighContrastLight, "\"high_contrast_light\""),
97            (ThemePreset::HighContrastDark, "\"high_contrast_dark\""),
98        ] {
99            let encoded = serde_json::to_string(&preset).unwrap();
100            assert_eq!(encoded, json);
101            let decoded: ThemePreset = serde_json::from_str(json).unwrap();
102            assert_eq!(decoded, preset);
103        }
104    }
105}