use core::{fmt::Debug, time::Duration};
use std::collections::HashSet;
use bevy_camera::NormalizedRenderTarget;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
entity::{EntityHashMap, EntityHashSet},
prelude::*,
query::QueryData,
system::SystemParam,
traversal::Traversal,
};
use bevy_input::{
mouse::{MouseScrollPixelsPerLine, MouseScrollUnit},
touch::TouchPhase,
};
use bevy_math::Vec2;
use bevy_platform::collections::HashMap;
use bevy_platform::time::Instant;
use bevy_reflect::prelude::*;
use bevy_window::Window;
use tracing::debug;
use crate::{
backend::{prelude::PointerLocation, HitData},
hover::{get_hovered_entities, is_directly_hovered, HoverMap, PreviousHoverMap},
pointer::{Location, PointerAction, PointerButton, PointerId, PointerInput, PointerMap},
PickingSettings,
};
#[derive(Clone, PartialEq, Debug, Reflect)]
pub struct Pointer {
pub id: PointerId,
pub target: NormalizedRenderTarget,
pub position: Vec2,
pub(crate) propagate: bool,
}
impl Pointer {
pub fn new(id: PointerId, location: Location) -> Self {
Self {
id,
target: location.target,
position: location.position,
propagate: true,
}
}
pub(crate) fn without_propagate(&self) -> Self {
Self {
id: self.id,
target: self.target.clone(),
position: self.position,
propagate: false,
}
}
pub fn location(&self) -> Location {
Location {
position: self.position,
target: self.target.clone(),
}
}
}
#[derive(QueryData)]
pub struct PointerTraversal {
child_of: Option<&'static ChildOf>,
window: Option<&'static Window>,
}
pub trait PointerEvent: EntityEvent {
fn pointer(&self) -> &Pointer;
}
impl<E> Traversal<E> for PointerTraversal
where
E: PointerEvent,
{
fn traverse(item: Self::Item<'_, '_>, event: &E) -> Option<Entity> {
if !event.pointer().propagate {
return None;
}
let PointerTraversalItem { child_of, window } = item;
if let Some(child_of) = child_of {
return Some(child_of.parent());
};
if window.is_none()
&& let NormalizedRenderTarget::Window(window_ref) = event.pointer().target
{
return Some(window_ref.entity());
}
None
}
}
macro_rules! impl_pointer_event {
($event:ident) => {
impl PointerEvent for $event {
fn pointer(&self) -> &Pointer {
&self.pointer
}
}
};
}
#[derive(EntityEvent, Message, Clone, PartialEq, Debug, Reflect)]
#[entity_event(propagate = PointerTraversal, auto_propagate)]
#[reflect(Clone, PartialEq)]
pub struct PointerCancel {
pub entity: Entity,
pub pointer: Pointer,
pub hit: HitData,
}
impl_pointer_event!(PointerCancel);
#[derive(EntityEvent, Message, Clone, PartialEq, Debug, Reflect)]
#[entity_event(propagate = PointerTraversal, auto_propagate)]
#[reflect(Clone, PartialEq)]
pub struct PointerOver {
pub entity: Entity,
pub pointer: Pointer,
pub hit: HitData,
}
impl_pointer_event!(PointerOver);
#[derive(EntityEvent, Message, Clone, PartialEq, Debug, Reflect)]
#[entity_event(propagate = PointerTraversal, auto_propagate)]
#[reflect(Clone, PartialEq)]
pub struct PointerEnter {
pub entity: Entity,
pub pointer: Pointer,
pub hit: HitData,
pub is_in_bounds: bool,
}
impl_pointer_event!(PointerEnter);
#[derive(EntityEvent, Message, Clone, PartialEq, Debug, Reflect)]
#[entity_event(propagate = PointerTraversal, auto_propagate)]
#[reflect(Clone, PartialEq)]
pub struct PointerOut {
pub entity: Entity,
pub pointer: Pointer,
pub hit: HitData,
}
impl_pointer_event!(PointerOut);
#[derive(EntityEvent, Message, Clone, PartialEq, Debug, Reflect)]
#[entity_event(propagate = PointerTraversal, auto_propagate)]
#[reflect(Clone, PartialEq)]
pub struct PointerLeave {
pub entity: Entity,
pub pointer: Pointer,
pub hit: HitData,
pub was_in_bounds: bool,
}
impl_pointer_event!(PointerLeave);
#[derive(EntityEvent, Message, Clone, PartialEq, Debug, Reflect)]
#[entity_event(propagate = PointerTraversal, auto_propagate)]
#[reflect(Clone, PartialEq)]
pub struct PointerPress {
pub entity: Entity,
pub pointer: Pointer,
pub button: PointerButton,
pub hit: HitData,
pub count: u8,
}
impl_pointer_event!(PointerPress);
#[derive(EntityEvent, Message, Clone, PartialEq, Debug, Reflect)]
#[entity_event(propagate = PointerTraversal, auto_propagate)]
#[reflect(Clone, PartialEq)]
pub struct PointerRelease {
pub entity: Entity,
pub pointer: Pointer,
pub button: PointerButton,
pub hit: HitData,
}
impl_pointer_event!(PointerRelease);
#[derive(EntityEvent, Message, Clone, PartialEq, Debug, Reflect)]
#[entity_event(propagate = PointerTraversal, auto_propagate)]
#[reflect(Clone, PartialEq)]
pub struct PointerClick {
pub entity: Entity,
pub pointer: Pointer,
pub button: PointerButton,
pub hit: HitData,
pub duration: Duration,
pub count: u8,
}
impl_pointer_event!(PointerClick);
#[derive(EntityEvent, Message, Clone, PartialEq, Debug, Reflect)]
#[entity_event(propagate = PointerTraversal, auto_propagate)]
#[reflect(Clone, PartialEq)]
pub struct PointerMove {
pub entity: Entity,
pub pointer: Pointer,
pub hit: HitData,
pub delta: Vec2,
}
impl_pointer_event!(PointerMove);
#[derive(EntityEvent, Message, Clone, PartialEq, Debug, Reflect)]
#[entity_event(propagate = PointerTraversal, auto_propagate)]
#[reflect(Clone, PartialEq)]
pub struct PointerDragStart {
pub entity: Entity,
pub pointer: Pointer,
pub button: PointerButton,
pub hit: HitData,
}
impl_pointer_event!(PointerDragStart);
#[derive(EntityEvent, Message, Clone, PartialEq, Debug, Reflect)]
#[entity_event(propagate = PointerTraversal, auto_propagate)]
#[reflect(Clone, PartialEq)]
pub struct PointerDrag {
pub entity: Entity,
pub pointer: Pointer,
pub button: PointerButton,
pub distance: Vec2,
pub delta: Vec2,
}
impl_pointer_event!(PointerDrag);
#[derive(EntityEvent, Message, Clone, PartialEq, Debug, Reflect)]
#[entity_event(propagate = PointerTraversal, auto_propagate)]
#[reflect(Clone, PartialEq)]
pub struct PointerDragEnd {
pub entity: Entity,
pub pointer: Pointer,
pub button: PointerButton,
pub distance: Vec2,
}
impl_pointer_event!(PointerDragEnd);
#[derive(EntityEvent, Message, Clone, PartialEq, Debug, Reflect)]
#[entity_event(propagate = PointerTraversal, auto_propagate)]
#[reflect(Clone, PartialEq)]
pub struct PointerDragEnter {
pub entity: Entity,
pub pointer: Pointer,
pub button: PointerButton,
pub dragged: Entity,
pub hit: HitData,
}
impl_pointer_event!(PointerDragEnter);
#[derive(EntityEvent, Message, Clone, PartialEq, Debug, Reflect)]
#[entity_event(propagate = PointerTraversal, auto_propagate)]
#[reflect(Clone, PartialEq)]
pub struct PointerDragOver {
pub entity: Entity,
pub pointer: Pointer,
pub button: PointerButton,
pub dragged: Entity,
pub hit: HitData,
}
impl_pointer_event!(PointerDragOver);
#[derive(EntityEvent, Message, Clone, PartialEq, Debug, Reflect)]
#[entity_event(propagate = PointerTraversal, auto_propagate)]
#[reflect(Clone, PartialEq)]
pub struct PointerDragLeave {
pub entity: Entity,
pub pointer: Pointer,
pub button: PointerButton,
pub dragged: Entity,
pub hit: HitData,
}
impl_pointer_event!(PointerDragLeave);
#[derive(EntityEvent, Message, Clone, PartialEq, Debug, Reflect)]
#[entity_event(propagate = PointerTraversal, auto_propagate)]
#[reflect(Clone, PartialEq)]
pub struct PointerDragDrop {
pub entity: Entity,
pub pointer: Pointer,
pub button: PointerButton,
pub dropped: Entity,
pub hit: HitData,
}
impl_pointer_event!(PointerDragDrop);
#[derive(EntityEvent, Message, Clone, PartialEq, Debug, Reflect)]
#[entity_event(propagate = PointerTraversal, auto_propagate)]
#[reflect(Clone, PartialEq)]
pub struct PointerScroll {
pub entity: Entity,
pub pointer: Pointer,
pub unit: MouseScrollUnit,
pub x: f32,
pub y: f32,
pub hit: HitData,
pub phase: TouchPhase,
}
impl_pointer_event!(PointerScroll);
impl PointerScroll {
pub fn to_lines(&self, conversion_ratio: &MouseScrollPixelsPerLine) -> Self {
if self.unit == MouseScrollUnit::Pixel {
PointerScroll {
entity: self.entity,
pointer: self.pointer.clone(),
unit: MouseScrollUnit::Line,
x: self.x / *conversion_ratio,
y: self.y / *conversion_ratio,
hit: self.hit.clone(),
phase: self.phase,
}
} else {
self.clone()
}
}
pub fn to_pixels(&self, conversion_ratio: &MouseScrollPixelsPerLine) -> Self {
if self.unit == MouseScrollUnit::Line {
PointerScroll {
entity: self.entity,
pointer: self.pointer.clone(),
unit: MouseScrollUnit::Pixel,
x: self.x * *conversion_ratio,
y: self.y * *conversion_ratio,
hit: self.hit.clone(),
phase: self.phase,
}
} else {
self.clone()
}
}
}
#[derive(Clone, PartialEq, Debug, Reflect)]
#[reflect(Clone, PartialEq)]
pub struct DragEntry {
pub start_pos: Vec2,
pub latest_pos: Vec2,
}
#[derive(Debug, Clone, Default, Reflect)]
#[reflect(Debug, Clone, Default)]
pub struct PointerButtonState {
pub pressing: EntityHashMap<(Location, Instant, HitData)>,
pub clicking: EntityHashMap<(Instant, u8)>,
pub dragging: EntityHashMap<DragEntry>,
pub dragging_over: EntityHashMap<HitData>,
}
impl PointerButtonState {
pub fn clear(&mut self) {
self.pressing.clear();
self.dragging.clear();
self.dragging_over.clear();
}
}
#[derive(Debug, Clone, Default, Deref, DerefMut, Reflect)]
#[reflect(Debug, Clone, Default)]
pub struct HoveredEntityAncestors(EntityHashMap<EntityHashSet>);
impl HoveredEntityAncestors {
pub fn rebuild(
&mut self,
hover_map: &HoverMap,
pointer_state: &PointerState,
ancestors_query: &Query<&ChildOf>,
) {
self.clear();
for hovered_entity in hover_map
.iter()
.flat_map(|(_, hashmap)| hashmap.iter().map(|data| *data.0))
{
if self.contains_key(&hovered_entity) {
continue;
}
if let Some(previous_entry) =
pointer_state.hovered_entity_ancestors.get(&hovered_entity)
{
self.insert(hovered_entity, previous_entry.clone());
} else {
let mut ancestors = EntityHashSet::new();
for member in ancestors_query.iter_ancestors(hovered_entity) {
ancestors.insert(member);
}
self.insert(hovered_entity, ancestors);
}
}
}
pub fn get_ancestors_union(&self, hover_entities: &EntityHashSet) -> EntityHashSet {
hover_entities
.iter()
.flat_map(|entity| self.get(entity))
.flat_map(|set| set.iter().copied())
.collect::<EntityHashSet>()
}
pub fn get_ancestors(&self, hover_entity: &Entity) -> Option<&EntityHashSet> {
self.get(hover_entity)
}
}
#[derive(Debug, Clone, Default, Resource, Reflect)]
#[reflect(Debug, Clone, Default, Resource)]
pub struct PointerState {
pub pointer_buttons: HashMap<(PointerId, PointerButton), PointerButtonState>,
pub hovered_entity_ancestors: HoveredEntityAncestors,
}
impl PointerState {
pub fn get(&self, pointer_id: PointerId, button: PointerButton) -> Option<&PointerButtonState> {
self.pointer_buttons.get(&(pointer_id, button))
}
pub fn get_mut(
&mut self,
pointer_id: PointerId,
button: PointerButton,
) -> &mut PointerButtonState {
self.pointer_buttons
.entry((pointer_id, button))
.or_default()
}
pub fn get_ancestors(&self, hovered_entity: &Entity) -> Option<&EntityHashSet> {
self.hovered_entity_ancestors.get_ancestors(hovered_entity)
}
pub fn get_ancestors_union(&self, hovered_entities: &EntityHashSet) -> EntityHashSet {
self.hovered_entity_ancestors
.get_ancestors_union(hovered_entities)
}
pub fn clear(&mut self, pointer_id: PointerId) {
for button in PointerButton::iter() {
if let Some(state) = self.pointer_buttons.get_mut(&(pointer_id, button)) {
state.clear();
}
}
}
}
#[derive(SystemParam)]
pub struct PickingMessageWriters<'w> {
cancel_events: MessageWriter<'w, PointerCancel>,
click_events: MessageWriter<'w, PointerClick>,
pressed_events: MessageWriter<'w, PointerPress>,
drag_drop_events: MessageWriter<'w, PointerDragDrop>,
drag_end_events: MessageWriter<'w, PointerDragEnd>,
drag_enter_events: MessageWriter<'w, PointerDragEnter>,
drag_events: MessageWriter<'w, PointerDrag>,
drag_leave_events: MessageWriter<'w, PointerDragLeave>,
drag_over_events: MessageWriter<'w, PointerDragOver>,
drag_start_events: MessageWriter<'w, PointerDragStart>,
scroll_events: MessageWriter<'w, PointerScroll>,
move_events: MessageWriter<'w, PointerMove>,
out_events: MessageWriter<'w, PointerOut>,
over_events: MessageWriter<'w, PointerOver>,
leave_events: MessageWriter<'w, PointerLeave>,
enter_events: MessageWriter<'w, PointerEnter>,
released_events: MessageWriter<'w, PointerRelease>,
}
pub fn pointer_events(
mut input_events: MessageReader<PointerInput>,
pointers: Query<&PointerLocation>,
ancestors_query: Query<&ChildOf>,
pointer_map: Res<PointerMap>,
hover_map: Res<HoverMap>,
previous_hover_map: Res<PreviousHoverMap>,
picking_settings: Res<PickingSettings>,
mut pointer_state: ResMut<PointerState>,
mut hovered_entity_ancestors: Local<HoveredEntityAncestors>,
mut sent_leave: Local<HashSet<(PointerId, Entity)>>,
mut sent_enter: Local<HashSet<(PointerId, Entity)>>,
mut commands: Commands,
mut message_writers: PickingMessageWriters,
) {
let now = Instant::now();
let pointer_location = |pointer_id: PointerId| {
pointer_map
.get_entity(pointer_id)
.and_then(|entity| pointers.get(entity).ok())
.and_then(|pointer| pointer.location.clone())
};
hovered_entity_ancestors.rebuild(&hover_map, &pointer_state, &ancestors_query);
sent_leave.clear();
sent_enter.clear();
for (pointer_id, hovered_entity, hit) in previous_hover_map
.iter()
.flat_map(|(id, hashmap)| hashmap.iter().map(|data| (*id, *data.0, data.1.clone())))
{
if !hover_map
.get(&pointer_id)
.iter()
.any(|e| e.contains_key(&hovered_entity))
{
let Some(location) = pointer_location(pointer_id) else {
debug!(
"Unable to get location for pointer {:?} during pointer out",
pointer_id
);
continue;
};
let pointer = Pointer::new(pointer_id, location);
let out_event = PointerOut {
pointer: pointer.clone(),
entity: hovered_entity,
hit: hit.clone(),
};
commands.trigger(out_event.clone());
message_writers.out_events.write(out_event);
let mut entities_to_send_leave =
pointer_state.get_ancestors(&hovered_entity).map_or_else(
|| {
ancestors_query
.iter_ancestors(hovered_entity)
.collect::<EntityHashSet>()
},
Clone::clone,
);
entities_to_send_leave.insert(hovered_entity);
entities_to_send_leave.retain(|entity| !sent_leave.contains(&(pointer_id, *entity)));
if !entities_to_send_leave.is_empty() {
let new_hovered_entities = get_hovered_entities(&hover_map, &pointer_id);
let new_hovered_ancestors =
hovered_entity_ancestors.get_ancestors_union(&new_hovered_entities);
let union = new_hovered_entities
.union(&new_hovered_ancestors)
.copied()
.collect::<EntityHashSet>();
entities_to_send_leave.retain(|entity| !union.contains(entity));
for leave_event in entities_to_send_leave.iter().map(|entity| PointerLeave {
pointer: pointer.without_propagate(),
hit: hit.clone(),
was_in_bounds: is_directly_hovered(&previous_hover_map.0, &pointer_id, entity),
entity: *entity,
}) {
let entity = leave_event.entity;
commands.trigger(leave_event.clone());
message_writers.leave_events.write(leave_event);
sent_leave.insert((pointer_id, entity));
}
}
for button in PointerButton::iter() {
let state = pointer_state.get_mut(pointer_id, button);
state.dragging_over.remove(&hovered_entity);
for drag_target in state.dragging.keys() {
let drag_leave_event = PointerDragLeave {
pointer: pointer.clone(),
entity: hovered_entity,
button,
dragged: *drag_target,
hit: hit.clone(),
};
commands.trigger(drag_leave_event.clone());
message_writers.drag_leave_events.write(drag_leave_event);
}
}
}
}
for (pointer_id, hovered_entity, hit) in hover_map
.iter()
.flat_map(|(id, hashmap)| hashmap.iter().map(|data| (*id, *data.0, data.1.clone())))
{
let Some(location) = pointer_location(pointer_id) else {
debug!(
"Unable to get location for pointer {:?} during pointer over",
pointer_id
);
continue;
};
let pointer = Pointer::new(pointer_id, location);
for button in PointerButton::iter() {
let state = pointer_state.get_mut(pointer_id, button);
if !state.dragging.is_empty()
&& state
.dragging_over
.insert(hovered_entity, hit.clone())
.is_none()
{
for drag_target in state.dragging.keys() {
let drag_enter_event = PointerDragEnter {
pointer: pointer.clone(),
entity: hovered_entity,
button,
dragged: *drag_target,
hit: hit.clone(),
};
commands.trigger(drag_enter_event.clone());
message_writers.drag_enter_events.write(drag_enter_event);
}
}
}
if !previous_hover_map
.get(&pointer_id)
.iter()
.any(|e| e.contains_key(&hovered_entity))
{
let mut entities_to_send_enter = hovered_entity_ancestors
.get_ancestors(&hovered_entity)
.map_or_else(
|| {
ancestors_query
.iter_ancestors(hovered_entity)
.collect::<EntityHashSet>()
},
Clone::clone,
);
entities_to_send_enter.insert(hovered_entity);
entities_to_send_enter
.retain(|entity: &Entity| !sent_enter.contains(&(pointer_id, *entity)));
if !entities_to_send_enter.is_empty() {
let prev_hovered_entities = get_hovered_entities(&previous_hover_map, &pointer_id);
let prev_hovered_ancestors =
pointer_state.get_ancestors_union(&prev_hovered_entities);
let union = prev_hovered_entities
.union(&prev_hovered_ancestors)
.copied()
.collect::<EntityHashSet>();
entities_to_send_enter.retain(|entity| !union.contains(entity));
for enter_event in entities_to_send_enter.iter().map(|entity| PointerEnter {
pointer: pointer.clone(),
entity: *entity,
hit: hit.clone(),
is_in_bounds: is_directly_hovered(&hover_map.0, &pointer_id, entity),
}) {
let entity = enter_event.entity;
commands.trigger(enter_event.clone());
message_writers.enter_events.write(enter_event);
sent_enter.insert((pointer_id, entity));
}
}
let over_event = PointerOver {
pointer: pointer.clone(),
entity: hovered_entity,
hit: hit.clone(),
};
commands.trigger(over_event.clone());
message_writers.over_events.write(over_event);
}
}
core::mem::swap(
&mut hovered_entity_ancestors.0,
&mut pointer_state.hovered_entity_ancestors,
);
for PointerInput {
pointer_id,
location,
action,
} in input_events.read().cloned()
{
let pointer = Pointer::new(pointer_id, location.clone());
match action {
PointerAction::Press(button) => {
let state = pointer_state.get_mut(pointer_id, button);
state.clicking.retain(|_, (last_click, _)| {
now - *last_click <= picking_settings.multi_click_interval
});
for (hovered_entity, hit) in hover_map
.get(&pointer_id)
.iter()
.flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.clone())))
{
let count = state
.clicking
.get(&hovered_entity)
.map_or(1, |(_, count)| count.saturating_add(1));
state.clicking.insert(hovered_entity, (now, count));
let pressed_event = PointerPress {
pointer: pointer.clone(),
entity: hovered_entity,
button,
hit: hit.clone(),
count,
};
commands.trigger(pressed_event.clone());
message_writers.pressed_events.write(pressed_event);
state
.pressing
.insert(hovered_entity, (location.clone(), now, hit));
}
}
PointerAction::Release(button) => {
let state = pointer_state.get_mut(pointer_id, button);
state.clicking.retain(|_, (last_click, _)| {
now - *last_click <= picking_settings.multi_click_interval
});
for (hovered_entity, hit) in previous_hover_map
.get(&pointer_id)
.iter()
.flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.clone())))
{
if let Some((_, press_instant, _)) = state.pressing.get(&hovered_entity) {
let count = state
.clicking
.get(&hovered_entity)
.map_or(1, |(_, count)| *count);
state.clicking.insert(hovered_entity, (now, count));
let click_event = PointerClick {
pointer: pointer.clone(),
entity: hovered_entity,
button,
hit: hit.clone(),
duration: now - *press_instant,
count,
};
commands.trigger(click_event.clone());
message_writers.click_events.write(click_event);
}
let released_event = PointerRelease {
pointer: pointer.clone(),
button,
hit: hit.clone(),
entity: hovered_entity,
};
commands.trigger(released_event.clone());
message_writers.released_events.write(released_event);
}
for (drag_target, drag) in state.dragging.drain() {
for (dragged_over, hit) in state.dragging_over.iter() {
let drag_drop_event = PointerDragDrop {
pointer: pointer.clone(),
entity: *dragged_over,
button,
dropped: drag_target,
hit: hit.clone(),
};
commands.trigger(drag_drop_event.clone());
message_writers.drag_drop_events.write(drag_drop_event);
}
let drag_end_event = PointerDragEnd {
pointer: pointer.clone(),
entity: drag_target,
button,
distance: drag.latest_pos - drag.start_pos,
};
commands.trigger(drag_end_event.clone());
message_writers.drag_end_events.write(drag_end_event);
for (dragged_over, hit) in state.dragging_over.iter() {
let drag_leave_event = PointerDragLeave {
pointer: pointer.clone(),
entity: *dragged_over,
button,
dragged: drag_target,
hit: hit.clone(),
};
commands.trigger(drag_leave_event.clone());
message_writers.drag_leave_events.write(drag_leave_event);
}
}
state.clear();
}
PointerAction::Move { delta } => {
if delta == Vec2::ZERO {
continue; }
for button in PointerButton::iter() {
let state = pointer_state.get_mut(pointer_id, button);
for (press_target, (location, _, hit)) in state.pressing.iter() {
if state.dragging.contains_key(press_target) {
continue; }
state.dragging.insert(
*press_target,
DragEntry {
start_pos: location.position,
latest_pos: location.position,
},
);
let drag_start_event = PointerDragStart {
pointer: pointer.clone(),
entity: *press_target,
button,
hit: hit.clone(),
};
commands.trigger(drag_start_event.clone());
message_writers.drag_start_events.write(drag_start_event);
for (hovered_entity, hit) in hover_map
.get(&pointer_id)
.iter()
.flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.to_owned())))
.filter(|(hovered_entity, _)| *hovered_entity != *press_target)
{
state.dragging_over.insert(hovered_entity, hit.clone());
let drag_enter_event = PointerDragEnter {
pointer: pointer.clone(),
entity: hovered_entity,
button,
dragged: *press_target,
hit: hit.clone(),
};
commands.trigger(drag_enter_event.clone());
message_writers.drag_enter_events.write(drag_enter_event);
}
}
for (drag_target, drag) in state.dragging.iter_mut() {
let delta = location.position - drag.latest_pos;
if delta == Vec2::ZERO {
continue; }
let drag_event = PointerDrag {
pointer: pointer.clone(),
entity: *drag_target,
button,
distance: location.position - drag.start_pos,
delta,
};
commands.trigger(drag_event.clone());
message_writers.drag_events.write(drag_event);
drag.latest_pos = location.position;
for (hovered_entity, hit) in hover_map
.get(&pointer_id)
.iter()
.flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.to_owned())))
.filter(|(hovered_entity, _)| *hovered_entity != *drag_target)
{
let drag_over_event = PointerDragOver {
pointer: pointer.clone(),
entity: hovered_entity,
button,
dragged: *drag_target,
hit: hit.clone(),
};
commands.trigger(drag_over_event.clone());
message_writers.drag_over_events.write(drag_over_event);
}
}
}
for (hovered_entity, hit) in hover_map
.get(&pointer_id)
.iter()
.flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.to_owned())))
{
let move_event = PointerMove {
pointer: pointer.clone(),
entity: hovered_entity,
hit: hit.clone(),
delta,
};
commands.trigger(move_event.clone());
message_writers.move_events.write(move_event);
}
}
PointerAction::Scroll { x, y, unit, phase } => {
for (hovered_entity, hit) in hover_map
.get(&pointer_id)
.iter()
.flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.clone())))
{
let scroll_event = PointerScroll {
pointer: pointer.clone(),
entity: hovered_entity,
unit,
x,
y,
hit: hit.clone(),
phase,
};
commands.trigger(scroll_event.clone());
message_writers.scroll_events.write(scroll_event);
}
}
PointerAction::Cancel => {
for (hovered_entity, hit) in hover_map
.get(&pointer_id)
.iter()
.flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.to_owned())))
{
let cancel_event = PointerCancel {
pointer: pointer.clone(),
entity: hovered_entity,
hit,
};
commands.trigger(cancel_event.clone());
message_writers.cancel_events.write(cancel_event);
}
pointer_state.clear(pointer_id);
}
}
}
}
#[cfg(test)]
mod tests {
use bevy_app::App;
use bevy_camera::{Camera, ManualTextureViewHandle};
use super::*;
const POINTER_ID: PointerId = PointerId::Mouse;
const STUB_LOCATION: Location = Location {
target: NormalizedRenderTarget::TextureView(ManualTextureViewHandle(5)),
position: Vec2::new(3., 4.),
};
fn initialize_app_for_test(app: &mut App) {
app.init_resource::<HoverMap>()
.init_resource::<PreviousHoverMap>()
.init_resource::<PickingSettings>()
.init_resource::<PointerState>()
.init_resource::<PointerMap>()
.add_message::<PointerInput>()
.add_message::<PointerCancel>()
.add_message::<PointerClick>()
.add_message::<PointerPress>()
.add_message::<PointerDragDrop>()
.add_message::<PointerDragEnd>()
.add_message::<PointerDragEnter>()
.add_message::<PointerDrag>()
.add_message::<PointerDragLeave>()
.add_message::<PointerDragOver>()
.add_message::<PointerDragStart>()
.add_message::<PointerScroll>()
.add_message::<PointerMove>()
.add_message::<PointerOut>()
.add_message::<PointerOver>()
.add_message::<PointerLeave>()
.add_message::<PointerEnter>()
.add_message::<PointerRelease>();
app.world_mut()
.spawn((POINTER_ID, PointerLocation::new(STUB_LOCATION)));
}
fn update_hover_map_with_hovered_entities(app: &mut App, camera: Entity, entities: &[Entity]) {
let mut hover_map = HoverMap::default();
let mut entity_map = EntityHashMap::with_capacity(entities.len());
for entity in entities {
entity_map.insert(
*entity,
HitData {
depth: 0.0,
camera,
position: None,
normal: None,
extra: None,
},
);
}
hover_map.insert(PointerId::Mouse, entity_map);
let previous_hover_map = app.world().resource::<HoverMap>().0.clone();
app.world_mut()
.insert_resource(PreviousHoverMap(previous_hover_map));
app.world_mut().insert_resource(hover_map);
}
#[test]
fn enter_leave_events() {
#[derive(Resource, Default)]
struct EnterEventCounts(HashMap<(Entity, bool), usize>);
#[derive(Resource, Default)]
struct LeaveEventCounts(HashMap<(Entity, bool), usize>);
fn observe_enter(event: On<PointerEnter>, mut counts: ResMut<EnterEventCounts>) {
*counts
.0
.entry((event.entity, event.event().is_in_bounds))
.or_insert(0_usize) += 1;
}
fn observe_leave(event: On<PointerLeave>, mut counts: ResMut<LeaveEventCounts>) {
*counts
.0
.entry((event.entity, event.event().was_in_bounds))
.or_insert(0_usize) += 1;
}
fn assert_msg_event_counts(app: &App, enter_count: usize, leave_count: usize) {
let enter_messages = app.world().resource::<Messages<PointerEnter>>();
let leave_messages = app.world().resource::<Messages<PointerLeave>>();
assert_eq!(enter_messages.len(), enter_count);
assert_eq!(leave_messages.len(), leave_count);
}
fn assert_observer_event_counts(
app: &App,
entity: Entity,
enter_in_bounds_counts: usize,
enter_out_of_bounds_counts: usize,
leave_in_bounds_counts: usize,
leave_out_of_bounds_counts: usize,
) {
assert_eq!(
*app.world()
.resource::<EnterEventCounts>()
.0
.get(&(entity, true))
.unwrap_or(&0),
enter_in_bounds_counts
);
assert_eq!(
*app.world()
.resource::<EnterEventCounts>()
.0
.get(&(entity, false))
.unwrap_or(&0),
enter_out_of_bounds_counts
);
assert_eq!(
*app.world()
.resource::<LeaveEventCounts>()
.0
.get(&(entity, true))
.unwrap_or(&0),
leave_in_bounds_counts
);
assert_eq!(
*app.world()
.resource::<LeaveEventCounts>()
.0
.get(&(entity, false))
.unwrap_or(&0),
leave_out_of_bounds_counts
);
}
let mut app = App::new();
initialize_app_for_test(&mut app);
app.init_resource::<EnterEventCounts>()
.init_resource::<LeaveEventCounts>();
let enter_messages = app.world().resource::<Messages<PointerEnter>>();
let leave_messages = app.world().resource::<Messages<PointerLeave>>();
assert_eq!(enter_messages.len(), 0);
assert_eq!(leave_messages.len(), 0);
let camera = app.world_mut().spawn(Camera::default()).id();
let child_one = app
.world_mut()
.spawn_empty()
.observe(observe_enter)
.observe(observe_leave)
.id();
let child_two = app
.world_mut()
.spawn_empty()
.observe(observe_enter)
.observe(observe_leave)
.id();
let parent = app
.world_mut()
.spawn_empty()
.add_children(&[child_one, child_two])
.observe(observe_enter)
.observe(observe_leave)
.id();
update_hover_map_with_hovered_entities(&mut app, camera, &[child_one]);
assert!(app.world_mut().run_system_cached(pointer_events).is_ok());
assert_msg_event_counts(&app, 2, 0);
assert_observer_event_counts(&app, parent, 0, 1, 0, 0);
assert_observer_event_counts(&app, child_one, 1, 0, 0, 0);
assert_observer_event_counts(&app, child_two, 0, 0, 0, 0);
app.world_mut().increment_change_tick();
update_hover_map_with_hovered_entities(&mut app, camera, &[child_two, parent]);
assert!(app.world_mut().run_system_cached(pointer_events).is_ok());
assert_msg_event_counts(&app, 3, 1);
assert_observer_event_counts(&app, parent, 0, 1, 0, 0);
assert_observer_event_counts(&app, child_one, 1, 0, 1, 0);
assert_observer_event_counts(&app, child_two, 1, 0, 0, 0);
app.world_mut().increment_change_tick();
update_hover_map_with_hovered_entities(&mut app, camera, &[parent]);
assert!(app.world_mut().run_system_cached(pointer_events).is_ok());
assert_msg_event_counts(&app, 3, 2);
assert_observer_event_counts(&app, parent, 0, 1, 0, 0);
assert_observer_event_counts(&app, child_one, 1, 0, 1, 0);
assert_observer_event_counts(&app, child_two, 1, 0, 1, 0);
app.world_mut().increment_change_tick();
update_hover_map_with_hovered_entities(&mut app, camera, &[child_two]);
assert!(app.world_mut().run_system_cached(pointer_events).is_ok());
assert_msg_event_counts(&app, 4, 2);
assert_observer_event_counts(&app, parent, 0, 1, 0, 0);
assert_observer_event_counts(&app, child_one, 1, 0, 1, 0);
assert_observer_event_counts(&app, child_two, 2, 0, 1, 0);
app.world_mut().increment_change_tick();
update_hover_map_with_hovered_entities(&mut app, camera, &[]);
assert!(app.world_mut().run_system_cached(pointer_events).is_ok());
assert_msg_event_counts(&app, 4, 4);
assert_observer_event_counts(&app, parent, 0, 1, 0, 1);
assert_observer_event_counts(&app, child_one, 1, 0, 1, 0);
assert_observer_event_counts(&app, child_two, 2, 0, 2, 0);
app.world_mut().increment_change_tick();
update_hover_map_with_hovered_entities(&mut app, camera, &[parent, child_one]);
assert!(app.world_mut().run_system_cached(pointer_events).is_ok());
assert_msg_event_counts(&app, 6, 4);
assert_observer_event_counts(&app, parent, 1, 1, 0, 1);
assert_observer_event_counts(&app, child_one, 2, 0, 1, 0);
assert_observer_event_counts(&app, child_two, 2, 0, 2, 0);
app.world_mut().increment_change_tick();
}
}