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    lx: f32,
12    ly: f32,
13    rx: f32,
14    ry: f32,
15    lt_held: bool,
16    rt_held: bool,
17    south: bool,
18    east: bool,
19    west: bool,
20    dpad_l: bool,
21    dpad_u: bool,
22    dpad_r: bool,
23    dpad_d: bool,
24    north: bool,
25    lb: bool,
26    rb: bool,
27    lt_edge: bool,
28    rt_edge: bool,
29    south_edge: bool,
30    east_edge: bool,
31    west_edge: bool,
32    dpad_l_edge: bool,
33    dpad_u_edge: bool,
34    dpad_r_edge: bool,
35    dpad_d_edge: bool,
36    north_edge: bool,
37    lb_edge: bool,
38    rb_edge: bool,
39}
40
41impl PadBridge {
42    pub fn button(&mut self, button: GamepadButton, pressed: bool) {
43        let (held, edge) = match button {
44            GamepadButton::South => (&mut self.south, &mut self.south_edge),
45            GamepadButton::East => (&mut self.east, &mut self.east_edge),
46            GamepadButton::West => (&mut self.west, &mut self.west_edge),
47            GamepadButton::North => (&mut self.north, &mut self.north_edge),
48            GamepadButton::DPadLeft => (&mut self.dpad_l, &mut self.dpad_l_edge),
49            GamepadButton::DPadUp => (&mut self.dpad_u, &mut self.dpad_u_edge),
50            GamepadButton::DPadRight => (&mut self.dpad_r, &mut self.dpad_r_edge),
51            GamepadButton::DPadDown => (&mut self.dpad_d, &mut self.dpad_d_edge),
52            GamepadButton::LeftShoulder => (&mut self.lb, &mut self.lb_edge),
53            GamepadButton::RightShoulder => (&mut self.rb, &mut self.rb_edge),
54            _ => return,
55        };
56        if pressed && !*held {
57            *edge = true;
58        }
59        *held = pressed;
60    }
61
62    pub fn axis(&mut self, axis: GamepadAxis, value: f32) {
63        match axis {
64            GamepadAxis::LeftStickX => self.lx = value,
65            GamepadAxis::LeftStickY => self.ly = value,
66            GamepadAxis::RightStickX => self.rx = value,
67            GamepadAxis::RightStickY => self.ry = value,
68            GamepadAxis::LeftTrigger => {
69                let held = value > TRIGGER_HELD;
70                if held && !self.lt_held {
71                    self.lt_edge = true;
72                }
73                self.lt_held = held;
74            }
75            GamepadAxis::RightTrigger => {
76                let held = value > TRIGGER_HELD;
77                if held && !self.rt_held {
78                    self.rt_edge = true;
79                }
80                self.rt_held = held;
81            }
82        }
83    }
84
85    pub fn snapshot(&self) -> GamepadState {
86        GamepadState {
87            left_stick: Vec2::new(self.lx, self.ly),
88            right_stick: Vec2::new(self.rx, self.ry),
89            right_trigger_held: self.rt_held,
90            right_trigger_pressed: self.rt_edge,
91            left_trigger_held: self.lt_held,
92            left_trigger_pressed: self.lt_edge,
93            south_pressed: self.south_edge,
94            east_pressed: self.east_edge,
95            west_pressed: self.west_edge,
96            dpad_left_pressed: self.dpad_l_edge,
97            dpad_up_pressed: self.dpad_u_edge,
98            dpad_right_pressed: self.dpad_r_edge,
99            dpad_down_pressed: self.dpad_d_edge,
100            north_pressed: self.north_edge,
101            left_shoulder_held: self.lb,
102            left_shoulder_pressed: self.lb_edge,
103            right_shoulder_held: self.rb,
104            right_shoulder_pressed: self.rb_edge,
105        }
106    }
107
108    pub fn clear_edges(&mut self) {
109        self.lt_edge = false;
110        self.rt_edge = false;
111        self.south_edge = false;
112        self.east_edge = false;
113        self.west_edge = false;
114        self.dpad_l_edge = false;
115        self.dpad_u_edge = false;
116        self.dpad_r_edge = false;
117        self.dpad_d_edge = false;
118        self.north_edge = false;
119        self.lb_edge = false;
120        self.rb_edge = false;
121    }
122}
123
124#[derive(Default)]
125pub struct PadBank {
126    pads: HashMap<GamepadId, PadBridge>,
127}
128
129impl PadBank {
130    pub fn feed(&mut self, events: Vec<GamepadEvent>) {
131        for ev in events {
132            match ev {
133                GamepadEvent::Connected { .. } => {}
134                GamepadEvent::Disconnected { id } => {
135                    self.pads.remove(&id);
136                }
137                GamepadEvent::Button {
138                    id,
139                    button,
140                    pressed,
141                } => {
142                    self.pads.entry(id).or_default().button(button, pressed);
143                }
144                GamepadEvent::Axis { id, axis, value } => {
145                    self.pads.entry(id).or_default().axis(axis, value);
146                }
147            }
148        }
149    }
150
151    /// Snapshots paired with their device id (id-sorted): callers route
152    /// per-player pads before the id is discarded. Prefer this over
153    /// [`PadBank::drain`], which drops the routing key.
154    pub fn drain_with_id(&mut self) -> Vec<(GamepadId, GamepadState)> {
155        let mut ids: Vec<GamepadId> = self.pads.keys().copied().collect();
156        ids.sort_by_key(|id| id.0);
157        let mut out = Vec::with_capacity(ids.len());
158        for id in ids {
159            if let Some(bridge) = self.pads.get_mut(&id) {
160                out.push((id, bridge.snapshot()));
161                bridge.clear_edges();
162            }
163        }
164        out
165    }
166
167    pub fn drain(&mut self) -> Vec<GamepadState> {
168        self.drain_with_id().into_iter().map(|(_, s)| s).collect()
169    }
170
171    pub fn live(&self) -> bool {
172        !self.pads.is_empty()
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    #[test]
181    fn rising_edge_fires_once() {
182        let mut b = PadBridge::default();
183        b.button(GamepadButton::South, true);
184        assert!(b.snapshot().south_pressed);
185        b.clear_edges();
186        b.button(GamepadButton::South, true);
187        assert!(!b.snapshot().south_pressed);
188    }
189
190    #[test]
191    fn trigger_threshold_edges() {
192        let mut b = PadBridge::default();
193        b.axis(GamepadAxis::RightTrigger, 0.8);
194        let s = b.snapshot();
195        assert!(s.right_trigger_held && s.right_trigger_pressed);
196    }
197
198    #[test]
199    fn shoulder_and_dpad_down_buttons_track() {
200        let mut b = PadBridge::default();
201        b.button(GamepadButton::RightShoulder, true);
202        b.button(GamepadButton::LeftShoulder, true);
203        b.button(GamepadButton::DPadDown, true);
204        b.button(GamepadButton::West, true);
205        let s = b.snapshot();
206        assert!(s.right_shoulder_held && s.right_shoulder_pressed);
207        assert!(s.left_shoulder_held && s.left_shoulder_pressed);
208        assert!(s.dpad_down_pressed);
209        assert!(s.west_pressed);
210    }
211
212    #[test]
213    fn drain_with_id_routes_devices() {
214        let mut bank = PadBank::default();
215        let id0 = GamepadId(3);
216        let id1 = GamepadId(1);
217        bank.feed(vec![
218            GamepadEvent::Button {
219                id: id0,
220                button: GamepadButton::South,
221                pressed: true,
222            },
223            GamepadEvent::Button {
224                id: id1,
225                button: GamepadButton::East,
226                pressed: true,
227            },
228        ]);
229        let drained = bank.drain_with_id();
230        assert_eq!(drained.len(), 2);
231        assert_eq!(drained[0].0, id1, "id-sorted");
232        assert!(drained[1].1.south_pressed);
233        assert!(drained[0].1.east_pressed);
234    }
235}