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:
TotalKrill 2024-08-10 01:16:37 +02:00 committed by GitHub
parent e490b919df
commit 3e10fd8534
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 1225 additions and 7 deletions

View file

@ -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" }

View 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(),
},
));
}
}
}
}

View 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);
}
}

View 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>();
}
}

View 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));
}
}
}
}

View 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();
}
}

View file

@ -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),
);
}
}