From 8624928052a51720e2cd045b5c43e7d590371b53 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld Date: Tue, 18 Nov 2025 18:21:24 +0100 Subject: [PATCH] gestures: Cycle through workspaces --- src/input/actions.rs | 37 +++++++++------ src/input/mod.rs | 4 +- src/shell/focus/order.rs | 37 +++++++++------ src/shell/mod.rs | 98 ++++++++++++++++++++++++++++++---------- 4 files changed, 123 insertions(+), 53 deletions(-) diff --git a/src/input/actions.rs b/src/input/actions.rs index 5f14e60a..4cecbe28 100644 --- a/src/input/actions.rs +++ b/src/input/actions.rs @@ -1108,18 +1108,21 @@ fn to_next_workspace( workspace_state: &mut WorkspaceUpdateGuard<'_, State>, ) -> Result, InvalidWorkspaceIndex> { let current_output = seat.active_output(); - let workspace = shell - .workspaces - .active_num(¤t_output) - .1 - .checked_add(1) - .ok_or(InvalidWorkspaceIndex)?; + let active = shell.workspaces.active_num(¤t_output).1; + let mut workspace = active.checked_add(1).ok_or(InvalidWorkspaceIndex)?; + + if workspace >= shell.workspaces.len(¤t_output) { + workspace = 0; + } + if workspace == active { + return Err(InvalidWorkspaceIndex); + } shell.activate( ¤t_output, workspace, if gesture { - WorkspaceDelta::new_gesture() + WorkspaceDelta::new_gesture(true) } else { WorkspaceDelta::new_shortcut() }, @@ -1134,18 +1137,24 @@ fn to_previous_workspace( workspace_state: &mut WorkspaceUpdateGuard<'_, State>, ) -> Result, InvalidWorkspaceIndex> { let current_output = seat.active_output(); - let workspace = shell - .workspaces - .active_num(¤t_output) - .1 - .checked_sub(1) - .ok_or(InvalidWorkspaceIndex)?; + let active = shell.workspaces.active_num(¤t_output).1; + let workspace = active.checked_sub(1).unwrap_or( + shell + .workspaces + .len(¤t_output) + .checked_sub(1) + .ok_or(InvalidWorkspaceIndex)?, + ); + + if workspace == active { + return Err(InvalidWorkspaceIndex); + } shell.activate( ¤t_output, workspace, if gesture { - WorkspaceDelta::new_gesture() + WorkspaceDelta::new_gesture(false) } else { WorkspaceDelta::new_shortcut() }, diff --git a/src/input/mod.rs b/src/input/mod.rs index 31d540dd..06cd440e 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -1056,10 +1056,12 @@ impl State { } match gesture_state.action { - Some(SwipeAction::NextWorkspace) | Some(SwipeAction::PrevWorkspace) => { + Some(x @ SwipeAction::NextWorkspace) + | Some(x @ SwipeAction::PrevWorkspace) => { self.common.shell.write().update_workspace_delta( &seat.active_output(), gesture_state.delta, + matches!(x, SwipeAction::NextWorkspace), ) } _ => {} diff --git a/src/shell/focus/order.rs b/src/shell/focus/order.rs index d0c39b40..81f23c62 100644 --- a/src/shell/focus/order.rs +++ b/src/shell/focus/order.rs @@ -146,21 +146,32 @@ fn render_input_order_internal( }; let has_fullscreen = workspace.fullscreen.is_some(); - let percentage = match start { - WorkspaceDelta::Shortcut(st) => ease( - EaseInOutCubic, - 0.0, - 1.0, - Instant::now().duration_since(*st).as_millis() as f32 - / ANIMATION_DURATION.as_millis() as f32, + let (forward, percentage) = match start { + WorkspaceDelta::Shortcut(st) => ( + *previous_idx < current.1, + ease( + EaseInOutCubic, + 0.0, + 1.0, + Instant::now().duration_since(*st).as_millis() as f32 + / ANIMATION_DURATION.as_millis() as f32, + ), + ), + WorkspaceDelta::Gesture { + percentage: prog, + forward, + } => (*forward, *prog as f32), + WorkspaceDelta::GestureEnd { + start, + spring, + forward, + } => ( + *forward, + (spring.value_at(Instant::now().duration_since(*start)) as f32).clamp(0.0, 1.0), ), - WorkspaceDelta::Gesture(prog) => *prog as f32, - WorkspaceDelta::GestureEnd(st, spring) => { - (spring.value_at(Instant::now().duration_since(*st)) as f32).clamp(0.0, 1.0) - } }; - let offset = Point::::from(match (layout, *previous_idx < current.1) { + let offset = Point::::from(match (layout, forward) { (WorkspaceLayout::Vertical, true) => { (0, (-output_size.h as f32 * percentage).round() as i32) } @@ -177,7 +188,7 @@ fn render_input_order_internal( ( Some((previous, has_fullscreen, offset)), - Point::::from(match (layout, *previous_idx < current.1) { + Point::::from(match (layout, forward) { (WorkspaceLayout::Vertical, true) => (0, output_size.h + offset.y), (WorkspaceLayout::Vertical, false) => (0, -(output_size.h - offset.y)), (WorkspaceLayout::Horizontal, true) => (output_size.w + offset.x, 0), diff --git a/src/shell/mod.rs b/src/shell/mod.rs index cf983061..902c3fe1 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -301,28 +301,39 @@ pub struct SessionLock { #[derive(Debug, Clone, Copy)] pub enum WorkspaceDelta { Shortcut(Instant), - Gesture(f64), - GestureEnd(Instant, Spring), + Gesture { + percentage: f64, + forward: bool, + }, + GestureEnd { + start: Instant, + spring: Spring, + forward: bool, + }, // InvalidGesture(f64), TODO // InvalidGestureEnd(Instant, Spring), TODO } impl WorkspaceDelta { - pub fn new_gesture() -> Self { - WorkspaceDelta::Gesture(0.0) + pub fn new_gesture(forward: bool) -> Self { + WorkspaceDelta::Gesture { + percentage: 0.0, + forward, + } } - pub fn new_gesture_end(delta: f64, velocity: f64) -> Self { + pub fn new_gesture_end(delta: f64, velocity: f64, forward: bool) -> Self { let params: SpringParams = SpringParams::new(1.0, 1000.0, 0.0001); - WorkspaceDelta::GestureEnd( - Instant::now(), - Spring { + WorkspaceDelta::GestureEnd { + start: Instant::now(), + forward, + spring: Spring { from: delta, to: 1.0, initial_velocity: velocity, params, }, - ) + } } pub fn new_shortcut() -> Self { @@ -332,7 +343,7 @@ impl WorkspaceDelta { pub fn is_animating(&self) -> bool { matches!( self, - WorkspaceDelta::Shortcut(_) | WorkspaceDelta::GestureEnd(_, _) + WorkspaceDelta::Shortcut(_) | WorkspaceDelta::GestureEnd { .. } ) } } @@ -497,8 +508,8 @@ impl WorkspaceSet { } else { // snap to workspace, when in between workspaces due to swipe gesture if let Some((p_idx, p_delta)) = self.previously_active { - if matches!(p_delta, WorkspaceDelta::Gesture(..)) - && matches!(workspace_delta, WorkspaceDelta::GestureEnd(..)) + if matches!(p_delta, WorkspaceDelta::Gesture { .. }) + && matches!(workspace_delta, WorkspaceDelta::GestureEnd { .. }) { self.previously_active = Some((p_idx, workspace_delta)); } else { @@ -521,10 +532,16 @@ impl WorkspaceSet { Err(InvalidWorkspaceIndex) } - fn update_workspace_delta(&mut self, delta: f64) { + fn update_workspace_delta(&mut self, delta: f64, forward: bool) { let easing = delta.clamp(0.0, GESTURE_MAX_LENGTH).abs() / GESTURE_MAX_LENGTH; if let Some((idx, _)) = self.previously_active { - self.previously_active = Some((idx, WorkspaceDelta::Gesture(easing))); + self.previously_active = Some(( + idx, + WorkspaceDelta::Gesture { + percentage: easing, + forward, + }, + )); } } @@ -550,8 +567,9 @@ impl WorkspaceSet { self.previously_active = None; } } - WorkspaceDelta::GestureEnd(st, spring) => { - if Instant::now().duration_since(st).as_millis() > spring.duration().as_millis() + WorkspaceDelta::GestureEnd { start, spring, .. } => { + if Instant::now().duration_since(start).as_millis() + > spring.duration().as_millis() { self.previously_active = None; } @@ -1576,16 +1594,16 @@ impl Shell { } } - pub fn update_workspace_delta(&mut self, output: &Output, delta: f64) { + pub fn update_workspace_delta(&mut self, output: &Output, delta: f64, forward: bool) { match &mut self.workspaces.mode { WorkspaceMode::OutputBound => { if let Some(set) = self.workspaces.sets.get_mut(output) { - set.update_workspace_delta(delta); + set.update_workspace_delta(delta, forward); } } WorkspaceMode::Global => { for set in self.workspaces.sets.values_mut() { - set.update_workspace_delta(delta); + set.update_workspace_delta(delta, forward); } } } @@ -1606,19 +1624,34 @@ impl Shell { ) { set.workspaces[set.active].tiling_layer.cleanup_drag(); } - if let Some((_, WorkspaceDelta::Gesture(delta))) = set.previously_active { + if let Some(( + _, + WorkspaceDelta::Gesture { + percentage: delta, + forward, + }, + )) = set.previously_active + { if (velocity > 0.0 && velocity.abs() >= GESTURE_VELOCITY_THRESHOLD) || (velocity.abs() < GESTURE_VELOCITY_THRESHOLD && delta.abs() > GESTURE_POSITION_THRESHOLD) { set.activate( set.active, - WorkspaceDelta::new_gesture_end(delta.abs(), velocity.abs()), + WorkspaceDelta::new_gesture_end( + delta.abs(), + velocity.abs(), + forward, + ), workspace_state, )?; } else { set.activate_previous( - WorkspaceDelta::new_gesture_end(1.0 - delta.abs(), velocity.abs()), + WorkspaceDelta::new_gesture_end( + 1.0 - delta.abs(), + velocity.abs(), + !forward, + ), workspace_state, )?; } @@ -1635,19 +1668,34 @@ impl Shell { } WorkspaceMode::Global => { for set in self.workspaces.sets.values_mut() { - if let Some((_, WorkspaceDelta::Gesture(delta))) = set.previously_active { + if let Some(( + _, + WorkspaceDelta::Gesture { + percentage: delta, + forward, + }, + )) = set.previously_active + { if (velocity > 0.0 && velocity.abs() >= GESTURE_VELOCITY_THRESHOLD) || (velocity.abs() < GESTURE_VELOCITY_THRESHOLD && delta.abs() > GESTURE_POSITION_THRESHOLD) { set.activate( set.active, - WorkspaceDelta::new_gesture_end(delta.abs(), velocity.abs()), + WorkspaceDelta::new_gesture_end( + delta.abs(), + velocity.abs(), + forward, + ), workspace_state, )?; } else { set.activate_previous( - WorkspaceDelta::new_gesture_end(1.0 - delta.abs(), velocity.abs()), + WorkspaceDelta::new_gesture_end( + 1.0 - delta.abs(), + velocity.abs(), + !forward, + ), workspace_state, )?; }