diff --git a/crates/bevy_ui/src/entity.rs b/crates/bevy_ui/src/entity.rs index 680a88dca0c47..6179e7bb85e78 100644 --- a/crates/bevy_ui/src/entity.rs +++ b/crates/bevy_ui/src/entity.rs @@ -2,7 +2,7 @@ use super::Node; use crate::{ render::UI_PIPELINE_HANDLE, widget::{Button, Image, Text}, - CalculatedSize, FocusPolicy, Interaction, PropagatePolicy, Style, + CalculatedSize, Interaction, PropagatePolicy, Style, }; use bevy_asset::Handle; use bevy_ecs::Bundle; @@ -117,7 +117,6 @@ pub struct TextComponents { pub draw: Draw, pub text: Text, pub calculated_size: CalculatedSize, - pub focus_policy: FocusPolicy, pub propagate_policy: PropagatePolicy, pub transform: Transform, pub local_transform: LocalTransform, @@ -126,7 +125,6 @@ pub struct TextComponents { impl Default for TextComponents { fn default() -> Self { TextComponents { - focus_policy: FocusPolicy::Pass, propagate_policy: PropagatePolicy::Pass, draw: Draw { is_transparent: true, @@ -148,7 +146,6 @@ pub struct ButtonComponents { pub button: Button, pub style: Style, pub interaction: Interaction, - pub focus_policy: FocusPolicy, pub propagate_policy: PropagatePolicy, pub mesh: Handle, // TODO: maybe abstract this out pub material: Handle, @@ -182,7 +179,6 @@ impl Default for ButtonComponents { }, )]), interaction: Default::default(), - focus_policy: Default::default(), propagate_policy: Default::default(), node: Default::default(), style: Default::default(), diff --git a/crates/bevy_ui/src/events.rs b/crates/bevy_ui/src/events.rs deleted file mode 100644 index d007fe6fdc958..0000000000000 --- a/crates/bevy_ui/src/events.rs +++ /dev/null @@ -1,183 +0,0 @@ -use crate::Node; -use bevy_app::{EventReader, Events}; -use bevy_core::FloatOrd; -use bevy_ecs::prelude::*; -use bevy_input::{mouse::MouseButton, Input}; -use bevy_math::Vec2; -use bevy_transform::components::Transform; -use bevy_window::CursorMoved; -use std::{ - collections::{HashMap, HashSet}, - time::{Duration, Instant}, -}; - -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub enum PropagatePolicy { - Block, - Pass, -} - -impl Default for PropagatePolicy { - fn default() -> Self { - PropagatePolicy::Block - } -} - -#[derive(Debug, Clone)] -pub struct MouseDown { - pub entity: Entity, -} - -#[derive(Debug, Clone)] -pub struct MouseUp { - pub entity: Entity, -} - -#[derive(Debug, Clone)] -pub struct MouseEnter { - pub entity: Entity, -} - -#[derive(Debug, Clone)] -pub struct MouseLeave { - pub entity: Entity, -} - -#[derive(Debug, Clone)] -pub struct MouseHover { - pub entity: Entity, -} - -#[derive(Debug, Clone)] -pub struct Click { - pub entity: Entity, -} - -#[derive(Debug, Clone)] -pub struct DoubleClick { - pub entity: Entity, -} - -pub struct EventState { - cursor_moved_event_reader: EventReader, - cursor_position: Vec2, - hovered_entities: HashSet, - pressed_entities: HashSet, - clicked_entities: HashMap, -} - -impl Default for EventState { - fn default() -> Self { - EventState { - cursor_moved_event_reader: Default::default(), - cursor_position: Default::default(), - hovered_entities: HashSet::new(), - pressed_entities: HashSet::new(), - clicked_entities: HashMap::new(), - } - } -} - -pub fn ui_event_system( - mut state: Local, - mouse_button_input: Res>, - cursor_moved_events: Res>, - mut events: ( - ResMut>, - ResMut>, - ResMut>, - ResMut>, - ResMut>, - ResMut>, - ResMut>, - ), - mut node_query: Query<(Entity, &Node, &Transform, Option<&PropagatePolicy>)>, -) { - let ( - mut mouse_down_events, - mut mouse_up_events, - mut mouse_enter_events, - mut mouse_leave_events, - mut mouse_hover_events, - mut click_events, - mut double_click_events, - ) = events; - - if let Some(cursor_moved) = state.cursor_moved_event_reader.latest(&cursor_moved_events) { - state.cursor_position = cursor_moved.position; - } - - let mut query_iter = node_query.iter(); - let mut moused_over_z_sorted_nodes = query_iter - .iter() - .filter_map(|(entity, node, transform, propagate_policy)| { - let position = transform.value.w_axis(); - let ui_position = position.truncate().truncate(); - let extents = node.size / 2.0; - let min = ui_position - extents; - let max = ui_position + extents; - // if the current cursor position is within the bounds of the node, consider it for events - if (min.x()..max.x()).contains(&state.cursor_position.x()) - && (min.y()..max.y()).contains(&state.cursor_position.y()) - { - Some((entity, propagate_policy, FloatOrd(position.z()))) - } else { - None - } - }) - .collect::>(); - - moused_over_z_sorted_nodes.sort_by_key(|(_, _, z)| -*z); - - let mouse_pressed = mouse_button_input.just_pressed(MouseButton::Left); - let mouse_released = mouse_button_input.just_released(MouseButton::Left); - - let mut new_hovered_entities = HashSet::new(); - for (entity, propagate_policy, _) in moused_over_z_sorted_nodes { - new_hovered_entities.insert(entity); - - if !state.hovered_entities.contains(&entity) { - mouse_enter_events.send(MouseEnter { entity }); - state.hovered_entities.insert(entity); - } - - if mouse_pressed { - state.pressed_entities.insert(entity); - mouse_down_events.send(MouseDown { entity }); - } - - if mouse_released { - mouse_up_events.send(MouseUp { entity }); - if state.pressed_entities.contains(&entity) { - click_events.send(Click { entity }); - if state.clicked_entities.contains_key(&entity) { - double_click_events.send(DoubleClick { entity }); - } - state.clicked_entities.insert(entity, Instant::now()); - } - } - - mouse_hover_events.send(MouseHover { entity }); - - match propagate_policy.cloned().unwrap_or(PropagatePolicy::Block) { - PropagatePolicy::Block => { - break; - } - PropagatePolicy::Pass => { /* allow the next node to be hovered/clicked */ } - } - } - - for entity in state.hovered_entities.clone().difference(&new_hovered_entities) { - mouse_leave_events.send(MouseLeave { - entity: *entity, - }); - state.pressed_entities.remove(&entity); - } - - state.hovered_entities = new_hovered_entities; - - let time = Instant::now(); - state - .clicked_entities - .retain(|_entity, clicked_time| (time - *clicked_time).as_millis() < 300); -} diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs deleted file mode 100644 index 20caeb46150d9..0000000000000 --- a/crates/bevy_ui/src/focus.rs +++ /dev/null @@ -1,93 +0,0 @@ -use crate::{events::*, Node}; -use bevy_app::{EventReader, Events}; -use bevy_core::FloatOrd; -use bevy_ecs::prelude::*; -use bevy_input::{mouse::MouseButton, Input}; -use bevy_math::Vec2; -use bevy_transform::components::Transform; -use bevy_window::CursorMoved; - -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub enum Interaction { - Pressed, - Hovered, - None, -} - -impl Default for Interaction { - fn default() -> Self { - Interaction::None - } -} - -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -pub enum FocusPolicy { - Block, - Pass, -} - -impl Default for FocusPolicy { - fn default() -> Self { - FocusPolicy::Block - } -} - -#[derive(Default)] -pub struct State { - mousedown_reader: EventReader, - mouseup_reader: EventReader, - mouseenter_reader: EventReader, - mouseleave_reader: EventReader, -} - -pub fn ui_focus_system( - mut state: Local, - ui_events: ( - Res>, - Res>, - Res>, - Res>, - ), - mut node_query: Query<( - Entity, - &Node, - Option<&mut Interaction>, - Option<&FocusPolicy>, - )>, -) { - let (mousedown_events, mouseup_events, mouseenter_events, mouseleave_events) = ui_events; - - for (entity, _node, interaction, _) in &mut node_query.iter() { - if let Some(mut interaction) = interaction { - for event in state.mousedown_reader.iter(&mousedown_events) { - if event.entity == entity { - if *interaction != Interaction::Pressed { - *interaction = Interaction::Pressed; - } - } - } - - for event in state.mouseup_reader.iter(&mouseup_events) { - if event.entity == entity { - if *interaction == Interaction::Pressed { - *interaction = Interaction::Hovered; - } - } - } - - for event in state.mouseleave_reader.iter(&mouseleave_events) { - if event.entity == entity { - *interaction = Interaction::None - } - } - - for event in state.mouseenter_reader.iter(&mouseenter_events) { - if event.entity == entity { - if *interaction != Interaction::Hovered { - *interaction = Interaction::Hovered; - } - } - } - } - } -} diff --git a/crates/bevy_ui/src/interaction.rs b/crates/bevy_ui/src/interaction.rs new file mode 100644 index 0000000000000..02f3a16ef6bc9 --- /dev/null +++ b/crates/bevy_ui/src/interaction.rs @@ -0,0 +1,221 @@ +use crate::Node; +use bevy_app::{EventReader, Events}; +use bevy_core::FloatOrd; +use bevy_ecs::prelude::*; +use bevy_input::{mouse::MouseButton, Input}; +use bevy_math::{vec2, Vec2}; +use bevy_transform::components::Transform; +use bevy_window::CursorMoved; +use std::collections::HashSet; + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum PropagatePolicy { + Block, + Pass, +} + +impl Default for PropagatePolicy { + fn default() -> Self { + PropagatePolicy::Block + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum Interaction { + Pressed, + Hovered, + None, +} + +impl Default for Interaction { + fn default() -> Self { + Interaction::None + } +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum RegionAction { + Enter, + Exit, + Hover(Vec2), + Move(Vec2), +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum PressAction { + Up, + Down, +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub struct PointerRegion { + pub entity: Entity, + pub action: RegionAction, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct PointerPress { + pub entity: Entity, + pub action: PressAction, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct PointerClick { + pub entity: Entity, +} + +pub struct EventState { + cursor_moved_event_reader: EventReader, + cursor_position: Vec2, + hovered_entities: HashSet, + pressed_entities: HashSet, +} + +impl Default for EventState { + fn default() -> Self { + EventState { + cursor_moved_event_reader: Default::default(), + cursor_position: Default::default(), + hovered_entities: HashSet::new(), + pressed_entities: HashSet::new(), + } + } +} + +pub fn ui_event_system( + mut state: Local, + mouse_button_input: Res>, + cursor_moved_events: Res>, + mut region_events: ResMut>, + mut click_events: ResMut>, + mut press_events: ResMut>, + mut node_query: Query<( + Entity, + &Node, + &mut Interaction, + &Transform, + Option<&PropagatePolicy>, + )>, +) { + let mut cursor_has_moved = false; + if let Some(cursor_moved) = state.cursor_moved_event_reader.latest(&cursor_moved_events) { + state.cursor_position = cursor_moved.position; + cursor_has_moved = true; + } + + let mut new_hovered_entities = HashSet::new(); + { + let mut query_iter = node_query.iter(); + let mut moused_over_z_sorted_nodes = query_iter + .iter() + .filter_map(|(entity, node, interaction, transform, propagate_policy)| { + let position = transform.value.w_axis(); + let ui_position = position.truncate().truncate(); + let extents = node.size / 2.0; + let min = ui_position - extents; + let max = ui_position + extents; + let cursor_x = state.cursor_position.x(); + let cursor_y = state.cursor_position.y(); + + // if the current cursor position is within the bounds of the node, consider it for events + if (min.x()..max.x()).contains(&cursor_x) && (min.y()..max.y()).contains(&cursor_y) + { + Some(( + entity, + interaction, + vec2(cursor_x - min.x(), cursor_y - min.y()), + propagate_policy, + FloatOrd(position.z()), + )) + } else { + None + } + }) + .collect::>(); + + moused_over_z_sorted_nodes.sort_by_key(|(_, _, _, _, z)| -*z); + + let mouse_pressed = mouse_button_input.just_pressed(MouseButton::Left); + let mouse_released = mouse_button_input.just_released(MouseButton::Left); + + for (entity, mut interaction, position, propagate_policy, _) in moused_over_z_sorted_nodes { + new_hovered_entities.insert(entity); + + if !state.hovered_entities.contains(&entity) { + state.hovered_entities.insert(entity); + + region_events.send(PointerRegion { + entity, + action: RegionAction::Enter, + }); + if state.pressed_entities.contains(&entity) { + *interaction = Interaction::Pressed; + } else { + *interaction = Interaction::Hovered; + } + } + + if mouse_pressed { + state.pressed_entities.insert(entity); + + press_events.send(PointerPress { + entity, + action: PressAction::Down, + }); + + *interaction = Interaction::Pressed; + } + + if mouse_released { + press_events.send(PointerPress { + entity, + action: PressAction::Up, + }); + if state.pressed_entities.contains(&entity) { + click_events.send(PointerClick { entity }); + } + + *interaction = Interaction::Hovered; + state.pressed_entities.clear(); + } + + if cursor_has_moved { + if state.pressed_entities.contains(&entity) { + region_events.send(PointerRegion { + entity, + action: RegionAction::Move(position), + }); + } else { + region_events.send(PointerRegion { + entity, + action: RegionAction::Hover(position), + }); + } + } + + match propagate_policy.cloned().unwrap_or(PropagatePolicy::Block) { + PropagatePolicy::Block => { + break; + } + PropagatePolicy::Pass => { /* allow the next node to be hovered/clicked */ } + } + } + } + + for entity in state + .hovered_entities + .clone() + .difference(&new_hovered_entities) + { + region_events.send(PointerRegion { + entity: *entity, + action: RegionAction::Exit, + }); + + if let Ok(mut interaction) = node_query.get_mut::(*entity) { + *interaction = Interaction::None; + } + } + + state.hovered_entities = new_hovered_entities; +} diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index 7efee485a2b75..f2b9573c62fc9 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -1,8 +1,7 @@ mod anchors; pub mod entity; -mod events; mod flex; -mod focus; +mod interaction; mod margins; mod node; mod render; @@ -10,9 +9,8 @@ pub mod update; pub mod widget; pub use anchors::*; -pub use events::*; pub use flex::*; -pub use focus::*; +pub use interaction::*; pub use margins::*; pub use node::*; pub use render::*; @@ -20,7 +18,7 @@ pub use render::*; pub mod prelude { pub use crate::{ entity::*, - events::*, + interaction::*, node::*, widget::{Button, Text}, Anchors, Interaction, Margins, @@ -41,23 +39,18 @@ pub mod stage { impl Plugin for UiPlugin { fn build(&self, app: &mut AppBuilder) { - app.add_event::() - .add_event::() - .add_event::() - .add_event::() - .add_event::() - .add_event::() - .add_event::() - .init_resource::() + app.init_resource::() .add_stage_before(bevy_app::stage::POST_UPDATE, stage::UI) - .add_system_to_stage(bevy_app::stage::PRE_UPDATE, ui_focus_system.system()) .add_system_to_stage(bevy_app::stage::PRE_UPDATE, ui_event_system.system()) // add these stages to front because these must run before transform update systems .add_system_to_stage(stage::UI, widget::text_system.system()) .add_system_to_stage(stage::UI, widget::image_node_system.system()) .add_system_to_stage(stage::UI, ui_z_system.system()) .add_system_to_stage(stage::UI, flex_node_system.system()) - .add_system_to_stage(bevy_render::stage::DRAW, widget::draw_text_system.system()); + .add_system_to_stage(bevy_render::stage::DRAW, widget::draw_text_system.system()) + .add_event::() + .add_event::() + .add_event::(); let resources = app.resources(); let mut render_graph = resources.get_mut::().unwrap(); diff --git a/examples/ui/button.rs b/examples/ui/button.rs index d809c7a2e44ad..6aac73c2acd72 100644 --- a/examples/ui/button.rs +++ b/examples/ui/button.rs @@ -30,25 +30,18 @@ impl FromResources for ButtonMaterials { #[derive(Default)] struct EventListenerState { - mousedown_reader: EventReader, - mouseup_reader: EventReader, - mouseenter_reader: EventReader, - mouseleave_reader: EventReader, - click_reader: EventReader, - doubleclick_reader: EventReader, + region_reader: EventReader, + press_reader: EventReader, + click_reader: EventReader, } +#[allow(clippy::too_many_arguments)] fn button_system( button_materials: Res, mut state: ResMut, - button_events: ( - Res>, - Res>, - Res>, - Res>, - Res>, - Res>, - ), + region_events: Res>, + press_events: Res>, + click_events: Res>, mut interaction_query: Query<( &Button, Mutated, @@ -58,15 +51,6 @@ fn button_system( event_query: Query<&Button>, text_query: Query<&mut Text>, ) { - let ( - mousedown_events, - mouseup_events, - mouseenter_events, - mouseleave_events, - click_events, - doubleclick_events, - ) = button_events; - for (_button, interaction, mut material, children) in &mut interaction_query.iter() { let mut text = text_query.get_mut::(children[0]).unwrap(); match *interaction { @@ -85,39 +69,29 @@ fn button_system( } } - for my_event in state.mousedown_reader.iter(&mousedown_events) { - if let Ok(_button) = event_query.get::