use std::collections::HashSet;
use winit::event::{ElementState, MouseButton, MouseScrollDelta, WindowEvent};
use winit::keyboard::{Key, NamedKey};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TextInputFrameEvent {
InsertText(String),
Backspace,
DeleteForward,
MoveCaretLeft,
MoveCaretRight,
}
#[derive(Default, Debug, Clone)]
pub struct InputState {
pub keys_down: HashSet<Key>,
pub keys_pressed: HashSet<Key>,
pub keys_released: HashSet<Key>,
pub mouse_down: HashSet<MouseButton>,
pub mouse_pressed: HashSet<MouseButton>,
pub mouse_released: HashSet<MouseButton>,
pub cursor_pos: Option<(f32, f32)>,
prev_cursor_pos: Option<(f32, f32)>,
mouse_movement: (f32, f32),
mouse_dragging: bool,
mouse_drag_delta: (f32, f32),
pub wheel_delta: (f32, f32),
text_input_events: Vec<TextInputFrameEvent>,
}
impl InputState {
pub fn start_frame(&mut self) {
self.mouse_movement = match (self.cursor_pos, self.prev_cursor_pos) {
(Some((cx, cy)), Some((px, py))) => (cx - px, cy - py),
_ => (0.0, 0.0),
};
self.prev_cursor_pos = self.cursor_pos;
let any_button_down = !self.mouse_down.is_empty();
let moved = self.mouse_movement.0 != 0.0 || self.mouse_movement.1 != 0.0;
self.mouse_dragging = any_button_down && moved;
self.mouse_drag_delta = if self.mouse_dragging {
self.mouse_movement
} else {
(0.0, 0.0)
};
}
pub fn end_frame(&mut self) {
self.keys_pressed.clear();
self.keys_released.clear();
self.mouse_pressed.clear();
self.mouse_released.clear();
self.wheel_delta = (0.0, 0.0);
self.text_input_events.clear();
}
#[inline]
pub fn key_down(&self, key: &Key) -> bool {
self.keys_down.contains(key)
}
#[inline]
pub fn key_pressed(&self, key: &Key) -> bool {
self.keys_pressed.contains(key)
}
#[inline]
pub fn key_released(&self, key: &Key) -> bool {
self.keys_released.contains(key)
}
#[inline]
pub fn mouse_movement(&self) -> (f32, f32) {
self.mouse_movement
}
#[inline]
pub fn mouse_dragging(&self) -> bool {
self.mouse_dragging
}
#[inline]
pub fn mouse_drag_delta(&self) -> (f32, f32) {
self.mouse_drag_delta
}
#[inline]
pub fn mouse_dragging_button(&self, button: MouseButton) -> bool {
self.mouse_down.contains(&button)
&& (self.mouse_movement.0 != 0.0 || self.mouse_movement.1 != 0.0)
}
#[inline]
pub fn mouse_drag_delta_button(&self, button: MouseButton) -> (f32, f32) {
if self.mouse_dragging_button(button) {
self.mouse_movement
} else {
(0.0, 0.0)
}
}
#[inline]
pub fn text_input_events(&self) -> &[TextInputFrameEvent] {
&self.text_input_events
}
}
#[derive(Default, Debug, Clone)]
pub struct UserInput {
state: InputState,
}
impl UserInput {
pub fn new() -> Self {
Self::default()
}
pub fn state(&self) -> &InputState {
&self.state
}
pub fn state_mut(&mut self) -> &mut InputState {
&mut self.state
}
pub fn start_frame(&mut self) {
self.state.start_frame();
}
pub fn end_frame(&mut self) {
self.state.end_frame();
}
pub fn handle_window_event(&mut self, event: &WindowEvent) -> bool {
match event {
WindowEvent::KeyboardInput { event, .. } => {
fn normalize_key(key: &Key) -> Key {
match key {
Key::Character(s) => {
if s.len() == 1 {
let c = s.chars().next().unwrap_or('\0');
if c.is_ascii_alphabetic() {
return Key::Character(
c.to_ascii_lowercase().to_string().into(),
);
}
}
Key::Character(s.clone())
}
_ => key.clone(),
}
}
let key = normalize_key(&event.logical_key);
match event.state {
ElementState::Pressed => {
let was_down = self.state.keys_down.contains(&key);
self.state.keys_down.insert(key.clone());
if !was_down {
self.state.keys_pressed.insert(key);
}
match &event.logical_key {
Key::Named(NamedKey::Backspace) => {
self.state
.text_input_events
.push(TextInputFrameEvent::Backspace);
}
Key::Named(NamedKey::Delete) => {
self.state
.text_input_events
.push(TextInputFrameEvent::DeleteForward);
}
Key::Named(NamedKey::ArrowLeft) => {
self.state
.text_input_events
.push(TextInputFrameEvent::MoveCaretLeft);
}
Key::Named(NamedKey::ArrowRight) => {
self.state
.text_input_events
.push(TextInputFrameEvent::MoveCaretRight);
}
_ => {}
}
if let Some(text) = event.text.as_ref() {
let filtered: String =
text.chars().filter(|c| !c.is_control()).collect();
if !filtered.is_empty() {
self.state
.text_input_events
.push(TextInputFrameEvent::InsertText(filtered));
}
}
}
ElementState::Released => {
self.state.keys_down.remove(&key);
self.state.keys_released.insert(key);
}
}
true
}
WindowEvent::MouseInput { state, button, .. } => {
match state {
ElementState::Pressed => {
let was_down = self.state.mouse_down.contains(button);
self.state.mouse_down.insert(*button);
if !was_down {
self.state.mouse_pressed.insert(*button);
}
}
ElementState::Released => {
self.state.mouse_down.remove(button);
self.state.mouse_released.insert(*button);
}
}
true
}
WindowEvent::CursorMoved { position, .. } => {
self.state.cursor_pos = Some((position.x as f32, position.y as f32));
true
}
WindowEvent::MouseWheel { delta, .. } => {
let (dx, dy) = match delta {
MouseScrollDelta::LineDelta(x, y) => (*x, *y),
MouseScrollDelta::PixelDelta(pos) => (pos.x as f32, pos.y as f32),
};
self.state.wheel_delta.0 += dx;
self.state.wheel_delta.1 += dy;
true
}
_ => false,
}
}
}