Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_feathers/src/controls/checkbox.rs
9374 views
1
use bevy_app::{Plugin, PreUpdate};
2
use bevy_camera::visibility::Visibility;
3
use bevy_ecs::{
4
bundle::Bundle,
5
children,
6
component::Component,
7
entity::Entity,
8
hierarchy::{ChildOf, Children},
9
lifecycle::RemovedComponents,
10
query::{Added, Changed, Has, Or, With},
11
reflect::ReflectComponent,
12
schedule::IntoScheduleConfigs,
13
spawn::{Spawn, SpawnRelated, SpawnableList},
14
system::{Commands, Query},
15
};
16
use bevy_input_focus::tab_navigation::TabIndex;
17
use bevy_math::Rot2;
18
use bevy_picking::{hover::Hovered, PickingSystems};
19
use bevy_reflect::{prelude::ReflectDefault, Reflect};
20
use bevy_text::FontSize;
21
use bevy_ui::{
22
AlignItems, BorderRadius, Checked, Display, FlexDirection, InteractionDisabled, JustifyContent,
23
Node, PositionType, UiRect, UiTransform, Val,
24
};
25
use bevy_ui_widgets::Checkbox;
26
27
use crate::{
28
constants::{fonts, size},
29
cursor::EntityCursor,
30
font_styles::InheritableFont,
31
handle_or_path::HandleOrPath,
32
theme::{ThemeBackgroundColor, ThemeBorderColor, ThemeFontColor},
33
tokens,
34
};
35
36
/// Marker for the checkbox frame (contains both checkbox and label)
37
#[derive(Component, Default, Clone, Reflect)]
38
#[reflect(Component, Clone, Default)]
39
struct CheckboxFrame;
40
41
/// Marker for the checkbox outline
42
#[derive(Component, Default, Clone, Reflect)]
43
#[reflect(Component, Clone, Default)]
44
struct CheckboxOutline;
45
46
/// Marker for the checkbox check mark
47
#[derive(Component, Default, Clone, Reflect)]
48
#[reflect(Component, Clone, Default)]
49
struct CheckboxMark;
50
51
/// Template function to spawn a checkbox.
52
///
53
/// # Arguments
54
/// * `props` - construction properties for the checkbox.
55
/// * `overrides` - a bundle of components that are merged in with the normal checkbox components.
56
/// * `label` - the label of the checkbox.
57
///
58
/// # Emitted events
59
/// * [`bevy_ui_widgets::ValueChange<bool>`] with the new value when the checkbox changes state.
60
///
61
/// These events can be disabled by adding an [`bevy_ui::InteractionDisabled`] component to the entity
62
pub fn checkbox<C: SpawnableList<ChildOf> + Send + Sync + 'static, B: Bundle>(
63
overrides: B,
64
label: C,
65
) -> impl Bundle {
66
(
67
Node {
68
display: Display::Flex,
69
flex_direction: FlexDirection::Row,
70
justify_content: JustifyContent::Start,
71
align_items: AlignItems::Center,
72
column_gap: Val::Px(4.0),
73
..Default::default()
74
},
75
Checkbox,
76
CheckboxFrame,
77
Hovered::default(),
78
EntityCursor::System(bevy_window::SystemCursorIcon::Pointer),
79
TabIndex(0),
80
ThemeFontColor(tokens::CHECKBOX_TEXT),
81
InheritableFont {
82
font: HandleOrPath::Path(fonts::REGULAR.to_owned()),
83
font_size: FontSize::Px(14.0),
84
},
85
overrides,
86
Children::spawn((
87
Spawn((
88
Node {
89
width: size::CHECKBOX_SIZE,
90
height: size::CHECKBOX_SIZE,
91
border: UiRect::all(Val::Px(2.0)),
92
border_radius: BorderRadius::all(Val::Px(4.0)),
93
..Default::default()
94
},
95
CheckboxOutline,
96
ThemeBackgroundColor(tokens::CHECKBOX_BG),
97
ThemeBorderColor(tokens::CHECKBOX_BORDER),
98
children![(
99
// Cheesy checkmark: rotated node with L-shaped border.
100
Node {
101
position_type: PositionType::Absolute,
102
left: Val::Px(4.0),
103
top: Val::Px(0.0),
104
width: Val::Px(6.),
105
height: Val::Px(11.),
106
border: UiRect {
107
bottom: Val::Px(2.0),
108
right: Val::Px(2.0),
109
..Default::default()
110
},
111
..Default::default()
112
},
113
UiTransform::from_rotation(Rot2::FRAC_PI_4),
114
CheckboxMark,
115
ThemeBorderColor(tokens::CHECKBOX_MARK),
116
)],
117
)),
118
label,
119
)),
120
)
121
}
122
123
fn update_checkbox_styles(
124
q_checkboxes: Query<
125
(
126
Entity,
127
Has<InteractionDisabled>,
128
Has<Checked>,
129
&Hovered,
130
&ThemeFontColor,
131
),
132
(
133
With<CheckboxFrame>,
134
Or<(Changed<Hovered>, Added<Checked>, Added<InteractionDisabled>)>,
135
),
136
>,
137
q_children: Query<&Children>,
138
mut q_outline: Query<(&ThemeBackgroundColor, &ThemeBorderColor), With<CheckboxOutline>>,
139
mut q_mark: Query<&ThemeBorderColor, With<CheckboxMark>>,
140
mut commands: Commands,
141
) {
142
for (checkbox_ent, disabled, checked, hovered, font_color) in q_checkboxes.iter() {
143
let Some(outline_ent) = q_children
144
.iter_descendants(checkbox_ent)
145
.find(|en| q_outline.contains(*en))
146
else {
147
continue;
148
};
149
let Some(mark_ent) = q_children
150
.iter_descendants(checkbox_ent)
151
.find(|en| q_mark.contains(*en))
152
else {
153
continue;
154
};
155
let (outline_bg, outline_border) = q_outline.get_mut(outline_ent).unwrap();
156
let mark_color = q_mark.get_mut(mark_ent).unwrap();
157
set_checkbox_styles(
158
checkbox_ent,
159
outline_ent,
160
mark_ent,
161
disabled,
162
checked,
163
hovered.0,
164
outline_bg,
165
outline_border,
166
mark_color,
167
font_color,
168
&mut commands,
169
);
170
}
171
}
172
173
fn update_checkbox_styles_remove(
174
q_checkboxes: Query<
175
(
176
Entity,
177
Has<InteractionDisabled>,
178
Has<Checked>,
179
&Hovered,
180
&ThemeFontColor,
181
),
182
With<CheckboxFrame>,
183
>,
184
q_children: Query<&Children>,
185
mut q_outline: Query<(&ThemeBackgroundColor, &ThemeBorderColor), With<CheckboxOutline>>,
186
mut q_mark: Query<&ThemeBorderColor, With<CheckboxMark>>,
187
mut removed_disabled: RemovedComponents<InteractionDisabled>,
188
mut removed_checked: RemovedComponents<Checked>,
189
mut commands: Commands,
190
) {
191
removed_disabled
192
.read()
193
.chain(removed_checked.read())
194
.for_each(|ent| {
195
if let Ok((checkbox_ent, disabled, checked, hovered, font_color)) =
196
q_checkboxes.get(ent)
197
{
198
let Some(outline_ent) = q_children
199
.iter_descendants(checkbox_ent)
200
.find(|en| q_outline.contains(*en))
201
else {
202
return;
203
};
204
let Some(mark_ent) = q_children
205
.iter_descendants(checkbox_ent)
206
.find(|en| q_mark.contains(*en))
207
else {
208
return;
209
};
210
let (outline_bg, outline_border) = q_outline.get_mut(outline_ent).unwrap();
211
let mark_color = q_mark.get_mut(mark_ent).unwrap();
212
set_checkbox_styles(
213
checkbox_ent,
214
outline_ent,
215
mark_ent,
216
disabled,
217
checked,
218
hovered.0,
219
outline_bg,
220
outline_border,
221
mark_color,
222
font_color,
223
&mut commands,
224
);
225
}
226
});
227
}
228
229
fn set_checkbox_styles(
230
checkbox_ent: Entity,
231
outline_ent: Entity,
232
mark_ent: Entity,
233
disabled: bool,
234
checked: bool,
235
hovered: bool,
236
outline_bg: &ThemeBackgroundColor,
237
outline_border: &ThemeBorderColor,
238
mark_color: &ThemeBorderColor,
239
font_color: &ThemeFontColor,
240
commands: &mut Commands,
241
) {
242
let outline_border_token = match (disabled, hovered) {
243
(true, _) => tokens::CHECKBOX_BORDER_DISABLED,
244
(false, true) => tokens::CHECKBOX_BORDER_HOVER,
245
_ => tokens::CHECKBOX_BORDER,
246
};
247
248
let outline_bg_token = match (disabled, checked) {
249
(true, true) => tokens::CHECKBOX_BG_CHECKED_DISABLED,
250
(true, false) => tokens::CHECKBOX_BG_DISABLED,
251
(false, true) => tokens::CHECKBOX_BG_CHECKED,
252
(false, false) => tokens::CHECKBOX_BG,
253
};
254
255
let mark_token = match disabled {
256
true => tokens::CHECKBOX_MARK_DISABLED,
257
false => tokens::CHECKBOX_MARK,
258
};
259
260
let font_color_token = match disabled {
261
true => tokens::CHECKBOX_TEXT_DISABLED,
262
false => tokens::CHECKBOX_TEXT,
263
};
264
265
let cursor_shape = match disabled {
266
true => bevy_window::SystemCursorIcon::NotAllowed,
267
false => bevy_window::SystemCursorIcon::Pointer,
268
};
269
270
// Change outline background
271
if outline_bg.0 != outline_bg_token {
272
commands
273
.entity(outline_ent)
274
.insert(ThemeBackgroundColor(outline_bg_token));
275
}
276
277
// Change outline border
278
if outline_border.0 != outline_border_token {
279
commands
280
.entity(outline_ent)
281
.insert(ThemeBorderColor(outline_border_token));
282
}
283
284
// Change mark color
285
if mark_color.0 != mark_token {
286
commands
287
.entity(mark_ent)
288
.insert(ThemeBorderColor(mark_token));
289
}
290
291
// Change mark visibility
292
commands.entity(mark_ent).insert(match checked {
293
true => Visibility::Inherited,
294
false => Visibility::Hidden,
295
});
296
297
// Change font color
298
if font_color.0 != font_color_token {
299
commands
300
.entity(checkbox_ent)
301
.insert(ThemeFontColor(font_color_token));
302
}
303
304
// Change cursor shape
305
commands
306
.entity(checkbox_ent)
307
.insert(EntityCursor::System(cursor_shape));
308
}
309
310
/// Plugin which registers the systems for updating the checkbox styles.
311
pub struct CheckboxPlugin;
312
313
impl Plugin for CheckboxPlugin {
314
fn build(&self, app: &mut bevy_app::App) {
315
app.add_systems(
316
PreUpdate,
317
(update_checkbox_styles, update_checkbox_styles_remove).in_set(PickingSystems::Last),
318
);
319
}
320
}
321
322