crankit_input/
button.rs

1/// State of the playdate buttons
2#[non_exhaustive]
3#[derive(Debug, Copy, Clone, Eq, PartialEq)]
4pub struct State {
5    /// Buttons currently being pressed
6    pub current: Set,
7    /// Buttons that have just started to be pressed
8    ///
9    /// Meaning they were not pressed last frame, and are now currently pressed
10    pub pushed: Set,
11    /// Buttons that have just been released
12    ///
13    /// Meaning they were pressed last frame, and are no longer pressed
14    pub released: Set,
15}
16
17impl State {
18    /// Returns true if the given button is currently pressed
19    #[inline]
20    #[must_use]
21    pub fn is_pressed(self, button: Button) -> bool {
22        self.current.contains(button)
23    }
24
25    /// Returns true if the given button is has just started to be pressed
26    ///
27    /// Meaning it was not pressed last frame, and is now currently pressed
28    #[inline]
29    #[must_use]
30    pub fn is_just_pressed(self, button: Button) -> bool {
31        self.pushed.contains(button)
32    }
33
34    /// Returns true if the given button is has just started to be pressed
35    ///
36    /// Meaning it was pressed last frame, and is no longer pressed
37    #[inline]
38    #[must_use]
39    pub fn is_just_released(self, button: Button) -> bool {
40        self.released.contains(button)
41    }
42
43    /// Returns true if any of the given button is currently pressed
44    #[inline]
45    #[must_use]
46    pub fn is_any_pressed(&self, buttons: Set) -> bool {
47        self.current.contains_any(buttons)
48    }
49
50    /// Returns true if any of the given button was just pressed
51    #[inline]
52    #[must_use]
53    pub fn is_any_just_pressed(&self, buttons: Set) -> bool {
54        self.pushed.contains_any(buttons)
55    }
56
57    /// Returns true if any of the given button was just released
58    #[inline]
59    #[must_use]
60    pub fn is_any_just_released(&self, buttons: Set) -> bool {
61        self.released.contains_any(buttons)
62    }
63
64    /// Returns the currently pressed state of the d-pad as a 2d vector
65    ///
66    /// See [`Self::d_pad`] for more details
67    #[inline]
68    #[must_use]
69    pub fn d_pad<T: From<i8>>(self) -> [T; 2] {
70        self.current.d_pad()
71    }
72
73    /// Returns the buttons of the d-pad that have just started to be pressed as a 2d vector
74    ///
75    /// See [`Self::d_pad`] for more details
76    #[inline]
77    #[must_use]
78    pub fn d_pad_just_pressed<T: From<i8>>(self) -> [T; 2] {
79        self.pushed.d_pad()
80    }
81
82    /// Returns the buttons of the d-pad that have just been released as a 2d vector
83    ///
84    /// See [`Self::d_pad`] for more details
85    #[inline]
86    #[must_use]
87    pub fn d_pad_just_released<T: From<i8>>(self) -> [T; 2] {
88        self.released.d_pad()
89    }
90}
91
92/// Set of [`Button`]
93#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
94pub struct Set(pub(crate) u8);
95
96impl Set {
97    /// The set of 4 D-Pad buttons (up, down, left, right)
98    #[allow(clippy::cast_possible_truncation)]
99    pub const D_PAD: Self =
100        Self(Button::Left as u8 | Button::Right as u8 | Button::Up as u8 | Button::Down as u8);
101
102    #[inline]
103    #[must_use]
104    #[allow(missing_docs)]
105    pub fn new() -> Self {
106        Self::default()
107    }
108
109    #[allow(missing_docs)]
110    pub fn insert(&mut self, button: Button) {
111        self.0 |= Set::from(button).0;
112    }
113
114    /// Returns true if the set contains the button
115    #[inline]
116    #[must_use]
117    pub fn contains(self, button: Button) -> bool {
118        self.contains_any(button)
119    }
120
121    /// Returns true if the set contains any of the buttons in the other set.
122    ///
123    /// Returns `false` if the other set is empty.
124    #[inline]
125    #[must_use]
126    pub fn contains_any(self, buttons: impl Into<Self>) -> bool {
127        (self.0 & buttons.into().0) > 0
128    }
129
130    /// Returns the d-pad buttons contained in this set as a 2d vector
131    ///
132    /// The axes correspond to the playdate screen coordinate system (`x` is right, and `y` is down):
133    /// * Left is [-1, 0]
134    /// * Right is [1, 0]
135    /// * Down is [0, 1]
136    /// * Up is [0, -1]
137    ///
138    /// If more than one D-Pad button is contained in the set, this method returns the sum of the vectors.
139    #[must_use]
140    pub fn d_pad<T: From<i8>>(self) -> [T; 2] {
141        let mut x = 0;
142        let mut y = 0;
143        if self.contains(Button::Up) {
144            y -= 1;
145        }
146        if self.contains(Button::Down) {
147            y += 1;
148        }
149        if self.contains(Button::Left) {
150            x -= 1;
151        }
152        if self.contains(Button::Right) {
153            x += 1;
154        }
155        [x.into(), y.into()]
156    }
157}
158
159impl Extend<Button> for Set {
160    fn extend<T: IntoIterator<Item = Button>>(&mut self, iter: T) {
161        iter.into_iter().for_each(|b| self.insert(b));
162    }
163}
164
165impl FromIterator<Button> for Set {
166    fn from_iter<T: IntoIterator<Item = Button>>(iter: T) -> Self {
167        let mut result = Self::default();
168        result.extend(iter);
169        result
170    }
171}
172
173impl From<&[Button]> for Set {
174    fn from(value: &[Button]) -> Self {
175        value.iter().copied().collect()
176    }
177}
178
179impl<const N: usize> From<[Button; N]> for Set {
180    fn from(value: [Button; N]) -> Self {
181        value.into_iter().collect()
182    }
183}
184
185impl From<Button> for Set {
186    fn from(value: Button) -> Self {
187        Self(value as u8)
188    }
189}
190
191#[cfg(test)]
192mod tests {
193    use rstest::rstest;
194
195    use super::*;
196
197    #[rstest]
198    #[case(Button::A, Button::A, true)]
199    #[case(Button::A, Button::B, false)]
200    #[case(Button::B, Button::A, false)]
201    #[case(Button::B, Button::B, true)]
202    #[case([Button::A, Button::B], Button::B, true)]
203    #[case([Button::A, Button::B], Button::A, true)]
204    #[case([Button::A, Button::B], Button::Up, false)]
205    #[case([Button::A, Button::B, Button::Up], Button::Up, true)]
206    fn test_set_contains(
207        #[case] set: impl Into<Set>,
208        #[case] button: Button,
209        #[case] expected: bool,
210    ) {
211        let set = set.into();
212        assert_eq!(set.contains(button), expected);
213        assert_eq!(set.contains_any(button), expected);
214    }
215
216    #[rstest]
217    #[case(Set::default(), Set::from_iter([Button::A]), false)]
218    #[case(Set::default(), Set::from_iter([Button::A, Button::B]), false)]
219    #[case(Set::default(), Set::default(), false)]
220    #[case(Set::from_iter([Button::A]), Set::default(), false)]
221    #[case(Set::from_iter([Button::A]), Set::from_iter([Button::A]), true)]
222    #[case(Set::from_iter([Button::A, Button::B]), Set::from_iter([Button::A]), true)]
223    #[case(Set::from_iter([Button::A, Button::B]), Set::from_iter([Button::A, Button::B]), true)]
224    #[case(Set::from_iter([Button::A]), Set::from_iter([Button::A, Button::B]), true)]
225    fn test_set_contains_any(#[case] set: Set, #[case] buttons: Set, #[case] expected: bool) {
226        assert_eq!(set.contains_any(buttons), expected);
227    }
228
229    #[rstest]
230    #[case(Set::default(), [0, 0])]
231    #[case([Button::Up], [0, -1])]
232    #[case([Button::Down], [0, 1])]
233    #[case([Button::Left], [-1, 0])]
234    #[case([Button::Right], [1, 0])]
235    #[case([Button::Right, Button::Down, Button::Up], [1, 0])]
236    #[case([Button::Left, Button::Right, Button::Up], [0, -1])]
237    #[case([Button::Left, Button::Right, Button::Up, Button::Down], [0, 0])]
238    fn d_pad_vector(#[case] set: impl Into<Set>, #[case] expected: [i8; 2]) {
239        let set = set.into();
240        assert_eq!(set.d_pad::<i8>(), expected);
241        assert_eq!(set.d_pad::<i32>(), [expected[0].into(), expected[1].into()]);
242        let _: [f32; 2] = set.d_pad::<f32>();
243    }
244}
245
246/// A button on the playdate
247#[repr(u8)]
248#[allow(clippy::exhaustive_enums, missing_docs)]
249#[derive(Debug, Copy, Clone, Eq, PartialEq)]
250pub enum Button {
251    Left = 1,
252    Right = 2,
253    Up = 4,
254    Down = 8,
255    B = 16,
256    A = 32,
257}