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_camera::RenderTarget;
16use bevy_ecs::prelude::*;
17use bevy_input::{
18    mouse::MouseWheel,
19    prelude::*,
20    touch::{TouchInput, TouchPhase},
21    ButtonState,
22};
23use bevy_math::Vec2;
24use bevy_platform::collections::{HashMap, HashSet};
25use bevy_reflect::prelude::*;
26use bevy_window::{PrimaryWindow, WindowEvent, WindowRef};
27use tracing::debug;
28
29use crate::pointer::{
30    Location, PointerAction, PointerButton, PointerId, PointerInput, PointerLocation,
31};
32
33use crate::PickingSystems;
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#[derive(Copy, Clone, Resource, Debug, Reflect)]
43#[reflect(Resource, Default, Clone)]
44/// Settings for enabling and disabling updating mouse and touch inputs for picking
45///
46/// ## Custom initialization
47/// ```
48/// # use bevy_app::App;
49/// # use bevy_picking::input::{PointerInputSettings,PointerInputPlugin};
50/// App::new()
51///     .insert_resource(PointerInputSettings {
52///         is_touch_enabled: false,
53///         is_mouse_enabled: true,
54///     })
55///     // or DefaultPlugins
56///     .add_plugins(PointerInputPlugin);
57/// ```
58pub struct PointerInputSettings {
59    /// Should touch inputs be updated?
60    pub is_touch_enabled: bool,
61    /// Should mouse inputs be updated?
62    pub is_mouse_enabled: bool,
63}
64
65impl PointerInputSettings {
66    fn is_mouse_enabled(state: Res<Self>) -> bool {
67        state.is_mouse_enabled
68    }
69
70    fn is_touch_enabled(state: Res<Self>) -> bool {
71        state.is_touch_enabled
72    }
73}
74
75impl Default for PointerInputSettings {
76    fn default() -> Self {
77        Self {
78            is_touch_enabled: true,
79            is_mouse_enabled: true,
80        }
81    }
82}
83
84/// Adds mouse and touch inputs for picking pointers to your app. This is a default input plugin,
85/// that you can replace with your own plugin as needed.
86///
87/// Toggling mouse input or touch input can be done at runtime by modifying
88/// [`PointerInputSettings`] resource.
89///
90/// [`PointerInputSettings`] can be initialized with custom values, but will be
91/// initialized with default values if it is not present at the moment this is
92/// added to the app.
93pub struct PointerInputPlugin;
94
95impl Plugin for PointerInputPlugin {
96    fn build(&self, app: &mut App) {
97        app.init_resource::<PointerInputSettings>()
98            .add_systems(Startup, spawn_mouse_pointer)
99            .add_systems(
100                First,
101                (
102                    mouse_pick_events.run_if(PointerInputSettings::is_mouse_enabled),
103                    touch_pick_events.run_if(PointerInputSettings::is_touch_enabled),
104                )
105                    .chain()
106                    .in_set(PickingSystems::Input),
107            )
108            .add_systems(
109                Last,
110                deactivate_touch_pointers.run_if(PointerInputSettings::is_touch_enabled),
111            );
112    }
113}
114
115/// Spawns the default mouse pointer.
116pub fn spawn_mouse_pointer(mut commands: Commands) {
117    commands.spawn(PointerId::Mouse);
118}
119
120/// Sends mouse pointer events to be processed by the core plugin
121pub fn mouse_pick_events(
122    // Input
123    mut window_events: MessageReader<WindowEvent>,
124    primary_window: Query<Entity, With<PrimaryWindow>>,
125    // Locals
126    mut cursor_last: Local<Vec2>,
127    // Output
128    mut pointer_inputs: MessageWriter<PointerInput>,
129) {
130    for window_event in window_events.read() {
131        match window_event {
132            // Handle cursor movement events
133            WindowEvent::CursorMoved(event) => {
134                let location = Location {
135                    target: match RenderTarget::Window(WindowRef::Entity(event.window))
136                        .normalize(primary_window.single().ok())
137                    {
138                        Some(target) => target,
139                        None => continue,
140                    },
141                    position: event.position,
142                };
143                pointer_inputs.write(PointerInput::new(
144                    PointerId::Mouse,
145                    location,
146                    PointerAction::Move {
147                        delta: event.position - *cursor_last,
148                    },
149                ));
150                *cursor_last = event.position;
151            }
152            // Handle mouse button press events
153            WindowEvent::MouseButtonInput(input) => {
154                let location = Location {
155                    target: match RenderTarget::Window(WindowRef::Entity(input.window))
156                        .normalize(primary_window.single().ok())
157                    {
158                        Some(target) => target,
159                        None => continue,
160                    },
161                    position: *cursor_last,
162                };
163                let button = match input.button {
164                    MouseButton::Left => PointerButton::Primary,
165                    MouseButton::Right => PointerButton::Secondary,
166                    MouseButton::Middle => PointerButton::Middle,
167                    MouseButton::Other(_) | MouseButton::Back | MouseButton::Forward => continue,
168                };
169                let action = match input.state {
170                    ButtonState::Pressed => PointerAction::Press(button),
171                    ButtonState::Released => PointerAction::Release(button),
172                };
173                pointer_inputs.write(PointerInput::new(PointerId::Mouse, location, action));
174            }
175            WindowEvent::MouseWheel(event) => {
176                let MouseWheel { unit, x, y, window } = *event;
177
178                let location = Location {
179                    target: match RenderTarget::Window(WindowRef::Entity(window))
180                        .normalize(primary_window.single().ok())
181                    {
182                        Some(target) => target,
183                        None => continue,
184                    },
185                    position: *cursor_last,
186                };
187
188                let action = PointerAction::Scroll { x, y, unit };
189
190                pointer_inputs.write(PointerInput::new(PointerId::Mouse, location, action));
191            }
192            _ => {}
193        }
194    }
195}
196
197/// Sends touch pointer events to be consumed by the core plugin
198pub fn touch_pick_events(
199    // Input
200    mut window_events: MessageReader<WindowEvent>,
201    primary_window: Query<Entity, With<PrimaryWindow>>,
202    // Locals
203    mut touch_cache: Local<HashMap<u64, TouchInput>>,
204    // Output
205    mut commands: Commands,
206    mut pointer_inputs: MessageWriter<PointerInput>,
207) {
208    for window_event in window_events.read() {
209        if let WindowEvent::TouchInput(touch) = window_event {
210            let pointer = PointerId::Touch(touch.id);
211            let location = Location {
212                target: match RenderTarget::Window(WindowRef::Entity(touch.window))
213                    .normalize(primary_window.single().ok())
214                {
215                    Some(target) => target,
216                    None => continue,
217                },
218                position: touch.position,
219            };
220            match touch.phase {
221                TouchPhase::Started => {
222                    debug!("Spawning pointer {:?}", pointer);
223                    commands.spawn((pointer, PointerLocation::new(location.clone())));
224
225                    pointer_inputs.write(PointerInput::new(
226                        pointer,
227                        location,
228                        PointerAction::Press(PointerButton::Primary),
229                    ));
230
231                    touch_cache.insert(touch.id, *touch);
232                }
233                TouchPhase::Moved => {
234                    // Send a move event only if it isn't the same as the last one
235                    if let Some(last_touch) = touch_cache.get(&touch.id) {
236                        if last_touch == touch {
237                            continue;
238                        }
239                        pointer_inputs.write(PointerInput::new(
240                            pointer,
241                            location,
242                            PointerAction::Move {
243                                delta: touch.position - last_touch.position,
244                            },
245                        ));
246                    }
247                    touch_cache.insert(touch.id, *touch);
248                }
249                TouchPhase::Ended => {
250                    pointer_inputs.write(PointerInput::new(
251                        pointer,
252                        location,
253                        PointerAction::Release(PointerButton::Primary),
254                    ));
255                    touch_cache.remove(&touch.id);
256                }
257                TouchPhase::Canceled => {
258                    pointer_inputs.write(PointerInput::new(
259                        pointer,
260                        location,
261                        PointerAction::Cancel,
262                    ));
263                    touch_cache.remove(&touch.id);
264                }
265            }
266        }
267    }
268}
269
270/// Deactivates unused touch pointers.
271///
272/// Because each new touch gets assigned a new ID, we need to remove the pointers associated with
273/// touches that are no longer active.
274pub fn deactivate_touch_pointers(
275    mut commands: Commands,
276    mut despawn_list: Local<HashSet<(Entity, PointerId)>>,
277    pointers: Query<(Entity, &PointerId)>,
278    mut touches: MessageReader<TouchInput>,
279) {
280    for touch in touches.read() {
281        if let TouchPhase::Ended | TouchPhase::Canceled = touch.phase {
282            for (entity, pointer) in &pointers {
283                if pointer.get_touch_id() == Some(touch.id) {
284                    despawn_list.insert((entity, *pointer));
285                }
286            }
287        }
288    }
289    // A hash set is used to prevent despawning the same entity twice.
290    for (entity, pointer) in despawn_list.drain() {
291        debug!("Despawning pointer {:?}", pointer);
292        commands.entity(entity).despawn();
293    }
294}