1use std::collections::HashSet;
10
11use crate::{
12 app::SystemStage,
13 ecs::{plugin::Plugin, system::ResMut},
14};
15
16#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
18pub enum GamepadButton {
19 South,
20 East,
21 North,
22 West,
23 C,
24 Z,
25 LeftTrigger,
26 LeftTrigger2,
27 RightTrigger,
28 RightTrigger2,
29 Select,
30 Start,
31 Mode,
32 LeftThumb,
33 RightThumb,
34 DPadUp,
35 DPadDown,
36 DPadLeft,
37 DPadRight,
38 Unknown,
39}
40
41impl From<GamepadButton> for gilrs::Button {
42 fn from(value: GamepadButton) -> Self {
43 match value {
44 GamepadButton::South => Self::South,
45 GamepadButton::East => Self::East,
46 GamepadButton::North => Self::North,
47 GamepadButton::West => Self::West,
48 GamepadButton::C => Self::C,
49 GamepadButton::Z => Self::Z,
50 GamepadButton::LeftTrigger => Self::LeftTrigger,
51 GamepadButton::LeftTrigger2 => Self::LeftTrigger2,
52 GamepadButton::RightTrigger => Self::RightTrigger,
53 GamepadButton::RightTrigger2 => Self::RightTrigger2,
54 GamepadButton::Select => Self::Select,
55 GamepadButton::Start => Self::Start,
56 GamepadButton::Mode => Self::Mode,
57 GamepadButton::LeftThumb => Self::LeftThumb,
58 GamepadButton::RightThumb => Self::RightThumb,
59 GamepadButton::DPadUp => Self::DPadUp,
60 GamepadButton::DPadDown => Self::DPadDown,
61 GamepadButton::DPadLeft => Self::DPadLeft,
62 GamepadButton::DPadRight => Self::DPadRight,
63 GamepadButton::Unknown => Self::Unknown,
64 }
65 }
66}
67
68impl From<gilrs::Button> for GamepadButton {
69 fn from(value: gilrs::Button) -> Self {
70 match value {
71 gilrs::Button::South => Self::South,
72 gilrs::Button::East => Self::East,
73 gilrs::Button::North => Self::North,
74 gilrs::Button::West => Self::West,
75 gilrs::Button::C => Self::C,
76 gilrs::Button::Z => Self::Z,
77 gilrs::Button::LeftTrigger => Self::LeftTrigger,
78 gilrs::Button::LeftTrigger2 => Self::LeftTrigger2,
79 gilrs::Button::RightTrigger => Self::RightTrigger,
80 gilrs::Button::RightTrigger2 => Self::RightTrigger2,
81 gilrs::Button::Select => Self::Select,
82 gilrs::Button::Start => Self::Start,
83 gilrs::Button::Mode => Self::Mode,
84 gilrs::Button::LeftThumb => Self::LeftThumb,
85 gilrs::Button::RightThumb => Self::RightThumb,
86 gilrs::Button::DPadUp => Self::DPadUp,
87 gilrs::Button::DPadDown => Self::DPadDown,
88 gilrs::Button::DPadLeft => Self::DPadLeft,
89 gilrs::Button::DPadRight => Self::DPadRight,
90 _ => Self::Unknown,
91 }
92 }
93}
94
95#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
97pub enum GamepadAxis {
98 LeftStickX,
99 LeftStickY,
100 LeftZ,
101 RightStickX,
102 RightStickY,
103 RightZ,
104 DPadX,
105 DPadY,
106 Unknown,
107}
108
109impl From<GamepadAxis> for gilrs::Axis {
110 fn from(value: GamepadAxis) -> Self {
111 match value {
112 GamepadAxis::LeftStickX => Self::LeftStickX,
113 GamepadAxis::LeftStickY => Self::LeftStickY,
114 GamepadAxis::LeftZ => Self::LeftZ,
115 GamepadAxis::RightStickX => Self::RightStickX,
116 GamepadAxis::RightStickY => Self::RightStickY,
117 GamepadAxis::RightZ => Self::RightZ,
118 GamepadAxis::DPadX => Self::DPadX,
119 GamepadAxis::DPadY => Self::DPadY,
120 GamepadAxis::Unknown => Self::Unknown,
121 }
122 }
123}
124
125#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
130pub struct GamepadId(gilrs::GamepadId);
131
132pub struct Gamepads {
135 gilrs: std::sync::Mutex<gilrs::Gilrs>,
142 pressed_this_tick: HashSet<(GamepadId, GamepadButton)>,
149 released_this_tick: HashSet<(GamepadId, GamepadButton)>,
150}
151
152impl Gamepads {
153 fn new() -> Result<Self, Box<gilrs::Error>> {
154 Ok(Self {
155 gilrs: std::sync::Mutex::new(gilrs::Gilrs::new().map_err(Box::new)?),
156 pressed_this_tick: HashSet::new(),
157 released_this_tick: HashSet::new(),
158 })
159 }
160
161 fn tick(&mut self) {
162 self.pressed_this_tick.clear();
163 self.released_this_tick.clear();
164 let mut gilrs = self.gilrs.lock().unwrap();
165 while let Some(event) = gilrs.next_event() {
166 let id = GamepadId(event.id);
167 match event.event {
168 gilrs::EventType::ButtonPressed(button, _) => {
169 self.pressed_this_tick.insert((id, button.into()));
170 }
171 gilrs::EventType::ButtonReleased(button, _) => {
172 self.released_this_tick.insert((id, button.into()));
173 }
174 _ => {}
175 }
176 }
177 }
178
179 pub fn ids(&self) -> Vec<GamepadId> {
181 self.gilrs.lock().unwrap().gamepads().map(|(id, _)| GamepadId(id)).collect()
182 }
183
184 pub fn is_connected(&self, id: GamepadId) -> bool {
185 self.gilrs.lock().unwrap().connected_gamepad(id.0).is_some()
186 }
187
188 pub fn button_held(&self, id: GamepadId, button: GamepadButton) -> bool {
191 self.gilrs.lock().unwrap().connected_gamepad(id.0).is_some_and(|gamepad| gamepad.is_pressed(button.into()))
192 }
193
194 pub fn button_pressed(&self, id: GamepadId, button: GamepadButton) -> bool {
196 self.pressed_this_tick.contains(&(id, button))
197 }
198
199 pub fn button_released(&self, id: GamepadId, button: GamepadButton) -> bool {
201 self.released_this_tick.contains(&(id, button))
202 }
203
204 pub fn axis(&self, id: GamepadId, axis: GamepadAxis) -> f32 {
207 self.gilrs.lock().unwrap().connected_gamepad(id.0).map_or(0.0, |gamepad| gamepad.value(axis.into()))
208 }
209}
210
211fn tick_gamepads(mut gamepads: ResMut<Gamepads>) {
212 gamepads.tick();
213}
214
215pub struct GamepadPlugin;
229
230impl GamepadPlugin {
231 pub fn new() -> Self {
232 Self
233 }
234}
235
236impl Default for GamepadPlugin {
237 fn default() -> Self {
238 Self::new()
239 }
240}
241
242struct GamepadPluginRan;
246
247impl Plugin for GamepadPlugin {
248 fn build(&self, app: &mut crate::prelude::App) {
249 if !app.try_insert_resource(GamepadPluginRan) {
250 return;
251 }
252 match Gamepads::new() {
253 Ok(gamepads) => {
254 app.add_resource(gamepads);
255 app.add_system(SystemStage::PreUpdate, tick_gamepads);
256 }
257 Err(e) => tracing::error!("GamepadPlugin: failed to initialize gamepad backend: {e}"),
258 }
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265
266 #[test]
267 fn every_gamepad_button_round_trips_through_the_gilrs_conversion() {
268 let all = [
269 GamepadButton::South,
270 GamepadButton::East,
271 GamepadButton::North,
272 GamepadButton::West,
273 GamepadButton::C,
274 GamepadButton::Z,
275 GamepadButton::LeftTrigger,
276 GamepadButton::LeftTrigger2,
277 GamepadButton::RightTrigger,
278 GamepadButton::RightTrigger2,
279 GamepadButton::Select,
280 GamepadButton::Start,
281 GamepadButton::Mode,
282 GamepadButton::LeftThumb,
283 GamepadButton::RightThumb,
284 GamepadButton::DPadUp,
285 GamepadButton::DPadDown,
286 GamepadButton::DPadLeft,
287 GamepadButton::DPadRight,
288 GamepadButton::Unknown,
289 ];
290 for button in all {
291 let gilrs_button: gilrs::Button = button.into();
292 assert_eq!(GamepadButton::from(gilrs_button), button);
293 }
294 }
295}