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<KeyCode>,
mouse_clicked: bool,
mouse_location: Option<Position>,
scroll_direction: Option<Direction>,
is_focused: bool,
is_focused_this_frame: bool,
is_unfocused_this_frame: bool,
gil_cx: Option<Gilrs>,
gamepad_map: FxHashMap<usize, GamepadId>,
gamepad_map_rev: FxHashMap<GamepadId, usize>,
connected_gamepads: VecDeque<usize>,
pressed_buttons: FxHashSet<ControllerButton>,
released_buttons: FxHashSet<ControllerButton>,
}
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<Position> {
self.mouse_location
}
pub fn scroll_direction_this_frame(&self) -> Option<Direction> {
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<usize> {
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<f32> {
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<PowerInfo> {
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()
}
}
|