streamduck_core/util/
mod.rs

1use std::collections::hash_map::DefaultHasher;
2use std::collections::HashMap;
3use std::hash::{Hash, Hasher};
4use std::ops::Deref;
5use std::sync::{Arc};
6use serde_json::{Error, Value};
7use tokio::sync::RwLock;
8use crate::core::button::Button;
9use crate::core::{ButtonPanel, Panel, RawButtonPanel, UniqueButton, UniqueButtonMap};
10use crate::font::get_font_names;
11use crate::images::SDSerializedImage;
12use crate::modules::components::{UIFieldType, UIFieldValue, UIPathValue, UIValue};
13
14pub use rusttype;
15
16/// Wraps button in [Arc] and [RwLock], but packed into a more convenient function
17pub fn make_button_unique(button: Button) -> UniqueButton {
18    Arc::new(RwLock::new(button))
19}
20
21/// Parses button panel to Value, serializing all the unique buttons in process
22pub async fn serialize_panel(panel: ButtonPanel) -> Result<Value, Error> {
23    let panel = panel_to_raw(&panel).await;
24
25    serialize_panel_raw(panel)
26}
27
28/// Serializes raw button panel to Value in more understandable function
29pub fn serialize_panel_raw(panel: RawButtonPanel) -> Result<Value, Error> {
30    serde_json::to_value(panel)
31}
32
33/// Parses value into a button panel
34pub fn deserialize_panel(value: Value) -> Result<ButtonPanel, Error> {
35    Ok(make_panel_unique(deserialize_panel_raw(value)?))
36}
37
38/// Parses value into a raw button panel
39pub fn deserialize_panel_raw(value: Value) -> Result<RawButtonPanel, Error> {
40    Ok(serde_json::from_value(value)?)
41}
42
43/// Converts raw button panel into button panel
44pub fn make_panel_unique(raw_panel: RawButtonPanel) -> ButtonPanel {
45    Arc::new(RwLock::new(
46        Panel::<UniqueButtonMap> {
47            display_name: raw_panel.display_name,
48            data: raw_panel.data,
49            buttons: raw_panel.buttons.into_iter().map(|(key, button)| (key, make_button_unique(button))).collect()
50        }
51    ))
52}
53
54
55/// Converts button panel to raw button panel
56pub async fn panel_to_raw(panel: &ButtonPanel) -> RawButtonPanel {
57    let panel = (*panel.read().await).clone();
58
59    let mut buttons = HashMap::new();
60
61    for (key, button) in panel.buttons {
62        buttons.insert(key, button_to_raw(&button).await);
63    }
64
65    RawButtonPanel {
66        display_name: panel.display_name,
67        data: panel.data,
68        buttons
69    }
70}
71
72/// Converts unique button to raw button
73pub async fn button_to_raw(button: &UniqueButton) -> Button {
74    button.read().await.deref().clone()
75}
76
77/// Hashes string
78pub fn hash_str(data: &String) -> String {
79    let mut hasher = DefaultHasher::new();
80
81    data.hash(&mut hasher);
82
83    hasher.finish().to_string()
84}
85
86/// Hashes image
87pub fn hash_image(data: &SDSerializedImage) -> String {
88    let mut hasher = DefaultHasher::new();
89
90    data.hash(&mut hasher);
91
92    hasher.finish().to_string()
93}
94
95/// Hashes value
96pub fn hash_value<H: Hasher>(value: &Value, state: &mut H) {
97    match value {
98        Value::Null => 0.hash(state),
99        Value::Bool(b) => b.hash(state),
100        Value::Number(n) => n.hash(state),
101        Value::String(s) => s.hash(state),
102        Value::Array(a) => a.iter().for_each(|x| hash_value(x, state)),
103        Value::Object(o) => o.iter().for_each(|(k, v)| {
104            k.hash(state);
105            hash_value(v, state)
106        }),
107    }
108}
109
110/// Converts [UIValue] to [UIPathValue]
111pub fn convert_value_to_path(value: UIValue, current_path: &str) -> UIPathValue {
112    let current_path = if current_path.is_empty() { "".to_string() } else { format!("{}.", current_path) };
113
114    match &value.value {
115        UIFieldValue::Collapsable(m) => {
116            let path = format!("{}{}", current_path, value.name);
117
118            let values: Vec<UIPathValue> = m.clone().into_iter()
119                .map(|x| convert_value_to_path(x, &path))
120                .collect();
121
122            UIPathValue {
123                name: value.name,
124                path,
125                display_name: value.display_name,
126                description: value.description,
127                ty: value.ty,
128                value: UIFieldValue::Collapsable(values)
129            }
130        }
131
132        UIFieldValue::Array(a) => {
133            let path = format!("{}{}", current_path, value.name);
134
135            let values: Vec<Vec<UIPathValue>> = a.clone().into_iter()
136                .enumerate()
137                .map(|(index, x)| x.into_iter()
138                    .map(|y| convert_value_to_path(y, &format!("{}.{}", path, index)))
139                    .collect())
140                .collect();
141
142            UIPathValue {
143                name: value.name,
144                path,
145                display_name: value.display_name,
146                description: value.description,
147                ty: value.ty,
148                value: UIFieldValue::Array(values)
149            }
150        }
151
152        _ => {
153            let path = format!("{}{}", current_path, value.name);
154
155            UIPathValue {
156                name: value.name,
157                path,
158                display_name: value.display_name,
159                description: value.description,
160                ty: value.ty,
161                value: value.value.into()
162            }
163        }
164    }
165}
166
167/// Copies component data from reference to new button
168pub fn straight_copy(reference: &Button, new_button: &mut Button, component_name: &str) {
169    if let Some(data) = reference.0.get(component_name) {
170        new_button.0.insert(component_name.to_string(), data.clone());
171    }
172}
173
174/// Util function to navigate paths and perform changes on values
175pub fn change_from_path<T: Fn(&mut UIValue) -> bool>(path: &str, ui_values: Vec<UIValue>, func: &T, keep: bool) -> (Vec<UIValue>, bool) {
176    let mut path = path.split(".");
177
178    let mut changes = vec![];
179    let mut success = false;
180
181    if let Some(path_piece) = path.next() {
182        for mut value in ui_values {
183            if value.name == path_piece {
184                match value.value.clone() {
185                    UIFieldValue::Collapsable(submenu) => {
186                        let path = path.clone().collect::<Vec<&str>>().join(".");
187
188                        let (changed_values, changed_success) = change_from_path(path.as_str(), submenu, func, keep);
189
190                        value.value = UIFieldValue::Collapsable(changed_values);
191                        success = changed_success;
192
193                        changes.push(value);
194                    }
195
196                    UIFieldValue::Array(array) => {
197                        if let Some(path_index) = path.next() {
198                            if let Ok(path_index) = path_index.parse::<usize>() {
199                                let mut new_array = vec![];
200
201                                for (index, item) in array.into_iter().enumerate() {
202                                    if path_index == index {
203                                        let path = path.clone().collect::<Vec<&str>>().join(".");
204
205                                        let (changed_values, changed_success) = change_from_path(path.as_str(), item, func, true);
206                                        success = changed_success;
207                                        new_array.push(changed_values);
208                                    } else {
209                                        new_array.push(item);
210                                    }
211                                }
212
213                                value.value = UIFieldValue::Array(new_array);
214
215                                changes.push(value);
216                            }
217                        } else {
218                            success = func(&mut value);
219
220                            changes.push(value);
221                        }
222                    }
223
224                    _ => {
225                        success = func(&mut value);
226
227                        changes.push(value);
228                    }
229                }
230            } else {
231                if keep {
232                    changes.push(value);
233                }
234            }
235        }
236    }
237
238    (changes, success)
239}
240
241/// Returns function for adding an element to an array, for use with [change_from_path]
242pub fn add_array_function() -> fn(&mut UIValue) -> bool {
243    |x| {
244        if let UIFieldType::Array(template_fields) = &x.ty {
245            let mut new_item = vec![];
246
247            for field in template_fields {
248                new_item.push(UIValue {
249                    name: field.name.clone(),
250                    display_name: field.display_name.clone(),
251                    description: field.description.clone(),
252                    ty: field.ty.clone(),
253                    value: field.default_value.clone()
254                })
255            }
256
257            if let UIFieldValue::Array(array) = &mut x.value {
258                array.push(new_item);
259                true
260            } else {
261                false
262            }
263        } else {
264            false
265        }
266    }
267}
268
269/// Returns function for removing an element from array, for use with [change_from_path]
270pub fn remove_array_function(index: usize) -> Box<dyn Fn(&mut UIValue) -> bool> {
271    Box::new(move |x| {
272        if let UIFieldValue::Array(array) = &mut x.value {
273            array.remove(index);
274            true
275        } else {
276            false
277        }
278    })
279}
280
281/// Returns function for setting value to UIValue, for use with [change_from_path]
282pub fn set_value_function(value: UIPathValue) -> Box<dyn Fn(&mut UIValue) -> bool> {
283    let fonts = get_font_names();
284
285    Box::new(move |x| {
286        match &x.ty {
287            UIFieldType::Header |
288            UIFieldType::Label |
289            UIFieldType::Collapsable |
290            UIFieldType::Array(_) |
291            UIFieldType::Button { .. } |
292            UIFieldType::ImagePreview => false,
293
294            UIFieldType::Choice(variants) => {
295                if let Ok(variant) = value.value.try_into_string() {
296                    if variants.contains(&variant) {
297                        x.value = UIFieldValue::Choice(variant);
298                        true
299                    } else {
300                        false
301                    }
302                } else {
303                    false
304                }
305            }
306
307            UIFieldType::InputFieldFloat => {
308                if let Ok(f) = value.value.try_into_f32() {
309                    x.value = UIFieldValue::InputFieldFloat(f);
310                    true
311                } else {
312                    false
313                }
314            }
315
316            UIFieldType::InputFieldInteger => {
317                if let Ok(i) = value.value.try_into_i32() {
318                    x.value = UIFieldValue::InputFieldInteger(i);
319                    true
320                } else {
321                    false
322                }
323            }
324
325            UIFieldType::InputFieldString => {
326                if let Ok(s) = value.value.try_into_string() {
327                    x.value = UIFieldValue::InputFieldString(s);
328                    true
329                } else {
330                    false
331                }
332            }
333
334            UIFieldType::InputFieldFloat2 => {
335                if let Ok((f1, f2)) = value.value.try_into_f32_f32() {
336                    x.value = UIFieldValue::InputFieldFloat2(f1, f2);
337                    true
338                } else {
339                    false
340                }
341            }
342
343            UIFieldType::InputFieldInteger2 => {
344                if let Ok((i1, i2)) = value.value.try_into_i32_i32() {
345                    x.value = UIFieldValue::InputFieldInteger2(i1, i2);
346                    true
347                } else {
348                    false
349                }
350            }
351
352            UIFieldType::InputFieldUnsignedInteger => {
353                if let Ok(u) = value.value.try_into_u32() {
354                    x.value = UIFieldValue::InputFieldUnsignedInteger(u);
355                    true
356                } else {
357                    false
358                }
359            }
360
361            UIFieldType::ValueSliderFloat(limits) => {
362                if let Ok(f) = value.value.try_into_f32() {
363                    if !limits.allow_out_of_bounds {
364                        x.value = UIFieldValue::ValueSliderFloat(f.clamp(limits.min_value, limits.max_value));
365                    } else {
366                        x.value = UIFieldValue::ValueSliderFloat(f);
367                    }
368                    true
369                } else {
370                    false
371                }
372            }
373
374            UIFieldType::ValueSliderInteger(limits) => {
375                if let Ok(i) = value.value.try_into_i32() {
376                    if !limits.allow_out_of_bounds {
377                        x.value = UIFieldValue::ValueSliderInteger(i.clamp(limits.min_value, limits.max_value));
378                    } else {
379                        x.value = UIFieldValue::ValueSliderInteger(i);
380                    }
381                    true
382                } else {
383                    false
384                }
385            }
386
387            UIFieldType::Checkbox { .. } => {
388                if let Ok(b) = value.value.try_into_bool() {
389                    x.value = UIFieldValue::Checkbox(b);
390                    true
391                } else {
392                    false
393                }
394            }
395
396            UIFieldType::Color => {
397                if let Ok(b) = value.value.try_into_color() {
398                    x.value = UIFieldValue::Color(b.0, b.1, b.2, b.3);
399                    true
400                } else {
401                    false
402                }
403            }
404
405            UIFieldType::ImageData => {
406                if let Ok(s) = value.value.try_into_string() {
407                    x.value = UIFieldValue::ImageData(s);
408                    true
409                } else {
410                    false
411                }
412            }
413
414            UIFieldType::ExistingImage => {
415                if let Ok(s) = value.value.try_into_string() {
416                    x.value = UIFieldValue::ExistingImage(s);
417                    true
418                } else {
419                    false
420                }
421            }
422            UIFieldType::Font => {
423                if let Ok(s) = value.value.try_into_string() {
424                    if fonts.contains(&s) {
425                        x.value = UIFieldValue::Font(s);
426                        true
427                    } else {
428                        false
429                    }
430                } else {
431                    false
432                }
433            }
434        }
435    })
436}