Skip to content

Changing the notification protocol for core_widgets. #20086

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions crates/bevy_core_widgets/src/core_button.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use accesskit::Role;
use bevy_a11y::AccessibilityNode;
use bevy_app::{App, Plugin};
use bevy_ecs::query::Has;
use bevy_ecs::system::In;
use bevy_ecs::{
component::Component,
entity::Entity,
Expand All @@ -15,7 +16,7 @@ use bevy_input_focus::FocusedInput;
use bevy_picking::events::{Cancel, Click, DragEnd, Pointer, Press, Release};
use bevy_ui::{InteractionDisabled, Pressed};

use crate::{Callback, Notify};
use crate::{Activate, Callback, Notify};

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

fn button_on_key_event(
Expand All @@ -41,7 +42,7 @@ fn button_on_key_event(
&& (event.key_code == KeyCode::Enter || event.key_code == KeyCode::Space)
{
trigger.propagate(false);
commands.notify(&bstate.on_activate);
commands.notify_with(&bstate.on_activate, Activate(trigger.target()));
}
}
}
Expand All @@ -55,7 +56,7 @@ fn button_on_pointer_click(
if let Ok((bstate, pressed, disabled)) = q_state.get_mut(trigger.target()) {
trigger.propagate(false);
if pressed && !disabled {
commands.notify(&bstate.on_activate);
commands.notify_with(&bstate.on_activate, Activate(trigger.target()));
}
}
}
Expand Down
12 changes: 9 additions & 3 deletions crates/bevy_core_widgets/src/core_checkbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use bevy_input_focus::{FocusedInput, InputFocus, InputFocusVisible};
use bevy_picking::events::{Click, Pointer};
use bevy_ui::{Checkable, Checked, InteractionDisabled};

use crate::{Callback, Notify as _};
use crate::{Callback, Notify as _, ValueChange};

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

fn checkbox_on_key_input(
Expand Down Expand Up @@ -162,7 +162,13 @@ fn set_checkbox_state(
new_state: bool,
) {
if !matches!(checkbox.on_change, Callback::Ignore) {
commands.notify_with(&checkbox.on_change, new_state);
commands.notify_with(
&checkbox.on_change,
ValueChange {
source: entity.into(),
value: new_state,
},
);
} else if new_state {
commands.entity(entity.into()).insert(Checked);
} else {
Expand Down
9 changes: 4 additions & 5 deletions crates/bevy_core_widgets/src/core_radio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use bevy_ecs::query::Has;
use bevy_ecs::system::In;
use bevy_ecs::{
component::Component,
entity::Entity,
observer::On,
query::With,
system::{Commands, Query},
Expand All @@ -17,7 +16,7 @@ use bevy_input_focus::FocusedInput;
use bevy_picking::events::{Click, Pointer};
use bevy_ui::{Checkable, Checked, InteractionDisabled};

use crate::{Callback, Notify};
use crate::{Activate, Callback, Notify};

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

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

// Trigger the on_change event for the newly checked radio button
commands.notify_with(on_change, next_id);
commands.notify_with(on_change, Activate(next_id));
}
}
}
Expand Down Expand Up @@ -201,7 +200,7 @@ fn radio_group_on_button_click(
}

// Trigger the on_change event for the newly checked radio button
commands.notify_with(on_change, radio_id);
commands.notify_with(on_change, Activate(radio_id));
}
}

Expand Down
36 changes: 30 additions & 6 deletions crates/bevy_core_widgets/src/core_slider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use bevy_math::ops;
use bevy_picking::events::{Drag, DragEnd, DragStart, Pointer, Press};
use bevy_ui::{ComputedNode, ComputedNodeTarget, InteractionDisabled, UiGlobalTransform, UiScale};

use crate::{Callback, Notify};
use crate::{Callback, Notify, ValueChange};

/// Defines how the slider should behave when you click on the track (not the thumb).
#[derive(Debug, Default, PartialEq, Clone, Copy)]
Expand Down Expand Up @@ -78,7 +78,7 @@ pub struct CoreSlider {
/// Callback which is called when the slider is dragged or the value is changed via other user
/// interaction. If this value is `Callback::Ignore`, then the slider will update it's own
/// internal [`SliderValue`] state without notification.
pub on_change: Callback<In<f32>>,
pub on_change: Callback<In<ValueChange<f32>>>,
/// Set the track-clicking behavior for this slider.
pub track_click: TrackClick,
// TODO: Think about whether we want a "vertical" option.
Expand Down Expand Up @@ -298,7 +298,13 @@ pub(crate) fn slider_on_pointer_down(
.entity(trigger.target())
.insert(SliderValue(new_value));
} else {
commands.notify_with(&slider.on_change, new_value);
commands.notify_with(
&slider.on_change,
ValueChange {
source: trigger.target(),
value: new_value,
},
);
}
}
}
Expand Down Expand Up @@ -370,7 +376,13 @@ pub(crate) fn slider_on_drag(
.entity(trigger.target())
.insert(SliderValue(rounded_value));
} else {
commands.notify_with(&slider.on_change, rounded_value);
commands.notify_with(
&slider.on_change,
ValueChange {
source: trigger.target(),
value: rounded_value,
},
);
}
}
}
Expand Down Expand Up @@ -417,7 +429,13 @@ fn slider_on_key_input(
.entity(trigger.target())
.insert(SliderValue(new_value));
} else {
commands.notify_with(&slider.on_change, new_value);
commands.notify_with(
&slider.on_change,
ValueChange {
source: trigger.target(),
value: new_value,
},
);
}
}
}
Expand Down Expand Up @@ -509,7 +527,13 @@ fn slider_on_set_value(
.entity(trigger.target())
.insert(SliderValue(new_value));
} else {
commands.notify_with(&slider.on_change, new_value);
commands.notify_with(
&slider.on_change,
ValueChange {
source: trigger.target(),
value: new_value,
},
);
}
}
}
Expand Down
14 changes: 14 additions & 0 deletions crates/bevy_core_widgets/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ mod core_slider;

use bevy_app::{PluginGroup, PluginGroupBuilder};

use bevy_ecs::entity::Entity;
pub use callback::{Callback, Notify};
pub use core_button::{CoreButton, CoreButtonPlugin};
pub use core_checkbox::{CoreCheckbox, CoreCheckboxPlugin, SetChecked, ToggleChecked};
Expand Down Expand Up @@ -50,3 +51,16 @@ impl PluginGroup for CoreWidgetsPlugins {
.add(CoreSliderPlugin)
}
}

/// Notification sent by a button or menu item.
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Activate(pub Entity);

/// Notification sent by a widget that edits a scalar value.
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct ValueChange<T> {
/// The id of the widget that produced this value.
pub source: Entity,
/// The new value.
pub value: T,
}
6 changes: 3 additions & 3 deletions crates/bevy_feathers/src/controls/button.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use bevy_app::{Plugin, PreUpdate};
use bevy_core_widgets::{Callback, CoreButton};
use bevy_core_widgets::{Activate, Callback, CoreButton};
use bevy_ecs::{
bundle::Bundle,
component::Component,
Expand All @@ -9,7 +9,7 @@ use bevy_ecs::{
query::{Added, Changed, Has, Or},
schedule::IntoScheduleConfigs,
spawn::{SpawnRelated, SpawnableList},
system::{Commands, Query},
system::{Commands, In, Query},
};
use bevy_input_focus::tab_navigation::TabIndex;
use bevy_picking::{hover::Hovered, PickingSystems};
Expand Down Expand Up @@ -45,7 +45,7 @@ pub struct ButtonProps {
/// Rounded corners options
pub corners: RoundedCorners,
/// Click handler
pub on_click: Callback,
pub on_click: Callback<In<Activate>>,
}

/// Template function to spawn a button.
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_feathers/src/controls/checkbox.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use bevy_app::{Plugin, PreUpdate};
use bevy_core_widgets::{Callback, CoreCheckbox};
use bevy_core_widgets::{Callback, CoreCheckbox, ValueChange};
use bevy_ecs::{
bundle::Bundle,
children,
Expand Down Expand Up @@ -34,7 +34,7 @@ use crate::{
#[derive(Default)]
pub struct CheckboxProps {
/// Change handler
pub on_change: Callback<In<bool>>,
pub on_change: Callback<In<ValueChange<bool>>>,
}

/// Marker for the checkbox frame (contains both checkbox and label)
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_feathers/src/controls/slider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use core::f32::consts::PI;

use bevy_app::{Plugin, PreUpdate};
use bevy_color::Color;
use bevy_core_widgets::{Callback, CoreSlider, SliderRange, SliderValue, TrackClick};
use bevy_core_widgets::{Callback, CoreSlider, SliderRange, SliderValue, TrackClick, ValueChange};
use bevy_ecs::{
bundle::Bundle,
children,
Expand Down Expand Up @@ -42,7 +42,7 @@ pub struct SliderProps {
/// Slider maximum value
pub max: f32,
/// On-change handler
pub on_change: Callback<In<f32>>,
pub on_change: Callback<In<ValueChange<f32>>>,
}

impl Default for SliderProps {
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_feathers/src/controls/toggle_switch.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use accesskit::Role;
use bevy_a11y::AccessibilityNode;
use bevy_app::{Plugin, PreUpdate};
use bevy_core_widgets::{Callback, CoreCheckbox};
use bevy_core_widgets::{Callback, CoreCheckbox, ValueChange};
use bevy_ecs::{
bundle::Bundle,
children,
Expand Down Expand Up @@ -30,7 +30,7 @@ use crate::{
#[derive(Default)]
pub struct ToggleSwitchProps {
/// Change handler
pub on_change: Callback<In<bool>>,
pub on_change: Callback<In<ValueChange<bool>>>,
}

/// Marker for the toggle switch outline
Expand Down
33 changes: 19 additions & 14 deletions examples/ui/core_widgets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
use bevy::{
color::palettes::basic::*,
core_widgets::{
Callback, CoreButton, CoreCheckbox, CoreRadio, CoreRadioGroup, CoreSlider,
Activate, Callback, CoreButton, CoreCheckbox, CoreRadio, CoreRadioGroup, CoreSlider,
CoreSliderDragState, CoreSliderThumb, CoreWidgetsPlugins, SliderRange, SliderValue,
TrackClick,
TrackClick, ValueChange,
},
input_focus::{
tab_navigation::{TabGroup, TabIndex, TabNavigationPlugin},
Expand Down Expand Up @@ -120,24 +120,24 @@ fn update_widget_values(

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

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

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

fn demo_root(
asset_server: &AssetServer,
on_click: Callback,
on_change_value: Callback<In<f32>>,
on_change_radio: Callback<In<Entity>>,
on_click: Callback<In<Activate>>,
on_change_value: Callback<In<ValueChange<f32>>>,
on_change_radio: Callback<In<Activate>>,
) -> impl Bundle {
(
Node {
Expand All @@ -181,7 +181,7 @@ fn demo_root(
)
}

fn button(asset_server: &AssetServer, on_click: Callback) -> impl Bundle {
fn button(asset_server: &AssetServer, on_click: Callback<In<Activate>>) -> impl Bundle {
(
Node {
width: Val::Px(150.0),
Expand Down Expand Up @@ -324,7 +324,12 @@ fn set_button_style(
}

/// Create a demo slider
fn slider(min: f32, max: f32, value: f32, on_change: Callback<In<f32>>) -> impl Bundle {
fn slider(
min: f32,
max: f32,
value: f32,
on_change: Callback<In<ValueChange<f32>>>,
) -> impl Bundle {
(
Node {
display: Display::Flex,
Expand Down Expand Up @@ -469,7 +474,7 @@ fn thumb_color(disabled: bool, hovered: bool) -> Color {
fn checkbox(
asset_server: &AssetServer,
caption: &str,
on_change: Callback<In<bool>>,
on_change: Callback<In<ValueChange<bool>>>,
) -> impl Bundle {
(
Node {
Expand Down Expand Up @@ -662,7 +667,7 @@ fn set_checkbox_or_radio_style(
}

/// Create a demo radio group
fn radio_group(asset_server: &AssetServer, on_change: Callback<In<Entity>>) -> impl Bundle {
fn radio_group(asset_server: &AssetServer, on_change: Callback<In<Activate>>) -> impl Bundle {
(
Node {
display: Display::Flex,
Expand Down
Loading