1#![forbid(unsafe_code)]
9
10use std::collections::{HashMap, HashSet};
11
12pub use winit::keyboard::KeyCode as Key;
13
14use gilrs::ff::{BaseEffect, BaseEffectType, EffectBuilder, Replay, Ticks};
15pub use gilrs::{Axis, Button, GamepadId};
16use gilrs::{Event, Gilrs};
17
18#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
19pub enum MouseButton {
20 Left,
21 Right,
22 Middle,
23 Other(u16),
24}
25
26impl From<winit::event::MouseButton> for MouseButton {
27 fn from(b: winit::event::MouseButton) -> Self {
28 match b {
29 winit::event::MouseButton::Left => MouseButton::Left,
30 winit::event::MouseButton::Right => MouseButton::Right,
31 winit::event::MouseButton::Middle => MouseButton::Middle,
32 winit::event::MouseButton::Back => MouseButton::Other(3),
33 winit::event::MouseButton::Forward => MouseButton::Other(4),
34 winit::event::MouseButton::Other(n) => MouseButton::Other(n),
35 }
36 }
37}
38
39pub struct Gamepad {
42 id: GamepadId,
43 name: String,
44 connected: bool,
45 held: HashSet<Button>,
46 pressed: HashSet<Button>,
47 released: HashSet<Button>,
48 axes: HashMap<Axis, f32>,
49}
50
51impl Gamepad {
52 fn new(id: GamepadId) -> Self {
53 Self {
54 id,
55 name: String::new(),
56 connected: true,
57 held: HashSet::new(),
58 pressed: HashSet::new(),
59 released: HashSet::new(),
60 axes: HashMap::new(),
61 }
62 }
63
64 pub fn id(&self) -> GamepadId {
65 self.id
66 }
67 pub fn name(&self) -> &str {
68 &self.name
69 }
70 pub fn is_connected(&self) -> bool {
71 self.connected
72 }
73 pub fn button_held(&self, b: Button) -> bool {
74 self.held.contains(&b)
75 }
76 pub fn button_pressed(&self, b: Button) -> bool {
77 self.pressed.contains(&b)
78 }
79 pub fn button_released(&self, b: Button) -> bool {
80 self.released.contains(&b)
81 }
82 pub fn axis(&self, axis: Axis) -> f32 {
84 self.axes.get(&axis).copied().unwrap_or(0.0)
85 }
86}
87
88#[derive(Default)]
89pub struct Input {
90 keys_held: HashSet<Key>,
91 keys_pressed: HashSet<Key>,
92 keys_released: HashSet<Key>,
93 mouse_held: HashSet<MouseButton>,
94 mouse_pressed: HashSet<MouseButton>,
95 mouse_released: HashSet<MouseButton>,
96 mouse_pos: (f32, f32),
97 mouse_delta: (f32, f32),
98 scroll: (f32, f32),
99 gilrs: Option<Gilrs>,
101 gamepads: HashMap<GamepadId, Gamepad>,
102 rumble: Vec<gilrs::ff::Effect>,
104}
105
106impl Input {
107 pub fn new() -> Self {
108 let mut me = Self::default();
109 match Gilrs::new() {
110 Ok(g) => {
111 for (id, gp) in g.gamepads() {
114 let mut pad = Gamepad::new(id);
115 pad.name = gp.name().to_string();
116 me.gamepads.insert(id, pad);
117 }
118 me.gilrs = Some(g);
119 }
120 Err(e) => log::warn!("gamepad backend unavailable, continuing without it: {e}"),
121 }
122 me
123 }
124
125 pub fn gamepads(&self) -> impl Iterator<Item = &Gamepad> {
127 self.gamepads.values().filter(|g| g.connected)
128 }
129
130 pub fn first_gamepad(&self) -> Option<&Gamepad> {
132 self.gamepads.values().find(|g| g.connected)
133 }
134
135 pub fn gamepad(&self, id: GamepadId) -> Option<&Gamepad> {
137 self.gamepads.get(&id).filter(|g| g.connected)
138 }
139
140 pub fn poll_gamepads(&mut self) {
143 let Some(gilrs) = self.gilrs.as_mut() else {
144 return;
145 };
146 while let Some(Event { id, event, .. }) = gilrs.next_event() {
147 use gilrs::EventType::*;
148 let pad = self.gamepads.entry(id).or_insert_with(|| Gamepad::new(id));
149 match event {
150 Connected => {
151 pad.connected = true;
152 pad.name = gilrs.gamepad(id).name().to_string();
153 }
154 Disconnected | Dropped => {
155 pad.connected = false;
156 pad.held.clear();
157 pad.axes.clear();
158 }
159 ButtonPressed(b, _) => {
160 pad.held.insert(b);
161 pad.pressed.insert(b);
162 }
163 ButtonReleased(b, _) => {
164 pad.held.remove(&b);
165 pad.released.insert(b);
166 }
167 AxisChanged(axis, value, _) => {
168 pad.axes.insert(axis, value);
169 }
170 _ => {}
171 }
172 }
173 }
174
175 pub fn set_rumble(&mut self, id: GamepadId, magnitude: f32, duration_ms: u32) {
178 let Some(gilrs) = self.gilrs.as_mut() else {
179 return;
180 };
181 if !gilrs
182 .connected_gamepad(id)
183 .is_some_and(|g| g.is_ff_supported())
184 {
185 return;
186 }
187 let mag = (magnitude.clamp(0.0, 1.0) * f32::from(u16::MAX)) as u16;
188 let effect = EffectBuilder::new()
189 .add_effect(BaseEffect {
190 kind: BaseEffectType::Strong { magnitude: mag },
191 scheduling: Replay {
192 play_for: Ticks::from_ms(duration_ms),
193 ..Default::default()
194 },
195 envelope: Default::default(),
196 })
197 .gamepads(&[id])
198 .finish(gilrs);
199 if let Ok(effect) = effect {
200 let _ = effect.play();
201 self.rumble.push(effect);
204 if self.rumble.len() > 16 {
205 self.rumble.remove(0);
206 }
207 }
208 }
209
210 pub fn key_held(&self, k: Key) -> bool {
211 self.keys_held.contains(&k)
212 }
213 pub fn key_pressed(&self, k: Key) -> bool {
214 self.keys_pressed.contains(&k)
215 }
216 pub fn key_released(&self, k: Key) -> bool {
217 self.keys_released.contains(&k)
218 }
219
220 pub fn mouse_held(&self, b: MouseButton) -> bool {
221 self.mouse_held.contains(&b)
222 }
223 pub fn mouse_pressed(&self, b: MouseButton) -> bool {
224 self.mouse_pressed.contains(&b)
225 }
226 pub fn mouse_released(&self, b: MouseButton) -> bool {
227 self.mouse_released.contains(&b)
228 }
229
230 pub fn mouse_pos(&self) -> (f32, f32) {
231 self.mouse_pos
232 }
233 pub fn mouse_delta(&self) -> (f32, f32) {
234 self.mouse_delta
235 }
236 pub fn scroll(&self) -> (f32, f32) {
237 self.scroll
238 }
239
240 pub fn handle_window_event(&mut self, event: &winit::event::WindowEvent) {
241 use winit::event::{ElementState, MouseScrollDelta, WindowEvent};
242 match event {
243 WindowEvent::KeyboardInput { event, .. } => {
244 let winit::keyboard::PhysicalKey::Code(code) = event.physical_key else {
245 return;
246 };
247 match event.state {
248 ElementState::Pressed => {
249 if self.keys_held.insert(code) {
250 self.keys_pressed.insert(code);
251 }
252 }
253 ElementState::Released => {
254 if self.keys_held.remove(&code) {
255 self.keys_released.insert(code);
256 }
257 }
258 }
259 }
260 WindowEvent::MouseInput { state, button, .. } => {
261 let b = MouseButton::from(*button);
262 match state {
263 ElementState::Pressed => {
264 if self.mouse_held.insert(b) {
265 self.mouse_pressed.insert(b);
266 }
267 }
268 ElementState::Released => {
269 if self.mouse_held.remove(&b) {
270 self.mouse_released.insert(b);
271 }
272 }
273 }
274 }
275 WindowEvent::CursorMoved { position, .. } => {
276 let new = (position.x as f32, position.y as f32);
277 self.mouse_delta.0 += new.0 - self.mouse_pos.0;
278 self.mouse_delta.1 += new.1 - self.mouse_pos.1;
279 self.mouse_pos = new;
280 }
281 WindowEvent::MouseWheel { delta, .. } => match delta {
282 MouseScrollDelta::LineDelta(x, y) => {
283 self.scroll.0 += x;
284 self.scroll.1 += y;
285 }
286 MouseScrollDelta::PixelDelta(p) => {
287 self.scroll.0 += p.x as f32;
288 self.scroll.1 += p.y as f32;
289 }
290 },
291 WindowEvent::Focused(false) => {
292 self.keys_held.clear();
293 self.mouse_held.clear();
294 }
295 _ => {}
296 }
297 }
298
299 pub fn end_frame(&mut self) {
301 self.keys_pressed.clear();
302 self.keys_released.clear();
303 self.mouse_pressed.clear();
304 self.mouse_released.clear();
305 self.mouse_delta = (0.0, 0.0);
306 self.scroll = (0.0, 0.0);
307 for pad in self.gamepads.values_mut() {
308 pad.pressed.clear();
309 pad.released.clear();
310 }
311 }
312}