Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_feathers/src/controls/button.rs
6596 views
1
use bevy_app::{Plugin, PreUpdate};
2
use bevy_core_widgets::{Activate, Callback, CoreButton};
3
use bevy_ecs::{
4
bundle::Bundle,
5
component::Component,
6
entity::Entity,
7
hierarchy::{ChildOf, Children},
8
lifecycle::RemovedComponents,
9
query::{Added, Changed, Has, Or},
10
reflect::ReflectComponent,
11
schedule::IntoScheduleConfigs,
12
spawn::{SpawnRelated, SpawnableList},
13
system::{Commands, In, Query},
14
};
15
use bevy_input_focus::tab_navigation::TabIndex;
16
use bevy_picking::{hover::Hovered, PickingSystems};
17
use bevy_reflect::{prelude::ReflectDefault, Reflect};
18
use bevy_ui::{AlignItems, InteractionDisabled, JustifyContent, Node, Pressed, UiRect, Val};
19
20
use crate::{
21
constants::{fonts, size},
22
cursor::EntityCursor,
23
font_styles::InheritableFont,
24
handle_or_path::HandleOrPath,
25
rounded_corners::RoundedCorners,
26
theme::{ThemeBackgroundColor, ThemeFontColor},
27
tokens,
28
};
29
30
/// Color variants for buttons. This also functions as a component used by the dynamic styling
31
/// system to identify which entities are buttons.
32
#[derive(Component, Default, Clone, Reflect, Debug, PartialEq, Eq)]
33
#[reflect(Component, Clone, Default)]
34
pub enum ButtonVariant {
35
/// The standard button appearance
36
#[default]
37
Normal,
38
/// A button with a more prominent color, this is used for "call to action" buttons,
39
/// default buttons for dialog boxes, and so on.
40
Primary,
41
}
42
43
/// Parameters for the button template, passed to [`button`] function.
44
#[derive(Default)]
45
pub struct ButtonProps {
46
/// Color variant for the button.
47
pub variant: ButtonVariant,
48
/// Rounded corners options
49
pub corners: RoundedCorners,
50
/// Click handler
51
pub on_click: Callback<In<Activate>>,
52
}
53
54
/// Template function to spawn a button.
55
///
56
/// # Arguments
57
/// * `props` - construction properties for the button.
58
/// * `overrides` - a bundle of components that are merged in with the normal button components.
59
/// * `children` - a [`SpawnableList`] of child elements, such as a label or icon for the button.
60
pub fn button<C: SpawnableList<ChildOf> + Send + Sync + 'static, B: Bundle>(
61
props: ButtonProps,
62
overrides: B,
63
children: C,
64
) -> impl Bundle {
65
(
66
Node {
67
height: size::ROW_HEIGHT,
68
justify_content: JustifyContent::Center,
69
align_items: AlignItems::Center,
70
padding: UiRect::axes(Val::Px(8.0), Val::Px(0.)),
71
flex_grow: 1.0,
72
..Default::default()
73
},
74
CoreButton {
75
on_activate: props.on_click,
76
},
77
props.variant,
78
Hovered::default(),
79
EntityCursor::System(bevy_window::SystemCursorIcon::Pointer),
80
TabIndex(0),
81
props.corners.to_border_radius(4.0),
82
ThemeBackgroundColor(tokens::BUTTON_BG),
83
ThemeFontColor(tokens::BUTTON_TEXT),
84
InheritableFont {
85
font: HandleOrPath::Path(fonts::REGULAR.to_owned()),
86
font_size: 14.0,
87
},
88
overrides,
89
Children::spawn(children),
90
)
91
}
92
93
fn update_button_styles(
94
q_buttons: Query<
95
(
96
Entity,
97
&ButtonVariant,
98
Has<InteractionDisabled>,
99
Has<Pressed>,
100
&Hovered,
101
&ThemeBackgroundColor,
102
&ThemeFontColor,
103
),
104
Or<(
105
Changed<Hovered>,
106
Changed<ButtonVariant>,
107
Added<Pressed>,
108
Added<InteractionDisabled>,
109
)>,
110
>,
111
mut commands: Commands,
112
) {
113
for (button_ent, variant, disabled, pressed, hovered, bg_color, font_color) in q_buttons.iter()
114
{
115
set_button_styles(
116
button_ent,
117
variant,
118
disabled,
119
pressed,
120
hovered.0,
121
bg_color,
122
font_color,
123
&mut commands,
124
);
125
}
126
}
127
128
fn update_button_styles_remove(
129
q_buttons: Query<(
130
Entity,
131
&ButtonVariant,
132
Has<InteractionDisabled>,
133
Has<Pressed>,
134
&Hovered,
135
&ThemeBackgroundColor,
136
&ThemeFontColor,
137
)>,
138
mut removed_disabled: RemovedComponents<InteractionDisabled>,
139
mut removed_pressed: RemovedComponents<Pressed>,
140
mut commands: Commands,
141
) {
142
removed_disabled
143
.read()
144
.chain(removed_pressed.read())
145
.for_each(|ent| {
146
if let Ok((button_ent, variant, disabled, pressed, hovered, bg_color, font_color)) =
147
q_buttons.get(ent)
148
{
149
set_button_styles(
150
button_ent,
151
variant,
152
disabled,
153
pressed,
154
hovered.0,
155
bg_color,
156
font_color,
157
&mut commands,
158
);
159
}
160
});
161
}
162
163
fn set_button_styles(
164
button_ent: Entity,
165
variant: &ButtonVariant,
166
disabled: bool,
167
pressed: bool,
168
hovered: bool,
169
bg_color: &ThemeBackgroundColor,
170
font_color: &ThemeFontColor,
171
commands: &mut Commands,
172
) {
173
let bg_token = match (variant, disabled, pressed, hovered) {
174
(ButtonVariant::Normal, true, _, _) => tokens::BUTTON_BG_DISABLED,
175
(ButtonVariant::Normal, false, true, _) => tokens::BUTTON_BG_PRESSED,
176
(ButtonVariant::Normal, false, false, true) => tokens::BUTTON_BG_HOVER,
177
(ButtonVariant::Normal, false, false, false) => tokens::BUTTON_BG,
178
(ButtonVariant::Primary, true, _, _) => tokens::BUTTON_PRIMARY_BG_DISABLED,
179
(ButtonVariant::Primary, false, true, _) => tokens::BUTTON_PRIMARY_BG_PRESSED,
180
(ButtonVariant::Primary, false, false, true) => tokens::BUTTON_PRIMARY_BG_HOVER,
181
(ButtonVariant::Primary, false, false, false) => tokens::BUTTON_PRIMARY_BG,
182
};
183
184
let font_color_token = match (variant, disabled) {
185
(ButtonVariant::Normal, true) => tokens::BUTTON_TEXT_DISABLED,
186
(ButtonVariant::Normal, false) => tokens::BUTTON_TEXT,
187
(ButtonVariant::Primary, true) => tokens::BUTTON_PRIMARY_TEXT_DISABLED,
188
(ButtonVariant::Primary, false) => tokens::BUTTON_PRIMARY_TEXT,
189
};
190
191
let cursor_shape = match disabled {
192
true => bevy_window::SystemCursorIcon::NotAllowed,
193
false => bevy_window::SystemCursorIcon::Pointer,
194
};
195
196
// Change background color
197
if bg_color.0 != bg_token {
198
commands
199
.entity(button_ent)
200
.insert(ThemeBackgroundColor(bg_token));
201
}
202
203
// Change font color
204
if font_color.0 != font_color_token {
205
commands
206
.entity(button_ent)
207
.insert(ThemeFontColor(font_color_token));
208
}
209
210
// Change cursor shape
211
commands
212
.entity(button_ent)
213
.insert(EntityCursor::System(cursor_shape));
214
}
215
216
/// Plugin which registers the systems for updating the button styles.
217
pub struct ButtonPlugin;
218
219
impl Plugin for ButtonPlugin {
220
fn build(&self, app: &mut bevy_app::App) {
221
app.add_systems(
222
PreUpdate,
223
(update_button_styles, update_button_styles_remove).in_set(PickingSystems::Last),
224
);
225
}
226
}
227
228