From b85de592e2e24212527b23a76f147276633aafde Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Thu, 21 Dec 2023 02:12:29 +0100 Subject: [PATCH] Switch app cycling to double-tap This patch simplifies quickly switching between applications by changing the left/right swipe on the gesture handle to a double-tap. While the horizontal sliding could be previewed to show which application it is switching to, it does not allow for transactional layout changes. Fixes #137. --- src/input.rs | 44 +++++++++++++++++++++++++++++++++++++++----- src/windows/mod.rs | 33 +++++++++++++++++++++------------ 2 files changed, 60 insertions(+), 17 deletions(-) diff --git a/src/input.rs b/src/input.rs index 762daed..5cc82cf 100644 --- a/src/input.rs +++ b/src/input.rs @@ -34,6 +34,9 @@ const RESUME_INHIBIT_DURATION: Duration = Duration::from_millis(500); /// Time before button press is considered a hold. const BUTTON_HOLD_DURATION: Duration = Duration::from_millis(500); +/// Maximum time between taps to be considered a double-tap. +const MAX_DOUBLE_TAP_DURATION: Duration = Duration::from_millis(300); + /// Square of the maximum distance before touch input is considered a drag. const MAX_TAP_DISTANCE: f64 = 400.; @@ -52,6 +55,7 @@ pub struct TouchState { pub user_gestures: Vec, pub position: Point, + pending_single_tap: Option, event_loop: LoopHandle<'static, Catacomb>, velocity_timer: Option, input_surface: Option, @@ -61,6 +65,7 @@ pub struct TouchState { slot: Option, touch: TouchHandle, start: TouchStart, + last_tap: Instant, is_drag: bool, } @@ -69,6 +74,8 @@ impl TouchState { Self { event_loop, touch, + last_tap: Instant::now(), + pending_single_tap: Default::default(), velocity_timer: Default::default(), input_surface: Default::default(), user_gestures: Default::default(), @@ -164,7 +171,11 @@ impl TouchState { return Some(TouchAction::Drag); } - (!touching).then_some(TouchAction::Tap) + if self.last_tap.elapsed() <= MAX_DOUBLE_TAP_DURATION { + (!touching).then_some(TouchAction::DoubleTap) + } else { + (!touching).then_some(TouchAction::Tap) + } } /// Find gestures matching an origin point. @@ -225,6 +236,7 @@ enum TouchAction { UserGesture(GestureBindingAction), Drag, Tap, + DoubleTap, } impl From for TouchAction { @@ -493,12 +505,19 @@ impl Catacomb { match self.touch_state.action(self.windows.canvas()) { Some(TouchAction::Tap) => { - // Clear focus when tapping outside of any window. - if self.touch_state.input_surface.is_none() { - self.windows.set_focus(None, None, None); + let timer = Timer::from_duration(MAX_DOUBLE_TAP_DURATION); + let pending_single_tap = + self.event_loop.insert_source(timer, Self::on_tap).unwrap(); + self.touch_state.pending_single_tap = Some(pending_single_tap); + self.touch_state.last_tap = Instant::now(); + }, + Some(TouchAction::DoubleTap) => { + // Cancel single-tap. + if let Some(pending_single_tap) = self.touch_state.pending_single_tap.take() { + self.event_loop.remove(pending_single_tap); } - self.windows.on_tap(self.touch_state.position); + self.windows.on_double_tap(self.touch_state.position); }, Some( TouchAction::Drag | TouchAction::HandleGesture(_) | TouchAction::UserGesture(_), @@ -585,6 +604,21 @@ impl Catacomb { self.touch_state.touch.cancel(); } + /// Single-tap handler. + fn on_tap(_time: Instant, _data: &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); + } + + catacomb.windows.on_tap(catacomb.touch_state.position); + + // Ensure updates are rendered. + catacomb.unstall(); + + TimeoutAction::Drop + } + /// Process a single velocity tick. fn on_velocity_tick(&mut self) -> TimeoutAction { // Update velocity and new position. diff --git a/src/windows/mod.rs b/src/windows/mod.rs index ecce3cd..6d5d545 100644 --- a/src/windows/mod.rs +++ b/src/windows/mod.rs @@ -44,9 +44,6 @@ const MAX_LOCKED_TRANSACTION_MILLIS: u64 = 100; /// Maximum time before a transaction is cancelled. const MAX_TRANSACTION_MILLIS: u64 = 1000; -/// Horizantal screen percentage required to cycle through windows. -const CYCLE_PERCENTAGE: f64 = 0.3; - /// Global transaction timer in milliseconds. static TRANSACTION_START: AtomicU64 = AtomicU64::new(0); @@ -833,7 +830,7 @@ impl Windows { View::Overview(overview) => overview, View::Workspace => { // Clear focus on gesture handle tap. - if point.y >= (self.output.size().h - GESTURE_HANDLE_HEIGHT) as f64 { + if point.y >= (self.canvas.size().h - GESTURE_HANDLE_HEIGHT) as f64 { self.set_focus(None, None, None); } return; @@ -855,6 +852,26 @@ impl Windows { self.set_view(View::Workspace); } + /// Hand quick double-touch input. + pub fn on_double_tap(&mut self, point: Point) { + // Ensure we're in workspace view. + if !matches!(self.view, View::Workspace) { + return; + } + + // Ignore tap outside of gesture handle. + let canvas_size = self.canvas.size().to_f64(); + if point.y < (canvas_size.h - GESTURE_HANDLE_HEIGHT as f64) { + return; + } + + if point.x >= canvas_size.w / 1.5 { + self.layouts.cycle_active(&self.output, 1); + } else if point.x < canvas_size.w / 3. { + self.layouts.cycle_active(&self.output, -1); + } + } + /// Handle a touch drag. pub fn on_drag(&mut self, touch_state: &mut TouchState, mut point: Point) { let overview = match &mut self.view { @@ -1019,14 +1036,6 @@ impl Windows { // Resize back to workspace size. self.resize_visible(); }, - (HandleGesture::Horizontal(delta), View::Workspace) => { - let delta_percentage = delta / self.output.size().w as f64; - if delta_percentage <= -CYCLE_PERCENTAGE { - self.layouts.cycle_active(&self.output, 1); - } else if delta >= CYCLE_PERCENTAGE { - self.layouts.cycle_active(&self.output, -1); - } - }, (HandleGesture::Vertical(_) | HandleGesture::Horizontal(_), _) => (), } }