Skip to main content

agg_gui/
gamepad.rs

1//! # Gamepad input — platform shell → app
2//!
3//! Same flow as [`crate::tilt`]: platform shells poll their gamepad
4//! API each frame (Web Gamepad API in the browser, a crate like
5//! `gilrs` natively — shells own the dependency) and publish the
6//! first active pad's state here; apps read [`state`] whenever they
7//! gather input. `None` means no pad is connected (or none has been
8//! touched yet — browsers hide pads until the first button press).
9//!
10//! Axes are in SCREEN convention: `left_x` positive toward the
11//! screen's right edge, `left_y` positive toward its bottom (the
12//! standard-mapping Gamepad API convention). Buttons use the
13//! position-based [`buttons`] bits so apps don't care about
14//! Xbox/PlayStation labels.
15
16use std::sync::Mutex;
17
18/// Button bits, by physical position (standard gamepad mapping).
19pub mod buttons {
20    /// Bottom face button (Xbox A / PS Cross).
21    pub const SOUTH: u32 = 1 << 0;
22    /// Right face button (Xbox B / PS Circle).
23    pub const EAST: u32 = 1 << 1;
24    /// Left face button (Xbox X / PS Square).
25    pub const WEST: u32 = 1 << 2;
26    /// Top face button (Xbox Y / PS Triangle).
27    pub const NORTH: u32 = 1 << 3;
28    pub const L1: u32 = 1 << 4;
29    pub const R1: u32 = 1 << 5;
30    pub const SELECT: u32 = 1 << 6;
31    pub const START: u32 = 1 << 7;
32    pub const DPAD_UP: u32 = 1 << 8;
33    pub const DPAD_DOWN: u32 = 1 << 9;
34    pub const DPAD_LEFT: u32 = 1 << 10;
35    pub const DPAD_RIGHT: u32 = 1 << 11;
36}
37
38/// One pad's state, republished by the shell every frame.
39#[derive(Clone, Copy, Debug, Default, PartialEq)]
40pub struct GamepadState {
41    /// Left stick, -1..1 per axis, screen convention (y down).
42    pub left_x: f64,
43    pub left_y: f64,
44    /// Pressed buttons as [`buttons`] bits.
45    pub buttons: u32,
46}
47
48impl GamepadState {
49    pub fn pressed(&self, bit: u32) -> bool {
50        self.buttons & bit != 0
51    }
52}
53
54static STATE: Mutex<Option<GamepadState>> = Mutex::new(None);
55
56/// Shell side: publish the current pad state (`None` = disconnected).
57pub fn set_state(state: Option<GamepadState>) {
58    *STATE.lock().unwrap() = state;
59}
60
61/// App side: the most recent pad state, if a pad is live.
62pub fn state() -> Option<GamepadState> {
63    *STATE.lock().unwrap()
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn state_roundtrips_and_buttons_test() {
72        set_state(Some(GamepadState {
73            left_x: 0.5,
74            left_y: -1.0,
75            buttons: buttons::SOUTH | buttons::START,
76        }));
77        let s = state().unwrap();
78        assert_eq!(s.left_x, 0.5);
79        assert!(s.pressed(buttons::SOUTH));
80        assert!(s.pressed(buttons::START));
81        assert!(!s.pressed(buttons::EAST));
82        set_state(None);
83        assert_eq!(state(), None);
84    }
85}