Skip to content

Commit

Permalink
Switch app cycling to double-tap
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
chrisduerr committed Dec 21, 2023
1 parent 02552a5 commit b85de59
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 17 deletions.
44 changes: 39 additions & 5 deletions src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.;

Expand All @@ -52,6 +55,7 @@ pub struct TouchState {
pub user_gestures: Vec<GestureBinding>,
pub position: Point<f64, Logical>,

pending_single_tap: Option<RegistrationToken>,
event_loop: LoopHandle<'static, Catacomb>,
velocity_timer: Option<RegistrationToken>,
input_surface: Option<InputSurface>,
Expand All @@ -61,6 +65,7 @@ pub struct TouchState {
slot: Option<TouchSlot>,
touch: TouchHandle,
start: TouchStart,
last_tap: Instant,
is_drag: bool,
}

Expand All @@ -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(),
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -225,6 +236,7 @@ enum TouchAction {
UserGesture(GestureBindingAction),
Drag,
Tap,
DoubleTap,
}

impl From<HandleGesture> for TouchAction {
Expand Down Expand Up @@ -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(_),
Expand Down Expand Up @@ -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.
Expand Down
33 changes: 21 additions & 12 deletions src/windows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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;
Expand All @@ -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<f64, Logical>) {
// 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<f64, Logical>) {
let overview = match &mut self.view {
Expand Down Expand Up @@ -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(_), _) => (),
}
}
Expand Down

0 comments on commit b85de59

Please sign in to comment.