1#[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}