gestures: Cycle through workspaces

This commit is contained in:
Victoria Brekenfeld 2025-11-18 18:21:24 +01:00 • committed by Jeremy Soller
parent 1f2863fa18
commit 8624928052
4 changed files with 123 additions and 53 deletions

View file

@ -1108,18 +1108,21 @@ fn to_next_workspace(
workspace_state: &mut WorkspaceUpdateGuard<'_, State>, workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
) -> Result<Point<i32, Global>, InvalidWorkspaceIndex> { ) -> Result<Point<i32, Global>, InvalidWorkspaceIndex> {
let current_output = seat.active_output(); let current_output = seat.active_output();
let workspace = shell let active = shell.workspaces.active_num(&current_output).1;
.workspaces let mut workspace = active.checked_add(1).ok_or(InvalidWorkspaceIndex)?;
.active_num(&current_output)
.1 if workspace >= shell.workspaces.len(&current_output) {
.checked_add(1) workspace = 0;
.ok_or(InvalidWorkspaceIndex)?; }
if workspace == active {
return Err(InvalidWorkspaceIndex);
}
shell.activate( shell.activate(
&current_output, &current_output,
workspace, workspace,
if gesture { if gesture {
WorkspaceDelta::new_gesture() WorkspaceDelta::new_gesture(true)
} else { } else {
WorkspaceDelta::new_shortcut() WorkspaceDelta::new_shortcut()
}, },
@ -1134,18 +1137,24 @@ fn to_previous_workspace(
workspace_state: &mut WorkspaceUpdateGuard<'_, State>, workspace_state: &mut WorkspaceUpdateGuard<'_, State>,
) -> Result<Point<i32, Global>, InvalidWorkspaceIndex> { ) -> Result<Point<i32, Global>, InvalidWorkspaceIndex> {
let current_output = seat.active_output(); let current_output = seat.active_output();
let workspace = shell let active = shell.workspaces.active_num(&current_output).1;
.workspaces let workspace = active.checked_sub(1).unwrap_or(
.active_num(&current_output) shell
.1 .workspaces
.checked_sub(1) .len(&current_output)
.ok_or(InvalidWorkspaceIndex)?; .checked_sub(1)
.ok_or(InvalidWorkspaceIndex)?,
);
if workspace == active {
return Err(InvalidWorkspaceIndex);
}
shell.activate( shell.activate(
&current_output, &current_output,
workspace, workspace,
if gesture { if gesture {
WorkspaceDelta::new_gesture() WorkspaceDelta::new_gesture(false)
} else { } else {
WorkspaceDelta::new_shortcut() WorkspaceDelta::new_shortcut()
}, },

View file

@ -1056,10 +1056,12 @@ impl State {
} }
match gesture_state.action { 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( self.common.shell.write().update_workspace_delta(
&seat.active_output(), &seat.active_output(),
gesture_state.delta, gesture_state.delta,
matches!(x, SwipeAction::NextWorkspace),
) )
} }
_ => {} _ => {}

View file

@ -146,21 +146,32 @@ fn render_input_order_internal<R: 'static>(
}; };
let has_fullscreen = workspace.fullscreen.is_some(); let has_fullscreen = workspace.fullscreen.is_some();
let percentage = match start { let (forward, percentage) = match start {
WorkspaceDelta::Shortcut(st) => ease( WorkspaceDelta::Shortcut(st) => (
EaseInOutCubic, *previous_idx < current.1,
0.0, ease(
1.0, EaseInOutCubic,
Instant::now().duration_since(*st).as_millis() as f32 0.0,
/ ANIMATION_DURATION.as_millis() as f32, 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::<i32, Logical>::from(match (layout, *previous_idx < current.1) { let offset = Point::<i32, Logical>::from(match (layout, forward) {
(WorkspaceLayout::Vertical, true) => { (WorkspaceLayout::Vertical, true) => {
(0, (-output_size.h as f32 * percentage).round() as i32) (0, (-output_size.h as f32 * percentage).round() as i32)
} }
@ -177,7 +188,7 @@ fn render_input_order_internal<R: 'static>(
( (
Some((previous, has_fullscreen, offset)), Some((previous, has_fullscreen, offset)),
Point::<i32, Logical>::from(match (layout, *previous_idx < current.1) { Point::<i32, Logical>::from(match (layout, forward) {
(WorkspaceLayout::Vertical, true) => (0, output_size.h + offset.y), (WorkspaceLayout::Vertical, true) => (0, output_size.h + offset.y),
(WorkspaceLayout::Vertical, false) => (0, -(output_size.h - offset.y)), (WorkspaceLayout::Vertical, false) => (0, -(output_size.h - offset.y)),
(WorkspaceLayout::Horizontal, true) => (output_size.w + offset.x, 0), (WorkspaceLayout::Horizontal, true) => (output_size.w + offset.x, 0),

View file

@ -301,28 +301,39 @@ pub struct SessionLock {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum WorkspaceDelta { pub enum WorkspaceDelta {
Shortcut(Instant), Shortcut(Instant),
Gesture(f64), Gesture {
GestureEnd(Instant, Spring), percentage: f64,
forward: bool,
},
GestureEnd {
start: Instant,
spring: Spring,
forward: bool,
},
// InvalidGesture(f64), TODO // InvalidGesture(f64), TODO
// InvalidGestureEnd(Instant, Spring), TODO // InvalidGestureEnd(Instant, Spring), TODO
} }
impl WorkspaceDelta { impl WorkspaceDelta {
pub fn new_gesture() -> Self { pub fn new_gesture(forward: bool) -> Self {
WorkspaceDelta::Gesture(0.0) 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); let params: SpringParams = SpringParams::new(1.0, 1000.0, 0.0001);
WorkspaceDelta::GestureEnd( WorkspaceDelta::GestureEnd {
Instant::now(), start: Instant::now(),
Spring { forward,
spring: Spring {
from: delta, from: delta,
to: 1.0, to: 1.0,
initial_velocity: velocity, initial_velocity: velocity,
params, params,
}, },
) }
} }
pub fn new_shortcut() -> Self { pub fn new_shortcut() -> Self {
@ -332,7 +343,7 @@ impl WorkspaceDelta {
pub fn is_animating(&self) -> bool { pub fn is_animating(&self) -> bool {
matches!( matches!(
self, self,
WorkspaceDelta::Shortcut(_) | WorkspaceDelta::GestureEnd(_, _) WorkspaceDelta::Shortcut(_) | WorkspaceDelta::GestureEnd { .. }
) )
} }
} }
@ -497,8 +508,8 @@ impl WorkspaceSet {
} else { } else {
// snap to workspace, when in between workspaces due to swipe gesture // snap to workspace, when in between workspaces due to swipe gesture
if let Some((p_idx, p_delta)) = self.previously_active { if let Some((p_idx, p_delta)) = self.previously_active {
if matches!(p_delta, WorkspaceDelta::Gesture(..)) if matches!(p_delta, WorkspaceDelta::Gesture { .. })
&& matches!(workspace_delta, WorkspaceDelta::GestureEnd(..)) && matches!(workspace_delta, WorkspaceDelta::GestureEnd { .. })
{ {
self.previously_active = Some((p_idx, workspace_delta)); self.previously_active = Some((p_idx, workspace_delta));
} else { } else {
@ -521,10 +532,16 @@ impl WorkspaceSet {
Err(InvalidWorkspaceIndex) 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; let easing = delta.clamp(0.0, GESTURE_MAX_LENGTH).abs() / GESTURE_MAX_LENGTH;
if let Some((idx, _)) = self.previously_active { 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; self.previously_active = None;
} }
} }
WorkspaceDelta::GestureEnd(st, spring) => { WorkspaceDelta::GestureEnd { start, spring, .. } => {
if Instant::now().duration_since(st).as_millis() > spring.duration().as_millis() if Instant::now().duration_since(start).as_millis()
> spring.duration().as_millis()
{ {
self.previously_active = None; 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 { match &mut self.workspaces.mode {
WorkspaceMode::OutputBound => { WorkspaceMode::OutputBound => {
if let Some(set) = self.workspaces.sets.get_mut(output) { if let Some(set) = self.workspaces.sets.get_mut(output) {
set.update_workspace_delta(delta); set.update_workspace_delta(delta, forward);
} }
} }
WorkspaceMode::Global => { WorkspaceMode::Global => {
for set in self.workspaces.sets.values_mut() { 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(); 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) if (velocity > 0.0 && velocity.abs() >= GESTURE_VELOCITY_THRESHOLD)
|| (velocity.abs() < GESTURE_VELOCITY_THRESHOLD || (velocity.abs() < GESTURE_VELOCITY_THRESHOLD
&& delta.abs() > GESTURE_POSITION_THRESHOLD) && delta.abs() > GESTURE_POSITION_THRESHOLD)
{ {
set.activate( set.activate(
set.active, set.active,
WorkspaceDelta::new_gesture_end(delta.abs(), velocity.abs()), WorkspaceDelta::new_gesture_end(
delta.abs(),
velocity.abs(),
forward,
),
workspace_state, workspace_state,
)?; )?;
} else { } else {
set.activate_previous( 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, workspace_state,
)?; )?;
} }
@ -1635,19 +1668,34 @@ impl Shell {
} }
WorkspaceMode::Global => { WorkspaceMode::Global => {
for set in self.workspaces.sets.values_mut() { 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) if (velocity > 0.0 && velocity.abs() >= GESTURE_VELOCITY_THRESHOLD)
|| (velocity.abs() < GESTURE_VELOCITY_THRESHOLD || (velocity.abs() < GESTURE_VELOCITY_THRESHOLD
&& delta.abs() > GESTURE_POSITION_THRESHOLD) && delta.abs() > GESTURE_POSITION_THRESHOLD)
{ {
set.activate( set.activate(
set.active, set.active,
WorkspaceDelta::new_gesture_end(delta.abs(), velocity.abs()), WorkspaceDelta::new_gesture_end(
delta.abs(),
velocity.abs(),
forward,
),
workspace_state, workspace_state,
)?; )?;
} else { } else {
set.activate_previous( 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, workspace_state,
)?; )?;
} }