From 01a4e93f3f4f11c9f0b8800224136f5afbdbb6d1 Mon Sep 17 00:00:00 2001 From: Asuro Date: Thu, 5 Aug 2021 23:33:26 +0200 Subject: [PATCH] basic touch input --- config/input.toml | 1 + config/log.toml | 1 + config/testinput.toml | 31 ++++++++++ src/config.rs | 1 + src/input.rs | 130 ++++++++++++++++++++++++++++++++++++------ src/tests/mod.rs | 102 ++++++++++++++++++++++++++++++++- 6 files changed, 247 insertions(+), 19 deletions(-) create mode 100644 config/testinput.toml diff --git a/config/input.toml b/config/input.toml index 1b60ca1..685cee0 100644 --- a/config/input.toml +++ b/config/input.toml @@ -48,6 +48,7 @@ scan_code = 64 [[button]] name = "select" mouse = "left" +touch = 1 [[axis]] name = "move_forward" diff --git a/config/log.toml b/config/log.toml index 73d0727..e4e7397 100644 --- a/config/log.toml +++ b/config/log.toml @@ -4,4 +4,5 @@ mesh_load_info = true [input] mouse_motion = false buttons = false +touch = true missing_bindings = true \ No newline at end of file diff --git a/config/testinput.toml b/config/testinput.toml new file mode 100644 index 0000000..11b3021 --- /dev/null +++ b/config/testinput.toml @@ -0,0 +1,31 @@ +[[button]] +name = "quit" +scan_code = 1 + +[[button]] +name = "select" +mouse = "left" + +[[button]] +name = "touchclick" +touch = 1 + +[[button]] +name = "doubletouchclick" +touch = 2 + +[[button]] +name = "button_left" +scan_code = 30 + +[[button]] +name = "button_right" +scan_code = 32 + +[[axis]] +name = "move_sideways" +positive_button = "button_right" +negative_button = "button_left" + +[config] +line_height_px = 16 \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index bb1560c..52fccc0 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,6 +7,7 @@ use toml; pub struct LogConfigInput { pub mouse_motion: bool, pub buttons: bool, + pub touch: bool, pub missing_bindings: bool } diff --git a/src/input.rs b/src/input.rs index 4558faf..7fe496a 100644 --- a/src/input.rs +++ b/src/input.rs @@ -2,19 +2,21 @@ use std::collections::{HashMap, HashSet}; use std::fs; +use std::hash::Hash; use std::iter::FromIterator; use gilrs; use gilrs::{EventType, Gilrs}; use serde_derive::{Deserialize, Serialize}; use toml; -use winit::event::{DeviceEvent, ElementState, Event, ModifiersState, MouseButton, MouseScrollDelta, ScanCode, WindowEvent}; +use winit::event::{DeviceEvent, ElementState, Event, ModifiersState, MouseButton, MouseScrollDelta, ScanCode, Touch, TouchPhase, WindowEvent}; use crate::config::LogConfig; #[derive(Debug, Clone)] pub struct VirtualButton { - pub digital_inputs: Vec + pub digital_inputs: Vec, + pub touch_count: Option } #[derive(Debug)] @@ -45,6 +47,7 @@ struct InputConfigButton { name: String, scan_code: Option, mouse: Option, + touch: Option, controller_button: Option, ctrl: Option, shift: Option, @@ -75,10 +78,12 @@ pub struct InputState { input_events: HashSet, pressed_scan_codes: HashSet, pressed_mouse_buttons: HashSet, + pressed_touch_ids: HashSet, analog_wheel_state: f32, config: InputConfigConfig, log_config: LogConfig, controller_input: Gilrs, + touch_inputs: Vec } #[derive(Debug, Eq, PartialEq, Hash, Clone)] @@ -106,10 +111,11 @@ impl InputState { let mut state = InputState { virtual_buttons: HashMap::new(), virtual_axes: HashMap::new(), input_events: HashSet::new(), pressed_scan_codes: HashSet::new(), - pressed_mouse_buttons: HashSet::new(), analog_wheel_state: 0.0, + pressed_mouse_buttons: HashSet::new(), pressed_touch_ids: HashSet::new(), touch_inputs: vec![], analog_wheel_state: 0.0, config: config.config, mouse_delta_x: 0.0, mouse_delta_y: 0.0, mouse_position: [0., 0.], log_config, controller_input: Gilrs::new().unwrap() }; + // Create virtual buttons from config config.button.iter().for_each(|bn| { let modifiers = KeyboardModifierState { shift: bn.shift.is_some(), @@ -117,33 +123,55 @@ impl InputState { alt: bn.alt.is_some(), logo: bn.logo.is_some() }; - let input = if let Some(scan_code) = bn.scan_code { - DigitalInput::Keyboard(KeyboardInput { scan_code, modifiers }) - } else if let Some(button_name) = &bn.mouse { + + let mut inputs = vec![]; + + // Keyboard buttons + if let Some(scan_code) = bn.scan_code { + inputs.push(DigitalInput::Keyboard(KeyboardInput { scan_code, modifiers: modifiers.clone() })); + } + + // Mouse buttons + if let Some(button_name) = &bn.mouse { let button_name_lower = button_name.to_lowercase(); - match button_name_lower.as_str() { + let button_input = match button_name_lower.as_str() { "left" => DigitalInput::Mouse(MouseInput { button: MouseButton::Left, modifiers }), "middle" => DigitalInput::Mouse(MouseInput { button: MouseButton::Middle, modifiers }), "right" => DigitalInput::Mouse(MouseInput { button: MouseButton::Right, modifiers }), "wheelup" => DigitalInput::Wheel(WheelInput { direction: WheelInputDirection::Up, modifiers }), "wheeldown" => DigitalInput::Wheel(WheelInput { direction: WheelInputDirection::Down, modifiers }), other => DigitalInput::Mouse(MouseInput { button: MouseButton::Other(other.parse().expect(&format!("Unknown button: {:?}", other))), modifiers }), - } - } else if let Some(controller_button) = &bn.controller_button { - DigitalInput::Controller(ControllerInput { + }; + inputs.push(button_input); + } + + // Controller buttons + if let Some(controller_button) = &bn.controller_button { + inputs.push(DigitalInput::Controller(ControllerInput { button: string_to_button(controller_button.as_str()).expect(&format!("Unknown controller button: {}", controller_button.as_str())) - }) - } else { - panic!("No mouse or keyboard input for button {:?}", bn.name); - }; + })); + } - if let Some(virtual_button) = state.virtual_buttons.get_mut(&bn.name) { - virtual_button.digital_inputs.push(input); - } else { - state.virtual_buttons.insert(bn.name.clone(), VirtualButton { digital_inputs: vec![input] }); + // Touch clicks + if let Some(_) = bn.touch { + if let Some(virtual_button) = state.virtual_buttons.get_mut(&bn.name) { + virtual_button.touch_count = bn.touch; + } else { + state.virtual_buttons.insert(bn.name.clone(), VirtualButton { digital_inputs: vec![], touch_count: bn.touch }); + } + } + + // Insert digital inputs + for input in inputs { + if let Some(virtual_button) = state.virtual_buttons.get_mut(&bn.name) { + virtual_button.digital_inputs.push(input); + } else { + state.virtual_buttons.insert(bn.name.clone(), VirtualButton { digital_inputs: vec![input], touch_count: bn.touch }); + } } }); + // Create virtual axes from config config.axis.iter().for_each(|axis| { let axis_input = match axis { InputConfigAxis { positive_button: Some(pos_button_name), negative_button: Some(neg_button_name), .. } => { @@ -234,6 +262,12 @@ impl InputState { Event::WindowEvent { event: WindowEvent::CursorMoved { position, .. }, .. } => { self.mouse_position = [position.x, position.y]; }, + Event::WindowEvent { event: WindowEvent::Touch(touch), .. } => { + if self.log_config.input.touch { + println!("Touch {:?}, at {:?}, id: {:?}, force: {:?}", touch.phase, touch.location, touch.id, touch.force); + } + self.on_touch_event(touch); + }, _ => {} } } @@ -241,6 +275,11 @@ impl InputState { pub fn button_down(self: &Self, button_code: &str) -> bool { match self.virtual_buttons.get(button_code) { Some(virtual_button) => { + if let Some(count) = virtual_button.touch_count { + if self.pressed_touch_ids.len() == count as usize { + return true; + } + } virtual_button.digital_inputs.iter().any(|vi| self.digital_input_pressed(vi)) } None => { @@ -255,6 +294,13 @@ impl InputState { pub fn button_just_pressed(self: &Self, button_code: &str) -> bool { match self.virtual_buttons.get(button_code) { Some(virtual_button) => { + if let Some(count) = virtual_button.touch_count { + if self.pressed_touch_ids.len() == count as usize + && self.touch_inputs.iter().any(|ti| ti.phase == TouchPhase::Started) { + return true; + } + } + self.input_events.iter().any(|input_event| { if let DigitalInputEvent::Pressed(digital_input) = input_event { virtual_button.digital_inputs.iter().any(|virtual_button_input| virtual_button_input == digital_input) @@ -275,6 +321,13 @@ impl InputState { pub fn button_just_released(self: &Self, button_code: &str) -> bool { match self.virtual_buttons.get(button_code) { Some(virtual_button) => { + if let Some(count) = virtual_button.touch_count { + if self.pressed_touch_ids.len() < count as usize + && self.touch_inputs.iter().any(|ti| ti.phase == TouchPhase::Ended) { + return true; + } + } + self.input_events.iter().any(|input_event| { if let DigitalInputEvent::Released(digital_input) = input_event { virtual_button.digital_inputs.iter().any(|virtual_button_input| virtual_button_input == digital_input) @@ -412,6 +465,24 @@ impl InputState { }; } + pub fn on_touch_event(&mut self, event: &Touch) { + self.mouse_position = [event.location.x, event.location.y]; + match event.phase { + winit::event::TouchPhase::Started => { + self.pressed_touch_ids.insert(event.id); + self.touch_inputs.push(TouchInput { id: event.id, touch_location: [event.location.x, event.location.y], phase: event.phase }); + }, + winit::event::TouchPhase::Moved => {}, + winit::event::TouchPhase::Ended => { + self.pressed_touch_ids.remove(&event.id); + self.touch_inputs.push(TouchInput { id: event.id, touch_location: [event.location.x, event.location.y], phase: event.phase }); + }, + winit::event::TouchPhase::Cancelled => { + self.pressed_touch_ids.remove(&event.id); + }, + } + } + pub fn frame_start(self: &mut Self) { while let Some(event) = self.controller_input.next_event() { match event.event { @@ -440,6 +511,7 @@ impl InputState { self.mouse_delta_x = 0.0; self.mouse_delta_y = 0.0; self.input_events.clear(); + self.touch_inputs.clear(); } } @@ -490,6 +562,28 @@ pub struct ControllerInput { button: gilrs::Button, } +#[derive(Debug, Clone)] +pub struct TouchInput { + id: u64, + touch_location: [f64; 2], + phase: TouchPhase +} + +impl Eq for TouchInput {} + +impl PartialEq for TouchInput { + fn eq(&self, other: &Self) -> bool { + self.id.eq(&other.id) && self.phase.eq(&other.phase) + } +} + +impl Hash for TouchInput { + fn hash(&self, state: &mut H) { + state.write_u64(self.id); + self.phase.hash(state) + } +} + #[derive(Debug, Eq, PartialEq, Hash, Clone)] pub enum WheelInputDirection { Up, diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 56f4c47..13414a5 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,8 +1,9 @@ #[cfg(test)] mod tests { use cgmath::{vec3}; + use winit::{dpi::PhysicalPosition, event::{DeviceId, ElementState, Event, Force, KeyboardInput, ModifiersState, Touch, TouchPhase, WindowEvent}, window::WindowId}; - use crate::game::player::{intersect_triangle}; + use crate::{config::LogConfig, game::player::{intersect_triangle}, input::InputState}; fn epsilon_eq(f1: f32, f2: f32) { assert!(f32::abs(f1 - f2) < f32::EPSILON, "{} == {}", f1, f2); @@ -36,4 +37,103 @@ mod tests { let dist3 = intersect_triangle(zero, vec3(0.9950371902, 0.09950371902, 0.0), a, b, c); epsilon_eq_option(dist3, Some(2.0099751242)); } + + fn create_test_input_state() -> InputState { + InputState::new("config/testinput.toml", LogConfig::from_file("config/log.toml")) + } + + #[test] + fn key_test() { + let mut state = create_test_input_state(); + state.frame_start(); + state.frame_end(); + unsafe { + state.on_window_event(&Event::WindowEvent{ window_id: WindowId::dummy(), event: WindowEvent::KeyboardInput { + device_id: DeviceId::dummy(), + input: KeyboardInput { scancode: 1, state: ElementState::Pressed, virtual_keycode: None, modifiers: ModifiersState::empty() }, + is_synthetic: true, + }}); + } + state.frame_start(); + assert_eq!(state.button_just_pressed("quit"), true); + assert_eq!(state.button_down("quit"), true); + state.frame_end(); + state.frame_start(); + assert_eq!(state.button_just_pressed("quit"), false); + assert_eq!(state.button_down("quit"), true); + state.frame_end(); + unsafe { + state.on_window_event(&Event::WindowEvent{ window_id: WindowId::dummy(), event: WindowEvent::KeyboardInput { + device_id: DeviceId::dummy(), + input: KeyboardInput { scancode: 1, state: ElementState::Released, virtual_keycode: None, modifiers: ModifiersState::empty() }, + is_synthetic: true, + }}); + } + state.frame_start(); + assert_eq!(state.button_down("quit"), false); + } + + fn touch_event(state: &mut InputState, phase: TouchPhase, id: u64) { + unsafe { + state.on_window_event(&Event::WindowEvent{ window_id: WindowId::dummy(), event: WindowEvent::Touch(Touch { + device_id: DeviceId::dummy(), phase, location: PhysicalPosition::new(0., 0.), force: Some(Force::Normalized(0.5)), id + })}); + } + } + + #[test] + fn touch_test() { + let mut state = create_test_input_state(); + state.frame_start(); + state.frame_end(); + touch_event(&mut state, TouchPhase::Started, 1); + state.frame_start(); + assert_eq!(state.button_just_pressed("touchclick"), true); + assert_eq!(state.button_down("touchclick"), true); + state.frame_end(); + touch_event(&mut state, TouchPhase::Moved, 1); + state.frame_start(); + assert_eq!(state.button_just_pressed("touchclick"), false); + assert_eq!(state.button_down("touchclick"), true); + state.frame_end(); + touch_event(&mut state, TouchPhase::Ended, 1); + state.frame_start(); + assert_eq!(state.button_just_released("touchclick"), true); + assert_eq!(state.button_down("touchclick"), false); + state.frame_end(); + } + + #[test] + fn multi_touch_test() { + let mut state = create_test_input_state(); + state.frame_start(); + state.frame_end(); + touch_event(&mut state, TouchPhase::Started, 2); + // TODO: add tolerance for delay + touch_event(&mut state, TouchPhase::Started, 3); + state.frame_start(); + assert_eq!(state.button_just_pressed("touchclick"), false); + assert_eq!(state.button_down("touchclick"), false); + assert_eq!(state.button_just_pressed("doubletouchclick"), true); + assert_eq!(state.button_down("doubletouchclick"), true); + state.frame_end(); + touch_event(&mut state, TouchPhase::Moved, 2); + state.frame_start(); + assert_eq!(state.button_just_pressed("doubletouchclick"), false); + assert_eq!(state.button_down("doubletouchclick"), true); + state.frame_end(); + touch_event(&mut state, TouchPhase::Ended, 2); + state.frame_start(); + assert_eq!(state.button_just_released("doubletouchclick"), true); + assert_eq!(state.button_down("doubletouchclick"), false); + assert_eq!(state.button_just_pressed("touchclick"), false); + // TODO: don't enable button_down on release of double touch + // assert_eq!(state.button_down("touchclick"), false); + state.frame_end(); + touch_event(&mut state, TouchPhase::Ended, 3); + state.frame_start(); + // TODO: only set button_just_released for one frame + // assert_eq!(state.button_just_released("doubletouchclick"), false); + assert_eq!(state.button_down("doubletouchclick"), false); + } } \ No newline at end of file