Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_input/src/gamepad.rs
6595 views
1
//! The gamepad input functionality.
2
3
use core::{ops::RangeInclusive, time::Duration};
4
5
use crate::{Axis, ButtonInput, ButtonState};
6
use alloc::string::String;
7
#[cfg(feature = "bevy_reflect")]
8
use bevy_ecs::prelude::ReflectComponent;
9
use bevy_ecs::{
10
change_detection::DetectChangesMut,
11
component::Component,
12
entity::Entity,
13
event::{BufferedEvent, EventReader, EventWriter},
14
name::Name,
15
system::{Commands, Query},
16
};
17
use bevy_math::ops;
18
use bevy_math::Vec2;
19
use bevy_platform::collections::HashMap;
20
#[cfg(feature = "bevy_reflect")]
21
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
22
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
23
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
24
use derive_more::derive::From;
25
use log::{info, warn};
26
use thiserror::Error;
27
28
/// A gamepad event.
29
///
30
/// This event type is used over the [`GamepadConnectionEvent`],
31
/// [`GamepadButtonChangedEvent`] and [`GamepadAxisChangedEvent`] when
32
/// the in-frame relative ordering of events is important.
33
///
34
/// This event is produced by `bevy_input`.
35
#[derive(BufferedEvent, Debug, Clone, PartialEq, From)]
36
#[cfg_attr(
37
feature = "bevy_reflect",
38
derive(Reflect),
39
reflect(Debug, PartialEq, Clone)
40
)]
41
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
42
#[cfg_attr(
43
all(feature = "serialize", feature = "bevy_reflect"),
44
reflect(Serialize, Deserialize)
45
)]
46
pub enum GamepadEvent {
47
/// A gamepad has been connected or disconnected.
48
Connection(GamepadConnectionEvent),
49
/// A button of the gamepad has been triggered.
50
Button(GamepadButtonChangedEvent),
51
/// An axis of the gamepad has been triggered.
52
Axis(GamepadAxisChangedEvent),
53
}
54
55
/// A raw gamepad event.
56
///
57
/// This event type is used over the [`GamepadConnectionEvent`],
58
/// [`RawGamepadButtonChangedEvent`] and [`RawGamepadAxisChangedEvent`] when
59
/// the in-frame relative ordering of events is important.
60
///
61
/// This event type is used by `bevy_input` to feed its components.
62
#[derive(BufferedEvent, Debug, Clone, PartialEq, From)]
63
#[cfg_attr(
64
feature = "bevy_reflect",
65
derive(Reflect),
66
reflect(Debug, PartialEq, Clone)
67
)]
68
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
69
#[cfg_attr(
70
all(feature = "serialize", feature = "bevy_reflect"),
71
reflect(Serialize, Deserialize)
72
)]
73
pub enum RawGamepadEvent {
74
/// A gamepad has been connected or disconnected.
75
Connection(GamepadConnectionEvent),
76
/// A button of the gamepad has been triggered.
77
Button(RawGamepadButtonChangedEvent),
78
/// An axis of the gamepad has been triggered.
79
Axis(RawGamepadAxisChangedEvent),
80
}
81
82
/// [`GamepadButton`] changed event unfiltered by [`GamepadSettings`].
83
#[derive(BufferedEvent, Debug, Copy, Clone, PartialEq)]
84
#[cfg_attr(
85
feature = "bevy_reflect",
86
derive(Reflect),
87
reflect(Debug, PartialEq, Clone)
88
)]
89
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
90
#[cfg_attr(
91
all(feature = "serialize", feature = "bevy_reflect"),
92
reflect(Serialize, Deserialize)
93
)]
94
pub struct RawGamepadButtonChangedEvent {
95
/// The gamepad on which the button is triggered.
96
pub gamepad: Entity,
97
/// The type of the triggered button.
98
pub button: GamepadButton,
99
/// The value of the button.
100
pub value: f32,
101
}
102
103
impl RawGamepadButtonChangedEvent {
104
/// Creates a [`RawGamepadButtonChangedEvent`].
105
pub fn new(gamepad: Entity, button_type: GamepadButton, value: f32) -> Self {
106
Self {
107
gamepad,
108
button: button_type,
109
value,
110
}
111
}
112
}
113
114
/// [`GamepadAxis`] changed event unfiltered by [`GamepadSettings`].
115
#[derive(BufferedEvent, Debug, Copy, Clone, PartialEq)]
116
#[cfg_attr(
117
feature = "bevy_reflect",
118
derive(Reflect),
119
reflect(Debug, PartialEq, Clone)
120
)]
121
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
122
#[cfg_attr(
123
all(feature = "serialize", feature = "bevy_reflect"),
124
reflect(Serialize, Deserialize)
125
)]
126
pub struct RawGamepadAxisChangedEvent {
127
/// The gamepad on which the axis is triggered.
128
pub gamepad: Entity,
129
/// The type of the triggered axis.
130
pub axis: GamepadAxis,
131
/// The value of the axis.
132
pub value: f32,
133
}
134
135
impl RawGamepadAxisChangedEvent {
136
/// Creates a [`RawGamepadAxisChangedEvent`].
137
pub fn new(gamepad: Entity, axis_type: GamepadAxis, value: f32) -> Self {
138
Self {
139
gamepad,
140
axis: axis_type,
141
value,
142
}
143
}
144
}
145
146
/// A [`Gamepad`] connection event. Created when a connection to a gamepad
147
/// is established and when a gamepad is disconnected.
148
#[derive(BufferedEvent, Debug, Clone, PartialEq)]
149
#[cfg_attr(
150
feature = "bevy_reflect",
151
derive(Reflect),
152
reflect(Debug, PartialEq, Clone)
153
)]
154
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
155
#[cfg_attr(
156
all(feature = "serialize", feature = "bevy_reflect"),
157
reflect(Serialize, Deserialize)
158
)]
159
pub struct GamepadConnectionEvent {
160
/// The gamepad whose connection status changed.
161
pub gamepad: Entity,
162
/// The change in the gamepads connection.
163
pub connection: GamepadConnection,
164
}
165
166
impl GamepadConnectionEvent {
167
/// Creates a [`GamepadConnectionEvent`].
168
pub fn new(gamepad: Entity, connection: GamepadConnection) -> Self {
169
Self {
170
gamepad,
171
connection,
172
}
173
}
174
175
/// Whether the gamepad is connected.
176
pub fn connected(&self) -> bool {
177
matches!(self.connection, GamepadConnection::Connected { .. })
178
}
179
180
/// Whether the gamepad is disconnected.
181
pub fn disconnected(&self) -> bool {
182
!self.connected()
183
}
184
}
185
186
/// [`GamepadButton`] event triggered by a digital state change.
187
#[derive(BufferedEvent, Debug, Clone, Copy, PartialEq, Eq)]
188
#[cfg_attr(
189
feature = "bevy_reflect",
190
derive(Reflect),
191
reflect(Debug, PartialEq, Clone)
192
)]
193
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
194
#[cfg_attr(
195
all(feature = "serialize", feature = "bevy_reflect"),
196
reflect(Serialize, Deserialize)
197
)]
198
pub struct GamepadButtonStateChangedEvent {
199
/// The entity that represents this gamepad.
200
pub entity: Entity,
201
/// The gamepad button assigned to the event.
202
pub button: GamepadButton,
203
/// The pressed state of the button.
204
pub state: ButtonState,
205
}
206
207
impl GamepadButtonStateChangedEvent {
208
/// Creates a new [`GamepadButtonStateChangedEvent`].
209
pub fn new(entity: Entity, button: GamepadButton, state: ButtonState) -> Self {
210
Self {
211
entity,
212
button,
213
state,
214
}
215
}
216
}
217
218
/// [`GamepadButton`] event triggered by an analog state change.
219
#[derive(BufferedEvent, Debug, Clone, Copy, PartialEq)]
220
#[cfg_attr(
221
feature = "bevy_reflect",
222
derive(Reflect),
223
reflect(Debug, PartialEq, Clone)
224
)]
225
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
226
#[cfg_attr(
227
all(feature = "serialize", feature = "bevy_reflect"),
228
reflect(Serialize, Deserialize)
229
)]
230
pub struct GamepadButtonChangedEvent {
231
/// The entity that represents this gamepad.
232
pub entity: Entity,
233
/// The gamepad button assigned to the event.
234
pub button: GamepadButton,
235
/// The pressed state of the button.
236
pub state: ButtonState,
237
/// The analog value of the button (rescaled to be in the 0.0..=1.0 range).
238
pub value: f32,
239
}
240
241
impl GamepadButtonChangedEvent {
242
/// Creates a new [`GamepadButtonChangedEvent`].
243
pub fn new(entity: Entity, button: GamepadButton, state: ButtonState, value: f32) -> Self {
244
Self {
245
entity,
246
button,
247
state,
248
value,
249
}
250
}
251
}
252
253
/// [`GamepadAxis`] event triggered by an analog state change.
254
#[derive(BufferedEvent, Debug, Clone, Copy, PartialEq)]
255
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
256
#[cfg_attr(
257
feature = "bevy_reflect",
258
derive(Reflect),
259
reflect(Debug, PartialEq, Clone)
260
)]
261
#[cfg_attr(
262
all(feature = "bevy_reflect", feature = "serialize"),
263
reflect(Serialize, Deserialize)
264
)]
265
pub struct GamepadAxisChangedEvent {
266
/// The entity that represents this gamepad.
267
pub entity: Entity,
268
/// The gamepad axis assigned to the event.
269
pub axis: GamepadAxis,
270
/// The value of this axis (rescaled to account for axis settings).
271
pub value: f32,
272
}
273
274
impl GamepadAxisChangedEvent {
275
/// Creates a new [`GamepadAxisChangedEvent`].
276
pub fn new(entity: Entity, axis: GamepadAxis, value: f32) -> Self {
277
Self {
278
entity,
279
axis,
280
value,
281
}
282
}
283
}
284
285
/// Errors that occur when setting axis settings for gamepad input.
286
#[derive(Error, Debug, PartialEq)]
287
pub enum AxisSettingsError {
288
/// The given parameter `livezone_lowerbound` was not in range -1.0..=0.0.
289
#[error("invalid livezone_lowerbound {0}, expected value [-1.0..=0.0]")]
290
LiveZoneLowerBoundOutOfRange(f32),
291
/// The given parameter `deadzone_lowerbound` was not in range -1.0..=0.0.
292
#[error("invalid deadzone_lowerbound {0}, expected value [-1.0..=0.0]")]
293
DeadZoneLowerBoundOutOfRange(f32),
294
/// The given parameter `deadzone_lowerbound` was not in range -1.0..=0.0.
295
#[error("invalid deadzone_upperbound {0}, expected value [0.0..=1.0]")]
296
DeadZoneUpperBoundOutOfRange(f32),
297
/// The given parameter `deadzone_lowerbound` was not in range -1.0..=0.0.
298
#[error("invalid livezone_upperbound {0}, expected value [0.0..=1.0]")]
299
LiveZoneUpperBoundOutOfRange(f32),
300
/// Parameter `livezone_lowerbound` was not less than or equal to parameter `deadzone_lowerbound`.
301
#[error("invalid parameter values livezone_lowerbound {} deadzone_lowerbound {}, expected livezone_lowerbound <= deadzone_lowerbound", livezone_lowerbound, deadzone_lowerbound)]
302
LiveZoneLowerBoundGreaterThanDeadZoneLowerBound {
303
/// The value of the `livezone_lowerbound` parameter.
304
livezone_lowerbound: f32,
305
/// The value of the `deadzone_lowerbound` parameter.
306
deadzone_lowerbound: f32,
307
},
308
/// Parameter `deadzone_upperbound` was not less than or equal to parameter `livezone_upperbound`.
309
#[error("invalid parameter values livezone_upperbound {} deadzone_upperbound {}, expected deadzone_upperbound <= livezone_upperbound", livezone_upperbound, deadzone_upperbound)]
310
DeadZoneUpperBoundGreaterThanLiveZoneUpperBound {
311
/// The value of the `livezone_upperbound` parameter.
312
livezone_upperbound: f32,
313
/// The value of the `deadzone_upperbound` parameter.
314
deadzone_upperbound: f32,
315
},
316
/// The given parameter was not in range 0.0..=2.0.
317
#[error("invalid threshold {0}, expected 0.0 <= threshold <= 2.0")]
318
Threshold(f32),
319
}
320
321
/// Errors that occur when setting button settings for gamepad input.
322
#[derive(Error, Debug, PartialEq)]
323
pub enum ButtonSettingsError {
324
/// The given parameter was not in range 0.0..=1.0.
325
#[error("invalid release_threshold {0}, expected value [0.0..=1.0]")]
326
ReleaseThresholdOutOfRange(f32),
327
/// The given parameter was not in range 0.0..=1.0.
328
#[error("invalid press_threshold {0}, expected [0.0..=1.0]")]
329
PressThresholdOutOfRange(f32),
330
/// Parameter `release_threshold` was not less than or equal to `press_threshold`.
331
#[error("invalid parameter values release_threshold {} press_threshold {}, expected release_threshold <= press_threshold", release_threshold, press_threshold)]
332
ReleaseThresholdGreaterThanPressThreshold {
333
/// The value of the `press_threshold` parameter.
334
press_threshold: f32,
335
/// The value of the `release_threshold` parameter.
336
release_threshold: f32,
337
},
338
}
339
340
/// Stores a connected gamepad's metadata such as the name and its [`GamepadButton`] and [`GamepadAxis`].
341
///
342
/// An entity with this component is spawned automatically after [`GamepadConnectionEvent`]
343
/// and updated by [`gamepad_event_processing_system`].
344
///
345
/// See also [`GamepadSettings`] for configuration.
346
///
347
/// # Examples
348
///
349
/// ```
350
/// # use bevy_input::gamepad::{Gamepad, GamepadAxis, GamepadButton};
351
/// # use bevy_ecs::system::Query;
352
/// # use bevy_ecs::name::Name;
353
/// #
354
/// fn gamepad_usage_system(gamepads: Query<(&Name, &Gamepad)>) {
355
/// for (name, gamepad) in &gamepads {
356
/// println!("{name}");
357
///
358
/// if gamepad.just_pressed(GamepadButton::North) {
359
/// println!("{} just pressed North", name)
360
/// }
361
///
362
/// if let Some(left_stick_x) = gamepad.get(GamepadAxis::LeftStickX) {
363
/// println!("left stick X: {}", left_stick_x)
364
/// }
365
/// }
366
/// }
367
/// ```
368
#[derive(Component, Debug)]
369
#[cfg_attr(
370
feature = "bevy_reflect",
371
derive(Reflect),
372
reflect(Debug, Component, Default)
373
)]
374
#[require(GamepadSettings)]
375
pub struct Gamepad {
376
/// The USB vendor ID as assigned by the USB-IF, if available.
377
pub(crate) vendor_id: Option<u16>,
378
379
/// The USB product ID as assigned by the [vendor][Self::vendor_id], if available.
380
pub(crate) product_id: Option<u16>,
381
382
/// [`ButtonInput`] of [`GamepadButton`] representing their digital state.
383
pub(crate) digital: ButtonInput<GamepadButton>,
384
385
/// [`Axis`] of [`GamepadButton`] representing their analog state.
386
pub(crate) analog: Axis<GamepadInput>,
387
}
388
389
impl Gamepad {
390
/// Returns the USB vendor ID as assigned by the USB-IF, if available.
391
pub fn vendor_id(&self) -> Option<u16> {
392
self.vendor_id
393
}
394
395
/// Returns the USB product ID as assigned by the [vendor], if available.
396
///
397
/// [vendor]: Self::vendor_id
398
pub fn product_id(&self) -> Option<u16> {
399
self.product_id
400
}
401
402
/// Returns the analog data of the provided [`GamepadAxis`] or [`GamepadButton`].
403
///
404
/// This will be clamped between [[`Axis::MIN`],[`Axis::MAX`]].
405
pub fn get(&self, input: impl Into<GamepadInput>) -> Option<f32> {
406
self.analog.get(input.into())
407
}
408
409
/// Returns the unclamped analog data of the provided [`GamepadAxis`] or [`GamepadButton`].
410
///
411
/// This value may be outside the [`Axis::MIN`] and [`Axis::MAX`] range.
412
pub fn get_unclamped(&self, input: impl Into<GamepadInput>) -> Option<f32> {
413
self.analog.get_unclamped(input.into())
414
}
415
416
/// Returns the left stick as a [`Vec2`].
417
pub fn left_stick(&self) -> Vec2 {
418
Vec2 {
419
x: self.get(GamepadAxis::LeftStickX).unwrap_or(0.0),
420
y: self.get(GamepadAxis::LeftStickY).unwrap_or(0.0),
421
}
422
}
423
424
/// Returns the right stick as a [`Vec2`].
425
pub fn right_stick(&self) -> Vec2 {
426
Vec2 {
427
x: self.get(GamepadAxis::RightStickX).unwrap_or(0.0),
428
y: self.get(GamepadAxis::RightStickY).unwrap_or(0.0),
429
}
430
}
431
432
/// Returns the directional pad as a [`Vec2`].
433
pub fn dpad(&self) -> Vec2 {
434
Vec2 {
435
x: self.get(GamepadButton::DPadRight).unwrap_or(0.0)
436
- self.get(GamepadButton::DPadLeft).unwrap_or(0.0),
437
y: self.get(GamepadButton::DPadUp).unwrap_or(0.0)
438
- self.get(GamepadButton::DPadDown).unwrap_or(0.0),
439
}
440
}
441
442
/// Returns `true` if the [`GamepadButton`] has been pressed.
443
pub fn pressed(&self, button_type: GamepadButton) -> bool {
444
self.digital.pressed(button_type)
445
}
446
447
/// Returns `true` if any item in the [`GamepadButton`] iterator has been pressed.
448
pub fn any_pressed(&self, button_inputs: impl IntoIterator<Item = GamepadButton>) -> bool {
449
self.digital.any_pressed(button_inputs)
450
}
451
452
/// Returns `true` if all items in the [`GamepadButton`] iterator have been pressed.
453
pub fn all_pressed(&self, button_inputs: impl IntoIterator<Item = GamepadButton>) -> bool {
454
self.digital.all_pressed(button_inputs)
455
}
456
457
/// Returns `true` if the [`GamepadButton`] has been pressed during the current frame.
458
///
459
/// Note: This function does not imply information regarding the current state of [`ButtonInput::pressed`] or [`ButtonInput::just_released`].
460
pub fn just_pressed(&self, button_type: GamepadButton) -> bool {
461
self.digital.just_pressed(button_type)
462
}
463
464
/// Returns `true` if any item in the [`GamepadButton`] iterator has been pressed during the current frame.
465
pub fn any_just_pressed(&self, button_inputs: impl IntoIterator<Item = GamepadButton>) -> bool {
466
self.digital.any_just_pressed(button_inputs)
467
}
468
469
/// Returns `true` if all items in the [`GamepadButton`] iterator have been just pressed.
470
pub fn all_just_pressed(&self, button_inputs: impl IntoIterator<Item = GamepadButton>) -> bool {
471
self.digital.all_just_pressed(button_inputs)
472
}
473
474
/// Returns `true` if the [`GamepadButton`] has been released during the current frame.
475
///
476
/// Note: This function does not imply information regarding the current state of [`ButtonInput::pressed`] or [`ButtonInput::just_pressed`].
477
pub fn just_released(&self, button_type: GamepadButton) -> bool {
478
self.digital.just_released(button_type)
479
}
480
481
/// Returns `true` if any item in the [`GamepadButton`] iterator has just been released.
482
pub fn any_just_released(
483
&self,
484
button_inputs: impl IntoIterator<Item = GamepadButton>,
485
) -> bool {
486
self.digital.any_just_released(button_inputs)
487
}
488
489
/// Returns `true` if all items in the [`GamepadButton`] iterator have just been released.
490
pub fn all_just_released(
491
&self,
492
button_inputs: impl IntoIterator<Item = GamepadButton>,
493
) -> bool {
494
self.digital.all_just_released(button_inputs)
495
}
496
497
/// Returns an iterator over all digital [button]s that are pressed.
498
///
499
/// [button]: GamepadButton
500
pub fn get_pressed(&self) -> impl Iterator<Item = &GamepadButton> {
501
self.digital.get_pressed()
502
}
503
504
/// Returns an iterator over all digital [button]s that were just pressed.
505
///
506
/// [button]: GamepadButton
507
pub fn get_just_pressed(&self) -> impl Iterator<Item = &GamepadButton> {
508
self.digital.get_just_pressed()
509
}
510
511
/// Returns an iterator over all digital [button]s that were just released.
512
///
513
/// [button]: GamepadButton
514
pub fn get_just_released(&self) -> impl Iterator<Item = &GamepadButton> {
515
self.digital.get_just_released()
516
}
517
518
/// Returns an iterator over all analog [axes][GamepadInput].
519
pub fn get_analog_axes(&self) -> impl Iterator<Item = &GamepadInput> {
520
self.analog.all_axes()
521
}
522
523
/// [`ButtonInput`] of [`GamepadButton`] representing their digital state.
524
pub fn digital(&self) -> &ButtonInput<GamepadButton> {
525
&self.digital
526
}
527
528
/// Mutable [`ButtonInput`] of [`GamepadButton`] representing their digital state. Useful for mocking inputs.
529
pub fn digital_mut(&mut self) -> &mut ButtonInput<GamepadButton> {
530
&mut self.digital
531
}
532
533
/// [`Axis`] of [`GamepadButton`] representing their analog state.
534
pub fn analog(&self) -> &Axis<GamepadInput> {
535
&self.analog
536
}
537
538
/// Mutable [`Axis`] of [`GamepadButton`] representing their analog state. Useful for mocking inputs.
539
pub fn analog_mut(&mut self) -> &mut Axis<GamepadInput> {
540
&mut self.analog
541
}
542
}
543
544
impl Default for Gamepad {
545
fn default() -> Self {
546
let mut analog = Axis::default();
547
for button in GamepadButton::all().iter().copied() {
548
analog.set(button, 0.0);
549
}
550
for axis_type in GamepadAxis::all().iter().copied() {
551
analog.set(axis_type, 0.0);
552
}
553
554
Self {
555
vendor_id: None,
556
product_id: None,
557
digital: Default::default(),
558
analog,
559
}
560
}
561
}
562
563
/// Represents gamepad input types that are mapped in the range [0.0, 1.0].
564
///
565
/// ## Usage
566
///
567
/// This is used to determine which button has changed its value when receiving gamepad button events.
568
/// It is also used in the [`Gamepad`] component.
569
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
570
#[cfg_attr(
571
feature = "bevy_reflect",
572
derive(Reflect),
573
reflect(Debug, Hash, PartialEq, Clone)
574
)]
575
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
576
#[cfg_attr(
577
all(feature = "serialize", feature = "bevy_reflect"),
578
reflect(Serialize, Deserialize)
579
)]
580
pub enum GamepadButton {
581
/// The bottom action button of the action pad (i.e. PS: Cross, Xbox: A).
582
South,
583
/// The right action button of the action pad (i.e. PS: Circle, Xbox: B).
584
East,
585
/// The upper action button of the action pad (i.e. PS: Triangle, Xbox: Y).
586
North,
587
/// The left action button of the action pad (i.e. PS: Square, Xbox: X).
588
West,
589
590
/// The C button.
591
C,
592
/// The Z button.
593
Z,
594
595
/// The first left trigger.
596
LeftTrigger,
597
/// The second left trigger.
598
LeftTrigger2,
599
/// The first right trigger.
600
RightTrigger,
601
/// The second right trigger.
602
RightTrigger2,
603
/// The select button.
604
Select,
605
/// The start button.
606
Start,
607
/// The mode button.
608
Mode,
609
610
/// The left thumb stick button.
611
LeftThumb,
612
/// The right thumb stick button.
613
RightThumb,
614
615
/// The up button of the D-Pad.
616
DPadUp,
617
/// The down button of the D-Pad.
618
DPadDown,
619
/// The left button of the D-Pad.
620
DPadLeft,
621
/// The right button of the D-Pad.
622
DPadRight,
623
624
/// Miscellaneous buttons, considered non-standard (i.e. Extra buttons on a flight stick that do not have a gamepad equivalent).
625
Other(u8),
626
}
627
628
impl GamepadButton {
629
/// Returns an array of all the standard [`GamepadButton`].
630
pub const fn all() -> [GamepadButton; 19] {
631
[
632
GamepadButton::South,
633
GamepadButton::East,
634
GamepadButton::North,
635
GamepadButton::West,
636
GamepadButton::C,
637
GamepadButton::Z,
638
GamepadButton::LeftTrigger,
639
GamepadButton::LeftTrigger2,
640
GamepadButton::RightTrigger,
641
GamepadButton::RightTrigger2,
642
GamepadButton::Select,
643
GamepadButton::Start,
644
GamepadButton::Mode,
645
GamepadButton::LeftThumb,
646
GamepadButton::RightThumb,
647
GamepadButton::DPadUp,
648
GamepadButton::DPadDown,
649
GamepadButton::DPadLeft,
650
GamepadButton::DPadRight,
651
]
652
}
653
}
654
655
/// Represents gamepad input types that are mapped in the range [-1.0, 1.0].
656
///
657
/// ## Usage
658
///
659
/// This is used to determine which axis has changed its value when receiving a
660
/// gamepad axis event. It is also used in the [`Gamepad`] component.
661
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
662
#[cfg_attr(
663
feature = "bevy_reflect",
664
derive(Reflect),
665
reflect(Debug, PartialEq, Hash, Clone)
666
)]
667
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
668
#[cfg_attr(
669
all(feature = "serialize", feature = "bevy_reflect"),
670
reflect(Serialize, Deserialize)
671
)]
672
pub enum GamepadAxis {
673
/// The horizontal value of the left stick.
674
LeftStickX,
675
/// The vertical value of the left stick.
676
LeftStickY,
677
/// Generally the throttle axis of a HOTAS setup.
678
/// Refer to [`GamepadButton::LeftTrigger2`] for the analog trigger on a gamepad controller.
679
LeftZ,
680
/// The horizontal value of the right stick.
681
RightStickX,
682
/// The vertical value of the right stick.
683
RightStickY,
684
/// The yaw of the main joystick, not supported on common gamepads.
685
/// Refer to [`GamepadButton::RightTrigger2`] for the analog trigger on a gamepad controller.
686
RightZ,
687
/// Non-standard support for other axis types (i.e. HOTAS sliders, potentiometers, etc).
688
Other(u8),
689
}
690
691
impl GamepadAxis {
692
/// Returns an array of all the standard [`GamepadAxis`].
693
pub const fn all() -> [GamepadAxis; 6] {
694
[
695
GamepadAxis::LeftStickX,
696
GamepadAxis::LeftStickY,
697
GamepadAxis::LeftZ,
698
GamepadAxis::RightStickX,
699
GamepadAxis::RightStickY,
700
GamepadAxis::RightZ,
701
]
702
}
703
}
704
705
/// Encapsulation over [`GamepadAxis`] and [`GamepadButton`].
706
// This is done so Gamepad can share a single Axis<T> and simplifies the API by having only one get/get_unclamped method
707
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, From)]
708
#[cfg_attr(
709
feature = "bevy_reflect",
710
derive(Reflect),
711
reflect(Debug, Hash, PartialEq, Clone)
712
)]
713
pub enum GamepadInput {
714
/// A [`GamepadAxis`].
715
Axis(GamepadAxis),
716
/// A [`GamepadButton`].
717
Button(GamepadButton),
718
}
719
720
/// Gamepad settings component.
721
///
722
/// ## Usage
723
///
724
/// It is used to create a `bevy` component that stores the settings of [`GamepadButton`] and [`GamepadAxis`] in [`Gamepad`].
725
/// If no user defined [`ButtonSettings`], [`AxisSettings`], or [`ButtonAxisSettings`]
726
/// are defined, the default settings of each are used as a fallback accordingly.
727
///
728
/// ## Note
729
///
730
/// The [`GamepadSettings`] are used to determine when raw gamepad events
731
/// should register. Events that don't meet the change thresholds defined in [`GamepadSettings`]
732
/// will not register. To modify these settings, mutate the corresponding component.
733
#[derive(Component, Clone, Default, Debug)]
734
#[cfg_attr(
735
feature = "bevy_reflect",
736
derive(Reflect),
737
reflect(Debug, Default, Component, Clone)
738
)]
739
pub struct GamepadSettings {
740
/// The default button settings.
741
pub default_button_settings: ButtonSettings,
742
/// The default axis settings.
743
pub default_axis_settings: AxisSettings,
744
/// The default button axis settings.
745
pub default_button_axis_settings: ButtonAxisSettings,
746
/// The user defined button settings.
747
pub button_settings: HashMap<GamepadButton, ButtonSettings>,
748
/// The user defined axis settings.
749
pub axis_settings: HashMap<GamepadAxis, AxisSettings>,
750
/// The user defined button axis settings.
751
pub button_axis_settings: HashMap<GamepadButton, ButtonAxisSettings>,
752
}
753
754
impl GamepadSettings {
755
/// Returns the [`ButtonSettings`] of the [`GamepadButton`].
756
///
757
/// If no user defined [`ButtonSettings`] are specified the default [`ButtonSettings`] get returned.
758
///
759
/// # Examples
760
///
761
/// ```
762
/// # use bevy_input::gamepad::{GamepadSettings, GamepadButton};
763
/// #
764
/// # let settings = GamepadSettings::default();
765
/// let button_settings = settings.get_button_settings(GamepadButton::South);
766
/// ```
767
pub fn get_button_settings(&self, button: GamepadButton) -> &ButtonSettings {
768
self.button_settings
769
.get(&button)
770
.unwrap_or(&self.default_button_settings)
771
}
772
773
/// Returns the [`AxisSettings`] of the [`GamepadAxis`].
774
///
775
/// If no user defined [`AxisSettings`] are specified the default [`AxisSettings`] get returned.
776
///
777
/// # Examples
778
///
779
/// ```
780
/// # use bevy_input::gamepad::{GamepadSettings, GamepadAxis};
781
/// #
782
/// # let settings = GamepadSettings::default();
783
/// let axis_settings = settings.get_axis_settings(GamepadAxis::LeftStickX);
784
/// ```
785
pub fn get_axis_settings(&self, axis: GamepadAxis) -> &AxisSettings {
786
self.axis_settings
787
.get(&axis)
788
.unwrap_or(&self.default_axis_settings)
789
}
790
791
/// Returns the [`ButtonAxisSettings`] of the [`GamepadButton`].
792
///
793
/// If no user defined [`ButtonAxisSettings`] are specified the default [`ButtonAxisSettings`] get returned.
794
///
795
/// # Examples
796
///
797
/// ```
798
/// # use bevy_input::gamepad::{GamepadSettings, GamepadButton};
799
/// #
800
/// # let settings = GamepadSettings::default();
801
/// let button_axis_settings = settings.get_button_axis_settings(GamepadButton::South);
802
/// ```
803
pub fn get_button_axis_settings(&self, button: GamepadButton) -> &ButtonAxisSettings {
804
self.button_axis_settings
805
.get(&button)
806
.unwrap_or(&self.default_button_axis_settings)
807
}
808
}
809
810
/// Manages settings for gamepad buttons.
811
///
812
/// It is used inside [`GamepadSettings`] to define the threshold for a [`GamepadButton`]
813
/// to be considered pressed or released. A button is considered pressed if the `press_threshold`
814
/// value is surpassed and released if the `release_threshold` value is undercut.
815
///
816
/// Allowed values: `0.0 <= ``release_threshold`` <= ``press_threshold`` <= 1.0`
817
#[derive(Debug, PartialEq, Clone)]
818
#[cfg_attr(
819
feature = "bevy_reflect",
820
derive(Reflect),
821
reflect(Debug, Default, Clone)
822
)]
823
pub struct ButtonSettings {
824
press_threshold: f32,
825
release_threshold: f32,
826
}
827
828
impl Default for ButtonSettings {
829
fn default() -> Self {
830
ButtonSettings {
831
press_threshold: 0.75,
832
release_threshold: 0.65,
833
}
834
}
835
}
836
837
impl ButtonSettings {
838
/// Creates a new [`ButtonSettings`] instance.
839
///
840
/// # Parameters
841
///
842
/// + `press_threshold` is the button input value above which the button is considered pressed.
843
/// + `release_threshold` is the button input value below which the button is considered released.
844
///
845
/// Restrictions:
846
/// + `0.0 <= ``release_threshold`` <= ``press_threshold`` <= 1.0`
847
///
848
/// # Errors
849
///
850
/// If the restrictions are not met, returns one of
851
/// `GamepadSettingsError::ButtonReleaseThresholdOutOfRange`,
852
/// `GamepadSettingsError::ButtonPressThresholdOutOfRange`, or
853
/// `GamepadSettingsError::ButtonReleaseThresholdGreaterThanPressThreshold`.
854
pub fn new(
855
press_threshold: f32,
856
release_threshold: f32,
857
) -> Result<ButtonSettings, ButtonSettingsError> {
858
if !(0.0..=1.0).contains(&release_threshold) {
859
Err(ButtonSettingsError::ReleaseThresholdOutOfRange(
860
release_threshold,
861
))
862
} else if !(0.0..=1.0).contains(&press_threshold) {
863
Err(ButtonSettingsError::PressThresholdOutOfRange(
864
press_threshold,
865
))
866
} else if release_threshold > press_threshold {
867
Err(
868
ButtonSettingsError::ReleaseThresholdGreaterThanPressThreshold {
869
press_threshold,
870
release_threshold,
871
},
872
)
873
} else {
874
Ok(ButtonSettings {
875
press_threshold,
876
release_threshold,
877
})
878
}
879
}
880
881
/// Returns `true` if the button is pressed.
882
///
883
/// A button is considered pressed if the `value` passed is greater than or equal to the press threshold.
884
pub fn is_pressed(&self, value: f32) -> bool {
885
value >= self.press_threshold
886
}
887
888
/// Returns `true` if the button is released.
889
///
890
/// A button is considered released if the `value` passed is lower than or equal to the release threshold.
891
pub fn is_released(&self, value: f32) -> bool {
892
value <= self.release_threshold
893
}
894
895
/// Get the button input threshold above which the button is considered pressed.
896
pub fn press_threshold(&self) -> f32 {
897
self.press_threshold
898
}
899
900
/// Try to set the button input threshold above which the button is considered pressed.
901
///
902
/// # Errors
903
///
904
/// If the value passed is outside the range [release threshold..=1.0], returns either
905
/// `GamepadSettingsError::ButtonPressThresholdOutOfRange` or
906
/// `GamepadSettingsError::ButtonReleaseThresholdGreaterThanPressThreshold`.
907
pub fn try_set_press_threshold(&mut self, value: f32) -> Result<(), ButtonSettingsError> {
908
if (self.release_threshold..=1.0).contains(&value) {
909
self.press_threshold = value;
910
Ok(())
911
} else if !(0.0..1.0).contains(&value) {
912
Err(ButtonSettingsError::PressThresholdOutOfRange(value))
913
} else {
914
Err(
915
ButtonSettingsError::ReleaseThresholdGreaterThanPressThreshold {
916
press_threshold: value,
917
release_threshold: self.release_threshold,
918
},
919
)
920
}
921
}
922
923
/// Try to set the button input threshold above which the button is considered pressed.
924
/// If the value passed is outside the range [release threshold..=1.0], the value will not be changed.
925
///
926
/// Returns the new value of the press threshold.
927
pub fn set_press_threshold(&mut self, value: f32) -> f32 {
928
self.try_set_press_threshold(value).ok();
929
self.press_threshold
930
}
931
932
/// Get the button input threshold below which the button is considered released.
933
pub fn release_threshold(&self) -> f32 {
934
self.release_threshold
935
}
936
937
/// Try to set the button input threshold below which the button is considered released.
938
///
939
/// # Errors
940
///
941
/// If the value passed is outside the range [0.0..=press threshold], returns
942
/// `ButtonSettingsError::ReleaseThresholdOutOfRange` or
943
/// `ButtonSettingsError::ReleaseThresholdGreaterThanPressThreshold`.
944
pub fn try_set_release_threshold(&mut self, value: f32) -> Result<(), ButtonSettingsError> {
945
if (0.0..=self.press_threshold).contains(&value) {
946
self.release_threshold = value;
947
Ok(())
948
} else if !(0.0..1.0).contains(&value) {
949
Err(ButtonSettingsError::ReleaseThresholdOutOfRange(value))
950
} else {
951
Err(
952
ButtonSettingsError::ReleaseThresholdGreaterThanPressThreshold {
953
press_threshold: self.press_threshold,
954
release_threshold: value,
955
},
956
)
957
}
958
}
959
960
/// Try to set the button input threshold below which the button is considered released. If the
961
/// value passed is outside the range [0.0..=press threshold], the value will not be changed.
962
///
963
/// Returns the new value of the release threshold.
964
pub fn set_release_threshold(&mut self, value: f32) -> f32 {
965
self.try_set_release_threshold(value).ok();
966
self.release_threshold
967
}
968
}
969
970
/// Settings for a [`GamepadAxis`].
971
///
972
/// It is used inside the [`GamepadSettings`] to define the sensitivity range and
973
/// threshold for an axis.
974
/// Values that are higher than `livezone_upperbound` will be rounded up to 1.0.
975
/// Values that are lower than `livezone_lowerbound` will be rounded down to -1.0.
976
/// Values that are in-between `deadzone_lowerbound` and `deadzone_upperbound` will be rounded to 0.0.
977
/// Otherwise, values will be linearly rescaled to fit into the sensitivity range.
978
/// For example, a value that is one fourth of the way from `deadzone_upperbound` to `livezone_upperbound` will be scaled to 0.25.
979
///
980
/// The valid range is `[-1.0, 1.0]`.
981
#[derive(Debug, Clone, PartialEq)]
982
#[cfg_attr(
983
feature = "bevy_reflect",
984
derive(Reflect),
985
reflect(Debug, PartialEq, Default, Clone)
986
)]
987
pub struct AxisSettings {
988
/// Values that are higher than `livezone_upperbound` will be rounded up to 1.0.
989
livezone_upperbound: f32,
990
/// Positive values that are less than `deadzone_upperbound` will be rounded down to 0.0.
991
deadzone_upperbound: f32,
992
/// Negative values that are greater than `deadzone_lowerbound` will be rounded up to 0.0.
993
deadzone_lowerbound: f32,
994
/// Values that are lower than `livezone_lowerbound` will be rounded down to -1.0.
995
livezone_lowerbound: f32,
996
/// `threshold` defines the minimum difference between old and new values to apply the changes.
997
threshold: f32,
998
}
999
1000
impl Default for AxisSettings {
1001
fn default() -> Self {
1002
AxisSettings {
1003
livezone_upperbound: 1.0,
1004
deadzone_upperbound: 0.05,
1005
deadzone_lowerbound: -0.05,
1006
livezone_lowerbound: -1.0,
1007
threshold: 0.01,
1008
}
1009
}
1010
}
1011
1012
impl AxisSettings {
1013
/// Creates a new [`AxisSettings`] instance.
1014
///
1015
/// # Arguments
1016
///
1017
/// + `livezone_lowerbound` - the value below which inputs will be rounded down to -1.0.
1018
/// + `deadzone_lowerbound` - the value above which negative inputs will be rounded up to 0.0.
1019
/// + `deadzone_upperbound` - the value below which positive inputs will be rounded down to 0.0.
1020
/// + `livezone_upperbound` - the value above which inputs will be rounded up to 1.0.
1021
/// + `threshold` - the minimum value by which input must change before the change is registered.
1022
///
1023
/// Restrictions:
1024
///
1025
/// + `-1.0 <= livezone_lowerbound <= deadzone_lowerbound <= 0.0`
1026
/// + `0.0 <= deadzone_upperbound <= livezone_upperbound <= 1.0`
1027
/// + `0.0 <= threshold <= 2.0`
1028
///
1029
/// # Errors
1030
///
1031
/// Returns an [`AxisSettingsError`] if any restrictions on the zone values are not met.
1032
/// If the zone restrictions are met, but the `threshold` value restrictions are not met,
1033
/// returns [`AxisSettingsError::Threshold`].
1034
pub fn new(
1035
livezone_lowerbound: f32,
1036
deadzone_lowerbound: f32,
1037
deadzone_upperbound: f32,
1038
livezone_upperbound: f32,
1039
threshold: f32,
1040
) -> Result<AxisSettings, AxisSettingsError> {
1041
if !(-1.0..=0.0).contains(&livezone_lowerbound) {
1042
Err(AxisSettingsError::LiveZoneLowerBoundOutOfRange(
1043
livezone_lowerbound,
1044
))
1045
} else if !(-1.0..=0.0).contains(&deadzone_lowerbound) {
1046
Err(AxisSettingsError::DeadZoneLowerBoundOutOfRange(
1047
deadzone_lowerbound,
1048
))
1049
} else if !(0.0..=1.0).contains(&deadzone_upperbound) {
1050
Err(AxisSettingsError::DeadZoneUpperBoundOutOfRange(
1051
deadzone_upperbound,
1052
))
1053
} else if !(0.0..=1.0).contains(&livezone_upperbound) {
1054
Err(AxisSettingsError::LiveZoneUpperBoundOutOfRange(
1055
livezone_upperbound,
1056
))
1057
} else if livezone_lowerbound > deadzone_lowerbound {
1058
Err(
1059
AxisSettingsError::LiveZoneLowerBoundGreaterThanDeadZoneLowerBound {
1060
livezone_lowerbound,
1061
deadzone_lowerbound,
1062
},
1063
)
1064
} else if deadzone_upperbound > livezone_upperbound {
1065
Err(
1066
AxisSettingsError::DeadZoneUpperBoundGreaterThanLiveZoneUpperBound {
1067
livezone_upperbound,
1068
deadzone_upperbound,
1069
},
1070
)
1071
} else if !(0.0..=2.0).contains(&threshold) {
1072
Err(AxisSettingsError::Threshold(threshold))
1073
} else {
1074
Ok(Self {
1075
livezone_lowerbound,
1076
deadzone_lowerbound,
1077
deadzone_upperbound,
1078
livezone_upperbound,
1079
threshold,
1080
})
1081
}
1082
}
1083
1084
/// Get the value above which inputs will be rounded up to 1.0.
1085
pub fn livezone_upperbound(&self) -> f32 {
1086
self.livezone_upperbound
1087
}
1088
1089
/// Try to set the value above which inputs will be rounded up to 1.0.
1090
///
1091
/// # Errors
1092
///
1093
/// If the value passed is less than the deadzone upper bound,
1094
/// returns `AxisSettingsError::DeadZoneUpperBoundGreaterThanLiveZoneUpperBound`.
1095
/// If the value passed is not in range [0.0..=1.0], returns `AxisSettingsError::LiveZoneUpperBoundOutOfRange`.
1096
pub fn try_set_livezone_upperbound(&mut self, value: f32) -> Result<(), AxisSettingsError> {
1097
if !(0.0..=1.0).contains(&value) {
1098
Err(AxisSettingsError::LiveZoneUpperBoundOutOfRange(value))
1099
} else if value < self.deadzone_upperbound {
1100
Err(
1101
AxisSettingsError::DeadZoneUpperBoundGreaterThanLiveZoneUpperBound {
1102
livezone_upperbound: value,
1103
deadzone_upperbound: self.deadzone_upperbound,
1104
},
1105
)
1106
} else {
1107
self.livezone_upperbound = value;
1108
Ok(())
1109
}
1110
}
1111
1112
/// Try to set the value above which inputs will be rounded up to 1.0.
1113
/// If the value passed is negative or less than `deadzone_upperbound`,
1114
/// the value will not be changed.
1115
///
1116
/// Returns the new value of `livezone_upperbound`.
1117
pub fn set_livezone_upperbound(&mut self, value: f32) -> f32 {
1118
self.try_set_livezone_upperbound(value).ok();
1119
self.livezone_upperbound
1120
}
1121
1122
/// Get the value below which positive inputs will be rounded down to 0.0.
1123
pub fn deadzone_upperbound(&self) -> f32 {
1124
self.deadzone_upperbound
1125
}
1126
1127
/// Try to set the value below which positive inputs will be rounded down to 0.0.
1128
///
1129
/// # Errors
1130
///
1131
/// If the value passed is greater than the live zone upper bound,
1132
/// returns `AxisSettingsError::DeadZoneUpperBoundGreaterThanLiveZoneUpperBound`.
1133
/// If the value passed is not in range [0.0..=1.0], returns `AxisSettingsError::DeadZoneUpperBoundOutOfRange`.
1134
pub fn try_set_deadzone_upperbound(&mut self, value: f32) -> Result<(), AxisSettingsError> {
1135
if !(0.0..=1.0).contains(&value) {
1136
Err(AxisSettingsError::DeadZoneUpperBoundOutOfRange(value))
1137
} else if self.livezone_upperbound < value {
1138
Err(
1139
AxisSettingsError::DeadZoneUpperBoundGreaterThanLiveZoneUpperBound {
1140
livezone_upperbound: self.livezone_upperbound,
1141
deadzone_upperbound: value,
1142
},
1143
)
1144
} else {
1145
self.deadzone_upperbound = value;
1146
Ok(())
1147
}
1148
}
1149
1150
/// Try to set the value below which positive inputs will be rounded down to 0.0.
1151
/// If the value passed is negative or greater than `livezone_upperbound`,
1152
/// the value will not be changed.
1153
///
1154
/// Returns the new value of `deadzone_upperbound`.
1155
pub fn set_deadzone_upperbound(&mut self, value: f32) -> f32 {
1156
self.try_set_deadzone_upperbound(value).ok();
1157
self.deadzone_upperbound
1158
}
1159
1160
/// Get the value below which negative inputs will be rounded down to -1.0.
1161
pub fn livezone_lowerbound(&self) -> f32 {
1162
self.livezone_lowerbound
1163
}
1164
1165
/// Try to set the value below which negative inputs will be rounded down to -1.0.
1166
///
1167
/// # Errors
1168
///
1169
/// If the value passed is less than the deadzone lower bound,
1170
/// returns `AxisSettingsError::LiveZoneLowerBoundGreaterThanDeadZoneLowerBound`.
1171
/// If the value passed is not in range [-1.0..=0.0], returns `AxisSettingsError::LiveZoneLowerBoundOutOfRange`.
1172
pub fn try_set_livezone_lowerbound(&mut self, value: f32) -> Result<(), AxisSettingsError> {
1173
if !(-1.0..=0.0).contains(&value) {
1174
Err(AxisSettingsError::LiveZoneLowerBoundOutOfRange(value))
1175
} else if value > self.deadzone_lowerbound {
1176
Err(
1177
AxisSettingsError::LiveZoneLowerBoundGreaterThanDeadZoneLowerBound {
1178
livezone_lowerbound: value,
1179
deadzone_lowerbound: self.deadzone_lowerbound,
1180
},
1181
)
1182
} else {
1183
self.livezone_lowerbound = value;
1184
Ok(())
1185
}
1186
}
1187
1188
/// Try to set the value below which negative inputs will be rounded down to -1.0.
1189
/// If the value passed is positive or greater than `deadzone_lowerbound`,
1190
/// the value will not be changed.
1191
///
1192
/// Returns the new value of `livezone_lowerbound`.
1193
pub fn set_livezone_lowerbound(&mut self, value: f32) -> f32 {
1194
self.try_set_livezone_lowerbound(value).ok();
1195
self.livezone_lowerbound
1196
}
1197
1198
/// Get the value above which inputs will be rounded up to 0.0.
1199
pub fn deadzone_lowerbound(&self) -> f32 {
1200
self.deadzone_lowerbound
1201
}
1202
1203
/// Try to set the value above which inputs will be rounded up to 0.0.
1204
///
1205
/// # Errors
1206
///
1207
/// If the value passed is less than the live zone lower bound,
1208
/// returns `AxisSettingsError::LiveZoneLowerBoundGreaterThanDeadZoneLowerBound`.
1209
/// If the value passed is not in range [-1.0..=0.0], returns `AxisSettingsError::DeadZoneLowerBoundOutOfRange`.
1210
pub fn try_set_deadzone_lowerbound(&mut self, value: f32) -> Result<(), AxisSettingsError> {
1211
if !(-1.0..=0.0).contains(&value) {
1212
Err(AxisSettingsError::DeadZoneLowerBoundOutOfRange(value))
1213
} else if self.livezone_lowerbound > value {
1214
Err(
1215
AxisSettingsError::LiveZoneLowerBoundGreaterThanDeadZoneLowerBound {
1216
livezone_lowerbound: self.livezone_lowerbound,
1217
deadzone_lowerbound: value,
1218
},
1219
)
1220
} else {
1221
self.deadzone_lowerbound = value;
1222
Ok(())
1223
}
1224
}
1225
1226
/// Try to set the value above which inputs will be rounded up to 0.0.
1227
/// If the value passed is less than -1.0 or less than `livezone_lowerbound`,
1228
/// the value will not be changed.
1229
///
1230
/// Returns the new value of `deadzone_lowerbound`.
1231
pub fn set_deadzone_lowerbound(&mut self, value: f32) -> f32 {
1232
self.try_set_deadzone_lowerbound(value).ok();
1233
self.deadzone_lowerbound
1234
}
1235
1236
/// Get the minimum value by which input must change before the change is registered.
1237
pub fn threshold(&self) -> f32 {
1238
self.threshold
1239
}
1240
1241
/// Try to set the minimum value by which input must change before the change is registered.
1242
///
1243
/// # Errors
1244
///
1245
/// If the value passed is not within [0.0..=2.0], returns `GamepadSettingsError::AxisThreshold`.
1246
pub fn try_set_threshold(&mut self, value: f32) -> Result<(), AxisSettingsError> {
1247
if !(0.0..=2.0).contains(&value) {
1248
Err(AxisSettingsError::Threshold(value))
1249
} else {
1250
self.threshold = value;
1251
Ok(())
1252
}
1253
}
1254
1255
/// Try to set the minimum value by which input must change before the changes will be applied.
1256
/// If the value passed is not within [0.0..=2.0], the value will not be changed.
1257
///
1258
/// Returns the new value of threshold.
1259
pub fn set_threshold(&mut self, value: f32) -> f32 {
1260
self.try_set_threshold(value).ok();
1261
self.threshold
1262
}
1263
1264
/// Clamps the `raw_value` according to the `AxisSettings`.
1265
pub fn clamp(&self, raw_value: f32) -> f32 {
1266
if self.deadzone_lowerbound <= raw_value && raw_value <= self.deadzone_upperbound {
1267
0.0
1268
} else if raw_value >= self.livezone_upperbound {
1269
1.0
1270
} else if raw_value <= self.livezone_lowerbound {
1271
-1.0
1272
} else {
1273
raw_value
1274
}
1275
}
1276
1277
/// Determines whether the change from `old_raw_value` to `new_raw_value` should
1278
/// be registered as a change, according to the [`AxisSettings`].
1279
fn should_register_change(&self, new_raw_value: f32, old_raw_value: Option<f32>) -> bool {
1280
match old_raw_value {
1281
None => true,
1282
Some(old_raw_value) => ops::abs(new_raw_value - old_raw_value) >= self.threshold,
1283
}
1284
}
1285
1286
/// Filters the `new_raw_value` based on the `old_raw_value`, according to the [`AxisSettings`].
1287
///
1288
/// Returns the clamped and scaled `new_raw_value` if the change exceeds the settings threshold,
1289
/// and `None` otherwise.
1290
fn filter(
1291
&self,
1292
new_raw_value: f32,
1293
old_raw_value: Option<f32>,
1294
) -> Option<FilteredAxisPosition> {
1295
let clamped_unscaled = self.clamp(new_raw_value);
1296
match self.should_register_change(clamped_unscaled, old_raw_value) {
1297
true => Some(FilteredAxisPosition {
1298
scaled: self.get_axis_position_from_value(clamped_unscaled),
1299
raw: new_raw_value,
1300
}),
1301
false => None,
1302
}
1303
}
1304
1305
#[inline(always)]
1306
fn get_axis_position_from_value(&self, value: f32) -> ScaledAxisWithDeadZonePosition {
1307
if value < self.deadzone_upperbound && value > self.deadzone_lowerbound {
1308
ScaledAxisWithDeadZonePosition::Dead
1309
} else if value > self.livezone_upperbound {
1310
ScaledAxisWithDeadZonePosition::AboveHigh
1311
} else if value < self.livezone_lowerbound {
1312
ScaledAxisWithDeadZonePosition::BelowLow
1313
} else if value >= self.deadzone_upperbound {
1314
ScaledAxisWithDeadZonePosition::High(linear_remapping(
1315
value,
1316
self.deadzone_upperbound..=self.livezone_upperbound,
1317
0.0..=1.0,
1318
))
1319
} else if value <= self.deadzone_lowerbound {
1320
ScaledAxisWithDeadZonePosition::Low(linear_remapping(
1321
value,
1322
self.livezone_lowerbound..=self.deadzone_lowerbound,
1323
-1.0..=0.0,
1324
))
1325
} else {
1326
unreachable!();
1327
}
1328
}
1329
}
1330
1331
/// A linear remapping of `value` from `old` to `new`.
1332
fn linear_remapping(value: f32, old: RangeInclusive<f32>, new: RangeInclusive<f32>) -> f32 {
1333
// https://stackoverflow.com/a/929104
1334
((value - old.start()) / (old.end() - old.start())) * (new.end() - new.start()) + new.start()
1335
}
1336
1337
#[derive(Debug, Clone, Copy)]
1338
/// Deadzone-aware axis position.
1339
enum ScaledAxisWithDeadZonePosition {
1340
/// The input clipped below the valid range of the axis.
1341
BelowLow,
1342
/// The input is lower than the deadzone.
1343
Low(f32),
1344
/// The input falls within the deadzone, meaning it is counted as 0.
1345
Dead,
1346
/// The input is higher than the deadzone.
1347
High(f32),
1348
/// The input clipped above the valid range of the axis.
1349
AboveHigh,
1350
}
1351
1352
struct FilteredAxisPosition {
1353
scaled: ScaledAxisWithDeadZonePosition,
1354
raw: f32,
1355
}
1356
1357
impl ScaledAxisWithDeadZonePosition {
1358
/// Converts the value into a float in the range [-1, 1].
1359
fn to_f32(self) -> f32 {
1360
match self {
1361
ScaledAxisWithDeadZonePosition::BelowLow => -1.,
1362
ScaledAxisWithDeadZonePosition::Low(scaled)
1363
| ScaledAxisWithDeadZonePosition::High(scaled) => scaled,
1364
ScaledAxisWithDeadZonePosition::Dead => 0.,
1365
ScaledAxisWithDeadZonePosition::AboveHigh => 1.,
1366
}
1367
}
1368
}
1369
1370
#[derive(Debug, Clone, Copy)]
1371
/// Low/High-aware axis position.
1372
enum ScaledAxisPosition {
1373
/// The input fell short of the "low" value.
1374
ClampedLow,
1375
/// The input was in the normal range.
1376
Scaled(f32),
1377
/// The input surpassed the "high" value.
1378
ClampedHigh,
1379
}
1380
1381
struct FilteredButtonAxisPosition {
1382
scaled: ScaledAxisPosition,
1383
raw: f32,
1384
}
1385
1386
impl ScaledAxisPosition {
1387
/// Converts the value into a float in the range [0, 1].
1388
fn to_f32(self) -> f32 {
1389
match self {
1390
ScaledAxisPosition::ClampedLow => 0.,
1391
ScaledAxisPosition::Scaled(scaled) => scaled,
1392
ScaledAxisPosition::ClampedHigh => 1.,
1393
}
1394
}
1395
}
1396
1397
/// Settings for a [`GamepadButton`].
1398
///
1399
/// It is used inside the [`GamepadSettings`] to define the sensitivity range and
1400
/// threshold for a button axis.
1401
///
1402
/// ## Logic
1403
///
1404
/// - Values that are higher than or equal to `high` will be rounded to 1.0.
1405
/// - Values that are lower than or equal to `low` will be rounded to 0.0.
1406
/// - Otherwise, values will not be rounded.
1407
///
1408
/// The valid range is from 0.0 to 1.0, inclusive.
1409
#[derive(Debug, Clone)]
1410
#[cfg_attr(
1411
feature = "bevy_reflect",
1412
derive(Reflect),
1413
reflect(Debug, Default, Clone)
1414
)]
1415
pub struct ButtonAxisSettings {
1416
/// The high value at which to apply rounding.
1417
pub high: f32,
1418
/// The low value at which to apply rounding.
1419
pub low: f32,
1420
/// The threshold to apply rounding.
1421
pub threshold: f32,
1422
}
1423
1424
impl Default for ButtonAxisSettings {
1425
fn default() -> Self {
1426
ButtonAxisSettings {
1427
high: 0.95,
1428
low: 0.05,
1429
threshold: 0.01,
1430
}
1431
}
1432
}
1433
1434
impl ButtonAxisSettings {
1435
/// Clamps the `raw_value` according to the specified settings.
1436
///
1437
/// If the `raw_value` is:
1438
/// - lower than or equal to `low` it will be rounded to 0.0.
1439
/// - higher than or equal to `high` it will be rounded to 1.0.
1440
/// - Otherwise it will not be rounded.
1441
fn clamp(&self, raw_value: f32) -> f32 {
1442
if raw_value <= self.low {
1443
return 0.0;
1444
}
1445
if raw_value >= self.high {
1446
return 1.0;
1447
}
1448
1449
raw_value
1450
}
1451
1452
/// Determines whether the change from an `old_raw_value` to a `new_raw_value` should
1453
/// be registered as a change event, according to the specified settings.
1454
fn should_register_change(&self, new_raw_value: f32, old_raw_value: Option<f32>) -> bool {
1455
match old_raw_value {
1456
None => true,
1457
Some(old_raw_value) => ops::abs(new_raw_value - old_raw_value) >= self.threshold,
1458
}
1459
}
1460
1461
/// Filters the `new_raw_value` based on the `old_raw_value`, according to the [`ButtonAxisSettings`].
1462
///
1463
/// Returns the clamped and scaled `new_raw_value`, according to the [`ButtonAxisSettings`], if the change
1464
/// exceeds the settings threshold, and `None` otherwise.
1465
fn filter(
1466
&self,
1467
new_raw_value: f32,
1468
old_raw_value: Option<f32>,
1469
) -> Option<FilteredButtonAxisPosition> {
1470
let clamped_unscaled = self.clamp(new_raw_value);
1471
match self.should_register_change(clamped_unscaled, old_raw_value) {
1472
true => Some(FilteredButtonAxisPosition {
1473
scaled: self.get_axis_position_from_value(clamped_unscaled),
1474
raw: new_raw_value,
1475
}),
1476
false => None,
1477
}
1478
}
1479
1480
/// Clamps and scales the `value` according to the specified settings.
1481
///
1482
/// If the `value` is:
1483
/// - lower than or equal to `low` it will be rounded to 0.0.
1484
/// - higher than or equal to `high` it will be rounded to 1.0.
1485
/// - Otherwise, it will be scaled from (low, high) to (0, 1).
1486
fn get_axis_position_from_value(&self, value: f32) -> ScaledAxisPosition {
1487
if value <= self.low {
1488
ScaledAxisPosition::ClampedLow
1489
} else if value >= self.high {
1490
ScaledAxisPosition::ClampedHigh
1491
} else {
1492
ScaledAxisPosition::Scaled(linear_remapping(value, self.low..=self.high, 0.0..=1.0))
1493
}
1494
}
1495
}
1496
1497
/// Handles [`GamepadConnectionEvent`]s events.
1498
///
1499
/// On connection, adds the components representing a [`Gamepad`] to the entity.
1500
/// On disconnection, removes the [`Gamepad`] and other related components.
1501
/// Entities are left alive and might leave components like [`GamepadSettings`] to preserve state in the case of a reconnection.
1502
///
1503
/// ## Note
1504
///
1505
/// Whenever a [`Gamepad`] connects or disconnects, an information gets printed to the console using the [`info!`] macro.
1506
pub fn gamepad_connection_system(
1507
mut commands: Commands,
1508
mut connection_events: EventReader<GamepadConnectionEvent>,
1509
) {
1510
for connection_event in connection_events.read() {
1511
let id = connection_event.gamepad;
1512
match &connection_event.connection {
1513
GamepadConnection::Connected {
1514
name,
1515
vendor_id,
1516
product_id,
1517
} => {
1518
let Ok(mut gamepad) = commands.get_entity(id) else {
1519
warn!("Gamepad {id} removed before handling connection event.");
1520
continue;
1521
};
1522
gamepad.insert((
1523
Name::new(name.clone()),
1524
Gamepad {
1525
vendor_id: *vendor_id,
1526
product_id: *product_id,
1527
..Default::default()
1528
},
1529
));
1530
info!("Gamepad {id} connected.");
1531
}
1532
GamepadConnection::Disconnected => {
1533
let Ok(mut gamepad) = commands.get_entity(id) else {
1534
warn!("Gamepad {id} removed before handling disconnection event. You can ignore this if you manually removed it.");
1535
continue;
1536
};
1537
// Gamepad entities are left alive to preserve their state (e.g. [`GamepadSettings`]).
1538
// Instead of despawning, we remove Gamepad components that don't need to preserve state
1539
// and re-add them if they ever reconnect.
1540
gamepad.remove::<Gamepad>();
1541
info!("Gamepad {id} disconnected.");
1542
}
1543
}
1544
}
1545
}
1546
1547
// Note that we don't expose `gilrs::Gamepad::uuid` due to
1548
// https://gitlab.com/gilrs-project/gilrs/-/issues/153.
1549
//
1550
/// The connection status of a gamepad.
1551
#[derive(Debug, Clone, PartialEq)]
1552
#[cfg_attr(
1553
feature = "bevy_reflect",
1554
derive(Reflect),
1555
reflect(Debug, PartialEq, Clone)
1556
)]
1557
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
1558
#[cfg_attr(
1559
all(feature = "serialize", feature = "bevy_reflect"),
1560
reflect(Serialize, Deserialize)
1561
)]
1562
pub enum GamepadConnection {
1563
/// The gamepad is connected.
1564
Connected {
1565
/// The name of the gamepad.
1566
///
1567
/// This name is generally defined by the OS.
1568
///
1569
/// For example on Windows the name may be "HID-compliant game controller".
1570
name: String,
1571
1572
/// The USB vendor ID as assigned by the USB-IF, if available.
1573
vendor_id: Option<u16>,
1574
1575
/// The USB product ID as assigned by the vendor, if available.
1576
product_id: Option<u16>,
1577
},
1578
/// The gamepad is disconnected.
1579
Disconnected,
1580
}
1581
1582
/// Consumes [`RawGamepadEvent`] events, filters them using their [`GamepadSettings`] and if successful,
1583
/// updates the [`Gamepad`] and sends [`GamepadAxisChangedEvent`], [`GamepadButtonStateChangedEvent`], [`GamepadButtonChangedEvent`] events.
1584
pub fn gamepad_event_processing_system(
1585
mut gamepads: Query<(&mut Gamepad, &GamepadSettings)>,
1586
mut raw_events: EventReader<RawGamepadEvent>,
1587
mut processed_events: EventWriter<GamepadEvent>,
1588
mut processed_axis_events: EventWriter<GamepadAxisChangedEvent>,
1589
mut processed_digital_events: EventWriter<GamepadButtonStateChangedEvent>,
1590
mut processed_analog_events: EventWriter<GamepadButtonChangedEvent>,
1591
) {
1592
// Clear digital buttons state
1593
for (mut gamepad, _) in gamepads.iter_mut() {
1594
gamepad.bypass_change_detection().digital.clear();
1595
}
1596
1597
for event in raw_events.read() {
1598
match event {
1599
// Connections require inserting/removing components so they are done in a separate system
1600
RawGamepadEvent::Connection(send_event) => {
1601
processed_events.write(GamepadEvent::from(send_event.clone()));
1602
}
1603
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent {
1604
gamepad,
1605
axis,
1606
value,
1607
}) => {
1608
let (gamepad, axis, value) = (*gamepad, *axis, *value);
1609
let Ok((mut gamepad_axis, gamepad_settings)) = gamepads.get_mut(gamepad) else {
1610
continue;
1611
};
1612
let Some(filtered_value) = gamepad_settings
1613
.get_axis_settings(axis)
1614
.filter(value, gamepad_axis.get(axis))
1615
else {
1616
continue;
1617
};
1618
gamepad_axis.analog.set(axis, filtered_value.raw);
1619
let send_event =
1620
GamepadAxisChangedEvent::new(gamepad, axis, filtered_value.scaled.to_f32());
1621
processed_axis_events.write(send_event);
1622
processed_events.write(GamepadEvent::from(send_event));
1623
}
1624
RawGamepadEvent::Button(RawGamepadButtonChangedEvent {
1625
gamepad,
1626
button,
1627
value,
1628
}) => {
1629
let (gamepad, button, value) = (*gamepad, *button, *value);
1630
let Ok((mut gamepad_buttons, settings)) = gamepads.get_mut(gamepad) else {
1631
continue;
1632
};
1633
let Some(filtered_value) = settings
1634
.get_button_axis_settings(button)
1635
.filter(value, gamepad_buttons.get(button))
1636
else {
1637
continue;
1638
};
1639
let button_settings = settings.get_button_settings(button);
1640
gamepad_buttons.analog.set(button, filtered_value.raw);
1641
1642
if button_settings.is_released(filtered_value.raw) {
1643
// Check if button was previously pressed
1644
if gamepad_buttons.pressed(button) {
1645
processed_digital_events.write(GamepadButtonStateChangedEvent::new(
1646
gamepad,
1647
button,
1648
ButtonState::Released,
1649
));
1650
}
1651
// We don't have to check if the button was previously pressed here
1652
// because that check is performed within Input<T>::release()
1653
gamepad_buttons.digital.release(button);
1654
} else if button_settings.is_pressed(filtered_value.raw) {
1655
// Check if button was previously not pressed
1656
if !gamepad_buttons.pressed(button) {
1657
processed_digital_events.write(GamepadButtonStateChangedEvent::new(
1658
gamepad,
1659
button,
1660
ButtonState::Pressed,
1661
));
1662
}
1663
gamepad_buttons.digital.press(button);
1664
};
1665
1666
let button_state = if gamepad_buttons.digital.pressed(button) {
1667
ButtonState::Pressed
1668
} else {
1669
ButtonState::Released
1670
};
1671
let send_event = GamepadButtonChangedEvent::new(
1672
gamepad,
1673
button,
1674
button_state,
1675
filtered_value.scaled.to_f32(),
1676
);
1677
processed_analog_events.write(send_event);
1678
processed_events.write(GamepadEvent::from(send_event));
1679
}
1680
}
1681
}
1682
}
1683
1684
/// The intensity at which a gamepad's force-feedback motors may rumble.
1685
#[derive(Clone, Copy, Debug, PartialEq)]
1686
#[cfg_attr(
1687
feature = "bevy_reflect",
1688
derive(Reflect),
1689
reflect(Debug, PartialEq, Clone)
1690
)]
1691
pub struct GamepadRumbleIntensity {
1692
/// The rumble intensity of the strong gamepad motor.
1693
///
1694
/// Ranges from `0.0` to `1.0`.
1695
///
1696
/// By convention, this is usually a low-frequency motor on the left-hand
1697
/// side of the gamepad, though it may vary across platforms and hardware.
1698
pub strong_motor: f32,
1699
/// The rumble intensity of the weak gamepad motor.
1700
///
1701
/// Ranges from `0.0` to `1.0`.
1702
///
1703
/// By convention, this is usually a high-frequency motor on the right-hand
1704
/// side of the gamepad, though it may vary across platforms and hardware.
1705
pub weak_motor: f32,
1706
}
1707
1708
impl GamepadRumbleIntensity {
1709
/// Rumble both gamepad motors at maximum intensity.
1710
pub const MAX: Self = GamepadRumbleIntensity {
1711
strong_motor: 1.0,
1712
weak_motor: 1.0,
1713
};
1714
1715
/// Rumble the weak motor at maximum intensity.
1716
pub const WEAK_MAX: Self = GamepadRumbleIntensity {
1717
strong_motor: 0.0,
1718
weak_motor: 1.0,
1719
};
1720
1721
/// Rumble the strong motor at maximum intensity.
1722
pub const STRONG_MAX: Self = GamepadRumbleIntensity {
1723
strong_motor: 1.0,
1724
weak_motor: 0.0,
1725
};
1726
1727
/// Creates a new rumble intensity with weak motor intensity set to the given value.
1728
///
1729
/// Clamped within the `0.0` to `1.0` range.
1730
pub const fn weak_motor(intensity: f32) -> Self {
1731
Self {
1732
weak_motor: intensity,
1733
strong_motor: 0.0,
1734
}
1735
}
1736
1737
/// Creates a new rumble intensity with strong motor intensity set to the given value.
1738
///
1739
/// Clamped within the `0.0` to `1.0` range.
1740
pub const fn strong_motor(intensity: f32) -> Self {
1741
Self {
1742
strong_motor: intensity,
1743
weak_motor: 0.0,
1744
}
1745
}
1746
}
1747
1748
/// An event that controls force-feedback rumbling of a [`Gamepad`] [`entity`](Entity).
1749
///
1750
/// # Notes
1751
///
1752
/// Does nothing if the gamepad or platform does not support rumble.
1753
///
1754
/// # Example
1755
///
1756
/// ```
1757
/// # use bevy_input::gamepad::{Gamepad, GamepadRumbleRequest, GamepadRumbleIntensity};
1758
/// # use bevy_ecs::prelude::{EventWriter, Res, Query, Entity, With};
1759
/// # use core::time::Duration;
1760
/// fn rumble_gamepad_system(
1761
/// mut rumble_requests: EventWriter<GamepadRumbleRequest>,
1762
/// gamepads: Query<Entity, With<Gamepad>>,
1763
/// ) {
1764
/// for entity in gamepads.iter() {
1765
/// rumble_requests.write(GamepadRumbleRequest::Add {
1766
/// gamepad: entity,
1767
/// intensity: GamepadRumbleIntensity::MAX,
1768
/// duration: Duration::from_secs_f32(0.5),
1769
/// });
1770
/// }
1771
/// }
1772
/// ```
1773
#[doc(alias = "haptic feedback")]
1774
#[doc(alias = "force feedback")]
1775
#[doc(alias = "vibration")]
1776
#[doc(alias = "vibrate")]
1777
#[derive(BufferedEvent, Clone)]
1778
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Clone))]
1779
pub enum GamepadRumbleRequest {
1780
/// Add a rumble to the given gamepad.
1781
///
1782
/// Simultaneous rumble effects add up to the sum of their strengths.
1783
///
1784
/// Consequently, if two rumbles at half intensity are added at the same
1785
/// time, their intensities will be added up, and the controller will rumble
1786
/// at full intensity until one of the rumbles finishes, then the rumble
1787
/// will continue at the intensity of the remaining event.
1788
///
1789
/// To replace an existing rumble, send a [`GamepadRumbleRequest::Stop`] event first.
1790
Add {
1791
/// How long the gamepad should rumble.
1792
duration: Duration,
1793
/// How intense the rumble should be.
1794
intensity: GamepadRumbleIntensity,
1795
/// The gamepad to rumble.
1796
gamepad: Entity,
1797
},
1798
/// Stop all running rumbles on the given [`Entity`].
1799
Stop {
1800
/// The gamepad to stop rumble.
1801
gamepad: Entity,
1802
},
1803
}
1804
1805
impl GamepadRumbleRequest {
1806
/// Get the [`Entity`] associated with this request.
1807
pub fn gamepad(&self) -> Entity {
1808
match self {
1809
Self::Add { gamepad, .. } | Self::Stop { gamepad } => *gamepad,
1810
}
1811
}
1812
}
1813
1814
#[cfg(test)]
1815
mod tests {
1816
use super::{
1817
gamepad_connection_system, gamepad_event_processing_system, AxisSettings,
1818
AxisSettingsError, ButtonAxisSettings, ButtonSettings, ButtonSettingsError, Gamepad,
1819
GamepadAxis, GamepadAxisChangedEvent, GamepadButton, GamepadButtonChangedEvent,
1820
GamepadButtonStateChangedEvent,
1821
GamepadConnection::{Connected, Disconnected},
1822
GamepadConnectionEvent, GamepadEvent, GamepadSettings, RawGamepadAxisChangedEvent,
1823
RawGamepadButtonChangedEvent, RawGamepadEvent,
1824
};
1825
use crate::ButtonState;
1826
use alloc::string::ToString;
1827
use bevy_app::{App, PreUpdate};
1828
use bevy_ecs::entity::Entity;
1829
use bevy_ecs::event::Events;
1830
use bevy_ecs::schedule::IntoScheduleConfigs;
1831
1832
fn test_button_axis_settings_filter(
1833
settings: ButtonAxisSettings,
1834
new_raw_value: f32,
1835
old_raw_value: Option<f32>,
1836
expected: Option<f32>,
1837
) {
1838
let actual = settings
1839
.filter(new_raw_value, old_raw_value)
1840
.map(|f| f.scaled.to_f32());
1841
assert_eq!(
1842
expected, actual,
1843
"Testing filtering for {settings:?} with new_raw_value = {new_raw_value:?}, old_raw_value = {old_raw_value:?}",
1844
);
1845
}
1846
1847
#[test]
1848
fn test_button_axis_settings_default_filter() {
1849
let cases = [
1850
// clamped
1851
(1.0, None, Some(1.0)),
1852
(0.99, None, Some(1.0)),
1853
(0.96, None, Some(1.0)),
1854
(0.95, None, Some(1.0)),
1855
// linearly rescaled from 0.05..=0.95 to 0.0..=1.0
1856
(0.9499, None, Some(0.9998889)),
1857
(0.84, None, Some(0.87777776)),
1858
(0.43, None, Some(0.42222223)),
1859
(0.05001, None, Some(0.000011109644)),
1860
// clamped
1861
(0.05, None, Some(0.0)),
1862
(0.04, None, Some(0.0)),
1863
(0.01, None, Some(0.0)),
1864
(0.0, None, Some(0.0)),
1865
];
1866
1867
for (new_raw_value, old_raw_value, expected) in cases {
1868
let settings = ButtonAxisSettings::default();
1869
test_button_axis_settings_filter(settings, new_raw_value, old_raw_value, expected);
1870
}
1871
}
1872
1873
#[test]
1874
fn test_button_axis_settings_default_filter_with_old_raw_value() {
1875
let cases = [
1876
// 0.43 gets rescaled to 0.42222223 (0.05..=0.95 -> 0.0..=1.0)
1877
(0.43, Some(0.44001), Some(0.42222223)),
1878
(0.43, Some(0.44), None),
1879
(0.43, Some(0.43), None),
1880
(0.43, Some(0.41999), Some(0.42222223)),
1881
(0.43, Some(0.17), Some(0.42222223)),
1882
(0.43, Some(0.84), Some(0.42222223)),
1883
(0.05, Some(0.055), Some(0.0)),
1884
(0.95, Some(0.945), Some(1.0)),
1885
];
1886
1887
for (new_raw_value, old_raw_value, expected) in cases {
1888
let settings = ButtonAxisSettings::default();
1889
test_button_axis_settings_filter(settings, new_raw_value, old_raw_value, expected);
1890
}
1891
}
1892
1893
fn test_axis_settings_filter(
1894
settings: AxisSettings,
1895
new_raw_value: f32,
1896
old_raw_value: Option<f32>,
1897
expected: Option<f32>,
1898
) {
1899
let actual = settings.filter(new_raw_value, old_raw_value);
1900
assert_eq!(
1901
expected, actual.map(|f| f.scaled.to_f32()),
1902
"Testing filtering for {settings:?} with new_raw_value = {new_raw_value:?}, old_raw_value = {old_raw_value:?}",
1903
);
1904
}
1905
1906
#[test]
1907
fn test_axis_settings_default_filter() {
1908
// new (raw), expected (rescaled linearly)
1909
let cases = [
1910
// high enough to round to 1.0
1911
(1.0, Some(1.0)),
1912
(0.99, Some(1.0)),
1913
(0.96, Some(1.0)),
1914
(0.95, Some(1.0)),
1915
// for the following, remember that 0.05 is the "low" value and 0.95 is the "high" value
1916
// barely below the high value means barely below 1 after scaling
1917
(0.9499, Some(0.9998889)), // scaled as: (0.9499 - 0.05) / (0.95 - 0.05)
1918
(0.84, Some(0.87777776)), // scaled as: (0.84 - 0.05) / (0.95 - 0.05)
1919
(0.43, Some(0.42222223)), // scaled as: (0.43 - 0.05) / (0.95 - 0.05)
1920
// barely above the low value means barely above 0 after scaling
1921
(0.05001, Some(0.000011109644)), // scaled as: (0.05001 - 0.05) / (0.95 - 0.05)
1922
// low enough to be rounded to 0 (dead zone)
1923
(0.05, Some(0.0)),
1924
(0.04, Some(0.0)),
1925
(0.01, Some(0.0)),
1926
(0.0, Some(0.0)),
1927
// same exact tests as above, but below 0 (bottom half of the dead zone and live zone)
1928
// low enough to be rounded to -1
1929
(-1.0, Some(-1.0)),
1930
(-0.99, Some(-1.0)),
1931
(-0.96, Some(-1.0)),
1932
(-0.95, Some(-1.0)),
1933
// scaled inputs
1934
(-0.9499, Some(-0.9998889)), // scaled as: (-0.9499 - -0.05) / (-0.95 - -0.05)
1935
(-0.84, Some(-0.87777776)), // scaled as: (-0.84 - -0.05) / (-0.95 - -0.05)
1936
(-0.43, Some(-0.42222226)), // scaled as: (-0.43 - -0.05) / (-0.95 - -0.05)
1937
(-0.05001, Some(-0.000011146069)), // scaled as: (-0.05001 - -0.05) / (-0.95 - -0.05)
1938
// high enough to be rounded to 0 (dead zone)
1939
(-0.05, Some(0.0)),
1940
(-0.04, Some(0.0)),
1941
(-0.01, Some(0.0)),
1942
];
1943
1944
for (new_raw_value, expected) in cases {
1945
let settings = AxisSettings::new(-0.95, -0.05, 0.05, 0.95, 0.01).unwrap();
1946
test_axis_settings_filter(settings, new_raw_value, None, expected);
1947
}
1948
}
1949
1950
#[test]
1951
fn test_axis_settings_default_filter_with_old_raw_values() {
1952
let threshold = 0.01;
1953
// expected values are hardcoded to be rescaled to from 0.05..=0.95 to 0.0..=1.0
1954
// new (raw), old (raw), expected
1955
let cases = [
1956
// enough increase to change
1957
(0.43, Some(0.43 + threshold * 1.1), Some(0.42222223)),
1958
// enough decrease to change
1959
(0.43, Some(0.43 - threshold * 1.1), Some(0.42222223)),
1960
// not enough increase to change
1961
(0.43, Some(0.43 + threshold * 0.9), None),
1962
// not enough decrease to change
1963
(0.43, Some(0.43 - threshold * 0.9), None),
1964
// enough increase to change
1965
(-0.43, Some(-0.43 + threshold * 1.1), Some(-0.42222226)),
1966
// enough decrease to change
1967
(-0.43, Some(-0.43 - threshold * 1.1), Some(-0.42222226)),
1968
// not enough increase to change
1969
(-0.43, Some(-0.43 + threshold * 0.9), None),
1970
// not enough decrease to change
1971
(-0.43, Some(-0.43 - threshold * 0.9), None),
1972
// test upper deadzone logic
1973
(0.05, Some(0.0), None),
1974
(0.06, Some(0.0), Some(0.0111111095)),
1975
// test lower deadzone logic
1976
(-0.05, Some(0.0), None),
1977
(-0.06, Some(0.0), Some(-0.011111081)),
1978
// test upper livezone logic
1979
(0.95, Some(1.0), None),
1980
(0.94, Some(1.0), Some(0.9888889)),
1981
// test lower livezone logic
1982
(-0.95, Some(-1.0), None),
1983
(-0.94, Some(-1.0), Some(-0.9888889)),
1984
];
1985
1986
for (new_raw_value, old_raw_value, expected) in cases {
1987
let settings = AxisSettings::new(-0.95, -0.05, 0.05, 0.95, threshold).unwrap();
1988
test_axis_settings_filter(settings, new_raw_value, old_raw_value, expected);
1989
}
1990
}
1991
1992
#[test]
1993
fn test_button_settings_default_is_pressed() {
1994
let cases = [
1995
(1.0, true),
1996
(0.95, true),
1997
(0.9, true),
1998
(0.8, true),
1999
(0.75, true),
2000
(0.7, false),
2001
(0.65, false),
2002
(0.5, false),
2003
(0.0, false),
2004
];
2005
2006
for (value, expected) in cases {
2007
let settings = ButtonSettings::default();
2008
let actual = settings.is_pressed(value);
2009
2010
assert_eq!(
2011
expected, actual,
2012
"testing ButtonSettings::is_pressed() for value: {value}",
2013
);
2014
}
2015
}
2016
2017
#[test]
2018
fn test_button_settings_default_is_released() {
2019
let cases = [
2020
(1.0, false),
2021
(0.95, false),
2022
(0.9, false),
2023
(0.8, false),
2024
(0.75, false),
2025
(0.7, false),
2026
(0.65, true),
2027
(0.5, true),
2028
(0.0, true),
2029
];
2030
2031
for (value, expected) in cases {
2032
let settings = ButtonSettings::default();
2033
let actual = settings.is_released(value);
2034
2035
assert_eq!(
2036
expected, actual,
2037
"testing ButtonSettings::is_released() for value: {value}",
2038
);
2039
}
2040
}
2041
2042
#[test]
2043
fn test_new_button_settings_given_valid_parameters() {
2044
let cases = [
2045
(1.0, 0.0),
2046
(1.0, 1.0),
2047
(1.0, 0.9),
2048
(0.9, 0.9),
2049
(0.9, 0.0),
2050
(0.0, 0.0),
2051
];
2052
2053
for (press_threshold, release_threshold) in cases {
2054
let bs = ButtonSettings::new(press_threshold, release_threshold);
2055
match bs {
2056
Ok(button_settings) => {
2057
assert_eq!(button_settings.press_threshold, press_threshold);
2058
assert_eq!(button_settings.release_threshold, release_threshold);
2059
}
2060
Err(_) => {
2061
panic!(
2062
"ButtonSettings::new({press_threshold}, {release_threshold}) should be valid"
2063
);
2064
}
2065
}
2066
}
2067
}
2068
2069
#[test]
2070
fn test_new_button_settings_given_invalid_parameters() {
2071
let cases = [
2072
(1.1, 0.0),
2073
(1.1, 1.0),
2074
(1.0, 1.1),
2075
(-1.0, 0.9),
2076
(-1.0, 0.0),
2077
(-1.0, -0.4),
2078
(0.9, 1.0),
2079
(0.0, 0.1),
2080
];
2081
2082
for (press_threshold, release_threshold) in cases {
2083
let bs = ButtonSettings::new(press_threshold, release_threshold);
2084
match bs {
2085
Ok(_) => {
2086
panic!(
2087
"ButtonSettings::new({press_threshold}, {release_threshold}) should be invalid"
2088
);
2089
}
2090
Err(err_code) => match err_code {
2091
ButtonSettingsError::PressThresholdOutOfRange(_press_threshold) => {}
2092
ButtonSettingsError::ReleaseThresholdGreaterThanPressThreshold {
2093
press_threshold: _press_threshold,
2094
release_threshold: _release_threshold,
2095
} => {}
2096
ButtonSettingsError::ReleaseThresholdOutOfRange(_release_threshold) => {}
2097
},
2098
}
2099
}
2100
}
2101
2102
#[test]
2103
fn test_try_out_of_range_axis_settings() {
2104
let mut axis_settings = AxisSettings::default();
2105
assert_eq!(
2106
AxisSettings::new(-0.95, -0.05, 0.05, 0.95, 0.001),
2107
Ok(AxisSettings {
2108
livezone_lowerbound: -0.95,
2109
deadzone_lowerbound: -0.05,
2110
deadzone_upperbound: 0.05,
2111
livezone_upperbound: 0.95,
2112
threshold: 0.001,
2113
})
2114
);
2115
assert_eq!(
2116
Err(AxisSettingsError::LiveZoneLowerBoundOutOfRange(-2.0)),
2117
axis_settings.try_set_livezone_lowerbound(-2.0)
2118
);
2119
assert_eq!(
2120
Err(AxisSettingsError::LiveZoneLowerBoundOutOfRange(0.1)),
2121
axis_settings.try_set_livezone_lowerbound(0.1)
2122
);
2123
assert_eq!(
2124
Err(AxisSettingsError::DeadZoneLowerBoundOutOfRange(-2.0)),
2125
axis_settings.try_set_deadzone_lowerbound(-2.0)
2126
);
2127
assert_eq!(
2128
Err(AxisSettingsError::DeadZoneLowerBoundOutOfRange(0.1)),
2129
axis_settings.try_set_deadzone_lowerbound(0.1)
2130
);
2131
2132
assert_eq!(
2133
Err(AxisSettingsError::DeadZoneUpperBoundOutOfRange(-0.1)),
2134
axis_settings.try_set_deadzone_upperbound(-0.1)
2135
);
2136
assert_eq!(
2137
Err(AxisSettingsError::DeadZoneUpperBoundOutOfRange(1.1)),
2138
axis_settings.try_set_deadzone_upperbound(1.1)
2139
);
2140
assert_eq!(
2141
Err(AxisSettingsError::LiveZoneUpperBoundOutOfRange(-0.1)),
2142
axis_settings.try_set_livezone_upperbound(-0.1)
2143
);
2144
assert_eq!(
2145
Err(AxisSettingsError::LiveZoneUpperBoundOutOfRange(1.1)),
2146
axis_settings.try_set_livezone_upperbound(1.1)
2147
);
2148
2149
axis_settings.set_livezone_lowerbound(-0.7);
2150
axis_settings.set_deadzone_lowerbound(-0.3);
2151
assert_eq!(
2152
Err(
2153
AxisSettingsError::LiveZoneLowerBoundGreaterThanDeadZoneLowerBound {
2154
livezone_lowerbound: -0.1,
2155
deadzone_lowerbound: -0.3,
2156
}
2157
),
2158
axis_settings.try_set_livezone_lowerbound(-0.1)
2159
);
2160
assert_eq!(
2161
Err(
2162
AxisSettingsError::LiveZoneLowerBoundGreaterThanDeadZoneLowerBound {
2163
livezone_lowerbound: -0.7,
2164
deadzone_lowerbound: -0.9
2165
}
2166
),
2167
axis_settings.try_set_deadzone_lowerbound(-0.9)
2168
);
2169
2170
axis_settings.set_deadzone_upperbound(0.3);
2171
axis_settings.set_livezone_upperbound(0.7);
2172
assert_eq!(
2173
Err(
2174
AxisSettingsError::DeadZoneUpperBoundGreaterThanLiveZoneUpperBound {
2175
deadzone_upperbound: 0.8,
2176
livezone_upperbound: 0.7
2177
}
2178
),
2179
axis_settings.try_set_deadzone_upperbound(0.8)
2180
);
2181
assert_eq!(
2182
Err(
2183
AxisSettingsError::DeadZoneUpperBoundGreaterThanLiveZoneUpperBound {
2184
deadzone_upperbound: 0.3,
2185
livezone_upperbound: 0.1
2186
}
2187
),
2188
axis_settings.try_set_livezone_upperbound(0.1)
2189
);
2190
}
2191
2192
struct TestContext {
2193
pub app: App,
2194
}
2195
2196
impl TestContext {
2197
pub fn new() -> Self {
2198
let mut app = App::new();
2199
app.add_systems(
2200
PreUpdate,
2201
(
2202
gamepad_connection_system,
2203
gamepad_event_processing_system.after(gamepad_connection_system),
2204
),
2205
)
2206
.add_event::<GamepadEvent>()
2207
.add_event::<GamepadConnectionEvent>()
2208
.add_event::<RawGamepadButtonChangedEvent>()
2209
.add_event::<GamepadButtonChangedEvent>()
2210
.add_event::<GamepadButtonStateChangedEvent>()
2211
.add_event::<GamepadAxisChangedEvent>()
2212
.add_event::<RawGamepadAxisChangedEvent>()
2213
.add_event::<RawGamepadEvent>();
2214
Self { app }
2215
}
2216
2217
pub fn update(&mut self) {
2218
self.app.update();
2219
}
2220
2221
pub fn send_gamepad_connection_event(&mut self, gamepad: Option<Entity>) -> Entity {
2222
let gamepad = gamepad.unwrap_or_else(|| self.app.world_mut().spawn_empty().id());
2223
self.app
2224
.world_mut()
2225
.resource_mut::<Events<GamepadConnectionEvent>>()
2226
.write(GamepadConnectionEvent::new(
2227
gamepad,
2228
Connected {
2229
name: "Test gamepad".to_string(),
2230
vendor_id: None,
2231
product_id: None,
2232
},
2233
));
2234
gamepad
2235
}
2236
2237
pub fn send_gamepad_disconnection_event(&mut self, gamepad: Entity) {
2238
self.app
2239
.world_mut()
2240
.resource_mut::<Events<GamepadConnectionEvent>>()
2241
.write(GamepadConnectionEvent::new(gamepad, Disconnected));
2242
}
2243
2244
pub fn send_raw_gamepad_event(&mut self, event: RawGamepadEvent) {
2245
self.app
2246
.world_mut()
2247
.resource_mut::<Events<RawGamepadEvent>>()
2248
.write(event);
2249
}
2250
2251
pub fn send_raw_gamepad_event_batch(
2252
&mut self,
2253
events: impl IntoIterator<Item = RawGamepadEvent>,
2254
) {
2255
self.app
2256
.world_mut()
2257
.resource_mut::<Events<RawGamepadEvent>>()
2258
.write_batch(events);
2259
}
2260
}
2261
2262
#[test]
2263
fn connection_event() {
2264
let mut ctx = TestContext::new();
2265
assert_eq!(
2266
ctx.app
2267
.world_mut()
2268
.query::<&Gamepad>()
2269
.iter(ctx.app.world())
2270
.len(),
2271
0
2272
);
2273
ctx.send_gamepad_connection_event(None);
2274
ctx.update();
2275
assert_eq!(
2276
ctx.app
2277
.world_mut()
2278
.query::<(&Gamepad, &GamepadSettings)>()
2279
.iter(ctx.app.world())
2280
.len(),
2281
1
2282
);
2283
}
2284
2285
#[test]
2286
fn disconnection_event() {
2287
let mut ctx = TestContext::new();
2288
assert_eq!(
2289
ctx.app
2290
.world_mut()
2291
.query::<&Gamepad>()
2292
.iter(ctx.app.world())
2293
.len(),
2294
0
2295
);
2296
let entity = ctx.send_gamepad_connection_event(None);
2297
ctx.update();
2298
assert_eq!(
2299
ctx.app
2300
.world_mut()
2301
.query::<(&Gamepad, &GamepadSettings)>()
2302
.iter(ctx.app.world())
2303
.len(),
2304
1
2305
);
2306
ctx.send_gamepad_disconnection_event(entity);
2307
ctx.update();
2308
// Gamepad component should be removed
2309
assert!(ctx
2310
.app
2311
.world_mut()
2312
.query::<&Gamepad>()
2313
.get(ctx.app.world(), entity)
2314
.is_err());
2315
// Settings should be kept
2316
assert!(ctx
2317
.app
2318
.world_mut()
2319
.query::<&GamepadSettings>()
2320
.get(ctx.app.world(), entity)
2321
.is_ok());
2322
2323
// Mistakenly sending a second disconnection event shouldn't break anything
2324
ctx.send_gamepad_disconnection_event(entity);
2325
ctx.update();
2326
assert!(ctx
2327
.app
2328
.world_mut()
2329
.query::<&Gamepad>()
2330
.get(ctx.app.world(), entity)
2331
.is_err());
2332
assert!(ctx
2333
.app
2334
.world_mut()
2335
.query::<&GamepadSettings>()
2336
.get(ctx.app.world(), entity)
2337
.is_ok());
2338
}
2339
2340
#[test]
2341
fn connection_disconnection_frame_event() {
2342
let mut ctx = TestContext::new();
2343
assert_eq!(
2344
ctx.app
2345
.world_mut()
2346
.query::<&Gamepad>()
2347
.iter(ctx.app.world())
2348
.len(),
2349
0
2350
);
2351
let entity = ctx.send_gamepad_connection_event(None);
2352
ctx.send_gamepad_disconnection_event(entity);
2353
ctx.update();
2354
// Gamepad component should be removed
2355
assert!(ctx
2356
.app
2357
.world_mut()
2358
.query::<&Gamepad>()
2359
.get(ctx.app.world(), entity)
2360
.is_err());
2361
// Settings should be kept
2362
assert!(ctx
2363
.app
2364
.world_mut()
2365
.query::<&GamepadSettings>()
2366
.get(ctx.app.world(), entity)
2367
.is_ok());
2368
}
2369
2370
#[test]
2371
fn reconnection_event() {
2372
let button_settings = ButtonSettings::new(0.7, 0.2).expect("correct parameters");
2373
let mut ctx = TestContext::new();
2374
assert_eq!(
2375
ctx.app
2376
.world_mut()
2377
.query::<&Gamepad>()
2378
.iter(ctx.app.world())
2379
.len(),
2380
0
2381
);
2382
let entity = ctx.send_gamepad_connection_event(None);
2383
ctx.update();
2384
let mut settings = ctx
2385
.app
2386
.world_mut()
2387
.query::<&mut GamepadSettings>()
2388
.get_mut(ctx.app.world_mut(), entity)
2389
.expect("be alive");
2390
assert_ne!(settings.default_button_settings, button_settings);
2391
settings.default_button_settings = button_settings.clone();
2392
ctx.send_gamepad_disconnection_event(entity);
2393
ctx.update();
2394
assert_eq!(
2395
ctx.app
2396
.world_mut()
2397
.query::<&Gamepad>()
2398
.iter(ctx.app.world())
2399
.len(),
2400
0
2401
);
2402
ctx.send_gamepad_connection_event(Some(entity));
2403
ctx.update();
2404
let settings = ctx
2405
.app
2406
.world_mut()
2407
.query::<&GamepadSettings>()
2408
.get(ctx.app.world(), entity)
2409
.expect("be alive");
2410
assert_eq!(settings.default_button_settings, button_settings);
2411
}
2412
2413
#[test]
2414
fn reconnection_same_frame_event() {
2415
let mut ctx = TestContext::new();
2416
assert_eq!(
2417
ctx.app
2418
.world_mut()
2419
.query::<&Gamepad>()
2420
.iter(ctx.app.world())
2421
.len(),
2422
0
2423
);
2424
let entity = ctx.send_gamepad_connection_event(None);
2425
ctx.send_gamepad_disconnection_event(entity);
2426
ctx.update();
2427
assert_eq!(
2428
ctx.app
2429
.world_mut()
2430
.query::<&Gamepad>()
2431
.iter(ctx.app.world())
2432
.len(),
2433
0
2434
);
2435
assert!(ctx
2436
.app
2437
.world_mut()
2438
.query::<(Entity, &GamepadSettings)>()
2439
.get(ctx.app.world(), entity)
2440
.is_ok());
2441
}
2442
2443
#[test]
2444
fn gamepad_axis_valid() {
2445
let mut ctx = TestContext::new();
2446
2447
// Create test gamepad
2448
let entity = ctx.send_gamepad_connection_event(None);
2449
ctx.app
2450
.world_mut()
2451
.resource_mut::<Events<RawGamepadEvent>>()
2452
.write_batch([
2453
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
2454
entity,
2455
GamepadAxis::LeftStickY,
2456
0.5,
2457
)),
2458
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
2459
entity,
2460
GamepadAxis::RightStickX,
2461
0.6,
2462
)),
2463
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
2464
entity,
2465
GamepadAxis::RightZ,
2466
-0.4,
2467
)),
2468
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
2469
entity,
2470
GamepadAxis::RightStickY,
2471
-0.8,
2472
)),
2473
]);
2474
ctx.update();
2475
assert_eq!(
2476
ctx.app
2477
.world()
2478
.resource::<Events<GamepadAxisChangedEvent>>()
2479
.len(),
2480
4
2481
);
2482
}
2483
2484
#[test]
2485
fn gamepad_axis_threshold_filter() {
2486
let mut ctx = TestContext::new();
2487
2488
// Create test gamepad
2489
let entity = ctx.send_gamepad_connection_event(None);
2490
let settings = GamepadSettings::default().default_axis_settings;
2491
// Set of events to ensure they are being properly filtered
2492
let base_value = 0.5;
2493
let events = [
2494
// Event above threshold
2495
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
2496
entity,
2497
GamepadAxis::LeftStickX,
2498
base_value,
2499
)),
2500
// Event below threshold, should be filtered
2501
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
2502
entity,
2503
GamepadAxis::LeftStickX,
2504
base_value + settings.threshold - 0.01,
2505
)),
2506
// Event above threshold
2507
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
2508
entity,
2509
GamepadAxis::LeftStickX,
2510
base_value + settings.threshold + 0.01,
2511
)),
2512
];
2513
ctx.app
2514
.world_mut()
2515
.resource_mut::<Events<RawGamepadEvent>>()
2516
.write_batch(events);
2517
ctx.update();
2518
assert_eq!(
2519
ctx.app
2520
.world()
2521
.resource::<Events<GamepadAxisChangedEvent>>()
2522
.len(),
2523
2
2524
);
2525
}
2526
2527
#[test]
2528
fn gamepad_axis_deadzone_filter() {
2529
let mut ctx = TestContext::new();
2530
2531
// Create test gamepad
2532
let entity = ctx.send_gamepad_connection_event(None);
2533
let settings = GamepadSettings::default().default_axis_settings;
2534
2535
// Set of events to ensure they are being properly filtered
2536
let events = [
2537
// Event below deadzone upperbound should be filtered
2538
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
2539
entity,
2540
GamepadAxis::LeftStickX,
2541
settings.deadzone_upperbound - 0.01,
2542
)),
2543
// Event above deadzone lowerbound should be filtered
2544
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
2545
entity,
2546
GamepadAxis::LeftStickX,
2547
settings.deadzone_lowerbound + 0.01,
2548
)),
2549
];
2550
ctx.app
2551
.world_mut()
2552
.resource_mut::<Events<RawGamepadEvent>>()
2553
.write_batch(events);
2554
ctx.update();
2555
assert_eq!(
2556
ctx.app
2557
.world()
2558
.resource::<Events<GamepadAxisChangedEvent>>()
2559
.len(),
2560
0
2561
);
2562
}
2563
2564
#[test]
2565
fn gamepad_axis_deadzone_rounded() {
2566
let mut ctx = TestContext::new();
2567
2568
// Create test gamepad
2569
let entity = ctx.send_gamepad_connection_event(None);
2570
let settings = GamepadSettings::default().default_axis_settings;
2571
2572
// Set of events to ensure they are being properly filtered
2573
let events = [
2574
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
2575
entity,
2576
GamepadAxis::LeftStickX,
2577
1.0,
2578
)),
2579
// Event below deadzone upperbound should be rounded to 0
2580
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
2581
entity,
2582
GamepadAxis::LeftStickX,
2583
settings.deadzone_upperbound - 0.01,
2584
)),
2585
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
2586
entity,
2587
GamepadAxis::LeftStickX,
2588
1.0,
2589
)),
2590
// Event above deadzone lowerbound should be rounded to 0
2591
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
2592
entity,
2593
GamepadAxis::LeftStickX,
2594
settings.deadzone_lowerbound + 0.01,
2595
)),
2596
];
2597
let results = [1.0, 0.0, 1.0, 0.0];
2598
ctx.app
2599
.world_mut()
2600
.resource_mut::<Events<RawGamepadEvent>>()
2601
.write_batch(events);
2602
ctx.update();
2603
2604
let events = ctx
2605
.app
2606
.world()
2607
.resource::<Events<GamepadAxisChangedEvent>>();
2608
let mut event_reader = events.get_cursor();
2609
for (event, result) in event_reader.read(events).zip(results) {
2610
assert_eq!(event.value, result);
2611
}
2612
assert_eq!(
2613
ctx.app
2614
.world()
2615
.resource::<Events<GamepadAxisChangedEvent>>()
2616
.len(),
2617
4
2618
);
2619
}
2620
2621
#[test]
2622
fn gamepad_axis_livezone_filter() {
2623
let mut ctx = TestContext::new();
2624
2625
// Create test gamepad
2626
let entity = ctx.send_gamepad_connection_event(None);
2627
let settings = GamepadSettings::default().default_axis_settings;
2628
2629
// Set of events to ensure they are being properly filtered
2630
let events = [
2631
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
2632
entity,
2633
GamepadAxis::LeftStickX,
2634
1.0,
2635
)),
2636
// Event above livezone upperbound should be filtered
2637
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
2638
entity,
2639
GamepadAxis::LeftStickX,
2640
settings.livezone_upperbound + 0.01,
2641
)),
2642
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
2643
entity,
2644
GamepadAxis::LeftStickX,
2645
-1.0,
2646
)),
2647
// Event below livezone lowerbound should be filtered
2648
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
2649
entity,
2650
GamepadAxis::LeftStickX,
2651
settings.livezone_lowerbound - 0.01,
2652
)),
2653
];
2654
ctx.app
2655
.world_mut()
2656
.resource_mut::<Events<RawGamepadEvent>>()
2657
.write_batch(events);
2658
ctx.update();
2659
assert_eq!(
2660
ctx.app
2661
.world()
2662
.resource::<Events<GamepadAxisChangedEvent>>()
2663
.len(),
2664
2
2665
);
2666
}
2667
2668
#[test]
2669
fn gamepad_axis_livezone_rounded() {
2670
let mut ctx = TestContext::new();
2671
2672
// Create test gamepad
2673
let entity = ctx.send_gamepad_connection_event(None);
2674
let settings = GamepadSettings::default().default_axis_settings;
2675
2676
// Set of events to ensure they are being properly filtered
2677
let events = [
2678
// Event above livezone upperbound should be rounded to 1
2679
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
2680
entity,
2681
GamepadAxis::LeftStickX,
2682
settings.livezone_upperbound + 0.01,
2683
)),
2684
// Event below livezone lowerbound should be rounded to -1
2685
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
2686
entity,
2687
GamepadAxis::LeftStickX,
2688
settings.livezone_lowerbound - 0.01,
2689
)),
2690
];
2691
let results = [1.0, -1.0];
2692
ctx.app
2693
.world_mut()
2694
.resource_mut::<Events<RawGamepadEvent>>()
2695
.write_batch(events);
2696
ctx.update();
2697
2698
let events = ctx
2699
.app
2700
.world()
2701
.resource::<Events<GamepadAxisChangedEvent>>();
2702
let mut event_reader = events.get_cursor();
2703
for (event, result) in event_reader.read(events).zip(results) {
2704
assert_eq!(event.value, result);
2705
}
2706
assert_eq!(
2707
ctx.app
2708
.world()
2709
.resource::<Events<GamepadAxisChangedEvent>>()
2710
.len(),
2711
2
2712
);
2713
}
2714
2715
#[test]
2716
fn gamepad_buttons_pressed() {
2717
let mut ctx = TestContext::new();
2718
2719
// Create test gamepad
2720
let entity = ctx.send_gamepad_connection_event(None);
2721
let digital_settings = GamepadSettings::default().default_button_settings;
2722
2723
let events = [RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
2724
entity,
2725
GamepadButton::DPadDown,
2726
digital_settings.press_threshold,
2727
))];
2728
ctx.app
2729
.world_mut()
2730
.resource_mut::<Events<RawGamepadEvent>>()
2731
.write_batch(events);
2732
ctx.update();
2733
2734
assert_eq!(
2735
ctx.app
2736
.world()
2737
.resource::<Events<GamepadButtonStateChangedEvent>>()
2738
.len(),
2739
1
2740
);
2741
let events = ctx
2742
.app
2743
.world()
2744
.resource::<Events<GamepadButtonStateChangedEvent>>();
2745
let mut event_reader = events.get_cursor();
2746
for event in event_reader.read(events) {
2747
assert_eq!(event.button, GamepadButton::DPadDown);
2748
assert_eq!(event.state, ButtonState::Pressed);
2749
}
2750
assert!(ctx
2751
.app
2752
.world_mut()
2753
.query::<&Gamepad>()
2754
.get(ctx.app.world(), entity)
2755
.unwrap()
2756
.pressed(GamepadButton::DPadDown));
2757
2758
ctx.app
2759
.world_mut()
2760
.resource_mut::<Events<GamepadButtonStateChangedEvent>>()
2761
.clear();
2762
ctx.update();
2763
2764
assert_eq!(
2765
ctx.app
2766
.world()
2767
.resource::<Events<GamepadButtonStateChangedEvent>>()
2768
.len(),
2769
0
2770
);
2771
assert!(ctx
2772
.app
2773
.world_mut()
2774
.query::<&Gamepad>()
2775
.get(ctx.app.world(), entity)
2776
.unwrap()
2777
.pressed(GamepadButton::DPadDown));
2778
}
2779
2780
#[test]
2781
fn gamepad_buttons_just_pressed() {
2782
let mut ctx = TestContext::new();
2783
2784
// Create test gamepad
2785
let entity = ctx.send_gamepad_connection_event(None);
2786
let digital_settings = GamepadSettings::default().default_button_settings;
2787
2788
ctx.send_raw_gamepad_event(RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
2789
entity,
2790
GamepadButton::DPadDown,
2791
digital_settings.press_threshold,
2792
)));
2793
ctx.update();
2794
2795
// Check it is flagged for this frame
2796
assert!(ctx
2797
.app
2798
.world_mut()
2799
.query::<&Gamepad>()
2800
.get(ctx.app.world(), entity)
2801
.unwrap()
2802
.just_pressed(GamepadButton::DPadDown));
2803
ctx.update();
2804
2805
//Check it clears next frame
2806
assert!(!ctx
2807
.app
2808
.world_mut()
2809
.query::<&Gamepad>()
2810
.get(ctx.app.world(), entity)
2811
.unwrap()
2812
.just_pressed(GamepadButton::DPadDown));
2813
}
2814
#[test]
2815
fn gamepad_buttons_released() {
2816
let mut ctx = TestContext::new();
2817
2818
// Create test gamepad
2819
let entity = ctx.send_gamepad_connection_event(None);
2820
let digital_settings = GamepadSettings::default().default_button_settings;
2821
2822
ctx.send_raw_gamepad_event(RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
2823
entity,
2824
GamepadButton::DPadDown,
2825
digital_settings.press_threshold,
2826
)));
2827
ctx.update();
2828
2829
ctx.app
2830
.world_mut()
2831
.resource_mut::<Events<GamepadButtonStateChangedEvent>>()
2832
.clear();
2833
ctx.send_raw_gamepad_event(RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
2834
entity,
2835
GamepadButton::DPadDown,
2836
digital_settings.release_threshold - 0.01,
2837
)));
2838
ctx.update();
2839
assert_eq!(
2840
ctx.app
2841
.world()
2842
.resource::<Events<GamepadButtonStateChangedEvent>>()
2843
.len(),
2844
1
2845
);
2846
let events = ctx
2847
.app
2848
.world()
2849
.resource::<Events<GamepadButtonStateChangedEvent>>();
2850
let mut event_reader = events.get_cursor();
2851
for event in event_reader.read(events) {
2852
assert_eq!(event.button, GamepadButton::DPadDown);
2853
assert_eq!(event.state, ButtonState::Released);
2854
}
2855
assert!(!ctx
2856
.app
2857
.world_mut()
2858
.query::<&Gamepad>()
2859
.get(ctx.app.world(), entity)
2860
.unwrap()
2861
.pressed(GamepadButton::DPadDown));
2862
ctx.app
2863
.world_mut()
2864
.resource_mut::<Events<GamepadButtonStateChangedEvent>>()
2865
.clear();
2866
ctx.update();
2867
2868
assert_eq!(
2869
ctx.app
2870
.world()
2871
.resource::<Events<GamepadButtonStateChangedEvent>>()
2872
.len(),
2873
0
2874
);
2875
}
2876
2877
#[test]
2878
fn gamepad_buttons_just_released() {
2879
let mut ctx = TestContext::new();
2880
2881
// Create test gamepad
2882
let entity = ctx.send_gamepad_connection_event(None);
2883
let digital_settings = GamepadSettings::default().default_button_settings;
2884
2885
ctx.send_raw_gamepad_event_batch([
2886
RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
2887
entity,
2888
GamepadButton::DPadDown,
2889
digital_settings.press_threshold,
2890
)),
2891
RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
2892
entity,
2893
GamepadButton::DPadDown,
2894
digital_settings.release_threshold - 0.01,
2895
)),
2896
]);
2897
ctx.update();
2898
2899
// Check it is flagged for this frame
2900
assert!(ctx
2901
.app
2902
.world_mut()
2903
.query::<&Gamepad>()
2904
.get(ctx.app.world(), entity)
2905
.unwrap()
2906
.just_released(GamepadButton::DPadDown));
2907
ctx.update();
2908
2909
//Check it clears next frame
2910
assert!(!ctx
2911
.app
2912
.world_mut()
2913
.query::<&Gamepad>()
2914
.get(ctx.app.world(), entity)
2915
.unwrap()
2916
.just_released(GamepadButton::DPadDown));
2917
}
2918
2919
#[test]
2920
fn gamepad_buttons_axis() {
2921
let mut ctx = TestContext::new();
2922
2923
// Create test gamepad
2924
let entity = ctx.send_gamepad_connection_event(None);
2925
let digital_settings = GamepadSettings::default().default_button_settings;
2926
let analog_settings = GamepadSettings::default().default_button_axis_settings;
2927
2928
// Test events
2929
let events = [
2930
// Should trigger event
2931
RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
2932
entity,
2933
GamepadButton::DPadDown,
2934
digital_settings.press_threshold,
2935
)),
2936
// Should trigger event
2937
RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
2938
entity,
2939
GamepadButton::DPadDown,
2940
digital_settings.release_threshold,
2941
)),
2942
// Shouldn't trigger a state changed event
2943
RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
2944
entity,
2945
GamepadButton::DPadDown,
2946
digital_settings.release_threshold - analog_settings.threshold * 1.01,
2947
)),
2948
// Shouldn't trigger any event
2949
RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
2950
entity,
2951
GamepadButton::DPadDown,
2952
digital_settings.release_threshold - (analog_settings.threshold * 1.5),
2953
)),
2954
// Shouldn't trigger a state changed event
2955
RawGamepadEvent::Button(RawGamepadButtonChangedEvent::new(
2956
entity,
2957
GamepadButton::DPadDown,
2958
digital_settings.release_threshold - (analog_settings.threshold * 2.02),
2959
)),
2960
];
2961
ctx.send_raw_gamepad_event_batch(events);
2962
ctx.update();
2963
assert_eq!(
2964
ctx.app
2965
.world()
2966
.resource::<Events<GamepadButtonStateChangedEvent>>()
2967
.len(),
2968
2
2969
);
2970
assert_eq!(
2971
ctx.app
2972
.world()
2973
.resource::<Events<GamepadButtonChangedEvent>>()
2974
.len(),
2975
4
2976
);
2977
}
2978
}
2979
2980