Skip to main content

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        if self.x == 0 && self.y == 0 {
75            return Angle::ZERO;
76        }
77        #[expect(clippy::cast_precision_loss)]
78        let r = math::atan2(self.y as f32, self.x as f32);
79        Angle::from_radians(r)
80    }
81}
82
83impl From<Pad> for Point {
84    fn from(value: Pad) -> Self {
85        Self {
86            x: value.x,
87            y: value.y,
88        }
89    }
90}
91
92impl From<Point> for Pad {
93    fn from(value: Point) -> Self {
94        Self {
95            x: value.x,
96            y: value.y,
97        }
98    }
99}
100
101impl From<Pad> for Size {
102    fn from(value: Pad) -> Self {
103        Self {
104            width: value.x,
105            height: value.y,
106        }
107    }
108}
109
110impl From<Size> for Pad {
111    fn from(value: Size) -> Self {
112        Self {
113            x: value.width,
114            y: value.height,
115        }
116    }
117}
118
119/// 8-directional DPad-like representation of the [`Pad`].
120///
121/// Constructed with [`Pad::as_dpad8`]. Useful for simple games and ports.
122/// The middle of the pad is a "dead zone" pressing which will not activate any direction.
123///
124/// Implements all the same methods as [`DPad4`].
125///
126/// Invariant: it's not possible for opposite directions (left and right, or down and up)
127/// to be active at the same time. However, it's possible for heighboring directions
128/// (like up and right) to be active at the same time if the player presses a diagonal.
129///
130/// For completeness, here is the full list of possible states:
131///
132/// * right
133/// * right and up
134/// * up
135/// * left and up
136/// * left
137/// * left and down
138/// * down
139/// * right and down
140/// * none
141#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
142pub struct DPad8 {
143    pub left: bool,
144    pub right: bool,
145    pub up: bool,
146    pub down: bool,
147}
148
149impl DPad8 {
150    /// Check if any direction is pressed.
151    #[must_use]
152    pub fn any(&self) -> bool {
153        self.left || self.right || self.up || self.down
154    }
155
156    /// Given the old state, get directions that were not pressed but are pressed now.
157    #[must_use]
158    pub fn just_pressed(&self, old: &Self) -> Self {
159        Self {
160            left: self.left && !old.left,
161            right: self.right && !old.right,
162            up: self.up && !old.up,
163            down: self.down && !old.down,
164        }
165    }
166
167    /// Given the old state, get directions that were pressed but aren't pressed now.
168    #[must_use]
169    pub fn just_released(&self, old: &Self) -> Self {
170        Self {
171            left: !self.left && old.left,
172            right: !self.right && old.right,
173            up: !self.up && old.up,
174            down: !self.down && old.down,
175        }
176    }
177
178    /// Given the old state, get directions that were pressed and are still pressed now.
179    #[must_use]
180    pub fn held(&self, old: &Self) -> Self {
181        Self {
182            left: self.left && old.left,
183            right: self.right && old.right,
184            up: self.up && old.up,
185            down: self.down && old.down,
186        }
187    }
188}
189
190impl From<Pad> for DPad8 {
191    fn from(value: Pad) -> Self {
192        value.as_dpad8()
193    }
194}
195
196impl From<Option<DPad8>> for DPad8 {
197    fn from(value: Option<DPad8>) -> Self {
198        value.unwrap_or_default()
199    }
200}
201
202impl From<DPad4> for DPad8 {
203    fn from(value: DPad4) -> Self {
204        let mut pad = Self::default();
205        match value {
206            DPad4::None => {}
207            DPad4::Left => pad.left = true,
208            DPad4::Right => pad.right = true,
209            DPad4::Up => pad.up = true,
210            DPad4::Down => pad.down = true,
211        }
212        pad
213    }
214}
215
216/// 4-directional DPad-like representation of the [`Pad`].
217///
218/// Constructed with [`Pad::as_dpad4`]. Useful for simple games and ports.
219/// The middle of the pad is a "dead zone" pressing which will not activate any direction.
220///
221/// Implements all the same methods as [`DPad8`].
222#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
223pub enum DPad4 {
224    #[default]
225    None,
226    Left,
227    Right,
228    Up,
229    Down,
230}
231
232impl DPad4 {
233    /// Check if any direction is pressed.
234    #[must_use]
235    pub fn any(self) -> bool {
236        self != Self::None
237    }
238
239    /// Given the old state, get directions that were not pressed but are pressed now.
240    #[must_use]
241    pub fn just_pressed(self, old: Self) -> Self {
242        if self == old { Self::None } else { self }
243    }
244
245    /// Given the old state, get directions that were pressed but aren't pressed now.
246    #[must_use]
247    pub fn just_released(self, old: Self) -> Self {
248        if self == old { Self::None } else { old }
249    }
250
251    /// Given the old state, get directions that were pressed and are still pressed now.
252    #[must_use]
253    pub fn held(self, old: Self) -> Self {
254        if self == old { self } else { Self::None }
255    }
256}
257
258impl From<Pad> for DPad4 {
259    fn from(value: Pad) -> Self {
260        value.as_dpad4()
261    }
262}
263
264impl From<Option<DPad4>> for DPad4 {
265    fn from(value: Option<DPad4>) -> Self {
266        value.unwrap_or_default()
267    }
268}
269
270/// State of the buttons.
271#[derive(Default, Clone, Copy)]
272pub struct Buttons {
273    /// South. The bottom button, like A on the X-Box controller.
274    ///
275    /// Typically used for confirmation, main action, jump, etc.
276    pub s: bool,
277
278    /// East. The right button, like B on the X-Box controller.
279    ///
280    /// Typically used for cancellation, going to previous screen, etc.
281    pub e: bool,
282
283    /// West. The left button, like X on the X-Box controller.
284    ///
285    /// Typically used for attack.
286    pub w: bool,
287
288    /// North. The top button, like Y on the X-Box controller.
289    ///
290    /// Typically used for a secondary action, like charged attack.
291    pub n: bool,
292
293    /// The menu button, almost always handled by the runtime.
294    pub menu: bool,
295}
296
297impl Buttons {
298    /// Check if any button is pressed.
299    #[must_use]
300    pub fn any(&self) -> bool {
301        self.s || self.e || self.w || self.n || self.menu
302    }
303
304    /// Given the old state, get buttons that were not pressed but are pressed now.
305    #[must_use]
306    pub fn just_pressed(&self, old: &Self) -> Self {
307        Self {
308            s: self.s && !old.s,
309            e: self.e && !old.e,
310            w: self.w && !old.w,
311            n: self.n && !old.n,
312            menu: self.menu && !old.menu,
313        }
314    }
315
316    /// Given the old state, get buttons that were pressed but aren't pressed now.
317    #[must_use]
318    pub fn just_released(&self, old: &Self) -> Self {
319        Self {
320            s: !self.s && old.s,
321            e: !self.e && old.e,
322            w: !self.w && old.w,
323            n: !self.n && old.n,
324            menu: !self.menu && old.menu,
325        }
326    }
327
328    /// Given the old state, get buttons that were pressed but are still pressed now.
329    #[must_use]
330    pub fn held(&self, old: &Self) -> Self {
331        Self {
332            s: self.s && old.s,
333            e: self.e && old.e,
334            w: self.w && old.w,
335            n: self.n && old.n,
336            menu: self.menu && old.menu,
337        }
338    }
339}
340
341/// Get the current touch pad state.
342#[must_use]
343pub fn read_pad(p: Peer) -> Option<Pad> {
344    let p = u32::from(p.0);
345    let raw = unsafe { bindings::read_pad(p) };
346    if raw == 0xffff {
347        None
348    } else {
349        Some(Pad {
350            x: i32::from((raw >> 16) as i16),
351            y: i32::from(raw as i16),
352        })
353    }
354}
355
356/// Get the currently pressed buttons.
357#[must_use]
358pub fn read_buttons(p: Peer) -> Buttons {
359    let p = u32::from(p.0);
360    let raw = unsafe { bindings::read_buttons(p) };
361    Buttons {
362        s: has_bit_set(raw, 0),
363        e: has_bit_set(raw, 1),
364        w: has_bit_set(raw, 2),
365        n: has_bit_set(raw, 3),
366        menu: has_bit_set(raw, 4),
367    }
368}
369
370/// Check if the given i32 value has the given bit set.
371#[inline]
372fn has_bit_set(val: u32, bit: usize) -> bool {
373    (val >> bit) & 0b1 != 0
374}
375
376mod bindings {
377    #[link(wasm_import_module = "input")]
378    unsafe extern "C" {
379        pub(crate) fn read_pad(peer: u32) -> u32;
380        pub(crate) fn read_buttons(peer: u32) -> u32;
381    }
382}