use bevy_ecs::hierarchy::ChildOf;
use bevy_ecs::prelude::*;
use super::PointerState;
#[derive(Clone, Debug)]
pub enum Phase<T> {
Tunnel(T),
Bubble(T),
}
impl<T> Phase<T> {
#[inline]
pub fn value(&self) -> &T {
match self {
Phase::Tunnel(v) | Phase::Bubble(v) => v,
}
}
#[inline]
pub fn is_tunnel(&self) -> bool {
matches!(self, Phase::Tunnel(_))
}
#[inline]
pub fn is_bubble(&self) -> bool {
matches!(self, Phase::Bubble(_))
}
}
pub type EventHandler<T> =
fn(world: &mut World, sender: Entity, entity: Entity, ev: &Phase<T>) -> bool;
pub type PointerEventHandler = EventHandler<PointerState>;
#[derive(Component, Clone, Copy)]
#[component(storage = "SparseSet")]
pub struct OnPointerPressed(pub PointerEventHandler);
#[derive(Component, Clone, Copy)]
#[component(storage = "SparseSet")]
pub struct OnPointerReleased(pub PointerEventHandler);
#[derive(Component, Clone, Copy)]
#[component(storage = "SparseSet")]
pub struct OnPointerEntered(pub PointerEventHandler);
#[derive(Component, Clone, Copy)]
#[component(storage = "SparseSet")]
pub struct OnPointerExited(pub PointerEventHandler);
#[derive(Component, Clone, Copy)]
#[component(storage = "SparseSet")]
pub struct OnPointerMoved(pub PointerEventHandler);
pub fn build_bubble_path(world: &World, start: Entity) -> Vec<Entity> {
use crate::ecs::window::Window;
let mut path = vec![start];
if world.get::<Window>(start).is_some() {
return path;
}
let mut current = start;
while let Some(child_of) = world.get::<ChildOf>(current) {
let parent = child_of.parent();
path.push(parent);
if world.get::<Window>(parent).is_some() {
break;
}
current = parent;
}
path
}
pub fn dispatch_event_for_handler<T: Clone, H: Component + Copy>(
world: &mut World,
sender: Entity,
path: &[Entity],
event: &T,
get_handler: fn(&H) -> EventHandler<T>,
) {
for &entity in path.iter().rev() {
if world.get_entity(entity).is_err() {
return; }
if let Some(handler_comp) = world.get::<H>(entity).copied() {
let handler = get_handler(&handler_comp);
if handler(world, sender, entity, &Phase::Tunnel(event.clone())) {
return; }
}
}
for &entity in path.iter() {
if world.get_entity(entity).is_err() {
return; }
if let Some(handler_comp) = world.get::<H>(entity).copied() {
let handler = get_handler(&handler_comp);
if handler(world, sender, entity, &Phase::Bubble(event.clone())) {
return; }
}
}
}
pub fn dispatch_pointer_events(world: &mut World) {
let targets: Vec<(Entity, PointerState)> = {
let mut query = world.query::<(Entity, &PointerState)>();
query.iter(world).map(|(e, s)| (e, s.clone())).collect()
};
for (sender, state) in &targets {
let path = build_bubble_path(world, *sender);
dispatch_event_for_handler::<PointerState, OnPointerMoved>(
world,
*sender,
&path,
state,
|h| h.0,
);
if state.left_down || state.right_down || state.middle_down {
dispatch_event_for_handler::<PointerState, OnPointerPressed>(
world,
*sender,
&path,
state,
|h| h.0,
);
}
}
for (entity, _) in &targets {
if let Some(mut pointer_state) = world.get_mut::<PointerState>(*entity) {
pointer_state.left_down = false;
pointer_state.right_down = false;
pointer_state.middle_down = false;
pointer_state.xbutton1_down = false;
pointer_state.xbutton2_down = false;
pointer_state.double_click = super::DoubleClick::None;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_phase_tunnel() {
let phase = Phase::Tunnel(42);
assert!(phase.is_tunnel());
assert!(!phase.is_bubble());
assert_eq!(*phase.value(), 42);
}
#[test]
fn test_phase_bubble() {
let phase = Phase::Bubble("test");
assert!(!phase.is_tunnel());
assert!(phase.is_bubble());
assert_eq!(*phase.value(), "test");
}
#[test]
fn test_phase_clone() {
let phase = Phase::Tunnel(100);
let cloned = phase.clone();
assert_eq!(*cloned.value(), 100);
}
#[test]
fn test_handler_component_size() {
use std::mem::size_of;
assert_eq!(
size_of::<OnPointerPressed>(),
size_of::<PointerEventHandler>()
);
assert_eq!(
size_of::<OnPointerMoved>(),
size_of::<PointerEventHandler>()
);
}
#[test]
fn test_build_bubble_path_single_entity() {
let mut world = World::new();
let entity = world.spawn_empty().id();
let path = build_bubble_path(&world, entity);
assert_eq!(path.len(), 1);
assert_eq!(path[0], entity);
}
#[test]
fn test_build_bubble_path_hierarchy() {
let mut world = World::new();
let root = world.spawn_empty().id();
let child = world.spawn(ChildOf(root)).id();
let grandchild = world.spawn(ChildOf(child)).id();
let path = build_bubble_path(&world, grandchild);
assert_eq!(path.len(), 3);
assert_eq!(path[0], grandchild);
assert_eq!(path[1], child);
assert_eq!(path[2], root);
}
#[test]
fn test_dispatch_with_no_handlers() {
let mut world = World::new();
let entity = world.spawn(PointerState::default()).id();
dispatch_pointer_events(&mut world);
assert!(world.get_entity(entity).is_ok());
}
#[test]
fn test_dispatch_with_handler() {
use std::sync::atomic::{AtomicU32, Ordering};
static CALL_COUNT: AtomicU32 = AtomicU32::new(0);
fn test_handler(
_world: &mut World,
_sender: Entity,
_entity: Entity,
ev: &Phase<PointerState>,
) -> bool {
if ev.is_bubble() {
CALL_COUNT.fetch_add(1, Ordering::SeqCst);
}
false }
CALL_COUNT.store(0, Ordering::SeqCst);
let mut world = World::new();
let entity = world
.spawn((PointerState::default(), OnPointerMoved(test_handler)))
.id();
dispatch_pointer_events(&mut world);
assert_eq!(CALL_COUNT.load(Ordering::SeqCst), 1);
assert!(world.get_entity(entity).is_ok());
}
#[test]
fn test_dispatch_stop_propagation() {
use std::sync::atomic::{AtomicU32, Ordering};
static TUNNEL_COUNT: AtomicU32 = AtomicU32::new(0);
static BUBBLE_COUNT: AtomicU32 = AtomicU32::new(0);
fn stopping_handler(
_world: &mut World,
_sender: Entity,
_entity: Entity,
ev: &Phase<PointerState>,
) -> bool {
if ev.is_tunnel() {
TUNNEL_COUNT.fetch_add(1, Ordering::SeqCst);
false } else {
BUBBLE_COUNT.fetch_add(1, Ordering::SeqCst);
true }
}
fn never_called_in_bubble_handler(
_world: &mut World,
_sender: Entity,
_entity: Entity,
ev: &Phase<PointerState>,
) -> bool {
if ev.is_bubble() {
BUBBLE_COUNT.fetch_add(100, Ordering::SeqCst); }
false
}
TUNNEL_COUNT.store(0, Ordering::SeqCst);
BUBBLE_COUNT.store(0, Ordering::SeqCst);
let mut world = World::new();
let root = world
.spawn(OnPointerMoved(never_called_in_bubble_handler))
.id();
let child = world
.spawn((
ChildOf(root),
PointerState::default(),
OnPointerMoved(stopping_handler),
))
.id();
dispatch_pointer_events(&mut world);
assert_eq!(TUNNEL_COUNT.load(Ordering::SeqCst), 1);
assert_eq!(BUBBLE_COUNT.load(Ordering::SeqCst), 1);
assert!(world.get_entity(child).is_ok());
}
}