beuvy_runtime/button/
build.rs1use 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}