1
//! Gamepad manager — cross-platform state for the controller surface
2
//! (SUPER_PLAN_2 §1 feature 6 + research/03).
3
//!
4
//! Poll + push-driven, like the sensors:
5
//!
6
//! - The **platform backend** (`dll/src/desktop/extra/gamepad/<plat>.rs`)
7
//!   polls `gilrs` / iOS `GCController` / Android `InputDevice` and calls
8
//!   [`push_gamepad_state`] whenever a pad's state changes.
9
//! - The dll **layout pass** drains the channel via
10
//!   [`drain_gamepad_states`] and folds each into the manager through
11
//!   [`GamepadManager::set_state`].
12
//! - **Callbacks** read [`GamepadManager::state`] / [`GamepadManager::primary`]
13
//!   synchronously (via `CallbackInfo::get_gamepad_state`) to drive
14
//!   movement / menu UI.
15
//!
16
//! Unlike the sensors' fixed three slots, the set of pads is dynamic: one
17
//! [`GamepadState`] slot per [`GamepadId`] seen this session, kept across
18
//! frames so a disconnect stays observable (`connected = false`). No
19
//! platform deps (SUPER_PLAN_2 §0.5); the channel mirrors `sensors.rs`.
20

            
21
use alloc::vec::Vec;
22

            
23
use azul_core::dom::DomNodeId;
24
use azul_core::events::{
25
    EventData, EventProvider, EventSource as CoreEventSource, EventType, SyntheticEvent,
26
};
27
use azul_core::task::Instant;
28
pub use azul_core::gamepad::{GamepadAxis, GamepadButton, GamepadId, GamepadState};
29

            
30
/// Cross-platform gamepad state. One per `App` — the OS exposes a single
31
/// per-process controller subscription, not per-window.
32
#[derive(Debug, Clone, PartialEq, Default)]
33
pub struct GamepadManager {
34
    /// One slot per pad seen this session; `connected` flips to `false` on
35
    /// unplug (the slot is retained so a callback can observe it).
36
    pads: Vec<GamepadState>,
37
    /// `true` when a pad's state advanced since the last event-pass drain.
38
    /// Set by [`set_state`](Self::set_state); cleared by the dll after dispatch.
39
    pending_event: bool,
40
}
41

            
42
impl GamepadManager {
43
2359
    pub fn new() -> Self {
44
2359
        Self::default()
45
2359
    }
46

            
47
    /// Latest state for `id`, or `None` if that pad was never seen.
48
3
    pub fn state(&self, id: GamepadId) -> Option<GamepadState> {
49
3
        self.pads.iter().find(|p| p.id == id).copied()
50
3
    }
51

            
52
    /// The first currently-connected pad — the common single-controller
53
    /// case, so a callback doesn't have to track ids.
54
36
    pub fn primary(&self) -> Option<GamepadState> {
55
36
        self.pads.iter().find(|p| p.connected).copied()
56
36
    }
57

            
58
    /// Every pad slot seen this session (connected or not).
59
3
    pub fn gamepads(&self) -> &[GamepadState] {
60
3
        &self.pads
61
3
    }
62

            
63
    /// Apply a state the backend delivered (upsert by id). Returns `true`
64
    /// if it advanced (bit-pattern different from the previous slot), so an
65
    /// idle controller doesn't make every frame look "changed".
66
44
    pub fn set_state(&mut self, state: GamepadState) -> bool {
67
44
        let changed = if let Some(slot) = self.pads.iter_mut().find(|p| p.id == state.id) {
68
3
            let changed = !state_bitwise_eq(slot, &state);
69
3
            *slot = state;
70
3
            changed
71
        } else {
72
41
            self.pads.push(state);
73
41
            true
74
        };
75
44
        if changed {
76
43
            self.pending_event = true;
77
43
        }
78
44
        changed
79
44
    }
80

            
81
    /// Clear the pending-event flag. The dll calls this after the event pass
82
    /// has collected the `GamepadInput` event.
83
    pub fn clear_pending_event(&mut self) {
84
        self.pending_event = false;
85
    }
86
}
87

            
88
impl EventProvider for GamepadManager {
89
    /// Yield a window-level `GamepadInput` event when a pad's state advanced
90
    /// since the last drain (target = root; read it via
91
    /// `CallbackInfo::get_primary_gamepad` / `get_gamepad_state`).
92
35
    fn get_pending_events(&self, timestamp: Instant) -> Vec<SyntheticEvent> {
93
35
        if self.pending_event {
94
35
            alloc::vec![SyntheticEvent::new(
95
35
                EventType::GamepadInput,
96
35
                CoreEventSource::User,
97
                DomNodeId::ROOT,
98
35
                timestamp,
99
35
                EventData::None,
100
            )]
101
        } else {
102
            Vec::new()
103
        }
104
35
    }
105
}
106

            
107
3
fn state_bitwise_eq(a: &GamepadState, b: &GamepadState) -> bool {
108
3
    a.id == b.id
109
3
        && a.connected == b.connected
110
3
        && a.buttons == b.buttons
111
1
        && a.left_stick_x.to_bits() == b.left_stick_x.to_bits()
112
1
        && a.left_stick_y.to_bits() == b.left_stick_y.to_bits()
113
1
        && a.right_stick_x.to_bits() == b.right_stick_x.to_bits()
114
1
        && a.right_stick_y.to_bits() == b.right_stick_y.to_bits()
115
1
        && a.left_z.to_bits() == b.left_z.to_bits()
116
1
        && a.right_z.to_bits() == b.right_z.to_bits()
117
3
}
118

            
119
// ────────── Async update channel (platform backend → manager) ──────────
120
//
121
// gilrs / GCController / InputDevice deliver on the backend's poll thread
122
// with no handle to the live `GamepadManager` (inside the window's
123
// `LayoutWindow`). The backend parks each changed state here; the layout
124
// pass drains it and applies the latest per id. Pure Rust — no platform
125
// dependency (SUPER_PLAN_2 §0.5). Mirrors the sensor reading channel.
126

            
127
static PENDING_STATES: std::sync::Mutex<Vec<GamepadState>> = std::sync::Mutex::new(Vec::new());
128

            
129
/// Park a gamepad state delivered by a platform backend (in the dll).
130
/// Thread-safe; poison-recovering.
131
38
pub fn push_gamepad_state(state: GamepadState) {
132
38
    let mut q = PENDING_STATES.lock().unwrap_or_else(|e| e.into_inner());
133
38
    q.push(state);
134
38
}
135

            
136
/// Drain every state parked by [`push_gamepad_state`], in arrival order.
137
/// Called once per layout pass; the caller applies them through
138
/// [`GamepadManager::set_state`] (the last per id wins).
139
73
pub fn drain_gamepad_states() -> Vec<GamepadState> {
140
73
    let mut q = PENDING_STATES.lock().unwrap_or_else(|e| e.into_inner());
141
73
    core::mem::take(&mut *q)
142
73
}
143

            
144
#[cfg(test)]
145
mod tests {
146
    use super::*;
147

            
148
10
    fn st(id: u32, connected: bool, buttons: u32) -> GamepadState {
149
10
        let mut s = GamepadState::empty(GamepadId { id });
150
10
        s.connected = connected;
151
10
        s.buttons = buttons;
152
10
        s
153
10
    }
154

            
155
    #[test]
156
1
    fn manager_upserts_by_id_and_flags_change() {
157
1
        let mut mgr = GamepadManager::new();
158
1
        assert_eq!(mgr.state(GamepadId { id: 0 }), None);
159
        // First state for an id is a change + adds a slot.
160
1
        assert!(mgr.set_state(st(0, true, 0b1)));
161
1
        assert!(mgr.state(GamepadId { id: 0 }).is_some());
162
        // Same state again — no change.
163
1
        assert!(!mgr.set_state(st(0, true, 0b1)));
164
        // Different buttons — change, same slot (not a new pad).
165
1
        assert!(mgr.set_state(st(0, true, 0b11)));
166
1
        assert_eq!(mgr.gamepads().len(), 1);
167
        // A second pad adds a slot.
168
1
        assert!(mgr.set_state(st(1, true, 0)));
169
1
        assert_eq!(mgr.gamepads().len(), 2);
170
1
    }
171

            
172
    #[test]
173
1
    fn primary_is_first_connected() {
174
1
        let mut mgr = GamepadManager::new();
175
1
        mgr.set_state(st(0, false, 0)); // disconnected
176
1
        mgr.set_state(st(1, true, 0));
177
1
        assert_eq!(mgr.primary().map(|p| p.id.id), Some(1));
178
1
    }
179

            
180
    #[test]
181
1
    fn is_pressed_decodes_the_bitset() {
182
1
        let s = st(0, true, GamepadButton::South.bit() | GamepadButton::Start.bit());
183
1
        assert!(s.is_pressed(GamepadButton::South));
184
1
        assert!(s.is_pressed(GamepadButton::Start));
185
1
        assert!(!s.is_pressed(GamepadButton::East));
186
1
    }
187

            
188
    #[test]
189
1
    fn states_round_trip_through_the_channel() {
190
1
        let _ = drain_gamepad_states();
191
1
        push_gamepad_state(st(0, true, 0b1));
192
1
        push_gamepad_state(st(0, true, 0b10)); // last per id wins
193
1
        push_gamepad_state(st(1, true, 0));
194
1
        let drained = drain_gamepad_states();
195
1
        assert_eq!(drained.len(), 3);
196

            
197
1
        let mut mgr = GamepadManager::new();
198
4
        for s in &drained {
199
3
            mgr.set_state(*s);
200
3
        }
201
1
        assert_eq!(mgr.state(GamepadId { id: 0 }).map(|p| p.buttons), Some(0b10));
202
1
        assert_eq!(mgr.gamepads().len(), 2);
203
1
        assert!(drain_gamepad_states().is_empty());
204
1
    }
205
}