Skip to main content

oxihuman_core/
user_preferences.rs

1//! User preferences storage with typed values.
2
3#[allow(dead_code)]
4#[derive(Clone, PartialEq, Debug)]
5pub enum PrefValue {
6    Bool(bool),
7    Int(i64),
8    Float(f64),
9    Str(String),
10    Color([f32; 4]),
11    Vec2([f32; 2]),
12}
13
14#[allow(dead_code)]
15#[derive(Clone)]
16pub struct Preference {
17    pub key: String,
18    pub value: PrefValue,
19    pub category: String,
20    pub description: String,
21}
22
23#[allow(dead_code)]
24pub struct UserPreferences {
25    pub prefs: Vec<Preference>,
26    pub dirty: bool,
27}
28
29#[allow(dead_code)]
30pub fn new_user_preferences() -> UserPreferences {
31    UserPreferences {
32        prefs: Vec::new(),
33        dirty: false,
34    }
35}
36
37#[allow(dead_code)]
38pub fn set_pref(prefs: &mut UserPreferences, key: &str, value: PrefValue, category: &str) {
39    if let Some(existing) = prefs.prefs.iter_mut().find(|p| p.key == key) {
40        existing.value = value;
41        existing.category = category.to_string();
42    } else {
43        prefs.prefs.push(Preference {
44            key: key.to_string(),
45            value,
46            category: category.to_string(),
47            description: String::new(),
48        });
49    }
50    prefs.dirty = true;
51}
52
53#[allow(dead_code)]
54pub fn get_pref<'a>(prefs: &'a UserPreferences, key: &str) -> Option<&'a Preference> {
55    prefs.prefs.iter().find(|p| p.key == key)
56}
57
58#[allow(dead_code)]
59pub fn get_bool(prefs: &UserPreferences, key: &str, default: bool) -> bool {
60    match get_pref(prefs, key) {
61        Some(p) => match &p.value {
62            PrefValue::Bool(v) => *v,
63            _ => default,
64        },
65        None => default,
66    }
67}
68
69#[allow(dead_code)]
70pub fn get_int(prefs: &UserPreferences, key: &str, default: i64) -> i64 {
71    match get_pref(prefs, key) {
72        Some(p) => match &p.value {
73            PrefValue::Int(v) => *v,
74            _ => default,
75        },
76        None => default,
77    }
78}
79
80#[allow(dead_code)]
81pub fn get_float(prefs: &UserPreferences, key: &str, default: f64) -> f64 {
82    match get_pref(prefs, key) {
83        Some(p) => match &p.value {
84            PrefValue::Float(v) => *v,
85            _ => default,
86        },
87        None => default,
88    }
89}
90
91#[allow(dead_code)]
92pub fn get_string(prefs: &UserPreferences, key: &str, default: &str) -> String {
93    match get_pref(prefs, key) {
94        Some(p) => match &p.value {
95            PrefValue::Str(v) => v.clone(),
96            _ => default.to_string(),
97        },
98        None => default.to_string(),
99    }
100}
101
102#[allow(dead_code)]
103pub fn remove_pref(prefs: &mut UserPreferences, key: &str) -> bool {
104    let before = prefs.prefs.len();
105    prefs.prefs.retain(|p| p.key != key);
106    let removed = prefs.prefs.len() < before;
107    if removed {
108        prefs.dirty = true;
109    }
110    removed
111}
112
113#[allow(dead_code)]
114pub fn pref_count(prefs: &UserPreferences) -> usize {
115    prefs.prefs.len()
116}
117
118#[allow(dead_code)]
119pub fn prefs_in_category<'a>(prefs: &'a UserPreferences, cat: &str) -> Vec<&'a Preference> {
120    prefs.prefs.iter().filter(|p| p.category == cat).collect()
121}
122
123#[allow(dead_code)]
124pub fn mark_clean(prefs: &mut UserPreferences) {
125    prefs.dirty = false;
126}
127
128#[allow(dead_code)]
129pub fn reset_to_defaults(prefs: &mut UserPreferences) {
130    prefs.prefs.clear();
131    prefs.dirty = false;
132}
133
134#[allow(dead_code)]
135pub fn preferences_to_json(prefs: &UserPreferences) -> String {
136    let mut out = String::from("{\"prefs\":[");
137    for (i, p) in prefs.prefs.iter().enumerate() {
138        if i > 0 {
139            out.push(',');
140        }
141        let val_str = match &p.value {
142            PrefValue::Bool(v) => format!("{v}"),
143            PrefValue::Int(v) => format!("{v}"),
144            PrefValue::Float(v) => format!("{v}"),
145            PrefValue::Str(v) => format!("\"{}\"", v.replace('"', "\\\"")),
146            PrefValue::Color(c) => format!("[{},{},{},{}]", c[0], c[1], c[2], c[3]),
147            PrefValue::Vec2(v) => format!("[{},{}]", v[0], v[1]),
148        };
149        out.push_str(&format!(
150            "{{\"key\":\"{}\",\"value\":{},\"category\":\"{}\"}}",
151            p.key.replace('"', "\\\""),
152            val_str,
153            p.category.replace('"', "\\\"")
154        ));
155    }
156    out.push_str("]}");
157    out
158}
159
160#[allow(dead_code)]
161pub fn preferences_from_pairs(pairs: &[(String, String, String)]) -> UserPreferences {
162    let mut prefs = new_user_preferences();
163    for (key, value_str, category) in pairs {
164        set_pref(&mut prefs, key, PrefValue::Str(value_str.clone()), category);
165    }
166    prefs
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172
173    #[test]
174    fn test_new_user_preferences() {
175        let p = new_user_preferences();
176        assert_eq!(pref_count(&p), 0);
177        assert!(!p.dirty);
178    }
179
180    #[test]
181    fn test_set_and_get_pref() {
182        let mut p = new_user_preferences();
183        set_pref(&mut p, "key1", PrefValue::Bool(true), "general");
184        let pref = get_pref(&p, "key1");
185        assert!(pref.is_some());
186        assert_eq!(pref.expect("should succeed").value, PrefValue::Bool(true));
187    }
188
189    #[test]
190    fn test_set_pref_updates_existing() {
191        let mut p = new_user_preferences();
192        set_pref(&mut p, "key1", PrefValue::Int(10), "general");
193        set_pref(&mut p, "key1", PrefValue::Int(20), "general");
194        assert_eq!(pref_count(&p), 1);
195        assert_eq!(get_int(&p, "key1", 0), 20);
196    }
197
198    #[test]
199    fn test_get_bool_with_default() {
200        let p = new_user_preferences();
201        assert!(get_bool(&p, "missing", true));
202        assert!(!get_bool(&p, "missing", false));
203    }
204
205    #[test]
206    fn test_get_int_with_default() {
207        let p = new_user_preferences();
208        assert_eq!(get_int(&p, "missing", 42), 42);
209    }
210
211    #[test]
212    fn test_get_float_with_default() {
213        let p = new_user_preferences();
214        assert!((get_float(&p, "missing", 2.78) - 2.78).abs() < 1e-10);
215    }
216
217    #[test]
218    fn test_get_string_with_default() {
219        let p = new_user_preferences();
220        assert_eq!(get_string(&p, "missing", "default"), "default");
221    }
222
223    #[test]
224    fn test_get_bool_value() {
225        let mut p = new_user_preferences();
226        set_pref(&mut p, "flag", PrefValue::Bool(false), "ui");
227        assert!(!get_bool(&p, "flag", true));
228    }
229
230    #[test]
231    fn test_get_string_value() {
232        let mut p = new_user_preferences();
233        set_pref(&mut p, "theme", PrefValue::Str("dark".to_string()), "ui");
234        assert_eq!(get_string(&p, "theme", "light"), "dark");
235    }
236
237    #[test]
238    fn test_remove_pref() {
239        let mut p = new_user_preferences();
240        set_pref(&mut p, "key1", PrefValue::Int(1), "general");
241        assert!(remove_pref(&mut p, "key1"));
242        assert_eq!(pref_count(&p), 0);
243        assert!(!remove_pref(&mut p, "key1"));
244    }
245
246    #[test]
247    fn test_pref_count() {
248        let mut p = new_user_preferences();
249        assert_eq!(pref_count(&p), 0);
250        set_pref(&mut p, "a", PrefValue::Int(1), "x");
251        set_pref(&mut p, "b", PrefValue::Int(2), "x");
252        assert_eq!(pref_count(&p), 2);
253    }
254
255    #[test]
256    fn test_prefs_in_category() {
257        let mut p = new_user_preferences();
258        set_pref(&mut p, "a", PrefValue::Int(1), "ui");
259        set_pref(&mut p, "b", PrefValue::Int(2), "graphics");
260        set_pref(&mut p, "c", PrefValue::Int(3), "ui");
261        let ui_prefs = prefs_in_category(&p, "ui");
262        assert_eq!(ui_prefs.len(), 2);
263    }
264
265    #[test]
266    fn test_mark_clean() {
267        let mut p = new_user_preferences();
268        set_pref(&mut p, "a", PrefValue::Int(1), "x");
269        assert!(p.dirty);
270        mark_clean(&mut p);
271        assert!(!p.dirty);
272    }
273
274    #[test]
275    fn test_reset_to_defaults() {
276        let mut p = new_user_preferences();
277        set_pref(&mut p, "a", PrefValue::Int(1), "x");
278        reset_to_defaults(&mut p);
279        assert_eq!(pref_count(&p), 0);
280        assert!(!p.dirty);
281    }
282
283    #[test]
284    fn test_preferences_to_json() {
285        let mut p = new_user_preferences();
286        set_pref(&mut p, "theme", PrefValue::Str("dark".to_string()), "ui");
287        let json = preferences_to_json(&p);
288        assert!(json.contains("\"theme\""));
289        assert!(json.contains("\"dark\""));
290    }
291
292    #[test]
293    fn test_preferences_from_pairs() {
294        let pairs = vec![
295            ("key1".to_string(), "value1".to_string(), "cat1".to_string()),
296            ("key2".to_string(), "value2".to_string(), "cat2".to_string()),
297        ];
298        let p = preferences_from_pairs(&pairs);
299        assert_eq!(pref_count(&p), 2);
300        assert_eq!(get_string(&p, "key1", ""), "value1");
301    }
302}