azul_core/gamepad.rs
1//! POD types for the gamepad / game-controller surface
2//! (SUPER_PLAN_2 §1 feature 6 + research/03 §"Feature 6").
3//!
4//! Cross-platform controller input: `gilrs` on the desktop
5//! (Windows / Linux / macOS), iOS `GCController` + Android `InputDevice`
6//! on mobile (research/03). Defined here in `azul-core` so the manager +
7//! accessors cross the FFI without `azul-layout` as a dependency; the
8//! stateful side lives in `azul_layout::managers::gamepad::GamepadManager`.
9//!
10//! Poll model, like the sensors: the backend keeps a [`GamepadState`]
11//! snapshot per connected pad current, and a callback reads the latest each
12//! frame (`CallbackInfo::get_gamepad_state`) to drive movement / menus.
13//! Button + axis naming follows the SDL / gilrs "standard gamepad" mapping,
14//! so the face buttons are Xbox-style: South = A, East = B, West = X,
15//! North = Y.
16
17/// A connected gamepad's id — stable for the lifetime of the connection,
18/// assigned by the backend on connect. (gilrs `GamepadId` / the platform
19/// device id, normalised to a `u32`.)
20#[repr(C)]
21#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
22pub struct GamepadId {
23 pub id: u32,
24}
25
26/// A standard-layout gamepad button. Face buttons are Xbox-style by
27/// position (South = A / Cross, East = B / Circle, West = X / Square,
28/// North = Y / Triangle), so layouts stay consistent across vendors.
29///
30/// The discriminant order is also the bit position in
31/// [`GamepadState::buttons`] — don't reorder without bumping the ABI.
32#[repr(C)]
33#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
34pub enum GamepadButton {
35 /// Bottom face button (A / Cross).
36 South,
37 /// Right face button (B / Circle).
38 East,
39 /// Top face button (Y / Triangle).
40 North,
41 /// Left face button (X / Square).
42 West,
43 /// Left shoulder button (L1 / LB).
44 LeftBumper,
45 /// Right shoulder button (R1 / RB).
46 RightBumper,
47 /// Left trigger as a digital press (L2 / LT). Analog value: `LeftZ`.
48 LeftTrigger,
49 /// Right trigger as a digital press (R2 / RT). Analog value: `RightZ`.
50 RightTrigger,
51 /// Select / Back / Share.
52 Select,
53 /// Start / Options / Menu.
54 Start,
55 /// Vendor / guide button (Xbox / PS / Home).
56 Mode,
57 /// Left stick click (L3).
58 LeftThumb,
59 /// Right stick click (R3).
60 RightThumb,
61 /// D-pad up.
62 DPadUp,
63 /// D-pad down.
64 DPadDown,
65 /// D-pad left.
66 DPadLeft,
67 /// D-pad right.
68 DPadRight,
69}
70
71/// A gamepad analog axis. Stick axes are in `[-1, 1]` (right / up positive);
72/// trigger axes ([`GamepadAxis::LeftZ`] / [`GamepadAxis::RightZ`]) in
73/// `[0, 1]`.
74#[repr(C)]
75#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
76pub enum GamepadAxis {
77 /// Left stick horizontal (left −1 … right +1).
78 LeftStickX,
79 /// Left stick vertical (down −1 … up +1).
80 LeftStickY,
81 /// Right stick horizontal.
82 RightStickX,
83 /// Right stick vertical.
84 RightStickY,
85 /// Left trigger pressure (0 … 1).
86 LeftZ,
87 /// Right trigger pressure (0 … 1).
88 RightZ,
89}
90
91/// Snapshot of one gamepad's state. Buttons are a bitset (bit `n` = the
92/// [`GamepadButton`] with discriminant `n`); axes are explicit fields. All
93/// POD / `Copy`, so it crosses the FFI by value.
94#[repr(C)]
95#[derive(Debug, Clone, Copy, PartialEq)]
96pub struct GamepadState {
97 /// Which pad this snapshot is for.
98 pub id: GamepadId,
99 /// `false` once the pad disconnects (the manager keeps the last slot so
100 /// a callback can observe the disconnect).
101 pub connected: bool,
102 /// Pressed-button bitset — bit `n` set ⇔ the `GamepadButton` with
103 /// discriminant `n` is held. Read via [`GamepadState::is_pressed`].
104 pub buttons: u32,
105 /// Left stick X in `[-1, 1]`.
106 pub left_stick_x: f32,
107 /// Left stick Y in `[-1, 1]`.
108 pub left_stick_y: f32,
109 /// Right stick X in `[-1, 1]`.
110 pub right_stick_x: f32,
111 /// Right stick Y in `[-1, 1]`.
112 pub right_stick_y: f32,
113 /// Left trigger pressure in `[0, 1]`.
114 pub left_z: f32,
115 /// Right trigger pressure in `[0, 1]`.
116 pub right_z: f32,
117}
118
119impl GamepadButton {
120 /// This button's bit in [`GamepadState::buttons`].
121 pub fn bit(self) -> u32 {
122 1u32 << (self as u32)
123 }
124}
125
126impl GamepadState {
127 /// An empty (disconnected) state for `id` — all buttons up, axes zero.
128 pub fn empty(id: GamepadId) -> Self {
129 Self {
130 id,
131 connected: false,
132 buttons: 0,
133 left_stick_x: 0.0,
134 left_stick_y: 0.0,
135 right_stick_x: 0.0,
136 right_stick_y: 0.0,
137 left_z: 0.0,
138 right_z: 0.0,
139 }
140 }
141
142 /// Whether `button` is currently held.
143 pub fn is_pressed(&self, button: GamepadButton) -> bool {
144 self.buttons & button.bit() != 0
145 }
146
147 /// The current value of `axis` (sticks `[-1, 1]`, triggers `[0, 1]`).
148 pub fn axis(&self, axis: GamepadAxis) -> f32 {
149 match axis {
150 GamepadAxis::LeftStickX => self.left_stick_x,
151 GamepadAxis::LeftStickY => self.left_stick_y,
152 GamepadAxis::RightStickX => self.right_stick_x,
153 GamepadAxis::RightStickY => self.right_stick_y,
154 GamepadAxis::LeftZ => self.left_z,
155 GamepadAxis::RightZ => self.right_z,
156 }
157 }
158}
159
160// FFI Option wrapper for `CallbackInfo::get_gamepad_state(id) ->
161// Option<GamepadState>` (mirrors `OptionSensorReading`).
162impl_option!(
163 GamepadState,
164 OptionGamepadState,
165 [Debug, Clone, Copy, PartialEq]
166);