Skip to main content

beuvy_runtime/button/
build.rs

1use super::{AddButton, Button, ButtonInner, ButtonLabel, DisabledButton, state};
2use crate::build_pending::UiBuildPending;
3use crate::focus::{UiFocusable, hidden_outline};
4use crate::interaction_style::{
5    UiDisabled, UiStateStyleSource, pointer_cancel, pointer_drag_end, pointer_release,
6};
7use crate::style::{
8    apply_utility_patch, resolve_classes_with_fallback, root_visual_styles_from_patch,
9    text_primary_color, text_visual_styles_from_patch,
10};
11use crate::text::{AddText, typography_from_patch};
12use bevy::picking::Pickable;
13use bevy::prelude::*;
14use bevy::text::TextLayout;
15
16const DEFAULT_BUTTON_CLASS: &str = "button-root";
17const DEFAULT_BUTTON_LABEL_CLASS: &str = "button-label";
18
19pub(super) fn add_button(mut commands: Commands, query: Query<(Entity, &AddButton)>) {
20    for (entity, add_button) in query {
21        let visibility = if add_button.visible {
22            Visibility::Visible
23        } else {
24            Visibility::Hidden
25        };
26        let button_patch = resolve_classes_with_fallback(
27            DEFAULT_BUTTON_CLASS,
28            add_button.class.as_deref(),
29            "button root",
30        );
31        let mut button_node = default_button_node();
32        apply_utility_patch(&mut button_node, &button_patch);
33        let root_styles = root_visual_styles_from_patch(&button_patch);
34
35        let label_patch = resolve_classes_with_fallback(
36            DEFAULT_BUTTON_LABEL_CLASS,
37            add_button
38                .label_class
39                .as_deref()
40                .or(add_button.class.as_deref()),
41            "button label",
42        );
43        let label_styles = text_visual_styles_from_patch(&label_patch);
44
45        let add_button = add_button.clone();
46        commands
47            .entity(entity)
48            .queue_silenced(move |mut entity: EntityWorldMut| {
49                entity.insert((
50                    Button {
51                        name: add_button.name.clone(),
52                        button_type: add_button.button_type,
53                    },
54                    Interaction::None,
55                    button_node,
56                    BackgroundColor(Color::NONE),
57                    UiFocusable,
58                    hidden_outline(),
59                    visibility,
60                ));
61                if let Some(styles) = root_styles.clone() {
62                    entity.insert(styles);
63                }
64
65                let source = entity.id();
66                let child = entity.world_scope(|world| {
67                    let mut child = world.spawn((
68                        ButtonInner,
69                        Pickable::IGNORE,
70                        Node::default(),
71                        TextLayout::new_with_no_wrap(),
72                        build_button_text(&add_button, &label_patch),
73                    ));
74                    if let Some(styles) = label_styles.clone() {
75                        child.insert((styles, UiStateStyleSource(source)));
76                    }
77                    child.id()
78                });
79
80                entity.add_child(child);
81                entity.insert(ButtonLabel { entity: child });
82
83                if add_button.disabled {
84                    entity.insert((DisabledButton, UiDisabled));
85                } else {
86                    entity.remove::<DisabledButton>().remove::<UiDisabled>();
87                }
88
89                entity
90                    .observe(state::button_hover_over)
91                    .observe(state::button_hover_out)
92                    .observe(state::button_press)
93                    .observe(pointer_release)
94                    .observe(pointer_cancel)
95                    .observe(pointer_drag_end)
96                    .observe(state::button_click);
97
98                entity.remove::<AddButton>().remove::<UiBuildPending>();
99            });
100    }
101}
102
103pub fn default_button_node() -> Node {
104    Node::default()
105}
106
107fn build_button_text(
108    add_button: &AddButton,
109    label_patch: &crate::utility::UtilityStylePatch,
110) -> AddText {
111    let base_typography = typography_from_patch(label_patch, add_button.label_typography.clone());
112    let base = AddText {
113        text: add_button.text.clone(),
114        size: base_typography.font_size,
115        color: label_patch
116            .visual
117            .text_color
118            .as_deref()
119            .and_then(crate::style::resolve_color_value)
120            .unwrap_or_else(text_primary_color),
121        ..default()
122    }
123    .typography(base_typography);
124
125    match (
126        add_button.localized_text_format.clone(),
127        add_button.localized_text,
128    ) {
129        (Some(localized_text_format), _) => base.with_localized_format(localized_text_format),
130        (None, Some(localized_text)) => base.with_localized(localized_text),
131        (None, None) => base,
132    }
133}