basic touch input

This commit is contained in:
2021-08-05 23:33:26 +02:00
parent 02ba2bb95a
commit 01a4e93f3f
6 changed files with 247 additions and 19 deletions

View File

@@ -48,6 +48,7 @@ scan_code = 64
[[button]] [[button]]
name = "select" name = "select"
mouse = "left" mouse = "left"
touch = 1
[[axis]] [[axis]]
name = "move_forward" name = "move_forward"

View File

@@ -4,4 +4,5 @@ mesh_load_info = true
[input] [input]
mouse_motion = false mouse_motion = false
buttons = false buttons = false
touch = true
missing_bindings = true missing_bindings = true

31
config/testinput.toml Normal file
View File

@@ -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

View File

@@ -7,6 +7,7 @@ use toml;
pub struct LogConfigInput { pub struct LogConfigInput {
pub mouse_motion: bool, pub mouse_motion: bool,
pub buttons: bool, pub buttons: bool,
pub touch: bool,
pub missing_bindings: bool pub missing_bindings: bool
} }

View File

@@ -2,19 +2,21 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::fs; use std::fs;
use std::hash::Hash;
use std::iter::FromIterator; use std::iter::FromIterator;
use gilrs; use gilrs;
use gilrs::{EventType, Gilrs}; use gilrs::{EventType, Gilrs};
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use toml; 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; use crate::config::LogConfig;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct VirtualButton { pub struct VirtualButton {
pub digital_inputs: Vec<DigitalInput> pub digital_inputs: Vec<DigitalInput>,
pub touch_count: Option<u8>
} }
#[derive(Debug)] #[derive(Debug)]
@@ -45,6 +47,7 @@ struct InputConfigButton {
name: String, name: String,
scan_code: Option<u32>, scan_code: Option<u32>,
mouse: Option<String>, mouse: Option<String>,
touch: Option<u8>,
controller_button: Option<String>, controller_button: Option<String>,
ctrl: Option<bool>, ctrl: Option<bool>,
shift: Option<bool>, shift: Option<bool>,
@@ -75,10 +78,12 @@ pub struct InputState {
input_events: HashSet<DigitalInputEvent>, input_events: HashSet<DigitalInputEvent>,
pressed_scan_codes: HashSet<ScanCode>, pressed_scan_codes: HashSet<ScanCode>,
pressed_mouse_buttons: HashSet<MouseButton>, pressed_mouse_buttons: HashSet<MouseButton>,
pressed_touch_ids: HashSet<u64>,
analog_wheel_state: f32, analog_wheel_state: f32,
config: InputConfigConfig, config: InputConfigConfig,
log_config: LogConfig, log_config: LogConfig,
controller_input: Gilrs, controller_input: Gilrs,
touch_inputs: Vec<TouchInput>
} }
#[derive(Debug, Eq, PartialEq, Hash, Clone)] #[derive(Debug, Eq, PartialEq, Hash, Clone)]
@@ -106,10 +111,11 @@ impl InputState {
let mut state = InputState { virtual_buttons: HashMap::new(), virtual_axes: HashMap::new(), let mut state = InputState { virtual_buttons: HashMap::new(), virtual_axes: HashMap::new(),
input_events: HashSet::new(), pressed_scan_codes: HashSet::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, config: config.config, mouse_delta_x: 0.0, mouse_delta_y: 0.0, mouse_position: [0., 0.], log_config,
controller_input: Gilrs::new().unwrap() }; controller_input: Gilrs::new().unwrap() };
// Create virtual buttons from config
config.button.iter().for_each(|bn| { config.button.iter().for_each(|bn| {
let modifiers = KeyboardModifierState { let modifiers = KeyboardModifierState {
shift: bn.shift.is_some(), shift: bn.shift.is_some(),
@@ -117,33 +123,55 @@ impl InputState {
alt: bn.alt.is_some(), alt: bn.alt.is_some(),
logo: bn.logo.is_some() logo: bn.logo.is_some()
}; };
let input = if let Some(scan_code) = bn.scan_code {
DigitalInput::Keyboard(KeyboardInput { scan_code, modifiers }) let mut inputs = vec![];
} else if let Some(button_name) = &bn.mouse {
// 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(); 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 }), "left" => DigitalInput::Mouse(MouseInput { button: MouseButton::Left, modifiers }),
"middle" => DigitalInput::Mouse(MouseInput { button: MouseButton::Middle, modifiers }), "middle" => DigitalInput::Mouse(MouseInput { button: MouseButton::Middle, modifiers }),
"right" => DigitalInput::Mouse(MouseInput { button: MouseButton::Right, modifiers }), "right" => DigitalInput::Mouse(MouseInput { button: MouseButton::Right, modifiers }),
"wheelup" => DigitalInput::Wheel(WheelInput { direction: WheelInputDirection::Up, modifiers }), "wheelup" => DigitalInput::Wheel(WheelInput { direction: WheelInputDirection::Up, modifiers }),
"wheeldown" => DigitalInput::Wheel(WheelInput { direction: WheelInputDirection::Down, modifiers }), "wheeldown" => DigitalInput::Wheel(WheelInput { direction: WheelInputDirection::Down, modifiers }),
other => DigitalInput::Mouse(MouseInput { button: MouseButton::Other(other.parse().expect(&format!("Unknown button: {:?}", other))), 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 { inputs.push(button_input);
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) { // Controller buttons
virtual_button.digital_inputs.push(input); if let Some(controller_button) = &bn.controller_button {
} else { inputs.push(DigitalInput::Controller(ControllerInput {
state.virtual_buttons.insert(bn.name.clone(), VirtualButton { digital_inputs: vec![input] }); button: string_to_button(controller_button.as_str()).expect(&format!("Unknown controller button: {}", controller_button.as_str()))
}));
}
// 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| { config.axis.iter().for_each(|axis| {
let axis_input = match axis { let axis_input = match axis {
InputConfigAxis { positive_button: Some(pos_button_name), negative_button: Some(neg_button_name), .. } => { 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, .. }, .. } => { Event::WindowEvent { event: WindowEvent::CursorMoved { position, .. }, .. } => {
self.mouse_position = [position.x, position.y]; 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 { pub fn button_down(self: &Self, button_code: &str) -> bool {
match self.virtual_buttons.get(button_code) { match self.virtual_buttons.get(button_code) {
Some(virtual_button) => { 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)) virtual_button.digital_inputs.iter().any(|vi| self.digital_input_pressed(vi))
} }
None => { None => {
@@ -255,6 +294,13 @@ impl InputState {
pub fn button_just_pressed(self: &Self, button_code: &str) -> bool { pub fn button_just_pressed(self: &Self, button_code: &str) -> bool {
match self.virtual_buttons.get(button_code) { match self.virtual_buttons.get(button_code) {
Some(virtual_button) => { 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| { self.input_events.iter().any(|input_event| {
if let DigitalInputEvent::Pressed(digital_input) = input_event { if let DigitalInputEvent::Pressed(digital_input) = input_event {
virtual_button.digital_inputs.iter().any(|virtual_button_input| virtual_button_input == digital_input) 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 { pub fn button_just_released(self: &Self, button_code: &str) -> bool {
match self.virtual_buttons.get(button_code) { match self.virtual_buttons.get(button_code) {
Some(virtual_button) => { 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| { self.input_events.iter().any(|input_event| {
if let DigitalInputEvent::Released(digital_input) = input_event { if let DigitalInputEvent::Released(digital_input) = input_event {
virtual_button.digital_inputs.iter().any(|virtual_button_input| virtual_button_input == digital_input) 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) { pub fn frame_start(self: &mut Self) {
while let Some(event) = self.controller_input.next_event() { while let Some(event) = self.controller_input.next_event() {
match event.event { match event.event {
@@ -440,6 +511,7 @@ impl InputState {
self.mouse_delta_x = 0.0; self.mouse_delta_x = 0.0;
self.mouse_delta_y = 0.0; self.mouse_delta_y = 0.0;
self.input_events.clear(); self.input_events.clear();
self.touch_inputs.clear();
} }
} }
@@ -490,6 +562,28 @@ pub struct ControllerInput {
button: gilrs::Button, 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<H: std::hash::Hasher>(&self, state: &mut H) {
state.write_u64(self.id);
self.phase.hash(state)
}
}
#[derive(Debug, Eq, PartialEq, Hash, Clone)] #[derive(Debug, Eq, PartialEq, Hash, Clone)]
pub enum WheelInputDirection { pub enum WheelInputDirection {
Up, Up,

View File

@@ -1,8 +1,9 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use cgmath::{vec3}; 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) { fn epsilon_eq(f1: f32, f2: f32) {
assert!(f32::abs(f1 - f2) < f32::EPSILON, "{} == {}", f1, f2); 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); let dist3 = intersect_triangle(zero, vec3(0.9950371902, 0.09950371902, 0.0), a, b, c);
epsilon_eq_option(dist3, Some(2.0099751242)); 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);
}
} }