mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
Mod picking upstream 2 (#14686)
Ci fixed version of: #14541 Upstream the remainder of bevy_picking_core and all of bevy_picking_input. This work is intentionally nonfunctional and has minimal changes, but does compile. More work is necessary to replace bevy_eventlistener with propagating observers. This work is being coordinated as part of "bevy_mod_picking upstream" working group. Come say hi on discord! --------- Co-authored-by: Miles Silberling-Cook <nth.tensor@gmail.com> Co-authored-by: Aevyrie <aevyrie@gmail.com>
This commit is contained in:
parent
e490b919df
commit
3e10fd8534
7 changed files with 1225 additions and 7 deletions
|
@ -9,7 +9,10 @@ license = "MIT OR Apache-2.0"
|
|||
|
||||
[dependencies]
|
||||
bevy_app = { path = "../bevy_app", version = "0.15.0-dev" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.15.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev" }
|
||||
bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.15.0-dev" }
|
||||
bevy_input = { path = "../bevy_input", version = "0.15.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.15.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.15.0-dev" }
|
||||
|
|
667
crates/bevy_picking/src/events.rs
Normal file
667
crates/bevy_picking/src/events.rs
Normal file
|
@ -0,0 +1,667 @@
|
|||
//! Processes data from input and backends, producing interaction events.
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_hierarchy::Parent;
|
||||
use bevy_math::Vec2;
|
||||
use bevy_reflect::prelude::*;
|
||||
use bevy_utils::{tracing::debug, Duration, HashMap, Instant};
|
||||
|
||||
use crate::{
|
||||
backend::{prelude::PointerLocation, HitData},
|
||||
focus::{HoverMap, PreviousHoverMap},
|
||||
pointer::{
|
||||
InputMove, InputPress, Location, PointerButton, PointerId, PointerMap, PressDirection,
|
||||
},
|
||||
};
|
||||
|
||||
/// Stores the common data needed for all `PointerEvent`s.
|
||||
#[derive(Clone, PartialEq, Debug, Reflect, Component)]
|
||||
pub struct Pointer<E: Debug + Clone + Reflect> {
|
||||
/// The target of this event
|
||||
pub target: Entity,
|
||||
/// The pointer that triggered this event
|
||||
pub pointer_id: PointerId,
|
||||
/// The location of the pointer during this event
|
||||
pub pointer_location: Location,
|
||||
/// Additional event-specific data. [`Drop`] for example, has an additional field to describe
|
||||
/// the `Entity` that is being dropped on the target.
|
||||
pub event: E,
|
||||
}
|
||||
|
||||
impl<E> Event for Pointer<E>
|
||||
where
|
||||
E: Debug + Clone + Reflect,
|
||||
{
|
||||
type Traversal = Parent;
|
||||
const AUTO_PROPAGATE: bool = true;
|
||||
}
|
||||
|
||||
impl<E: Debug + Clone + Reflect> std::fmt::Display for Pointer<E> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_fmt(format_args!(
|
||||
"{:?}, {:.1?}, {:?}, {:.1?}",
|
||||
self.pointer_id, self.pointer_location.position, self.target, self.event
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Debug + Clone + Reflect> std::ops::Deref for Pointer<E> {
|
||||
type Target = E;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.event
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Debug + Clone + Reflect> Pointer<E> {
|
||||
/// Construct a new `PointerEvent`.
|
||||
pub fn new(id: PointerId, location: Location, target: Entity, event: E) -> Self {
|
||||
Self {
|
||||
pointer_id: id,
|
||||
pointer_location: location,
|
||||
target,
|
||||
event,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fires when a pointer is no longer available.
|
||||
#[derive(Event, Clone, PartialEq, Debug, Reflect)]
|
||||
pub struct PointerCancel {
|
||||
/// ID of the pointer that was cancelled.
|
||||
#[reflect(ignore)]
|
||||
pub pointer_id: PointerId,
|
||||
}
|
||||
|
||||
/// Fires when a the pointer crosses into the bounds of the `target` entity.
|
||||
#[derive(Clone, PartialEq, Debug, Reflect)]
|
||||
pub struct Over {
|
||||
/// Information about the picking intersection.
|
||||
pub hit: HitData,
|
||||
}
|
||||
|
||||
/// Fires when a the pointer crosses out of the bounds of the `target` entity.
|
||||
#[derive(Clone, PartialEq, Debug, Reflect)]
|
||||
pub struct Out {
|
||||
/// Information about the latest prior picking intersection.
|
||||
pub hit: HitData,
|
||||
}
|
||||
|
||||
/// Fires when a pointer button is pressed over the `target` entity.
|
||||
#[derive(Clone, PartialEq, Debug, Reflect)]
|
||||
pub struct Down {
|
||||
/// Pointer button pressed to trigger this event.
|
||||
pub button: PointerButton,
|
||||
/// Information about the picking intersection.
|
||||
pub hit: HitData,
|
||||
}
|
||||
|
||||
/// Fires when a pointer button is released over the `target` entity.
|
||||
#[derive(Clone, PartialEq, Debug, Reflect)]
|
||||
pub struct Up {
|
||||
/// Pointer button lifted to trigger this event.
|
||||
pub button: PointerButton,
|
||||
/// Information about the picking intersection.
|
||||
pub hit: HitData,
|
||||
}
|
||||
|
||||
/// Fires when a pointer sends a pointer down event followed by a pointer up event, with the same
|
||||
/// `target` entity for both events.
|
||||
#[derive(Clone, PartialEq, Debug, Reflect)]
|
||||
pub struct Click {
|
||||
/// Pointer button pressed and lifted to trigger this event.
|
||||
pub button: PointerButton,
|
||||
/// Information about the picking intersection.
|
||||
pub hit: HitData,
|
||||
/// Duration between the pointer pressed and lifted for this click
|
||||
pub duration: Duration,
|
||||
}
|
||||
|
||||
/// Fires while a pointer is moving over the `target` entity.
|
||||
#[derive(Clone, PartialEq, Debug, Reflect)]
|
||||
pub struct Move {
|
||||
/// Information about the picking intersection.
|
||||
pub hit: HitData,
|
||||
/// The change in position since the last move event.
|
||||
pub delta: Vec2,
|
||||
}
|
||||
|
||||
/// Fires when the `target` entity receives a pointer down event followed by a pointer move event.
|
||||
#[derive(Clone, PartialEq, Debug, Reflect)]
|
||||
pub struct DragStart {
|
||||
/// Pointer button pressed and moved to trigger this event.
|
||||
pub button: PointerButton,
|
||||
/// Information about the picking intersection.
|
||||
pub hit: HitData,
|
||||
}
|
||||
|
||||
/// Fires while the `target` entity is being dragged.
|
||||
#[derive(Clone, PartialEq, Debug, Reflect)]
|
||||
pub struct Drag {
|
||||
/// Pointer button pressed and moved to trigger this event.
|
||||
pub button: PointerButton,
|
||||
/// The total distance vector of a drag, measured from drag start to the current position.
|
||||
pub distance: Vec2,
|
||||
/// The change in position since the last drag event.
|
||||
pub delta: Vec2,
|
||||
}
|
||||
|
||||
/// Fires when a pointer is dragging the `target` entity and a pointer up event is received.
|
||||
#[derive(Clone, PartialEq, Debug, Reflect)]
|
||||
pub struct DragEnd {
|
||||
/// Pointer button pressed, moved, and lifted to trigger this event.
|
||||
pub button: PointerButton,
|
||||
/// The vector of drag movement measured from start to final pointer position.
|
||||
pub distance: Vec2,
|
||||
}
|
||||
|
||||
/// Fires when a pointer dragging the `dragged` entity enters the `target` entity.
|
||||
#[derive(Clone, PartialEq, Debug, Reflect)]
|
||||
pub struct DragEnter {
|
||||
/// Pointer button pressed to enter drag.
|
||||
pub button: PointerButton,
|
||||
/// The entity that was being dragged when the pointer entered the `target` entity.
|
||||
pub dragged: Entity,
|
||||
/// Information about the picking intersection.
|
||||
pub hit: HitData,
|
||||
}
|
||||
|
||||
/// Fires while the `dragged` entity is being dragged over the `target` entity.
|
||||
#[derive(Clone, PartialEq, Debug, Reflect)]
|
||||
pub struct DragOver {
|
||||
/// Pointer button pressed while dragging over.
|
||||
pub button: PointerButton,
|
||||
/// The entity that was being dragged when the pointer was over the `target` entity.
|
||||
pub dragged: Entity,
|
||||
/// Information about the picking intersection.
|
||||
pub hit: HitData,
|
||||
}
|
||||
|
||||
/// Fires when a pointer dragging the `dragged` entity leaves the `target` entity.
|
||||
#[derive(Clone, PartialEq, Debug, Reflect)]
|
||||
pub struct DragLeave {
|
||||
/// Pointer button pressed while leaving drag.
|
||||
pub button: PointerButton,
|
||||
/// The entity that was being dragged when the pointer left the `target` entity.
|
||||
pub dragged: Entity,
|
||||
/// Information about the latest prior picking intersection.
|
||||
pub hit: HitData,
|
||||
}
|
||||
|
||||
/// Fires when a pointer drops the `dropped` entity onto the `target` entity.
|
||||
#[derive(Clone, PartialEq, Debug, Reflect)]
|
||||
pub struct Drop {
|
||||
/// Pointer button lifted to drop.
|
||||
pub button: PointerButton,
|
||||
/// The entity that was dropped onto the `target` entity.
|
||||
pub dropped: Entity,
|
||||
/// Information about the picking intersection.
|
||||
pub hit: HitData,
|
||||
}
|
||||
|
||||
/// Generates pointer events from input and focus data
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn pointer_events(
|
||||
// Input
|
||||
mut input_presses: EventReader<InputPress>,
|
||||
mut input_moves: EventReader<InputMove>,
|
||||
pointer_map: Res<PointerMap>,
|
||||
pointers: Query<&PointerLocation>,
|
||||
hover_map: Res<HoverMap>,
|
||||
previous_hover_map: Res<PreviousHoverMap>,
|
||||
// Output
|
||||
mut pointer_move: EventWriter<Pointer<Move>>,
|
||||
mut pointer_over: EventWriter<Pointer<Over>>,
|
||||
mut pointer_out: EventWriter<Pointer<Out>>,
|
||||
mut pointer_up: EventWriter<Pointer<Up>>,
|
||||
mut pointer_down: EventWriter<Pointer<Down>>,
|
||||
) {
|
||||
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())
|
||||
};
|
||||
|
||||
for InputMove {
|
||||
pointer_id,
|
||||
location,
|
||||
delta,
|
||||
} in input_moves.read().cloned()
|
||||
{
|
||||
for (hovered_entity, hit) in hover_map
|
||||
.get(&pointer_id)
|
||||
.iter()
|
||||
.flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.to_owned())))
|
||||
{
|
||||
pointer_move.send(Pointer::new(
|
||||
pointer_id,
|
||||
location.clone(),
|
||||
hovered_entity,
|
||||
Move { hit, delta },
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
for press_event in input_presses.read() {
|
||||
let button = press_event.button;
|
||||
// We use the previous hover map because we want to consider pointers that just left the
|
||||
// entity. Without this, touch inputs would never send up events because they are lifted up
|
||||
// and leave the bounds of the entity at the same time.
|
||||
for (hovered_entity, hit) in previous_hover_map
|
||||
.get(&press_event.pointer_id)
|
||||
.iter()
|
||||
.flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.clone())))
|
||||
{
|
||||
if let PressDirection::Up = press_event.direction {
|
||||
let Some(location) = pointer_location(press_event.pointer_id) else {
|
||||
debug!(
|
||||
"Unable to get location for pointer {:?} during event {:?}",
|
||||
press_event.pointer_id, press_event
|
||||
);
|
||||
continue;
|
||||
};
|
||||
pointer_up.send(Pointer::new(
|
||||
press_event.pointer_id,
|
||||
location,
|
||||
hovered_entity,
|
||||
Up { button, hit },
|
||||
));
|
||||
}
|
||||
}
|
||||
for (hovered_entity, hit) in hover_map
|
||||
.get(&press_event.pointer_id)
|
||||
.iter()
|
||||
.flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.clone())))
|
||||
{
|
||||
if let PressDirection::Down = press_event.direction {
|
||||
let Some(location) = pointer_location(press_event.pointer_id) else {
|
||||
debug!(
|
||||
"Unable to get location for pointer {:?} during event {:?}",
|
||||
press_event.pointer_id, press_event
|
||||
);
|
||||
continue;
|
||||
};
|
||||
pointer_down.send(Pointer::new(
|
||||
press_event.pointer_id,
|
||||
location,
|
||||
hovered_entity,
|
||||
Down { button, hit },
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the entity is hovered...
|
||||
for (pointer_id, hovered_entity, hit) in hover_map
|
||||
.iter()
|
||||
.flat_map(|(id, hashmap)| hashmap.iter().map(|data| (*id, *data.0, data.1.clone())))
|
||||
{
|
||||
// ...but was not hovered last frame...
|
||||
if !previous_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 over",
|
||||
pointer_id
|
||||
);
|
||||
continue;
|
||||
};
|
||||
pointer_over.send(Pointer::new(
|
||||
pointer_id,
|
||||
location,
|
||||
hovered_entity,
|
||||
Over { hit },
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// If the entity was hovered by a specific pointer last frame...
|
||||
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())))
|
||||
{
|
||||
// ...but is now not being hovered by that same pointer...
|
||||
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;
|
||||
};
|
||||
pointer_out.send(Pointer::new(
|
||||
pointer_id,
|
||||
location,
|
||||
hovered_entity,
|
||||
Out { hit },
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps pointers to the entities they are dragging.
|
||||
#[derive(Debug, Deref, DerefMut, Default, Resource)]
|
||||
pub struct DragMap(pub HashMap<(PointerId, PointerButton), HashMap<Entity, DragEntry>>);
|
||||
|
||||
/// An entry in the [`DragMap`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DragEntry {
|
||||
/// The position of the pointer at drag start.
|
||||
pub start_pos: Vec2,
|
||||
/// The latest position of the pointer during this drag, used to compute deltas.
|
||||
pub latest_pos: Vec2,
|
||||
}
|
||||
|
||||
/// Uses pointer events to determine when click and drag events occur.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn send_click_and_drag_events(
|
||||
// Input
|
||||
mut pointer_down: EventReader<Pointer<Down>>,
|
||||
mut pointer_up: EventReader<Pointer<Up>>,
|
||||
mut input_move: EventReader<InputMove>,
|
||||
mut input_presses: EventReader<InputPress>,
|
||||
pointer_map: Res<PointerMap>,
|
||||
pointers: Query<&PointerLocation>,
|
||||
// Locals
|
||||
mut down_map: Local<
|
||||
HashMap<(PointerId, PointerButton), HashMap<Entity, (Pointer<Down>, Instant)>>,
|
||||
>,
|
||||
// Output
|
||||
mut drag_map: ResMut<DragMap>,
|
||||
mut pointer_click: EventWriter<Pointer<Click>>,
|
||||
mut pointer_drag_start: EventWriter<Pointer<DragStart>>,
|
||||
mut pointer_drag_end: EventWriter<Pointer<DragEnd>>,
|
||||
mut pointer_drag: EventWriter<Pointer<Drag>>,
|
||||
) {
|
||||
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())
|
||||
};
|
||||
|
||||
// Triggers during movement even if not over an entity
|
||||
for InputMove {
|
||||
pointer_id,
|
||||
location,
|
||||
delta: _,
|
||||
} in input_move.read().cloned()
|
||||
{
|
||||
for button in PointerButton::iter() {
|
||||
let Some(down_list) = down_map.get(&(pointer_id, button)) else {
|
||||
continue;
|
||||
};
|
||||
let drag_list = drag_map.entry((pointer_id, button)).or_default();
|
||||
|
||||
for (down, _instant) in down_list.values() {
|
||||
if drag_list.contains_key(&down.target) {
|
||||
continue; // this entity is already logged as being dragged
|
||||
}
|
||||
drag_list.insert(
|
||||
down.target,
|
||||
DragEntry {
|
||||
start_pos: down.pointer_location.position,
|
||||
latest_pos: down.pointer_location.position,
|
||||
},
|
||||
);
|
||||
pointer_drag_start.send(Pointer::new(
|
||||
pointer_id,
|
||||
down.pointer_location.clone(),
|
||||
down.target,
|
||||
DragStart {
|
||||
button,
|
||||
hit: down.hit.clone(),
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
for (dragged_entity, drag) in drag_list.iter_mut() {
|
||||
let drag_event = Drag {
|
||||
button,
|
||||
distance: location.position - drag.start_pos,
|
||||
delta: location.position - drag.latest_pos,
|
||||
};
|
||||
drag.latest_pos = location.position;
|
||||
pointer_drag.send(Pointer::new(
|
||||
pointer_id,
|
||||
location.clone(),
|
||||
*dragged_entity,
|
||||
drag_event,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Triggers when button is released over an entity
|
||||
let now = Instant::now();
|
||||
for Pointer {
|
||||
pointer_id,
|
||||
pointer_location,
|
||||
target,
|
||||
event: Up { button, hit },
|
||||
} in pointer_up.read().cloned()
|
||||
{
|
||||
// Can't have a click without the button being pressed down first
|
||||
if let Some((_down, down_instant)) = down_map
|
||||
.get(&(pointer_id, button))
|
||||
.and_then(|down| down.get(&target))
|
||||
{
|
||||
let duration = now - *down_instant;
|
||||
pointer_click.send(Pointer::new(
|
||||
pointer_id,
|
||||
pointer_location,
|
||||
target,
|
||||
Click {
|
||||
button,
|
||||
hit,
|
||||
duration,
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Triggers when button is pressed over an entity
|
||||
for event in pointer_down.read() {
|
||||
let button = event.button;
|
||||
let down_button_entity_map = down_map.entry((event.pointer_id, button)).or_default();
|
||||
down_button_entity_map.insert(event.target, (event.clone(), now));
|
||||
}
|
||||
|
||||
// Triggered for all button presses
|
||||
for press in input_presses.read() {
|
||||
if press.direction != PressDirection::Up {
|
||||
continue; // We are only interested in button releases
|
||||
}
|
||||
down_map.insert((press.pointer_id, press.button), HashMap::new());
|
||||
let Some(drag_list) = drag_map.insert((press.pointer_id, press.button), HashMap::new())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(location) = pointer_location(press.pointer_id) else {
|
||||
debug!(
|
||||
"Unable to get location for pointer {:?} during event {:?}",
|
||||
press.pointer_id, press
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
for (drag_target, drag) in drag_list {
|
||||
let drag_end = DragEnd {
|
||||
button: press.button,
|
||||
distance: drag.latest_pos - drag.start_pos,
|
||||
};
|
||||
pointer_drag_end.send(Pointer::new(
|
||||
press.pointer_id,
|
||||
location.clone(),
|
||||
drag_target,
|
||||
drag_end,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Uses pointer events to determine when drag-over events occur
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn send_drag_over_events(
|
||||
// Input
|
||||
drag_map: Res<DragMap>,
|
||||
mut pointer_over: EventReader<Pointer<Over>>,
|
||||
mut pointer_move: EventReader<Pointer<Move>>,
|
||||
mut pointer_out: EventReader<Pointer<Out>>,
|
||||
mut pointer_drag_end: EventReader<Pointer<DragEnd>>,
|
||||
// Local
|
||||
mut drag_over_map: Local<HashMap<(PointerId, PointerButton), HashMap<Entity, HitData>>>,
|
||||
|
||||
// Output
|
||||
mut pointer_drag_enter: EventWriter<Pointer<DragEnter>>,
|
||||
mut pointer_drag_over: EventWriter<Pointer<DragOver>>,
|
||||
mut pointer_drag_leave: EventWriter<Pointer<DragLeave>>,
|
||||
mut pointer_drop: EventWriter<Pointer<Drop>>,
|
||||
) {
|
||||
// Fire PointerDragEnter events.
|
||||
for Pointer {
|
||||
pointer_id,
|
||||
pointer_location,
|
||||
target,
|
||||
event: Over { hit },
|
||||
} in pointer_over.read().cloned()
|
||||
{
|
||||
for button in PointerButton::iter() {
|
||||
for drag_target in drag_map
|
||||
.get(&(pointer_id, button))
|
||||
.iter()
|
||||
.flat_map(|drag_list| drag_list.keys())
|
||||
.filter(
|
||||
|&&drag_target| target != drag_target, /* can't drag over itself */
|
||||
)
|
||||
{
|
||||
let drag_entry = drag_over_map.entry((pointer_id, button)).or_default();
|
||||
drag_entry.insert(target, hit.clone());
|
||||
let event = DragEnter {
|
||||
button,
|
||||
dragged: *drag_target,
|
||||
hit: hit.clone(),
|
||||
};
|
||||
pointer_drag_enter.send(Pointer::new(
|
||||
pointer_id,
|
||||
pointer_location.clone(),
|
||||
target,
|
||||
event,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fire PointerDragOver events.
|
||||
for Pointer {
|
||||
pointer_id,
|
||||
pointer_location,
|
||||
target,
|
||||
event: Move { hit, delta: _ },
|
||||
} in pointer_move.read().cloned()
|
||||
{
|
||||
for button in PointerButton::iter() {
|
||||
for drag_target in drag_map
|
||||
.get(&(pointer_id, button))
|
||||
.iter()
|
||||
.flat_map(|drag_list| drag_list.keys())
|
||||
.filter(
|
||||
|&&drag_target| target != drag_target, /* can't drag over itself */
|
||||
)
|
||||
{
|
||||
pointer_drag_over.send(Pointer::new(
|
||||
pointer_id,
|
||||
pointer_location.clone(),
|
||||
target,
|
||||
DragOver {
|
||||
button,
|
||||
dragged: *drag_target,
|
||||
hit: hit.clone(),
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fire PointerDragLeave and PointerDrop events when the pointer stops dragging.
|
||||
for Pointer {
|
||||
pointer_id,
|
||||
pointer_location,
|
||||
target,
|
||||
event: DragEnd {
|
||||
button,
|
||||
distance: _,
|
||||
},
|
||||
} in pointer_drag_end.read().cloned()
|
||||
{
|
||||
let Some(drag_over_set) = drag_over_map.get_mut(&(pointer_id, button)) else {
|
||||
continue;
|
||||
};
|
||||
for (dragged_over, hit) in drag_over_set.drain() {
|
||||
pointer_drag_leave.send(Pointer::new(
|
||||
pointer_id,
|
||||
pointer_location.clone(),
|
||||
dragged_over,
|
||||
DragLeave {
|
||||
button,
|
||||
dragged: target,
|
||||
hit: hit.clone(),
|
||||
},
|
||||
));
|
||||
pointer_drop.send(Pointer::new(
|
||||
pointer_id,
|
||||
pointer_location.clone(),
|
||||
dragged_over,
|
||||
Drop {
|
||||
button,
|
||||
dropped: target,
|
||||
hit: hit.clone(),
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Fire PointerDragLeave events when the pointer goes out of the target.
|
||||
for Pointer {
|
||||
pointer_id,
|
||||
pointer_location,
|
||||
target,
|
||||
event: Out { hit },
|
||||
} in pointer_out.read().cloned()
|
||||
{
|
||||
for button in PointerButton::iter() {
|
||||
let Some(dragged_over) = drag_over_map.get_mut(&(pointer_id, button)) else {
|
||||
continue;
|
||||
};
|
||||
if dragged_over.remove(&target).is_none() {
|
||||
continue;
|
||||
}
|
||||
let Some(drag_list) = drag_map.get(&(pointer_id, button)) else {
|
||||
continue;
|
||||
};
|
||||
for drag_target in drag_list.keys() {
|
||||
pointer_drag_leave.send(Pointer::new(
|
||||
pointer_id,
|
||||
pointer_location.clone(),
|
||||
target,
|
||||
DragLeave {
|
||||
button,
|
||||
dragged: *drag_target,
|
||||
hit: hit.clone(),
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
266
crates/bevy_picking/src/focus.rs
Normal file
266
crates/bevy_picking/src/focus.rs
Normal file
|
@ -0,0 +1,266 @@
|
|||
//! Determines which entities are being hovered by which pointers.
|
||||
|
||||
use std::{collections::BTreeMap, fmt::Debug};
|
||||
|
||||
use crate::{
|
||||
backend::{self, HitData},
|
||||
events::PointerCancel,
|
||||
pointer::{PointerId, PointerInteraction, PointerPress},
|
||||
Pickable,
|
||||
};
|
||||
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_math::FloatOrd;
|
||||
use bevy_reflect::prelude::*;
|
||||
use bevy_utils::HashMap;
|
||||
|
||||
type DepthSortedHits = Vec<(Entity, HitData)>;
|
||||
|
||||
/// Events returned from backends can be grouped with an order field. This allows picking to work
|
||||
/// with multiple layers of rendered output to the same render target.
|
||||
type PickLayer = FloatOrd;
|
||||
|
||||
/// Maps [`PickLayer`]s to the map of entities within that pick layer, sorted by depth.
|
||||
type LayerMap = BTreeMap<PickLayer, DepthSortedHits>;
|
||||
|
||||
/// Maps Pointers to a [`LayerMap`]. Note this is much more complex than the [`HoverMap`] because
|
||||
/// this data structure is used to sort entities by layer then depth for every pointer.
|
||||
type OverMap = HashMap<PointerId, LayerMap>;
|
||||
|
||||
/// The source of truth for all hover state. This is used to determine what events to send, and what
|
||||
/// state components should be in.
|
||||
///
|
||||
/// Maps pointers to the entities they are hovering over.
|
||||
///
|
||||
/// "Hovering" refers to the *hover* state, which is not the same as whether or not a picking
|
||||
/// backend is reporting hits between a pointer and an entity. A pointer is "hovering" an entity
|
||||
/// only if the pointer is hitting the entity (as reported by a picking backend) *and* no entities
|
||||
/// between it and the pointer block interactions.
|
||||
///
|
||||
/// For example, if a pointer is hitting a UI button and a 3d mesh, but the button is in front of
|
||||
/// the mesh, and [`Pickable::should_block_lower`], the UI button will be hovered, but the mesh will
|
||||
/// not.
|
||||
///
|
||||
/// # Advanced Users
|
||||
///
|
||||
/// If you want to completely replace the provided picking events or state produced by this plugin,
|
||||
/// you can use this resource to do that. All of the event systems for picking are built *on top of*
|
||||
/// this authoritative hover state, and you can do the same. You can also use the
|
||||
/// [`PreviousHoverMap`] as a robust way of determining changes in hover state from the previous
|
||||
/// update.
|
||||
#[derive(Debug, Deref, DerefMut, Default, Resource)]
|
||||
pub struct HoverMap(pub HashMap<PointerId, HashMap<Entity, HitData>>);
|
||||
|
||||
/// The previous state of the hover map, used to track changes to hover state.
|
||||
#[derive(Debug, Deref, DerefMut, Default, Resource)]
|
||||
pub struct PreviousHoverMap(pub HashMap<PointerId, HashMap<Entity, HitData>>);
|
||||
|
||||
/// Coalesces all data from inputs and backends to generate a map of the currently hovered entities.
|
||||
/// This is the final focusing step to determine which entity the pointer is hovering over.
|
||||
pub fn update_focus(
|
||||
// Inputs
|
||||
pickable: Query<&Pickable>,
|
||||
pointers: Query<&PointerId>,
|
||||
mut under_pointer: EventReader<backend::PointerHits>,
|
||||
mut cancellations: EventReader<PointerCancel>,
|
||||
// Local
|
||||
mut over_map: Local<OverMap>,
|
||||
// Output
|
||||
mut hover_map: ResMut<HoverMap>,
|
||||
mut previous_hover_map: ResMut<PreviousHoverMap>,
|
||||
) {
|
||||
reset_maps(
|
||||
&mut hover_map,
|
||||
&mut previous_hover_map,
|
||||
&mut over_map,
|
||||
&pointers,
|
||||
);
|
||||
build_over_map(&mut under_pointer, &mut over_map, &mut cancellations);
|
||||
build_hover_map(&pointers, pickable, &over_map, &mut hover_map);
|
||||
}
|
||||
|
||||
/// Clear non-empty local maps, reusing allocated memory.
|
||||
fn reset_maps(
|
||||
hover_map: &mut HoverMap,
|
||||
previous_hover_map: &mut PreviousHoverMap,
|
||||
over_map: &mut OverMap,
|
||||
pointers: &Query<&PointerId>,
|
||||
) {
|
||||
// Swap the previous and current hover maps. This results in the previous values being stored in
|
||||
// `PreviousHoverMap`. Swapping is okay because we clear the `HoverMap` which now holds stale
|
||||
// data. This process is done without any allocations.
|
||||
core::mem::swap(&mut previous_hover_map.0, &mut hover_map.0);
|
||||
|
||||
for entity_set in hover_map.values_mut() {
|
||||
entity_set.clear();
|
||||
}
|
||||
for layer_map in over_map.values_mut() {
|
||||
layer_map.clear();
|
||||
}
|
||||
|
||||
// Clear pointers from the maps if they have been removed.
|
||||
let active_pointers: Vec<PointerId> = pointers.iter().copied().collect();
|
||||
hover_map.retain(|pointer, _| active_pointers.contains(pointer));
|
||||
over_map.retain(|pointer, _| active_pointers.contains(pointer));
|
||||
}
|
||||
|
||||
/// Build an ordered map of entities that are under each pointer
|
||||
fn build_over_map(
|
||||
backend_events: &mut EventReader<backend::PointerHits>,
|
||||
pointer_over_map: &mut Local<OverMap>,
|
||||
pointer_cancel: &mut EventReader<PointerCancel>,
|
||||
) {
|
||||
let cancelled_pointers: Vec<PointerId> = pointer_cancel.read().map(|p| p.pointer_id).collect();
|
||||
|
||||
for entities_under_pointer in backend_events
|
||||
.read()
|
||||
.filter(|e| !cancelled_pointers.contains(&e.pointer))
|
||||
{
|
||||
let pointer = entities_under_pointer.pointer;
|
||||
let layer_map = pointer_over_map
|
||||
.entry(pointer)
|
||||
.or_insert_with(BTreeMap::new);
|
||||
for (entity, pick_data) in entities_under_pointer.picks.iter() {
|
||||
let layer = entities_under_pointer.order;
|
||||
let hits = layer_map.entry(FloatOrd(layer)).or_insert_with(Vec::new);
|
||||
hits.push((*entity, pick_data.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
for layers in pointer_over_map.values_mut() {
|
||||
for hits in layers.values_mut() {
|
||||
hits.sort_by_key(|(_, hit)| FloatOrd(hit.depth));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Build an unsorted set of hovered entities, accounting for depth, layer, and [`Pickable`]. Note
|
||||
/// that unlike the pointer map, this uses [`Pickable`] to determine if lower entities receive hover
|
||||
/// focus. Often, only a single entity per pointer will be hovered.
|
||||
fn build_hover_map(
|
||||
pointers: &Query<&PointerId>,
|
||||
pickable: Query<&Pickable>,
|
||||
over_map: &Local<OverMap>,
|
||||
// Output
|
||||
hover_map: &mut HoverMap,
|
||||
) {
|
||||
for pointer_id in pointers.iter() {
|
||||
let pointer_entity_set = hover_map.entry(*pointer_id).or_insert_with(HashMap::new);
|
||||
if let Some(layer_map) = over_map.get(pointer_id) {
|
||||
// Note we reverse here to start from the highest layer first.
|
||||
for (entity, pick_data) in layer_map.values().rev().flatten() {
|
||||
if let Ok(pickable) = pickable.get(*entity) {
|
||||
if pickable.is_hoverable {
|
||||
pointer_entity_set.insert(*entity, pick_data.clone());
|
||||
}
|
||||
if pickable.should_block_lower {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
pointer_entity_set.insert(*entity, pick_data.clone()); // Emit events by default
|
||||
break; // Entities block by default so we break out of the loop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A component that aggregates picking interaction state of this entity across all pointers.
|
||||
///
|
||||
/// Unlike bevy's `Interaction` component, this is an aggregate of the state of all pointers
|
||||
/// interacting with this entity. Aggregation is done by taking the interaction with the highest
|
||||
/// precedence.
|
||||
///
|
||||
/// For example, if we have an entity that is being hovered by one pointer, and pressed by another,
|
||||
/// the entity will be considered pressed. If that entity is instead being hovered by both pointers,
|
||||
/// it will be considered hovered.
|
||||
#[derive(Component, Copy, Clone, Default, Eq, PartialEq, Debug, Reflect)]
|
||||
#[reflect(Component, Default)]
|
||||
pub enum PickingInteraction {
|
||||
/// The entity is being pressed down by a pointer.
|
||||
Pressed = 2,
|
||||
/// The entity is being hovered by a pointer.
|
||||
Hovered = 1,
|
||||
/// No pointers are interacting with this entity.
|
||||
#[default]
|
||||
None = 0,
|
||||
}
|
||||
|
||||
/// Uses pointer events to update [`PointerInteraction`] and [`PickingInteraction`] components.
|
||||
pub fn update_interactions(
|
||||
// Input
|
||||
hover_map: Res<HoverMap>,
|
||||
previous_hover_map: Res<PreviousHoverMap>,
|
||||
// Outputs
|
||||
mut commands: Commands,
|
||||
mut pointers: Query<(&PointerId, &PointerPress, &mut PointerInteraction)>,
|
||||
mut interact: Query<&mut PickingInteraction>,
|
||||
) {
|
||||
// Clear all previous hover data from pointers and entities
|
||||
for (pointer, _, mut pointer_interaction) in &mut pointers {
|
||||
pointer_interaction.sorted_entities.clear();
|
||||
if let Some(previously_hovered_entities) = previous_hover_map.get(pointer) {
|
||||
for entity in previously_hovered_entities.keys() {
|
||||
if let Ok(mut interaction) = interact.get_mut(*entity) {
|
||||
*interaction = PickingInteraction::None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a map to hold the aggregated interaction for each entity. This is needed because we
|
||||
// need to be able to insert the interaction component on entities if they do not exist. To do
|
||||
// so we need to know the final aggregated interaction state to avoid the scenario where we set
|
||||
// an entity to `Pressed`, then overwrite that with a lower precedent like `Hovered`.
|
||||
let mut new_interaction_state = HashMap::<Entity, PickingInteraction>::new();
|
||||
for (pointer, pointer_press, mut pointer_interaction) in &mut pointers {
|
||||
if let Some(pointers_hovered_entities) = hover_map.get(pointer) {
|
||||
// Insert a sorted list of hit entities into the pointer's interaction component.
|
||||
let mut sorted_entities: Vec<_> = pointers_hovered_entities.clone().drain().collect();
|
||||
sorted_entities.sort_by_key(|(_entity, hit)| FloatOrd(hit.depth));
|
||||
pointer_interaction.sorted_entities = sorted_entities;
|
||||
|
||||
for hovered_entity in pointers_hovered_entities.iter().map(|(entity, _)| entity) {
|
||||
merge_interaction_states(pointer_press, hovered_entity, &mut new_interaction_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Take the aggregated entity states and update or insert the component if missing.
|
||||
for (hovered_entity, new_interaction) in new_interaction_state.drain() {
|
||||
if let Ok(mut interaction) = interact.get_mut(hovered_entity) {
|
||||
*interaction = new_interaction;
|
||||
} else if let Some(mut entity_commands) = commands.get_entity(hovered_entity) {
|
||||
entity_commands.try_insert(new_interaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Merge the interaction state of this entity into the aggregated map.
|
||||
fn merge_interaction_states(
|
||||
pointer_press: &PointerPress,
|
||||
hovered_entity: &Entity,
|
||||
new_interaction_state: &mut HashMap<Entity, PickingInteraction>,
|
||||
) {
|
||||
let new_interaction = match pointer_press.is_any_pressed() {
|
||||
true => PickingInteraction::Pressed,
|
||||
false => PickingInteraction::Hovered,
|
||||
};
|
||||
|
||||
if let Some(old_interaction) = new_interaction_state.get_mut(hovered_entity) {
|
||||
// Only update if the new value has a higher precedence than the old value.
|
||||
if *old_interaction != new_interaction
|
||||
&& matches!(
|
||||
(*old_interaction, new_interaction),
|
||||
(PickingInteraction::Hovered, PickingInteraction::Pressed)
|
||||
| (PickingInteraction::None, PickingInteraction::Pressed)
|
||||
| (PickingInteraction::None, PickingInteraction::Hovered)
|
||||
)
|
||||
{
|
||||
*old_interaction = new_interaction;
|
||||
}
|
||||
} else {
|
||||
new_interaction_state.insert(*hovered_entity, new_interaction);
|
||||
}
|
||||
}
|
86
crates/bevy_picking/src/input/mod.rs
Normal file
86
crates/bevy_picking/src/input/mod.rs
Normal file
|
@ -0,0 +1,86 @@
|
|||
//! `bevy_picking::input` is a thin layer that provides unsurprising default inputs to `bevy_picking`.
|
||||
//! The included systems are responsible for sending mouse and touch inputs to their
|
||||
//! respective `Pointer`s.
|
||||
//!
|
||||
//! Because this resides in its own crate, it's easy to omit it, and provide your own inputs as
|
||||
//! needed. Because `Pointer`s aren't coupled to the underlying input hardware, you can easily mock
|
||||
//! inputs, and allow users full accessibility to map whatever inputs they need to pointer input.
|
||||
//!
|
||||
//! If, for example, you wanted to add support for VR input, all you need to do is spawn a pointer
|
||||
//! entity with a custom [`PointerId`](crate::pointer::PointerId), and write a system
|
||||
//! that updates its position.
|
||||
//!
|
||||
//! TODO: Update docs
|
||||
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_reflect::prelude::*;
|
||||
|
||||
use crate::PickSet;
|
||||
|
||||
pub mod mouse;
|
||||
pub mod touch;
|
||||
|
||||
/// Common imports for `bevy_picking_input`.
|
||||
pub mod prelude {
|
||||
pub use crate::input::InputPlugin;
|
||||
}
|
||||
|
||||
/// Adds mouse and touch inputs for picking pointers to your app. This is a default input plugin,
|
||||
/// that you can replace with your own plugin as needed.
|
||||
///
|
||||
/// [`crate::PickingPluginsSettings::is_input_enabled`] can be used to toggle whether
|
||||
/// the core picking plugin processes the inputs sent by this, or other input plugins, in one place.
|
||||
#[derive(Copy, Clone, Resource, Debug, Reflect)]
|
||||
#[reflect(Resource, Default)]
|
||||
pub struct InputPlugin {
|
||||
/// Should touch inputs be updated?
|
||||
pub is_touch_enabled: bool,
|
||||
/// Should mouse inputs be updated?
|
||||
pub is_mouse_enabled: bool,
|
||||
}
|
||||
|
||||
impl InputPlugin {
|
||||
fn is_mouse_enabled(state: Res<Self>) -> bool {
|
||||
state.is_mouse_enabled
|
||||
}
|
||||
|
||||
fn is_touch_enabled(state: Res<Self>) -> bool {
|
||||
state.is_touch_enabled
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for InputPlugin {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
is_touch_enabled: true,
|
||||
is_mouse_enabled: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for InputPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.insert_resource(*self)
|
||||
.add_systems(Startup, mouse::spawn_mouse_pointer)
|
||||
.add_systems(
|
||||
First,
|
||||
(
|
||||
mouse::mouse_pick_events.run_if(InputPlugin::is_mouse_enabled),
|
||||
touch::touch_pick_events.run_if(InputPlugin::is_touch_enabled),
|
||||
// IMPORTANT: the commands must be flushed after `touch_pick_events` is run
|
||||
// because we need pointer spawning to happen immediately to prevent issues with
|
||||
// missed events during drag and drop.
|
||||
apply_deferred,
|
||||
)
|
||||
.chain()
|
||||
.in_set(PickSet::Input),
|
||||
)
|
||||
.add_systems(
|
||||
Last,
|
||||
touch::deactivate_touch_pointers.run_if(InputPlugin::is_touch_enabled),
|
||||
)
|
||||
.register_type::<Self>()
|
||||
.register_type::<InputPlugin>();
|
||||
}
|
||||
}
|
67
crates/bevy_picking/src/input/mouse.rs
Normal file
67
crates/bevy_picking/src/input/mouse.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
//! Provides sensible defaults for mouse picking inputs.
|
||||
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_input::{mouse::MouseButtonInput, prelude::*, ButtonState};
|
||||
use bevy_math::Vec2;
|
||||
use bevy_render::camera::RenderTarget;
|
||||
use bevy_window::{CursorMoved, PrimaryWindow, Window, WindowRef};
|
||||
|
||||
use crate::{
|
||||
pointer::{InputMove, InputPress, Location, PointerButton, PointerId},
|
||||
PointerBundle,
|
||||
};
|
||||
|
||||
/// Spawns the default mouse pointer.
|
||||
pub fn spawn_mouse_pointer(mut commands: Commands) {
|
||||
commands.spawn((PointerBundle::new(PointerId::Mouse),));
|
||||
}
|
||||
|
||||
/// Sends mouse pointer events to be processed by the core plugin
|
||||
pub fn mouse_pick_events(
|
||||
// Input
|
||||
windows: Query<(Entity, &Window), With<PrimaryWindow>>,
|
||||
mut cursor_moves: EventReader<CursorMoved>,
|
||||
mut cursor_last: Local<Vec2>,
|
||||
mut mouse_inputs: EventReader<MouseButtonInput>,
|
||||
// Output
|
||||
mut pointer_move: EventWriter<InputMove>,
|
||||
mut pointer_presses: EventWriter<InputPress>,
|
||||
) {
|
||||
for event in cursor_moves.read() {
|
||||
pointer_move.send(InputMove::new(
|
||||
PointerId::Mouse,
|
||||
Location {
|
||||
target: RenderTarget::Window(WindowRef::Entity(event.window))
|
||||
.normalize(Some(
|
||||
match windows.get_single() {
|
||||
Ok(w) => w,
|
||||
Err(_) => continue,
|
||||
}
|
||||
.0,
|
||||
))
|
||||
.unwrap(),
|
||||
position: event.position,
|
||||
},
|
||||
event.position - *cursor_last,
|
||||
));
|
||||
*cursor_last = event.position;
|
||||
}
|
||||
|
||||
for input in mouse_inputs.read() {
|
||||
let button = match input.button {
|
||||
MouseButton::Left => PointerButton::Primary,
|
||||
MouseButton::Right => PointerButton::Secondary,
|
||||
MouseButton::Middle => PointerButton::Middle,
|
||||
MouseButton::Other(_) | MouseButton::Back | MouseButton::Forward => continue,
|
||||
};
|
||||
|
||||
match input.state {
|
||||
ButtonState::Pressed => {
|
||||
pointer_presses.send(InputPress::new_down(PointerId::Mouse, button));
|
||||
}
|
||||
ButtonState::Released => {
|
||||
pointer_presses.send(InputPress::new_up(PointerId::Mouse, button));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
105
crates/bevy_picking/src/input/touch.rs
Normal file
105
crates/bevy_picking/src/input/touch.rs
Normal file
|
@ -0,0 +1,105 @@
|
|||
//! Provides sensible defaults for touch picking inputs.
|
||||
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_hierarchy::DespawnRecursiveExt;
|
||||
use bevy_input::touch::{TouchInput, TouchPhase};
|
||||
use bevy_math::Vec2;
|
||||
use bevy_render::camera::RenderTarget;
|
||||
use bevy_utils::{tracing::debug, HashMap, HashSet};
|
||||
use bevy_window::{PrimaryWindow, WindowRef};
|
||||
|
||||
use crate::{
|
||||
events::PointerCancel,
|
||||
pointer::{InputMove, InputPress, Location, PointerButton, PointerId},
|
||||
PointerBundle,
|
||||
};
|
||||
|
||||
/// Sends touch pointer events to be consumed by the core plugin
|
||||
///
|
||||
/// IMPORTANT: the commands must be flushed after this system is run because we need spawning to
|
||||
/// happen immediately to prevent issues with missed events needed for drag and drop.
|
||||
pub fn touch_pick_events(
|
||||
// Input
|
||||
mut touches: EventReader<TouchInput>,
|
||||
primary_window: Query<Entity, With<PrimaryWindow>>,
|
||||
// Local
|
||||
mut location_cache: Local<HashMap<u64, TouchInput>>,
|
||||
// Output
|
||||
mut commands: Commands,
|
||||
mut input_moves: EventWriter<InputMove>,
|
||||
mut input_presses: EventWriter<InputPress>,
|
||||
mut cancel_events: EventWriter<PointerCancel>,
|
||||
) {
|
||||
for touch in touches.read() {
|
||||
let pointer = PointerId::Touch(touch.id);
|
||||
let location = Location {
|
||||
target: match RenderTarget::Window(WindowRef::Entity(touch.window))
|
||||
.normalize(primary_window.get_single().ok())
|
||||
{
|
||||
Some(target) => target,
|
||||
None => continue,
|
||||
},
|
||||
position: touch.position,
|
||||
};
|
||||
match touch.phase {
|
||||
TouchPhase::Started => {
|
||||
debug!("Spawning pointer {:?}", pointer);
|
||||
commands.spawn((PointerBundle::new(pointer).with_location(location.clone()),));
|
||||
|
||||
input_moves.send(InputMove::new(pointer, location, Vec2::ZERO));
|
||||
input_presses.send(InputPress::new_down(pointer, PointerButton::Primary));
|
||||
location_cache.insert(touch.id, *touch);
|
||||
}
|
||||
TouchPhase::Moved => {
|
||||
// Send a move event only if it isn't the same as the last one
|
||||
if let Some(last_touch) = location_cache.get(&touch.id) {
|
||||
if last_touch == touch {
|
||||
continue;
|
||||
}
|
||||
input_moves.send(InputMove::new(
|
||||
pointer,
|
||||
location,
|
||||
touch.position - last_touch.position,
|
||||
));
|
||||
}
|
||||
location_cache.insert(touch.id, *touch);
|
||||
}
|
||||
TouchPhase::Ended | TouchPhase::Canceled => {
|
||||
input_presses.send(InputPress::new_up(pointer, PointerButton::Primary));
|
||||
location_cache.remove(&touch.id);
|
||||
cancel_events.send(PointerCancel {
|
||||
pointer_id: pointer,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Deactivates unused touch pointers.
|
||||
///
|
||||
/// Because each new touch gets assigned a new ID, we need to remove the pointers associated with
|
||||
/// touches that are no longer active.
|
||||
pub fn deactivate_touch_pointers(
|
||||
mut commands: Commands,
|
||||
mut despawn_list: Local<HashSet<(Entity, PointerId)>>,
|
||||
pointers: Query<(Entity, &PointerId)>,
|
||||
mut touches: EventReader<TouchInput>,
|
||||
) {
|
||||
for touch in touches.read() {
|
||||
match touch.phase {
|
||||
TouchPhase::Ended | TouchPhase::Canceled => {
|
||||
for (entity, pointer) in &pointers {
|
||||
if pointer.get_touch_id() == Some(touch.id) {
|
||||
despawn_list.insert((entity, *pointer));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
// A hash set is used to prevent despawning the same entity twice.
|
||||
for (entity, pointer) in despawn_list.drain() {
|
||||
debug!("Despawning pointer {:?}", pointer);
|
||||
commands.entity(entity).despawn_recursive();
|
||||
}
|
||||
}
|
|
@ -3,6 +3,9 @@
|
|||
#![deny(missing_docs)]
|
||||
|
||||
pub mod backend;
|
||||
pub mod events;
|
||||
pub mod focus;
|
||||
pub mod input;
|
||||
pub mod pointer;
|
||||
|
||||
use bevy_app::prelude::*;
|
||||
|
@ -26,8 +29,6 @@ impl PickingPluginsSettings {
|
|||
pub fn input_should_run(state: Res<Self>) -> bool {
|
||||
state.is_input_enabled && state.is_enabled
|
||||
}
|
||||
// TODO: remove this allow after focus/hover is implemented in bevy_picking
|
||||
#[allow(rustdoc::broken_intra_doc_links)]
|
||||
/// Whether or not systems updating entities' [`PickingInteraction`](focus::PickingInteraction)
|
||||
/// component should be running.
|
||||
pub fn focus_should_run(state: Res<Self>) -> bool {
|
||||
|
@ -72,11 +73,7 @@ pub struct Pickable {
|
|||
///
|
||||
/// Entities without the [`Pickable`] component will block by default.
|
||||
pub should_block_lower: bool,
|
||||
// TODO: remove this allow after focus/hover is implemented in bevy_picking
|
||||
#[allow(rustdoc::broken_intra_doc_links)]
|
||||
/// Should this entity be added to the [`HoverMap`](focus::HoverMap) and thus emit events when
|
||||
/// targeted?
|
||||
///
|
||||
|
||||
/// If this is set to `false` and `should_block_lower` is set to true, this entity will block
|
||||
/// lower entities from being interacted and at the same time will itself not emit any events.
|
||||
///
|
||||
|
@ -214,3 +211,30 @@ impl Plugin for PickingPlugin {
|
|||
.register_type::<backend::ray::RayId>();
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates [`Pointer`](events::Pointer) events and handles event bubbling.
|
||||
pub struct InteractionPlugin;
|
||||
|
||||
impl Plugin for InteractionPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
use events::*;
|
||||
use focus::{update_focus, update_interactions};
|
||||
|
||||
app.init_resource::<focus::HoverMap>()
|
||||
.init_resource::<focus::PreviousHoverMap>()
|
||||
.init_resource::<DragMap>()
|
||||
.add_event::<PointerCancel>()
|
||||
.add_systems(
|
||||
PreUpdate,
|
||||
(
|
||||
update_focus,
|
||||
pointer_events,
|
||||
update_interactions,
|
||||
send_click_and_drag_events,
|
||||
send_drag_over_events,
|
||||
)
|
||||
.chain()
|
||||
.in_set(PickSet::Focus),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue