Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ui_widgets/src/checkbox.rs
6849 views
1
use accesskit::Role;
2
use bevy_a11y::AccessibilityNode;
3
use bevy_app::{App, Plugin};
4
use bevy_ecs::event::EntityEvent;
5
use bevy_ecs::query::{Has, Without};
6
use bevy_ecs::system::{In, ResMut};
7
use bevy_ecs::{
8
component::Component,
9
observer::On,
10
system::{Commands, Query},
11
};
12
use bevy_input::keyboard::{KeyCode, KeyboardInput};
13
use bevy_input::ButtonState;
14
use bevy_input_focus::{FocusedInput, InputFocus, InputFocusVisible};
15
use bevy_picking::events::{Click, Pointer};
16
use bevy_ui::{Checkable, Checked, InteractionDisabled};
17
18
use crate::{Callback, Notify as _, ValueChange};
19
use bevy_ecs::entity::Entity;
20
21
/// Headless widget implementation for checkboxes. The [`Checked`] component represents the current
22
/// state of the checkbox. The `on_change` field is an optional system id that will be run when the
23
/// checkbox is clicked, or when the `Enter` or `Space` key is pressed while the checkbox is
24
/// focused. If the `on_change` field is `Callback::Ignore`, then instead of calling a callback, the
25
/// checkbox will update its own [`Checked`] state directly.
26
///
27
/// # Toggle switches
28
///
29
/// The [`Checkbox`] component can be used to implement other kinds of toggle widgets. If you
30
/// are going to do a toggle switch, you should override the [`AccessibilityNode`] component with
31
/// the `Switch` role instead of the `Checkbox` role.
32
#[derive(Component, Debug, Default)]
33
#[require(AccessibilityNode(accesskit::Node::new(Role::CheckBox)), Checkable)]
34
pub struct Checkbox {
35
/// One-shot system that is run when the checkbox state needs to be changed. If this value is
36
/// `Callback::Ignore`, then the checkbox will update it's own internal [`Checked`] state
37
/// without notification.
38
pub on_change: Callback<In<ValueChange<bool>>>,
39
}
40
41
fn checkbox_on_key_input(
42
mut ev: On<FocusedInput<KeyboardInput>>,
43
q_checkbox: Query<(&Checkbox, Has<Checked>), Without<InteractionDisabled>>,
44
mut commands: Commands,
45
) {
46
if let Ok((checkbox, is_checked)) = q_checkbox.get(ev.focused_entity) {
47
let event = &ev.event().input;
48
if event.state == ButtonState::Pressed
49
&& !event.repeat
50
&& (event.key_code == KeyCode::Enter || event.key_code == KeyCode::Space)
51
{
52
ev.propagate(false);
53
set_checkbox_state(&mut commands, ev.focused_entity, checkbox, !is_checked);
54
}
55
}
56
}
57
58
fn checkbox_on_pointer_click(
59
mut click: On<Pointer<Click>>,
60
q_checkbox: Query<(&Checkbox, Has<Checked>, Has<InteractionDisabled>)>,
61
focus: Option<ResMut<InputFocus>>,
62
focus_visible: Option<ResMut<InputFocusVisible>>,
63
mut commands: Commands,
64
) {
65
if let Ok((checkbox, is_checked, disabled)) = q_checkbox.get(click.entity) {
66
// Clicking on a button makes it the focused input,
67
// and hides the focus ring if it was visible.
68
if let Some(mut focus) = focus {
69
focus.0 = Some(click.entity);
70
}
71
if let Some(mut focus_visible) = focus_visible {
72
focus_visible.0 = false;
73
}
74
75
click.propagate(false);
76
if !disabled {
77
set_checkbox_state(&mut commands, click.entity, checkbox, !is_checked);
78
}
79
}
80
}
81
82
/// Event which can be triggered on a checkbox to set the checked state. This can be used to control
83
/// the checkbox via gamepad buttons or other inputs.
84
///
85
/// # Example:
86
///
87
/// ```
88
/// use bevy_ecs::system::Commands;
89
/// use bevy_ui_widgets::{Checkbox, SetChecked};
90
///
91
/// fn setup(mut commands: Commands) {
92
/// // Create a checkbox
93
/// let entity = commands.spawn((
94
/// Checkbox::default(),
95
/// )).id();
96
///
97
/// // Set to checked
98
/// commands.trigger(SetChecked { entity, checked: true});
99
/// }
100
/// ```
101
#[derive(EntityEvent)]
102
pub struct SetChecked {
103
/// The [`Checkbox`] entity to set the "checked" state on.
104
pub entity: Entity,
105
/// Sets the `checked` state to `true` or `false`.
106
pub checked: bool,
107
}
108
109
/// Event which can be triggered on a checkbox to toggle the checked state. This can be used to
110
/// control the checkbox via gamepad buttons or other inputs.
111
///
112
/// # Example:
113
///
114
/// ```
115
/// use bevy_ecs::system::Commands;
116
/// use bevy_ui_widgets::{Checkbox, ToggleChecked};
117
///
118
/// fn setup(mut commands: Commands) {
119
/// // Create a checkbox
120
/// let entity = commands.spawn((
121
/// Checkbox::default(),
122
/// )).id();
123
///
124
/// // Set to checked
125
/// commands.trigger(ToggleChecked { entity });
126
/// }
127
/// ```
128
#[derive(EntityEvent)]
129
pub struct ToggleChecked {
130
/// The [`Entity`] of the toggled [`Checkbox`]
131
pub entity: Entity,
132
}
133
134
fn checkbox_on_set_checked(
135
set_checked: On<SetChecked>,
136
q_checkbox: Query<(&Checkbox, Has<Checked>, Has<InteractionDisabled>)>,
137
mut commands: Commands,
138
) {
139
if let Ok((checkbox, is_checked, disabled)) = q_checkbox.get(set_checked.entity) {
140
if disabled {
141
return;
142
}
143
144
let will_be_checked = set_checked.checked;
145
if will_be_checked != is_checked {
146
set_checkbox_state(&mut commands, set_checked.entity, checkbox, will_be_checked);
147
}
148
}
149
}
150
151
fn checkbox_on_toggle_checked(
152
toggle_checked: On<ToggleChecked>,
153
q_checkbox: Query<(&Checkbox, Has<Checked>, Has<InteractionDisabled>)>,
154
mut commands: Commands,
155
) {
156
if let Ok((checkbox, is_checked, disabled)) = q_checkbox.get(toggle_checked.entity) {
157
if disabled {
158
return;
159
}
160
161
set_checkbox_state(&mut commands, toggle_checked.entity, checkbox, !is_checked);
162
}
163
}
164
165
fn set_checkbox_state(
166
commands: &mut Commands,
167
entity: impl Into<Entity>,
168
checkbox: &Checkbox,
169
new_state: bool,
170
) {
171
if !matches!(checkbox.on_change, Callback::Ignore) {
172
commands.notify_with(
173
&checkbox.on_change,
174
ValueChange {
175
source: entity.into(),
176
value: new_state,
177
},
178
);
179
} else if new_state {
180
commands.entity(entity.into()).insert(Checked);
181
} else {
182
commands.entity(entity.into()).remove::<Checked>();
183
}
184
}
185
186
/// Plugin that adds the observers for the [`Checkbox`] widget.
187
pub struct CheckboxPlugin;
188
189
impl Plugin for CheckboxPlugin {
190
fn build(&self, app: &mut App) {
191
app.add_observer(checkbox_on_key_input)
192
.add_observer(checkbox_on_pointer_click)
193
.add_observer(checkbox_on_set_checked)
194
.add_observer(checkbox_on_toggle_checked);
195
}
196
}
197
198