Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_picking/src/input.rs
6596 views
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
14
use bevy_app::prelude::*;
15
use bevy_camera::RenderTarget;
16
use bevy_ecs::prelude::*;
17
use bevy_input::{
18
mouse::MouseWheel,
19
prelude::*,
20
touch::{TouchInput, TouchPhase},
21
ButtonState,
22
};
23
use bevy_math::Vec2;
24
use bevy_platform::collections::{HashMap, HashSet};
25
use bevy_reflect::prelude::*;
26
use bevy_window::{PrimaryWindow, WindowEvent, WindowRef};
27
use tracing::debug;
28
29
use crate::pointer::{
30
Location, PointerAction, PointerButton, PointerId, PointerInput, PointerLocation,
31
};
32
33
use crate::PickingSystems;
34
35
/// The picking input prelude.
36
///
37
/// This includes the most common types in this module, re-exported for your convenience.
38
pub 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
/// ```
58
pub 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
65
impl 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
75
impl 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.
93
pub struct PointerInputPlugin;
94
95
impl 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.
116
pub 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
121
pub fn mouse_pick_events(
122
// Input
123
mut window_events: EventReader<WindowEvent>,
124
primary_window: Query<Entity, With<PrimaryWindow>>,
125
// Locals
126
mut cursor_last: Local<Vec2>,
127
// Output
128
mut pointer_events: EventWriter<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_events.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_events.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_events.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
198
pub fn touch_pick_events(
199
// Input
200
mut window_events: EventReader<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_events: EventWriter<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_events.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_events.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_events.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_events.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.
274
pub fn deactivate_touch_pointers(
275
mut commands: Commands,
276
mut despawn_list: Local<HashSet<(Entity, PointerId)>>,
277
pointers: Query<(Entity, &PointerId)>,
278
mut touches: EventReader<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
}
295
296