summaryrefslogtreecommitdiff
path: root/src/input.rs
blob: 3eea33c958d475fa2742c8edf2b1d69f150bfd56 (plain)
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()
	}
}