bevy_input/
axis.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//! The generic axis type.

use bevy_ecs::system::Resource;
use bevy_utils::HashMap;
use core::hash::Hash;

#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;

/// Stores the position data of the input devices of type `T`.
///
/// The values are stored as `f32`s, using [`Axis::set`].
/// Use [`Axis::get`] to retrieve the value clamped between [`Axis::MIN`] and [`Axis::MAX`]
/// inclusive, or unclamped using [`Axis::get_unclamped`].
#[derive(Debug, Resource)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct Axis<T> {
    /// The position data of the input devices.
    axis_data: HashMap<T, f32>,
}

impl<T> Default for Axis<T>
where
    T: Copy + Eq + Hash,
{
    fn default() -> Self {
        Axis {
            axis_data: HashMap::default(),
        }
    }
}

impl<T> Axis<T>
where
    T: Copy + Eq + Hash,
{
    /// The smallest possible axis value.
    pub const MIN: f32 = -1.0;

    /// The largest possible axis value.
    pub const MAX: f32 = 1.0;

    /// Sets the position data of the `input_device` to `position_data`.
    ///
    /// If the `input_device`:
    /// - was present before, the position data is updated, and the old value is returned.
    /// - wasn't present before, `None` is returned.
    pub fn set(&mut self, input_device: impl Into<T>, position_data: f32) -> Option<f32> {
        self.axis_data.insert(input_device.into(), position_data)
    }

    /// Returns the position data of the provided `input_device`.
    ///
    /// This will be clamped between [`Axis::MIN`] and [`Axis::MAX`] inclusive.
    pub fn get(&self, input_device: impl Into<T>) -> Option<f32> {
        self.axis_data
            .get(&input_device.into())
            .copied()
            .map(|value| value.clamp(Self::MIN, Self::MAX))
    }

    /// Returns the unclamped position data of the provided `input_device`.
    ///
    /// This value may be outside the [`Axis::MIN`] and [`Axis::MAX`] range.
    ///
    /// Use for things like camera zoom, where you want devices like mouse wheels to be able to
    /// exceed the normal range. If being able to move faster on one input device
    /// than another would give an unfair advantage, you should likely use [`Axis::get`] instead.
    pub fn get_unclamped(&self, input_device: impl Into<T>) -> Option<f32> {
        self.axis_data.get(&input_device.into()).copied()
    }

    /// Removes the position data of the `input_device`, returning the position data if the input device was previously set.
    pub fn remove(&mut self, input_device: impl Into<T>) -> Option<f32> {
        self.axis_data.remove(&input_device.into())
    }

    /// Returns an iterator over all axes.
    pub fn all_axes(&self) -> impl Iterator<Item = &T> {
        self.axis_data.keys()
    }

    /// Returns an iterator over all axes and their values.
    pub fn all_axes_and_values(&self) -> impl Iterator<Item = (&T, f32)> {
        self.axis_data.iter().map(|(axis, value)| (axis, *value))
    }
}

#[cfg(test)]
mod tests {
    use crate::{gamepad::GamepadButton, Axis};

    #[test]
    fn test_axis_set() {
        let cases = [
            (-1.5, Some(-1.0)),
            (-1.1, Some(-1.0)),
            (-1.0, Some(-1.0)),
            (-0.9, Some(-0.9)),
            (-0.1, Some(-0.1)),
            (0.0, Some(0.0)),
            (0.1, Some(0.1)),
            (0.9, Some(0.9)),
            (1.0, Some(1.0)),
            (1.1, Some(1.0)),
            (1.6, Some(1.0)),
        ];

        for (value, expected) in cases {
            let mut axis = Axis::<GamepadButton>::default();

            axis.set(GamepadButton::RightTrigger, value);

            let actual = axis.get(GamepadButton::RightTrigger);
            assert_eq!(expected, actual);
        }
    }

    #[test]
    fn test_axis_remove() {
        let cases = [-1.0, -0.9, -0.1, 0.0, 0.1, 0.9, 1.0];

        for value in cases {
            let mut axis = Axis::<GamepadButton>::default();

            axis.set(GamepadButton::RightTrigger, value);
            assert!(axis.get(GamepadButton::RightTrigger).is_some());

            axis.remove(GamepadButton::RightTrigger);
            let actual = axis.get(GamepadButton::RightTrigger);
            let expected = None;

            assert_eq!(expected, actual);
        }
    }
}