bevy_input/
axis.rs

1//! The generic axis type.
2
3use bevy_ecs::system::Resource;
4use bevy_utils::HashMap;
5use core::hash::Hash;
6
7#[cfg(feature = "bevy_reflect")]
8use bevy_reflect::Reflect;
9
10/// Stores the position data of the input devices of type `T`.
11///
12/// The values are stored as `f32`s, using [`Axis::set`].
13/// Use [`Axis::get`] to retrieve the value clamped between [`Axis::MIN`] and [`Axis::MAX`]
14/// inclusive, or unclamped using [`Axis::get_unclamped`].
15#[derive(Debug, Resource)]
16#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
17pub struct Axis<T> {
18    /// The position data of the input devices.
19    axis_data: HashMap<T, f32>,
20}
21
22impl<T> Default for Axis<T>
23where
24    T: Copy + Eq + Hash,
25{
26    fn default() -> Self {
27        Axis {
28            axis_data: HashMap::default(),
29        }
30    }
31}
32
33impl<T> Axis<T>
34where
35    T: Copy + Eq + Hash,
36{
37    /// The smallest possible axis value.
38    pub const MIN: f32 = -1.0;
39
40    /// The largest possible axis value.
41    pub const MAX: f32 = 1.0;
42
43    /// Sets the position data of the `input_device` to `position_data`.
44    ///
45    /// If the `input_device`:
46    /// - was present before, the position data is updated, and the old value is returned.
47    /// - wasn't present before, `None` is returned.
48    pub fn set(&mut self, input_device: impl Into<T>, position_data: f32) -> Option<f32> {
49        self.axis_data.insert(input_device.into(), position_data)
50    }
51
52    /// Returns the position data of the provided `input_device`.
53    ///
54    /// This will be clamped between [`Axis::MIN`] and [`Axis::MAX`] inclusive.
55    pub fn get(&self, input_device: impl Into<T>) -> Option<f32> {
56        self.axis_data
57            .get(&input_device.into())
58            .copied()
59            .map(|value| value.clamp(Self::MIN, Self::MAX))
60    }
61
62    /// Returns the unclamped position data of the provided `input_device`.
63    ///
64    /// This value may be outside the [`Axis::MIN`] and [`Axis::MAX`] range.
65    ///
66    /// Use for things like camera zoom, where you want devices like mouse wheels to be able to
67    /// exceed the normal range. If being able to move faster on one input device
68    /// than another would give an unfair advantage, you should likely use [`Axis::get`] instead.
69    pub fn get_unclamped(&self, input_device: impl Into<T>) -> Option<f32> {
70        self.axis_data.get(&input_device.into()).copied()
71    }
72
73    /// Removes the position data of the `input_device`, returning the position data if the input device was previously set.
74    pub fn remove(&mut self, input_device: impl Into<T>) -> Option<f32> {
75        self.axis_data.remove(&input_device.into())
76    }
77
78    /// Returns an iterator over all axes.
79    pub fn all_axes(&self) -> impl Iterator<Item = &T> {
80        self.axis_data.keys()
81    }
82
83    /// Returns an iterator over all axes and their values.
84    pub fn all_axes_and_values(&self) -> impl Iterator<Item = (&T, f32)> {
85        self.axis_data.iter().map(|(axis, value)| (axis, *value))
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use crate::{gamepad::GamepadButton, Axis};
92
93    #[test]
94    fn test_axis_set() {
95        let cases = [
96            (-1.5, Some(-1.0)),
97            (-1.1, Some(-1.0)),
98            (-1.0, Some(-1.0)),
99            (-0.9, Some(-0.9)),
100            (-0.1, Some(-0.1)),
101            (0.0, Some(0.0)),
102            (0.1, Some(0.1)),
103            (0.9, Some(0.9)),
104            (1.0, Some(1.0)),
105            (1.1, Some(1.0)),
106            (1.6, Some(1.0)),
107        ];
108
109        for (value, expected) in cases {
110            let mut axis = Axis::<GamepadButton>::default();
111
112            axis.set(GamepadButton::RightTrigger, value);
113
114            let actual = axis.get(GamepadButton::RightTrigger);
115            assert_eq!(expected, actual);
116        }
117    }
118
119    #[test]
120    fn test_axis_remove() {
121        let cases = [-1.0, -0.9, -0.1, 0.0, 0.1, 0.9, 1.0];
122
123        for value in cases {
124            let mut axis = Axis::<GamepadButton>::default();
125
126            axis.set(GamepadButton::RightTrigger, value);
127            assert!(axis.get(GamepadButton::RightTrigger).is_some());
128
129            axis.remove(GamepadButton::RightTrigger);
130            let actual = axis.get(GamepadButton::RightTrigger);
131            let expected = None;
132
133            assert_eq!(expected, actual);
134        }
135    }
136}