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