leafwing_input_manager/user_input/
gamepad.rs

1//! Gamepad inputs
2
3use std::hash::{Hash, Hasher};
4
5use bevy::ecs::message::Messages;
6use bevy::ecs::system::lifetimeless::{Read, SQuery};
7use bevy::ecs::system::{StaticSystemParam, SystemParam, SystemState};
8use bevy::input::gamepad::{
9    GamepadInput, RawGamepadAxisChangedEvent, RawGamepadButtonChangedEvent, RawGamepadEvent,
10};
11use bevy::input::{Axis, ButtonInput};
12use bevy::math::FloatOrd;
13use bevy::prelude::{
14    Entity, Gamepad, GamepadAxis, GamepadButton, Query, Reflect, Res, ResMut, Vec2, With, World,
15};
16use leafwing_input_manager_macros::serde_typetag;
17use serde::{Deserialize, Serialize};
18
19use crate as leafwing_input_manager;
20use crate::axislike::AxisDirection;
21use crate::buttonlike::ButtonValue;
22use crate::clashing_inputs::BasicInputs;
23use crate::input_processing::{
24    AxisProcessor, DualAxisProcessor, WithAxisProcessingPipelineExt,
25    WithDualAxisProcessingPipelineExt,
26};
27use crate::user_input::UserInput;
28use crate::InputControlKind;
29
30use super::updating::{CentralInputStore, UpdatableInput};
31use super::{Axislike, Buttonlike, DualAxislike};
32
33/// Retrieves the first connected gamepad.
34///
35/// If no gamepad is connected, `Entity::PLACEHOLDER` is returned.
36#[must_use]
37pub fn find_gamepad(gamepads: Option<Query<Entity, With<Gamepad>>>) -> Entity {
38    match gamepads {
39        None => Entity::PLACEHOLDER,
40        Some(gamepads) => gamepads.iter().next().unwrap_or(Entity::PLACEHOLDER),
41    }
42}
43
44/// Retrieves the current value of the specified `axis`.
45#[must_use]
46#[inline]
47fn read_axis_value(input_store: &CentralInputStore, gamepad: Entity, axis: GamepadAxis) -> f32 {
48    let axis = SpecificGamepadAxis::new(gamepad, axis);
49    input_store.value(&axis)
50}
51
52/// A [`GamepadAxis`] for a specific gamepad (as opposed to all gamepads).
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
54pub struct SpecificGamepadAxis {
55    /// The gamepad that this axis is attached to.
56    pub gamepad: Entity,
57    /// The axis.
58    pub axis: GamepadAxis,
59}
60
61impl SpecificGamepadAxis {
62    /// Creates a new [`SpecificGamepadAxis`] with the given gamepad and axis.
63    pub fn new(gamepad: Entity, axis: GamepadAxis) -> Self {
64        Self { gamepad, axis }
65    }
66}
67
68/// A [`GamepadButton`] for a specific gamepad (as opposed to all gamepads).
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
70pub struct SpecificGamepadButton {
71    /// The gamepad that this button is attached to.
72    pub gamepad: Entity,
73    /// The button.
74    pub button: GamepadButton,
75}
76
77impl SpecificGamepadButton {
78    /// Creates a new [`SpecificGamepadButton`] with the given gamepad and
79    /// button.
80    pub fn new(gamepad: Entity, button: GamepadButton) -> Self {
81        Self { gamepad, button }
82    }
83}
84
85/// Provides button-like behavior for a specific direction on a [`GamepadAxis`].
86///
87/// By default, it reads from **any connected gamepad**.
88/// Use the [`InputMap::set_gamepad`](crate::input_map::InputMap::set_gamepad) for specific ones.
89///
90/// ```rust,ignore
91/// use bevy::prelude::*;
92/// use bevy::input::InputPlugin;
93/// use bevy::input::gamepad::GamepadEvent;
94/// use leafwing_input_manager::prelude::*;
95///
96/// let mut app = App::new();
97/// app.add_plugins(InputPlugin);
98///
99/// // Positive Y-axis movement on left stick
100/// let input = GamepadControlDirection::LEFT_UP;
101///
102/// // Movement in the opposite direction doesn't activate the input
103/// GamepadControlAxis::LEFT_Y.set_value(app.world_mut(), -1.0);
104/// app.update();
105/// assert!(!app.read_pressed(input));
106///
107/// // Movement in the chosen direction activates the input
108/// GamepadControlAxis::LEFT_Y.set_value(app.world_mut(), 1.0);
109/// app.update();
110/// assert!(app.read_pressed(input));
111/// ```
112#[derive(Debug, Clone, Copy, PartialEq, Reflect, Serialize, Deserialize)]
113#[must_use]
114pub struct GamepadControlDirection {
115    /// The axis that this input tracks.
116    pub axis: GamepadAxis,
117
118    /// The direction of the axis to monitor (positive or negative).
119    pub direction: AxisDirection,
120
121    /// The threshold value for the direction to be considered pressed.
122    /// Must be non-negative.
123    pub threshold: f32,
124}
125
126impl GamepadControlDirection {
127    /// Creates a [`GamepadControlDirection`] triggered by a negative value on the specified `axis`.
128    #[inline]
129    pub const fn negative(axis: GamepadAxis) -> Self {
130        Self {
131            axis,
132            direction: AxisDirection::Negative,
133            threshold: 0.0,
134        }
135    }
136
137    /// Creates a [`GamepadControlDirection`] triggered by a positive value on the specified `axis`.
138    #[inline]
139    pub const fn positive(axis: GamepadAxis) -> Self {
140        Self {
141            axis,
142            direction: AxisDirection::Positive,
143            threshold: 0.0,
144        }
145    }
146
147    /// Sets the `threshold` value.
148    ///
149    /// # Requirements
150    ///
151    /// - `threshold` >= `0.0`.
152    ///
153    /// # Panics
154    ///
155    /// Panics if the requirement isn't met.
156    #[inline]
157    pub fn threshold(mut self, threshold: f32) -> Self {
158        assert!(threshold >= 0.0);
159        self.threshold = threshold;
160        self
161    }
162
163    /// "Up" on the left analog stick (positive Y-axis movement).
164    pub const LEFT_UP: Self = Self::positive(GamepadAxis::LeftStickY);
165
166    /// "Down" on the left analog stick (negative Y-axis movement).
167    pub const LEFT_DOWN: Self = Self::negative(GamepadAxis::LeftStickY);
168
169    /// "Left" on the left analog stick (negative X-axis movement).
170    pub const LEFT_LEFT: Self = Self::negative(GamepadAxis::LeftStickX);
171
172    /// "Right" on the left analog stick (positive X-axis movement).
173    pub const LEFT_RIGHT: Self = Self::positive(GamepadAxis::LeftStickX);
174
175    /// "Up" on the right analog stick (positive Y-axis movement).
176    pub const RIGHT_UP: Self = Self::positive(GamepadAxis::RightStickY);
177
178    /// "Down" on the right analog stick (negative Y-axis movement).
179    pub const RIGHT_DOWN: Self = Self::negative(GamepadAxis::RightStickY);
180
181    /// "Left" on the right analog stick (negative X-axis movement).
182    pub const RIGHT_LEFT: Self = Self::negative(GamepadAxis::RightStickX);
183
184    /// "Right" on the right analog stick (positive X-axis movement).
185    pub const RIGHT_RIGHT: Self = Self::positive(GamepadAxis::RightStickX);
186}
187
188impl UserInput for GamepadControlDirection {
189    /// [`GamepadControlDirection`] acts as a virtual button.
190    #[inline]
191    fn kind(&self) -> InputControlKind {
192        InputControlKind::Button
193    }
194
195    /// [`GamepadControlDirection`] represents a simple virtual button.
196    #[inline]
197    fn decompose(&self) -> BasicInputs {
198        BasicInputs::Simple(Box::new((*self).threshold(0.0)))
199    }
200}
201
202#[serde_typetag]
203impl Buttonlike for GamepadControlDirection {
204    /// Checks if there is any recent stick movement along the specified direction.
205    #[inline]
206    fn pressed(&self, input_store: &CentralInputStore, gamepad: Entity) -> bool {
207        let value = read_axis_value(input_store, gamepad, self.axis);
208        self.direction.is_active(value, self.threshold)
209    }
210
211    /// Sends a [`RawGamepadEvent::Axis`] message with a magnitude of 1.0 for the specified direction on the provided gamepad [`Entity`].
212    fn press_as_gamepad(&self, world: &mut World, gamepad: Option<Entity>) {
213        let mut query_state = SystemState::<Query<Entity, With<Gamepad>>>::new(world);
214        let query = query_state.get(world);
215        let gamepad = gamepad.unwrap_or(find_gamepad(Some(query)));
216
217        let message = RawGamepadEvent::Axis(RawGamepadAxisChangedEvent {
218            gamepad,
219            axis: self.axis,
220            value: self.direction.full_active_value(),
221        });
222        world
223            .resource_mut::<Messages<RawGamepadEvent>>()
224            .write(message);
225    }
226
227    /// Sends a [`RawGamepadEvent::Axis`] message with a magnitude of 0.0 for the specified direction.
228    fn release_as_gamepad(&self, world: &mut World, gamepad: Option<Entity>) {
229        let mut query_state = SystemState::<Query<Entity, With<Gamepad>>>::new(world);
230        let query = query_state.get(world);
231        let gamepad = gamepad.unwrap_or(find_gamepad(Some(query)));
232
233        let message = RawGamepadEvent::Axis(RawGamepadAxisChangedEvent {
234            gamepad,
235            axis: self.axis,
236            value: 0.0,
237        });
238        world
239            .resource_mut::<Messages<RawGamepadEvent>>()
240            .write(message);
241    }
242
243    fn set_value_as_gamepad(&self, world: &mut World, value: f32, gamepad: Option<Entity>) {
244        if value > 0.0 {
245            self.press_as_gamepad(world, gamepad);
246        } else {
247            self.release_as_gamepad(world, gamepad);
248        }
249    }
250}
251
252impl Eq for GamepadControlDirection {}
253
254impl Hash for GamepadControlDirection {
255    fn hash<H: Hasher>(&self, state: &mut H) {
256        self.axis.hash(state);
257        self.direction.hash(state);
258        FloatOrd(self.threshold).hash(state);
259    }
260}
261
262impl UpdatableInput for GamepadAxis {
263    type SourceData = SQuery<(Entity, Read<Gamepad>)>;
264
265    fn compute(
266        mut central_input_store: ResMut<CentralInputStore>,
267        source_data: StaticSystemParam<Self::SourceData>,
268    ) {
269        for (gamepad_entity, gamepad) in source_data.iter() {
270            for input in gamepad.get_analog_axes() {
271                let GamepadInput::Axis(axis) = input else {
272                    continue;
273                };
274                let value = gamepad.get(*axis).unwrap_or_default();
275                central_input_store.update_axislike(
276                    SpecificGamepadAxis {
277                        gamepad: gamepad_entity,
278                        axis: *axis,
279                    },
280                    value,
281                );
282                central_input_store.update_axislike(*axis, value);
283            }
284        }
285    }
286}
287
288impl UserInput for GamepadAxis {
289    fn kind(&self) -> InputControlKind {
290        InputControlKind::Axis
291    }
292
293    fn decompose(&self) -> BasicInputs {
294        BasicInputs::Composite(vec![
295            Box::new(GamepadControlDirection::negative(*self)),
296            Box::new(GamepadControlDirection::positive(*self)),
297        ])
298    }
299}
300
301#[serde_typetag]
302impl Axislike for GamepadAxis {
303    fn value(&self, input_store: &CentralInputStore, gamepad: Entity) -> f32 {
304        read_axis_value(input_store, gamepad, *self)
305    }
306}
307
308/// Unlike [`GamepadButton`], this struct represents a specific axis on a specific gamepad.
309///
310/// In the majority of cases, [`GamepadControlAxis`] or [`GamepadStick`] should be used instead.
311impl UserInput for SpecificGamepadAxis {
312    fn kind(&self) -> InputControlKind {
313        InputControlKind::Axis
314    }
315
316    fn decompose(&self) -> BasicInputs {
317        BasicInputs::Composite(vec![
318            Box::new(GamepadControlDirection::negative(self.axis)),
319            Box::new(GamepadControlDirection::positive(self.axis)),
320        ])
321    }
322}
323
324#[serde_typetag]
325impl Axislike for SpecificGamepadAxis {
326    fn value(&self, input_store: &CentralInputStore, gamepad: Entity) -> f32 {
327        read_axis_value(input_store, gamepad, self.axis)
328    }
329}
330
331/// A wrapper around a specific [`GamepadAxis`] (e.g., left stick X-axis, right stick Y-axis).
332///
333/// By default, it reads from **any connected gamepad**.
334/// Use the [`InputMap::set_gamepad`](crate::input_map::InputMap::set_gamepad) for specific ones.
335///
336/// # Value Processing
337///
338/// You can customize how the values are processed using a pipeline of processors.
339/// See [`WithAxisProcessingPipelineExt`] for details.
340///
341/// ```rust,ignore
342/// use bevy::prelude::*;
343/// use bevy::input::InputPlugin;
344/// use leafwing_input_manager::prelude::*;
345///
346/// let mut app = App::new();
347/// app.add_plugins(InputPlugin);
348///
349/// // Y-axis movement on left stick
350/// let input = GamepadControlAxis::LEFT_Y;
351///
352/// // Movement on the chosen axis activates the input
353/// GamepadControlAxis::LEFT_Y.set_value(app.world_mut(), 1.0);
354/// app.update();
355/// assert_eq!(app.read_axis_value(input), 1.0);
356///
357/// // You can configure a processing pipeline (e.g., doubling the value)
358/// let doubled = GamepadControlAxis::LEFT_Y.sensitivity(2.0);
359/// assert_eq!(app.read_axis_value(doubled), 2.0);
360/// ```
361#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
362#[must_use]
363pub struct GamepadControlAxis {
364    /// The wrapped axis.
365    pub axis: GamepadAxis,
366
367    /// A processing pipeline that handles input values.
368    pub processors: Vec<AxisProcessor>,
369}
370
371impl GamepadControlAxis {
372    /// Creates a [`GamepadControlAxis`] for continuous input from the given axis.
373    /// No processing is applied to raw data from the gamepad.
374    #[inline]
375    pub const fn new(axis: GamepadAxis) -> Self {
376        Self {
377            axis,
378            processors: Vec::new(),
379        }
380    }
381
382    /// The horizontal axis (X-axis) of the left stick.
383    /// No processing is applied to raw data from the gamepad.
384    pub const LEFT_X: Self = Self::new(GamepadAxis::LeftStickX);
385
386    /// The vertical axis (Y-axis) of the left stick.
387    /// No processing is applied to raw data from the gamepad.
388    pub const LEFT_Y: Self = Self::new(GamepadAxis::LeftStickY);
389
390    /// The left `Z` button. No processing is applied to raw data from the gamepad.
391    pub const LEFT_Z: Self = Self::new(GamepadAxis::LeftZ);
392
393    /// The horizontal axis (X-axis) of the right stick.
394    /// No processing is applied to raw data from the gamepad.
395    pub const RIGHT_X: Self = Self::new(GamepadAxis::RightStickX);
396
397    /// The vertical axis (Y-axis) of the right stick.
398    /// No processing is applied to raw data from the gamepad.
399    pub const RIGHT_Y: Self = Self::new(GamepadAxis::RightStickY);
400
401    /// The right `Z` button. No processing is applied to raw data from the gamepad.
402    pub const RIGHT_Z: Self = Self::new(GamepadAxis::RightZ);
403}
404
405impl UserInput for GamepadControlAxis {
406    /// [`GamepadControlAxis`] acts as an axis input.
407    #[inline]
408    fn kind(&self) -> InputControlKind {
409        InputControlKind::Axis
410    }
411
412    /// [`GamepadControlAxis`] represents a composition of two [`GamepadControlDirection`]s.
413    #[inline]
414    fn decompose(&self) -> BasicInputs {
415        BasicInputs::Composite(vec![
416            Box::new(GamepadControlDirection::negative(self.axis)),
417            Box::new(GamepadControlDirection::positive(self.axis)),
418        ])
419    }
420}
421
422#[serde_typetag]
423impl Axislike for GamepadControlAxis {
424    /// Retrieves the current value of this axis after processing by the associated processors.
425    #[inline]
426    fn value(&self, input_store: &CentralInputStore, gamepad: Entity) -> f32 {
427        let value = read_axis_value(input_store, gamepad, self.axis);
428        self.processors
429            .iter()
430            .fold(value, |value, processor| processor.process(value))
431    }
432
433    /// Sends a [`RawGamepadEvent::Axis`] message with the specified value on the provided gamepad.
434    fn set_value_as_gamepad(&self, world: &mut World, value: f32, gamepad: Option<Entity>) {
435        let mut query_state = SystemState::<Query<Entity, With<Gamepad>>>::new(world);
436        let query = query_state.get(world);
437        let gamepad = gamepad.unwrap_or(find_gamepad(Some(query)));
438
439        let message = RawGamepadEvent::Axis(RawGamepadAxisChangedEvent {
440            gamepad,
441            axis: self.axis,
442            value,
443        });
444        world
445            .resource_mut::<Messages<RawGamepadEvent>>()
446            .write(message);
447    }
448}
449
450impl WithAxisProcessingPipelineExt for GamepadControlAxis {
451    #[inline]
452    fn reset_processing_pipeline(mut self) -> Self {
453        self.processors.clear();
454        self
455    }
456
457    #[inline]
458    fn replace_processing_pipeline(
459        mut self,
460        processors: impl IntoIterator<Item = AxisProcessor>,
461    ) -> Self {
462        self.processors = processors.into_iter().collect();
463        self
464    }
465
466    #[inline]
467    fn with_processor(mut self, processor: impl Into<AxisProcessor>) -> Self {
468        self.processors.push(processor.into());
469        self
470    }
471}
472
473/// A gamepad stick (e.g., left stick and right stick).
474///
475/// By default, it reads from **any connected gamepad**.
476/// Use the [`InputMap::set_gamepad`](crate::input_map::InputMap::set_gamepad) for specific ones.
477///
478/// # Value Processing
479///
480/// You can customize how the values are processed using a pipeline of processors.
481/// See [`WithDualAxisProcessingPipelineExt`] for details.
482///
483/// ```rust,ignore
484/// use bevy::prelude::*;
485/// use bevy::input::InputPlugin;
486/// use leafwing_input_manager::prelude::*;
487///
488/// let mut app = App::new();
489/// app.add_plugins(InputPlugin);
490///
491/// // Left stick
492/// let input = GamepadStick::LEFT;
493///
494/// // Movement on either axis activates the input
495/// GamepadControlAxis::LEFT_Y.set_value(app.world_mut(), 1.0);
496/// app.update();
497/// assert_eq!(app.read_axis_values(input), [0.0, 1.0]);
498///
499/// // You can configure a processing pipeline (e.g., doubling the Y value)
500/// let doubled = GamepadStick::LEFT.sensitivity_y(2.0);
501/// assert_eq!(app.read_axis_values(doubled), [2.0]);
502/// ```
503#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
504#[must_use]
505pub struct GamepadStick {
506    /// Horizontal movement of the stick.
507    pub x: GamepadAxis,
508
509    /// Vertical movement of the stick.
510    pub y: GamepadAxis,
511
512    /// A processing pipeline that handles input values.
513    pub processors: Vec<DualAxisProcessor>,
514}
515
516impl GamepadStick {
517    /// The left gamepad stick. No processing is applied to raw data from the gamepad.
518    pub const LEFT: Self = Self {
519        x: GamepadAxis::LeftStickX,
520        y: GamepadAxis::LeftStickY,
521        processors: Vec::new(),
522    };
523
524    /// The right gamepad stick. No processing is applied to raw data from the gamepad.
525    pub const RIGHT: Self = Self {
526        x: GamepadAxis::RightStickX,
527        y: GamepadAxis::RightStickY,
528        processors: Vec::new(),
529    };
530}
531
532impl UserInput for GamepadStick {
533    /// [`GamepadStick`] acts as a dual-axis input.
534    #[inline]
535    fn kind(&self) -> InputControlKind {
536        InputControlKind::DualAxis
537    }
538
539    /// [`GamepadStick`] represents a composition of four [`GamepadControlDirection`]s.
540    #[inline]
541    fn decompose(&self) -> BasicInputs {
542        BasicInputs::Composite(vec![
543            Box::new(GamepadControlDirection::negative(self.x)),
544            Box::new(GamepadControlDirection::positive(self.x)),
545            Box::new(GamepadControlDirection::negative(self.y)),
546            Box::new(GamepadControlDirection::positive(self.y)),
547        ])
548    }
549}
550
551#[serde_typetag]
552impl DualAxislike for GamepadStick {
553    /// Retrieves the current X and Y values of this stick after processing by the associated processors.
554    #[inline]
555    fn axis_pair(&self, input_store: &CentralInputStore, gamepad: Entity) -> Vec2 {
556        let x = read_axis_value(input_store, gamepad, self.x);
557        let y = read_axis_value(input_store, gamepad, self.y);
558        self.processors
559            .iter()
560            .fold(Vec2::new(x, y), |value, processor| processor.process(value))
561    }
562
563    /// Sends a [`RawGamepadEvent::Axis`] message with the specified values on the provided gamepad [`Entity`].
564    fn set_axis_pair_as_gamepad(&self, world: &mut World, value: Vec2, gamepad: Option<Entity>) {
565        let mut query_state = SystemState::<Query<Entity, With<Gamepad>>>::new(world);
566        let query = query_state.get(world);
567        let gamepad = gamepad.unwrap_or(find_gamepad(Some(query)));
568
569        let message = RawGamepadEvent::Axis(RawGamepadAxisChangedEvent {
570            gamepad,
571            axis: self.x,
572            value: value.x,
573        });
574        world
575            .resource_mut::<Messages<RawGamepadEvent>>()
576            .write(message);
577
578        let message = RawGamepadEvent::Axis(RawGamepadAxisChangedEvent {
579            gamepad,
580            axis: self.y,
581            value: value.y,
582        });
583        world
584            .resource_mut::<Messages<RawGamepadEvent>>()
585            .write(message);
586    }
587}
588
589impl WithDualAxisProcessingPipelineExt for GamepadStick {
590    #[inline]
591    fn reset_processing_pipeline(mut self) -> Self {
592        self.processors.clear();
593        self
594    }
595
596    #[inline]
597    fn replace_processing_pipeline(
598        mut self,
599        processor: impl IntoIterator<Item = DualAxisProcessor>,
600    ) -> Self {
601        self.processors = processor.into_iter().collect();
602        self
603    }
604
605    #[inline]
606    fn with_processor(mut self, processor: impl Into<DualAxisProcessor>) -> Self {
607        self.processors.push(processor.into());
608        self
609    }
610}
611
612/// Checks if the given [`GamepadButton`] is currently pressed.
613#[must_use]
614#[inline]
615fn button_pressed(input_store: &CentralInputStore, gamepad: Entity, button: GamepadButton) -> bool {
616    let button = SpecificGamepadButton::new(gamepad, button);
617    input_store.pressed(&button)
618}
619
620/// Retrieves the current value of the given [`GamepadButton`].
621///
622/// This will be 0.0 if the button is released, and 1.0 if it is pressed.
623/// Physically triggerlike buttons will return a value between 0.0 and 1.0,
624/// depending on how far the button is pressed.
625#[must_use]
626#[inline]
627fn button_value(input_store: &CentralInputStore, gamepad: Entity, button: GamepadButton) -> f32 {
628    let button = SpecificGamepadButton::new(gamepad, button);
629    input_store.button_value(&button)
630}
631
632/// The [`SystemParam`] that combines the [`ButtonInput`] and [`Axis`] resources for [`GamepadButton`]s.
633#[derive(SystemParam)]
634pub struct GamepadButtonInput<'w> {
635    /// The [`ButtonInput`] for [`GamepadButton`]s.
636    pub buttons: Res<'w, ButtonInput<GamepadButton>>,
637
638    /// The [`Axis`] for [`GamepadButton`]s.
639    pub axes: Res<'w, Axis<GamepadButton>>,
640}
641
642impl UpdatableInput for GamepadButton {
643    type SourceData = SQuery<(Entity, Read<Gamepad>)>;
644
645    fn compute(
646        mut central_input_store: ResMut<CentralInputStore>,
647        source_data: StaticSystemParam<Self::SourceData>,
648    ) {
649        for (gamepad_entity, gamepad) in source_data.iter() {
650            for key in gamepad.get_pressed() {
651                let specific_button = SpecificGamepadButton {
652                    gamepad: gamepad_entity,
653                    button: *key,
654                };
655                let value = gamepad.get(*key).unwrap_or(1.0);
656                central_input_store
657                    .update_buttonlike(specific_button, ButtonValue::new(true, value));
658            }
659
660            for key in gamepad.get_just_released() {
661                let specific_button = SpecificGamepadButton {
662                    gamepad: gamepad_entity,
663                    button: *key,
664                };
665                let value = gamepad.get(*key).unwrap_or(0.0);
666                central_input_store
667                    .update_buttonlike(specific_button, ButtonValue::new(false, value));
668            }
669        }
670    }
671}
672
673/// Unlike [`GamepadButton`], this struct represents a specific button on a specific gamepad.
674///
675/// In the majority of cases, [`GamepadButton`] should be used instead.
676impl UserInput for SpecificGamepadButton {
677    fn kind(&self) -> InputControlKind {
678        InputControlKind::Button
679    }
680
681    fn decompose(&self) -> BasicInputs {
682        BasicInputs::Simple(Box::new(*self))
683    }
684}
685
686#[serde_typetag]
687impl Buttonlike for SpecificGamepadButton {
688    /// WARNING: The supplied gamepad is ignored, as the button is already specific to a gamepad.
689    fn pressed(&self, input_store: &CentralInputStore, _gamepad: Entity) -> bool {
690        button_pressed(input_store, self.gamepad, self.button)
691    }
692
693    /// WARNING: The supplied gamepad is ignored, as the button is already specific to a gamepad.
694    fn value(&self, input_store: &CentralInputStore, _gamepad: Entity) -> f32 {
695        button_value(input_store, self.gamepad, self.button)
696    }
697
698    fn press(&self, world: &mut World) {
699        self.set_value(world, 1.0);
700    }
701
702    fn release(&self, world: &mut World) {
703        self.set_value(world, 0.0);
704    }
705
706    fn set_value(&self, world: &mut World, value: f32) {
707        let message = RawGamepadEvent::Button(RawGamepadButtonChangedEvent {
708            gamepad: self.gamepad,
709            button: self.button,
710            value,
711        });
712        world
713            .resource_mut::<Messages<RawGamepadEvent>>()
714            .write(message);
715    }
716}
717
718// Built-in support for Bevy's GamepadButton.
719impl UserInput for GamepadButton {
720    /// [`GamepadButton`] acts as a button.
721    #[inline]
722    fn kind(&self) -> InputControlKind {
723        InputControlKind::Button
724    }
725
726    /// Creates a [`BasicInputs`] that only contains the [`GamepadButton`] itself,
727    /// as it represents a simple physical button.
728    #[inline]
729    fn decompose(&self) -> BasicInputs {
730        BasicInputs::Simple(Box::new(*self))
731    }
732}
733
734#[serde_typetag]
735impl Buttonlike for GamepadButton {
736    /// Checks if the specified button is currently pressed down.
737    #[inline]
738    fn pressed(&self, input_store: &CentralInputStore, gamepad: Entity) -> bool {
739        button_pressed(input_store, gamepad, *self)
740    }
741
742    /// Retrieves the current value of the specified button.
743    ///
744    /// This will be 0.0 if the button is released, and 1.0 if it is pressed.
745    /// Physically triggerlike buttons will return a value between 0.0 and 1.0,
746    /// depending on how far the button is pressed.
747    #[inline]
748    fn value(&self, input_store: &CentralInputStore, gamepad: Entity) -> f32 {
749        button_value(input_store, gamepad, *self)
750    }
751
752    /// Sends a [`RawGamepadEvent::Button`] message with a magnitude of 1.0 in the direction defined by `self` on the provided gamepad [`Entity`].
753    fn press_as_gamepad(&self, world: &mut World, gamepad: Option<Entity>) {
754        self.set_value_as_gamepad(world, 1.0, gamepad);
755    }
756
757    /// Sends a [`RawGamepadEvent::Button`] message with a magnitude of 0.0 in the direction defined by `self` on the provided gamepad [`Entity`].
758    fn release_as_gamepad(&self, world: &mut World, gamepad: Option<Entity>) {
759        self.set_value_as_gamepad(world, 0.0, gamepad);
760    }
761
762    /// Sends a [`RawGamepadEvent::Button`] message with the specified value in the direction defined by `self` on the provided gamepad [`Entity`].
763    #[inline]
764    fn set_value_as_gamepad(&self, world: &mut World, value: f32, gamepad: Option<Entity>) {
765        let mut query_state = SystemState::<Query<Entity, With<Gamepad>>>::new(world);
766        let query = query_state.get(world);
767        let gamepad = gamepad.unwrap_or(find_gamepad(Some(query)));
768
769        let message = RawGamepadEvent::Button(RawGamepadButtonChangedEvent {
770            gamepad,
771            button: *self,
772            value,
773        });
774        world
775            .resource_mut::<Messages<RawGamepadEvent>>()
776            .write(message);
777    }
778}
779
780#[cfg(test)]
781mod tests {
782    use super::*;
783    use crate::plugin::CentralInputStorePlugin;
784    use bevy::input::gamepad::{GamepadConnection, GamepadConnectionEvent};
785    use bevy::input::InputPlugin;
786    use bevy::prelude::*;
787
788    fn test_app() -> App {
789        let mut app = App::new();
790        app.add_plugins(MinimalPlugins);
791        app.add_plugins((InputPlugin, CentralInputStorePlugin));
792
793        // WARNING: you MUST register your gamepad during tests,
794        // or all gamepad input mocking actions will fail
795        let gamepad = app.world_mut().spawn(()).id();
796        let mut gamepad_connection_messages = app
797            .world_mut()
798            .resource_mut::<Messages<GamepadConnectionEvent>>();
799        gamepad_connection_messages.write(GamepadConnectionEvent {
800            // This MUST be consistent with any other mocked messages
801            gamepad,
802            connection: GamepadConnection::Connected {
803                name: "TestController".into(),
804                vendor_id: None,
805                product_id: None,
806            },
807        });
808
809        // Ensure that the gamepad is picked up by the appropriate system
810        app.update();
811        // Ensure that the connection message is flushed through
812        app.update();
813
814        app
815    }
816
817    #[test]
818    fn test_gamepad_axes() {
819        let left_up = GamepadControlDirection::LEFT_UP;
820        assert_eq!(left_up.kind(), InputControlKind::Button);
821
822        // The opposite of left up
823        let left_down = GamepadControlDirection::LEFT_DOWN;
824        assert_eq!(left_down.kind(), InputControlKind::Button);
825
826        let left_x = GamepadControlAxis::LEFT_X;
827        assert_eq!(left_x.kind(), InputControlKind::Axis);
828
829        let left_y = GamepadControlAxis::LEFT_Y;
830        assert_eq!(left_y.kind(), InputControlKind::Axis);
831
832        let left = GamepadStick::LEFT;
833        assert_eq!(left.kind(), InputControlKind::DualAxis);
834
835        // Up; but for the other stick
836        let right_up = GamepadControlDirection::RIGHT_DOWN;
837        assert_eq!(right_up.kind(), InputControlKind::Button);
838
839        let right_y = GamepadControlAxis::RIGHT_Y;
840        assert_eq!(right_y.kind(), InputControlKind::Axis);
841
842        let right = GamepadStick::RIGHT;
843        assert_eq!(right.kind(), InputControlKind::DualAxis);
844
845        // No inputs
846        let mut app = test_app();
847        app.update();
848        let gamepad = app
849            .world_mut()
850            .query_filtered::<Entity, With<Gamepad>>()
851            .iter(app.world())
852            .next()
853            .unwrap();
854        let inputs = app.world().resource::<CentralInputStore>();
855
856        assert!(!left_up.pressed(inputs, gamepad));
857        assert!(!left_down.pressed(inputs, gamepad));
858        assert!(!right_up.pressed(inputs, gamepad));
859        assert_eq!(left_x.value(inputs, gamepad), 0.0);
860        assert_eq!(left_y.value(inputs, gamepad), 0.0);
861        assert_eq!(right_y.value(inputs, gamepad), 0.0);
862        assert_eq!(left.axis_pair(inputs, gamepad), Vec2::ZERO);
863        assert_eq!(right.axis_pair(inputs, gamepad), Vec2::ZERO);
864
865        // Left stick moves upward
866        let data = Vec2::new(0.0, 1.0);
867        let mut app = test_app();
868        let gamepad = app
869            .world_mut()
870            .query_filtered::<Entity, With<Gamepad>>()
871            .iter(app.world())
872            .next()
873            .unwrap();
874        GamepadControlDirection::LEFT_UP.press_as_gamepad(app.world_mut(), Some(gamepad));
875        app.update();
876        let inputs = app.world().resource::<CentralInputStore>();
877
878        assert!(left_up.pressed(inputs, gamepad));
879        assert!(!left_down.pressed(inputs, gamepad));
880        assert!(!right_up.pressed(inputs, gamepad));
881        assert_eq!(left_x.value(inputs, gamepad), 0.0);
882        assert_eq!(left_y.value(inputs, gamepad), 1.0);
883        assert_eq!(right_y.value(inputs, gamepad), 0.0);
884        assert_eq!(left.axis_pair(inputs, gamepad), data);
885        assert_eq!(right.axis_pair(inputs, gamepad), Vec2::ZERO);
886
887        // Set Y-axis of left stick to 0.6
888        let data = Vec2::new(0.0, 0.6);
889        let mut app = test_app();
890        let gamepad = app
891            .world_mut()
892            .query_filtered::<Entity, With<Gamepad>>()
893            .iter(app.world())
894            .next()
895            .unwrap();
896        GamepadControlAxis::LEFT_Y.set_value_as_gamepad(app.world_mut(), data.y, Some(gamepad));
897        app.update();
898        let inputs = app.world().resource::<CentralInputStore>();
899
900        assert!(left_up.pressed(inputs, gamepad));
901        assert!(!left_down.pressed(inputs, gamepad));
902        assert!(!right_up.pressed(inputs, gamepad));
903        assert_eq!(left_x.value(inputs, gamepad), 0.0);
904        assert_eq!(left_y.value(inputs, gamepad), 0.6);
905        assert_eq!(right_y.value(inputs, gamepad), 0.0);
906        assert_eq!(left.axis_pair(inputs, gamepad), data);
907        assert_eq!(right.axis_pair(inputs, gamepad), Vec2::ZERO);
908
909        // Set left stick to (0.6, 0.4)
910        let data = Vec2::new(0.6, 0.4);
911        let mut app = test_app();
912        let gamepad = app
913            .world_mut()
914            .query_filtered::<Entity, With<Gamepad>>()
915            .iter(app.world())
916            .next()
917            .unwrap();
918        GamepadStick::LEFT.set_axis_pair_as_gamepad(app.world_mut(), data, Some(gamepad));
919        app.update();
920        let inputs = app.world().resource::<CentralInputStore>();
921
922        assert!(left_up.pressed(inputs, gamepad));
923        assert!(!left_down.pressed(inputs, gamepad));
924        assert!(!right_up.pressed(inputs, gamepad));
925        assert_eq!(left_x.value(inputs, gamepad), data.x);
926        assert_eq!(left_y.value(inputs, gamepad), data.y);
927        assert_eq!(right_y.value(inputs, gamepad), 0.0);
928        assert_eq!(left.axis_pair(inputs, gamepad), data);
929        assert_eq!(right.axis_pair(inputs, gamepad), Vec2::ZERO);
930    }
931
932    #[test]
933    #[ignore = "Input mocking is subtly broken: https://github.com/Leafwing-Studios/leafwing-input-manager/issues/516"]
934    fn test_gamepad_buttons() {
935        let up = GamepadButton::DPadUp;
936        assert_eq!(up.kind(), InputControlKind::Button);
937
938        let left = GamepadButton::DPadLeft;
939        assert_eq!(left.kind(), InputControlKind::Button);
940
941        let down = GamepadButton::DPadDown;
942        assert_eq!(left.kind(), InputControlKind::Button);
943
944        let right = GamepadButton::DPadRight;
945        assert_eq!(left.kind(), InputControlKind::Button);
946
947        // No inputs
948        let mut app = test_app();
949        app.update();
950        let gamepad = app.world_mut().spawn(()).id();
951        let inputs = app.world().resource::<CentralInputStore>();
952
953        assert!(!up.pressed(inputs, gamepad));
954        assert!(!left.pressed(inputs, gamepad));
955        assert!(!down.pressed(inputs, gamepad));
956        assert!(!right.pressed(inputs, gamepad));
957
958        // Press DPadLeft
959        let mut app = test_app();
960        GamepadButton::DPadLeft.press(app.world_mut());
961        app.update();
962        let inputs = app.world().resource::<CentralInputStore>();
963
964        assert!(!up.pressed(inputs, gamepad));
965        assert!(left.pressed(inputs, gamepad));
966        assert!(!down.pressed(inputs, gamepad));
967        assert!(!right.pressed(inputs, gamepad));
968    }
969
970    #[test]
971    #[ignore = "Input mocking is subtly broken: https://github.com/Leafwing-Studios/leafwing-input-manager/issues/516"]
972    fn test_gamepad_button_values() {
973        let up = GamepadButton::DPadUp;
974        assert_eq!(up.kind(), InputControlKind::Button);
975
976        let left = GamepadButton::DPadLeft;
977        assert_eq!(left.kind(), InputControlKind::Button);
978
979        let down = GamepadButton::DPadDown;
980        assert_eq!(down.kind(), InputControlKind::Button);
981
982        let right = GamepadButton::DPadRight;
983        assert_eq!(right.kind(), InputControlKind::Button);
984
985        // No inputs
986        let mut app = test_app();
987        app.update();
988        let gamepad = app.world_mut().spawn(()).id();
989        let inputs = app.world().resource::<CentralInputStore>();
990
991        assert_eq!(up.value(inputs, gamepad), 0.0);
992        assert_eq!(left.value(inputs, gamepad), 0.0);
993        assert_eq!(down.value(inputs, gamepad), 0.0);
994        assert_eq!(right.value(inputs, gamepad), 0.0);
995
996        // Press DPadLeft
997        let mut app = test_app();
998        GamepadButton::DPadLeft.press(app.world_mut());
999        app.update();
1000        let inputs = app.world().resource::<CentralInputStore>();
1001
1002        assert_eq!(up.value(inputs, gamepad), 0.0);
1003        assert_eq!(left.value(inputs, gamepad), 1.0);
1004        assert_eq!(down.value(inputs, gamepad), 0.0);
1005        assert_eq!(right.value(inputs, gamepad), 0.0);
1006    }
1007}