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