Skip to main content

repame_shell/
pads.rs

1use std::collections::HashMap;
2
3use glam::Vec2;
4use repame_input::GamepadState;
5use repose_core::input::{GamepadAxis, GamepadButton, GamepadEvent, GamepadId};
6
7pub const TRIGGER_HELD: f32 = 0.5;
8
9#[derive(Default)]
10pub struct PadBridge {
11    vendor_id: u16,
12    product_id: u16,
13    lx: f32,
14    ly: f32,
15    rx: f32,
16    ry: f32,
17    lt_held: bool,
18    rt_held: bool,
19    south: bool,
20    east: bool,
21    west: bool,
22    dpad_l: bool,
23    dpad_u: bool,
24    dpad_r: bool,
25    dpad_d: bool,
26    north: bool,
27    lb: bool,
28    rb: bool,
29    start: bool,
30    select: bool,
31    l3: bool,
32    r3: bool,
33    lt_edge: bool,
34    rt_edge: bool,
35    south_edge: bool,
36    east_edge: bool,
37    west_edge: bool,
38    dpad_l_edge: bool,
39    dpad_u_edge: bool,
40    dpad_r_edge: bool,
41    dpad_d_edge: bool,
42    north_edge: bool,
43    lb_edge: bool,
44    rb_edge: bool,
45    start_edge: bool,
46    select_edge: bool,
47    l3_edge: bool,
48    r3_edge: bool,
49}
50
51impl PadBridge {
52    pub fn button(&mut self, button: GamepadButton, pressed: bool) {
53        let (held, edge) = match button {
54            GamepadButton::South => (&mut self.south, &mut self.south_edge),
55            GamepadButton::East => (&mut self.east, &mut self.east_edge),
56            GamepadButton::West => (&mut self.west, &mut self.west_edge),
57            GamepadButton::North => (&mut self.north, &mut self.north_edge),
58            GamepadButton::DPadLeft => (&mut self.dpad_l, &mut self.dpad_l_edge),
59            GamepadButton::DPadUp => (&mut self.dpad_u, &mut self.dpad_u_edge),
60            GamepadButton::DPadRight => (&mut self.dpad_r, &mut self.dpad_r_edge),
61            GamepadButton::DPadDown => (&mut self.dpad_d, &mut self.dpad_d_edge),
62            GamepadButton::LeftShoulder => (&mut self.lb, &mut self.lb_edge),
63            GamepadButton::RightShoulder => (&mut self.rb, &mut self.rb_edge),
64            GamepadButton::Start => (&mut self.start, &mut self.start_edge),
65            GamepadButton::Select => (&mut self.select, &mut self.select_edge),
66            GamepadButton::LeftStick => (&mut self.l3, &mut self.l3_edge),
67            GamepadButton::RightStick => (&mut self.r3, &mut self.r3_edge),
68        };
69        if pressed && !*held {
70            *edge = true;
71        }
72        *held = pressed;
73    }
74
75    pub fn axis(&mut self, axis: GamepadAxis, value: f32) {
76        match axis {
77            GamepadAxis::LeftStickX => self.lx = value,
78            GamepadAxis::LeftStickY => self.ly = value,
79            GamepadAxis::RightStickX => self.rx = value,
80            GamepadAxis::RightStickY => self.ry = value,
81            GamepadAxis::LeftTrigger => {
82                let held = value > TRIGGER_HELD;
83                if held && !self.lt_held {
84                    self.lt_edge = true;
85                }
86                self.lt_held = held;
87            }
88            GamepadAxis::RightTrigger => {
89                let held = value > TRIGGER_HELD;
90                if held && !self.rt_held {
91                    self.rt_edge = true;
92                }
93                self.rt_held = held;
94            }
95        }
96    }
97
98    pub fn snapshot(&self) -> GamepadState {
99        GamepadState {
100            vendor_id: self.vendor_id,
101            product_id: self.product_id,
102            left_stick: Vec2::new(self.lx, self.ly),
103            right_stick: Vec2::new(self.rx, self.ry),
104            left_trigger_held: self.lt_held,
105            left_trigger_pressed: self.lt_edge,
106            right_trigger_held: self.rt_held,
107            right_trigger_pressed: self.rt_edge,
108            south_held: self.south,
109            south_pressed: self.south_edge,
110            east_held: self.east,
111            east_pressed: self.east_edge,
112            west_held: self.west,
113            west_pressed: self.west_edge,
114            north_held: self.north,
115            north_pressed: self.north_edge,
116            dpad_left_held: self.dpad_l,
117            dpad_left_pressed: self.dpad_l_edge,
118            dpad_up_held: self.dpad_u,
119            dpad_up_pressed: self.dpad_u_edge,
120            dpad_right_held: self.dpad_r,
121            dpad_right_pressed: self.dpad_r_edge,
122            dpad_down_held: self.dpad_d,
123            dpad_down_pressed: self.dpad_d_edge,
124            left_shoulder_held: self.lb,
125            left_shoulder_pressed: self.lb_edge,
126            right_shoulder_held: self.rb,
127            right_shoulder_pressed: self.rb_edge,
128            start_held: self.start,
129            start_pressed: self.start_edge,
130            select_held: self.select,
131            select_pressed: self.select_edge,
132            left_stick_click_held: self.l3,
133            left_stick_click_pressed: self.l3_edge,
134            right_stick_click_held: self.r3,
135            right_stick_click_pressed: self.r3_edge,
136        }
137    }
138
139    pub fn clear_edges(&mut self) {
140        self.lt_edge = false;
141        self.rt_edge = false;
142        self.south_edge = false;
143        self.east_edge = false;
144        self.west_edge = false;
145        self.dpad_l_edge = false;
146        self.dpad_u_edge = false;
147        self.dpad_r_edge = false;
148        self.dpad_d_edge = false;
149        self.north_edge = false;
150        self.lb_edge = false;
151        self.rb_edge = false;
152        self.start_edge = false;
153        self.select_edge = false;
154        self.l3_edge = false;
155        self.r3_edge = false;
156    }
157}
158
159#[derive(Default)]
160pub struct PadBank {
161    pads: HashMap<GamepadId, PadBridge>,
162}
163
164impl PadBank {
165    pub fn feed(&mut self, events: Vec<GamepadEvent>) {
166        for ev in events {
167            match ev {
168                GamepadEvent::Connected {
169                    id,
170                    vendor_id,
171                    product_id,
172                    ..
173                } => {
174                    let pad = self.pads.entry(id).or_default();
175                    pad.vendor_id = vendor_id;
176                    pad.product_id = product_id;
177                }
178                GamepadEvent::Disconnected { id } => {
179                    self.pads.remove(&id);
180                }
181                GamepadEvent::Button {
182                    id,
183                    button,
184                    pressed,
185                } => {
186                    self.pads.entry(id).or_default().button(button, pressed);
187                }
188                GamepadEvent::Axis { id, axis, value } => {
189                    self.pads.entry(id).or_default().axis(axis, value);
190                }
191            }
192        }
193    }
194
195    /// Snapshots paired with their device id (id-sorted): callers route
196    /// per-player pads before the id is discarded. Prefer this over
197    /// [`PadBank::drain`], which drops the routing key.
198    pub fn drain_with_id(&mut self) -> Vec<(GamepadId, GamepadState)> {
199        let mut ids: Vec<GamepadId> = self.pads.keys().copied().collect();
200        ids.sort_by_key(|id| id.0);
201        let mut out = Vec::with_capacity(ids.len());
202        for id in ids {
203            if let Some(bridge) = self.pads.get_mut(&id) {
204                out.push((id, bridge.snapshot()));
205                bridge.clear_edges();
206            }
207        }
208        out
209    }
210
211    pub fn drain(&mut self) -> Vec<GamepadState> {
212        self.drain_with_id().into_iter().map(|(_, s)| s).collect()
213    }
214
215    pub fn live(&self) -> bool {
216        !self.pads.is_empty()
217    }
218}
219
220#[cfg(test)]
221mod tests {
222    use super::*;
223
224    #[test]
225    fn rising_edge_fires_once() {
226        let mut b = PadBridge::default();
227        b.button(GamepadButton::South, true);
228        assert!(b.snapshot().south_pressed);
229        b.clear_edges();
230        b.button(GamepadButton::South, true);
231        assert!(!b.snapshot().south_pressed);
232    }
233
234    #[test]
235    fn trigger_threshold_edges() {
236        let mut b = PadBridge::default();
237        b.axis(GamepadAxis::RightTrigger, 0.8);
238        let s = b.snapshot();
239        assert!(s.right_trigger_held && s.right_trigger_pressed);
240    }
241
242    #[test]
243    fn shoulder_and_dpad_down_buttons_track() {
244        let mut b = PadBridge::default();
245        b.button(GamepadButton::RightShoulder, true);
246        b.button(GamepadButton::LeftShoulder, true);
247        b.button(GamepadButton::DPadDown, true);
248        b.button(GamepadButton::West, true);
249        let s = b.snapshot();
250        assert!(s.right_shoulder_held && s.right_shoulder_pressed);
251        assert!(s.left_shoulder_held && s.left_shoulder_pressed);
252        assert!(s.dpad_down_pressed);
253        assert!(s.west_pressed);
254    }
255
256    #[test]
257    fn drain_with_id_routes_devices() {
258        let mut bank = PadBank::default();
259        let id0 = GamepadId(3);
260        let id1 = GamepadId(1);
261        bank.feed(vec![
262            GamepadEvent::Connected {
263                id: id0,
264                name: "GameCube Adapter".to_string(),
265                vendor_id: 0x057e,
266                product_id: 0x0337,
267            },
268            GamepadEvent::Button {
269                id: id0,
270                button: GamepadButton::South,
271                pressed: true,
272            },
273            GamepadEvent::Button {
274                id: id1,
275                button: GamepadButton::East,
276                pressed: true,
277            },
278        ]);
279        let drained = bank.drain_with_id();
280        assert_eq!(drained.len(), 2);
281        assert_eq!(drained[0].0, id1, "id-sorted");
282        assert!(drained[1].1.south_pressed);
283        assert!(drained[0].1.east_pressed);
284        assert_eq!(
285            (drained[1].1.vendor_id, drained[1].1.product_id),
286            (0x057e, 0x0337),
287            "pad identity must reach the game"
288        );
289        assert_eq!(
290            (drained[0].1.vendor_id, drained[0].1.product_id),
291            (0, 0),
292            "a pad that never reported ids reads as zero"
293        );
294    }
295}