Implement MouseMotion and CursorMoved input events

This commit is contained in:
SeanOMik 2023-07-16 00:39:54 -04:00
parent e517852b25
commit 5a37fcf1e6
Signed by: SeanOMik
GPG Key ID: 568F326C7EB33ACB
10 changed files with 260 additions and 104 deletions

20
Cargo.lock generated
View File

@ -255,7 +255,7 @@ checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.25",
"syn 2.0.26",
]
[[package]]
@ -369,7 +369,7 @@ checksum = "fdde5c9cd29ebd706ce1b35600920a33550e402fc998a2e53ad3b42c3c47a192"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.25",
"syn 2.0.26",
]
[[package]]
@ -1044,7 +1044,9 @@ dependencies = [
"image",
"instant",
"petgraph",
"quote",
"resources",
"syn 2.0.26",
"tobj",
"tracing",
"tracing-log",
@ -1312,7 +1314,7 @@ dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 2.0.25",
"syn 2.0.26",
]
[[package]]
@ -1776,9 +1778,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.25"
version = "2.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2"
checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970"
dependencies = [
"proc-macro2",
"quote",
@ -1811,7 +1813,7 @@ checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.25",
"syn 2.0.26",
]
[[package]]
@ -1895,7 +1897,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.25",
"syn 2.0.26",
]
[[package]]
@ -2020,7 +2022,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.25",
"syn 2.0.26",
"wasm-bindgen-shared",
]
@ -2054,7 +2056,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.25",
"syn 2.0.26",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]

View File

@ -26,3 +26,5 @@ glam = { version = "0.24.0", features = ["bytemuck"] }
petgraph = "0.6.3"
resources = "1.1.0"
gilrs-core = "0.5.6"
syn = "2.0.26"
quote = "1.0.29"

View File

@ -1,6 +1,5 @@
use std::collections::HashMap;
use async_trait::async_trait;
use petgraph::{prelude::StableDiGraph, stable_graph::NodeIndex, visit::Topo};
use tracing::warn;
@ -8,6 +7,7 @@ use crate::game::Controls;
pub mod components;
pub mod world;
pub mod resources;
use world::World;

31
src/ecs/resources/mod.rs Normal file
View File

@ -0,0 +1,31 @@
use std::{any::Any, collections::VecDeque};
use winit::event::WindowEvent;
pub mod window_state;
pub use window_state::*;
pub trait Resource: Send + Sync + 'static {
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
}
impl Resource for WindowEvent<'static> {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
impl<T: Sync + Send + 'static> Resource for VecDeque<T> {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}

View File

@ -0,0 +1,25 @@
use std::any::Any;
use super::Resource;
#[derive(Clone, Default)]
pub struct WindowState {
pub is_focused: bool,
pub is_cursor_inside_window: bool,
}
impl WindowState {
pub fn new() -> Self {
Self::default()
}
}
impl Resource for WindowState {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}

View File

@ -1,31 +1,6 @@
use std::{any::{TypeId, Any}, collections::{HashMap, HashSet, VecDeque}, ops::{Deref, DerefMut}};
use std::{any::{TypeId, Any}, collections::{HashMap, HashSet}, ops::{Deref, DerefMut}};
use winit::event::WindowEvent;
pub trait Resource: Send + Sync + 'static {
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
}
impl Resource for WindowEvent<'static> {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
impl<T: Sync + Send + 'static> Resource for VecDeque<T> {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
use super::resources::Resource;
pub struct World {
inner: hecs::World,

View File

@ -12,9 +12,9 @@ use tracing_subscriber::{
util::SubscriberInitExt,
};
use winit::{window::{WindowBuilder, Window}, event::{Event, WindowEvent, KeyboardInput, ElementState, VirtualKeyCode}, event_loop::{EventLoop, ControlFlow}};
use winit::{window::{WindowBuilder, Window}, event::{Event, WindowEvent, KeyboardInput, ElementState, VirtualKeyCode, DeviceEvent}, event_loop::{EventLoop, ControlFlow}};
use crate::{render::{renderer::{Renderer, BasicRenderer}, render_job::RenderJob}, input_event::InputEvent, ecs::{components::{mesh::MeshComponent, transform::TransformComponent}, SimpleSystem, SystemDispatcher, world::World}, input::Input};
use crate::{render::{renderer::{Renderer, BasicRenderer}, render_job::RenderJob}, input_event::InputEvent, ecs::{components::{mesh::MeshComponent, transform::TransformComponent}, SimpleSystem, SystemDispatcher, world::World, resources::WindowState}, input::Input};
pub struct Controls<'a> {
pub world: &'a mut World,
@ -144,6 +144,41 @@ impl GameLoop {
Some(ControlFlow::Exit)
},
// TODO: Create system for this? or maybe merge into input system, idk
InputEvent::CursorEntered { .. } => {
let mut world = self.world.lock().await;
let state = match world.query_res_mut::<WindowState>() {
Some(i) => i,
None => {
world.insert_resource(WindowState::new());
// must succeed since it was just added
world.query_res_mut::<WindowState>().unwrap()
}
};
state.is_cursor_inside_window = true;
None
},
InputEvent::CursorLeft { .. } => {
let mut world = self.world.lock().await;
let state = match world.query_res_mut::<WindowState>() {
Some(i) => i,
None => {
world.insert_resource(WindowState::new());
// must succeed since it was just added
world.query_res_mut::<WindowState>().unwrap()
}
};
state.is_cursor_inside_window = false;
None
}
_ => {
//debug!("Got unhandled input event: \"{:?}\"", event);
@ -167,6 +202,32 @@ impl GameLoop {
pub async fn run_event_loop(&mut self, event: Event<'_, ()>, control_flow: &mut ControlFlow) {
*control_flow = ControlFlow::Poll;
match event {
Event::DeviceEvent { device_id, event } => match event {
// convert a MouseMotion event to an InputEvent
DeviceEvent::MouseMotion { delta } => {
let mut world = self.world.lock().await;
// make sure that the mouse is inside the window and the mouse has focus before reporting mouse motion
if let Some(window_state) = world.query_res::<WindowState>() {
if window_state.is_focused && window_state.is_cursor_inside_window {
let event_queue = match world.query_res_mut::<VecDeque<InputEvent>>() {
Some(i) => i,
None => {
world.insert_resource(VecDeque::<InputEvent>::new());
// must succeed since it was just added
world.query_res_mut::<VecDeque<InputEvent>>().unwrap()
}
};
let input_event = InputEvent::MouseMotion { device_id, delta, };
event_queue.push_back(input_event);
}
}
},
_ => {}
},
Event::WindowEvent {
ref event,
window_id,
@ -210,6 +271,21 @@ impl GameLoop {
WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
self.on_resize(**new_inner_size).await;
},
WindowEvent::Focused(is_focused) => {
let mut world = self.world.lock().await;
let state = match world.query_res_mut::<WindowState>() {
Some(s) => s,
None => {
world.insert_resource(WindowState::new());
// must succeed since it was just added
world.query_res_mut::<WindowState>().unwrap()
}
};
state.is_focused = *is_focused;
},
_ => {}
}

View File

@ -1,10 +1,11 @@
use std::{any::Any, collections::{HashMap, hash_map::DefaultHasher, VecDeque}, hash::{Hash, Hasher}, sync::{Arc, Mutex}};
use std::{any::{Any, TypeId}, collections::{HashMap, hash_map::DefaultHasher, VecDeque}, hash::{Hash, Hasher}, sync::{Arc, Mutex}};
use gilrs_core::Gilrs;
use tracing::warn;
use glam::Vec2;
use tracing::{warn, debug};
use winit::event::{VirtualKeyCode, ElementState};
use crate::{ecs::{SimpleSystem, world::Resource}, input_event::InputEvent};
use crate::{ecs::{SimpleSystem, resources::Resource}, input_event::InputEvent};
pub type KeyCode = winit::event::VirtualKeyCode;
@ -28,10 +29,21 @@ impl SimpleSystem for InputSystem {
let world = &mut controls.world;
if let Some(queue) = world.query_res_mut::<VecDeque<InputEvent>>() {
let event = queue.pop_front();
// Clone the queue, then clear it
let mut queue = {
let a = queue.clone();
queue.clear();
a
};
if let Some(input) = world.query_res_mut::<Input>() {
input.update(event.as_ref());
// clear input before processing the queue
input.clear();
// process the all events that happened this tick
while let Some(event) = queue.pop_front() {
input.update(&event);
}
}
}
@ -39,10 +51,20 @@ impl SimpleSystem for InputSystem {
}
}
pub struct MouseMotion {
pub delta: Vec2,
}
#[derive(Clone)]
pub struct MouseExact {
pub pos: Vec2,
}
pub struct Input {
gilrs: Option<Arc<Mutex<Gilrs>>>, // TODO
// the key is a u64. This is a hash of the value stored inside of the Box
events: HashMap<u64, Box<dyn Any + Send + Sync>>,
button_events: HashMap<u64, Box<dyn Any + Send + Sync>>,
typed_events: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
}
impl Resource for Input {
@ -68,13 +90,14 @@ impl Input {
Self {
gilrs,
events: HashMap::new(),
button_events: HashMap::new(),
typed_events: HashMap::new(),
}
}
pub fn update(&mut self, event: Option<&InputEvent>) -> bool {
pub fn clear(&mut self) {
// Convert JustPressed inputs to Pressed, and remove Released events
self.events.retain(|_, v| {
self.button_events.retain(|_, v| {
if let Some(ev) = v.downcast_mut::<ButtonEvent<KeyCode>>() {
match ev {
ButtonEvent::Released(_) => {
@ -90,51 +113,80 @@ impl Input {
true
});
if let Some(event) = event {
match event {
InputEvent::KeyboardInput { input, .. } => {
if let Some(code) = input.virtual_keycode {
// Get a hash of the input code
let mut hasher = DefaultHasher::new();
code.hash(&mut hasher);
let code_hash = hasher.finish();
// convert state
let buttonev = Box::new(match input.state {
ElementState::Pressed if self.is_pressed(code) => ButtonEvent::Pressed(code),
ElementState::Pressed => ButtonEvent::JustPressed(code),
ElementState::Released => ButtonEvent::Released(code),
});
self.events.insert(code_hash, buttonev);
}
},
/* WindowEvent::CursorMoved { position, .. } => Some(EventType::PositionChanged(position.x, position.y, InputEventCode::CursorMoved)),
WindowEvent::CursorEntered { .. } => Some(EventType::Signal(InputEventCode::CursorEnteredWindow)),
WindowEvent::CursorLeft { .. } => Some(EventType::Signal(InputEventCode::CursorLeftWindow)), */
//WindowEvent::MouseWheel { delta, .. } => Some(EventType::AxisChanged(delta, InputEventCode::MouseWheel)),
/* WindowEvent::MouseWheel { .. } => Some(InputEventCode::MouseWheel),
WindowEvent::MouseInput { .. } => Some(InputEventCode::MouseInput),
WindowEvent::TouchpadMagnify { .. } => Some(InputEventCode::TouchpadMagnify),
WindowEvent::SmartMagnify { .. } => Some(InputEventCode::SmartMagnify),
WindowEvent::TouchpadRotate { .. } => Some(InputEventCode::TouchpadRotate),
WindowEvent::TouchpadPressure { .. } => Some(InputEventCode::TouchpadPressure),
WindowEvent::AxisMotion { .. } => Some(InputEventCode::AxisMotion),
WindowEvent::Touch { .. } => Some(InputEventCode::Touch), */
_ => {},
self.typed_events.retain(|_, v| {
//if v.is::<MouseExact>() {
if v.downcast_ref::<MouseMotion>().is_some() {
return false;
}
true
});
}
pub fn update(&mut self, event: &InputEvent) -> bool {
match event {
InputEvent::KeyboardInput { input, .. } => {
if let Some(code) = input.virtual_keycode {
// Get a hash of the input code
let mut hasher = DefaultHasher::new();
code.hash(&mut hasher);
let code_hash = hasher.finish();
// convert state
let buttonev = Box::new(match input.state {
ElementState::Pressed if self.is_pressed(code) => ButtonEvent::Pressed(code),
ElementState::Pressed => ButtonEvent::JustPressed(code),
ElementState::Released => ButtonEvent::Released(code),
});
self.button_events.insert(code_hash, buttonev);
}
},
InputEvent::MouseMotion { delta, .. } => {
let delta = MouseMotion {
delta: Vec2::new(delta.0 as f32, delta.1 as f32)
};
self.typed_events.insert(delta.type_id(), Box::new(delta));
},
InputEvent::CursorMoved { position, .. } => {
let exact = MouseExact {
pos: Vec2::new(position.x as f32, position.y as f32)
};
self.typed_events.insert(exact.type_id(), Box::new(exact.clone()));
},
/* WindowEvent::CursorMoved { position, .. } => Some(EventType::PositionChanged(position.x, position.y, InputEventCode::CursorMoved)),
WindowEvent::CursorEntered { .. } => Some(EventType::Signal(InputEventCode::CursorEnteredWindow)),
WindowEvent::CursorLeft { .. } => Some(EventType::Signal(InputEventCode::CursorLeftWindow)), */
//WindowEvent::MouseWheel { delta, .. } => Some(EventType::AxisChanged(delta, InputEventCode::MouseWheel)),
/* WindowEvent::MouseWheel { .. } => Some(InputEventCode::MouseWheel),
WindowEvent::MouseInput { .. } => Some(InputEventCode::MouseInput),
WindowEvent::TouchpadMagnify { .. } => Some(InputEventCode::TouchpadMagnify),
WindowEvent::SmartMagnify { .. } => Some(InputEventCode::SmartMagnify),
WindowEvent::TouchpadRotate { .. } => Some(InputEventCode::TouchpadRotate),
WindowEvent::TouchpadPressure { .. } => Some(InputEventCode::TouchpadPressure),
WindowEvent::AxisMotion { .. } => Some(InputEventCode::AxisMotion),
WindowEvent::Touch { .. } => Some(InputEventCode::Touch), */
_ => {},
}
false
}
pub fn get_event<E: 'static>(&self) -> Option<&E> {
self.typed_events.get(&TypeId::of::<E>())
.and_then(|e| e.as_ref()
.downcast_ref::<E>())
}
pub fn was_just_pressed(&self, code: VirtualKeyCode) -> bool {
// get a hash of the key code
let mut hasher = DefaultHasher::new();
code.hash(&mut hasher);
let inner_hash = hasher.finish();
if let Some(e) = self.events.get(&inner_hash) {
if let Some(e) = self.button_events.get(&inner_hash) {
if let Some(ev) = e.downcast_ref::<ButtonEvent<KeyCode>>() {
match ev {
ButtonEvent::JustPressed(v) => {
@ -158,7 +210,7 @@ impl Input {
code.hash(&mut hasher);
let inner_hash = hasher.finish();
if let Some(e) = self.events.get(&inner_hash) {
if let Some(e) = self.button_events.get(&inner_hash) {
if let Some(ev) = e.downcast_ref::<ButtonEvent<KeyCode>>() {
match ev {
ButtonEvent::Pressed(v) | ButtonEvent::JustPressed(v) => {

View File

@ -1,6 +1,6 @@
use winit::{event::{DeviceId, KeyboardInput, ModifiersState, MouseScrollDelta, TouchPhase, MouseButton, AxisId, Touch, WindowEvent, ElementState}, dpi::PhysicalPosition};
use crate::ecs::world::Resource;
use crate::ecs::resources::Resource;
/// Wrapper around events from `winit::WindowEvent` that are specific to input related events.
///
@ -30,6 +30,15 @@ pub enum InputEvent {
is_synthetic: bool,
},
/// Change in physical position of a pointing device.
/// This represents raw, unfiltered physical motion.
///
/// This is from winit's `DeviceEvent::MouseMotion`
MouseMotion {
device_id: DeviceId,
delta: (f64, f64),
},
/// The cursor has moved on the window.
CursorMoved {
device_id: DeviceId,
@ -139,25 +148,6 @@ pub enum InputEventConversionError<'a> {
FromError(&'a WindowEvent<'a>)
}
impl<'a> Into<WindowEvent<'a>> for InputEvent {
fn into(self) -> WindowEvent<'a> {
match self {
InputEvent::KeyboardInput { device_id, input, is_synthetic } => WindowEvent::KeyboardInput { device_id, input, is_synthetic },
InputEvent::CursorMoved { device_id, position, modifiers } => WindowEvent::CursorMoved { device_id, position, modifiers },
InputEvent::CursorEntered { device_id } => WindowEvent::CursorEntered { device_id },
InputEvent::CursorLeft { device_id } => WindowEvent::CursorLeft { device_id },
InputEvent::MouseWheel { device_id, delta, phase, modifiers } => WindowEvent::MouseWheel { device_id, delta, phase, modifiers },
InputEvent::MouseInput { device_id, state, button, modifiers } => WindowEvent::MouseInput { device_id, state, button, modifiers },
InputEvent::TouchpadMagnify { device_id, delta, phase } => WindowEvent::TouchpadMagnify { device_id, delta, phase },
InputEvent::SmartMagnify { device_id } => WindowEvent::SmartMagnify { device_id },
InputEvent::TouchpadRotate { device_id, delta, phase } => WindowEvent::TouchpadRotate { device_id, delta, phase },
InputEvent::TouchpadPressure { device_id, pressure, stage } => WindowEvent::TouchpadPressure { device_id, pressure, stage },
InputEvent::AxisMotion { device_id, axis, value } => WindowEvent::AxisMotion { device_id, axis, value },
InputEvent::Touch(t) => WindowEvent::Touch(t),
}
}
}
impl<'a> TryFrom<&'a WindowEvent<'a>> for InputEvent {
type Error = InputEventConversionError<'a>;

View File

@ -10,9 +10,10 @@ use ecs::components::mesh::MeshComponent;
use ecs::components::transform::TransformComponent;
use game::Game;
use tracing::debug;
use crate::ecs::world::World;
use crate::input::{Input, KeyCode, InputSystem};
use crate::input::{Input, KeyCode, InputSystem, MouseMotion};
use crate::render::material::Material;
use crate::render::texture::Texture;
use crate::ecs::components::camera::CameraComponent;
@ -112,7 +113,9 @@ async fn main() {
dir_y += speed;
}
//debug!("dir: ({}, {})", dir_x, dir_y);
if let Some(motion) = input.get_event::<MouseMotion>() {
debug!("delta: {}", motion.delta);
}
for (_eid, (transform, )) in world.query_mut::<(&mut TransformComponent,)>() {
let t = &mut transform.transform;