diff --git a/src/window/mouse_manager.rs b/src/window/mouse_manager.rs index e21fc46..77f2c5c 100644 --- a/src/window/mouse_manager.rs +++ b/src/window/mouse_manager.rs @@ -1,9 +1,16 @@ -use std::cmp::Ordering; +use std::{ + cmp::Ordering, + collections::HashMap, + time::{Duration, Instant}, +}; use glutin::{ self, dpi::PhysicalPosition, - event::{ElementState, Event, MouseButton, MouseScrollDelta, WindowEvent}, + event::{ + DeviceId, ElementState, Event, MouseButton, MouseScrollDelta, Touch, TouchPhase, + WindowEvent, + }, PossiblyCurrent, WindowedContext, }; use skia_safe::Rect; @@ -53,6 +60,14 @@ fn mouse_button_to_button_text(mouse_button: &MouseButton) -> Option { } } +#[derive(Debug)] +struct TouchTrace { + start_time: Instant, + start: PhysicalPosition, + last: PhysicalPosition, + left_deadzone_once: bool, +} + pub struct MouseManager { dragging: Option, drag_position: PhysicalPosition, @@ -63,6 +78,9 @@ pub struct MouseManager { scroll_position: PhysicalPosition, + // the tuple allows to keep track of different fingers per device + touch_position: HashMap<(DeviceId, u64), TouchTrace>, + window_details_under_mouse: Option, mouse_hidden: bool, @@ -78,6 +96,7 @@ impl MouseManager { relative_position: PhysicalPosition::new(0, 0), drag_position: PhysicalPosition::new(0, 0), scroll_position: PhysicalPosition::new(0.0, 0.0), + touch_position: HashMap::new(), window_details_under_mouse: None, mouse_hidden: false, enabled: true, @@ -290,6 +309,108 @@ impl MouseManager { ); } + fn handle_touch( + &mut self, + keyboard_manager: &KeyboardManager, + renderer: &Renderer, + windowed_context: &WindowedContext, + finger_id: (DeviceId, u64), + location: PhysicalPosition, + phase: &TouchPhase, + ) { + match phase { + TouchPhase::Started => { + let settings = SETTINGS.get::(); + let enable_deadzone = settings.touch_deadzone >= 0.0; + + self.touch_position.insert( + finger_id, + TouchTrace { + start_time: Instant::now(), + start: location, + last: location, + left_deadzone_once: !enable_deadzone, + }, + ); + } + TouchPhase::Moved => { + let mut dragging_just_now = false; + + if let Some(trace) = self.touch_position.get_mut(&finger_id) { + if !trace.left_deadzone_once { + let distance_to_start = ((trace.start.x - location.x).powi(2) + + (trace.start.y - location.y).powi(2)) + .sqrt(); + + let settings = SETTINGS.get::(); + if distance_to_start >= settings.touch_deadzone { + trace.left_deadzone_once = true; + } + + let timeout_setting = Duration::from_micros( + (settings.touch_drag_timeout * 1_000_000.) as u64, + ); + if self.dragging.is_none() && trace.start_time.elapsed() >= timeout_setting + { + dragging_just_now = true; + } + } + + if self.dragging.is_some() || dragging_just_now { + self.handle_pointer_motion( + location.x.round() as i32, + location.y.round() as i32, + keyboard_manager, + renderer, + windowed_context, + ); + } + // the double check might seem useless, but the if branch above might set + // trace.left_deadzone_once - which urges to check again + else if trace.left_deadzone_once { + let delta = (trace.last.x - location.x, location.y - trace.last.y); + + // not updating the position would cause the movement to "escalate" from the + // starting point + trace.last = location; + + let font_size = renderer.grid_renderer.font_dimensions.into(); + self.handle_pixel_scroll(font_size, delta, keyboard_manager); + } + } + + if dragging_just_now { + self.handle_pointer_motion( + location.x.round() as i32, + location.y.round() as i32, + keyboard_manager, + renderer, + windowed_context, + ); + self.handle_pointer_transition(&MouseButton::Left, true, keyboard_manager); + } + } + TouchPhase::Ended | TouchPhase::Cancelled => { + if let Some(trace) = self.touch_position.remove(&finger_id) { + if self.dragging.is_some() { + self.handle_pointer_transition(&MouseButton::Left, false, keyboard_manager); + } + if !trace.left_deadzone_once { + self.handle_pointer_motion( + trace.start.x.round() as i32, + trace.start.y.round() as i32, + keyboard_manager, + renderer, + windowed_context, + ); + self.handle_pointer_transition(&MouseButton::Left, true, keyboard_manager); + self.handle_pointer_transition(&MouseButton::Left, false, keyboard_manager); + } + } + } + } + } + pub fn handle_event( &mut self, event: &Event<()>, @@ -334,6 +455,24 @@ impl MouseManager { (delta.x as f32, delta.y as f32), keyboard_manager, ), + Event::WindowEvent { + event: + WindowEvent::Touch(Touch { + device_id, + id, + location, + phase, + .. + }), + .. + } => self.handle_touch( + keyboard_manager, + renderer, + windowed_context, + (*device_id, *id), + location.cast(), + phase, + ), Event::WindowEvent { event: WindowEvent::MouseInput { button, state, .. }, .. diff --git a/src/window/settings.rs b/src/window/settings.rs index 2a0b76f..b5f0777 100644 --- a/src/window/settings.rs +++ b/src/window/settings.rs @@ -10,6 +10,8 @@ pub struct WindowSettings { pub remember_window_size: bool, pub remember_window_position: bool, pub hide_mouse_when_typing: bool, + pub touch_deadzone: f32, + pub touch_drag_timeout: f32, } impl Default for WindowSettings { @@ -23,6 +25,8 @@ impl Default for WindowSettings { remember_window_size: true, remember_window_position: true, hide_mouse_when_typing: false, + touch_deadzone: 6.0, + touch_drag_timeout: 0.17, } } }