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_hierarchy::DespawnRecursiveExt;
17use bevy_input::{
18    prelude::*,
19    touch::{TouchInput, TouchPhase},
20    ButtonState,
21};
22use bevy_math::Vec2;
23use bevy_reflect::prelude::*;
24use bevy_render::camera::RenderTarget;
25use bevy_utils::{tracing::debug, HashMap, HashSet};
26use bevy_window::{PrimaryWindow, WindowEvent, WindowRef};
27
28use crate::pointer::{
29    Location, PointerAction, PointerButton, PointerId, PointerInput, PointerLocation,
30    PressDirection,
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)]
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.get_single().ok())
122                    {
123                        Some(target) => target,
124                        None => continue,
125                    },
126                    position: event.position,
127                };
128                pointer_events.send(PointerInput::new(
129                    PointerId::Mouse,
130                    location,
131                    PointerAction::Moved {
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.get_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 direction = match input.state {
155                    ButtonState::Pressed => PressDirection::Down,
156                    ButtonState::Released => PressDirection::Up,
157                };
158                pointer_events.send(PointerInput::new(
159                    PointerId::Mouse,
160                    location,
161                    PointerAction::Pressed { direction, button },
162                ));
163            }
164            _ => {}
165        }
166    }
167}
168
169/// Sends touch pointer events to be consumed by the core plugin
170pub fn touch_pick_events(
171    // Input
172    mut window_events: EventReader<WindowEvent>,
173    primary_window: Query<Entity, With<PrimaryWindow>>,
174    // Locals
175    mut touch_cache: Local<HashMap<u64, TouchInput>>,
176    // Output
177    mut commands: Commands,
178    mut pointer_events: EventWriter<PointerInput>,
179) {
180    for window_event in window_events.read() {
181        if let WindowEvent::TouchInput(touch) = window_event {
182            let pointer = PointerId::Touch(touch.id);
183            let location = Location {
184                target: match RenderTarget::Window(WindowRef::Entity(touch.window))
185                    .normalize(primary_window.get_single().ok())
186                {
187                    Some(target) => target,
188                    None => continue,
189                },
190                position: touch.position,
191            };
192            match touch.phase {
193                TouchPhase::Started => {
194                    debug!("Spawning pointer {:?}", pointer);
195                    commands.spawn((pointer, PointerLocation::new(location.clone())));
196
197                    pointer_events.send(PointerInput::new(
198                        pointer,
199                        location,
200                        PointerAction::Pressed {
201                            direction: PressDirection::Down,
202                            button: PointerButton::Primary,
203                        },
204                    ));
205
206                    touch_cache.insert(touch.id, *touch);
207                }
208                TouchPhase::Moved => {
209                    // Send a move event only if it isn't the same as the last one
210                    if let Some(last_touch) = touch_cache.get(&touch.id) {
211                        if last_touch == touch {
212                            continue;
213                        }
214                        pointer_events.send(PointerInput::new(
215                            pointer,
216                            location,
217                            PointerAction::Moved {
218                                delta: touch.position - last_touch.position,
219                            },
220                        ));
221                    }
222                    touch_cache.insert(touch.id, *touch);
223                }
224                TouchPhase::Ended => {
225                    pointer_events.send(PointerInput::new(
226                        pointer,
227                        location,
228                        PointerAction::Pressed {
229                            direction: PressDirection::Up,
230                            button: PointerButton::Primary,
231                        },
232                    ));
233                    touch_cache.remove(&touch.id);
234                }
235                TouchPhase::Canceled => {
236                    pointer_events.send(PointerInput::new(
237                        pointer,
238                        location,
239                        PointerAction::Canceled,
240                    ));
241                    touch_cache.remove(&touch.id);
242                }
243            }
244        }
245    }
246}
247
248/// Deactivates unused touch pointers.
249///
250/// Because each new touch gets assigned a new ID, we need to remove the pointers associated with
251/// touches that are no longer active.
252pub fn deactivate_touch_pointers(
253    mut commands: Commands,
254    mut despawn_list: Local<HashSet<(Entity, PointerId)>>,
255    pointers: Query<(Entity, &PointerId)>,
256    mut touches: EventReader<TouchInput>,
257) {
258    for touch in touches.read() {
259        if let TouchPhase::Ended | TouchPhase::Canceled = touch.phase {
260            for (entity, pointer) in &pointers {
261                if pointer.get_touch_id() == Some(touch.id) {
262                    despawn_list.insert((entity, *pointer));
263                }
264            }
265        }
266    }
267    // A hash set is used to prevent despawning the same entity twice.
268    for (entity, pointer) in despawn_list.drain() {
269        debug!("Despawning pointer {:?}", pointer);
270        commands.entity(entity).despawn_recursive();
271    }
272}