firefly_rust/
input.rs

1//! Read inputs: touch pad, buttons, accelerometer.
2
3use crate::*;
4
5const DPAD_THRESHOLD: i32 = 100;
6
7/// A finger position on the touch pad.
8///
9/// Both x and y are somewhere the range between -1000 and 1000 (both ends included).
10/// The 1000 x is on the right, the 1000 y is on the top.
11#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
12pub struct Pad {
13    pub x: i32,
14    pub y: i32,
15}
16
17impl Pad {
18    pub const MAX: Pad = Pad { x: 1000, y: 1000 };
19    pub const MIN: Pad = Pad { x: -1000, y: -1000 };
20
21    /// Represent the pad values as a directional pad.
22    #[must_use]
23    pub fn as_dpad(&self) -> DPad {
24        DPad {
25            left: self.x <= -DPAD_THRESHOLD,
26            right: self.x >= DPAD_THRESHOLD,
27            down: self.y <= -DPAD_THRESHOLD,
28            up: self.y >= DPAD_THRESHOLD,
29        }
30    }
31
32    /// The distance from the pad center to the touch point.
33    #[must_use]
34    pub fn radius(self) -> f32 {
35        let r = self.x * self.x + self.y * self.y;
36        #[expect(clippy::cast_precision_loss)]
37        math::sqrt(r as f32)
38    }
39
40    /// The angle of the [polar coordinate] of the touch point.
41    ///
42    /// [polar coordinate]: https://en.wikipedia.org/wiki/Polar_coordinate_system
43    #[must_use]
44    pub fn azimuth(self) -> Angle {
45        #[expect(clippy::cast_precision_loss)]
46        let r = math::atan2(self.y as f32, self.x as f32);
47        Angle::from_radians(r)
48    }
49}
50
51impl From<Pad> for Point {
52    fn from(value: Pad) -> Self {
53        Self {
54            x: value.x,
55            y: value.y,
56        }
57    }
58}
59
60impl From<Point> for Pad {
61    fn from(value: Point) -> Self {
62        Self {
63            x: value.x,
64            y: value.y,
65        }
66    }
67}
68
69impl From<Pad> for Size {
70    fn from(value: Pad) -> Self {
71        Self {
72            width: value.x,
73            height: value.y,
74        }
75    }
76}
77
78impl From<Size> for Pad {
79    fn from(value: Size) -> Self {
80        Self {
81            x: value.width,
82            y: value.height,
83        }
84    }
85}
86
87/// DPad-like representation of the [`Pad`].
88///
89/// Constructed with [`Pad::as_dpad`]. Useful for simple games and ports.
90/// The middle of the pad is a "dead zone" pressing which will not activate any direction.
91///
92/// Invariant: it's not possible for opposite directions (left and right, or down and up)
93/// to be active at the same time. However, it's possible for heighboring directions
94/// (like up and right) to be active at the same time if the player presses a diagonal.
95#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
96pub struct DPad {
97    pub left: bool,
98    pub right: bool,
99    pub up: bool,
100    pub down: bool,
101}
102
103impl From<Pad> for DPad {
104    fn from(value: Pad) -> Self {
105        value.as_dpad()
106    }
107}
108
109impl DPad {
110    #[must_use]
111    pub fn any(&self) -> bool {
112        self.left || self.right || self.up || self.down
113    }
114
115    /// Given the old state, get directions that were not pressed but are pressed now.
116    #[must_use]
117    pub fn just_pressed(&self, old: &Self) -> Self {
118        Self {
119            left: self.left && !old.left,
120            right: self.right && !old.right,
121            up: self.up && !old.up,
122            down: self.down && !old.down,
123        }
124    }
125
126    /// Given the old state, get directions that were pressed but aren't pressed now.
127    #[must_use]
128    pub fn just_released(&self, old: &Self) -> Self {
129        Self {
130            left: !self.left && old.left,
131            right: !self.right && old.right,
132            up: !self.up && old.up,
133            down: !self.down && old.down,
134        }
135    }
136
137    /// Given the old state, get directions that were pressed and are still pressed now.
138    #[must_use]
139    pub fn held(&self, old: &Self) -> Self {
140        Self {
141            left: self.left && old.left,
142            right: self.right && old.right,
143            up: self.up && old.up,
144            down: self.down && old.down,
145        }
146    }
147}
148
149/// State of the buttons.
150#[derive(Default)]
151pub struct Buttons {
152    /// South. The bottom button, like A on the X-Box controller.
153    ///
154    /// Typically used for confirmation, main action, jump, etc.
155    pub s: bool,
156
157    /// East. The right button, like B on the X-Box controller.
158    ///
159    /// Typically used for cancellation, going to previous screen, etc.
160    pub e: bool,
161
162    /// West. The left button, like X on the X-Box controller.
163    ///
164    /// Typically used for attack.
165    pub w: bool,
166
167    /// North. The top button, like Y on the X-Box controller.
168    ///
169    /// Typically used for a secondary action, like charged attack.
170    pub n: bool,
171
172    /// The menu button, almost always handled by the runtime.
173    pub menu: bool,
174}
175
176impl Buttons {
177    /// Check if any button is pressed.
178    #[must_use]
179    pub fn any(&self) -> bool {
180        self.s || self.e || self.w || self.n || self.menu
181    }
182
183    /// Given the old state, get buttons that were not pressed but are pressed now.
184    #[must_use]
185    pub fn just_pressed(&self, old: &Self) -> Self {
186        Self {
187            s: self.s && !old.s,
188            e: self.e && !old.e,
189            w: self.w && !old.w,
190            n: self.n && !old.n,
191            menu: self.menu && !old.menu,
192        }
193    }
194
195    /// Given the old state, get buttons that were pressed but aren't pressed now.
196    #[must_use]
197    pub fn just_released(&self, old: &Self) -> Self {
198        Self {
199            s: !self.s && old.s,
200            e: !self.e && old.e,
201            w: !self.w && old.w,
202            n: !self.n && old.n,
203            menu: !self.menu && old.menu,
204        }
205    }
206
207    /// Given the old state, get buttons that were pressed but are still pressed now.
208    #[must_use]
209    pub fn held(&self, old: &Self) -> Self {
210        Self {
211            s: self.s && old.s,
212            e: self.e && old.e,
213            w: self.w && old.w,
214            n: self.n && old.n,
215            menu: self.menu && old.menu,
216        }
217    }
218}
219
220/// Get the current touch pad state.
221#[must_use]
222pub fn read_pad(p: Peer) -> Option<Pad> {
223    let p = u32::from(p.0);
224    let raw = unsafe { bindings::read_pad(p) };
225    if raw == 0xffff {
226        None
227    } else {
228        Some(Pad {
229            x: i32::from((raw >> 16) as i16),
230            y: i32::from(raw as i16),
231        })
232    }
233}
234
235/// Get the currently pressed buttons.
236#[must_use]
237pub fn read_buttons(p: Peer) -> Buttons {
238    let p = u32::from(p.0);
239    let raw = unsafe { bindings::read_buttons(p) };
240    Buttons {
241        s: has_bit_set(raw, 0),
242        e: has_bit_set(raw, 1),
243        w: has_bit_set(raw, 2),
244        n: has_bit_set(raw, 3),
245        menu: has_bit_set(raw, 4),
246    }
247}
248
249/// Check if the given i32 value has the given bit set.
250#[inline]
251fn has_bit_set(val: u32, bit: usize) -> bool {
252    (val >> bit) & 0b1 != 0
253}
254
255mod bindings {
256    #[link(wasm_import_module = "input")]
257    extern "C" {
258        pub(crate) fn read_pad(peer: u32) -> u32;
259        pub(crate) fn read_buttons(peer: u32) -> u32;
260    }
261}