Skip to main content

bevy_input/
gamepad.rs

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