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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
/// State of the playdate buttons
#[non_exhaustive]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct State {
    /// Buttons currently being pressed
    pub current: Set,
    /// Buttons that have just started to be pressed
    ///
    /// Meaning they were not pressed last frame, and are now currently pressed
    pub pushed: Set,
    /// Buttons that have just been released
    ///
    /// Meaning they were pressed last frame, and are no longer pressed
    pub released: Set,
}

impl State {
    /// Returns true if the given button is currently pressed
    #[inline]
    #[must_use]
    pub fn is_pressed(self, button: Button) -> bool {
        self.current.contains(button)
    }

    /// Returns true if the given button is has just started to be pressed
    ///
    /// Meaning it was not pressed last frame, and is now currently pressed
    #[inline]
    #[must_use]
    pub fn is_just_pressed(self, button: Button) -> bool {
        self.pushed.contains(button)
    }

    /// Returns true if the given button is has just started to be pressed
    ///
    /// Meaning it was pressed last frame, and is no longer pressed
    #[inline]
    #[must_use]
    pub fn is_just_released(self, button: Button) -> bool {
        self.released.contains(button)
    }

    /// Returns true if any of the given button is currently pressed
    #[inline]
    #[must_use]
    pub fn is_any_pressed(&self, buttons: Set) -> bool {
        self.current.contains_any(buttons)
    }

    /// Returns true if any of the given button was just pressed
    #[inline]
    #[must_use]
    pub fn is_any_just_pressed(&self, buttons: Set) -> bool {
        self.pushed.contains_any(buttons)
    }

    /// Returns true if any of the given button was just released
    #[inline]
    #[must_use]
    pub fn is_any_just_released(&self, buttons: Set) -> bool {
        self.released.contains_any(buttons)
    }

    /// Returns the currently pressed state of the d-pad as a 2d vector
    ///
    /// See [`Self::d_pad`] for more details
    #[inline]
    #[must_use]
    pub fn d_pad<T: From<i8>>(self) -> [T; 2] {
        self.current.d_pad()
    }

    /// Returns the buttons of the d-pad that have just started to be pressed as a 2d vector
    ///
    /// See [`Self::d_pad`] for more details
    #[inline]
    #[must_use]
    pub fn d_pad_just_pressed<T: From<i8>>(self) -> [T; 2] {
        self.pushed.d_pad()
    }

    /// Returns the buttons of the d-pad that have just been released as a 2d vector
    ///
    /// See [`Self::d_pad`] for more details
    #[inline]
    #[must_use]
    pub fn d_pad_just_released<T: From<i8>>(self) -> [T; 2] {
        self.released.d_pad()
    }
}

/// Set of [`Button`]
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq)]
pub struct Set(pub(crate) u8);

impl Set {
    /// The set of 4 D-Pad buttons (up, down, left, right)
    #[allow(clippy::cast_possible_truncation)]
    pub const D_PAD: Self =
        Self(Button::Left as u8 | Button::Right as u8 | Button::Up as u8 | Button::Down as u8);

    #[inline]
    #[must_use]
    #[allow(missing_docs)]
    pub fn new() -> Self {
        Self::default()
    }

    #[allow(missing_docs)]
    pub fn insert(&mut self, button: Button) {
        self.0 |= Set::from(button).0;
    }

    /// Returns true if the set contains the button
    #[inline]
    #[must_use]
    pub fn contains(self, button: Button) -> bool {
        self.contains_any(button)
    }

    /// Returns true if the set contains any of the buttons in the other set.
    ///
    /// Returns `false` if the other set is empty.
    #[inline]
    #[must_use]
    pub fn contains_any(self, buttons: impl Into<Self>) -> bool {
        (self.0 & buttons.into().0) > 0
    }

    /// Returns the d-pad buttons contained in this set as a 2d vector
    ///
    /// The axes correspond to the playdate screen coordinate system (`x` is right, and `y` is down):
    /// * Left is [-1, 0]
    /// * Right is [1, 0]
    /// * Down is [0, 1]
    /// * Up is [0, -1]
    ///
    /// If more than one D-Pad button is contained in the set, this method returns the sum of the vectors.
    #[must_use]
    pub fn d_pad<T: From<i8>>(self) -> [T; 2] {
        let mut x = 0;
        let mut y = 0;
        if self.contains(Button::Up) {
            y -= 1;
        }
        if self.contains(Button::Down) {
            y += 1;
        }
        if self.contains(Button::Left) {
            x -= 1;
        }
        if self.contains(Button::Right) {
            x += 1;
        }
        [x.into(), y.into()]
    }
}

impl Extend<Button> for Set {
    fn extend<T: IntoIterator<Item = Button>>(&mut self, iter: T) {
        iter.into_iter().for_each(|b| self.insert(b));
    }
}

impl FromIterator<Button> for Set {
    fn from_iter<T: IntoIterator<Item = Button>>(iter: T) -> Self {
        let mut result = Self::default();
        result.extend(iter);
        result
    }
}

impl From<&[Button]> for Set {
    fn from(value: &[Button]) -> Self {
        value.iter().copied().collect()
    }
}

impl<const N: usize> From<[Button; N]> for Set {
    fn from(value: [Button; N]) -> Self {
        value.into_iter().collect()
    }
}

impl From<Button> for Set {
    fn from(value: Button) -> Self {
        Self(value as u8)
    }
}

#[cfg(test)]
mod tests {
    use rstest::rstest;

    use super::*;

    #[rstest]
    #[case(Button::A, Button::A, true)]
    #[case(Button::A, Button::B, false)]
    #[case(Button::B, Button::A, false)]
    #[case(Button::B, Button::B, true)]
    #[case([Button::A, Button::B], Button::B, true)]
    #[case([Button::A, Button::B], Button::A, true)]
    #[case([Button::A, Button::B], Button::Up, false)]
    #[case([Button::A, Button::B, Button::Up], Button::Up, true)]
    fn test_set_contains(
        #[case] set: impl Into<Set>,
        #[case] button: Button,
        #[case] expected: bool,
    ) {
        let set = set.into();
        assert_eq!(set.contains(button), expected);
        assert_eq!(set.contains_any(button), expected);
    }

    #[rstest]
    #[case(Set::default(), Set::from_iter([Button::A]), false)]
    #[case(Set::default(), Set::from_iter([Button::A, Button::B]), false)]
    #[case(Set::default(), Set::default(), false)]
    #[case(Set::from_iter([Button::A]), Set::default(), false)]
    #[case(Set::from_iter([Button::A]), Set::from_iter([Button::A]), true)]
    #[case(Set::from_iter([Button::A, Button::B]), Set::from_iter([Button::A]), true)]
    #[case(Set::from_iter([Button::A, Button::B]), Set::from_iter([Button::A, Button::B]), true)]
    #[case(Set::from_iter([Button::A]), Set::from_iter([Button::A, Button::B]), true)]
    fn test_set_contains_any(#[case] set: Set, #[case] buttons: Set, #[case] expected: bool) {
        assert_eq!(set.contains_any(buttons), expected);
    }

    #[rstest]
    #[case(Set::default(), [0, 0])]
    #[case([Button::Up], [0, -1])]
    #[case([Button::Down], [0, 1])]
    #[case([Button::Left], [-1, 0])]
    #[case([Button::Right], [1, 0])]
    #[case([Button::Right, Button::Down, Button::Up], [1, 0])]
    #[case([Button::Left, Button::Right, Button::Up], [0, -1])]
    #[case([Button::Left, Button::Right, Button::Up, Button::Down], [0, 0])]
    fn d_pad_vector(#[case] set: impl Into<Set>, #[case] expected: [i8; 2]) {
        let set = set.into();
        assert_eq!(set.d_pad::<i8>(), expected);
        assert_eq!(set.d_pad::<i32>(), [expected[0].into(), expected[1].into()]);
        let _: [f32; 2] = set.d_pad::<f32>();
    }
}

/// A button on the playdate
#[repr(u8)]
#[allow(clippy::exhaustive_enums, missing_docs)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Button {
    Left = 1,
    Right = 2,
    Up = 4,
    Down = 8,
    B = 16,
    A = 32,
}