Skip to content
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

Add event for clicks #9240

Closed
wants to merge 32 commits into from
Closed
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d7c1113
Add API to support publishing click events for UI nodes
Jul 22, 2023
ae0e726
Refactor game_menu example to use Click events
Jul 22, 2023
b63703f
Add LastInteraction into prelude
Jul 22, 2023
615db07
Refactor for LastInteraction In ButtonBundle
Jul 23, 2023
8db5137
Fix PreUpdate systems call order
Jul 24, 2023
bdd29aa
Revert to old name for menu_action
Jul 24, 2023
396d95f
Extend state example to use Click events
Jul 24, 2023
7169033
Remove EventReader<Click> for simpler add_systems call
Jul 24, 2023
68d8f7a
Fix name error for function calls
Jul 24, 2023
7805a07
Combine Update add systems calls
Jul 24, 2023
b62bab1
For reals combine all add_systems calls run on Update
Jul 24, 2023
47b3226
Move add order for consistency
Jul 24, 2023
bb0a31d
Refactor display & visibility example for click events
Sep 2, 2023
020baca
Merge branch 'main' into add-event-for-clicks
Sep 2, 2023
9134ca3
Fix game menu example for changed event reader iterator
Sep 2, 2023
f07e153
Fix event reader iterator call on missed example
Sep 2, 2023
0729791
Minor grammar and syntax changes
Sep 2, 2023
898d202
Refactor size constraints example for click event
Sep 2, 2023
5174150
Remove ButtonActivatedEvent, replace with Click
Sep 2, 2023
bf4655c
Fix failing doc test
Sep 2, 2023
8dd6901
Change scope of event writer in an attempt to fix doc test
Sep 3, 2023
6842ccf
Simplify send of click event
Sep 3, 2023
15abb9c
Remove LastInteraction struct
Sep 3, 2023
94c39d9
Remove unused import
Sep 3, 2023
589d9d0
Simplify tuple unpacking for click events in examples
Sep 3, 2023
86961a3
Change tuple packing back
Sep 3, 2023
95d34b5
Move the click send to incorporate hover
Sep 3, 2023
ad556b2
Amend click event comment
Sep 4, 2023
5f12182
Change Click -> Clicked
Sep 6, 2023
198542a
Refactor iteration to old method
Sep 6, 2023
9f92e48
Fix click typo
Sep 6, 2023
4c9b271
Remove old comment
Sep 7, 2023
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
37 changes: 36 additions & 1 deletion crates/bevy_ui/src/focus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
change_detection::DetectChangesMut,
entity::Entity,
prelude::{Component, With},
event::EventWriter,
prelude::{Changed, Component, Event, With},
query::WorldQuery,
reflect::ReflectComponent,
system::{Local, Query, Res},
Expand Down Expand Up @@ -55,6 +56,22 @@ impl Default for Interaction {
}
}

/// Holds the last interaction that occurred for a UI node.
///
/// This is commonly used by adding `LastInteraction` as a member variable
/// to a UI node such that `Click` events are sent on click/press-release action.
AnbyKatz marked this conversation as resolved.
Show resolved Hide resolved
#[derive(Component, Default, Debug, Clone)]
pub struct LastInteraction(Interaction);

/// Used to publish which entity was clicked
///
/// Commonly used by creating a UI node that contains `LastInteraction` as a member
/// and then using `EventReader<Click>` to obtain the list of clicked UI nodes.
AnbyKatz marked this conversation as resolved.
Show resolved Hide resolved
///
/// Note click captures the full click/press-release action.
#[derive(Event)]
pub struct Click(pub Entity);
AnbyKatz marked this conversation as resolved.
Show resolved Hide resolved

/// 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.
Expand Down Expand Up @@ -296,3 +313,21 @@ pub fn ui_focus_system(
}
}
}

/// The system that sends a click event to publish which entities where clicked
/// for all UI nodes that contain the `LastInteraction` `Component`
AnbyKatz marked this conversation as resolved.
Show resolved Hide resolved
///
/// Included by default with `UiPlugin`.
pub fn ui_click(
mut click_events: EventWriter<Click>,
mut buttons: Query<(Entity, &Interaction, &mut LastInteraction), Changed<Interaction>>,
) {
for (entity, &interaction, mut last_interaction) in &mut buttons {
// Publish a click event for every entity that has changed in one cycle from
// `Interaction::Hovered` to `Interaction::Pressed`
if interaction == Interaction::Hovered && last_interaction.0 == Interaction::Pressed {
click_events.send(Click(entity));
}
last_interaction.0 = interaction;
}
}
9 changes: 7 additions & 2 deletions crates/bevy_ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pub mod prelude {
#[doc(hidden)]
pub use crate::{
camera_config::*, geometry::*, node_bundles::*, ui_node::*, widget::Button, widget::Label,
Interaction, UiScale,
Click, Interaction, LastInteraction, UiScale,
};
}

Expand Down Expand Up @@ -120,9 +120,14 @@ impl Plugin for UiPlugin {
.register_type::<widget::Button>()
.register_type::<widget::Label>()
.register_type::<ZIndex>()
.add_event::<Click>()
.add_systems(
PreUpdate,
ui_focus_system.in_set(UiSystem::Focus).after(InputSystem),
(
ui_focus_system.in_set(UiSystem::Focus).after(InputSystem),
ui_click,
)
.chain(),
);
// add these systems to front because these must run before transform update systems
#[cfg(feature = "bevy_text")]
Expand Down
7 changes: 5 additions & 2 deletions crates/bevy_ui/src/node_bundles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
use crate::widget::TextFlags;
use crate::{
widget::{Button, UiImageSize},
BackgroundColor, BorderColor, ContentSize, FocusPolicy, Interaction, Node, Style, UiImage,
UiTextureAtlasImage, ZIndex,
BackgroundColor, BorderColor, ContentSize, FocusPolicy, Interaction, LastInteraction, Node,
Style, UiImage, UiTextureAtlasImage, ZIndex,
};
use bevy_asset::Handle;
use bevy_ecs::bundle::Bundle;
Expand Down Expand Up @@ -305,6 +305,8 @@ pub struct ButtonBundle {
pub computed_visibility: ComputedVisibility,
/// Indicates the depth at which the node should appear in the UI
pub z_index: ZIndex,
/// Holds the previous Interation
pub last_interaction: LastInteraction,
}

impl Default for ButtonBundle {
Expand All @@ -323,6 +325,7 @@ impl Default for ButtonBundle {
visibility: Default::default(),
computed_visibility: Default::default(),
z_index: Default::default(),
last_interaction: Default::default(),
}
}
}
16 changes: 12 additions & 4 deletions examples/ecs/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ fn main() {
.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<T>` resource to see if they should run each frame.
.add_systems(Update, menu.run_if(in_state(AppState::Menu)))
.add_systems(
Update,
(
menu_interaction.run_if(in_state(AppState::Menu)),
menu_action.run_if(on_event::<Click>()),
),
AnbyKatz marked this conversation as resolved.
Show resolved Hide resolved
)
.add_systems(OnExit(AppState::Menu), cleanup_menu)
.add_systems(OnEnter(AppState::InGame), setup_game)
.add_systems(
Expand Down Expand Up @@ -90,8 +96,7 @@ fn setup_menu(mut commands: Commands) {
commands.insert_resource(MenuData { button_entity });
}

fn menu(
mut next_state: ResMut<NextState<AppState>>,
fn menu_interaction(
mut interaction_query: Query<
(&Interaction, &mut BackgroundColor),
(Changed<Interaction>, With<Button>),
Expand All @@ -101,7 +106,6 @@ fn menu(
match *interaction {
Interaction::Pressed => {
*color = PRESSED_BUTTON.into();
next_state.set(AppState::InGame);
}
Interaction::Hovered => {
*color = HOVERED_BUTTON.into();
Expand All @@ -113,6 +117,10 @@ fn menu(
}
}

fn menu_action(mut next_state: ResMut<NextState<AppState>>) {
next_state.set(AppState::InGame);
}

fn cleanup_menu(mut commands: Commands, menu_data: Res<MenuData>) {
commands.entity(menu_data.button_entity).despawn_recursive();
}
Expand Down
12 changes: 5 additions & 7 deletions examples/games/game_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -780,17 +780,15 @@ mod menu {
}

fn menu_action(
interaction_query: Query<
(&Interaction, &MenuButtonAction),
(Changed<Interaction>, With<Button>),
>,
mut click_events: EventReader<Click>,
menu_buttons: Query<&MenuButtonAction>,
mut app_exit_events: EventWriter<AppExit>,
mut menu_state: ResMut<NextState<MenuState>>,
mut game_state: ResMut<NextState<GameState>>,
) {
for (interaction, menu_button_action) in &interaction_query {
if *interaction == Interaction::Pressed {
match menu_button_action {
for event in &mut click_events {
if let Ok(menu_button) = menu_buttons.get(event.0) {
match menu_button {
MenuButtonAction::Quit => app_exit_events.send(AppExit),
MenuButtonAction::Play => {
game_state.set(GameState::Game);
Expand Down