From 9de289818dabd293be87c19fa169f99e0f1f2456 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sat, 27 Jan 2024 04:06:46 +0100 Subject: [PATCH] Replay tap events captured by gesture recognition Gestures generally inhibit touch events happening in their starting location due to requiring information about the touch end to fully determine if they match the desired gesture. This would cause simple "tap" events to get ignored in gesture's starting sectors. To remedy this, touch events are now replayed whenever they've been detected to be simple "tap" events if their start was suppressed due to being detected as a gesture. While this causes events to be slightly delayed until the release event, it should not be noticeable during normal operation. Closes #142. --- src/input.rs | 100 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 74 insertions(+), 26 deletions(-) diff --git a/src/input.rs b/src/input.rs index 1f44302..a6820ee 100644 --- a/src/input.rs +++ b/src/input.rs @@ -53,6 +53,7 @@ pub struct TouchState { event_loop: LoopHandle<'static, Catacomb>, velocity_timer: Option, input_surface: Option, + tap_surface: Option, active_app_id: Option, velocity: Point, events: Vec, @@ -74,6 +75,7 @@ impl TouchState { input_surface: Default::default(), user_gestures: Default::default(), active_app_id: Default::default(), + tap_surface: Default::default(), position: Default::default(), velocity: Default::default(), is_drag: Default::default(), @@ -405,7 +407,7 @@ impl Catacomb { /// Handle new touch input start. fn on_touch_down(&mut self, event: TouchEvent) { - let TouchEvent { time, slot, position, .. } = event; + let TouchEvent { slot, position, .. } = event; // Initialize the touch state. self.touch_state.start(self.windows.canvas(), slot, position); @@ -416,6 +418,7 @@ impl Catacomb { // Notify client. self.touch_state.input_surface = None; self.touch_state.active_app_id = None; + self.touch_state.tap_surface = None; if let Some(mut input_surface) = surface { // Get surface's App ID. let app_id = input_surface.toplevel.as_mut().and_then(InputSurfaceKind::take_app_id); @@ -440,28 +443,11 @@ impl Catacomb { self.touch_state.active_app_id = app_id; if !gesture_active { - // Update window focus. - match input_surface.toplevel.take() { - Some(InputSurfaceKind::Layout((window, _))) => { - self.windows.set_focus(Some(window), None, None); - }, - Some(InputSurfaceKind::Layer((layer, app_id))) => { - self.windows.set_focus(None, Some(layer), app_id); - }, - // For surfaces denying focus, we send events but inhibit focus. - None => (), - } - - // Calculate surface-local touch position. - let scale = self.windows.canvas().scale(); - let position = input_surface.local_position(scale, event.position); - - // Send touch event to the client. - let serial = SERIAL_COUNTER.next_serial(); - let surface = &input_surface.surface; - self.touch_state.touch.down(serial, time, surface, position, slot); + self.on_surface_down(event, &mut input_surface); self.touch_state.input_surface = Some(input_surface); + } else { + self.touch_state.tap_surface = Some(input_surface); } } @@ -474,8 +460,10 @@ impl Catacomb { /// Handle touch input release. fn on_touch_up(&mut self, event: TouchEvent) { // Notify client. - let serial = SERIAL_COUNTER.next_serial(); - self.touch_state.touch.up(serial, event.time, event.slot); + if self.touch_state.input_surface.is_some() { + let serial = SERIAL_COUNTER.next_serial(); + self.touch_state.touch.up(serial, event.time, event.slot); + } // Check if slot is the active one. if self.touch_state.slot != Some(event.slot) { @@ -485,9 +473,12 @@ impl Catacomb { match self.touch_state.action(self.windows.canvas()) { Some(TouchAction::Tap) => { + self.replay_ignored_tap(event); + + // Stage single-tap to trigger on double-tap timeout. let timer = Timer::from_duration(MAX_DOUBLE_TAP_DURATION); let pending_single_tap = - self.event_loop.insert_source(timer, Self::on_tap).unwrap(); + self.event_loop.insert_source(timer, Self::on_single_tap).unwrap(); self.touch_state.pending_single_tap = Some(pending_single_tap); self.touch_state.last_tap = Instant::now(); }, @@ -497,7 +488,9 @@ impl Catacomb { self.event_loop.remove(pending_single_tap); } - self.windows.on_double_tap(self.touch_state.position); + self.replay_ignored_tap(event); + + self.on_double_tap(); }, Some( TouchAction::Drag | TouchAction::HandleGesture(_) | TouchAction::UserGesture(_), @@ -528,6 +521,32 @@ impl Catacomb { self.update_position(event.position); } + /// Handle touch-down on a surface. + fn on_surface_down(&mut self, event: TouchEvent, input_surface: &mut InputSurface) { + let TouchEvent { time, slot, position, .. } = event; + + // Update window focus. + match input_surface.toplevel.take() { + Some(InputSurfaceKind::Layout((window, _))) => { + self.windows.set_focus(Some(window), None, None); + }, + Some(InputSurfaceKind::Layer((layer, app_id))) => { + self.windows.set_focus(None, Some(layer), app_id); + }, + // For surfaces denying focus, we send events but inhibit focus. + None => (), + } + + // Calculate surface-local touch position. + let scale = self.windows.canvas().scale(); + let position = input_surface.local_position(scale, position); + + // Send touch event to the client. + let serial = SERIAL_COUNTER.next_serial(); + let surface = &input_surface.surface; + self.touch_state.touch.down(serial, time, surface, position, slot); + } + /// Update the touch position. /// /// NOTE: This should be called after adding new timeouts to allow clearing @@ -584,8 +603,24 @@ impl Catacomb { self.touch_state.touch.cancel(); } + /// Replay touch tap event which were ignored due to gesture recognition. + fn replay_ignored_tap(&mut self, event: TouchEvent) { + let mut input_surface = match self.touch_state.tap_surface.take() { + Some(input_surface) => input_surface, + None => return, + }; + + self.on_surface_down(event, &mut input_surface); + + let serial = SERIAL_COUNTER.next_serial(); + self.touch_state.touch.up(serial, event.time + 1, event.slot); + + // Reset tap surface for potential double-tap. + self.touch_state.tap_surface = Some(input_surface); + } + /// Single-tap handler. - fn on_tap(_time: Instant, _data: &mut (), catacomb: &mut Self) -> TimeoutAction { + fn on_single_tap(_: Instant, _: &mut (), catacomb: &mut Self) -> TimeoutAction { // Clear focus when tapping outside of any window. if catacomb.touch_state.input_surface.is_none() { catacomb.windows.set_focus(None, None, None); @@ -599,6 +634,19 @@ impl Catacomb { TimeoutAction::Drop } + /// Double-tap handler. + fn on_double_tap(&mut self) { + // Clear focus when tapping outside of any window. + if self.touch_state.input_surface.is_none() { + self.windows.set_focus(None, None, None); + } + + self.windows.on_double_tap(self.touch_state.position); + + // Ensure updates are rendered. + self.unstall(); + } + /// Process a single velocity tick. fn on_velocity_tick(&mut self) -> TimeoutAction { // Update velocity and new position.