Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Picking event ordering #14862

Merged
merged 28 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
454cb25
Move `winit::WinitEvent` to `window::WindowEvent`
NthTensor Aug 20, 2024
85730c4
Add prototype unified event dispatcher
NthTensor Aug 22, 2024
e7d742d
Appease clippy
NthTensor Aug 22, 2024
7b0a0c5
Swap ordering of Drop and DragEnd events
NthTensor Aug 22, 2024
97d7757
Update docs and cleanup events
NthTensor Aug 29, 2024
ec773bd
Merge remote-tracking branch 'upstream/main' into unified_pointer_events
NthTensor Aug 29, 2024
d25d09d
Improve docs
NthTensor Aug 30, 2024
f377315
Add bevy_winit dep
NthTensor Aug 30, 2024
f3af43c
Flatten input module
NthTensor Aug 30, 2024
e870646
Clean up events and add Cancel event
NthTensor Aug 30, 2024
64ce18b
Clean up and re-order events
NthTensor Aug 30, 2024
7ac155e
Merge remote-tracking branch 'upstream' into unified_pointer_events
NthTensor Aug 30, 2024
c60431a
Rename bevy_events to bevy_window_events
NthTensor Aug 30, 2024
4c5c209
Remove bad bevy_winit dep
NthTensor Aug 30, 2024
862f96e
Fix typos
NthTensor Aug 30, 2024
8f3139a
Fix doc links
NthTensor Aug 30, 2024
4807687
Fix small ordering bug
NthTensor Aug 30, 2024
2908240
Update doc links again
NthTensor Aug 31, 2024
874981c
Fix system ambiguity
NthTensor Aug 31, 2024
e8824b8
Fix bevy_window import
NthTensor Aug 31, 2024
1ede775
Additional CI fixes
NthTensor Sep 1, 2024
69bf0d6
Fix import
NthTensor Sep 1, 2024
07748d1
Move mega-event dispatch back into winit
NthTensor Sep 3, 2024
4fd45b0
Merge remote-tracking branch 'upstream' into unified_pointer_events
NthTensor Sep 3, 2024
8bdf052
Increment AD test number because macos hates me
NthTensor Sep 3, 2024
9b23706
More doclink fixes
NthTensor Sep 3, 2024
a3edeaf
Address feadback
NthTensor Sep 4, 2024
d31a743
Fix spelling
NthTensor Sep 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
732 changes: 309 additions & 423 deletions crates/bevy_picking/src/events.rs

Large diffs are not rendered by default.

25 changes: 18 additions & 7 deletions crates/bevy_picking/src/focus.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
//! Determines which entities are being hovered by which pointers.

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

Expand Down Expand Up @@ -63,7 +65,7 @@ pub fn update_focus(
pickable: Query<&Pickable>,
pointers: Query<&PointerId>,
mut under_pointer: EventReader<backend::PointerHits>,
mut cancellations: EventReader<PointerCancel>,
mut pointer_input: EventReader<PointerInput>,
// Local
mut over_map: Local<OverMap>,
// Output
Expand All @@ -76,7 +78,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);
}

Expand Down Expand Up @@ -109,9 +111,18 @@ fn reset_maps(
fn build_over_map(
backend_events: &mut EventReader<backend::PointerHits>,
pointer_over_map: &mut Local<OverMap>,
pointer_cancel: &mut EventReader<PointerCancel>,
pointer_input: &mut EventReader<PointerInput>,
) {
let cancelled_pointers: Vec<PointerId> = pointer_cancel.read().map(|p| p.pointer_id).collect();
let cancelled_pointers: HashSet<PointerId> = 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()
Expand Down
4 changes: 0 additions & 4 deletions crates/bevy_picking/src/input/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,6 @@ impl Plugin for InputPlugin {
(
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),
Expand Down
130 changes: 89 additions & 41 deletions crates/bevy_picking/src/input/mouse.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
//! Provides sensible defaults for mouse picking inputs.

use bevy_ecs::prelude::*;
use bevy_input::{mouse::MouseButtonInput, prelude::*, ButtonState};
use bevy_input::{prelude::*, ButtonState};
use bevy_math::Vec2;
use bevy_render::camera::RenderTarget;
use bevy_window::{CursorMoved, PrimaryWindow, Window, WindowRef};
use bevy_window::{PrimaryWindow, WindowEvent, WindowRef};

use crate::{
pointer::{InputMove, InputPress, Location, PointerButton, PointerId},
pointer::{Location, PointerAction, PointerButton, PointerId, PointerInput, PressDirection},
PointerBundle,
};

Expand All @@ -19,49 +19,97 @@ pub fn spawn_mouse_pointer(mut commands: Commands) {
/// 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 window_events: EventReader<WindowEvent>,
primary_window: Query<Entity, With<PrimaryWindow>>,
// Locals
mut cursor_last: Local<Vec2>,
mut mouse_inputs: EventReader<MouseButtonInput>,
// Output
mut pointer_move: EventWriter<InputMove>,
mut pointer_presses: EventWriter<InputPress>,
mut pointer_events: EventWriter<PointerInput>,
) {
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));
for window_event in window_events.read() {
match window_event {
// Handle the cursor entering the window
WindowEvent::CursorEntered(event) => {
let location = Location {
target: match RenderTarget::Window(WindowRef::Entity(event.window))
.normalize(primary_window.get_single().ok())
{
Some(target) => target,
None => continue,
},
position: *cursor_last, // Note, this is a hack until winit starts providing locations
};
pointer_events.send(PointerInput::new(
PointerId::Mouse,
location,
PointerAction::EnteredWindow,
));
}
// Handle curcor leaving the window
WindowEvent::CursorLeft(event) => {
let location = Location {
target: match RenderTarget::Window(WindowRef::Entity(event.window))
.normalize(primary_window.get_single().ok())
{
Some(target) => target,
None => continue,
},
position: *cursor_last, // Note, this is a hack until winit starts providing locations
};
pointer_events.send(PointerInput::new(
PointerId::Mouse,
location,
PointerAction::LeftWindow,
));
}
// 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;
}
ButtonState::Released => {
pointer_presses.send(InputPress::new_up(PointerId::Mouse, button));
// 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 },
));
}
_ => {}
}
}
}
121 changes: 67 additions & 54 deletions crates/bevy_picking/src/input/touch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,73 +3,89 @@
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 bevy_window::{PrimaryWindow, WindowEvent, WindowRef};

use crate::{
events::PointerCancel,
pointer::{InputMove, InputPress, Location, PointerButton, PointerId},
pointer::{Location, PointerAction, PointerButton, PointerId, PointerInput, PressDirection},
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>,
mut window_events: EventReader<WindowEvent>,
primary_window: Query<Entity, With<PrimaryWindow>>,
// Local
mut location_cache: Local<HashMap<u64, TouchInput>>,
// Locals
mut touch_cache: Local<HashMap<u64, TouchInput>>,
// Output
mut commands: Commands,
mut input_moves: EventWriter<InputMove>,
mut input_presses: EventWriter<InputPress>,
mut cancel_events: EventWriter<PointerCancel>,
mut pointer_events: EventWriter<PointerInput>,
) {
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()),));
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()));

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;
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,
},
));
}
input_moves.send(InputMove::new(
touch_cache.insert(touch.id, *touch);
}
TouchPhase::Ended => {
pointer_events.send(PointerInput::new(
pointer,
location,
touch.position - last_touch.position,
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);
}
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,
});
}
}
}
Expand All @@ -86,15 +102,12 @@ pub fn deactivate_touch_pointers(
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));
}
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.
Expand Down
Loading
Loading