diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index c5211cd14850e..fa898f0d6e1f9 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -3,7 +3,8 @@ use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ change_detection::DetectChangesMut, entity::Entity, - prelude::{Component, With}, + event, + prelude::{Component, Event, With}, query::WorldQuery, reflect::ReflectComponent, system::{Local, Query, Res}, @@ -55,6 +56,15 @@ impl Default for Interaction { } } +/// Used to publish which entity was clicked +/// +/// Commonly used by creating a UI node and using [`EventReader`](event::EventReader) +/// to obtain the list of clicked UI nodes. +/// +/// Note click captures the full click/press-release action. +#[derive(Event)] +pub struct Clicked(pub Entity); + /// A component storing the position of the mouse relative to the node, (0., 0.) being the top-left corner and (1., 1.) being the bottom-right /// If the mouse is not over the node, the value will go beyond the range of (0., 0.) to (1., 1.) /// A None value means that the cursor position is unknown. @@ -142,6 +152,7 @@ pub fn ui_focus_system( ui_stack: Res, mut node_query: Query, primary_window: Query>, + mut click_events: event::EventWriter, ) { let primary_window = primary_window.iter().next(); @@ -245,6 +256,10 @@ pub fn ui_focus_system( } if contains_cursor { + // Emit a click event only if mouse was released under the button + if mouse_released { + click_events.send(Clicked(*entity)); + } Some(*entity) } else { if let Some(mut interaction) = node.interaction { diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index 1752180b77d86..dff394ebe4054 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -36,7 +36,7 @@ pub mod prelude { #[doc(hidden)] pub use crate::{ camera_config::*, geometry::*, node_bundles::*, ui_node::*, widget::Button, widget::Label, - Interaction, UiScale, + Clicked, Interaction, UiScale, }; } @@ -118,6 +118,7 @@ impl Plugin for UiPlugin { .register_type::() .register_type::() .register_type::() + .add_event::() .add_systems( PreUpdate, ui_focus_system.in_set(UiSystem::Focus).after(InputSystem), diff --git a/examples/ecs/state.rs b/examples/ecs/state.rs index 31c0346206bdb..9c0e3944efafb 100644 --- a/examples/ecs/state.rs +++ b/examples/ecs/state.rs @@ -16,14 +16,15 @@ fn main() { // All systems from the exit schedule of the state we're leaving are run first, // and then all systems from the enter schedule of the state we're entering are run second. .add_systems(OnEnter(AppState::Menu), setup_menu) - // By contrast, update systems are stored in the `Update` schedule. They simply - // check the value of the `State` resource to see if they should run each frame. - .add_systems(Update, menu.run_if(in_state(AppState::Menu))) .add_systems(OnExit(AppState::Menu), cleanup_menu) .add_systems(OnEnter(AppState::InGame), setup_game) .add_systems( Update, - (movement, change_color).run_if(in_state(AppState::InGame)), + ( + menu_interaction.run_if(in_state(AppState::Menu)), + menu_action.run_if(on_event::()), + (movement, change_color).run_if(in_state(AppState::InGame)), + ), ) .run(); } @@ -90,8 +91,7 @@ fn setup_menu(mut commands: Commands) { commands.insert_resource(MenuData { button_entity }); } -fn menu( - mut next_state: ResMut>, +fn menu_interaction( mut interaction_query: Query< (&Interaction, &mut BackgroundColor), (Changed, With