famiq/widgets/
mod.rs

1//! Famiq's built-in widgets.
2
3pub mod scroll;
4pub mod button;
5pub mod color;
6pub mod container;
7pub mod fps;
8pub mod selection;
9pub mod style;
10pub mod style_parse;
11pub mod text;
12pub mod text_input;
13pub mod circular;
14pub mod dialog;
15pub mod image;
16pub mod progress_bar;
17pub mod checkbox;
18pub mod tests;
19pub mod base_components;
20
21pub(crate) use scroll::ScrollMovePanelEntity;
22pub use base_components::*;
23
24use crate::resources::*;
25use crate::reactivity::*;
26use crate::utils::*;
27
28use bevy::ecs::system::{EntityCommands, SystemParam};
29use bevy::platform::collections::HashMap;
30use bevy::ecs::query::QueryData;
31use std::cell::RefCell;
32use bevy::prelude::*;
33
34pub trait SetupWidget {
35    /// get components required for widget.
36    fn components(&mut self) -> impl Bundle;
37
38    /// build/spawn the widget into UI world.
39    fn build(
40        &mut self,
41        reactive_data: &HashMap<String, RVal>,
42        commands: &mut Commands
43    ) -> Entity;
44
45    /// build/spawn the widget into UI world using world instead of commands.
46    fn rebuild(
47        &mut self,
48        reactive_data: &HashMap<String, RVal>,
49        old_entity: Entity,
50        world: &mut World
51    );
52}
53
54/// Built-in color variants. These colors can be set via class.
55#[derive(Clone, Default, PartialEq, Debug)]
56pub enum WidgetColor {
57    #[default]
58    Default, // White or Light
59    Dark,
60    Primary,
61    PrimaryDark,
62    Secondary,
63    Success,
64    SuccessDark,
65    Danger,
66    DangerDark,
67    Warning,
68    WarningDark,
69    Info,
70    InfoDark,
71    Transparent,
72    Custom(String),
73    CustomSrgba((f32, f32, f32, f32))
74}
75
76/// Built-in size variants. These sizes can be set via class.
77#[derive(Clone, Copy, Default, PartialEq, Debug)]
78pub enum WidgetSize {
79    #[default]
80    Default,
81    Small,
82    Large,
83    Custom(f32)
84}
85
86#[derive(Default, Clone, Debug)]
87pub struct WidgetAttributes {
88    pub id: Option<String>,
89    pub class: Option<String>,
90    pub node: Node,
91    pub color: WidgetColor,
92    pub size: WidgetSize,
93    pub width: Option<String>,
94    pub height: Option<String>,
95    pub display: Option<String>,
96    pub font_handle: Option<Handle<Font>>,
97    pub image_handle: Option<Handle<Image>>,
98    pub has_tooltip: bool,
99    pub tooltip_text: String,
100    pub model_key: Option<String>,
101    pub class_split: Vec<String>,
102    pub border_radius: BorderRadius,
103    pub(crate) default_visibility: Visibility,
104    pub(crate) default_z_index: ZIndex,
105    pub(crate) overrided_background_color: Option<Color>,
106    pub(crate) overrided_border_color: Option<Color>,
107    pub(crate) override_text_size: Option<f32>
108}
109
110pub trait SetWidgetAttributes: Sized {
111    fn attributes(&mut self) -> &mut WidgetAttributes;
112
113    fn cloned_attrs(&mut self) -> &mut WidgetAttributes;
114
115    fn set_model(&mut self, model_key: &str) {
116        self.attributes().model_key = Some(model_key.to_string());
117    }
118
119    fn set_id(&mut self, id: &str) {
120        self.attributes().id = Some(id.to_string());
121    }
122
123    fn set_class(&mut self, class: &str) {
124        self.attributes().class = Some(class.to_string());
125        self.attributes().class_split = class.split_whitespace().map(|s| s.to_string()).collect();
126    }
127
128    fn set_color(&mut self, color: &str) {
129        self.attributes().color = WidgetColor::Custom(color.to_string());
130    }
131
132    fn set_size(&mut self, size: f32) {
133        self.attributes().size = WidgetSize::Custom(size);
134    }
135
136    fn set_width(&mut self, width: &str) {
137        self.attributes().width = Some(width.to_string());
138    }
139
140    fn set_height(&mut self, height: &str) {
141        self.attributes().height = Some(height.to_string());
142    }
143
144    fn set_display(&mut self, display: &str) {
145        self.attributes().display = Some(display.to_string());
146    }
147
148    fn set_tooltip(&mut self, text: &str) {
149        self.attributes().has_tooltip = true;
150        self.attributes().tooltip_text = text.to_string();
151    }
152
153    fn _process_built_in_color_class(&mut self) {
154        if self.cloned_attrs().color != WidgetColor::Default {
155            return;
156        }
157        let mut use_color = WidgetColor::Default;
158        for class_name in self.cloned_attrs().class_split.iter() {
159            match class_name.as_str() {
160                "dark" => use_color = WidgetColor::Dark,
161                "primary" => use_color = WidgetColor::Primary,
162                "primary-dark" => use_color = WidgetColor::PrimaryDark,
163                "secondary" => use_color = WidgetColor::Secondary,
164                "danger" => use_color = WidgetColor::Danger,
165                "danger-dark" => use_color = WidgetColor::DangerDark,
166                "success" => use_color = WidgetColor::Success,
167                "success-dark" => use_color= WidgetColor::SuccessDark,
168                "warning" => use_color = WidgetColor::Warning,
169                "warning-dark" => use_color = WidgetColor::WarningDark,
170                "info" => use_color = WidgetColor::Info,
171                "info-dark" => use_color = WidgetColor::InfoDark,
172                _ => {}
173            }
174        }
175        self.cloned_attrs().color = use_color;
176    }
177
178    fn _process_built_in_alignment_class(&mut self) {
179        let class_split: Vec<String> = self.cloned_attrs().class_split.clone();
180
181        for class_name in class_split.iter() {
182            match class_name.as_str() {
183                // JustifyContent
184                "jc-start" => self.cloned_attrs().node.justify_content = JustifyContent::Start,
185                "jc-end" => self.cloned_attrs().node.justify_content = JustifyContent::End,
186                "jc-flex-start" => self.cloned_attrs().node.justify_content = JustifyContent::FlexStart,
187                "jc-flex-end" => self.cloned_attrs().node.justify_content = JustifyContent::FlexEnd,
188                "jc-center" => self.cloned_attrs().node.justify_content = JustifyContent::Center,
189                "jc-stretch" => self.cloned_attrs().node.justify_content = JustifyContent::Stretch,
190                "jc-space-between" => self.cloned_attrs().node.justify_content = JustifyContent::SpaceBetween,
191                "jc-space-evenly" => self.cloned_attrs().node.justify_content = JustifyContent::SpaceEvenly,
192                "jc-space-around" => self.cloned_attrs().node.justify_content = JustifyContent::SpaceAround,
193
194                // JustifyItems
195                "ji-start" => self.cloned_attrs().node.justify_items = JustifyItems::Start,
196                "ji-end" => self.cloned_attrs().node.justify_items = JustifyItems::End,
197                "ji-center" => self.cloned_attrs().node.justify_items = JustifyItems::Center,
198                "ji-stretch" => self.cloned_attrs().node.justify_items = JustifyItems::Stretch,
199                "ji-base-line" => self.cloned_attrs().node.justify_items = JustifyItems::Baseline,
200
201                // JustifySelf
202                "js-start" => self.cloned_attrs().node.justify_self = JustifySelf::Start,
203                "js-end" => self.cloned_attrs().node.justify_self = JustifySelf::End,
204                "js-center" => self.cloned_attrs().node.justify_self = JustifySelf::Center,
205                "js-stretch" => self.cloned_attrs().node.justify_self = JustifySelf::Stretch,
206                "js-base-line" => self.cloned_attrs().node.justify_self = JustifySelf::Baseline,
207
208                // AlignContent
209                "ac-start" => self.cloned_attrs().node.align_content = AlignContent::Start,
210                "ac-end" => self.cloned_attrs().node.align_content = AlignContent::End,
211                "ac-flex-start" => self.cloned_attrs().node.align_content = AlignContent::FlexStart,
212                "ac-flex-end" => self.cloned_attrs().node.align_content = AlignContent::FlexEnd,
213                "ac-center" => self.cloned_attrs().node.align_content = AlignContent::Center,
214                "ac-stretch" => self.cloned_attrs().node.align_content = AlignContent::Stretch,
215                "ac-space-between" => self.cloned_attrs().node.align_content = AlignContent::SpaceBetween,
216                "ac-space-evenly" => self.cloned_attrs().node.align_content = AlignContent::SpaceEvenly,
217                "ac-space-around" => self.cloned_attrs().node.align_content = AlignContent::SpaceAround,
218
219                // AlignItems
220                "ai-start" => self.cloned_attrs().node.align_items = AlignItems::Start,
221                "ai-end" => self.cloned_attrs().node.align_items = AlignItems::End,
222                "ai-flex-start" => self.cloned_attrs().node.align_items = AlignItems::FlexStart,
223                "ai-flex-end" => self.cloned_attrs().node.align_items = AlignItems::FlexEnd,
224                "ai-center" => self.cloned_attrs().node.align_items = AlignItems::Center,
225                "ai-stretch" => self.cloned_attrs().node.align_items = AlignItems::Stretch,
226                "ai-base-line" => self.cloned_attrs().node.align_items = AlignItems::Baseline,
227
228                // AlignSelf
229                "as-start" => self.cloned_attrs().node.align_self = AlignSelf::Start,
230                "as-end" => self.cloned_attrs().node.align_self = AlignSelf::End,
231                "as-flex-start" => self.cloned_attrs().node.align_self = AlignSelf::FlexStart,
232                "as-flex-end" => self.cloned_attrs().node.align_self = AlignSelf::FlexEnd,
233                "as-center" => self.cloned_attrs().node.align_self = AlignSelf::Center,
234                "as-stretch" => self.cloned_attrs().node.align_self = AlignSelf::Stretch,
235                "as-base-line" => self.cloned_attrs().node.align_self = AlignSelf::Baseline,
236                _ => {}
237            }
238        }
239    }
240
241    fn _process_built_in_spacing_class(&mut self) {
242        let class_split: Vec<String> = self.cloned_attrs().class_split.clone();
243        let node = &mut self.cloned_attrs().node;
244
245        for class_name in class_split.iter() {
246            if let Some((prefix, value)) = class_name.split_once('-') {
247
248                let spacing_value = if value == "auto" {
249                    Val::Auto
250                } else if let Ok(num) = value.parse::<f32>() {
251                    Val::Px(num * 5.0)
252                } else {
253                    continue;
254                };
255
256                match prefix {
257                    // Margin classes
258                    "mt" => node.margin.top = spacing_value,
259                    "mb" => node.margin.bottom = spacing_value,
260                    "ml" => node.margin.left = spacing_value,
261                    "mr" => node.margin.right = spacing_value,
262                    "my" => {
263                        node.margin.top = spacing_value;
264                        node.margin.bottom = spacing_value;
265                    }
266                    "mx" => {
267                        node.margin.left = spacing_value;
268                        node.margin.right = spacing_value;
269                    }
270
271                    // Padding classes
272                    "pt" => node.padding.top = spacing_value,
273                    "pb" => node.padding.bottom = spacing_value,
274                    "pl" => node.padding.left = spacing_value,
275                    "pr" => node.padding.right = spacing_value,
276                    "py" => {
277                        node.padding.top = spacing_value;
278                        node.padding.bottom = spacing_value;
279                    }
280                    "px" => {
281                        node.padding.left = spacing_value;
282                        node.padding.right = spacing_value;
283                    }
284                    _ => {}
285                }
286            }
287        }
288    }
289
290    // ref: https://vuetifyjs.com/en/styles/border-radius/#usage
291    fn _process_built_in_border_radius_class(&mut self) {
292        let class_split: Vec<String> = self.cloned_attrs().class_split.clone();
293        let br = &mut self.cloned_attrs().border_radius;
294
295        // default
296        *br = BorderRadius::all(Val::Px(5.0));
297
298        for class_name in class_split.iter() {
299            match class_name.as_str() {
300                "rounded-0" => *br = BorderRadius::all(Val::Px(0.0)),
301                "rounded-sm" => *br = BorderRadius::all(Val::Px(2.0)),
302                "rounded-lg" => *br = BorderRadius::all(Val::Px(8.0)),
303                "rounded-xl" => *br = BorderRadius::all(Val::Px(24.0)),
304                "rounded-pill" => *br = BorderRadius::all(Val::Px(9999.0)),
305                "rounded-circle" => *br = BorderRadius::all(Val::Percent(50.0)),
306                _ => {}
307            }
308        }
309    }
310
311    fn _process_built_in_size_class(&mut self) {
312        if self.cloned_attrs().size != WidgetSize::Default {
313            return;
314        }
315        let mut use_size = WidgetSize::Default;
316        for class_name in self.cloned_attrs().class_split.iter() {
317            match class_name.as_str() {
318                "small" => use_size = WidgetSize::Small,
319                "large" => use_size = WidgetSize::Large,
320                _ => {}
321            }
322        }
323        self.cloned_attrs().size = use_size;
324    }
325}
326
327#[derive(Copy, Clone, Debug, PartialEq, Component)]
328pub enum WidgetType {
329    Root, // globalzindex 1
330    Button,
331    Container,
332    Text,
333    FpsText, // globalzindex 6
334    TextInput,
335    Scroll,
336    Selection,
337    Circular,
338    ProgressBar,
339    Dialog, // globalzindex 5
340    Image,
341    BackgroudImage
342}
343
344/// Root builder, allows access to AssetServer, root_node, FamiqResource and RData.
345pub struct FamiqBuilder<'a> {
346    pub asset_server: &'a Res<'a, AssetServer>,
347    pub ui_root_node: EntityCommands<'a>,
348    pub resource: Mut<'a, FamiqResource>,
349    pub reactive_data: Mut<'a, RData>,
350    // pub containable_children: Mut<'a, ContainableChildren>
351}
352
353impl<'a> FamiqBuilder<'a> {
354    /// Create new root builder.
355    pub fn new(fa_query: &'a mut FaQuery, famiq_resource: &'a mut ResMut<FamiqResource>) -> Self {
356        Self {
357            asset_server: &fa_query.asset_server,
358            ui_root_node: fa_query.commands.entity(famiq_resource.root_node_entity.unwrap()),
359            resource: famiq_resource.reborrow(),
360            reactive_data: fa_query.reactive_data.reborrow(),
361            // containable_children: fa_query.containable_children.reborrow()
362        }
363    }
364
365    /// Inject root builder for global access.
366    pub fn inject(self) {
367        let boxed = Box::new(self);
368        let raw = Box::into_raw(boxed); // *mut FamiqBuilder<'a>
369        inject_builder(raw as *mut ());
370    }
371
372    /// Method to use custom font
373    ///
374    /// # Arguments
375    ///
376    /// * `font_path` - A path to the font, relative to Bevy's `assets/` folder.
377    ///
378    /// # Examples
379    ///
380    /// ## Normal Project Structure
381    ///
382    /// ```text
383    /// my_project/
384    /// ├── assets/
385    /// │   ├── fonts/
386    /// │   │   ├── Some-font.ttf
387    /// ├── src/
388    /// ```
389    ///
390    /// ```text
391    /// builder.use_font_path("fonts/Some-font.ttf");
392    /// ```
393    ///
394    /// ## Multi-Crate / Workspace Structure
395    ///
396    /// In a multi-crate workspace, the custom font path is read from the subcrate/member's `assets/` folder:
397    ///
398    /// ```text
399    /// my_project/
400    /// ├── sub_crate_1/
401    /// │   ├── assets/
402    /// │   │   ├── fonts/
403    /// │   │   │   ├── Some-font.ttf
404    /// │   ├── src/
405    /// ├── sub_crate_2/
406    /// │   ├── assets/
407    /// │   ├── src/
408    /// ```
409    ///
410    /// ```text
411    /// // Inside subcrate 1
412    /// builder.use_font_path("fonts/Some-font.ttf");
413    /// ```
414    pub fn use_font_path(mut self, font_path: &str) -> Self {
415        self.resource.font_path = font_path.to_string();
416        self
417    }
418
419    /// Method to use custom style file path.
420    ///
421    /// # Argument for native build
422    /// * style_path: Full path to the json file, relative to root directory.
423    ///
424    /// # Argument for wasm build
425    /// * style_path: Full path to json file, relative to assets directory.
426    pub fn use_style_path(mut self, style_path: &str) -> Self {
427        self.resource.style_path = style_path.to_string();
428        self
429    }
430
431    /// Method to enable hot-reload.
432    /// Should be called only for native builds (exclude wasm).
433    pub fn hot_reload(mut self) -> Self {
434        self.resource.hot_reload_styles = true;
435        self
436    }
437
438    /// Method to get font handle from provided font_path.
439    pub fn get_font_handle(&self) -> Handle<Font> {
440        self.asset_server.load(&self.resource.font_path)
441    }
442
443    pub fn insert_component<T: Bundle>(&mut self, entity: Entity, components: T) {
444        self.ui_root_node.commands().entity(entity).insert(components);
445    }
446
447    pub fn remove_component<T: Bundle>(&mut self, entity: Entity) {
448        self.ui_root_node.commands().entity(entity).remove::<T>();
449    }
450
451    pub fn get_entity(&mut self) -> Entity {
452        self.ui_root_node.id()
453    }
454
455    pub fn clean(&mut self) {
456        let root_entity = self.get_entity();
457        self.ui_root_node.commands().entity(root_entity).despawn();
458    }
459}
460
461pub fn hot_reload_is_enabled(famiq_res: Res<FamiqResource>) -> bool {
462    famiq_res.hot_reload_styles
463}
464
465pub fn hot_reload_is_disabled(famiq_res: Res<FamiqResource>) -> bool {
466    !famiq_res.hot_reload_styles && !famiq_res.external_style_applied
467}
468
469pub(crate) fn build_tooltip_node(
470    attributes: &WidgetAttributes,
471    commands: &mut Commands,
472    widget_entity: Entity
473) -> Entity {
474    let txt_font = TextFont {
475        font: attributes.font_handle.clone().unwrap(),
476        font_size: get_text_size(&attributes.size),
477        ..default()
478    };
479    let tooltip_entity = commands
480        .spawn((
481            Node {
482                position_type: PositionType::Absolute,
483                top: Val::Px(-28.0),
484                width: Val::Auto,
485                height: Val::Auto,
486                display: Display::None,
487                max_width: Val::Px(200.),
488                padding: UiRect {
489                    left: Val::Px(8.0),
490                    right: Val::Px(8.0),
491                    ..default()
492                },
493                margin: UiRect{
494                    left: Val::Auto,
495                    right: Val::Auto,
496                    ..default()
497                },
498                ..default()
499            },
500            GlobalZIndex(4),
501            BackgroundColor(Color::srgba(1.0, 1.0, 1.0, 0.6)),
502            BorderRadius::all(Val::Px(5.0)),
503            Transform::default(),
504            Text::new(&attributes.tooltip_text),
505            txt_font,
506            TextColor(color::BLACK_COLOR),
507            TextLayout::new_with_justify(JustifyText::Center),
508            IsFamiqTooltip
509        ))
510        .id();
511
512    commands
513        .entity(widget_entity)
514        .add_child(tooltip_entity)
515        .insert(TooltipEntity(tooltip_entity));
516
517    tooltip_entity
518}
519
520pub enum WidgetSelector<'a> {
521    ID(&'a str),
522    ENTITY(Entity)
523}
524
525/// Widget style query
526#[derive(QueryData)]
527#[query_data(mutable)]
528pub struct StyleQuery {
529    pub background_color: &'static mut BackgroundColor,
530    pub border_color: &'static mut BorderColor,
531    pub border_radius: &'static mut BorderRadius,
532    pub z_index: &'static mut ZIndex,
533    pub visibility: &'static mut Visibility,
534    pub box_shadow: &'static mut BoxShadow,
535    pub node: &'static mut Node,
536    pub id: Option<&'static WidgetId>,
537    pub class: Option<&'static WidgetClasses>,
538    pub default_style: &'static DefaultWidgetConfig
539}
540
541/// Text style query
542#[derive(QueryData)]
543#[query_data(mutable)]
544pub struct TextStyleQuery {
545    pub text_color: &'static mut TextColor,
546    pub text_font: &'static mut TextFont,
547    pub id: Option<&'static WidgetId>,
548    pub class: Option<&'static WidgetClasses>,
549    pub default_text_style: Option<&'static DefaultTextConfig>,
550    pub default_text_span_style: Option<&'static DefaultTextSpanConfig>,
551}
552
553/// Query for getting/updating widget's styles and text's styles.
554#[derive(SystemParam)]
555pub struct FaStyleQuery<'w, 's> {
556    pub style_query: Query<'w, 's, StyleQuery>,
557    pub text_style_query: Query<'w, 's, TextStyleQuery>,
558}
559
560impl<'w, 's> FaStyleQuery<'w, 's> {
561    /// Get `StyleQueryItem` based on `WidgetSelector`
562    pub fn get_style_mut(&mut self, selector: WidgetSelector) -> Option<StyleQueryItem<'_>> {
563        match selector {
564            WidgetSelector::ID(id) => self
565                .style_query
566                .iter_mut()
567                .find_map(|result| {
568                    result.id
569                        .filter(|w_id| w_id.0 == id)
570                        .map(|_| result)
571                }),
572
573            WidgetSelector::ENTITY(entity) => self.style_query.get_mut(entity).ok(),
574        }
575    }
576
577    /// Get `TextStyleQueryItem` based on `WidgetSelector`
578    pub fn get_text_style_mut(&mut self, selector: WidgetSelector) -> Option<TextStyleQueryItem<'_>> {
579        match selector {
580            WidgetSelector::ID(id) => self
581                .text_style_query
582                .iter_mut()
583                .find_map(|result| {
584                    result.id
585                        .filter(|w_id| w_id.0 == id)
586                        .map(|_| result)
587                }),
588
589            WidgetSelector::ENTITY(entity) => self.text_style_query.get_mut(entity).ok(),
590        }
591    }
592}
593
594/// Containable query for `container!`, `modal!` and `scroll!`.
595#[derive(QueryData)]
596#[query_data(mutable)]
597pub struct ContainableQuery {
598    entity: Entity,
599    scroll_panel: Option<&'static ScrollMovePanelEntity>,
600    id: Option<&'static WidgetId>
601}
602
603/// Famiq main query
604#[derive(SystemParam)]
605pub struct FaQuery<'w, 's> {
606    pub containable_query: Query<'w, 's, ContainableQuery, With<IsFamiqContainableWidget>>,
607    pub reactive_data: ResMut<'w, RData>,
608    pub commands: Commands<'w, 's>,
609    pub asset_server: Res<'w, AssetServer>,
610    pub reactive_subscriber: ResMut<'w, RSubscriber>,
611    // pub containable_children: ResMut<'w, ContainableChildren>
612}
613
614impl<'w, 's> FaQuery<'w, 's> {
615    /// Finds a `ContainableQueryReadOnlyItem` based on `WidgetSelector`
616    pub fn get_containable_item(&self, selector: WidgetSelector) -> Option<ContainableQueryReadOnlyItem<'_>> {
617        match selector {
618            WidgetSelector::ID(id) => self
619                .containable_query
620                .iter()
621                .find_map(|result| {
622                    result.id
623                        .filter(|w_id| w_id.0 == id)
624                        .map(|_| result)
625                }),
626
627            WidgetSelector::ENTITY(entity) => self.containable_query.get(entity).ok(),
628        }
629    }
630
631    /// Add child/children to containable widget
632    pub fn add_children(&mut self, selector: WidgetSelector, children: &[Entity]) {
633        if let Some(item) = self.get_containable_item(selector) {
634            if let Some(panel_entity) = item.scroll_panel {
635                self.commands
636                    .entity(panel_entity.0)
637                    .add_children(children);
638                return;
639            }
640            self.commands.entity(item.entity).add_children(children);
641        }
642    }
643
644    /// Insert child/children to containable widget at given index
645    pub fn insert_children(&mut self, selector: WidgetSelector, index: usize, children: &[Entity]) {
646        if let Some(item) = self.get_containable_item(selector) {
647            if let Some(panel_entity) = item.scroll_panel {
648                self.commands
649                    .entity(panel_entity.0)
650                    .insert_children(index, children);
651                return;
652            }
653            self.commands.entity(item.entity).insert_children(index, children);
654        }
655    }
656
657    /// Remove (despawn) children from containable widget.
658    pub fn remove_children(&mut self, children: &[Entity]) {
659        for child in children {
660            self.commands.entity(*child).despawn();
661        }
662    }
663
664    /// Insert new key-value into reactive data.
665    pub fn insert_data(&mut self, key: &str, value: RVal) {
666        self.reactive_data.data.insert(key.to_string(), value);
667    }
668
669    /// Insert into reactive data as RVal::Str
670    pub fn insert_str(&mut self, key: &str, value: impl Into<String>) {
671        self.insert_data(key, RVal::Str(value.into()));
672    }
673
674    /// Insert into reactive data as RVal::None
675    pub fn insert_none(&mut self, key: &str) {
676        self.insert_data(key, RVal::None);
677    }
678
679    /// Insert into reactive data as Rval::Num
680    pub fn insert_num(&mut self, key: &str, value: i32) {
681        self.insert_data(key, RVal::Num(value));
682    }
683
684    /// Insert into reactive data as Rval::Bool
685    pub fn insert_bool(&mut self, key: &str, value: bool) {
686        self.insert_data(key, RVal::Bool(value));
687    }
688
689    /// Insert into reactive data as Rval::FNum
690    pub fn insert_fnum(&mut self, key: &str, value: f32) {
691        self.insert_data(key, RVal::FNum(value));
692    }
693
694    /// Insert into reactive data as `Rval::List<String>`
695    pub fn insert_str_list(&mut self, key: &str, value: Vec<String>) {
696        self.insert_data(key, RVal::List(value));
697    }
698
699    /// Explicitly mutates specific key.
700    pub fn mutate_data(&mut self, key: &str, new_val: RVal) {
701        let old_val = self.reactive_data.data.get(key);
702        if old_val.is_none() {
703            panic!("\n[FamiqError]: mutate_data, key {:?} not found\n", key);
704        }
705        if !self.reactive_data.changed_keys.contains(&key.to_string()) {
706            self.reactive_data.changed_keys.push(key.to_string());
707        }
708        self.reactive_data.data.insert(key.to_string(), new_val);
709    }
710
711    /// Explicitly mutates specific key as RVal::Str
712    pub fn mutate_str(&mut self, key: &str, new_str: &str) {
713        self.mutate_data(key, RVal::Str(new_str.into()));
714    }
715
716    /// Explicitly mutates specific key as RVal::Num
717    pub fn mutate_num(&mut self, key: &str, new_num: i32) {
718        self.mutate_data(key, RVal::Num(new_num));
719    }
720
721    /// Explicitly mutates specific key as RVal::FNum
722    pub fn mutate_fnum(&mut self, key: &str, new_fnum: f32) {
723        self.mutate_data(key, RVal::FNum(new_fnum));
724    }
725
726    /// Explicitly mutates specific key as RVal::Bool
727    pub fn mutate_bool(&mut self, key: &str, new_bool: bool) {
728        self.mutate_data(key, RVal::Bool(new_bool));
729    }
730
731    /// Explicitly mutates specific key as RVal::None
732    pub fn mutate_none(&mut self, key: &str) {
733        self.mutate_data(key, RVal::None);
734    }
735
736    /// Explicitly mutates specific key as RVal::List<String>
737    pub fn mutate_str_list(&mut self, key: &str, new_list: Vec<String>) {
738        self.mutate_data(key, RVal::List(new_list));
739    }
740
741    /// Get value of provided key.
742    pub fn get_data(&self, key: &str) -> Option<&RVal> {
743        if let Some(val) = self.reactive_data.data.get(key) {
744            return Some(val);
745        }
746        None
747    }
748
749    /// Get mutable value of provided key.
750    pub fn get_data_mut(&mut self, key: &str) -> Option<&mut RVal> {
751        if self.get_data(key).is_none() {
752            return None;
753        }
754        if !self.reactive_data.changed_keys.contains(&key.to_string()) {
755            self.reactive_data.changed_keys.push(key.to_string());
756        }
757        self.reactive_data.data.get_mut(key)
758    }
759}
760
761/// Macro to extract children's entities from children attributes.
762#[macro_export]
763macro_rules! extract_children {
764    // For children: [ item1, item2, item3 ]
765    ($vec:ident, children: [ $( $child:expr ),* $(,)? ] $(, $($rest:tt)*)?) => {{
766        $(
767            $vec.push($child);
768        )*
769        $(
770            $crate::extract_children!($vec, $($rest)*);
771        )?
772    }};
773
774    // For children: vec!
775    ($vec:ident, children: $children_vec:expr $(, $($rest:tt)*)?) => {{
776        $vec.extend($children_vec);
777        $(
778            $crate::extract_children!($vec, $($rest)*);
779        )?
780    }};
781
782    // other keys
783    ($vec:ident, $key:ident : $val:expr $(, $($rest:tt)*)?) => {{
784        $(
785            $crate::extract_children!($vec, $builder, $($rest)*);
786        )?
787    }};
788    ($vec:ident,) => {{}};
789}
790
791/// Macro for setting common attributes to a widget.
792#[macro_export]
793macro_rules! common_attributes {
794    ( $builder:ident, $key:ident : $value:expr ) => {{
795        match stringify!($key) {
796            "id" => $builder.set_id($value),
797            "class" => $builder.set_class($value),
798            "color" => $builder.set_color($value),
799            "tooltip" => $builder.set_tooltip($value),
800            "width" => $builder.set_width($value),
801            "height" => $builder.set_height($value),
802            "display" => $builder.set_display($value),
803            _ => {}
804        }
805    }};
806}
807
808/// Represent different type of widget's builder.
809#[derive(Clone, Debug)]
810pub enum BuilderType {
811    Text(text::TextBuilder),
812    Button(button::ButtonBuilder),
813    Checkbox(checkbox::CheckboxBuilder),
814    Circular(circular::CircularBuilder),
815    Container(container::ContainerBuilder),
816    Fps(fps::FpsBuilder),
817    Image(image::ImageBuilder),
818    Dialog(dialog::DialogBuilder),
819    ProgressBar(progress_bar::ProgressBarBuilder),
820    Selection(selection::SelectionBuilder),
821    Scroll(scroll::ScrollBuilder)
822}
823
824#[derive(Clone, Debug)]
825pub struct WidgetBuilder {
826    pub builder: BuilderType
827}
828
829thread_local! {
830    static GLOBAL_BUILDER: RefCell<Option<*mut ()>> = RefCell::new(None);
831}
832
833/// Inject root builder for global access.
834pub fn inject_builder(ptr: *mut ()) {
835    GLOBAL_BUILDER.with(|cell| {
836        *cell.borrow_mut() = Some(ptr);
837    });
838}
839
840/// Access to mutable reference root builder.
841pub fn builder_mut<'a>() -> &'a mut FamiqBuilder<'a> {
842    GLOBAL_BUILDER.with(|cell| {
843        let ptr = cell
844            .borrow()
845            .expect("Can't access global widget builder!") as *mut FamiqBuilder<'a>;
846        unsafe { &mut *ptr }
847    })
848}