bevy_ui_widgets/
checkbox.rs1use accesskit::Role;
2use bevy_a11y::AccessibilityNode;
3use bevy_app::{App, Plugin};
4use bevy_ecs::event::EntityEvent;
5use bevy_ecs::query::{Has, With, Without};
6use bevy_ecs::system::ResMut;
7use bevy_ecs::{
8 component::Component,
9 observer::On,
10 system::{Commands, Query},
11};
12use bevy_input::keyboard::{KeyCode, KeyboardInput};
13use bevy_input::ButtonState;
14use bevy_input_focus::{FocusedInput, InputFocus, InputFocusVisible};
15use bevy_picking::events::{Click, Pointer};
16use bevy_ui::{Checkable, Checked, InteractionDisabled};
17
18use crate::ValueChange;
19use bevy_ecs::entity::Entity;
20
21#[derive(Component, Debug, Default)]
33#[require(AccessibilityNode(accesskit::Node::new(Role::CheckBox)), Checkable)]
34pub struct Checkbox;
35
36fn checkbox_on_key_input(
37 mut ev: On<FocusedInput<KeyboardInput>>,
38 q_checkbox: Query<Has<Checked>, (With<Checkbox>, Without<InteractionDisabled>)>,
39 mut commands: Commands,
40) {
41 if let Ok(is_checked) = q_checkbox.get(ev.focused_entity) {
42 let event = &ev.event().input;
43 if event.state == ButtonState::Pressed
44 && !event.repeat
45 && (event.key_code == KeyCode::Enter || event.key_code == KeyCode::Space)
46 {
47 ev.propagate(false);
48 commands.trigger(ValueChange {
49 source: ev.focused_entity,
50 value: !is_checked,
51 });
52 }
53 }
54}
55
56fn checkbox_on_pointer_click(
57 mut click: On<Pointer<Click>>,
58 q_checkbox: Query<(Has<Checked>, Has<InteractionDisabled>), With<Checkbox>>,
59 focus: Option<ResMut<InputFocus>>,
60 focus_visible: Option<ResMut<InputFocusVisible>>,
61 mut commands: Commands,
62) {
63 if let Ok((is_checked, disabled)) = q_checkbox.get(click.entity) {
64 if let Some(mut focus) = focus {
67 focus.0 = Some(click.entity);
68 }
69 if let Some(mut focus_visible) = focus_visible {
70 focus_visible.0 = false;
71 }
72
73 click.propagate(false);
74 if !disabled {
75 commands.trigger(ValueChange {
76 source: click.entity,
77 value: !is_checked,
78 });
79 }
80 }
81}
82
83#[derive(EntityEvent)]
103pub struct SetChecked {
104 pub entity: Entity,
106 pub checked: bool,
108}
109
110#[derive(EntityEvent)]
130pub struct ToggleChecked {
131 pub entity: Entity,
133}
134
135fn checkbox_on_set_checked(
136 set_checked: On<SetChecked>,
137 q_checkbox: Query<(Has<Checked>, Has<InteractionDisabled>), With<Checkbox>>,
138 mut commands: Commands,
139) {
140 if let Ok((is_checked, disabled)) = q_checkbox.get(set_checked.entity) {
141 if disabled {
142 return;
143 }
144
145 let will_be_checked = set_checked.checked;
146 if will_be_checked != is_checked {
147 commands.trigger(ValueChange {
148 source: set_checked.entity,
149 value: will_be_checked,
150 });
151 }
152 }
153}
154
155fn checkbox_on_toggle_checked(
156 toggle_checked: On<ToggleChecked>,
157 q_checkbox: Query<(Has<Checked>, Has<InteractionDisabled>), With<Checkbox>>,
158 mut commands: Commands,
159) {
160 if let Ok((is_checked, disabled)) = q_checkbox.get(toggle_checked.entity) {
161 if disabled {
162 return;
163 }
164
165 commands.trigger(ValueChange {
166 source: toggle_checked.entity,
167 value: !is_checked,
168 });
169 }
170}
171
172pub struct CheckboxPlugin;
174
175impl Plugin for CheckboxPlugin {
176 fn build(&self, app: &mut App) {
177 app.add_observer(checkbox_on_key_input)
178 .add_observer(checkbox_on_pointer_click)
179 .add_observer(checkbox_on_set_checked)
180 .add_observer(checkbox_on_toggle_checked);
181 }
182}
183
184pub fn checkbox_self_update(value_change: On<ValueChange<bool>>, mut commands: Commands) {
188 if value_change.value {
189 commands.entity(value_change.source).insert(Checked);
190 } else {
191 commands.entity(value_change.source).remove::<Checked>();
192 }
193}