use bevy::prelude::*;
#[derive(Debug, Clone, Copy, Default)]
pub struct PfPointerInfo {
pub position: Vec2,
pub delta: Vec2,
}
type Handler = dyn Fn(&mut World, Entity, PfPointerInfo) + Send + Sync;
#[derive(Resource, Default, Clone)]
pub struct PfEventHandlers(
pub bevy::platform::collections::HashMap<String, std::sync::Arc<Handler>>,
);
pub trait PfEventAppExt {
fn on_ui_event(
&mut self,
name: impl Into<String>,
f: impl Fn(&mut World, Entity, PfPointerInfo) + Send + Sync + 'static,
) -> &mut Self;
}
impl PfEventAppExt for bevy::app::App {
fn on_ui_event(
&mut self,
name: impl Into<String>,
f: impl Fn(&mut World, Entity, PfPointerInfo) + Send + Sync + 'static,
) -> &mut Self {
let world = self.world_mut();
if !world.contains_resource::<PfEventHandlers>() {
world.init_resource::<PfEventHandlers>();
}
world
.resource_mut::<PfEventHandlers>()
.0
.insert(name.into(), std::sync::Arc::new(f));
self
}
}
pub fn dispatch(world: &mut World, name: &str, entity: Entity, info: PfPointerInfo) {
let handler = world
.get_resource::<PfEventHandlers>()
.and_then(|h| h.0.get(name).cloned());
if let Some(handler) = handler {
handler(world, entity, info);
}
}
pub(crate) fn attach_event_handler(
world: &mut World,
entity: Entity,
event: &str,
handler: String,
) {
use bevy::picking::events::{
Click, Drag, DragDrop, DragEnd, DragStart, Move, Out, Over, Pointer, Press, Release,
};
macro_rules! wire {
(@delta $e:ident, zero) => { Vec2::ZERO };
(@delta $e:ident, delta) => { $e.delta };
($ev:ty, $delta:ident) => {{
world.entity_mut(entity).observe(
move |ev: On<Pointer<$ev>>, mut commands: Commands| {
let info = PfPointerInfo {
position: ev.pointer_location.position,
delta: wire!(@delta ev, $delta),
};
let handler = handler.clone();
commands.queue(move |world: &mut World| {
dispatch(world, &handler, entity, info);
});
},
);
}};
}
match event {
"Click" | "MouseDoubleClick" => wire!(Click, zero),
"MouseEnter" => wire!(Over, zero),
"MouseLeave" => wire!(Out, zero),
"MouseDown" => wire!(Press, zero),
"MouseUp" => wire!(Release, zero),
"MouseMove" => wire!(Move, delta),
"DragStart" => wire!(DragStart, zero),
"Drag" => wire!(Drag, delta),
"DragEnd" => wire!(DragEnd, zero),
"Drop" => wire!(DragDrop, zero),
_ => {}
}
}
pub fn hit_test(world: &mut World, position: Vec2) -> Vec<Entity> {
let mut query = world.query::<(
Entity,
&bevy::ui::ComputedNode,
&bevy::ui::UiGlobalTransform,
)>();
let mut hits = Vec::new();
for (entity, node, transform) in query.iter(world) {
let scale = node.inverse_scale_factor();
let size = node.size() * scale;
let center: Vec2 = transform.translation * scale;
let half = size / 2.0;
if (position.x - center.x).abs() <= half.x && (position.y - center.y).abs() <= half.y {
hits.push(entity);
}
}
hits
}