diff --git a/crates/bevy_picking/Cargo.toml b/crates/bevy_picking/Cargo.toml index eadd869e2d313..64eada2dbb04e 100644 --- a/crates/bevy_picking/Cargo.toml +++ b/crates/bevy_picking/Cargo.toml @@ -9,6 +9,7 @@ license = "MIT OR Apache-2.0" [dependencies] bevy_app = { path = "../bevy_app", version = "0.15.0-dev" } +bevy_asset = { path = "../bevy_asset", 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" } @@ -16,13 +17,11 @@ 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" } +bevy_time = { path = "../bevy_time", version = "0.15.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.15.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.15.0-dev" } bevy_window = { path = "../bevy_window", version = "0.15.0-dev" } -bevy_time = { path = "../bevy_time", version = "0.15.0-dev" } -bevy_asset = { path = "../bevy_asset", version = "0.15.0-dev" } - uuid = { version = "1.1", features = ["v4"] } [lints] diff --git a/crates/bevy_picking/src/events.rs b/crates/bevy_picking/src/events.rs index a1d5aee04bace..00293c1d5e297 100644 --- a/crates/bevy_picking/src/events.rs +++ b/crates/bevy_picking/src/events.rs @@ -1,8 +1,44 @@ -//! Processes data from input and backends, producing interaction events. +//! This module defines a stateful set of interaction events driven by the `PointerInput` stream +//! and the hover state of each Pointer. +//! +//! # Usage +//! +//! To receive events from this module, you must use an [`Observer`] +//! The simplest example, registering a callback when an entity is hovered over by a pointer, looks like this: +//! +//! ```rust +//! # use bevy_ecs::prelude::*; +//! # use bevy_picking::prelude::*; +//! # let mut world = World::default(); +//! world.spawn_empty() +//! .observe(|trigger: Trigger>| { +//! println!("I am being hovered over"); +//! }); +//! ``` +//! +//! Observers give us three important properties: +//! 1. They allow for attaching event handlers to specific entities, +//! 2. they allow events to bubble up the entity hierarchy, +//! 3. and they allow events of different types to be called in a specific order. +//! +//! The order in which interaction events are received is extremely important, and you can read more +//! about it on the docs for the dispatcher system: [`pointer_events`]. This system runs in +//! [`PreUpdate`](bevy_app::PreUpdate) in [`PickSet::Focus`](crate::PickSet::Focus). All pointer-event +//! observers resolve during the sync point between [`pointer_events`] and +//! [`update_interactions`](crate::focus::update_interactions). +//! +//! # Events Types +//! +//! The events this module defines fall into a few broad categories: +//! + Hovering and movement: [`Over`], [`Move`], and [`Out`]. +//! + Clicking and pressing: [`Down`], [`Up`], and [`Click`]. +//! + Dragging and dropping: [`DragStart`], [`Drag`], [`DragEnd`], [`DragEnter`], [`DragOver`], [`DragDrop`], [`DragLeave`]. +//! +//! When received by an observer, these events will always be wrapped by the [`Pointer`] type, which contains +//! general metadata about the pointer and it's location. use std::fmt::Debug; -use bevy_derive::{Deref, DerefMut}; use bevy_ecs::prelude::*; use bevy_hierarchy::Parent; use bevy_math::Vec2; @@ -13,15 +49,16 @@ use crate::{ backend::{prelude::PointerLocation, HitData}, focus::{HoverMap, PreviousHoverMap}, pointer::{ - InputMove, InputPress, Location, PointerButton, PointerId, PointerMap, PressDirection, + Location, PointerAction, PointerButton, PointerId, PointerInput, PointerMap, PressDirection, }, }; -/// Stores the common data needed for all `PointerEvent`s. +/// Stores the common data needed for all pointer events. +/// +/// The documentation for the [`pointer_events`] explains the events this module exposes and +/// the order in which they fire. #[derive(Clone, PartialEq, Debug, Reflect, Component)] pub struct Pointer { - /// 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 @@ -36,14 +73,15 @@ where E: Debug + Clone + Reflect, { type Traversal = Parent; + const AUTO_PROPAGATE: bool = true; } impl std::fmt::Display for Pointer { 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 + "{:?}, {:.1?}, {:.1?}", + self.pointer_id, self.pointer_location.position, self.event )) } } @@ -57,23 +95,21 @@ impl std::ops::Deref for Pointer { } impl Pointer { - /// Construct a new `PointerEvent`. - pub fn new(id: PointerId, location: Location, target: Entity, event: E) -> Self { + /// Construct a new `Pointer` event. + pub fn new(id: PointerId, location: Location, 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 pointer is canceled, and it's current interaction state is dropped. +#[derive(Clone, PartialEq, Debug, Reflect)] +pub struct Cancel { + /// Information about the picking intersection. + pub hit: HitData, } /// Fires when a the pointer crosses into the bounds of the `target` entity. @@ -202,24 +238,68 @@ pub struct DragDrop { pub hit: HitData, } -/// Generates pointer events from input and focus data +/// Dragging state. +#[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, +} + +/// An entry in the cache that drives the `pointer_events` system, storing additional data +/// about pointer button presses. +#[derive(Debug, Clone, Default)] +pub struct PointerState { + /// Stores the press location and start time for each button currently being pressed by the pointer. + pub pressing: HashMap, + /// Stores the the starting and current locations for each entity currently being dragged by the pointer. + pub dragging: HashMap, + /// Stores the hit data for each entity currently being dragged over by the pointer. + pub dragging_over: HashMap, +} + +/// Dispatches interaction events to the target entities. +/// +/// Within a single frame, events are dispatched in the following order: +/// + The sequence [`DragEnter`], [`Over`]. +/// + Any number of any of the following: +/// + For each movement: The sequence [`DragStart`], [`Drag`], [`DragOver`], [`Move`]. +/// + For each button press: Either [`Down`], or the sequence [`DragDrop`], [`DragEnd`], [`DragLeave`], [`Click`], [`Up`]. +/// + For each pointer cancellation: Simply [`Cancel`]. +/// + Finally the sequence [`DragLeave`], [`Out`]. +/// +/// Only the last event in a given sequence is garenteed to be present. +/// +/// Additionally, across multiple frames, the following are also strictly ordered by the interaction state machine: +/// + When a pointer moves over the target: [`Over`], [`Move`], [`Out`]. +/// + When a pointer presses buttons on the target: [`Down`], [`Up`], [`Click`]. +/// + When a pointer drags the target: [`DragStart`], [`Drag`], [`DragEnd`]. +/// + When a pointer drags something over the target: [`DragEnter`], [`DragOver`], [`DragDrop`], [`DragLeave`]. +/// + When a pointer is canceled: No other events will follow the [`Cancel`] event for that pointer. +/// +/// Two events -- [`Over`] and [`Out`] -- are driven only by the [`HoverMap`]. The rest rely on additional data from the +/// [`PointerInput`] event stream. To receive these events for a custom pointer, you must add [`PointerInput`] events. +/// +/// Note: Though it is common for the [`PointerInput`] stream may contain multiple pointer movements and presses each frame, +/// the hover state is determined only by the pointer's *final position*. Since the hover state ultimately determines which +/// entities receive events, this may mean that an entity can receive events which occurred before it was actually hovered. #[allow(clippy::too_many_arguments)] pub fn pointer_events( - mut commands: Commands, // Input - mut input_presses: EventReader, - mut input_moves: EventReader, - pointer_map: Res, + mut input_events: EventReader, + // ECS State pointers: Query<&PointerLocation>, + pointer_map: Res, hover_map: Res, previous_hover_map: Res, + // Local state + mut pointer_state: Local>, // Output - mut pointer_move: EventWriter>, - mut pointer_over: EventWriter>, - mut pointer_out: EventWriter>, - mut pointer_up: EventWriter>, - mut pointer_down: EventWriter>, + mut commands: Commands, ) { + // Setup utilities + let now = Instant::now(); let pointer_location = |pointer_id: PointerId| { pointer_map .get_entity(pointer_id) @@ -227,81 +307,6 @@ pub fn pointer_events( .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()))) - { - let event = Pointer::new( - pointer_id, - location.clone(), - hovered_entity, - Move { hit, delta }, - ); - commands.trigger_targets(event.clone(), event.target); - pointer_move.send(event); - } - } - - 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; - }; - let event = Pointer::new( - press_event.pointer_id, - location, - hovered_entity, - Up { button, hit }, - ); - commands.trigger_targets(event.clone(), event.target); - pointer_up.send(event); - } - } - 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; - }; - let event = Pointer::new( - press_event.pointer_id, - location, - hovered_entity, - Down { button, hit }, - ); - commands.trigger_targets(event.clone(), event.target); - pointer_down.send(event); - } - } - } - // If the entity is hovered... for (pointer_id, hovered_entity, hit) in hover_map .iter() @@ -320,9 +325,252 @@ pub fn pointer_events( ); continue; }; - let event = Pointer::new(pointer_id, location, hovered_entity, Over { hit }); - commands.trigger_targets(event.clone(), event.target); - pointer_over.send(event); + // Possibly send DragEnter events + for button in PointerButton::iter() { + let state = pointer_state.entry((pointer_id, button)).or_default(); + + for drag_target in state + .dragging + .keys() + .filter(|&&drag_target| hovered_entity != drag_target) + { + state.dragging_over.insert(hovered_entity, hit.clone()); + commands.trigger_targets( + Pointer::new( + pointer_id, + location.clone(), + DragEnter { + button, + dragged: *drag_target, + hit: hit.clone(), + }, + ), + hovered_entity, + ); + } + } + // Always send Over events + commands.trigger_targets( + Pointer::new(pointer_id, location.clone(), Over { hit: hit.clone() }), + hovered_entity, + ); + } + } + + // Dispatch input events... + for PointerInput { + pointer_id, + location, + action, + } in input_events.read().cloned() + { + match action { + // Pressed Button + PointerAction::Pressed { direction, button } => { + let state = pointer_state.entry((pointer_id, button)).or_default(); + + // Possibly emit DragEnd, DragDrop, DragLeave on button releases + if direction == PressDirection::Up { + // For each currently dragged entity + for (drag_target, drag) in state.dragging.drain() { + // Emit DragDrop + for (dragged_over, hit) in state.dragging_over.iter() { + commands.trigger_targets( + Pointer::new( + pointer_id, + location.clone(), + DragDrop { + button, + dropped: drag_target, + hit: hit.clone(), + }, + ), + *dragged_over, + ); + } + // Emit DragEnd + commands.trigger_targets( + Pointer::new( + pointer_id, + location.clone(), + DragEnd { + button, + distance: drag.latest_pos - drag.start_pos, + }, + ), + drag_target, + ); + // Emit DragLeave + for (dragged_over, hit) in state.dragging_over.iter() { + commands.trigger_targets( + Pointer::new( + pointer_id, + location.clone(), + DragLeave { + button, + dragged: drag_target, + hit: hit.clone(), + }, + ), + *dragged_over, + ); + } + } + } + + // Send a Down or possibly a Click and an Up button events + for (hovered_entity, hit) in previous_hover_map + .get(&pointer_id) + .iter() + .flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.clone()))) + { + match direction { + PressDirection::Down => { + // Send the Down event first + let event = + Pointer::new(pointer_id, location.clone(), Down { button, hit }); + commands.trigger_targets(event.clone(), hovered_entity); + // Also insert the press into the state + state + .pressing + .insert(hovered_entity, (event.pointer_location, now)); + } + PressDirection::Up => { + // If this pointer previously pressed the hovered entity, first send a Click event + if let Some((_location, press_instant)) = + state.pressing.get(&hovered_entity) + { + commands.trigger_targets( + Pointer::new( + pointer_id, + location.clone(), + Click { + button, + hit: hit.clone(), + duration: now - *press_instant, + }, + ), + hovered_entity, + ); + } + // Always send the Up event + commands.trigger_targets( + Pointer::new( + pointer_id, + location.clone(), + Up { + button, + hit: hit.clone(), + }, + ), + hovered_entity, + ); + // Also clear the state + state.pressing.clear(); + state.dragging.clear(); + state.dragging_over.clear(); + } + }; + } + } + // Moved + PointerAction::Moved { delta } => { + for (hovered_entity, hit) in hover_map + .get(&pointer_id) + .iter() + .flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.to_owned()))) + { + // Send drag events to entities being dragged or dragged over + for button in PointerButton::iter() { + let state = pointer_state.entry((pointer_id, button)).or_default(); + + // Emit a DragStart the first time the pointer moves while pressing an entity + for (location, _instant) in state.pressing.values() { + if state.dragging.contains_key(&hovered_entity) { + continue; // this entity is already logged as being dragged + } + state.dragging.insert( + hovered_entity, + DragEntry { + start_pos: location.position, + latest_pos: location.position, + }, + ); + commands.trigger_targets( + Pointer::new( + pointer_id, + location.clone(), + DragStart { + button, + hit: hit.clone(), + }, + ), + hovered_entity, + ); + } + + // Emit a Drag event to the dragged entity when it is dragged over another entity. + for (dragged_entity, drag) in state.dragging.iter_mut() { + let drag_event = Drag { + button, + distance: location.position - drag.start_pos, + delta: location.position - drag.latest_pos, + }; + drag.latest_pos = location.position; + let target = *dragged_entity; + let event = Pointer::new(pointer_id, location.clone(), drag_event); + commands.trigger_targets(event, target); + } + + // Emit a DragOver to the hovered entity when dragging a different entity over it. + for drag_target in state.dragging.keys() + .filter( + |&&drag_target| hovered_entity != drag_target, /* can't drag over itself */ + ) + { + commands.trigger_targets( + Pointer::new(pointer_id, location.clone(), DragOver { button, dragged: *drag_target, hit: hit.clone() }), + hovered_entity, + ); + } + } + + // Always send Move event + commands.trigger_targets( + Pointer::new( + pointer_id, + location.clone(), + Move { + hit: hit.clone(), + delta, + }, + ), + hovered_entity, + ); + } + } + // Canceled + PointerAction::Canceled => { + // Emit a Cancel to the hovered entity. + for (hovered_entity, hit) in hover_map + .get(&pointer_id) + .iter() + .flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.to_owned()))) + { + commands.trigger_targets( + Pointer::new(pointer_id, location.clone(), Cancel { hit }), + hovered_entity, + ); + } + // Clear the local state for the canceled pointer + for button in PointerButton::iter() { + if let Some(state) = pointer_state.get_mut(&(pointer_id, button)) { + state.pressing.clear(); + state.dragging.clear(); + state.dragging_over.clear(); + } + } + } } } @@ -344,329 +592,31 @@ pub fn pointer_events( ); continue; }; - let event = Pointer::new(pointer_id, location, hovered_entity, Out { hit }); - commands.trigger_targets(event.clone(), event.target); - pointer_out.send(event); - } - } -} - -/// Maps pointers to the entities they are dragging. -#[derive(Debug, Deref, DerefMut, Default, Resource)] -pub struct DragMap(pub HashMap<(PointerId, PointerButton), HashMap>); - -/// 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( - // for triggering observers - // - Pointer - // - Pointer - // - Pointer - mut commands: Commands, - // Input - mut pointer_down: EventReader>, - mut pointer_up: EventReader>, - mut input_move: EventReader, - mut input_presses: EventReader, - pointer_map: Res, - pointers: Query<&PointerLocation>, - // Locals - mut down_map: Local< - HashMap<(PointerId, PointerButton), HashMap, Instant)>>, - >, - // Outputs used for further processing - mut drag_map: ResMut, - mut pointer_drag_end: EventWriter>, -) { - 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 + // Possibly send DragLeave events + for button in PointerButton::iter() { + let state = pointer_state.entry((pointer_id, button)).or_default(); + state.dragging_over.remove(&hovered_entity); + for drag_target in state.dragging.keys() { + commands.trigger_targets( + Pointer::new( + pointer_id, + location.clone(), + DragLeave { + button, + dragged: *drag_target, + hit: hit.clone(), + }, + ), + hovered_entity, + ); } - drag_list.insert( - down.target, - DragEntry { - start_pos: down.pointer_location.position, - latest_pos: down.pointer_location.position, - }, - ); - let event = Pointer::new( - pointer_id, - down.pointer_location.clone(), - down.target, - DragStart { - button, - hit: down.hit.clone(), - }, - ); - commands.trigger_targets(event, down.target); } - 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; - let target = *dragged_entity; - let event = Pointer::new(pointer_id, location.clone(), target, drag_event); - commands.trigger_targets(event, target); - } - } - } - - // 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; - let event = Pointer::new( - pointer_id, - pointer_location, - target, - Click { - button, - hit, - duration, - }, - ); - commands.trigger_targets(event, target); - } - } - - // 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, - }; - let event = Pointer::new(press.pointer_id, location.clone(), drag_target, drag_end); - commands.trigger_targets(event.clone(), event.target); - pointer_drag_end.send(event); - } - } -} - -/// Uses pointer events to determine when drag-over events occur -#[allow(clippy::too_many_arguments)] -pub fn send_drag_over_events( - // uses this to trigger the following - // - Pointer, - // - Pointer, - // - Pointer, - // - Pointer, - mut commands: Commands, - // Input - drag_map: Res, - mut pointer_over: EventReader>, - mut pointer_move: EventReader>, - mut pointer_out: EventReader>, - mut pointer_drag_end: EventReader>, - // Local - mut drag_over_map: Local>>, -) { - // 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 = Pointer::new( - pointer_id, - pointer_location.clone(), - target, - DragEnter { - button, - dragged: *drag_target, - hit: hit.clone(), - }, - ); - commands.trigger_targets(event, target); - } - } - } - - // 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 */ - ) - { - let event = Pointer::new( - pointer_id, - pointer_location.clone(), - target, - DragOver { - button, - dragged: *drag_target, - hit: hit.clone(), - }, - ); - commands.trigger_targets(event, target); - } - } - } - - // Fire PointerDragLeave and PointerDrop events when the pointer stops dragging. - for Pointer { - pointer_id, - pointer_location, - target: drag_end_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() { - let target = dragged_over; - let event = Pointer::new( - pointer_id, - pointer_location.clone(), - dragged_over, - DragLeave { - button, - dragged: drag_end_target, - hit: hit.clone(), - }, - ); - commands.trigger_targets(event, target); - - let event = Pointer::new( - pointer_id, - pointer_location.clone(), - target, - DragDrop { - button, - dropped: target, - hit: hit.clone(), - }, + // Always send Out events + commands.trigger_targets( + Pointer::new(pointer_id, location.clone(), Out { hit: hit.clone() }), + hovered_entity, ); - commands.trigger_targets(event, target); - } - } - - // 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() { - let event = Pointer::new( - pointer_id, - pointer_location.clone(), - target, - DragLeave { - button, - dragged: *drag_target, - hit: hit.clone(), - }, - ); - commands.trigger_targets(event, target); - } } } } diff --git a/crates/bevy_picking/src/focus.rs b/crates/bevy_picking/src/focus.rs index 8ae93ce2befad..c5fd0b989c96d 100644 --- a/crates/bevy_picking/src/focus.rs +++ b/crates/bevy_picking/src/focus.rs @@ -1,11 +1,16 @@ //! Determines which entities are being hovered by which pointers. +//! +//! The most important type in this module is the [`HoverMap`], which maps pointers to the entities +//! they are hovering over. -use std::{collections::BTreeMap, fmt::Debug}; +use std::{ + collections::{BTreeMap, HashSet}, + fmt::Debug, +}; use crate::{ backend::{self, HitData}, - events::PointerCancel, - pointer::{PointerId, PointerInteraction, PointerPress}, + pointer::{PointerAction, PointerId, PointerInput, PointerInteraction, PointerPress}, Pickable, }; @@ -63,7 +68,7 @@ pub fn update_focus( pickable: Query<&Pickable>, pointers: Query<&PointerId>, mut under_pointer: EventReader, - mut cancellations: EventReader, + mut pointer_input: EventReader, // Local mut over_map: Local, // Output @@ -76,7 +81,7 @@ pub fn update_focus( &mut over_map, &pointers, ); - build_over_map(&mut under_pointer, &mut over_map, &mut cancellations); + build_over_map(&mut under_pointer, &mut over_map, &mut pointer_input); build_hover_map(&pointers, pickable, &over_map, &mut hover_map); } @@ -109,9 +114,18 @@ fn reset_maps( fn build_over_map( backend_events: &mut EventReader, pointer_over_map: &mut Local, - pointer_cancel: &mut EventReader, + pointer_input: &mut EventReader, ) { - let cancelled_pointers: Vec = pointer_cancel.read().map(|p| p.pointer_id).collect(); + let cancelled_pointers: HashSet = pointer_input + .read() + .filter_map(|p| { + if let PointerAction::Canceled = p.action { + Some(p.pointer_id) + } else { + None + } + }) + .collect(); for entities_under_pointer in backend_events .read() diff --git a/crates/bevy_picking/src/input.rs b/crates/bevy_picking/src/input.rs new file mode 100644 index 0000000000000..303c694bb4518 --- /dev/null +++ b/crates/bevy_picking/src/input.rs @@ -0,0 +1,267 @@ +//! This module provides unsurprising default inputs to `bevy_picking` through [`PointerInput`]. +//! The included systems are responsible for sending mouse and touch inputs to their +//! respective `Pointer`s. +//! +//! Because this has it's own plugin, 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`], and write a system +//! that updates its position. If you want this to work properly with the existing interaction events, +//! you need to be sure that you also write a [`PointerInput`] event stream. + +use bevy_app::prelude::*; +use bevy_ecs::prelude::*; +use bevy_hierarchy::DespawnRecursiveExt; +use bevy_input::touch::{TouchInput, TouchPhase}; +use bevy_input::{prelude::*, ButtonState}; +use bevy_math::Vec2; +use bevy_reflect::prelude::*; +use bevy_render::camera::RenderTarget; +use bevy_utils::{tracing::debug, HashMap, HashSet}; +use bevy_window::{PrimaryWindow, WindowEvent, WindowRef}; + +use crate::{ + pointer::{Location, PointerAction, PointerButton, PointerId, PointerInput, PressDirection}, + PointerBundle, +}; + +use crate::PickSet; + +/// Common imports for `bevy_picking`. +pub mod prelude { + pub use crate::input::PointerInputPlugin; +} + +/// 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::PickingPlugin::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. +/// +/// This plugin contains several settings, and is added to the world as a resource after initialization. +/// You can configure pointer input settings at runtime by accessing the resource. +#[derive(Copy, Clone, Resource, Debug, Reflect)] +#[reflect(Resource, Default)] +pub struct PointerInputPlugin { + /// Should touch inputs be updated? + pub is_touch_enabled: bool, + /// Should mouse inputs be updated? + pub is_mouse_enabled: bool, +} + +impl PointerInputPlugin { + fn is_mouse_enabled(state: Res) -> bool { + state.is_mouse_enabled + } + + fn is_touch_enabled(state: Res) -> bool { + state.is_touch_enabled + } +} + +impl Default for PointerInputPlugin { + fn default() -> Self { + Self { + is_touch_enabled: true, + is_mouse_enabled: true, + } + } +} + +impl Plugin for PointerInputPlugin { + fn build(&self, app: &mut App) { + app.insert_resource(*self) + .add_systems(Startup, spawn_mouse_pointer) + .add_systems( + First, + ( + mouse_pick_events.run_if(PointerInputPlugin::is_mouse_enabled), + touch_pick_events.run_if(PointerInputPlugin::is_touch_enabled), + ) + .chain() + .in_set(PickSet::Input), + ) + .add_systems( + Last, + deactivate_touch_pointers.run_if(PointerInputPlugin::is_touch_enabled), + ) + .register_type::() + .register_type::(); + } +} + +/// 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 + mut window_events: EventReader, + primary_window: Query>, + // Locals + mut cursor_last: Local, + // Output + mut pointer_events: EventWriter, +) { + for window_event in window_events.read() { + match window_event { + // Handle cursor movement events + WindowEvent::CursorMoved(event) => { + let location = Location { + target: match RenderTarget::Window(WindowRef::Entity(event.window)) + .normalize(primary_window.get_single().ok()) + { + Some(target) => target, + None => continue, + }, + position: event.position, + }; + pointer_events.send(PointerInput::new( + PointerId::Mouse, + location, + PointerAction::Moved { + delta: event.position - *cursor_last, + }, + )); + *cursor_last = event.position; + } + // Handle mouse button press events + WindowEvent::MouseButtonInput(input) => { + let location = Location { + target: match RenderTarget::Window(WindowRef::Entity(input.window)) + .normalize(primary_window.get_single().ok()) + { + Some(target) => target, + None => continue, + }, + position: *cursor_last, + }; + let button = match input.button { + MouseButton::Left => PointerButton::Primary, + MouseButton::Right => PointerButton::Secondary, + MouseButton::Middle => PointerButton::Middle, + MouseButton::Other(_) | MouseButton::Back | MouseButton::Forward => continue, + }; + let direction = match input.state { + ButtonState::Pressed => PressDirection::Down, + ButtonState::Released => PressDirection::Up, + }; + pointer_events.send(PointerInput::new( + PointerId::Mouse, + location, + PointerAction::Pressed { direction, button }, + )); + } + _ => {} + } + } +} + +/// Sends touch pointer events to be consumed by the core plugin +pub fn touch_pick_events( + // Input + mut window_events: EventReader, + primary_window: Query>, + // Locals + mut touch_cache: Local>, + // Output + mut commands: Commands, + mut pointer_events: EventWriter, +) { + for window_event in window_events.read() { + if let WindowEvent::TouchInput(touch) = window_event { + 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())); + + pointer_events.send(PointerInput::new( + pointer, + location, + PointerAction::Pressed { + direction: PressDirection::Down, + button: PointerButton::Primary, + }, + )); + + touch_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) = touch_cache.get(&touch.id) { + if last_touch == touch { + continue; + } + pointer_events.send(PointerInput::new( + pointer, + location, + PointerAction::Moved { + delta: touch.position - last_touch.position, + }, + )); + } + touch_cache.insert(touch.id, *touch); + } + TouchPhase::Ended => { + pointer_events.send(PointerInput::new( + pointer, + location, + PointerAction::Pressed { + direction: PressDirection::Up, + button: PointerButton::Primary, + }, + )); + touch_cache.remove(&touch.id); + } + TouchPhase::Canceled => { + pointer_events.send(PointerInput::new( + pointer, + location, + PointerAction::Canceled, + )); + touch_cache.remove(&touch.id); + } + } + } + } +} + +/// 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>, + pointers: Query<(Entity, &PointerId)>, + mut touches: EventReader, +) { + for touch in touches.read() { + if let TouchPhase::Ended | TouchPhase::Canceled = touch.phase { + 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(); + } +} diff --git a/crates/bevy_picking/src/input/mod.rs b/crates/bevy_picking/src/input/mod.rs deleted file mode 100644 index e3dd9e3695e6d..0000000000000 --- a/crates/bevy_picking/src/input/mod.rs +++ /dev/null @@ -1,86 +0,0 @@ -//! `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) -> bool { - state.is_mouse_enabled - } - - fn is_touch_enabled(state: Res) -> 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::() - .register_type::(); - } -} diff --git a/crates/bevy_picking/src/input/mouse.rs b/crates/bevy_picking/src/input/mouse.rs deleted file mode 100644 index 73cf321f61165..0000000000000 --- a/crates/bevy_picking/src/input/mouse.rs +++ /dev/null @@ -1,67 +0,0 @@ -//! 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>, - mut cursor_moves: EventReader, - mut cursor_last: Local, - mut mouse_inputs: EventReader, - // Output - mut pointer_move: EventWriter, - mut pointer_presses: EventWriter, -) { - 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)); - } - } - } -} diff --git a/crates/bevy_picking/src/input/touch.rs b/crates/bevy_picking/src/input/touch.rs deleted file mode 100644 index b6b7e6a33c85c..0000000000000 --- a/crates/bevy_picking/src/input/touch.rs +++ /dev/null @@ -1,105 +0,0 @@ -//! 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, - primary_window: Query>, - // Local - mut location_cache: Local>, - // Output - mut commands: Commands, - mut input_moves: EventWriter, - mut input_presses: EventWriter, - mut cancel_events: EventWriter, -) { - 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>, - pointers: Query<(Entity, &PointerId)>, - mut touches: EventReader, -) { - 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(); - } -} diff --git a/crates/bevy_picking/src/lib.rs b/crates/bevy_picking/src/lib.rs index cd75515c97422..3bb86458e866b 100644 --- a/crates/bevy_picking/src/lib.rs +++ b/crates/bevy_picking/src/lib.rs @@ -1,4 +1,152 @@ -//! TODO, write module doc +//! This crate provides 'picking' capabilities for the Bevy game engine. That means, in simple terms, figuring out +//! how to connect up a user's clicks or taps to the entities they are trying to interact with. +//! +//! ## Overview +//! +//! In the simplest case, this plugin allows you to click on things in the scene. However, it also +//! allows you to express more complex interactions, like detecting when a touch input drags a UI +//! element and drops it on a 3d mesh rendered to a different camera. The crate also provides a set of +//! interaction callbacks, allowing you to receive input directly on entities like here: +//! +//! ```rust +//! # use bevy_ecs::prelude::*; +//! # use bevy_picking::prelude::*; +//! # #[derive(Component)] +//! # struct MyComponent; +//! # let mut world = World::new(); +//! world.spawn(MyComponent) +//! .observe(|mut trigger: Trigger>| { +//! // Get the underlying event type +//! let click_event: &Pointer = trigger.event(); +//! // Stop the event from bubbling up the entity hierarchjy +//! trigger.propagate(false); +//! }); +//! ``` +//! +//! At its core, this crate provides a robust abstraction for computing picking state regardless of +//! pointing devices, or what you are hit testing against. It is designed to work with any input, including +//! mouse, touch, pens, or virtual pointers controlled by gamepads. +//! +//! ## Expressive Events +//! +//! The events in this module (see [`events`]) cannot be listened to with normal `EventReader`s. +//! Instead, they are dispatched to *ovservers* attached to specific entities. When events are generated, they +//! bubble up the entity hierarchy starting from their target, until they reach the root or bubbling is haulted +//! with a call to [`Trigger::propagate`](bevy_ecs::observer::Trigger::propagate). +//! See [`Observer`] for details. +//! +//! This allows you to run callbacks when any children of an entity are interacted with, and leads +//! to succinct, expressive code: +//! +//! ``` +//! # use bevy_ecs::prelude::*; +//! # use bevy_transform::prelude::*; +//! # use bevy_picking::prelude::*; +//! # #[derive(Event)] +//! # struct Greeting; +//! fn setup(mut commands: Commands) { +//! commands.spawn(Transform::default()) +//! // Spawn your entity here, e.g. a Mesh. +//! // When dragged, mutate the `Transform` component on the dragged target entity: +//! .observe(|trigger: Trigger>, mut transforms: Query<&mut Transform>| { +//! let mut transform = transforms.get_mut(trigger.entity()).unwrap(); +//! let drag = trigger.event(); +//! transform.rotate_local_y(drag.delta.x / 50.0); +//! }) +//! .observe(|trigger: Trigger>, mut commands: Commands| { +//! println!("Entity {:?} goes BOOM!", trigger.entity()); +//! commands.entity(trigger.entity()).despawn(); +//! }) +//! .observe(|trigger: Trigger>, mut events: EventWriter| { +//! events.send(Greeting); +//! }); +//! } +//! ``` +//! +//! ## Modularity +//! +//! #### Mix and Match Hit Testing Backends +//! +//! The plugin attempts to handle all the hard parts for you, all you need to do is tell it when a +//! pointer is hitting any entities. Multiple backends can be used at the same time! [Use this +//! simple API to write your own backend](crate::backend) in about 100 lines of code. +//! +//! #### Input Agnostic +//! +//! Picking provides a generic Pointer abstracton, which is useful for reacting to many different +//! types of input devices. Pointers can be controlled with anything, whether its the included mouse +//! or touch inputs, or a custom gamepad input system you write yourself to control a virtual pointer. +//! +//! ## Robustness +//! +//! In addition to these features, this plugin also correctly handles multitouch, multiple windows, +//! multiple cameras, viewports, and render layers. Using this as a library allows you to write a +//! picking backend that can interoperate with any other picking backend. +//! +//! # Getting Started +//! +//! TODO: This section will need to be re-written once more backends are introduced. +//! +//! #### Next Steps +//! +//! To learn more, take a look at the examples in the +//! [examples](https://github.com/bevyengine/bevy/tree/main/examples/picking). You +//! can read the next section to understand how the plugin works. +//! +//! # The Picking Pipeline +//! +//! This plugin is designed to be extremely modular. To do so, it works in well-defined stages that +//! form a pipeline, where events are used to pass data between each stage. +//! +//! #### Pointers ([`pointer`](mod@pointer)) +//! +//! The first stage of the pipeline is to gather inputs and update pointers. This stage is +//! ultimately responsible for generating [`PointerInput`](pointer::PointerInput) events. The provided +//! crate does this automatically for mouse, touch, and pen inputs. If you wanted to implement your own +//! pointer, controlled by some other input, you can do that here. The ordering of events within the +//! [`PointerInput`](pointer::PointerInput) stream is meaningful for events with the same +//! [`PointerId`](pointer::PointerId), but not between different pointers. +//! +//! Because pointer positions and presses are driven by these events, you can use them to mock +//! inputs for testing. +//! +//! After inputs are generated, they are then collected to update the current +//! [`PointerLocation`](pointer::PointerLocation) for each pointer. +//! +//! #### Backend ([`backend`]) +//! +//! A picking backend only has one job: reading [`PointerLocation`](pointer::PointerLocation) components, +//! and producing [`PointerHits`](backend::PointerHits). You can find all documentation and types needed to +//! implement a backend at [`backend`]. +//! +//! You will eventually need to choose which picking backend(s) you want to use. This crate does not +//! supply any backends, and expects you to select some from the other bevy crates or the third-party +//! ecosystem. You can find all the provided backends in the [`backend`] module. +//! +//! It's important to understand that you can mix and match backends! For example, you might have a +//! backend for your UI, and one for the 3d scene, with each being specialized for their purpose. +//! This crate provides some backends out of the box, but you can even write your own. It's been +//! made as easy as possible intentionally; the `bevy_mod_raycast` backend is 50 lines of code. +//! +//! #### Focus ([`focus`]) +//! +//! The next step is to use the data from the backends, combine and sort the results, and determine +//! what each cursor is hovering over, producing a [`HoverMap`](`crate::focus::HoverMap`). Note that +//! just because a pointer is over an entity, it is not necessarily *hovering* that entity. Although +//! multiple backends may be reporting that a pointer is hitting an entity, the focus system needs +//! to determine which entities are actually being hovered by this pointer based on the pick depth, +//! order of the backend, and the [`Pickable`] state of the entity. In other words, if one entity is +//! in front of another, usually only the topmost one will be hovered. +//! +//! #### Events ([`events`]) +//! +//! In the final step, the high-level pointer events are generated, such as events that trigger when +//! a pointer hovers or clicks an entity. These simple events are then used to generate more complex +//! events for dragging and dropping. +//! +//! Because it is completely agnostic to the the earlier stages of the pipeline, you can easily +//! extend the plugin with arbitrary backends and input methods, yet still use all the high level +//! features. #![deny(missing_docs)] @@ -16,45 +164,11 @@ use bevy_reflect::prelude::*; pub mod prelude { #[doc(hidden)] pub use crate::{ - events::*, input::InputPlugin, pointer::PointerButton, DefaultPickingPlugins, - InteractionPlugin, Pickable, PickingPlugin, PickingPluginsSettings, + events::*, input::PointerInputPlugin, pointer::PointerButton, DefaultPickingPlugins, + InteractionPlugin, Pickable, PickingPlugin, }; } -/// Used to globally toggle picking features at runtime. -#[derive(Clone, Debug, Resource, Reflect)] -#[reflect(Resource, Default)] -pub struct PickingPluginsSettings { - /// Enables and disables all picking features. - pub is_enabled: bool, - /// Enables and disables input collection. - pub is_input_enabled: bool, - /// Enables and disables updating interaction states of entities. - pub is_focus_enabled: bool, -} - -impl PickingPluginsSettings { - /// Whether or not input collection systems should be running. - pub fn input_should_run(state: Res) -> bool { - state.is_input_enabled && state.is_enabled - } - /// Whether or not systems updating entities' [`PickingInteraction`](focus::PickingInteraction) - /// component should be running. - pub fn focus_should_run(state: Res) -> bool { - state.is_focus_enabled && state.is_enabled - } -} - -impl Default for PickingPluginsSettings { - fn default() -> Self { - Self { - is_enabled: true, - is_input_enabled: true, - is_focus_enabled: true, - } - } -} - /// An optional component that overrides default picking behavior for an entity, allowing you to /// make an entity non-hoverable, or allow items below it to be hovered. See the documentation on /// the fields for more details. @@ -176,8 +290,9 @@ pub enum PickSet { Last, } -/// One plugin that contains the [`input::InputPlugin`], [`PickingPlugin`] and the [`InteractionPlugin`], -/// this is probably the plugin that will be most used. +/// One plugin that contains the [`PointerInputPlugin`](input::PointerInputPlugin), [`PickingPlugin`] +/// and the [`InteractionPlugin`], this is probably the plugin that will be most used. +/// /// Note: for any of these plugins to work, they require a picking backend to be active, /// The picking backend is responsible to turn an input, into a [`crate::backend::PointerHits`] /// that [`PickingPlugin`] and [`InteractionPlugin`] will refine into [`bevy_ecs::observer::Trigger`]s. @@ -187,8 +302,8 @@ pub struct DefaultPickingPlugins; impl Plugin for DefaultPickingPlugins { fn build(&self, app: &mut App) { app.add_plugins(( - input::InputPlugin::default(), - PickingPlugin, + input::PointerInputPlugin::default(), + PickingPlugin::default(), InteractionPlugin, )); } @@ -196,16 +311,48 @@ impl Plugin for DefaultPickingPlugins { /// This plugin sets up the core picking infrastructure. It receives input events, and provides the shared /// types used by other picking plugins. -#[derive(Default)] -pub struct PickingPlugin; +/// +/// This plugin contains several settings, and is added to the wrold as a resource after initialization. You +/// can configure picking settings at runtime through the resource. +#[derive(Copy, Clone, Debug, Resource, Reflect)] +#[reflect(Resource, Default)] +pub struct PickingPlugin { + /// Enables and disables all picking features. + pub is_enabled: bool, + /// Enables and disables input collection. + pub is_input_enabled: bool, + /// Enables and disables updating interaction states of entities. + pub is_focus_enabled: bool, +} + +impl PickingPlugin { + /// Whether or not input collection systems should be running. + pub fn input_should_run(state: Res) -> bool { + state.is_input_enabled && state.is_enabled + } + /// Whether or not systems updating entities' [`PickingInteraction`](focus::PickingInteraction) + /// component should be running. + pub fn focus_should_run(state: Res) -> bool { + state.is_focus_enabled && state.is_enabled + } +} + +impl Default for PickingPlugin { + fn default() -> Self { + Self { + is_enabled: true, + is_input_enabled: true, + is_focus_enabled: true, + } + } +} impl Plugin for PickingPlugin { fn build(&self, app: &mut App) { - app.init_resource::() + app.insert_resource(*self) .init_resource::() .init_resource::() - .add_event::() - .add_event::() + .add_event::() .add_event::() // Rather than try to mark all current and future backends as ambiguous with each other, // we allow them to send their hits in any order. These are later sorted, so submission @@ -215,9 +362,8 @@ impl Plugin for PickingPlugin { PreUpdate, ( pointer::update_pointer_map, - pointer::InputMove::receive, - pointer::InputPress::receive, - backend::ray::RayMap::repopulate.after(pointer::InputMove::receive), + pointer::PointerInput::receive, + backend::ray::RayMap::repopulate.after(pointer::PointerInput::receive), ) .in_set(PickSet::ProcessInput), ) @@ -232,22 +378,21 @@ impl Plugin for PickingPlugin { .configure_sets( PreUpdate, ( - PickSet::ProcessInput.run_if(PickingPluginsSettings::input_should_run), + PickSet::ProcessInput.run_if(Self::input_should_run), PickSet::Backend, - PickSet::Focus.run_if(PickingPluginsSettings::focus_should_run), + PickSet::Focus.run_if(Self::focus_should_run), PickSet::PostFocus, - // Eventually events will need to be dispatched here PickSet::Last, ) .ambiguous_with(bevy_asset::handle_internal_asset_events) .chain(), ) + .register_type::() + .register_type::() .register_type::() .register_type::() .register_type::() .register_type::() - .register_type::() - .register_type::() .register_type::(); } } @@ -263,23 +408,9 @@ impl Plugin for InteractionPlugin { app.init_resource::() .init_resource::() - .init_resource::() - .add_event::() - .add_event::>() - .add_event::>() - .add_event::>() - .add_event::>() - .add_event::>() - .add_event::>() .add_systems( PreUpdate, - ( - update_focus, - pointer_events, - update_interactions, - send_click_and_drag_events, - send_drag_over_events, - ) + (update_focus, pointer_events, update_interactions) .chain() .in_set(PickSet::Focus), ); diff --git a/crates/bevy_picking/src/pointer.rs b/crates/bevy_picking/src/pointer.rs index 3d1991c1ec246..01c292bc1dec7 100644 --- a/crates/bevy_picking/src/pointer.rs +++ b/crates/bevy_picking/src/pointer.rs @@ -1,4 +1,12 @@ //! Types and systems for pointer inputs, such as position and buttons. +//! +//! The picking system is built around the concept of a 'Pointer', which is an +//! abstract representation of a user input with a specific screen location. The cursor +//! and touch input is provided under [`crate::input`], but you can also implement +//! your own custom pointers by supplying a unique ID. +//! +//! The purpose of this module is primarily to provide a common interface that can be +//! driven by lower-level input devices and consumed by higher-level interaction systems. use bevy_ecs::prelude::*; use bevy_math::{Rect, Vec2}; @@ -83,7 +91,7 @@ pub fn update_pointer_map(pointers: Query<(Entity, &PointerId)>, mut map: ResMut } } -/// Tracks the state of the pointer's buttons in response to [`InputPress`]s. +/// Tracks the state of the pointer's buttons in response to [`PointerInput`] events. #[derive(Debug, Default, Clone, Component, Reflect, PartialEq, Eq)] #[reflect(Component, Default)] pub struct PointerPress { @@ -118,68 +126,6 @@ impl PointerPress { } } -/// Pointer input event for button presses. Fires when a pointer button changes state. -#[derive(Event, Debug, Clone, Copy, PartialEq, Eq, Reflect)] -pub struct InputPress { - /// The [`PointerId`] of the pointer that pressed a button. - pub pointer_id: PointerId, - /// Direction of the button press. - pub direction: PressDirection, - /// Identifies the pointer button changing in this event. - pub button: PointerButton, -} - -impl InputPress { - /// Create a new pointer button down event. - pub fn new_down(id: PointerId, button: PointerButton) -> InputPress { - Self { - pointer_id: id, - direction: PressDirection::Down, - button, - } - } - - /// Create a new pointer button up event. - pub fn new_up(id: PointerId, button: PointerButton) -> InputPress { - Self { - pointer_id: id, - direction: PressDirection::Up, - button, - } - } - - /// Returns true if the `button` of this pointer was just pressed. - #[inline] - pub fn is_just_down(&self, button: PointerButton) -> bool { - self.button == button && self.direction == PressDirection::Down - } - - /// Returns true if the `button` of this pointer was just released. - #[inline] - pub fn is_just_up(&self, button: PointerButton) -> bool { - self.button == button && self.direction == PressDirection::Up - } - - /// Receives [`InputPress`] events and updates corresponding [`PointerPress`] components. - pub fn receive( - mut events: EventReader, - mut pointers: Query<(&PointerId, &mut PointerPress)>, - ) { - for input_press_event in events.read() { - pointers.iter_mut().for_each(|(pointer_id, mut pointer)| { - if *pointer_id == input_press_event.pointer_id { - let is_down = input_press_event.direction == PressDirection::Down; - match input_press_event.button { - PointerButton::Primary => pointer.primary = is_down, - PointerButton::Secondary => pointer.secondary = is_down, - PointerButton::Middle => pointer.middle = is_down, - } - } - }); - } - } -} - /// The stage of the pointer button press event #[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect)] pub enum PressDirection { @@ -225,42 +171,6 @@ impl PointerLocation { } } -/// Pointer input event for pointer moves. Fires when a pointer changes location. -#[derive(Event, Debug, Clone, Reflect)] -pub struct InputMove { - /// The [`PointerId`] of the pointer that is moving. - pub pointer_id: PointerId, - /// The [`Location`] of the pointer. - pub location: Location, - /// The distance moved (change in `position`) since the last event. - pub delta: Vec2, -} - -impl InputMove { - /// Create a new [`InputMove`] event. - pub fn new(id: PointerId, location: Location, delta: Vec2) -> InputMove { - Self { - pointer_id: id, - location, - delta, - } - } - - /// Receives [`InputMove`] events and updates corresponding [`PointerLocation`] components. - pub fn receive( - mut events: EventReader, - mut pointers: Query<(&PointerId, &mut PointerLocation)>, - ) { - for event_pointer in events.read() { - pointers.iter_mut().for_each(|(id, mut pointer)| { - if *id == event_pointer.pointer_id { - pointer.location = Some(event_pointer.location.to_owned()); - } - }); - } - } -} - /// The location of a pointer, including the current [`NormalizedRenderTarget`], and the x/y /// position of the pointer on this render target. /// @@ -309,3 +219,79 @@ impl Location { .unwrap_or(false) } } + +/// Types of actions that can be taken by pointers. +#[derive(Debug, Clone, Copy, Reflect)] +pub enum PointerAction { + /// A button has been pressed on the pointer. + Pressed { + /// The press direction, either down or up. + direction: PressDirection, + /// The button that was pressed. + button: PointerButton, + }, + /// The pointer has moved. + Moved { + /// How much the pointer moved from the previous position. + delta: Vec2, + }, + /// The pointer has been canceled. The OS can cause this to happen to touch events. + Canceled, +} + +/// An input event effecting a pointer. +#[derive(Event, Debug, Clone, Reflect)] +pub struct PointerInput { + /// The id of the pointer. + pub pointer_id: PointerId, + /// The location of the pointer. For [[`PointerAction::Moved`]], this is the location after the movement. + pub location: Location, + /// The action that the event describes. + pub action: PointerAction, +} + +impl PointerInput { + /// Creates a new pointer input event. + /// + /// Note that `location` refers to the position of the pointer *after* the event occurred. + pub fn new(pointer_id: PointerId, location: Location, action: PointerAction) -> PointerInput { + PointerInput { + pointer_id, + location, + action, + } + } + + /// Updates pointer entities according to the input events. + pub fn receive( + mut events: EventReader, + mut pointers: Query<(&PointerId, &mut PointerLocation, &mut PointerPress)>, + ) { + for event in events.read() { + match event.action { + PointerAction::Pressed { direction, button } => { + pointers + .iter_mut() + .for_each(|(pointer_id, _, mut pointer)| { + if *pointer_id == event.pointer_id { + let is_down = direction == PressDirection::Down; + match button { + PointerButton::Primary => pointer.primary = is_down, + PointerButton::Secondary => pointer.secondary = is_down, + PointerButton::Middle => pointer.middle = is_down, + } + } + }); + } + PointerAction::Moved { .. } => { + pointers.iter_mut().for_each(|(id, mut pointer, _)| { + if *id == event.pointer_id { + pointer.location = Some(event.location.to_owned()); + } + }); + } + _ => {} + } + } + } +} diff --git a/crates/bevy_window/Cargo.toml b/crates/bevy_window/Cargo.toml index 6159c6ec94698..8c922a9c3d10c 100644 --- a/crates/bevy_window/Cargo.toml +++ b/crates/bevy_window/Cargo.toml @@ -16,6 +16,7 @@ serialize = ["serde", "smol_str/serde", "bevy_ecs/serialize"] bevy_a11y = { path = "../bevy_a11y", version = "0.15.0-dev" } bevy_app = { path = "../bevy_app", version = "0.15.0-dev" } bevy_ecs = { path = "../bevy_ecs", 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", features = [ "glam", diff --git a/crates/bevy_window/src/event.rs b/crates/bevy_window/src/event.rs index 682ccde3bd80f..caf97be6f41e6 100644 --- a/crates/bevy_window/src/event.rs +++ b/crates/bevy_window/src/event.rs @@ -1,8 +1,13 @@ #![allow(deprecated)] use std::path::PathBuf; -use bevy_ecs::entity::Entity; -use bevy_ecs::event::Event; +use bevy_ecs::{entity::Entity, event::Event}; +use bevy_input::{ + gestures::*, + keyboard::{KeyboardFocusLost, KeyboardInput}, + mouse::{MouseButtonInput, MouseMotion, MouseWheel}, + touch::TouchInput, +}; use bevy_math::{IVec2, Vec2}; use bevy_reflect::Reflect; use smol_str::SmolStr; @@ -413,3 +418,193 @@ impl AppLifecycle { } } } + +/// Wraps all `bevy_window` and `bevy_input` events in a common enum. +/// +/// Read these events with `EventReader` if you need to +/// access window events in the order they were received from the +/// operating system. Otherwise, the event types are individually +/// readable with `EventReader` (e.g. `EventReader`). +#[derive(Event, Debug, Clone, PartialEq, Reflect)] +#[reflect(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + reflect(Serialize, Deserialize) +)] +#[allow(missing_docs)] +pub enum WindowEvent { + AppLifecycle(AppLifecycle), + CursorEntered(CursorEntered), + CursorLeft(CursorLeft), + CursorMoved(CursorMoved), + FileDragAndDrop(FileDragAndDrop), + Ime(Ime), + ReceivedCharacter(ReceivedCharacter), + RequestRedraw(RequestRedraw), + WindowBackendScaleFactorChanged(WindowBackendScaleFactorChanged), + WindowCloseRequested(WindowCloseRequested), + WindowCreated(WindowCreated), + WindowDestroyed(WindowDestroyed), + WindowFocused(WindowFocused), + WindowMoved(WindowMoved), + WindowOccluded(WindowOccluded), + WindowResized(WindowResized), + WindowScaleFactorChanged(WindowScaleFactorChanged), + WindowThemeChanged(WindowThemeChanged), + + MouseButtonInput(MouseButtonInput), + MouseMotion(MouseMotion), + MouseWheel(MouseWheel), + + PinchGesture(PinchGesture), + RotationGesture(RotationGesture), + DoubleTapGesture(DoubleTapGesture), + PanGesture(PanGesture), + + TouchInput(TouchInput), + + KeyboardInput(KeyboardInput), + KeyboardFocusLost(KeyboardFocusLost), +} + +impl From for WindowEvent { + fn from(e: AppLifecycle) -> Self { + Self::AppLifecycle(e) + } +} +impl From for WindowEvent { + fn from(e: CursorEntered) -> Self { + Self::CursorEntered(e) + } +} +impl From for WindowEvent { + fn from(e: CursorLeft) -> Self { + Self::CursorLeft(e) + } +} +impl From for WindowEvent { + fn from(e: CursorMoved) -> Self { + Self::CursorMoved(e) + } +} +impl From for WindowEvent { + fn from(e: FileDragAndDrop) -> Self { + Self::FileDragAndDrop(e) + } +} +impl From for WindowEvent { + fn from(e: Ime) -> Self { + Self::Ime(e) + } +} +impl From for WindowEvent { + fn from(e: ReceivedCharacter) -> Self { + Self::ReceivedCharacter(e) + } +} +impl From for WindowEvent { + fn from(e: RequestRedraw) -> Self { + Self::RequestRedraw(e) + } +} +impl From for WindowEvent { + fn from(e: WindowBackendScaleFactorChanged) -> Self { + Self::WindowBackendScaleFactorChanged(e) + } +} +impl From for WindowEvent { + fn from(e: WindowCloseRequested) -> Self { + Self::WindowCloseRequested(e) + } +} +impl From for WindowEvent { + fn from(e: WindowCreated) -> Self { + Self::WindowCreated(e) + } +} +impl From for WindowEvent { + fn from(e: WindowDestroyed) -> Self { + Self::WindowDestroyed(e) + } +} +impl From for WindowEvent { + fn from(e: WindowFocused) -> Self { + Self::WindowFocused(e) + } +} +impl From for WindowEvent { + fn from(e: WindowMoved) -> Self { + Self::WindowMoved(e) + } +} +impl From for WindowEvent { + fn from(e: WindowOccluded) -> Self { + Self::WindowOccluded(e) + } +} +impl From for WindowEvent { + fn from(e: WindowResized) -> Self { + Self::WindowResized(e) + } +} +impl From for WindowEvent { + fn from(e: WindowScaleFactorChanged) -> Self { + Self::WindowScaleFactorChanged(e) + } +} +impl From for WindowEvent { + fn from(e: WindowThemeChanged) -> Self { + Self::WindowThemeChanged(e) + } +} +impl From for WindowEvent { + fn from(e: MouseButtonInput) -> Self { + Self::MouseButtonInput(e) + } +} +impl From for WindowEvent { + fn from(e: MouseMotion) -> Self { + Self::MouseMotion(e) + } +} +impl From for WindowEvent { + fn from(e: MouseWheel) -> Self { + Self::MouseWheel(e) + } +} +impl From for WindowEvent { + fn from(e: PinchGesture) -> Self { + Self::PinchGesture(e) + } +} +impl From for WindowEvent { + fn from(e: RotationGesture) -> Self { + Self::RotationGesture(e) + } +} +impl From for WindowEvent { + fn from(e: DoubleTapGesture) -> Self { + Self::DoubleTapGesture(e) + } +} +impl From for WindowEvent { + fn from(e: PanGesture) -> Self { + Self::PanGesture(e) + } +} +impl From for WindowEvent { + fn from(e: TouchInput) -> Self { + Self::TouchInput(e) + } +} +impl From for WindowEvent { + fn from(e: KeyboardInput) -> Self { + Self::KeyboardInput(e) + } +} +impl From for WindowEvent { + fn from(e: KeyboardFocusLost) -> Self { + Self::KeyboardFocusLost(e) + } +} diff --git a/crates/bevy_window/src/lib.rs b/crates/bevy_window/src/lib.rs index f27af26699424..fb0db3a27dd0d 100644 --- a/crates/bevy_window/src/lib.rs +++ b/crates/bevy_window/src/lib.rs @@ -91,7 +91,8 @@ impl Plugin for WindowPlugin { fn build(&self, app: &mut App) { // User convenience events #[allow(deprecated)] - app.add_event::() + app.add_event::() + .add_event::() .add_event::() .add_event::() .add_event::() @@ -143,7 +144,8 @@ impl Plugin for WindowPlugin { // Register event types #[allow(deprecated)] - app.register_type::() + app.register_type::() + .register_type::() .register_type::() .register_type::() .register_type::() diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 4901790ac7915..b70720deceb0c 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -13,7 +13,7 @@ //! See `winit_runner` for details. use bevy_derive::Deref; -use bevy_window::RawHandleWrapperHolder; +use bevy_window::{RawHandleWrapperHolder, WindowEvent}; use std::marker::PhantomData; use winit::event_loop::EventLoop; #[cfg(target_os = "android")] @@ -33,7 +33,6 @@ pub use winit::event_loop::EventLoopProxy; pub use winit::platform::web::CustomCursorExtWebSys; pub use winit::window::{CustomCursor as WinitCustomCursor, CustomCursorSource}; pub use winit_config::*; -pub use winit_event::*; pub use winit_windows::*; use crate::accessibility::{AccessKitAdapters, AccessKitPlugin, WinitActionRequestHandlers}; @@ -45,7 +44,6 @@ mod converters; mod state; mod system; mod winit_config; -pub mod winit_event; mod winit_monitors; mod winit_windows; @@ -122,7 +120,6 @@ impl Plugin for WinitPlugin { app.init_non_send_resource::() .init_resource::() .init_resource::() - .add_event::() .set_runner(winit_runner::) .add_systems( Last, @@ -162,12 +159,12 @@ pub struct WakeUp; pub struct EventLoopProxyWrapper(EventLoopProxy); trait AppSendEvent { - fn send(&mut self, event: impl Into); + fn send(&mut self, event: impl Into); } -impl AppSendEvent for Vec { - fn send(&mut self, event: impl Into) { - self.push(Into::::into(event)); +impl AppSendEvent for Vec { + fn send(&mut self, event: impl Into) { + self.push(Into::::into(event)); } } diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index 871a559b060a8..f515513a325b6 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -28,8 +28,8 @@ use winit::window::WindowId; use bevy_window::{ AppLifecycle, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime, ReceivedCharacter, RequestRedraw, Window, WindowBackendScaleFactorChanged, WindowCloseRequested, WindowDestroyed, - WindowFocused, WindowMoved, WindowOccluded, WindowResized, WindowScaleFactorChanged, - WindowThemeChanged, + WindowEvent as BevyWindowEvent, WindowFocused, WindowMoved, WindowOccluded, WindowResized, + WindowScaleFactorChanged, WindowThemeChanged, }; #[cfg(target_os = "android")] use bevy_window::{PrimaryWindow, RawHandleWrapper}; @@ -38,7 +38,7 @@ use crate::accessibility::AccessKitAdapters; use crate::system::{create_monitors, CachedWindow}; use crate::{ converters, create_windows, AppSendEvent, CreateMonitorParams, CreateWindowParams, - EventLoopProxyWrapper, UpdateMode, WinitEvent, WinitSettings, WinitWindows, + EventLoopProxyWrapper, UpdateMode, WinitSettings, WinitWindows, }; /// Persistent state that is used to run the [`App`] according to the current @@ -69,8 +69,8 @@ struct WinitAppRunnerState { lifecycle: AppLifecycle, /// The previous app lifecycle state. previous_lifecycle: AppLifecycle, - /// Winit events to send - winit_events: Vec, + /// Bevy window events to send + bevy_window_events: Vec, _marker: PhantomData, event_writer_system_state: SystemState<( @@ -110,7 +110,7 @@ impl WinitAppRunnerState { wait_elapsed: false, // 3 seems to be enough, 5 is a safe margin startup_forced_updates: 5, - winit_events: Vec::new(), + bevy_window_events: Vec::new(), _marker: PhantomData, event_writer_system_state, } @@ -258,7 +258,9 @@ impl ApplicationHandler for WinitAppRunnerState { &mut window_scale_factor_changed, ); } - WindowEvent::CloseRequested => self.winit_events.send(WindowCloseRequested { window }), + WindowEvent::CloseRequested => self + .bevy_window_events + .send(WindowCloseRequested { window }), WindowEvent::KeyboardInput { ref event, is_synthetic, @@ -274,10 +276,11 @@ impl ApplicationHandler for WinitAppRunnerState { if let Some(char) = &event.text { let char = char.clone(); #[allow(deprecated)] - self.winit_events.send(ReceivedCharacter { window, char }); + self.bevy_window_events + .send(ReceivedCharacter { window, char }); } } - self.winit_events + self.bevy_window_events .send(converters::convert_keyboard_input(event, window)); } } @@ -291,44 +294,44 @@ impl ApplicationHandler for WinitAppRunnerState { win.set_physical_cursor_position(Some(physical_position)); let position = (physical_position / win.resolution.scale_factor() as f64).as_vec2(); - self.winit_events.send(CursorMoved { + self.bevy_window_events.send(CursorMoved { window, position, delta, }); } WindowEvent::CursorEntered { .. } => { - self.winit_events.send(CursorEntered { window }); + self.bevy_window_events.send(CursorEntered { window }); } WindowEvent::CursorLeft { .. } => { win.set_physical_cursor_position(None); - self.winit_events.send(CursorLeft { window }); + self.bevy_window_events.send(CursorLeft { window }); } WindowEvent::MouseInput { state, button, .. } => { - self.winit_events.send(MouseButtonInput { + self.bevy_window_events.send(MouseButtonInput { button: converters::convert_mouse_button(button), state: converters::convert_element_state(state), window, }); } WindowEvent::PinchGesture { delta, .. } => { - self.winit_events.send(PinchGesture(delta as f32)); + self.bevy_window_events.send(PinchGesture(delta as f32)); } WindowEvent::RotationGesture { delta, .. } => { - self.winit_events.send(RotationGesture(delta)); + self.bevy_window_events.send(RotationGesture(delta)); } WindowEvent::DoubleTapGesture { .. } => { - self.winit_events.send(DoubleTapGesture); + self.bevy_window_events.send(DoubleTapGesture); } WindowEvent::PanGesture { delta, .. } => { - self.winit_events.send(PanGesture(Vec2 { + self.bevy_window_events.send(PanGesture(Vec2 { x: delta.x, y: delta.y, })); } WindowEvent::MouseWheel { delta, .. } => match delta { event::MouseScrollDelta::LineDelta(x, y) => { - self.winit_events.send(MouseWheel { + self.bevy_window_events.send(MouseWheel { unit: MouseScrollUnit::Line, x, y, @@ -336,7 +339,7 @@ impl ApplicationHandler for WinitAppRunnerState { }); } event::MouseScrollDelta::PixelDelta(p) => { - self.winit_events.send(MouseWheel { + self.bevy_window_events.send(MouseWheel { unit: MouseScrollUnit::Pixel, x: p.x as f32, y: p.y as f32, @@ -348,62 +351,65 @@ impl ApplicationHandler for WinitAppRunnerState { let location = touch .location .to_logical(win.resolution.scale_factor() as f64); - self.winit_events + self.bevy_window_events .send(converters::convert_touch_input(touch, location, window)); } WindowEvent::Focused(focused) => { win.focused = focused; - self.winit_events.send(WindowFocused { window, focused }); + self.bevy_window_events + .send(WindowFocused { window, focused }); if !focused { - self.winit_events.send(KeyboardFocusLost); + self.bevy_window_events.send(KeyboardFocusLost); } } WindowEvent::Occluded(occluded) => { - self.winit_events.send(WindowOccluded { window, occluded }); + self.bevy_window_events + .send(WindowOccluded { window, occluded }); } WindowEvent::DroppedFile(path_buf) => { - self.winit_events + self.bevy_window_events .send(FileDragAndDrop::DroppedFile { window, path_buf }); } WindowEvent::HoveredFile(path_buf) => { - self.winit_events + self.bevy_window_events .send(FileDragAndDrop::HoveredFile { window, path_buf }); } WindowEvent::HoveredFileCancelled => { - self.winit_events + self.bevy_window_events .send(FileDragAndDrop::HoveredFileCanceled { window }); } WindowEvent::Moved(position) => { let position = ivec2(position.x, position.y); win.position.set(position); - self.winit_events.send(WindowMoved { window, position }); + self.bevy_window_events + .send(WindowMoved { window, position }); } WindowEvent::Ime(event) => match event { event::Ime::Preedit(value, cursor) => { - self.winit_events.send(Ime::Preedit { + self.bevy_window_events.send(Ime::Preedit { window, value, cursor, }); } event::Ime::Commit(value) => { - self.winit_events.send(Ime::Commit { window, value }); + self.bevy_window_events.send(Ime::Commit { window, value }); } event::Ime::Enabled => { - self.winit_events.send(Ime::Enabled { window }); + self.bevy_window_events.send(Ime::Enabled { window }); } event::Ime::Disabled => { - self.winit_events.send(Ime::Disabled { window }); + self.bevy_window_events.send(Ime::Disabled { window }); } }, WindowEvent::ThemeChanged(theme) => { - self.winit_events.send(WindowThemeChanged { + self.bevy_window_events.send(WindowThemeChanged { window, theme: converters::convert_winit_theme(theme), }); } WindowEvent::Destroyed => { - self.winit_events.send(WindowDestroyed { window }); + self.bevy_window_events.send(WindowDestroyed { window }); } WindowEvent::RedrawRequested => { self.ran_update_since_last_redraw = false; @@ -429,7 +435,7 @@ impl ApplicationHandler for WinitAppRunnerState { if let DeviceEvent::MouseMotion { delta: (x, y) } = event { let delta = Vec2::new(x as f32, y as f32); - self.winit_events.send(MouseMotion { delta }); + self.bevy_window_events.send(MouseMotion { delta }); } } @@ -534,7 +540,7 @@ impl ApplicationHandler for WinitAppRunnerState { // Notifies a lifecycle change if self.lifecycle != self.previous_lifecycle { self.previous_lifecycle = self.lifecycle; - self.winit_events.send(self.lifecycle); + self.bevy_window_events.send(self.lifecycle); } // This is recorded before running app.update(), to run the next cycle after a correct timeout. @@ -674,15 +680,15 @@ impl WinitAppRunnerState { fn run_app_update(&mut self) { self.reset_on_update(); - self.forward_winit_events(); + self.forward_bevy_events(); if self.app.plugins_state() == PluginsState::Cleaned { self.app.update(); } } - fn forward_winit_events(&mut self) { - let buffered_events = self.winit_events.drain(..).collect::>(); + fn forward_bevy_events(&mut self) { + let buffered_events = self.bevy_window_events.drain(..).collect::>(); if buffered_events.is_empty() { return; @@ -692,95 +698,95 @@ impl WinitAppRunnerState { for winit_event in buffered_events.iter() { match winit_event.clone() { - WinitEvent::AppLifecycle(e) => { + BevyWindowEvent::AppLifecycle(e) => { world.send_event(e); } - WinitEvent::CursorEntered(e) => { + BevyWindowEvent::CursorEntered(e) => { world.send_event(e); } - WinitEvent::CursorLeft(e) => { + BevyWindowEvent::CursorLeft(e) => { world.send_event(e); } - WinitEvent::CursorMoved(e) => { + BevyWindowEvent::CursorMoved(e) => { world.send_event(e); } - WinitEvent::FileDragAndDrop(e) => { + BevyWindowEvent::FileDragAndDrop(e) => { world.send_event(e); } - WinitEvent::Ime(e) => { + BevyWindowEvent::Ime(e) => { world.send_event(e); } - WinitEvent::ReceivedCharacter(e) => { + BevyWindowEvent::ReceivedCharacter(e) => { world.send_event(e); } - WinitEvent::RequestRedraw(e) => { + BevyWindowEvent::RequestRedraw(e) => { world.send_event(e); } - WinitEvent::WindowBackendScaleFactorChanged(e) => { + BevyWindowEvent::WindowBackendScaleFactorChanged(e) => { world.send_event(e); } - WinitEvent::WindowCloseRequested(e) => { + BevyWindowEvent::WindowCloseRequested(e) => { world.send_event(e); } - WinitEvent::WindowCreated(e) => { + BevyWindowEvent::WindowCreated(e) => { world.send_event(e); } - WinitEvent::WindowDestroyed(e) => { + BevyWindowEvent::WindowDestroyed(e) => { world.send_event(e); } - WinitEvent::WindowFocused(e) => { + BevyWindowEvent::WindowFocused(e) => { world.send_event(e); } - WinitEvent::WindowMoved(e) => { + BevyWindowEvent::WindowMoved(e) => { world.send_event(e); } - WinitEvent::WindowOccluded(e) => { + BevyWindowEvent::WindowOccluded(e) => { world.send_event(e); } - WinitEvent::WindowResized(e) => { + BevyWindowEvent::WindowResized(e) => { world.send_event(e); } - WinitEvent::WindowScaleFactorChanged(e) => { + BevyWindowEvent::WindowScaleFactorChanged(e) => { world.send_event(e); } - WinitEvent::WindowThemeChanged(e) => { + BevyWindowEvent::WindowThemeChanged(e) => { world.send_event(e); } - WinitEvent::MouseButtonInput(e) => { + BevyWindowEvent::MouseButtonInput(e) => { world.send_event(e); } - WinitEvent::MouseMotion(e) => { + BevyWindowEvent::MouseMotion(e) => { world.send_event(e); } - WinitEvent::MouseWheel(e) => { + BevyWindowEvent::MouseWheel(e) => { world.send_event(e); } - WinitEvent::PinchGesture(e) => { + BevyWindowEvent::PinchGesture(e) => { world.send_event(e); } - WinitEvent::RotationGesture(e) => { + BevyWindowEvent::RotationGesture(e) => { world.send_event(e); } - WinitEvent::DoubleTapGesture(e) => { + BevyWindowEvent::DoubleTapGesture(e) => { world.send_event(e); } - WinitEvent::PanGesture(e) => { + BevyWindowEvent::PanGesture(e) => { world.send_event(e); } - WinitEvent::TouchInput(e) => { + BevyWindowEvent::TouchInput(e) => { world.send_event(e); } - WinitEvent::KeyboardInput(e) => { + BevyWindowEvent::KeyboardInput(e) => { world.send_event(e); } - WinitEvent::KeyboardFocusLost(e) => { + BevyWindowEvent::KeyboardFocusLost(e) => { world.send_event(e); } } } world - .resource_mut::>() + .resource_mut::>() .send_batch(buffered_events); } diff --git a/crates/bevy_winit/src/winit_event.rs b/crates/bevy_winit/src/winit_event.rs deleted file mode 100644 index 9244b1265db73..0000000000000 --- a/crates/bevy_winit/src/winit_event.rs +++ /dev/null @@ -1,209 +0,0 @@ -#![allow(deprecated)] -#![allow(missing_docs)] - -use bevy_ecs::prelude::*; -use bevy_input::keyboard::KeyboardInput; -use bevy_input::touch::TouchInput; -use bevy_input::{ - gestures::*, - keyboard::KeyboardFocusLost, - mouse::{MouseButtonInput, MouseMotion, MouseWheel}, -}; -use bevy_reflect::Reflect; -#[cfg(feature = "serialize")] -use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; -use bevy_window::{ - AppLifecycle, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime, ReceivedCharacter, - RequestRedraw, WindowBackendScaleFactorChanged, WindowCloseRequested, WindowCreated, - WindowDestroyed, WindowFocused, WindowMoved, WindowOccluded, WindowResized, - WindowScaleFactorChanged, WindowThemeChanged, -}; - -/// Wraps all `bevy_window` events in a common enum. -/// -/// Read these events with `EventReader` if you need to -/// access window events in the order they were received from `winit`. -/// Otherwise, the event types are individually readable with -/// `EventReader` (e.g. `EventReader`). -#[derive(Event, Debug, Clone, PartialEq, Reflect)] -#[reflect(Debug, PartialEq)] -#[cfg_attr( - feature = "serialize", - derive(serde::Serialize, serde::Deserialize), - reflect(Serialize, Deserialize) -)] -pub enum WinitEvent { - AppLifecycle(AppLifecycle), - CursorEntered(CursorEntered), - CursorLeft(CursorLeft), - CursorMoved(CursorMoved), - FileDragAndDrop(FileDragAndDrop), - Ime(Ime), - ReceivedCharacter(ReceivedCharacter), - RequestRedraw(RequestRedraw), - WindowBackendScaleFactorChanged(WindowBackendScaleFactorChanged), - WindowCloseRequested(WindowCloseRequested), - WindowCreated(WindowCreated), - WindowDestroyed(WindowDestroyed), - WindowFocused(WindowFocused), - WindowMoved(WindowMoved), - WindowOccluded(WindowOccluded), - WindowResized(WindowResized), - WindowScaleFactorChanged(WindowScaleFactorChanged), - WindowThemeChanged(WindowThemeChanged), - - MouseButtonInput(MouseButtonInput), - MouseMotion(MouseMotion), - MouseWheel(MouseWheel), - - PinchGesture(PinchGesture), - RotationGesture(RotationGesture), - DoubleTapGesture(DoubleTapGesture), - PanGesture(PanGesture), - - TouchInput(TouchInput), - - KeyboardInput(KeyboardInput), - KeyboardFocusLost(KeyboardFocusLost), -} - -impl From for WinitEvent { - fn from(e: AppLifecycle) -> Self { - Self::AppLifecycle(e) - } -} -impl From for WinitEvent { - fn from(e: CursorEntered) -> Self { - Self::CursorEntered(e) - } -} -impl From for WinitEvent { - fn from(e: CursorLeft) -> Self { - Self::CursorLeft(e) - } -} -impl From for WinitEvent { - fn from(e: CursorMoved) -> Self { - Self::CursorMoved(e) - } -} -impl From for WinitEvent { - fn from(e: FileDragAndDrop) -> Self { - Self::FileDragAndDrop(e) - } -} -impl From for WinitEvent { - fn from(e: Ime) -> Self { - Self::Ime(e) - } -} -impl From for WinitEvent { - fn from(e: ReceivedCharacter) -> Self { - Self::ReceivedCharacter(e) - } -} -impl From for WinitEvent { - fn from(e: RequestRedraw) -> Self { - Self::RequestRedraw(e) - } -} -impl From for WinitEvent { - fn from(e: WindowBackendScaleFactorChanged) -> Self { - Self::WindowBackendScaleFactorChanged(e) - } -} -impl From for WinitEvent { - fn from(e: WindowCloseRequested) -> Self { - Self::WindowCloseRequested(e) - } -} -impl From for WinitEvent { - fn from(e: WindowCreated) -> Self { - Self::WindowCreated(e) - } -} -impl From for WinitEvent { - fn from(e: WindowDestroyed) -> Self { - Self::WindowDestroyed(e) - } -} -impl From for WinitEvent { - fn from(e: WindowFocused) -> Self { - Self::WindowFocused(e) - } -} -impl From for WinitEvent { - fn from(e: WindowMoved) -> Self { - Self::WindowMoved(e) - } -} -impl From for WinitEvent { - fn from(e: WindowOccluded) -> Self { - Self::WindowOccluded(e) - } -} -impl From for WinitEvent { - fn from(e: WindowResized) -> Self { - Self::WindowResized(e) - } -} -impl From for WinitEvent { - fn from(e: WindowScaleFactorChanged) -> Self { - Self::WindowScaleFactorChanged(e) - } -} -impl From for WinitEvent { - fn from(e: WindowThemeChanged) -> Self { - Self::WindowThemeChanged(e) - } -} -impl From for WinitEvent { - fn from(e: MouseButtonInput) -> Self { - Self::MouseButtonInput(e) - } -} -impl From for WinitEvent { - fn from(e: MouseMotion) -> Self { - Self::MouseMotion(e) - } -} -impl From for WinitEvent { - fn from(e: MouseWheel) -> Self { - Self::MouseWheel(e) - } -} -impl From for WinitEvent { - fn from(e: PinchGesture) -> Self { - Self::PinchGesture(e) - } -} -impl From for WinitEvent { - fn from(e: RotationGesture) -> Self { - Self::RotationGesture(e) - } -} -impl From for WinitEvent { - fn from(e: DoubleTapGesture) -> Self { - Self::DoubleTapGesture(e) - } -} -impl From for WinitEvent { - fn from(e: PanGesture) -> Self { - Self::PanGesture(e) - } -} -impl From for WinitEvent { - fn from(e: TouchInput) -> Self { - Self::TouchInput(e) - } -} -impl From for WinitEvent { - fn from(e: KeyboardInput) -> Self { - Self::KeyboardInput(e) - } -} -impl From for WinitEvent { - fn from(e: KeyboardFocusLost) -> Self { - Self::KeyboardFocusLost(e) - } -}