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