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::atan(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    /// Given the old state, get directions that were not pressed but are pressed now.
111    #[must_use]
112    pub fn just_pressed(&self, old: &Self) -> Self {
113        Self {
114            left: self.left && !old.left,
115            right: self.right && !old.right,
116            up: self.up && !old.up,
117            down: self.down && !old.down,
118        }
119    }
120
121    /// Given the old state, get directions that were pressed but aren't pressed now.
122    #[must_use]
123    pub fn just_released(&self, old: &Self) -> Self {
124        Self {
125            left: !self.left && old.left,
126            right: !self.right && old.right,
127            up: !self.up && old.up,
128            down: !self.down && old.down,
129        }
130    }
131
132    /// Given the old state, get directions that were pressed and are still pressed now.
133    #[must_use]
134    pub fn held(&self, old: &Self) -> Self {
135        Self {
136            left: self.left && old.left,
137            right: self.right && old.right,
138            up: self.up && old.up,
139            down: self.down && old.down,
140        }
141    }
142}
143
144/// State of the buttons.
145#[derive(Default)]
146pub struct Buttons {
147    /// South. The bottom button, like A on the X-Box controller.
148    ///
149    /// Typically used for confirmation, main action, jump, etc.
150    pub s: bool,
151
152    /// East. The right button, like B on the X-Box controller.
153    ///
154    /// Typically used for cancellation, going to previous screen, etc.
155    pub e: bool,
156
157    /// West. The left button, like X on the X-Box controller.
158    ///
159    /// Typically used for attack.
160    pub w: bool,
161
162    /// North. The top button, like Y on the X-Box controller.
163    ///
164    /// Typically used for a secondary action, like charged attack.
165    pub n: bool,
166
167    /// The menu button, almost always handled by the runtime.
168    pub menu: bool,
169}
170
171impl Buttons {
172    /// Check if any button is pressed.
173    #[must_use]
174    pub fn any(&self) -> bool {
175        self.s || self.e || self.w || self.n || self.menu
176    }
177
178    /// Given the old state, get buttons that were not pressed but are pressed now.
179    #[must_use]
180    pub fn just_pressed(&self, old: &Self) -> Self {
181        Self {
182            s: self.s && !old.s,
183            e: self.e && !old.e,
184            w: self.w && !old.w,
185            n: self.n && !old.n,
186            menu: self.menu && !old.menu,
187        }
188    }
189
190    /// Given the old state, get buttons that were pressed but aren't pressed now.
191    #[must_use]
192    pub fn just_released(&self, old: &Self) -> Self {
193        Self {
194            s: !self.s && old.s,
195            e: !self.e && old.e,
196            w: !self.w && old.w,
197            n: !self.n && old.n,
198            menu: !self.menu && old.menu,
199        }
200    }
201
202    /// Given the old state, get buttons that were pressed but are still pressed now.
203    #[must_use]
204    pub fn held(&self, old: &Self) -> Self {
205        Self {
206            s: self.s && old.s,
207            e: self.e && old.e,
208            w: self.w && old.w,
209            n: self.n && old.n,
210            menu: self.menu && old.menu,
211        }
212    }
213}
214
215/// Get the current touch pad state.
216#[must_use]
217pub fn read_pad(p: Peer) -> Option<Pad> {
218    let p = u32::from(p.0);
219    let raw = unsafe { bindings::read_pad(p) };
220    if raw == 0xffff {
221        None
222    } else {
223        Some(Pad {
224            x: i32::from((raw >> 16) as i16),
225            y: i32::from(raw as i16),
226        })
227    }
228}
229
230/// Get the currently pressed buttons.
231#[must_use]
232pub fn read_buttons(p: Peer) -> Buttons {
233    let p = u32::from(p.0);
234    let raw = unsafe { bindings::read_buttons(p) };
235    Buttons {
236        s: has_bit_set(raw, 0),
237        e: has_bit_set(raw, 1),
238        w: has_bit_set(raw, 2),
239        n: has_bit_set(raw, 3),
240        menu: has_bit_set(raw, 4),
241    }
242}
243
244/// Check if the given i32 value has the given bit set.
245#[inline]
246fn has_bit_set(val: u32, bit: usize) -> bool {
247    (val >> bit) & 0b1 != 0
248}
249
250mod bindings {
251    #[link(wasm_import_module = "input")]
252    extern {
253        pub(crate) fn read_pad(peer: u32) -> u32;
254        pub(crate) fn read_buttons(peer: u32) -> u32;
255    }
256}