streamduck_core/thread/rendering/
component_values.rs

1use strum::VariantNames;
2use std::str::FromStr;
3use crate::core::button::{Button, parse_button_to_component};
4use crate::core::CoreHandle;
5use crate::modules::components::{map_ui_values, map_ui_values_ref, UIField, UIFieldType, UIFieldValue, UIValue};
6use crate::thread::rendering::{ButtonBackground, ButtonText, ButtonTextShadow, RendererComponent};
7use crate::thread::util::TextAlignment;
8use crate::images::SDImage;
9use crate::util::hash_str;
10
11/// Retrieves component values for the renderer in specified button
12pub async fn get_renderer_component_values(core: &CoreHandle, button: &Button) -> Vec<UIValue> {
13    if let Ok(component) = parse_button_to_component::<RendererComponent>(button) {
14        let mut fields = vec![];
15
16        fields.push(
17            UIValue {
18                name: "renderer".to_string(),
19                display_name: "Renderer to use".to_string(),
20                description: "Renderers can be added by plugins".to_string(),
21                ty: UIFieldType::Choice({
22                    let mut names = vec!["default".to_string()];
23
24                    names.extend(core.core.render_manager.read_renderers().await.values()
25                        .map(|x| x.name()));
26
27                    names
28                }),
29                value: UIFieldValue::Choice(if component.renderer.is_empty() { "default".to_string() } else { component.renderer.clone() })
30            }
31        );
32
33        if !component.renderer.is_empty() {
34            if let Some(renderer) = core.core.render_manager.read_renderers().await.get(&component.renderer).cloned() {
35                fields.extend(renderer.component_values(button, &component, core).await);
36            }
37        } else {
38            // Choice for background type
39            fields.push(
40                UIValue {
41                    name: "background_params".to_string(),
42                    display_name: "Background Parameters".to_string(),
43                    description: "Parameters related to background of the button".to_string(),
44                    ty: UIFieldType::Collapsable,
45                    value: UIFieldValue::Collapsable({
46                        let mut fields = vec![];
47
48                        fields.push(
49                            UIValue {
50                                name: "background".to_string(),
51                                display_name: "Background Type".to_string(),
52                                description: "Type of the background to use".to_string(),
53                                ty: UIFieldType::Choice(vec!["Solid Color".to_string(), "Horizontal Gradient".to_string(), "Vertical Gradient".to_string(), "Existing Image".to_string(), "New Image".to_string()]),
54                                value: UIFieldValue::Choice(
55                                    match &component.background {
56                                        ButtonBackground::Solid(_) => "Solid Color",
57                                        ButtonBackground::HorizontalGradient(_, _) => "Horizontal Gradient",
58                                        ButtonBackground::VerticalGradient(_, _) => "Vertical Gradient",
59                                        ButtonBackground::ExistingImage(_) => "Existing Image",
60                                        ButtonBackground::NewImage(_) => "New Image",
61                                    }.to_string()
62                                )
63                            }
64                        );
65
66                        // Different fields depending on background type
67                        match &component.background {
68                            ButtonBackground::Solid(color) => {
69                                fields.push(
70                                    UIValue {
71                                        name: "color".to_string(),
72                                        display_name: "Background Color".to_string(),
73                                        description: "Color that will be the background of the button".to_string(),
74                                        ty: UIFieldType::Color,
75                                        value: color.into()
76                                    }
77                                );
78                            }
79
80                            ButtonBackground::HorizontalGradient(start_color, end_color) => {
81                                fields.push(
82                                    UIValue {
83                                        name: "start_color".to_string(),
84                                        display_name: "Gradient Start Color".to_string(),
85                                        description: "Color that will be on left side of the gradient".to_string(),
86                                        ty: UIFieldType::Color,
87                                        value: start_color.into()
88                                    }
89                                );
90
91                                fields.push(
92                                    UIValue {
93                                        name: "end_color".to_string(),
94                                        display_name: "Gradient End Color".to_string(),
95                                        description: "Color that will be on right side of the gradient".to_string(),
96                                        ty: UIFieldType::Color,
97                                        value: end_color.into()
98                                    }
99                                );
100                            }
101                            ButtonBackground::VerticalGradient(start_color, end_color) => {
102                                fields.push(
103                                    UIValue {
104                                        name: "start_color".to_string(),
105                                        display_name: "Gradient Start Color".to_string(),
106                                        description: "Color that will be on top side of the gradient".to_string(),
107                                        ty: UIFieldType::Color,
108                                        value: start_color.into()
109                                    }
110                                );
111
112                                fields.push(
113                                    UIValue {
114                                        name: "end_color".to_string(),
115                                        display_name: "Gradient End Color".to_string(),
116                                        description: "Color that will be on bottom side of the gradient".to_string(),
117                                        ty: UIFieldType::Color,
118                                        value: end_color.into()
119                                    }
120                                );
121                            }
122                            ButtonBackground::ExistingImage(identifier) => {
123                                fields.push(
124                                    UIValue {
125                                        name: "image".to_string(),
126                                        display_name: "Image".to_string(),
127                                        description: "Image to use as background of the button".to_string(),
128                                        ty: UIFieldType::ExistingImage,
129                                        value: UIFieldValue::ExistingImage(identifier.to_string())
130                                    }
131                                );
132                            }
133                            ButtonBackground::NewImage(blob) => {
134                                fields.push(
135                                    UIValue {
136                                        name: "image".to_string(),
137                                        display_name: "Image".to_string(),
138                                        description: "Image to use as background of the button".to_string(),
139                                        ty: UIFieldType::ImageData,
140                                        value: UIFieldValue::ImageData(blob.to_string())
141                                    }
142                                );
143                            }
144                        }
145
146                        fields
147                    })
148                }
149            );
150
151            // Text array
152            fields.push(
153                UIValue {
154                    name: "text_params".to_string(),
155                    display_name: "Text Parameters".to_string(),
156                    description: "Parameters related to text on the button".to_string(),
157                    ty: UIFieldType::Collapsable,
158                    value: UIFieldValue::Collapsable({
159                        let mut fields = vec![];
160
161                        fields.push(
162                            UIValue {
163                                name: "text".to_string(),
164                                display_name: "Text Objects".to_string(),
165                                description: "Array of text objects".to_string(),
166                                ty: UIFieldType::Array(
167                                    vec![
168                                        UIField {
169                                            name: "text".to_string(),
170                                            display_name: "Text".to_string(),
171                                            description: "Text that will be displayed".to_string(),
172                                            ty: UIFieldType::InputFieldString,
173                                            default_value: UIFieldValue::InputFieldString("".to_string())
174                                        },
175                                        UIField {
176                                            name: "font".to_string(),
177                                            display_name: "Font".to_string(),
178                                            description: "Font that will be used for text rendering".to_string(),
179                                            ty: UIFieldType::Font,
180                                            default_value: UIFieldValue::Font("default".to_string())
181                                        },
182                                        UIField {
183                                            name: "scale".to_string(),
184                                            display_name: "Text Scale".to_string(),
185                                            description: "Scale of the text".to_string(),
186                                            ty: UIFieldType::InputFieldFloat2,
187                                            default_value: UIFieldValue::InputFieldFloat2(15.0, 15.0)
188                                        },
189                                        UIField {
190                                            name: "alignment".to_string(),
191                                            display_name: "Alignment".to_string(),
192                                            description: "To which point of the button the text will be anchored to".to_string(),
193                                            ty: UIFieldType::Choice(
194                                                TextAlignment::VARIANTS.iter().map(|x| x.to_string()).collect()
195                                            ),
196                                            default_value: UIFieldValue::Choice("Center".to_string())
197                                        },
198                                        UIField {
199                                            name: "padding".to_string(),
200                                            display_name: "Padding".to_string(),
201                                            description: "Gap to have from alignment/anchor point".to_string(),
202                                            ty: UIFieldType::InputFieldUnsignedInteger,
203                                            default_value: UIFieldValue::InputFieldUnsignedInteger(0)
204                                        },
205                                        UIField {
206                                            name: "offset".to_string(),
207                                            display_name: "Text Offset".to_string(),
208                                            description: "2D offset of the text from its alignment/anchor point".to_string(),
209                                            ty: UIFieldType::InputFieldFloat2,
210                                            default_value: UIFieldValue::InputFieldFloat2(0.0, 0.0)
211                                        },
212                                        UIField {
213                                            name: "color".to_string(),
214                                            display_name: "Text Color".to_string(),
215                                            description: "Color that text will be displayed in".to_string(),
216                                            ty: UIFieldType::Color,
217                                            default_value: UIFieldValue::Color(0, 0, 0, 255)
218                                        },
219                                        UIField {
220                                            name: "shadow_enabled".to_string(),
221                                            display_name: "Text Shadow".to_string(),
222                                            description: "If text shadow should be rendered or not".to_string(),
223                                            ty: UIFieldType::Checkbox {
224                                                disabled: false
225                                            },
226                                            default_value: UIFieldValue::Checkbox(false)
227                                        }
228                                    ]
229                                ),
230                                value: UIFieldValue::Array({
231                                    let mut text_objects = vec![];
232
233                                    for text in &component.text {
234                                        let mut values = vec![];
235
236                                        values.push(UIValue {
237                                            name: "text".to_string(),
238                                            display_name: "Text".to_string(),
239                                            description: "Text that will be displayed".to_string(),
240                                            ty: UIFieldType::InputFieldString,
241                                            value: UIFieldValue::InputFieldString(text.text.clone())
242                                        });
243
244                                        values.push(UIValue {
245                                            name: "font".to_string(),
246                                            display_name: "Font".to_string(),
247                                            description: "Font that will be used for text rendering".to_string(),
248                                            ty: UIFieldType::Font,
249                                            value: UIFieldValue::Font(text.font.clone())
250                                        });
251
252                                        values.push(UIValue {
253                                            name: "scale".to_string(),
254                                            display_name: "Text Scale".to_string(),
255                                            description: "Scale of the text".to_string(),
256                                            ty: UIFieldType::InputFieldFloat2,
257                                            value: UIFieldValue::InputFieldFloat2(text.scale.0, text.scale.1)
258                                        });
259
260                                        values.push(UIValue {
261                                            name: "alignment".to_string(),
262                                            display_name: "Alignment".to_string(),
263                                            description: "To which point of the button the text will be anchored to".to_string(),
264                                            ty: UIFieldType::Choice(
265                                                TextAlignment::VARIANTS.iter().map(|x| x.to_string()).collect()
266                                            ),
267                                            value: UIFieldValue::Choice(text.alignment.to_string())
268                                        });
269
270                                        values.push(UIValue {
271                                            name: "padding".to_string(),
272                                            display_name: "Padding".to_string(),
273                                            description: "Gap to have from alignment/anchor point".to_string(),
274                                            ty: UIFieldType::InputFieldUnsignedInteger,
275                                            value: UIFieldValue::InputFieldUnsignedInteger(text.padding)
276                                        });
277
278                                        values.push(UIValue {
279                                            name: "offset".to_string(),
280                                            display_name: "Text Offset".to_string(),
281                                            description: "2D offset of the text from its alignment/anchor point".to_string(),
282                                            ty: UIFieldType::InputFieldFloat2,
283                                            value: UIFieldValue::InputFieldFloat2(text.offset.0, text.offset.1)
284                                        });
285
286                                        values.push(UIValue {
287                                            name: "color".to_string(),
288                                            display_name: "Text Color".to_string(),
289                                            description: "Color that text will be displayed in".to_string(),
290                                            ty: UIFieldType::Color,
291                                            value: text.color.into()
292                                        });
293
294                                        if let Some(shadow) = &text.shadow {
295                                            values.push(
296                                                UIValue {
297                                                    name: "shadow_enabled".to_string(),
298                                                    display_name: "Text Shadow".to_string(),
299                                                    description: "If text shadow should be rendered or not".to_string(),
300                                                    ty: UIFieldType::Checkbox {
301                                                        disabled: false
302                                                    },
303                                                    value: UIFieldValue::Checkbox(true)
304                                                }
305                                            );
306
307                                            values.push(UIValue {
308                                                name: "shadow_color".to_string(),
309                                                display_name: "Text Shadow Color".to_string(),
310                                                description: "Color of the shadow".to_string(),
311                                                ty: UIFieldType::Color,
312                                                value: shadow.color.into()
313                                            });
314
315                                            values.push(UIValue {
316                                                name: "shadow_offset".to_string(),
317                                                display_name: "Text Shadow Offset".to_string(),
318                                                description: "Offset of the shadow from text".to_string(),
319                                                ty: UIFieldType::InputFieldInteger2,
320                                                value: UIFieldValue::InputFieldInteger2(shadow.offset.0, shadow.offset.1)
321                                            });
322                                        } else {
323                                            values.push(
324                                                UIValue {
325                                                    name: "shadow_enabled".to_string(),
326                                                    display_name: "Text Shadow".to_string(),
327                                                    description: "If text shadow should be rendered or not".to_string(),
328                                                    ty: UIFieldType::Checkbox {
329                                                        disabled: false
330                                                    },
331                                                    value: UIFieldValue::Checkbox(false)
332                                                }
333                                            );
334                                        }
335
336                                        text_objects.push(values);
337                                    }
338
339                                    text_objects
340                                })
341                            }
342                        );
343
344                        fields
345                    })
346                }
347            );
348
349            // Ignore plugin thread menu
350            fields.push(
351                UIValue {
352                    name: "plugin_blacklist".to_string(),
353                    display_name: "Allowed plugins to render".to_string(),
354                    description: "Disabled plugins will not appear on button".to_string(),
355                    ty: UIFieldType::Collapsable,
356                    value: UIFieldValue::Collapsable({
357                        let names = core.module_manager().get_modules_for_rendering(&button.component_names()).await;
358
359                        names.into_values()
360                            .map(|x| {
361                                let name = x.name();
362
363                                UIValue {
364                                    name: name.clone(),
365                                    display_name: name.clone(),
366                                    description: "".to_string(),
367                                    ty: UIFieldType::Checkbox { disabled: false },
368                                    value: UIFieldValue::Checkbox(!component.plugin_blacklist.contains(&name))
369                                }
370                            }).collect()
371                    })
372                }
373            );
374
375            fields.push(
376                UIValue {
377                    name: "to_cache".to_string(),
378                    display_name: "Caching".to_string(),
379                    description: "If renderer should cache render result or not. Caching might use a lot of RAM, no caching will use a lot more CPU".to_string(),
380                    ty: UIFieldType::Checkbox {
381                        disabled: false
382                    },
383                    value: UIFieldValue::Checkbox(component.to_cache)
384                }
385            );
386        }
387
388        fields
389    } else {
390        vec![]
391    }
392}
393
394
395/// Sets component values for the renderer in specified button
396pub async fn set_renderer_component_values(core: &CoreHandle, button: &mut Button, value: Vec<UIValue>) {
397    if let Ok(mut component) = parse_button_to_component::<RendererComponent>(button) {
398        let change_map = map_ui_values(value);
399
400        if let Some(value) = change_map.get("renderer") {
401            if let Ok(value) = value.value.try_into_string() {
402                if value == "default" {
403                    component.renderer = "".to_string();
404                } else {
405                    if let Some(_) = core.core.render_manager.read_renderers().await.get(&value) {
406                        component.renderer = value;
407                    }
408                }
409            }
410        }
411
412        if !component.renderer.is_empty() {
413            if let Some(renderer) = core.core.render_manager.read_renderers().await.get(&component.renderer).cloned() {
414                renderer.set_component_value(button, &mut component, core, change_map.values().cloned().collect()).await;
415            }
416        } else {
417            if let Some(value) = change_map.get("background_params") {
418                if let UIFieldValue::Collapsable(value) = &value.value {
419                    let change_map = map_ui_values(value.clone());
420
421                    // Setting background type
422                    if let Some(value) = change_map.get("background") {
423                        if let Ok(choice) = value.value.try_into_string() {
424                            match choice.as_str() {
425                                "Solid Color" => component.background = ButtonBackground::Solid((0, 0, 0, 255)),
426                                "Horizontal Gradient" => component.background = ButtonBackground::HorizontalGradient((0, 0, 0, 255), (0, 0, 0, 255)),
427                                "Vertical Gradient" => component.background = ButtonBackground::VerticalGradient((0, 0, 0, 255), (0, 0, 0, 255)),
428                                "Existing Image" => component.background = ButtonBackground::ExistingImage("".to_string()),
429                                "New Image" => component.background = ButtonBackground::NewImage("".to_string()),
430
431                                _ => {}
432                            }
433                        }
434                    }
435
436                    // Background type related parameters
437                    if let Some(value) = change_map.get("color") {
438                        if let ButtonBackground::Solid(_) = component.background {
439                            if let Ok(color) = (&value.value).try_into() {
440                                component.background = ButtonBackground::Solid(color);
441                            }
442                        }
443                    }
444
445                    if let Some(value) = change_map.get("start_color") {
446                        if let ButtonBackground::HorizontalGradient(_, end) = component.background {
447                            if let Ok(color) = (&value.value).try_into() {
448                                component.background = ButtonBackground::HorizontalGradient(color, end);
449                            }
450                        }
451
452                        if let ButtonBackground::VerticalGradient(_, end) = component.background {
453                            if let Ok(color) = (&value.value).try_into() {
454                                component.background = ButtonBackground::VerticalGradient(color, end);
455                            }
456                        }
457                    }
458
459                    if let Some(value) = change_map.get("end_color") {
460                        if let ButtonBackground::HorizontalGradient(start, _) = component.background {
461                            if let Ok(color) = (&value.value).try_into() {
462                                component.background = ButtonBackground::HorizontalGradient(start, color);
463                            }
464                        }
465
466                        if let ButtonBackground::VerticalGradient(start, _) = component.background {
467                            if let Ok(color) = (&value.value).try_into() {
468                                component.background = ButtonBackground::VerticalGradient(start, color);
469                            }
470                        }
471                    }
472
473                    if let Some(value) = change_map.get("image") {
474                        match &component.background {
475                            ButtonBackground::ExistingImage(_) => {
476                                if let Ok(identifier) = (&value.value).try_into() {
477                                    component.background = ButtonBackground::ExistingImage(identifier);
478                                }
479                            }
480
481                            ButtonBackground::NewImage(_) => {
482                                if let Ok(blob) = (&value.value).try_into_string() {
483                                    let identifier = hash_str(&blob);
484
485                                    if let Ok(image) = SDImage::from_base64(&blob, core.core.image_size).await {
486                                        component.background = ButtonBackground::ExistingImage(identifier.clone());
487
488                                        let mut handle = core.core.image_collection.write().await;
489                                        handle.insert(identifier, image);
490                                    } else {
491                                        component.background = ButtonBackground::NewImage("".to_string());
492                                    }
493                                }
494                            }
495
496                            _ => {}
497                        }
498                    }
499                }
500            }
501
502            if let Some(value) = change_map.get("text_params") {
503                if let UIFieldValue::Collapsable(value) = &value.value {
504                    let change_map = map_ui_values(value.clone());
505
506                    if let Some(value) = change_map.get("text") {
507                        if let UIFieldValue::Array(items) = &value.value {
508                            component.text = vec![];
509
510                            fn get_text_object(item: &Vec<UIValue>) -> Option<ButtonText> {
511                                let map = map_ui_values_ref(item);
512
513                                Some(ButtonText {
514                                    text: (&map.get("text")?.value).try_into().ok()?,
515                                    font: (&map.get("font")?.value).try_into().ok()?,
516                                    scale: (&map.get("scale")?.value).try_into().ok()?,
517                                    alignment: TextAlignment::from_str(&map.get("alignment")?.value.try_into_string().ok()?).ok()?,
518                                    padding: (&map.get("padding")?.value).try_into().ok()?,
519                                    offset: (&map.get("offset")?.value).try_into_f32_f32().ok()?,
520                                    color: (&map.get("color")?.value).try_into().ok()?,
521                                    shadow: if let Some(bool) = map.get("shadow_enabled")?.value.try_into_bool().ok() {
522                                        let get_shadow = || {
523                                            Some(ButtonTextShadow {
524                                                offset: (&map.get("shadow_offset")?.value).try_into().ok()?,
525                                                color: (&map.get("shadow_color")?.value).try_into().ok()?
526                                            })
527                                        };
528
529                                        if bool {
530                                            get_shadow().or(Some(ButtonTextShadow {
531                                                offset: (0, 0),
532                                                color: (0, 0, 0, 0)
533                                            }))
534                                        } else {
535                                            None
536                                        }
537                                    } else {
538                                        None
539                                    }
540                                })
541                            }
542
543                            for item in items {
544                                if let Some(object) = get_text_object(item) {
545                                    component.text.push(object)
546                                }
547                            }
548                        }
549                    }
550                }
551            }
552
553            if let Some(value) = change_map.get("plugin_blacklist") {
554                if let UIFieldValue::Collapsable(value) = &value.value {
555                    let change_map = map_ui_values(value.clone());
556
557                    for (name, value) in change_map {
558                        if let UIFieldValue::Checkbox(state) = value.value {
559                            if state {
560                                component.plugin_blacklist.retain(|x| *x != name);
561                            } else {
562                                component.plugin_blacklist.push(name);
563                            }
564                        }
565                    }
566                }
567            }
568
569            if let Some(value) = change_map.get("to_cache") {
570                if let Ok(value) = value.value.try_into_bool() {
571                    component.to_cache = value;
572                }
573            }
574        }
575
576        // Apply changes to button
577        button.insert_component(component).ok();
578
579        core.core.mark_for_redraw().await;
580    }
581}