Skip to content

Commit ace0114

Browse files
authored
Changing the notification protocol for core_widgets. (#20086)
Notifications now include the source entity. This is useful for callbacks that are responsible for more than one widget. Part of #19236 This is an incremental change only: I have not altered the fundamental nature of callbacks, as this is still in discussion. The only change here is to include the source entity id with the notification. The existing examples don't leverage this new field, but that will change when I work on the color sliders PR. I have been careful not to use the word "events" in describing the notification message structs because they are not capital-E `Events` at this time. That may change depending on the outcome of discussions. @alice-i-cecile
1 parent e5aa941 commit ace0114

File tree

13 files changed

+141
-74
lines changed

13 files changed

+141
-74
lines changed

crates/bevy_core_widgets/src/core_button.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use accesskit::Role;
22
use bevy_a11y::AccessibilityNode;
33
use bevy_app::{App, Plugin};
44
use bevy_ecs::query::Has;
5+
use bevy_ecs::system::In;
56
use bevy_ecs::{
67
component::Component,
78
entity::Entity,
@@ -15,7 +16,7 @@ use bevy_input_focus::FocusedInput;
1516
use bevy_picking::events::{Cancel, Click, DragEnd, Pointer, Press, Release};
1617
use bevy_ui::{InteractionDisabled, Pressed};
1718

18-
use crate::{Callback, Notify};
19+
use crate::{Activate, Callback, Notify};
1920

2021
/// Headless button widget. This widget maintains a "pressed" state, which is used to
2122
/// indicate whether the button is currently being pressed by the user. It emits a `ButtonClicked`
@@ -25,7 +26,7 @@ use crate::{Callback, Notify};
2526
pub struct CoreButton {
2627
/// Callback to invoke when the button is clicked, or when the `Enter` or `Space` key
2728
/// is pressed while the button is focused.
28-
pub on_activate: Callback,
29+
pub on_activate: Callback<In<Activate>>,
2930
}
3031

3132
fn button_on_key_event(
@@ -41,7 +42,7 @@ fn button_on_key_event(
4142
&& (event.key_code == KeyCode::Enter || event.key_code == KeyCode::Space)
4243
{
4344
trigger.propagate(false);
44-
commands.notify(&bstate.on_activate);
45+
commands.notify_with(&bstate.on_activate, Activate(trigger.target()));
4546
}
4647
}
4748
}
@@ -55,7 +56,7 @@ fn button_on_pointer_click(
5556
if let Ok((bstate, pressed, disabled)) = q_state.get_mut(trigger.target()) {
5657
trigger.propagate(false);
5758
if pressed && !disabled {
58-
commands.notify(&bstate.on_activate);
59+
commands.notify_with(&bstate.on_activate, Activate(trigger.target()));
5960
}
6061
}
6162
}

crates/bevy_core_widgets/src/core_checkbox.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use bevy_input_focus::{FocusedInput, InputFocus, InputFocusVisible};
1515
use bevy_picking::events::{Click, Pointer};
1616
use bevy_ui::{Checkable, Checked, InteractionDisabled};
1717

18-
use crate::{Callback, Notify as _};
18+
use crate::{Callback, Notify as _, ValueChange};
1919

2020
/// Headless widget implementation for checkboxes. The [`Checked`] component represents the current
2121
/// state of the checkbox. The `on_change` field is an optional system id that will be run when the
@@ -34,7 +34,7 @@ pub struct CoreCheckbox {
3434
/// One-shot system that is run when the checkbox state needs to be changed. If this value is
3535
/// `Callback::Ignore`, then the checkbox will update it's own internal [`Checked`] state
3636
/// without notification.
37-
pub on_change: Callback<In<bool>>,
37+
pub on_change: Callback<In<ValueChange<bool>>>,
3838
}
3939

4040
fn checkbox_on_key_input(
@@ -162,7 +162,13 @@ fn set_checkbox_state(
162162
new_state: bool,
163163
) {
164164
if !matches!(checkbox.on_change, Callback::Ignore) {
165-
commands.notify_with(&checkbox.on_change, new_state);
165+
commands.notify_with(
166+
&checkbox.on_change,
167+
ValueChange {
168+
source: entity.into(),
169+
value: new_state,
170+
},
171+
);
166172
} else if new_state {
167173
commands.entity(entity.into()).insert(Checked);
168174
} else {

crates/bevy_core_widgets/src/core_radio.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use bevy_ecs::query::Has;
66
use bevy_ecs::system::In;
77
use bevy_ecs::{
88
component::Component,
9-
entity::Entity,
109
observer::On,
1110
query::With,
1211
system::{Commands, Query},
@@ -17,7 +16,7 @@ use bevy_input_focus::FocusedInput;
1716
use bevy_picking::events::{Click, Pointer};
1817
use bevy_ui::{Checkable, Checked, InteractionDisabled};
1918

20-
use crate::{Callback, Notify};
19+
use crate::{Activate, Callback, Notify};
2120

2221
/// Headless widget implementation for a "radio button group". This component is used to group
2322
/// multiple [`CoreRadio`] components together, allowing them to behave as a single unit. It
@@ -38,7 +37,7 @@ use crate::{Callback, Notify};
3837
#[require(AccessibilityNode(accesskit::Node::new(Role::RadioGroup)))]
3938
pub struct CoreRadioGroup {
4039
/// Callback which is called when the selected radio button changes.
41-
pub on_change: Callback<In<Entity>>,
40+
pub on_change: Callback<In<Activate>>,
4241
}
4342

4443
/// Headless widget implementation for radio buttons. These should be enclosed within a
@@ -133,7 +132,7 @@ fn radio_group_on_key_input(
133132
let (next_id, _) = radio_buttons[next_index];
134133

135134
// Trigger the on_change event for the newly checked radio button
136-
commands.notify_with(on_change, next_id);
135+
commands.notify_with(on_change, Activate(next_id));
137136
}
138137
}
139138
}
@@ -201,7 +200,7 @@ fn radio_group_on_button_click(
201200
}
202201

203202
// Trigger the on_change event for the newly checked radio button
204-
commands.notify_with(on_change, radio_id);
203+
commands.notify_with(on_change, Activate(radio_id));
205204
}
206205
}
207206

crates/bevy_core_widgets/src/core_slider.rs

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use bevy_math::ops;
2323
use bevy_picking::events::{Drag, DragEnd, DragStart, Pointer, Press};
2424
use bevy_ui::{ComputedNode, ComputedNodeTarget, InteractionDisabled, UiGlobalTransform, UiScale};
2525

26-
use crate::{Callback, Notify};
26+
use crate::{Callback, Notify, ValueChange};
2727

2828
/// Defines how the slider should behave when you click on the track (not the thumb).
2929
#[derive(Debug, Default, PartialEq, Clone, Copy)]
@@ -78,7 +78,7 @@ pub struct CoreSlider {
7878
/// Callback which is called when the slider is dragged or the value is changed via other user
7979
/// interaction. If this value is `Callback::Ignore`, then the slider will update it's own
8080
/// internal [`SliderValue`] state without notification.
81-
pub on_change: Callback<In<f32>>,
81+
pub on_change: Callback<In<ValueChange<f32>>>,
8282
/// Set the track-clicking behavior for this slider.
8383
pub track_click: TrackClick,
8484
// TODO: Think about whether we want a "vertical" option.
@@ -298,7 +298,13 @@ pub(crate) fn slider_on_pointer_down(
298298
.entity(trigger.target())
299299
.insert(SliderValue(new_value));
300300
} else {
301-
commands.notify_with(&slider.on_change, new_value);
301+
commands.notify_with(
302+
&slider.on_change,
303+
ValueChange {
304+
source: trigger.target(),
305+
value: new_value,
306+
},
307+
);
302308
}
303309
}
304310
}
@@ -370,7 +376,13 @@ pub(crate) fn slider_on_drag(
370376
.entity(trigger.target())
371377
.insert(SliderValue(rounded_value));
372378
} else {
373-
commands.notify_with(&slider.on_change, rounded_value);
379+
commands.notify_with(
380+
&slider.on_change,
381+
ValueChange {
382+
source: trigger.target(),
383+
value: rounded_value,
384+
},
385+
);
374386
}
375387
}
376388
}
@@ -417,7 +429,13 @@ fn slider_on_key_input(
417429
.entity(trigger.target())
418430
.insert(SliderValue(new_value));
419431
} else {
420-
commands.notify_with(&slider.on_change, new_value);
432+
commands.notify_with(
433+
&slider.on_change,
434+
ValueChange {
435+
source: trigger.target(),
436+
value: new_value,
437+
},
438+
);
421439
}
422440
}
423441
}
@@ -509,7 +527,13 @@ fn slider_on_set_value(
509527
.entity(trigger.target())
510528
.insert(SliderValue(new_value));
511529
} else {
512-
commands.notify_with(&slider.on_change, new_value);
530+
commands.notify_with(
531+
&slider.on_change,
532+
ValueChange {
533+
source: trigger.target(),
534+
value: new_value,
535+
},
536+
);
513537
}
514538
}
515539
}

crates/bevy_core_widgets/src/lib.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ mod core_slider;
2323

2424
use bevy_app::{PluginGroup, PluginGroupBuilder};
2525

26+
use bevy_ecs::entity::Entity;
2627
pub use callback::{Callback, Notify};
2728
pub use core_button::{CoreButton, CoreButtonPlugin};
2829
pub use core_checkbox::{CoreCheckbox, CoreCheckboxPlugin, SetChecked, ToggleChecked};
@@ -50,3 +51,16 @@ impl PluginGroup for CoreWidgetsPlugins {
5051
.add(CoreSliderPlugin)
5152
}
5253
}
54+
55+
/// Notification sent by a button or menu item.
56+
#[derive(Copy, Clone, Debug, PartialEq)]
57+
pub struct Activate(pub Entity);
58+
59+
/// Notification sent by a widget that edits a scalar value.
60+
#[derive(Copy, Clone, Debug, PartialEq)]
61+
pub struct ValueChange<T> {
62+
/// The id of the widget that produced this value.
63+
pub source: Entity,
64+
/// The new value.
65+
pub value: T,
66+
}

crates/bevy_feathers/src/controls/button.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use bevy_app::{Plugin, PreUpdate};
2-
use bevy_core_widgets::{Callback, CoreButton};
2+
use bevy_core_widgets::{Activate, Callback, CoreButton};
33
use bevy_ecs::{
44
bundle::Bundle,
55
component::Component,
@@ -9,7 +9,7 @@ use bevy_ecs::{
99
query::{Added, Changed, Has, Or},
1010
schedule::IntoScheduleConfigs,
1111
spawn::{SpawnRelated, SpawnableList},
12-
system::{Commands, Query},
12+
system::{Commands, In, Query},
1313
};
1414
use bevy_input_focus::tab_navigation::TabIndex;
1515
use bevy_picking::{hover::Hovered, PickingSystems};
@@ -45,7 +45,7 @@ pub struct ButtonProps {
4545
/// Rounded corners options
4646
pub corners: RoundedCorners,
4747
/// Click handler
48-
pub on_click: Callback,
48+
pub on_click: Callback<In<Activate>>,
4949
}
5050

5151
/// Template function to spawn a button.

crates/bevy_feathers/src/controls/checkbox.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use bevy_app::{Plugin, PreUpdate};
2-
use bevy_core_widgets::{Callback, CoreCheckbox};
2+
use bevy_core_widgets::{Callback, CoreCheckbox, ValueChange};
33
use bevy_ecs::{
44
bundle::Bundle,
55
children,
@@ -34,7 +34,7 @@ use crate::{
3434
#[derive(Default)]
3535
pub struct CheckboxProps {
3636
/// Change handler
37-
pub on_change: Callback<In<bool>>,
37+
pub on_change: Callback<In<ValueChange<bool>>>,
3838
}
3939

4040
/// Marker for the checkbox frame (contains both checkbox and label)

crates/bevy_feathers/src/controls/slider.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use core::f32::consts::PI;
22

33
use bevy_app::{Plugin, PreUpdate};
44
use bevy_color::Color;
5-
use bevy_core_widgets::{Callback, CoreSlider, SliderRange, SliderValue, TrackClick};
5+
use bevy_core_widgets::{Callback, CoreSlider, SliderRange, SliderValue, TrackClick, ValueChange};
66
use bevy_ecs::{
77
bundle::Bundle,
88
children,
@@ -42,7 +42,7 @@ pub struct SliderProps {
4242
/// Slider maximum value
4343
pub max: f32,
4444
/// On-change handler
45-
pub on_change: Callback<In<f32>>,
45+
pub on_change: Callback<In<ValueChange<f32>>>,
4646
}
4747

4848
impl Default for SliderProps {

crates/bevy_feathers/src/controls/toggle_switch.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use accesskit::Role;
22
use bevy_a11y::AccessibilityNode;
33
use bevy_app::{Plugin, PreUpdate};
4-
use bevy_core_widgets::{Callback, CoreCheckbox};
4+
use bevy_core_widgets::{Callback, CoreCheckbox, ValueChange};
55
use bevy_ecs::{
66
bundle::Bundle,
77
children,
@@ -30,7 +30,7 @@ use crate::{
3030
#[derive(Default)]
3131
pub struct ToggleSwitchProps {
3232
/// Change handler
33-
pub on_change: Callback<In<bool>>,
33+
pub on_change: Callback<In<ValueChange<bool>>>,
3434
}
3535

3636
/// Marker for the toggle switch outline

examples/ui/core_widgets.rs

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
use bevy::{
44
color::palettes::basic::*,
55
core_widgets::{
6-
Callback, CoreButton, CoreCheckbox, CoreRadio, CoreRadioGroup, CoreSlider,
6+
Activate, Callback, CoreButton, CoreCheckbox, CoreRadio, CoreRadioGroup, CoreSlider,
77
CoreSliderDragState, CoreSliderThumb, CoreWidgetsPlugins, SliderRange, SliderValue,
8-
TrackClick,
8+
TrackClick, ValueChange,
99
},
1010
input_focus::{
1111
tab_navigation::{TabGroup, TabIndex, TabNavigationPlugin},
@@ -120,24 +120,24 @@ fn update_widget_values(
120120

121121
fn setup(mut commands: Commands, assets: Res<AssetServer>) {
122122
// System to print a value when the button is clicked.
123-
let on_click = commands.register_system(|| {
123+
let on_click = commands.register_system(|_: In<Activate>| {
124124
info!("Button clicked!");
125125
});
126126

127127
// System to update a resource when the slider value changes. Note that we could have
128128
// updated the slider value directly, but we want to demonstrate externalizing the state.
129129
let on_change_value = commands.register_system(
130-
|value: In<f32>, mut widget_states: ResMut<DemoWidgetStates>| {
131-
widget_states.slider_value = *value;
130+
|value: In<ValueChange<f32>>, mut widget_states: ResMut<DemoWidgetStates>| {
131+
widget_states.slider_value = value.0.value;
132132
},
133133
);
134134

135135
// System to update a resource when the radio group changes.
136136
let on_change_radio = commands.register_system(
137-
|value: In<Entity>,
137+
|value: In<Activate>,
138138
mut widget_states: ResMut<DemoWidgetStates>,
139139
q_radios: Query<&DemoRadio>| {
140-
if let Ok(radio) = q_radios.get(*value) {
140+
if let Ok(radio) = q_radios.get(value.0 .0) {
141141
widget_states.slider_click = radio.0;
142142
}
143143
},
@@ -155,9 +155,9 @@ fn setup(mut commands: Commands, assets: Res<AssetServer>) {
155155

156156
fn demo_root(
157157
asset_server: &AssetServer,
158-
on_click: Callback,
159-
on_change_value: Callback<In<f32>>,
160-
on_change_radio: Callback<In<Entity>>,
158+
on_click: Callback<In<Activate>>,
159+
on_change_value: Callback<In<ValueChange<f32>>>,
160+
on_change_radio: Callback<In<Activate>>,
161161
) -> impl Bundle {
162162
(
163163
Node {
@@ -181,7 +181,7 @@ fn demo_root(
181181
)
182182
}
183183

184-
fn button(asset_server: &AssetServer, on_click: Callback) -> impl Bundle {
184+
fn button(asset_server: &AssetServer, on_click: Callback<In<Activate>>) -> impl Bundle {
185185
(
186186
Node {
187187
width: Val::Px(150.0),
@@ -324,7 +324,12 @@ fn set_button_style(
324324
}
325325

326326
/// Create a demo slider
327-
fn slider(min: f32, max: f32, value: f32, on_change: Callback<In<f32>>) -> impl Bundle {
327+
fn slider(
328+
min: f32,
329+
max: f32,
330+
value: f32,
331+
on_change: Callback<In<ValueChange<f32>>>,
332+
) -> impl Bundle {
328333
(
329334
Node {
330335
display: Display::Flex,
@@ -469,7 +474,7 @@ fn thumb_color(disabled: bool, hovered: bool) -> Color {
469474
fn checkbox(
470475
asset_server: &AssetServer,
471476
caption: &str,
472-
on_change: Callback<In<bool>>,
477+
on_change: Callback<In<ValueChange<bool>>>,
473478
) -> impl Bundle {
474479
(
475480
Node {
@@ -662,7 +667,7 @@ fn set_checkbox_or_radio_style(
662667
}
663668

664669
/// Create a demo radio group
665-
fn radio_group(asset_server: &AssetServer, on_change: Callback<In<Entity>>) -> impl Bundle {
670+
fn radio_group(asset_server: &AssetServer, on_change: Callback<In<Activate>>) -> impl Bundle {
666671
(
667672
Node {
668673
display: Display::Flex,

0 commit comments

Comments
 (0)