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}