Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_feathers/src/controls/toggle_switch.rs
6596 views
1
use accesskit::Role;
2
use bevy_a11y::AccessibilityNode;
3
use bevy_app::{Plugin, PreUpdate};
4
use bevy_core_widgets::{Callback, CoreCheckbox, ValueChange};
5
use bevy_ecs::{
6
bundle::Bundle,
7
children,
8
component::Component,
9
entity::Entity,
10
hierarchy::Children,
11
lifecycle::RemovedComponents,
12
query::{Added, Changed, Has, Or, With},
13
reflect::ReflectComponent,
14
schedule::IntoScheduleConfigs,
15
spawn::SpawnRelated,
16
system::{Commands, In, Query},
17
world::Mut,
18
};
19
use bevy_input_focus::tab_navigation::TabIndex;
20
use bevy_picking::{hover::Hovered, PickingSystems};
21
use bevy_reflect::{prelude::ReflectDefault, Reflect};
22
use bevy_ui::{BorderRadius, Checked, InteractionDisabled, Node, PositionType, UiRect, Val};
23
24
use crate::{
25
constants::size,
26
cursor::EntityCursor,
27
theme::{ThemeBackgroundColor, ThemeBorderColor},
28
tokens,
29
};
30
31
/// Parameters for the toggle switch template, passed to [`toggle_switch`] function.
32
#[derive(Default)]
33
pub struct ToggleSwitchProps {
34
/// Change handler
35
pub on_change: Callback<In<ValueChange<bool>>>,
36
}
37
38
/// Marker for the toggle switch outline
39
#[derive(Component, Default, Clone, Reflect)]
40
#[reflect(Component, Clone, Default)]
41
struct ToggleSwitchOutline;
42
43
/// Marker for the toggle switch slide
44
#[derive(Component, Default, Clone, Reflect)]
45
#[reflect(Component, Clone, Default)]
46
struct ToggleSwitchSlide;
47
48
/// Template function to spawn a toggle switch.
49
///
50
/// # Arguments
51
/// * `props` - construction properties for the toggle switch.
52
/// * `overrides` - a bundle of components that are merged in with the normal toggle switch components.
53
pub fn toggle_switch<B: Bundle>(props: ToggleSwitchProps, overrides: B) -> impl Bundle {
54
(
55
Node {
56
width: size::TOGGLE_WIDTH,
57
height: size::TOGGLE_HEIGHT,
58
border: UiRect::all(Val::Px(2.0)),
59
..Default::default()
60
},
61
CoreCheckbox {
62
on_change: props.on_change,
63
},
64
ToggleSwitchOutline,
65
BorderRadius::all(Val::Px(5.0)),
66
ThemeBackgroundColor(tokens::SWITCH_BG),
67
ThemeBorderColor(tokens::SWITCH_BORDER),
68
AccessibilityNode(accesskit::Node::new(Role::Switch)),
69
Hovered::default(),
70
EntityCursor::System(bevy_window::SystemCursorIcon::Pointer),
71
TabIndex(0),
72
overrides,
73
children![(
74
Node {
75
position_type: PositionType::Absolute,
76
left: Val::Percent(0.),
77
top: Val::Px(0.),
78
bottom: Val::Px(0.),
79
width: Val::Percent(50.),
80
..Default::default()
81
},
82
BorderRadius::all(Val::Px(3.0)),
83
ToggleSwitchSlide,
84
ThemeBackgroundColor(tokens::SWITCH_SLIDE),
85
)],
86
)
87
}
88
89
fn update_switch_styles(
90
q_switches: Query<
91
(
92
Entity,
93
Has<InteractionDisabled>,
94
Has<Checked>,
95
&Hovered,
96
&ThemeBackgroundColor,
97
&ThemeBorderColor,
98
),
99
(
100
With<ToggleSwitchOutline>,
101
Or<(Changed<Hovered>, Added<Checked>, Added<InteractionDisabled>)>,
102
),
103
>,
104
q_children: Query<&Children>,
105
mut q_slide: Query<(&mut Node, &ThemeBackgroundColor), With<ToggleSwitchSlide>>,
106
mut commands: Commands,
107
) {
108
for (switch_ent, disabled, checked, hovered, outline_bg, outline_border) in q_switches.iter() {
109
let Some(slide_ent) = q_children
110
.iter_descendants(switch_ent)
111
.find(|en| q_slide.contains(*en))
112
else {
113
continue;
114
};
115
// Safety: since we just checked the query, should always work.
116
let (ref mut slide_style, slide_color) = q_slide.get_mut(slide_ent).unwrap();
117
set_switch_styles(
118
switch_ent,
119
slide_ent,
120
disabled,
121
checked,
122
hovered.0,
123
outline_bg,
124
outline_border,
125
slide_style,
126
slide_color,
127
&mut commands,
128
);
129
}
130
}
131
132
fn update_switch_styles_remove(
133
q_switches: Query<
134
(
135
Entity,
136
Has<InteractionDisabled>,
137
Has<Checked>,
138
&Hovered,
139
&ThemeBackgroundColor,
140
&ThemeBorderColor,
141
),
142
With<ToggleSwitchOutline>,
143
>,
144
q_children: Query<&Children>,
145
mut q_slide: Query<(&mut Node, &ThemeBackgroundColor), With<ToggleSwitchSlide>>,
146
mut removed_disabled: RemovedComponents<InteractionDisabled>,
147
mut removed_checked: RemovedComponents<Checked>,
148
mut commands: Commands,
149
) {
150
removed_disabled
151
.read()
152
.chain(removed_checked.read())
153
.for_each(|ent| {
154
if let Ok((switch_ent, disabled, checked, hovered, outline_bg, outline_border)) =
155
q_switches.get(ent)
156
{
157
let Some(slide_ent) = q_children
158
.iter_descendants(switch_ent)
159
.find(|en| q_slide.contains(*en))
160
else {
161
return;
162
};
163
// Safety: since we just checked the query, should always work.
164
let (ref mut slide_style, slide_color) = q_slide.get_mut(slide_ent).unwrap();
165
set_switch_styles(
166
switch_ent,
167
slide_ent,
168
disabled,
169
checked,
170
hovered.0,
171
outline_bg,
172
outline_border,
173
slide_style,
174
slide_color,
175
&mut commands,
176
);
177
}
178
});
179
}
180
181
fn set_switch_styles(
182
switch_ent: Entity,
183
slide_ent: Entity,
184
disabled: bool,
185
checked: bool,
186
hovered: bool,
187
outline_bg: &ThemeBackgroundColor,
188
outline_border: &ThemeBorderColor,
189
slide_style: &mut Mut<Node>,
190
slide_color: &ThemeBackgroundColor,
191
commands: &mut Commands,
192
) {
193
let outline_border_token = match (disabled, hovered) {
194
(true, _) => tokens::SWITCH_BORDER_DISABLED,
195
(false, true) => tokens::SWITCH_BORDER_HOVER,
196
_ => tokens::SWITCH_BORDER,
197
};
198
199
let outline_bg_token = match (disabled, checked) {
200
(true, true) => tokens::SWITCH_BG_CHECKED_DISABLED,
201
(true, false) => tokens::SWITCH_BG_DISABLED,
202
(false, true) => tokens::SWITCH_BG_CHECKED,
203
(false, false) => tokens::SWITCH_BG,
204
};
205
206
let slide_token = match disabled {
207
true => tokens::SWITCH_SLIDE_DISABLED,
208
false => tokens::SWITCH_SLIDE,
209
};
210
211
let slide_pos = match checked {
212
true => Val::Percent(50.),
213
false => Val::Percent(0.),
214
};
215
216
let cursor_shape = match disabled {
217
true => bevy_window::SystemCursorIcon::NotAllowed,
218
false => bevy_window::SystemCursorIcon::Pointer,
219
};
220
221
// Change outline background
222
if outline_bg.0 != outline_bg_token {
223
commands
224
.entity(switch_ent)
225
.insert(ThemeBackgroundColor(outline_bg_token));
226
}
227
228
// Change outline border
229
if outline_border.0 != outline_border_token {
230
commands
231
.entity(switch_ent)
232
.insert(ThemeBorderColor(outline_border_token));
233
}
234
235
// Change slide color
236
if slide_color.0 != slide_token {
237
commands
238
.entity(slide_ent)
239
.insert(ThemeBackgroundColor(slide_token));
240
}
241
242
// Change slide position
243
if slide_pos != slide_style.left {
244
slide_style.left = slide_pos;
245
}
246
247
// Change cursor shape
248
commands
249
.entity(switch_ent)
250
.insert(EntityCursor::System(cursor_shape));
251
}
252
253
/// Plugin which registers the systems for updating the toggle switch styles.
254
pub struct ToggleSwitchPlugin;
255
256
impl Plugin for ToggleSwitchPlugin {
257
fn build(&self, app: &mut bevy_app::App) {
258
app.add_systems(
259
PreUpdate,
260
(update_switch_styles, update_switch_styles_remove).in_set(PickingSystems::Last),
261
);
262
}
263
}
264
265