leafwing_input_manager/user_input/
gamepad.rs

1//! Gamepad inputs
2
3use std::hash::{Hash, Hasher};
4
5use bevy::ecs::system::lifetimeless::{Read, SQuery};
6use bevy::ecs::system::{StaticSystemParam, SystemParam, SystemState};
7use bevy::input::gamepad::{
8    GamepadInput, RawGamepadAxisChangedEvent, RawGamepadButtonChangedEvent, RawGamepadEvent,
9};
10use bevy::input::{Axis, ButtonInput};
11use bevy::math::FloatOrd;
12use bevy::prelude::{
13    Entity, Events, Gamepad, GamepadAxis, GamepadButton, Query, Reflect, Res, ResMut, Vec2, With,
14    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`] event 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 event = RawGamepadEvent::Axis(RawGamepadAxisChangedEvent {
218            gamepad,
219            axis: self.axis,
220            value: self.direction.full_active_value(),
221        });
222        world.resource_mut::<Events<RawGamepadEvent>>().send(event);
223    }
224
225    /// Sends a [`RawGamepadEvent::Axis`] event with a magnitude of 0.0 for the specified direction.
226    fn release_as_gamepad(&self, world: &mut World, gamepad: Option<Entity>) {
227        let mut query_state = SystemState::<Query<Entity, With<Gamepad>>>::new(world);
228        let query = query_state.get(world);
229        let gamepad = gamepad.unwrap_or(find_gamepad(Some(query)));
230
231        let event = RawGamepadEvent::Axis(RawGamepadAxisChangedEvent {
232            gamepad,
233            axis: self.axis,
234            value: 0.0,
235        });
236        world.resource_mut::<Events<RawGamepadEvent>>().send(event);
237    }
238
239    fn set_value_as_gamepad(&self, world: &mut World, value: f32, gamepad: Option<Entity>) {
240        if value > 0.0 {
241            self.press_as_gamepad(world, gamepad);
242        } else {
243            self.release_as_gamepad(world, gamepad);
244        }
245    }
246}
247
248impl Eq for GamepadControlDirection {}
249
250impl Hash for GamepadControlDirection {
251    fn hash<H: Hasher>(&self, state: &mut H) {
252        self.axis.hash(state);
253        self.direction.hash(state);
254        FloatOrd(self.threshold).hash(state);
255    }
256}
257
258impl UpdatableInput for GamepadAxis {
259    type SourceData = SQuery<(Entity, Read<Gamepad>)>;
260
261    fn compute(
262        mut central_input_store: ResMut<CentralInputStore>,
263        source_data: StaticSystemParam<Self::SourceData>,
264    ) {
265        for (gamepad_entity, gamepad) in source_data.iter() {
266            for input in gamepad.get_analog_axes() {
267                let GamepadInput::Axis(axis) = input else {
268                    continue;
269                };
270                let value = gamepad.get(*axis).unwrap_or_default();
271                central_input_store.update_axislike(
272                    SpecificGamepadAxis {
273                        gamepad: gamepad_entity,
274                        axis: *axis,
275                    },
276                    value,
277                );
278                central_input_store.update_axislike(*axis, value);
279            }
280        }
281    }
282}
283
284impl UserInput for GamepadAxis {
285    fn kind(&self) -> InputControlKind {
286        InputControlKind::Axis
287    }
288
289    fn decompose(&self) -> BasicInputs {
290        BasicInputs::Composite(vec![
291            Box::new(GamepadControlDirection::negative(*self)),
292            Box::new(GamepadControlDirection::positive(*self)),
293        ])
294    }
295}
296
297#[serde_typetag]
298impl Axislike for GamepadAxis {
299    fn value(&self, input_store: &CentralInputStore, gamepad: Entity) -> f32 {
300        read_axis_value(input_store, gamepad, *self)
301    }
302}
303
304/// Unlike [`GamepadButton`], this struct represents a specific axis on a specific gamepad.
305///
306/// In the majority of cases, [`GamepadControlAxis`] or [`GamepadStick`] should be used instead.
307impl UserInput for SpecificGamepadAxis {
308    fn kind(&self) -> InputControlKind {
309        InputControlKind::Axis
310    }
311
312    fn decompose(&self) -> BasicInputs {
313        BasicInputs::Composite(vec![
314            Box::new(GamepadControlDirection::negative(self.axis)),
315            Box::new(GamepadControlDirection::positive(self.axis)),
316        ])
317    }
318}
319
320#[serde_typetag]
321impl Axislike for SpecificGamepadAxis {
322    fn value(&self, input_store: &CentralInputStore, gamepad: Entity) -> f32 {
323        read_axis_value(input_store, gamepad, self.axis)
324    }
325}
326
327/// A wrapper around a specific [`GamepadAxis`] (e.g., left stick X-axis, right stick Y-axis).
328///
329/// By default, it reads from **any connected gamepad**.
330/// Use the [`InputMap::set_gamepad`](crate::input_map::InputMap::set_gamepad) for specific ones.
331///
332/// # Value Processing
333///
334/// You can customize how the values are processed using a pipeline of processors.
335/// See [`WithAxisProcessingPipelineExt`] for details.
336///
337/// ```rust,ignore
338/// use bevy::prelude::*;
339/// use bevy::input::InputPlugin;
340/// use leafwing_input_manager::prelude::*;
341///
342/// let mut app = App::new();
343/// app.add_plugins(InputPlugin);
344///
345/// // Y-axis movement on left stick
346/// let input = GamepadControlAxis::LEFT_Y;
347///
348/// // Movement on the chosen axis activates the input
349/// GamepadControlAxis::LEFT_Y.set_value(app.world_mut(), 1.0);
350/// app.update();
351/// assert_eq!(app.read_axis_value(input), 1.0);
352///
353/// // You can configure a processing pipeline (e.g., doubling the value)
354/// let doubled = GamepadControlAxis::LEFT_Y.sensitivity(2.0);
355/// assert_eq!(app.read_axis_value(doubled), 2.0);
356/// ```
357#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
358#[must_use]
359pub struct GamepadControlAxis {
360    /// The wrapped axis.
361    pub axis: GamepadAxis,
362
363    /// A processing pipeline that handles input values.
364    pub processors: Vec<AxisProcessor>,
365}
366
367impl GamepadControlAxis {
368    /// Creates a [`GamepadControlAxis`] for continuous input from the given axis.
369    /// No processing is applied to raw data from the gamepad.
370    #[inline]
371    pub const fn new(axis: GamepadAxis) -> Self {
372        Self {
373            axis,
374            processors: Vec::new(),
375        }
376    }
377
378    /// The horizontal axis (X-axis) of the left stick.
379    /// No processing is applied to raw data from the gamepad.
380    pub const LEFT_X: Self = Self::new(GamepadAxis::LeftStickX);
381
382    /// The vertical axis (Y-axis) of the left stick.
383    /// No processing is applied to raw data from the gamepad.
384    pub const LEFT_Y: Self = Self::new(GamepadAxis::LeftStickY);
385
386    /// The left `Z` button. No processing is applied to raw data from the gamepad.
387    pub const LEFT_Z: Self = Self::new(GamepadAxis::LeftZ);
388
389    /// The horizontal axis (X-axis) of the right stick.
390    /// No processing is applied to raw data from the gamepad.
391    pub const RIGHT_X: Self = Self::new(GamepadAxis::RightStickX);
392
393    /// The vertical axis (Y-axis) of the right stick.
394    /// No processing is applied to raw data from the gamepad.
395    pub const RIGHT_Y: Self = Self::new(GamepadAxis::RightStickY);
396
397    /// The right `Z` button. No processing is applied to raw data from the gamepad.
398    pub const RIGHT_Z: Self = Self::new(GamepadAxis::RightZ);
399}
400
401impl UserInput for GamepadControlAxis {
402    /// [`GamepadControlAxis`] acts as an axis input.
403    #[inline]
404    fn kind(&self) -> InputControlKind {
405        InputControlKind::Axis
406    }
407
408    /// [`GamepadControlAxis`] represents a composition of two [`GamepadControlDirection`]s.
409    #[inline]
410    fn decompose(&self) -> BasicInputs {
411        BasicInputs::Composite(vec![
412            Box::new(GamepadControlDirection::negative(self.axis)),
413            Box::new(GamepadControlDirection::positive(self.axis)),
414        ])
415    }
416}
417
418#[serde_typetag]
419impl Axislike for GamepadControlAxis {
420    /// Retrieves the current value of this axis after processing by the associated processors.
421    #[inline]
422    fn value(&self, input_store: &CentralInputStore, gamepad: Entity) -> f32 {
423        let value = read_axis_value(input_store, gamepad, self.axis);
424        self.processors
425            .iter()
426            .fold(value, |value, processor| processor.process(value))
427    }
428
429    /// Sends a [`RawGamepadEvent::Axis`] event with the specified value on the provided gamepad.
430    fn set_value_as_gamepad(&self, world: &mut World, value: f32, gamepad: Option<Entity>) {
431        let mut query_state = SystemState::<Query<Entity, With<Gamepad>>>::new(world);
432        let query = query_state.get(world);
433        let gamepad = gamepad.unwrap_or(find_gamepad(Some(query)));
434
435        let event = RawGamepadEvent::Axis(RawGamepadAxisChangedEvent {
436            gamepad,
437            axis: self.axis,
438            value,
439        });
440        world.resource_mut::<Events<RawGamepadEvent>>().send(event);
441    }
442}
443
444impl WithAxisProcessingPipelineExt for GamepadControlAxis {
445    #[inline]
446    fn reset_processing_pipeline(mut self) -> Self {
447        self.processors.clear();
448        self
449    }
450
451    #[inline]
452    fn replace_processing_pipeline(
453        mut self,
454        processors: impl IntoIterator<Item = AxisProcessor>,
455    ) -> Self {
456        self.processors = processors.into_iter().collect();
457        self
458    }
459
460    #[inline]
461    fn with_processor(mut self, processor: impl Into<AxisProcessor>) -> Self {
462        self.processors.push(processor.into());
463        self
464    }
465}
466
467/// A gamepad stick (e.g., left stick and right stick).
468///
469/// By default, it reads from **any connected gamepad**.
470/// Use the [`InputMap::set_gamepad`](crate::input_map::InputMap::set_gamepad) for specific ones.
471///
472/// # Value Processing
473///
474/// You can customize how the values are processed using a pipeline of processors.
475/// See [`WithDualAxisProcessingPipelineExt`] for details.
476///
477/// ```rust,ignore
478/// use bevy::prelude::*;
479/// use bevy::input::InputPlugin;
480/// use leafwing_input_manager::prelude::*;
481///
482/// let mut app = App::new();
483/// app.add_plugins(InputPlugin);
484///
485/// // Left stick
486/// let input = GamepadStick::LEFT;
487///
488/// // Movement on either axis activates the input
489/// GamepadControlAxis::LEFT_Y.set_value(app.world_mut(), 1.0);
490/// app.update();
491/// assert_eq!(app.read_axis_values(input), [0.0, 1.0]);
492///
493/// // You can configure a processing pipeline (e.g., doubling the Y value)
494/// let doubled = GamepadStick::LEFT.sensitivity_y(2.0);
495/// assert_eq!(app.read_axis_values(doubled), [2.0]);
496/// ```
497#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
498#[must_use]
499pub struct GamepadStick {
500    /// Horizontal movement of the stick.
501    pub x: GamepadAxis,
502
503    /// Vertical movement of the stick.
504    pub y: GamepadAxis,
505
506    /// A processing pipeline that handles input values.
507    pub processors: Vec<DualAxisProcessor>,
508}
509
510impl GamepadStick {
511    /// The left gamepad stick. No processing is applied to raw data from the gamepad.
512    pub const LEFT: Self = Self {
513        x: GamepadAxis::LeftStickX,
514        y: GamepadAxis::LeftStickY,
515        processors: Vec::new(),
516    };
517
518    /// The right gamepad stick. No processing is applied to raw data from the gamepad.
519    pub const RIGHT: Self = Self {
520        x: GamepadAxis::RightStickX,
521        y: GamepadAxis::RightStickY,
522        processors: Vec::new(),
523    };
524}
525
526impl UserInput for GamepadStick {
527    /// [`GamepadStick`] acts as a dual-axis input.
528    #[inline]
529    fn kind(&self) -> InputControlKind {
530        InputControlKind::DualAxis
531    }
532
533    /// [`GamepadStick`] represents a composition of four [`GamepadControlDirection`]s.
534    #[inline]
535    fn decompose(&self) -> BasicInputs {
536        BasicInputs::Composite(vec![
537            Box::new(GamepadControlDirection::negative(self.x)),
538            Box::new(GamepadControlDirection::positive(self.x)),
539            Box::new(GamepadControlDirection::negative(self.y)),
540            Box::new(GamepadControlDirection::positive(self.y)),
541        ])
542    }
543}
544
545#[serde_typetag]
546impl DualAxislike for GamepadStick {
547    /// Retrieves the current X and Y values of this stick after processing by the associated processors.
548    #[inline]
549    fn axis_pair(&self, input_store: &CentralInputStore, gamepad: Entity) -> Vec2 {
550        let x = read_axis_value(input_store, gamepad, self.x);
551        let y = read_axis_value(input_store, gamepad, self.y);
552        self.processors
553            .iter()
554            .fold(Vec2::new(x, y), |value, processor| processor.process(value))
555    }
556
557    /// Sends a [`RawGamepadEvent::Axis`] event with the specified values on the provided gamepad [`Entity`].
558    fn set_axis_pair_as_gamepad(&self, world: &mut World, value: Vec2, gamepad: Option<Entity>) {
559        let mut query_state = SystemState::<Query<Entity, With<Gamepad>>>::new(world);
560        let query = query_state.get(world);
561        let gamepad = gamepad.unwrap_or(find_gamepad(Some(query)));
562
563        let event = RawGamepadEvent::Axis(RawGamepadAxisChangedEvent {
564            gamepad,
565            axis: self.x,
566            value: value.x,
567        });
568        world.resource_mut::<Events<RawGamepadEvent>>().send(event);
569
570        let event = RawGamepadEvent::Axis(RawGamepadAxisChangedEvent {
571            gamepad,
572            axis: self.y,
573            value: value.y,
574        });
575        world.resource_mut::<Events<RawGamepadEvent>>().send(event);
576    }
577}
578
579impl WithDualAxisProcessingPipelineExt for GamepadStick {
580    #[inline]
581    fn reset_processing_pipeline(mut self) -> Self {
582        self.processors.clear();
583        self
584    }
585
586    #[inline]
587    fn replace_processing_pipeline(
588        mut self,
589        processor: impl IntoIterator<Item = DualAxisProcessor>,
590    ) -> Self {
591        self.processors = processor.into_iter().collect();
592        self
593    }
594
595    #[inline]
596    fn with_processor(mut self, processor: impl Into<DualAxisProcessor>) -> Self {
597        self.processors.push(processor.into());
598        self
599    }
600}
601
602/// Checks if the given [`GamepadButton`] is currently pressed.
603#[must_use]
604#[inline]
605fn button_pressed(input_store: &CentralInputStore, gamepad: Entity, button: GamepadButton) -> bool {
606    let button = SpecificGamepadButton::new(gamepad, button);
607    input_store.pressed(&button)
608}
609
610/// Retrieves the current value of the given [`GamepadButton`].
611///
612/// This will be 0.0 if the button is released, and 1.0 if it is pressed.
613/// Physically triggerlike buttons will return a value between 0.0 and 1.0,
614/// depending on how far the button is pressed.
615#[must_use]
616#[inline]
617fn button_value(input_store: &CentralInputStore, gamepad: Entity, button: GamepadButton) -> f32 {
618    let button = SpecificGamepadButton::new(gamepad, button);
619    input_store.button_value(&button)
620}
621
622/// The [`SystemParam`] that combines the [`ButtonInput`] and [`Axis`] resources for [`GamepadButton`]s.
623#[derive(SystemParam)]
624pub struct GamepadButtonInput<'w> {
625    /// The [`ButtonInput`] for [`GamepadButton`]s.
626    pub buttons: Res<'w, ButtonInput<GamepadButton>>,
627
628    /// The [`Axis`] for [`GamepadButton`]s.
629    pub axes: Res<'w, Axis<GamepadButton>>,
630}
631
632impl UpdatableInput for GamepadButton {
633    type SourceData = SQuery<(Entity, Read<Gamepad>)>;
634
635    fn compute(
636        mut central_input_store: ResMut<CentralInputStore>,
637        source_data: StaticSystemParam<Self::SourceData>,
638    ) {
639        for (gamepad_entity, gamepad) in source_data.iter() {
640            for key in gamepad.get_pressed() {
641                let specific_button = SpecificGamepadButton {
642                    gamepad: gamepad_entity,
643                    button: *key,
644                };
645                let value = specific_button.value(&central_input_store, gamepad_entity);
646                central_input_store
647                    .update_buttonlike(specific_button, ButtonValue::new(true, value));
648            }
649
650            for key in gamepad.get_just_released() {
651                let specific_button = SpecificGamepadButton {
652                    gamepad: gamepad_entity,
653                    button: *key,
654                };
655                let value = specific_button.value(&central_input_store, gamepad_entity);
656                central_input_store
657                    .update_buttonlike(specific_button, ButtonValue::new(false, value));
658            }
659        }
660    }
661}
662
663/// Unlike [`GamepadButton`], this struct represents a specific button on a specific gamepad.
664///
665/// In the majority of cases, [`GamepadButton`] should be used instead.
666impl UserInput for SpecificGamepadButton {
667    fn kind(&self) -> InputControlKind {
668        InputControlKind::Button
669    }
670
671    fn decompose(&self) -> BasicInputs {
672        BasicInputs::Simple(Box::new(*self))
673    }
674}
675
676#[serde_typetag]
677impl Buttonlike for SpecificGamepadButton {
678    /// WARNING: The supplied gamepad is ignored, as the button is already specific to a gamepad.
679    fn pressed(&self, input_store: &CentralInputStore, _gamepad: Entity) -> bool {
680        button_pressed(input_store, self.gamepad, self.button)
681    }
682
683    /// WARNING: The supplied gamepad is ignored, as the button is already specific to a gamepad.
684    fn value(&self, input_store: &CentralInputStore, _gamepad: Entity) -> f32 {
685        button_value(input_store, self.gamepad, self.button)
686    }
687
688    fn press(&self, world: &mut World) {
689        self.set_value(world, 1.0);
690    }
691
692    fn release(&self, world: &mut World) {
693        self.set_value(world, 0.0);
694    }
695
696    fn set_value(&self, world: &mut World, value: f32) {
697        let event = RawGamepadEvent::Button(RawGamepadButtonChangedEvent {
698            gamepad: self.gamepad,
699            button: self.button,
700            value,
701        });
702        world.resource_mut::<Events<RawGamepadEvent>>().send(event);
703    }
704}
705
706// Built-in support for Bevy's GamepadButton.
707impl UserInput for GamepadButton {
708    /// [`GamepadButton`] acts as a button.
709    #[inline]
710    fn kind(&self) -> InputControlKind {
711        InputControlKind::Button
712    }
713
714    /// Creates a [`BasicInputs`] that only contains the [`GamepadButton`] itself,
715    /// as it represents a simple physical button.
716    #[inline]
717    fn decompose(&self) -> BasicInputs {
718        BasicInputs::Simple(Box::new(*self))
719    }
720}
721
722#[serde_typetag]
723impl Buttonlike for GamepadButton {
724    /// Checks if the specified button is currently pressed down.
725    #[inline]
726    fn pressed(&self, input_store: &CentralInputStore, gamepad: Entity) -> bool {
727        button_pressed(input_store, gamepad, *self)
728    }
729
730    /// Retrieves the current value of the specified button.
731    ///
732    /// This will be 0.0 if the button is released, and 1.0 if it is pressed.
733    /// Physically triggerlike buttons will return a value between 0.0 and 1.0,
734    /// depending on how far the button is pressed.
735    #[inline]
736    fn value(&self, input_store: &CentralInputStore, gamepad: Entity) -> f32 {
737        button_value(input_store, gamepad, *self)
738    }
739
740    /// Sends a [`RawGamepadEvent::Button`] event with a magnitude of 1.0 in the direction defined by `self` on the provided gamepad [`Entity`].
741    fn press_as_gamepad(&self, world: &mut World, gamepad: Option<Entity>) {
742        self.set_value_as_gamepad(world, 1.0, gamepad);
743    }
744
745    /// Sends a [`RawGamepadEvent::Button`] event with a magnitude of 0.0 in the direction defined by `self` on the provided gamepad [`Entity`].
746    fn release_as_gamepad(&self, world: &mut World, gamepad: Option<Entity>) {
747        self.set_value_as_gamepad(world, 0.0, gamepad);
748    }
749
750    /// Sends a [`RawGamepadEvent::Button`] event with the specified value in the direction defined by `self` on the provided gamepad [`Entity`].
751    #[inline]
752    fn set_value_as_gamepad(&self, world: &mut World, value: f32, gamepad: Option<Entity>) {
753        let mut query_state = SystemState::<Query<Entity, With<Gamepad>>>::new(world);
754        let query = query_state.get(world);
755        let gamepad = gamepad.unwrap_or(find_gamepad(Some(query)));
756
757        let event = RawGamepadEvent::Button(RawGamepadButtonChangedEvent {
758            gamepad,
759            button: *self,
760            value,
761        });
762        world.resource_mut::<Events<RawGamepadEvent>>().send(event);
763    }
764}
765
766#[cfg(test)]
767mod tests {
768    use super::*;
769    use crate::plugin::CentralInputStorePlugin;
770    use bevy::input::gamepad::{GamepadConnection, GamepadConnectionEvent};
771    use bevy::input::InputPlugin;
772    use bevy::prelude::*;
773
774    fn test_app() -> App {
775        let mut app = App::new();
776        app.add_plugins(MinimalPlugins);
777        app.add_plugins((InputPlugin, CentralInputStorePlugin));
778
779        // WARNING: you MUST register your gamepad during tests,
780        // or all gamepad input mocking actions will fail
781        let gamepad = app.world_mut().spawn(()).id();
782        let mut gamepad_connection_events = app
783            .world_mut()
784            .resource_mut::<Events<GamepadConnectionEvent>>();
785        gamepad_connection_events.send(GamepadConnectionEvent {
786            // This MUST be consistent with any other mocked events
787            gamepad,
788            connection: GamepadConnection::Connected {
789                name: "TestController".into(),
790                vendor_id: None,
791                product_id: None,
792            },
793        });
794
795        // Ensure that the gamepad is picked up by the appropriate system
796        app.update();
797        // Ensure that the connection event is flushed through
798        app.update();
799
800        app
801    }
802
803    #[test]
804    fn test_gamepad_axes() {
805        let left_up = GamepadControlDirection::LEFT_UP;
806        assert_eq!(left_up.kind(), InputControlKind::Button);
807
808        // The opposite of left up
809        let left_down = GamepadControlDirection::LEFT_DOWN;
810        assert_eq!(left_down.kind(), InputControlKind::Button);
811
812        let left_x = GamepadControlAxis::LEFT_X;
813        assert_eq!(left_x.kind(), InputControlKind::Axis);
814
815        let left_y = GamepadControlAxis::LEFT_Y;
816        assert_eq!(left_y.kind(), InputControlKind::Axis);
817
818        let left = GamepadStick::LEFT;
819        assert_eq!(left.kind(), InputControlKind::DualAxis);
820
821        // Up; but for the other stick
822        let right_up = GamepadControlDirection::RIGHT_DOWN;
823        assert_eq!(right_up.kind(), InputControlKind::Button);
824
825        let right_y = GamepadControlAxis::RIGHT_Y;
826        assert_eq!(right_y.kind(), InputControlKind::Axis);
827
828        let right = GamepadStick::RIGHT;
829        assert_eq!(right.kind(), InputControlKind::DualAxis);
830
831        // No inputs
832        let mut app = test_app();
833        app.update();
834        let gamepad = app
835            .world_mut()
836            .query_filtered::<Entity, With<Gamepad>>()
837            .iter(app.world())
838            .next()
839            .unwrap();
840        let inputs = app.world().resource::<CentralInputStore>();
841
842        assert!(!left_up.pressed(inputs, gamepad));
843        assert!(!left_down.pressed(inputs, gamepad));
844        assert!(!right_up.pressed(inputs, gamepad));
845        assert_eq!(left_x.value(inputs, gamepad), 0.0);
846        assert_eq!(left_y.value(inputs, gamepad), 0.0);
847        assert_eq!(right_y.value(inputs, gamepad), 0.0);
848        assert_eq!(left.axis_pair(inputs, gamepad), Vec2::ZERO);
849        assert_eq!(right.axis_pair(inputs, gamepad), Vec2::ZERO);
850
851        // Left stick moves upward
852        let data = Vec2::new(0.0, 1.0);
853        let mut app = test_app();
854        let gamepad = app
855            .world_mut()
856            .query_filtered::<Entity, With<Gamepad>>()
857            .iter(app.world())
858            .next()
859            .unwrap();
860        GamepadControlDirection::LEFT_UP.press_as_gamepad(app.world_mut(), Some(gamepad));
861        app.update();
862        let inputs = app.world().resource::<CentralInputStore>();
863
864        assert!(left_up.pressed(inputs, gamepad));
865        assert!(!left_down.pressed(inputs, gamepad));
866        assert!(!right_up.pressed(inputs, gamepad));
867        assert_eq!(left_x.value(inputs, gamepad), 0.0);
868        assert_eq!(left_y.value(inputs, gamepad), 1.0);
869        assert_eq!(right_y.value(inputs, gamepad), 0.0);
870        assert_eq!(left.axis_pair(inputs, gamepad), data);
871        assert_eq!(right.axis_pair(inputs, gamepad), Vec2::ZERO);
872
873        // Set Y-axis of left stick to 0.6
874        let data = Vec2::new(0.0, 0.6);
875        let mut app = test_app();
876        let gamepad = app
877            .world_mut()
878            .query_filtered::<Entity, With<Gamepad>>()
879            .iter(app.world())
880            .next()
881            .unwrap();
882        GamepadControlAxis::LEFT_Y.set_value_as_gamepad(app.world_mut(), data.y, Some(gamepad));
883        app.update();
884        let inputs = app.world().resource::<CentralInputStore>();
885
886        assert!(left_up.pressed(inputs, gamepad));
887        assert!(!left_down.pressed(inputs, gamepad));
888        assert!(!right_up.pressed(inputs, gamepad));
889        assert_eq!(left_x.value(inputs, gamepad), 0.0);
890        assert_eq!(left_y.value(inputs, gamepad), 0.6);
891        assert_eq!(right_y.value(inputs, gamepad), 0.0);
892        assert_eq!(left.axis_pair(inputs, gamepad), data);
893        assert_eq!(right.axis_pair(inputs, gamepad), Vec2::ZERO);
894
895        // Set left stick to (0.6, 0.4)
896        let data = Vec2::new(0.6, 0.4);
897        let mut app = test_app();
898        let gamepad = app
899            .world_mut()
900            .query_filtered::<Entity, With<Gamepad>>()
901            .iter(app.world())
902            .next()
903            .unwrap();
904        GamepadStick::LEFT.set_axis_pair_as_gamepad(app.world_mut(), data, Some(gamepad));
905        app.update();
906        let inputs = app.world().resource::<CentralInputStore>();
907
908        assert!(left_up.pressed(inputs, gamepad));
909        assert!(!left_down.pressed(inputs, gamepad));
910        assert!(!right_up.pressed(inputs, gamepad));
911        assert_eq!(left_x.value(inputs, gamepad), data.x);
912        assert_eq!(left_y.value(inputs, gamepad), data.y);
913        assert_eq!(right_y.value(inputs, gamepad), 0.0);
914        assert_eq!(left.axis_pair(inputs, gamepad), data);
915        assert_eq!(right.axis_pair(inputs, gamepad), Vec2::ZERO);
916    }
917
918    #[test]
919    #[ignore = "Input mocking is subtly broken: https://github.com/Leafwing-Studios/leafwing-input-manager/issues/516"]
920    fn test_gamepad_buttons() {
921        let up = GamepadButton::DPadUp;
922        assert_eq!(up.kind(), InputControlKind::Button);
923
924        let left = GamepadButton::DPadLeft;
925        assert_eq!(left.kind(), InputControlKind::Button);
926
927        let down = GamepadButton::DPadDown;
928        assert_eq!(left.kind(), InputControlKind::Button);
929
930        let right = GamepadButton::DPadRight;
931        assert_eq!(left.kind(), InputControlKind::Button);
932
933        // No inputs
934        let mut app = test_app();
935        app.update();
936        let gamepad = app.world_mut().spawn(()).id();
937        let inputs = app.world().resource::<CentralInputStore>();
938
939        assert!(!up.pressed(inputs, gamepad));
940        assert!(!left.pressed(inputs, gamepad));
941        assert!(!down.pressed(inputs, gamepad));
942        assert!(!right.pressed(inputs, gamepad));
943
944        // Press DPadLeft
945        let mut app = test_app();
946        GamepadButton::DPadLeft.press(app.world_mut());
947        app.update();
948        let inputs = app.world().resource::<CentralInputStore>();
949
950        assert!(!up.pressed(inputs, gamepad));
951        assert!(left.pressed(inputs, gamepad));
952        assert!(!down.pressed(inputs, gamepad));
953        assert!(!right.pressed(inputs, gamepad));
954    }
955}