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    dpad_l: bool,
20    dpad_u: bool,
21    dpad_r: bool,
22    north: bool,
23    lt_edge: bool,
24    rt_edge: bool,
25    south_edge: bool,
26    east_edge: bool,
27    dpad_l_edge: bool,
28    dpad_u_edge: bool,
29    dpad_r_edge: bool,
30    north_edge: bool,
31}
32
33impl PadBridge {
34    pub fn button(&mut self, button: GamepadButton, pressed: bool) {
35        let (held, edge) = match button {
36            GamepadButton::South => (&mut self.south, &mut self.south_edge),
37            GamepadButton::East => (&mut self.east, &mut self.east_edge),
38            GamepadButton::North => (&mut self.north, &mut self.north_edge),
39            GamepadButton::DPadLeft => (&mut self.dpad_l, &mut self.dpad_l_edge),
40            GamepadButton::DPadUp => (&mut self.dpad_u, &mut self.dpad_u_edge),
41            GamepadButton::DPadRight => (&mut self.dpad_r, &mut self.dpad_r_edge),
42            _ => return,
43        };
44        if pressed && !*held {
45            *edge = true;
46        }
47        *held = pressed;
48    }
49
50    pub fn axis(&mut self, axis: GamepadAxis, value: f32) {
51        match axis {
52            GamepadAxis::LeftStickX => self.lx = value,
53            GamepadAxis::LeftStickY => self.ly = value,
54            GamepadAxis::RightStickX => self.rx = value,
55            GamepadAxis::RightStickY => self.ry = value,
56            GamepadAxis::LeftTrigger => {
57                let held = value > TRIGGER_HELD;
58                if held && !self.lt_held {
59                    self.lt_edge = true;
60                }
61                self.lt_held = held;
62            }
63            GamepadAxis::RightTrigger => {
64                let held = value > TRIGGER_HELD;
65                if held && !self.rt_held {
66                    self.rt_edge = true;
67                }
68                self.rt_held = held;
69            }
70        }
71    }
72
73    pub fn snapshot(&self) -> GamepadState {
74        GamepadState {
75            left_stick: Vec2::new(self.lx, self.ly),
76            right_stick: Vec2::new(self.rx, self.ry),
77            right_trigger_held: self.rt_held,
78            right_trigger_pressed: self.rt_edge,
79            left_trigger_held: self.lt_held,
80            left_trigger_pressed: self.lt_edge,
81            south_pressed: self.south_edge,
82            east_pressed: self.east_edge,
83            dpad_left_pressed: self.dpad_l_edge,
84            dpad_up_pressed: self.dpad_u_edge,
85            dpad_right_pressed: self.dpad_r_edge,
86            north_pressed: self.north_edge,
87        }
88    }
89
90    pub fn clear_edges(&mut self) {
91        self.lt_edge = false;
92        self.rt_edge = false;
93        self.south_edge = false;
94        self.east_edge = false;
95        self.dpad_l_edge = false;
96        self.dpad_u_edge = false;
97        self.dpad_r_edge = false;
98        self.north_edge = false;
99    }
100}
101
102#[derive(Default)]
103pub struct PadBank {
104    pads: HashMap<GamepadId, PadBridge>,
105}
106
107impl PadBank {
108    pub fn feed(&mut self, events: Vec<GamepadEvent>) {
109        for ev in events {
110            match ev {
111                GamepadEvent::Connected { .. } => {}
112                GamepadEvent::Disconnected { id } => {
113                    // Drop the bridge: a stale handle never aliases a new
114                    // device, and held buttons do not strand (the entry is
115                    // gone, so no snapshot reports them held). Reconnect
116                    // starts from a released zero state.
117                    self.pads.remove(&id);
118                }
119                GamepadEvent::Button { id, button, pressed } => {
120                    self.pads.entry(id).or_default().button(button, pressed);
121                }
122                GamepadEvent::Axis { id, axis, value } => {
123                    self.pads.entry(id).or_default().axis(axis, value);
124                }
125            }
126        }
127    }
128
129    pub fn drain(&mut self) -> Vec<GamepadState> {
130        let mut ids: Vec<GamepadId> = self.pads.keys().copied().collect();
131        ids.sort_by_key(|id| id.0);
132        let mut out = Vec::with_capacity(ids.len());
133        for id in ids {
134            if let Some(bridge) = self.pads.get_mut(&id) {
135                out.push(bridge.snapshot());
136                bridge.clear_edges();
137            }
138        }
139        out
140    }
141
142    pub fn live(&self) -> bool {
143        !self.pads.is_empty()
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[test]
152    fn rising_edge_fires_once() {
153        let mut b = PadBridge::default();
154        b.button(GamepadButton::South, true);
155        assert!(b.snapshot().south_pressed);
156        b.clear_edges();
157        b.button(GamepadButton::South, true);
158        assert!(!b.snapshot().south_pressed);
159    }
160
161    #[test]
162    fn trigger_threshold_edges() {
163        let mut b = PadBridge::default();
164        b.axis(GamepadAxis::RightTrigger, 0.8);
165        let s = b.snapshot();
166        assert!(s.right_trigger_held && s.right_trigger_pressed);
167    }
168}