From b0f82b071461f6efd9f33c4af4eefde663f653a1 Mon Sep 17 00:00:00 2001 From: Florian RICHER Date: Tue, 27 May 2025 19:11:28 +0200 Subject: [PATCH] input: Add support for Axis, Mouse Button, MouseWheel --- src/core/app.rs | 2 +- src/core/input/keyboard_state.rs | 41 +++------ src/core/input/mod.rs | 95 ++++++++++++++------- src/core/input/mouse_state.rs | 41 +++++++-- src/core/input/virtual_binding.rs | 16 ++-- src/core/input/virtual_input.rs | 136 +++++++++++++++++++----------- src/core/input/virtual_state.rs | 58 ++++++++++++- src/game/main_scene.rs | 5 +- src/main.rs | 74 ++++++++++------ 9 files changed, 308 insertions(+), 160 deletions(-) diff --git a/src/core/app.rs b/src/core/app.rs index 3a7145b..d14b4c5 100644 --- a/src/core/app.rs +++ b/src/core/app.rs @@ -195,7 +195,7 @@ impl ApplicationHandler for App { ui.label(format!("Resolution: {:?}", renderer.resolution())); ui.color_edit_button_rgb(&mut self.clear_color); - ui.label(format!("{:#?}", self.input_manager.get_virtual_input())); + ui.label(format!("{:#?}", self.input_manager)); ui.label(format!("Delta time: {:?}", self.timer.delta_time())); }); diff --git a/src/core/input/keyboard_state.rs b/src/core/input/keyboard_state.rs index c7b9b3e..f35e448 100644 --- a/src/core/input/keyboard_state.rs +++ b/src/core/input/keyboard_state.rs @@ -1,10 +1,10 @@ use egui_winit_vulkano::egui::ahash::HashMap; use winit::{ - event::{ElementState, KeyEvent}, + event::{ElementState, WindowEvent}, keyboard::PhysicalKey, }; -use super::virtual_input::VirtualInput; +use super::{process_new_element_state, virtual_input::VirtualInput}; #[derive(Debug, Default)] pub struct KeyboardState { @@ -12,35 +12,14 @@ pub struct KeyboardState { } impl KeyboardState { - pub fn get_key_states(&self) -> &HashMap { - &self.key_states - } - - pub fn get_key_state(&self, key: PhysicalKey) -> &ElementState { - self.key_states.get(&key).unwrap_or(&ElementState::Released) - } - - pub fn process_window_event(&mut self, event: &KeyEvent, virtual_input: &mut VirtualInput) { - let key_state = self.key_states.get(&event.physical_key); - let new_key_state = match key_state { - Some(key_state) => match event.state { - ElementState::Pressed => match key_state { - ElementState::Released => Some(ElementState::Pressed), - ElementState::Pressed => None, - }, - ElementState::Released => match key_state { - ElementState::Released => None, - ElementState::Pressed => Some(ElementState::Released), - }, - }, - None => match event.state { - ElementState::Pressed => Some(ElementState::Pressed), - ElementState::Released => Some(ElementState::Released), - }, - }; - if let Some(new_key_state) = new_key_state { - self.key_states.insert(event.physical_key, new_key_state); - virtual_input.update_key_binding(event.physical_key, new_key_state); + pub fn process_window_event(&mut self, event: &WindowEvent, virtual_input: &mut VirtualInput) { + if let WindowEvent::KeyboardInput { event, .. } = event { + let key_state = self.key_states.get(&event.physical_key); + let new_key_state = process_new_element_state(key_state, event.state); + if let Some(new_key_state) = new_key_state { + self.key_states.insert(event.physical_key, new_key_state); + virtual_input.update_key_binding(event.physical_key, new_key_state); + } } } } diff --git a/src/core/input/mod.rs b/src/core/input/mod.rs index f29b963..00ebe18 100644 --- a/src/core/input/mod.rs +++ b/src/core/input/mod.rs @@ -2,15 +2,15 @@ use std::collections::HashMap; use keyboard_state::KeyboardState; use mouse_state::MouseState; -use virtual_binding::VirtualBinding; use virtual_input::VirtualInput; -use winit::event::WindowEvent; +use winit::event::{ElementState, WindowEvent}; -pub mod keyboard_state; -pub mod mouse_state; -pub mod virtual_binding; -pub mod virtual_input; -pub mod virtual_state; +mod keyboard_state; +mod mouse_state; +mod virtual_binding; +mod virtual_input; +mod virtual_state; +pub use virtual_binding::{AxisDirection, VirtualBinding}; #[derive(Default)] pub struct InputManager { @@ -19,17 +19,34 @@ pub struct InputManager { virtual_input: VirtualInput, } +impl std::fmt::Debug for InputManager { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("InputManager") + .field("virtual_input", &self.virtual_input) + .finish() + } +} + impl InputManager { + pub fn new(input_mapping: HashMap>) -> Self { + let mut input_manager = InputManager::default(); + for (value_name, bindings) in input_mapping { + input_manager.add_virtual_bindings(value_name, bindings); + } + input_manager + } + pub fn process_window_event(&mut self, event: &WindowEvent) { match event { - WindowEvent::KeyboardInput { event, .. } => { + WindowEvent::AxisMotion { axis, value, .. } => { + self.virtual_input.update_axis_binding(*axis, *value as f32); + } + _ => { self.keyboard_state .process_window_event(event, &mut self.virtual_input); + self.mouse_state + .process_window_event(event, &mut self.virtual_input); } - WindowEvent::CursorMoved { position, .. } => { - self.mouse_state.process_window_event(position); - } - _ => {} } } @@ -38,27 +55,41 @@ impl InputManager { self.mouse_state.update(&mut self.virtual_input); } - pub fn get_mouse_state(&self) -> &MouseState { - &self.mouse_state - } - - pub fn get_keyboard_state(&self) -> &KeyboardState { - &self.keyboard_state - } - - pub fn get_virtual_input(&self) -> &VirtualInput { - &self.virtual_input - } - - pub fn add_virtual_binding(&mut self, value_name: String, binding: VirtualBinding) { - self.virtual_input.add_binding(value_name, binding); - } - - pub fn add_virtual_bindings(&mut self, value_name: String, bindings: Vec) { - self.virtual_input.add_bindings(value_name, bindings); - } - pub fn get_virtual_input_state(&self, value_name: &str) -> f32 { self.virtual_input.get_state(value_name) } + + fn add_virtual_bindings(&mut self, value_name: String, bindings: Vec) { + self.virtual_input.add_bindings(value_name, bindings); + } +} + +/// Maps the old element state to the new element state. +/// if is changed, returns Some(new_state), otherwise returns None +#[inline] +fn process_new_element_state( + old: Option<&ElementState>, + new: ElementState, +) -> Option { + match old { + Some(old) => match new { + ElementState::Pressed => match old { + ElementState::Released => Some(ElementState::Pressed), + ElementState::Pressed => None, + }, + ElementState::Released => match old { + ElementState::Released => None, + ElementState::Pressed => Some(ElementState::Released), + }, + }, + None => match new { + ElementState::Pressed => Some(ElementState::Pressed), + ElementState::Released => Some(ElementState::Released), + }, + } +} + +#[inline] +fn process_axis_deadzone(value: f32, deadzone: f32) -> f32 { + if value.abs() < deadzone { 0.0 } else { value } } diff --git a/src/core/input/mouse_state.rs b/src/core/input/mouse_state.rs index 8cdce4b..084f43c 100644 --- a/src/core/input/mouse_state.rs +++ b/src/core/input/mouse_state.rs @@ -1,22 +1,49 @@ -use winit::dpi::PhysicalPosition; +use std::collections::HashMap; -use super::virtual_input::VirtualInput; +use winit::event::{ElementState, MouseButton, MouseScrollDelta, WindowEvent}; + +use super::{process_new_element_state, virtual_input::VirtualInput}; #[derive(Debug, Default)] pub struct MouseState { old_position: glam::Vec2, - pub position: glam::Vec2, - pub delta: glam::Vec2, + position: glam::Vec2, + delta: glam::Vec2, + wheel_delta: glam::Vec2, + mouse_button_state: HashMap, } impl MouseState { - pub fn process_window_event(&mut self, position: &PhysicalPosition) { - self.position = glam::Vec2::new(position.x as f32, position.y as f32); + pub fn process_window_event(&mut self, event: &WindowEvent, virtual_input: &mut VirtualInput) { + match event { + WindowEvent::CursorMoved { position, .. } => { + self.position = glam::Vec2::new(position.x as f32, position.y as f32); + } + WindowEvent::MouseWheel { delta, .. } => { + self.wheel_delta += match delta { + MouseScrollDelta::PixelDelta(position) => { + glam::Vec2::new(position.x as f32, position.y as f32) + } + MouseScrollDelta::LineDelta(x, y) => glam::Vec2::new(*x as f32, *y as f32), + }; + } + WindowEvent::MouseInput { button, state, .. } => { + let key_state = self.mouse_button_state.get(button); + let new_key_state = process_new_element_state(key_state, *state); + if let Some(new_key_state) = new_key_state { + self.mouse_button_state.insert(*button, new_key_state); + virtual_input.update_mouse_button_binding(*button, new_key_state); + } + } + _ => {} + } } pub fn update(&mut self, virtual_input: &mut VirtualInput) { self.delta = self.position - self.old_position; self.old_position = self.position; - virtual_input.update_mouse_binding(&self.delta); + virtual_input.update_mouse_move_binding(&self.delta); + virtual_input.update_mouse_wheel_binding(&self.wheel_delta); + self.wheel_delta = glam::Vec2::ZERO; } } diff --git a/src/core/input/virtual_binding.rs b/src/core/input/virtual_binding.rs index 9209a78..c11dd48 100644 --- a/src/core/input/virtual_binding.rs +++ b/src/core/input/virtual_binding.rs @@ -1,16 +1,19 @@ -use winit::{event::AxisId, keyboard::PhysicalKey}; +use winit::{ + event::{AxisId, MouseButton}, + keyboard::PhysicalKey, +}; #[derive(Clone)] pub enum AxisDirection { - Positive, - Negative, + Normal, + Invert, } impl From<&AxisDirection> for f32 { fn from(direction: &AxisDirection) -> Self { match direction { - AxisDirection::Positive => 1.0, - AxisDirection::Negative => -1.0, + AxisDirection::Normal => 1.0, + AxisDirection::Invert => -1.0, } } } @@ -21,4 +24,7 @@ pub enum VirtualBinding { Axis(AxisId, AxisDirection, f32), // f32 deadzone MouseX(AxisDirection), MouseY(AxisDirection), + MouseWheelX(AxisDirection), + MouseWheelY(AxisDirection), + MouseButton(MouseButton, AxisDirection), } diff --git a/src/core/input/virtual_input.rs b/src/core/input/virtual_input.rs index f1ec4a8..875d66c 100644 --- a/src/core/input/virtual_input.rs +++ b/src/core/input/virtual_input.rs @@ -1,7 +1,10 @@ use std::{collections::HashMap, sync::Arc}; use egui_winit_vulkano::egui::mutex::Mutex; -use winit::{event::ElementState, keyboard::PhysicalKey}; +use winit::{ + event::{AxisId, ElementState, MouseButton}, + keyboard::PhysicalKey, +}; use super::{ virtual_binding::VirtualBinding, @@ -10,9 +13,15 @@ use super::{ #[derive(Default)] pub struct VirtualInput { + // Global states states: HashMap>>, + + // Per kind of input states to keep complexity low during state updates states_by_key: HashMap>>>, - mouse_states: Vec>>, + mouse_move_states: Vec>>, + mouse_wheel_states: Vec>>, + mouse_button_states: HashMap>>>, + axis_states: HashMap>>>, } impl std::fmt::Debug for VirtualInput { @@ -36,17 +45,50 @@ impl VirtualInput { } pub fn add_bindings(&mut self, value_name: String, new_bindings: Vec) { - add_bindings( - &mut self.states, - &mut self.states_by_key, - &mut self.mouse_states, - value_name, - new_bindings, - ); - } + let state = + self.states + .entry(value_name) + .or_insert(Arc::new(Mutex::new(VirtualInputState { + value: 0.0, + bindings: Vec::new(), + }))); - pub fn add_binding(&mut self, value_name: String, binding: VirtualBinding) { - self.add_bindings(value_name, vec![binding]); + for binding in &new_bindings { + match binding { + VirtualBinding::Keyboard(key, _) => { + self.states_by_key + .entry(*key) + .or_insert(Vec::new()) + .push(state.clone()); + } + VirtualBinding::MouseX(_) | VirtualBinding::MouseY(_) => { + self.mouse_move_states.push(state.clone()); + } + VirtualBinding::MouseButton(button, _) => { + self.mouse_button_states + .entry(*button) + .or_insert(Vec::new()) + .push(state.clone()); + } + VirtualBinding::MouseWheelX(_) | VirtualBinding::MouseWheelY(_) => { + self.mouse_wheel_states.push(state.clone()); + } + VirtualBinding::Axis(axis, _, _) => { + self.axis_states + .entry(*axis) + .or_insert(Vec::new()) + .push(state.clone()); + } + } + } + + state + .lock() + .bindings + .extend(new_bindings.iter().map(|b| VirtualBindingState { + value: 0.0, + binding: b.clone(), + })); } pub(super) fn update_key_binding(&mut self, key: PhysicalKey, key_state: ElementState) { @@ -57,54 +99,46 @@ impl VirtualInput { let mut state = state.lock(); state.update_from_key(key, key_state); } - } else { - log::trace!("{}", self.states_by_key.keys().len()); - log::warn!("No states found for key: {key:?}"); } } - pub(super) fn update_mouse_binding(&mut self, delta: &glam::Vec2) { - for state in &mut self.mouse_states { + pub(super) fn update_mouse_move_binding(&mut self, delta: &glam::Vec2) { + for state in &mut self.mouse_move_states { let mut state = state.lock(); state.update_from_mouse(delta); } } -} -fn add_bindings( - states: &mut HashMap>>, - states_by_key: &mut HashMap>>>, - mouse_states: &mut Vec>>, - value_name: String, - new_bindings: Vec, -) { - let state = states - .entry(value_name) - .or_insert(Arc::new(Mutex::new(VirtualInputState { - value: 0.0, - bindings: Vec::new(), - }))); - - for binding in &new_bindings { - match binding { - VirtualBinding::Keyboard(key, _) => { - states_by_key - .entry(*key) - .or_insert(Vec::new()) - .push(state.clone()); - } - VirtualBinding::MouseX(_) | VirtualBinding::MouseY(_) => { - mouse_states.push(state.clone()); - } - _ => {} + pub(super) fn update_mouse_wheel_binding(&mut self, delta: &glam::Vec2) { + for state in &mut self.mouse_wheel_states { + let mut state = state.lock(); + state.update_from_mouse_wheel(delta); } } - let mut state = state.lock(); - state - .bindings - .extend(new_bindings.iter().map(|b| VirtualBindingState { - value: 0.0, - binding: b.clone(), - })); + pub(super) fn update_mouse_button_binding( + &mut self, + button: MouseButton, + button_state: ElementState, + ) { + let states = self.mouse_button_states.get_mut(&button); + + if let Some(states) = states { + for state in states { + let mut state = state.lock(); + state.update_from_mouse_button(button, button_state); + } + } + } + + pub(super) fn update_axis_binding(&mut self, axis: AxisId, axis_state: f32) { + let states = self.axis_states.get_mut(&axis); + + if let Some(states) = states { + for state in states { + let mut state = state.lock(); + state.update_from_axis(axis, axis_state); + } + } + } } diff --git a/src/core/input/virtual_state.rs b/src/core/input/virtual_state.rs index 47f8b0d..93cf28f 100644 --- a/src/core/input/virtual_state.rs +++ b/src/core/input/virtual_state.rs @@ -1,6 +1,9 @@ -use winit::{event::ElementState, keyboard::PhysicalKey}; +use winit::{ + event::{AxisId, ElementState, MouseButton}, + keyboard::PhysicalKey, +}; -use super::virtual_binding::VirtualBinding; +use super::{process_axis_deadzone, virtual_binding::VirtualBinding}; pub struct VirtualBindingState { pub value: f32, @@ -46,4 +49,55 @@ impl VirtualInputState { } self.value = new_value; } + + pub fn update_from_mouse_wheel(&mut self, delta: &glam::Vec2) { + let mut new_value = 0.0; + for binding in &mut self.bindings { + match &binding.binding { + VirtualBinding::MouseWheelX(direction) => { + binding.value = f32::from(direction) * delta.x; + } + VirtualBinding::MouseWheelY(direction) => { + binding.value = f32::from(direction) * delta.y; + } + _ => {} + } + new_value += binding.value; + } + self.value = new_value; + } + + pub fn update_from_mouse_button(&mut self, button: MouseButton, button_state: ElementState) { + let mut new_value = 0.0; + for binding in &mut self.bindings { + match &binding.binding { + VirtualBinding::MouseButton(binding_button, direction) => { + if binding_button == &button { + if button_state == ElementState::Pressed { + binding.value = f32::from(direction); + } else { + binding.value = 0.0; + } + } + } + _ => {} + } + new_value += binding.value; + } + self.value = new_value; + } + + pub fn update_from_axis(&mut self, axis: AxisId, axis_state: f32) { + let mut new_value = 0.0; + for binding in &mut self.bindings { + if let VirtualBinding::Axis(binding_axis, direction, deadzone) = &binding.binding { + if binding_axis == &axis { + binding.value = + f32::from(direction) * process_axis_deadzone(axis_state, *deadzone); + } + } + new_value += binding.value; + } + self.value = new_value; + } } diff --git a/src/game/main_scene.rs b/src/game/main_scene.rs index a759478..1f27f0b 100644 --- a/src/game/main_scene.rs +++ b/src/game/main_scene.rs @@ -1,4 +1,5 @@ use crate::core::input::InputManager; +use crate::core::render::pipelines::triangle_pipeline::create_triangle_pipeline; use crate::core::render::primitives::camera::Camera; use crate::core::render::primitives::vertex::Vertex2D; use crate::core::render::render_context::RenderContext; @@ -10,10 +11,6 @@ use vulkano::buffer::Subbuffer; use vulkano::command_buffer::{AutoCommandBufferBuilder, PrimaryAutoCommandBuffer}; use vulkano::descriptor_set::{DescriptorSet, WriteDescriptorSet}; use vulkano::pipeline::{GraphicsPipeline, Pipeline, PipelineBindPoint}; -use winit::event::ElementState; -use winit::keyboard::{KeyCode, PhysicalKey}; - -use crate::core::render::pipelines::triangle_pipeline::create_triangle_pipeline; const VERTICES: [Vertex2D; 12] = [ // Triangle en haut à gauche diff --git a/src/main.rs b/src/main.rs index d671188..6a4984b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,10 @@ -use core::input::{ - InputManager, - virtual_binding::{AxisDirection, VirtualBinding}, -}; +use core::input::{AxisDirection, InputManager, VirtualBinding}; +use std::collections::HashMap; use vulkano::device::{DeviceExtensions, DeviceFeatures}; use vulkano_util::context::{VulkanoConfig, VulkanoContext}; use winit::{ + event::MouseButton, event_loop::{ControlFlow, EventLoop}, keyboard::{KeyCode, PhysicalKey}, }; @@ -16,29 +15,50 @@ mod game; fn main() { env_logger::init(); - let mut input_manager = InputManager::default(); - input_manager.add_virtual_bindings( - "move_forward".to_string(), - vec![ - VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyW), AxisDirection::Positive), - VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyS), AxisDirection::Negative), - ], - ); - input_manager.add_virtual_bindings( - "move_right".to_string(), - vec![ - VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyD), AxisDirection::Positive), - VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyA), AxisDirection::Negative), - ], - ); - input_manager.add_virtual_bindings( - "mouse_x".to_string(), - vec![VirtualBinding::MouseX(AxisDirection::Positive)], - ); - input_manager.add_virtual_bindings( - "mouse_y".to_string(), - vec![VirtualBinding::MouseY(AxisDirection::Positive)], - ); + let input_manager = InputManager::new(HashMap::from([ + ( + "move_forward".to_string(), + vec![ + VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyW), AxisDirection::Normal), + VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyS), AxisDirection::Invert), + VirtualBinding::Axis(0, AxisDirection::Normal, 1.0), + ], + ), + ( + "move_right".to_string(), + vec![ + VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyD), AxisDirection::Normal), + VirtualBinding::Keyboard(PhysicalKey::Code(KeyCode::KeyA), AxisDirection::Invert), + VirtualBinding::Axis(1, AxisDirection::Normal, 1.0), + ], + ), + ( + "mouse_x".to_string(), + vec![VirtualBinding::MouseX(AxisDirection::Normal)], + ), + ( + "mouse_y".to_string(), + vec![VirtualBinding::MouseY(AxisDirection::Normal)], + ), + ( + "mouse_wheel".to_string(), + vec![VirtualBinding::MouseWheelY(AxisDirection::Normal)], + ), + ( + "mouse_left".to_string(), + vec![VirtualBinding::MouseButton( + MouseButton::Left, + AxisDirection::Normal, + )], + ), + ( + "mouse_right".to_string(), + vec![VirtualBinding::MouseButton( + MouseButton::Right, + AxisDirection::Normal, + )], + ), + ])); let device_extensions = DeviceExtensions { khr_swapchain: true,