firefly_rust/
input.rs

1//! Read inputs: touch pad, buttons, accelerometer.
2
3use crate::*;
4
5const DPAD4_THRESHOLD: i32 = 300;
6const DPAD8_THRESHOLD: i32 = 300;
7
8/// A finger position on the touch pad.
9///
10/// Both x and y are somewhere the range between -1000 and 1000 (both ends included).
11/// The 1000 x is on the right, the 1000 y is on the top.
12///
13/// Note that the y coordinate is flipped compared to the screen coordinates.
14/// While the display coordinates follow the text reading direction,
15/// the touchpad coordinates follow the directions used in geometry.
16#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
17pub struct Pad {
18    pub x: i32,
19    pub y: i32,
20}
21
22impl Pad {
23    /// The top-right corner of the pad's bounding box.
24    pub const MAX: Pad = Pad { x: 1000, y: 1000 };
25    /// The bottom-left corner of the pad's bounding box.
26    pub const MIN: Pad = Pad { x: -1000, y: -1000 };
27
28    /// Represent the pad values as an 4-directional pad.
29    ///
30    /// Use [`Pad::as_dpad8`] instead if you want to also allow diagonal movement.
31    #[must_use]
32    pub fn as_dpad4(&self) -> DPad4 {
33        let x = self.x;
34        let y = self.y;
35        if y > DPAD4_THRESHOLD && y > x.abs() {
36            DPad4::Up
37        } else if y < -DPAD4_THRESHOLD && -y > x.abs() {
38            DPad4::Down
39        } else if x > DPAD4_THRESHOLD && x > y.abs() {
40            DPad4::Right
41        } else if x < -DPAD4_THRESHOLD && -x > y.abs() {
42            DPad4::Left
43        } else {
44            DPad4::None
45        }
46    }
47
48    /// Represent the pad values as an 8-directional pad.
49    ///
50    /// Use [`Pad::as_dpad4`] instead if you don't want to allow diagonal movement.
51    #[must_use]
52    pub fn as_dpad8(&self) -> DPad8 {
53        DPad8 {
54            left: self.x <= -DPAD8_THRESHOLD,
55            right: self.x >= DPAD8_THRESHOLD,
56            down: self.y <= -DPAD8_THRESHOLD,
57            up: self.y >= DPAD8_THRESHOLD,
58        }
59    }
60
61    /// The distance from the pad center to the touch point.
62    #[must_use]
63    pub fn radius(self) -> f32 {
64        let r = self.x * self.x + self.y * self.y;
65        #[expect(clippy::cast_precision_loss)]
66        math::sqrt(r as f32)
67    }
68
69    /// The angle of the [polar coordinate] of the touch point.
70    ///
71    /// [polar coordinate]: https://en.wikipedia.org/wiki/Polar_coordinate_system
72    #[must_use]
73    pub fn azimuth(self) -> Angle {
74        #[expect(clippy::cast_precision_loss)]
75        let r = math::atan2(self.y as f32, self.x as f32);
76        Angle::from_radians(r)
77    }
78}
79
80impl From<Pad> for Point {
81    fn from(value: Pad) -> Self {
82        Self {
83            x: value.x,
84            y: value.y,
85        }
86    }
87}
88
89impl From<Point> for Pad {
90    fn from(value: Point) -> Self {
91        Self {
92            x: value.x,
93            y: value.y,
94        }
95    }
96}
97
98impl From<Pad> for Size {
99    fn from(value: Pad) -> Self {
100        Self {
101            width: value.x,
102            height: value.y,
103        }
104    }
105}
106
107impl From<Size> for Pad {
108    fn from(value: Size) -> Self {
109        Self {
110            x: value.width,
111            y: value.height,
112        }
113    }
114}
115
116/// 8-directional DPad-like representation of the [`Pad`].
117///
118/// Constructed with [`Pad::as_dpad8`]. Useful for simple games and ports.
119/// The middle of the pad is a "dead zone" pressing which will not activate any direction.
120///
121/// Implements all the same methods as [`DPad4`].
122///
123/// Invariant: it's not possible for opposite directions (left and right, or down and up)
124/// to be active at the same time. However, it's possible for heighboring directions
125/// (like up and right) to be active at the same time if the player presses a diagonal.
126///
127/// For completeness, here is the full list of possible states:
128///
129/// * right
130/// * right and up
131/// * up
132/// * left and up
133/// * left
134/// * left and down
135/// * down
136/// * right and down
137/// * none
138#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
139pub struct DPad8 {
140    pub left: bool,
141    pub right: bool,
142    pub up: bool,
143    pub down: bool,
144}
145
146impl DPad8 {
147    /// Check if any direction is pressed.
148    #[must_use]
149    pub fn any(&self) -> bool {
150        self.left || self.right || self.up || self.down
151    }
152
153    /// Given the old state, get directions that were not pressed but are pressed now.
154    #[must_use]
155    pub fn just_pressed(&self, old: &Self) -> Self {
156        Self {
157            left: self.left && !old.left,
158            right: self.right && !old.right,
159            up: self.up && !old.up,
160            down: self.down && !old.down,
161        }
162    }
163
164    /// Given the old state, get directions that were pressed but aren't pressed now.
165    #[must_use]
166    pub fn just_released(&self, old: &Self) -> Self {
167        Self {
168            left: !self.left && old.left,
169            right: !self.right && old.right,
170            up: !self.up && old.up,
171            down: !self.down && old.down,
172        }
173    }
174
175    /// Given the old state, get directions that were pressed and are still pressed now.
176    #[must_use]
177    pub fn held(&self, old: &Self) -> Self {
178        Self {
179            left: self.left && old.left,
180            right: self.right && old.right,
181            up: self.up && old.up,
182            down: self.down && old.down,
183        }
184    }
185}
186
187impl From<Pad> for DPad8 {
188    fn from(value: Pad) -> Self {
189        value.as_dpad8()
190    }
191}
192
193impl From<Option<DPad8>> for DPad8 {
194    fn from(value: Option<DPad8>) -> Self {
195        value.unwrap_or_default()
196    }
197}
198
199impl From<DPad4> for DPad8 {
200    fn from(value: DPad4) -> Self {
201        let mut pad = Self::default();
202        match value {
203            DPad4::None => {}
204            DPad4::Left => pad.left = true,
205            DPad4::Right => pad.right = true,
206            DPad4::Up => pad.up = true,
207            DPad4::Down => pad.down = true,
208        }
209        pad
210    }
211}
212
213/// 4-directional DPad-like representation of the [`Pad`].
214///
215/// Constructed with [`Pad::as_dpad4`]. Useful for simple games and ports.
216/// The middle of the pad is a "dead zone" pressing which will not activate any direction.
217///
218/// Implements all the same methods as [`DPad8`].
219#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
220pub enum DPad4 {
221    #[default]
222    None,
223    Left,
224    Right,
225    Up,
226    Down,
227}
228
229impl DPad4 {
230    /// Check if any direction is pressed.
231    #[must_use]
232    pub fn any(self) -> bool {
233        self != Self::None
234    }
235
236    /// Given the old state, get directions that were not pressed but are pressed now.
237    #[must_use]
238    pub fn just_pressed(self, old: Self) -> Self {
239        if self == old { Self::None } else { self }
240    }
241
242    /// Given the old state, get directions that were pressed but aren't pressed now.
243    #[must_use]
244    pub fn just_released(self, old: Self) -> Self {
245        if self == old { Self::None } else { old }
246    }
247
248    /// Given the old state, get directions that were pressed and are still pressed now.
249    #[must_use]
250    pub fn held(self, old: Self) -> Self {
251        if self == old { self } else { Self::None }
252    }
253}
254
255impl From<Pad> for DPad4 {
256    fn from(value: Pad) -> Self {
257        value.as_dpad4()
258    }
259}
260
261impl From<Option<DPad4>> for DPad4 {
262    fn from(value: Option<DPad4>) -> Self {
263        value.unwrap_or_default()
264    }
265}
266
267/// State of the buttons.
268#[derive(Default)]
269pub struct Buttons {
270    /// South. The bottom button, like A on the X-Box controller.
271    ///
272    /// Typically used for confirmation, main action, jump, etc.
273    pub s: bool,
274
275    /// East. The right button, like B on the X-Box controller.
276    ///
277    /// Typically used for cancellation, going to previous screen, etc.
278    pub e: bool,
279
280    /// West. The left button, like X on the X-Box controller.
281    ///
282    /// Typically used for attack.
283    pub w: bool,
284
285    /// North. The top button, like Y on the X-Box controller.
286    ///
287    /// Typically used for a secondary action, like charged attack.
288    pub n: bool,
289
290    /// The menu button, almost always handled by the runtime.
291    pub menu: bool,
292}
293
294impl Buttons {
295    /// Check if any button is pressed.
296    #[must_use]
297    pub fn any(&self) -> bool {
298        self.s || self.e || self.w || self.n || self.menu
299    }
300
301    /// Given the old state, get buttons that were not pressed but are pressed now.
302    #[must_use]
303    pub fn just_pressed(&self, old: &Self) -> Self {
304        Self {
305            s: self.s && !old.s,
306            e: self.e && !old.e,
307            w: self.w && !old.w,
308            n: self.n && !old.n,
309            menu: self.menu && !old.menu,
310        }
311    }
312
313    /// Given the old state, get buttons that were pressed but aren't pressed now.
314    #[must_use]
315    pub fn just_released(&self, old: &Self) -> Self {
316        Self {
317            s: !self.s && old.s,
318            e: !self.e && old.e,
319            w: !self.w && old.w,
320            n: !self.n && old.n,
321            menu: !self.menu && old.menu,
322        }
323    }
324
325    /// Given the old state, get buttons that were pressed but are still pressed now.
326    #[must_use]
327    pub fn held(&self, old: &Self) -> Self {
328        Self {
329            s: self.s && old.s,
330            e: self.e && old.e,
331            w: self.w && old.w,
332            n: self.n && old.n,
333            menu: self.menu && old.menu,
334        }
335    }
336}
337
338/// Get the current touch pad state.
339#[must_use]
340pub fn read_pad(p: Peer) -> Option<Pad> {
341    let p = u32::from(p.0);
342    let raw = unsafe { bindings::read_pad(p) };
343    if raw == 0xffff {
344        None
345    } else {
346        Some(Pad {
347            x: i32::from((raw >> 16) as i16),
348            y: i32::from(raw as i16),
349        })
350    }
351}
352
353/// Get the currently pressed buttons.
354#[must_use]
355pub fn read_buttons(p: Peer) -> Buttons {
356    let p = u32::from(p.0);
357    let raw = unsafe { bindings::read_buttons(p) };
358    Buttons {
359        s: has_bit_set(raw, 0),
360        e: has_bit_set(raw, 1),
361        w: has_bit_set(raw, 2),
362        n: has_bit_set(raw, 3),
363        menu: has_bit_set(raw, 4),
364    }
365}
366
367/// Check if the given i32 value has the given bit set.
368#[inline]
369fn has_bit_set(val: u32, bit: usize) -> bool {
370    (val >> bit) & 0b1 != 0
371}
372
373mod bindings {
374    #[link(wasm_import_module = "input")]
375    unsafe extern "C" {
376        pub(crate) fn read_pad(peer: u32) -> u32;
377        pub(crate) fn read_buttons(peer: u32) -> u32;
378    }
379}