1pub 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 fn components(&mut self) -> impl Bundle;
37
38 fn build(
40 &mut self,
41 reactive_data: &HashMap<String, RVal>,
42 commands: &mut Commands
43 ) -> Entity;
44
45 fn rebuild(
47 &mut self,
48 reactive_data: &HashMap<String, RVal>,
49 old_entity: Entity,
50 world: &mut World
51 );
52}
53
54#[derive(Clone, Default, PartialEq, Debug)]
56pub enum WidgetColor {
57 #[default]
58 Default, 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#[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 "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 "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 "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 "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 "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 "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 "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 "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 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 *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, Button,
331 Container,
332 Text,
333 FpsText, TextInput,
335 Scroll,
336 Selection,
337 Circular,
338 ProgressBar,
339 Dialog, Image,
341 BackgroudImage
342}
343
344pub 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 }
352
353impl<'a> FamiqBuilder<'a> {
354 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 }
363 }
364
365 pub fn inject(self) {
367 let boxed = Box::new(self);
368 let raw = Box::into_raw(boxed); inject_builder(raw as *mut ());
370 }
371
372 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 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 pub fn hot_reload(mut self) -> Self {
434 self.resource.hot_reload_styles = true;
435 self
436 }
437
438 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#[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#[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#[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 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 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#[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#[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 }
613
614impl<'w, 's> FaQuery<'w, 's> {
615 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 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 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 pub fn remove_children(&mut self, children: &[Entity]) {
659 for child in children {
660 self.commands.entity(*child).despawn();
661 }
662 }
663
664 pub fn insert_data(&mut self, key: &str, value: RVal) {
666 self.reactive_data.data.insert(key.to_string(), value);
667 }
668
669 pub fn insert_str(&mut self, key: &str, value: impl Into<String>) {
671 self.insert_data(key, RVal::Str(value.into()));
672 }
673
674 pub fn insert_none(&mut self, key: &str) {
676 self.insert_data(key, RVal::None);
677 }
678
679 pub fn insert_num(&mut self, key: &str, value: i32) {
681 self.insert_data(key, RVal::Num(value));
682 }
683
684 pub fn insert_bool(&mut self, key: &str, value: bool) {
686 self.insert_data(key, RVal::Bool(value));
687 }
688
689 pub fn insert_fnum(&mut self, key: &str, value: f32) {
691 self.insert_data(key, RVal::FNum(value));
692 }
693
694 pub fn insert_str_list(&mut self, key: &str, value: Vec<String>) {
696 self.insert_data(key, RVal::List(value));
697 }
698
699 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 pub fn mutate_str(&mut self, key: &str, new_str: &str) {
713 self.mutate_data(key, RVal::Str(new_str.into()));
714 }
715
716 pub fn mutate_num(&mut self, key: &str, new_num: i32) {
718 self.mutate_data(key, RVal::Num(new_num));
719 }
720
721 pub fn mutate_fnum(&mut self, key: &str, new_fnum: f32) {
723 self.mutate_data(key, RVal::FNum(new_fnum));
724 }
725
726 pub fn mutate_bool(&mut self, key: &str, new_bool: bool) {
728 self.mutate_data(key, RVal::Bool(new_bool));
729 }
730
731 pub fn mutate_none(&mut self, key: &str) {
733 self.mutate_data(key, RVal::None);
734 }
735
736 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 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 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_export]
763macro_rules! extract_children {
764 ($vec:ident, children: [ $( $child:expr ),* $(,)? ] $(, $($rest:tt)*)?) => {{
766 $(
767 $vec.push($child);
768 )*
769 $(
770 $crate::extract_children!($vec, $($rest)*);
771 )?
772 }};
773
774 ($vec:ident, children: $children_vec:expr $(, $($rest:tt)*)?) => {{
776 $vec.extend($children_vec);
777 $(
778 $crate::extract_children!($vec, $($rest)*);
779 )?
780 }};
781
782 ($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_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#[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
833pub fn inject_builder(ptr: *mut ()) {
835 GLOBAL_BUILDER.with(|cell| {
836 *cell.borrow_mut() = Some(ptr);
837 });
838}
839
840pub 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}