basic touch input
This commit is contained in:
@@ -48,6 +48,7 @@ scan_code = 64
|
||||
[[button]]
|
||||
name = "select"
|
||||
mouse = "left"
|
||||
touch = 1
|
||||
|
||||
[[axis]]
|
||||
name = "move_forward"
|
||||
|
||||
@@ -4,4 +4,5 @@ mesh_load_info = true
|
||||
[input]
|
||||
mouse_motion = false
|
||||
buttons = false
|
||||
touch = true
|
||||
missing_bindings = true
|
||||
31
config/testinput.toml
Normal file
31
config/testinput.toml
Normal 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
|
||||
@@ -7,6 +7,7 @@ use toml;
|
||||
pub struct LogConfigInput {
|
||||
pub mouse_motion: bool,
|
||||
pub buttons: bool,
|
||||
pub touch: bool,
|
||||
pub missing_bindings: bool
|
||||
}
|
||||
|
||||
|
||||
130
src/input.rs
130
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<DigitalInput>
|
||||
pub digital_inputs: Vec<DigitalInput>,
|
||||
pub touch_count: Option<u8>
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -45,6 +47,7 @@ struct InputConfigButton {
|
||||
name: String,
|
||||
scan_code: Option<u32>,
|
||||
mouse: Option<String>,
|
||||
touch: Option<u8>,
|
||||
controller_button: Option<String>,
|
||||
ctrl: Option<bool>,
|
||||
shift: Option<bool>,
|
||||
@@ -75,10 +78,12 @@ pub struct InputState {
|
||||
input_events: HashSet<DigitalInputEvent>,
|
||||
pressed_scan_codes: HashSet<ScanCode>,
|
||||
pressed_mouse_buttons: HashSet<MouseButton>,
|
||||
pressed_touch_ids: HashSet<u64>,
|
||||
analog_wheel_state: f32,
|
||||
config: InputConfigConfig,
|
||||
log_config: LogConfig,
|
||||
controller_input: Gilrs,
|
||||
touch_inputs: Vec<TouchInput>
|
||||
}
|
||||
|
||||
#[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<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
state.write_u64(self.id);
|
||||
self.phase.hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
|
||||
pub enum WheelInputDirection {
|
||||
Up,
|
||||
|
||||
102
src/tests/mod.rs
102
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user