bevy_picking/
input.rs

1//! This module provides unsurprising default inputs to `bevy_picking` through [`PointerInput`].
2//! The included systems are responsible for sending  mouse and touch inputs to their
3//! respective `Pointer`s.
4//!
5//! Because this has it's own plugin, it's easy to omit it, and provide your own inputs as
6//! needed. Because `Pointer`s aren't coupled to the underlying input hardware, you can easily mock
7//! inputs, and allow users full accessibility to map whatever inputs they need to pointer input.
8//!
9//! If, for example, you wanted to add support for VR input, all you need to do is spawn a pointer
10//! entity with a custom [`PointerId`], and write a system
11//! that updates its position. If you want this to work properly with the existing interaction events,
12//! you need to be sure that you also write a [`PointerInput`] event stream.
13
14use bevy_app::prelude::*;
15use bevy_ecs::prelude::*;
16use bevy_input::{
17    mouse::MouseWheel,
18    prelude::*,
19    touch::{TouchInput, TouchPhase},
20    ButtonState,
21};
22use bevy_math::Vec2;
23use bevy_platform::collections::{HashMap, HashSet};
24use bevy_reflect::prelude::*;
25use bevy_render::camera::RenderTarget;
26use bevy_window::{PrimaryWindow, WindowEvent, WindowRef};
27use tracing::debug;
28
29use crate::pointer::{
30    Location, PointerAction, PointerButton, PointerId, PointerInput, PointerLocation,
31};
32
33use crate::PickSet;
34
35/// The picking input prelude.
36///
37/// This includes the most common types in this module, re-exported for your convenience.
38pub mod prelude {
39    pub use crate::input::PointerInputPlugin;
40}
41
42/// Adds mouse and touch inputs for picking pointers to your app. This is a default input plugin,
43/// that you can replace with your own plugin as needed.
44///
45/// [`crate::PickingPlugin::is_input_enabled`] can be used to toggle whether
46/// the core picking plugin processes the inputs sent by this, or other input plugins, in one place.
47///
48/// This plugin contains several settings, and is added to the world as a resource after initialization.
49/// You can configure pointer input settings at runtime by accessing the resource.
50#[derive(Copy, Clone, Resource, Debug, Reflect)]
51#[reflect(Resource, Default, Clone)]
52pub struct PointerInputPlugin {
53    /// Should touch inputs be updated?
54    pub is_touch_enabled: bool,
55    /// Should mouse inputs be updated?
56    pub is_mouse_enabled: bool,
57}
58
59impl PointerInputPlugin {
60    fn is_mouse_enabled(state: Res<Self>) -> bool {
61        state.is_mouse_enabled
62    }
63
64    fn is_touch_enabled(state: Res<Self>) -> bool {
65        state.is_touch_enabled
66    }
67}
68
69impl Default for PointerInputPlugin {
70    fn default() -> Self {
71        Self {
72            is_touch_enabled: true,
73            is_mouse_enabled: true,
74        }
75    }
76}
77
78impl Plugin for PointerInputPlugin {
79    fn build(&self, app: &mut App) {
80        app.insert_resource(*self)
81            .add_systems(Startup, spawn_mouse_pointer)
82            .add_systems(
83                First,
84                (
85                    mouse_pick_events.run_if(PointerInputPlugin::is_mouse_enabled),
86                    touch_pick_events.run_if(PointerInputPlugin::is_touch_enabled),
87                )
88                    .chain()
89                    .in_set(PickSet::Input),
90            )
91            .add_systems(
92                Last,
93                deactivate_touch_pointers.run_if(PointerInputPlugin::is_touch_enabled),
94            )
95            .register_type::<Self>()
96            .register_type::<PointerInputPlugin>();
97    }
98}
99
100/// Spawns the default mouse pointer.
101pub fn spawn_mouse_pointer(mut commands: Commands) {
102    commands.spawn(PointerId::Mouse);
103}
104
105/// Sends mouse pointer events to be processed by the core plugin
106pub fn mouse_pick_events(
107    // Input
108    mut window_events: EventReader<WindowEvent>,
109    primary_window: Query<Entity, With<PrimaryWindow>>,
110    // Locals
111    mut cursor_last: Local<Vec2>,
112    // Output
113    mut pointer_events: EventWriter<PointerInput>,
114) {
115    for window_event in window_events.read() {
116        match window_event {
117            // Handle cursor movement events
118            WindowEvent::CursorMoved(event) => {
119                let location = Location {
120                    target: match RenderTarget::Window(WindowRef::Entity(event.window))
121                        .normalize(primary_window.single().ok())
122                    {
123                        Some(target) => target,
124                        None => continue,
125                    },
126                    position: event.position,
127                };
128                pointer_events.write(PointerInput::new(
129                    PointerId::Mouse,
130                    location,
131                    PointerAction::Move {
132                        delta: event.position - *cursor_last,
133                    },
134                ));
135                *cursor_last = event.position;
136            }
137            // Handle mouse button press events
138            WindowEvent::MouseButtonInput(input) => {
139                let location = Location {
140                    target: match RenderTarget::Window(WindowRef::Entity(input.window))
141                        .normalize(primary_window.single().ok())
142                    {
143                        Some(target) => target,
144                        None => continue,
145                    },
146                    position: *cursor_last,
147                };
148                let button = match input.button {
149                    MouseButton::Left => PointerButton::Primary,
150                    MouseButton::Right => PointerButton::Secondary,
151                    MouseButton::Middle => PointerButton::Middle,
152                    MouseButton::Other(_) | MouseButton::Back | MouseButton::Forward => continue,
153                };
154                let action = match input.state {
155                    ButtonState::Pressed => PointerAction::Press(button),
156                    ButtonState::Released => PointerAction::Release(button),
157                };
158                pointer_events.write(PointerInput::new(PointerId::Mouse, location, action));
159            }
160            WindowEvent::MouseWheel(event) => {
161                let MouseWheel { unit, x, y, window } = *event;
162
163                let location = Location {
164                    target: match RenderTarget::Window(WindowRef::Entity(window))
165                        .normalize(primary_window.single().ok())
166                    {
167                        Some(target) => target,
168                        None => continue,
169                    },
170                    position: *cursor_last,
171                };
172
173                let action = PointerAction::Scroll { x, y, unit };
174
175                pointer_events.write(PointerInput::new(PointerId::Mouse, location, action));
176            }
177            _ => {}
178        }
179    }
180}
181
182/// Sends touch pointer events to be consumed by the core plugin
183pub fn touch_pick_events(
184    // Input
185    mut window_events: EventReader<WindowEvent>,
186    primary_window: Query<Entity, With<PrimaryWindow>>,
187    // Locals
188    mut touch_cache: Local<HashMap<u64, TouchInput>>,
189    // Output
190    mut commands: Commands,
191    mut pointer_events: EventWriter<PointerInput>,
192) {
193    for window_event in window_events.read() {
194        if let WindowEvent::TouchInput(touch) = window_event {
195            let pointer = PointerId::Touch(touch.id);
196            let location = Location {
197                target: match RenderTarget::Window(WindowRef::Entity(touch.window))
198                    .normalize(primary_window.single().ok())
199                {
200                    Some(target) => target,
201                    None => continue,
202                },
203                position: touch.position,
204            };
205            match touch.phase {
206                TouchPhase::Started => {
207                    debug!("Spawning pointer {:?}", pointer);
208                    commands.spawn((pointer, PointerLocation::new(location.clone())));
209
210                    pointer_events.write(PointerInput::new(
211                        pointer,
212                        location,
213                        PointerAction::Press(PointerButton::Primary),
214                    ));
215
216                    touch_cache.insert(touch.id, *touch);
217                }
218                TouchPhase::Moved => {
219                    // Send a move event only if it isn't the same as the last one
220                    if let Some(last_touch) = touch_cache.get(&touch.id) {
221                        if last_touch == touch {
222                            continue;
223                        }
224                        pointer_events.write(PointerInput::new(
225                            pointer,
226                            location,
227                            PointerAction::Move {
228                                delta: touch.position - last_touch.position,
229                            },
230                        ));
231                    }
232                    touch_cache.insert(touch.id, *touch);
233                }
234                TouchPhase::Ended => {
235                    pointer_events.write(PointerInput::new(
236                        pointer,
237                        location,
238                        PointerAction::Release(PointerButton::Primary),
239                    ));
240                    touch_cache.remove(&touch.id);
241                }
242                TouchPhase::Canceled => {
243                    pointer_events.write(PointerInput::new(
244                        pointer,
245                        location,
246                        PointerAction::Cancel,
247                    ));
248                    touch_cache.remove(&touch.id);
249                }
250            }
251        }
252    }
253}
254
255/// Deactivates unused touch pointers.
256///
257/// Because each new touch gets assigned a new ID, we need to remove the pointers associated with
258/// touches that are no longer active.
259pub fn deactivate_touch_pointers(
260    mut commands: Commands,
261    mut despawn_list: Local<HashSet<(Entity, PointerId)>>,
262    pointers: Query<(Entity, &PointerId)>,
263    mut touches: EventReader<TouchInput>,
264) {
265    for touch in touches.read() {
266        if let TouchPhase::Ended | TouchPhase::Canceled = touch.phase {
267            for (entity, pointer) in &pointers {
268                if pointer.get_touch_id() == Some(touch.id) {
269                    despawn_list.insert((entity, *pointer));
270                }
271            }
272        }
273    }
274    // A hash set is used to prevent despawning the same entity twice.
275    for (entity, pointer) in despawn_list.drain() {
276        debug!("Despawning pointer {:?}", pointer);
277        commands.entity(entity).despawn();
278    }
279}