use std::{collections::VecDeque, fmt::Debug, time::Duration}; use crossterm::event::KeyCode; use gilrs::{ Axis, Button, GamepadId, Gilrs, PowerInfo, ff::{BaseEffect, Replay, Ticks}, }; use rustc_hash::{FxHashMap, FxHashSet}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Direction { Up = 1, Down, Left, Right, } #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] pub struct Position { pub x: u16, pub y: u16, } #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] struct ControllerButton { gamepad_id: usize, button: Button, } #[derive(Debug)] pub struct InputManager { pressed_keys: FxHashSet, mouse_clicked: bool, mouse_location: Option, scroll_direction: Option, is_focused: bool, is_focused_this_frame: bool, is_unfocused_this_frame: bool, gil_cx: Option, gamepad_map: FxHashMap, gamepad_map_rev: FxHashMap, connected_gamepads: VecDeque, pressed_buttons: FxHashSet, released_buttons: FxHashSet, } impl InputManager { pub fn new() -> Self { Self { pressed_keys: FxHashSet::default(), mouse_clicked: false, mouse_location: None, scroll_direction: None, is_focused: true, is_focused_this_frame: false, is_unfocused_this_frame: false, gil_cx: Gilrs::new().ok(), gamepad_map: FxHashMap::default(), gamepad_map_rev: FxHashMap::default(), connected_gamepads: VecDeque::new(), pressed_buttons: FxHashSet::default(), released_buttons: FxHashSet::default(), } } pub fn press_key(&mut self, key_code: KeyCode) { self.pressed_keys.insert(key_code); } pub fn click_mouse(&mut self) { self.mouse_clicked = true; } pub fn move_mouse(&mut self, location: Position) { self.mouse_location = Some(location); } pub fn scroll(&mut self, direction: Direction) { self.scroll_direction = Some(direction); } pub fn reset_next_frame(&mut self) { puffin::profile_function!(); self.pressed_keys.clear(); self.mouse_clicked = false; self.scroll_direction = None; self.is_focused_this_frame = false; self.is_unfocused_this_frame = false; self.pressed_buttons.clear(); self.released_buttons.clear(); self.connected_gamepads.pop_front(); } pub fn is_key_pressed_this_frame(&self, key_code: KeyCode) -> bool { self.pressed_keys.contains(&key_code) } pub fn is_mouse_clicked_this_frame(&self) -> bool { self.mouse_clicked } pub fn mouse_location(&self) -> Option { self.mouse_location } pub fn scroll_direction_this_frame(&self) -> Option { self.scroll_direction } pub fn read_gil_events(&mut self) { let Some(gil) = self.gil_cx.as_mut() else { return; }; while let Some(event) = gil.next_event() { let gamepad_id = self.gamepad_map_rev.get(&event.id).copied(); match event.event { gilrs::EventType::Connected => { let gamepad_id = self.gamepad_map.len() + 1; self.gamepad_map.insert(gamepad_id, event.id); self.gamepad_map_rev.insert(event.id, gamepad_id); self.connected_gamepads.push_back(gamepad_id); } gilrs::EventType::ButtonPressed(button, _) => { let Some(gamepad_id) = gamepad_id else { continue; }; self.pressed_buttons .insert(ControllerButton { gamepad_id, button }); } gilrs::EventType::ButtonReleased(button, _) => { let Some(gamepad_id) = gamepad_id else { continue; }; self.released_buttons .insert(ControllerButton { gamepad_id, button }); } _ => {} } } } pub fn is_gamepad_connected_this_frame(&self) -> Option { self.connected_gamepads.front().copied() } pub fn is_button_pressed_this_frame(&self, gamepad_id: usize, button: Button) -> bool { self.pressed_buttons .contains(&ControllerButton { gamepad_id, button }) } pub fn is_button_released_this_frame(&self, gamepad_id: usize, button: Button) -> bool { self.released_buttons .contains(&ControllerButton { gamepad_id, button }) } pub fn is_button_down(&self, gamepad_id: usize, button: Button) -> bool { self.gamepad_map.get(&gamepad_id).is_some_and(|id| { self.gil_cx .as_ref() .is_some_and(|gil| gil.gamepad(*id).is_pressed(button)) }) } pub fn axis_value(&self, gamepad_id: usize, axis: Axis) -> Option { self.gamepad_map .get(&gamepad_id) .and_then(|id| self.gil_cx.as_ref().map(|gil| gil.gamepad(*id).value(axis))) } pub fn is_gamepad_connected(&self, gamepad_id: usize) -> bool { self.gamepad_map.get(&gamepad_id).is_some_and(|id| { self.gil_cx .as_ref() .is_some_and(|gil| gil.gamepad(*id).is_connected()) }) } pub fn gamepad_power_info(&self, gamepad_id: usize) -> Option { self.gamepad_map.get(&gamepad_id).and_then(|id| { self.gil_cx .as_ref() .map(|gil| gil.gamepad(*id).power_info()) }) } pub fn vibrate(&mut self, gamepad_id: usize, intensity: f32, duration: Duration) { let Some(gamepad_id) = self.gamepad_map.get(&gamepad_id) else { return; }; let Some(gil) = self.gil_cx.as_mut() else { return; }; let gamepad = gil.gamepad(*gamepad_id); let Ok(effect) = gilrs::ff::EffectBuilder::new() .add_gamepad(&gamepad) .add_effect(BaseEffect { kind: gilrs::ff::BaseEffectType::Strong { magnitude: (intensity * u16::MAX as f32).round() as u16, }, scheduling: Replay { after: Ticks::from_ms(0), play_for: Ticks::from_ms(duration.as_millis() as u32), with_delay: Ticks::from_ms(0), }, envelope: todo!(), }) .repeat(gilrs::ff::Repeat::For(Ticks::from_ms( duration.as_millis() as u32 ))) .finish(gil) else { return; }; _ = effect.play(); } pub fn focus(&mut self) { self.is_focused = true; self.is_focused_this_frame = true; } pub fn unfocus(&mut self) { self.is_focused = false; self.is_unfocused_this_frame = true; } pub fn is_focused(&self) -> bool { self.is_focused } pub fn was_focused_this_frame(&self) -> bool { self.is_focused_this_frame } pub fn was_unfocused_this_frame(&self) -> bool { self.is_unfocused_this_frame } } impl Default for InputManager { fn default() -> Self { Self::new() } }