-
-
Notifications
You must be signed in to change notification settings - Fork 167
/
pointer.rs
320 lines (290 loc) · 10.2 KB
/
pointer.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
//! Types and systems for pointer inputs, such as position and buttons.
use bevy_ecs::prelude::*;
use bevy_math::{Rect, Vec2};
use bevy_reflect::prelude::*;
use bevy_render::camera::{Camera, NormalizedRenderTarget};
use bevy_utils::HashMap;
use bevy_window::PrimaryWindow;
pub use uuid::Uuid;
use std::{fmt::Debug, ops::Deref};
use crate::backend::HitData;
/// Identifies a unique pointer entity. `Mouse` and `Touch` pointers are automatically spawned.
///
/// This component is needed because pointers can be spawned and despawned, but they need to have a
/// stable ID that persists regardless of the Entity they are associated with.
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, Component, Reflect)]
#[reflect(Component, Default)]
pub enum PointerId {
/// The mouse pointer.
#[default]
Mouse,
/// A touch input, usually numbered by window touch events from `winit`.
Touch(u64),
/// A custom, uniquely identified pointer. Useful for mocking inputs or implementing a software
/// controlled cursor.
#[reflect(ignore)]
Custom(Uuid),
}
impl PointerId {
/// Returns true if the pointer is a touch input.
pub fn is_touch(&self) -> bool {
matches!(self, PointerId::Touch(_))
}
/// Returns true if the pointer is the mouse.
pub fn is_mouse(&self) -> bool {
matches!(self, PointerId::Mouse)
}
/// Returns true if the pointer is a custom input.
pub fn is_custom(&self) -> bool {
matches!(self, PointerId::Custom(_))
}
/// Returns the touch id if the pointer is a touch input.
pub fn get_touch_id(&self) -> Option<u64> {
if let PointerId::Touch(id) = self {
Some(*id)
} else {
None
}
}
}
/// Holds a list of entities this pointer is currently interacting with, sorted from nearest to
/// farthest.
#[derive(Debug, Default, Clone, Component, Reflect)]
#[reflect(Component, Default)]
pub struct PointerInteraction {
pub(crate) sorted_entities: Vec<(Entity, HitData)>,
}
impl PointerInteraction {
/// Returns the nearest hit entity and data about that intersection.
pub fn get_nearest_hit(&self) -> Option<&(Entity, HitData)> {
self.sorted_entities.first()
}
}
impl Deref for PointerInteraction {
type Target = Vec<(Entity, HitData)>;
fn deref(&self) -> &Self::Target {
&self.sorted_entities
}
}
/// A resource that maps each [`PointerId`] to their [`Entity`] for easy lookups.
#[derive(Debug, Clone, Default, Resource)]
pub struct PointerMap {
inner: HashMap<PointerId, Entity>,
}
impl PointerMap {
/// Get the [`Entity`] of the supplied [`PointerId`].
pub fn get_entity(&self, pointer_id: PointerId) -> Option<Entity> {
self.inner.get(&pointer_id).copied()
}
}
/// Update the [`PointerMap`] resource with the current frame's data.
pub fn update_pointer_map(pointers: Query<(Entity, &PointerId)>, mut map: ResMut<PointerMap>) {
map.inner.clear();
for (entity, id) in &pointers {
map.inner.insert(*id, entity);
}
}
/// Tracks the state of the pointer's buttons in response to [`InputPress`]s.
#[derive(Debug, Default, Clone, Component, Reflect, PartialEq, Eq)]
#[reflect(Component, Default)]
pub struct PointerPress {
primary: bool,
secondary: bool,
middle: bool,
}
impl PointerPress {
/// Returns true if the primary pointer button is pressed.
#[inline]
pub fn is_primary_pressed(&self) -> bool {
self.primary
}
/// Returns true if the secondary pointer button is pressed.
#[inline]
pub fn is_secondary_pressed(&self) -> bool {
self.secondary
}
/// Returns true if the middle (tertiary) pointer button is pressed.
#[inline]
pub fn is_middle_pressed(&self) -> bool {
self.middle
}
/// Returns true if any pointer button is pressed.
#[inline]
pub fn is_any_pressed(&self) -> bool {
self.primary || self.middle || self.secondary
}
}
/// Pointer input event for button presses. Fires when a pointer button changes state.
#[derive(Event, Debug, Clone, Copy, PartialEq, Eq, Reflect)]
pub struct InputPress {
/// The [`PointerId`] of the pointer that pressed a button.
pub pointer_id: PointerId,
/// Direction of the button press.
pub direction: PressDirection,
/// Identifies the pointer button changing in this event.
pub button: PointerButton,
}
impl InputPress {
/// Create a new pointer button down event.
pub fn new_down(id: PointerId, button: PointerButton) -> InputPress {
Self {
pointer_id: id,
direction: PressDirection::Down,
button,
}
}
/// Create a new pointer button up event.
pub fn new_up(id: PointerId, button: PointerButton) -> InputPress {
Self {
pointer_id: id,
direction: PressDirection::Up,
button,
}
}
/// Returns true if the `button` of this pointer was just pressed.
#[inline]
pub fn is_just_down(&self, button: PointerButton) -> bool {
self.button == button && self.direction == PressDirection::Down
}
/// Returns true if the `button` of this pointer was just released.
#[inline]
pub fn is_just_up(&self, button: PointerButton) -> bool {
self.button == button && self.direction == PressDirection::Up
}
/// Receives [`InputPress`] events and updates corresponding [`PointerPress`] components.
pub fn receive(
mut events: EventReader<InputPress>,
mut pointers: Query<(&PointerId, &mut PointerPress)>,
) {
for input_press_event in events.read() {
pointers.iter_mut().for_each(|(pointer_id, mut pointer)| {
if *pointer_id == input_press_event.pointer_id {
let is_down = input_press_event.direction == PressDirection::Down;
match input_press_event.button {
PointerButton::Primary => pointer.primary = is_down,
PointerButton::Secondary => pointer.secondary = is_down,
PointerButton::Middle => pointer.middle = is_down,
}
}
})
}
}
}
/// The stage of the pointer button press event
#[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect)]
pub enum PressDirection {
/// The pointer button was just pressed
Down,
/// The pointer button was just released
Up,
}
/// The button that was just pressed or released
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect)]
pub enum PointerButton {
/// The primary pointer button
Primary,
/// The secondary pointer button
Secondary,
/// The tertiary pointer button
Middle,
}
impl PointerButton {
/// Iterator over all buttons that a pointer can have.
pub fn iter() -> impl Iterator<Item = PointerButton> {
[Self::Primary, Self::Secondary, Self::Middle].into_iter()
}
}
/// Component that tracks a pointer's current [`Location`].
#[derive(Debug, Default, Clone, Component, Reflect, PartialEq)]
#[reflect(Component, Default)]
pub struct PointerLocation {
/// The [`Location`] of the pointer. Note that a location is both the target, and the position
/// on the target.
#[reflect(ignore)]
pub location: Option<Location>,
}
impl PointerLocation {
/// Returns `Some(&`[`Location`]`)` if the pointer is active, or `None` if the pointer is
/// inactive.
pub fn location(&self) -> Option<&Location> {
self.location.as_ref()
}
}
/// Pointer input event for pointer moves. Fires when a pointer changes location.
#[derive(Event, Debug, Clone, Reflect)]
pub struct InputMove {
/// The [`PointerId`] of the pointer that is moving.
pub pointer_id: PointerId,
/// The [`Location`] of the pointer.
pub location: Location,
/// The distance moved (change in `position`) since the last event.
pub delta: Vec2,
}
impl InputMove {
/// Create a new [`InputMove`] event.
pub fn new(id: PointerId, location: Location, delta: Vec2) -> InputMove {
Self {
pointer_id: id,
location,
delta,
}
}
/// Receives [`InputMove`] events and updates corresponding [`PointerLocation`] components.
pub fn receive(
mut events: EventReader<InputMove>,
mut pointers: Query<(&PointerId, &mut PointerLocation)>,
) {
for event_pointer in events.read() {
pointers.iter_mut().for_each(|(id, mut pointer)| {
if *id == event_pointer.pointer_id {
pointer.location = Some(event_pointer.location.to_owned());
}
})
}
}
}
/// The location of a pointer, including the current [`NormalizedRenderTarget`], and the x/y
/// position of the pointer on this render target.
///
/// Note that:
/// - a pointer can move freely between render targets
/// - a pointer is not associated with a [`Camera`] because multiple cameras can target the same
/// render target. It is up to picking backends to associate a Pointer's `Location` with a
/// specific `Camera`, if any.
#[derive(Debug, Clone, Component, Reflect, PartialEq)]
pub struct Location {
/// The [`NormalizedRenderTarget`] associated with the pointer, usually a window.
pub target: NormalizedRenderTarget,
/// The position of the pointer in the `target`.
pub position: Vec2,
}
impl Location {
/// Returns `true` if this pointer's [`Location`] is within the [`Camera`]'s viewport.
///
/// Note this returns `false` if the location and camera have different render targets.
#[inline]
pub fn is_in_viewport(
&self,
camera: &Camera,
primary_window: &Query<Entity, With<PrimaryWindow>>,
) -> bool {
if camera
.target
.normalize(Some(match primary_window.get_single() {
Ok(w) => w,
Err(_) => return false,
}))
.as_ref()
!= Some(&self.target)
{
return false;
}
let position = Vec2::new(self.position.x, self.position.y);
camera
.logical_viewport_rect()
.map(|Rect { min, max }| {
(position - min).min_element() >= 0.0 && (position - max).max_element() <= 0.0
})
.unwrap_or(false)
}
}