feat: workspace switching touchpad gestures
This commit is contained in:
parent
f1d8225ddb
commit
fc2173d028
9 changed files with 767 additions and 89 deletions
232
src/shell/mod.rs
232
src/shell/mod.rs
|
|
@ -46,6 +46,7 @@ use smithay::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
backend::render::animations::spring::{Spring, SpringParams},
|
||||
config::{Config, KeyModifiers, KeyPattern},
|
||||
state::client_should_see_privileged_protocols,
|
||||
utils::prelude::*,
|
||||
|
|
@ -93,6 +94,9 @@ use self::{
|
|||
};
|
||||
|
||||
const ANIMATION_DURATION: Duration = Duration::from_millis(200);
|
||||
const GESTURE_MAX_LENGTH: f64 = 150.0;
|
||||
const GESTURE_POSITION_THRESHOLD: f64 = 0.5;
|
||||
const GESTURE_VELOCITY_THRESHOLD: f64 = 0.02;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Trigger {
|
||||
|
|
@ -223,9 +227,41 @@ pub struct SessionLock {
|
|||
pub surfaces: HashMap<Output, LockSurface>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum WorkspaceDelta {
|
||||
Shortcut(Instant),
|
||||
Gesture(f64),
|
||||
GestureEnd(Instant, Spring),
|
||||
// InvalidGesture(f64), TODO
|
||||
// InvalidGestureEnd(Instant, Spring), TODO
|
||||
}
|
||||
|
||||
impl WorkspaceDelta {
|
||||
pub fn new_gesture() -> Self {
|
||||
WorkspaceDelta::Gesture(0.0)
|
||||
}
|
||||
|
||||
pub fn new_gesture_end(delta: f64, velocity: f64) -> Self {
|
||||
let params: SpringParams = SpringParams::new(1.0, 1000.0, 0.0001);
|
||||
WorkspaceDelta::GestureEnd(
|
||||
Instant::now(),
|
||||
Spring {
|
||||
from: delta,
|
||||
to: 1.0,
|
||||
initial_velocity: velocity,
|
||||
params,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_shortcut() -> Self {
|
||||
WorkspaceDelta::Shortcut(Instant::now())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WorkspaceSet {
|
||||
previously_active: Option<(usize, Instant)>,
|
||||
previously_active: Option<(usize, WorkspaceDelta)>,
|
||||
active: usize,
|
||||
pub group: WorkspaceGroupHandle,
|
||||
idx: usize,
|
||||
|
|
@ -371,6 +407,7 @@ impl WorkspaceSet {
|
|||
fn activate(
|
||||
&mut self,
|
||||
idx: usize,
|
||||
workspace_delta: WorkspaceDelta,
|
||||
state: &mut WorkspaceUpdateGuard<'_, State>,
|
||||
) -> Result<bool, InvalidWorkspaceIndex> {
|
||||
if idx >= self.workspaces.len() {
|
||||
|
|
@ -383,15 +420,36 @@ impl WorkspaceSet {
|
|||
state.remove_workspace_state(&self.workspaces[old_active].handle, WState::Urgent);
|
||||
state.remove_workspace_state(&self.workspaces[idx].handle, WState::Urgent);
|
||||
state.add_workspace_state(&self.workspaces[idx].handle, WState::Active);
|
||||
|
||||
self.previously_active = Some((old_active, Instant::now()));
|
||||
self.previously_active = Some((old_active, workspace_delta));
|
||||
self.active = idx;
|
||||
Ok(true)
|
||||
} else {
|
||||
if let Some((p_idx, _)) = self.previously_active {
|
||||
self.previously_active = Some((p_idx, workspace_delta));
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn activate_previous(
|
||||
&mut self,
|
||||
workspace_delta: WorkspaceDelta,
|
||||
state: &mut WorkspaceUpdateGuard<'_, State>,
|
||||
) -> Result<bool, InvalidWorkspaceIndex> {
|
||||
if let Some((idx, _)) = self.previously_active {
|
||||
return self.activate(idx, workspace_delta, state);
|
||||
}
|
||||
Err(InvalidWorkspaceIndex)
|
||||
}
|
||||
|
||||
fn update_workspace_delta(&mut self, delta: f64) {
|
||||
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)));
|
||||
}
|
||||
}
|
||||
|
||||
fn set_output(
|
||||
&mut self,
|
||||
new_output: &Output,
|
||||
|
|
@ -410,8 +468,21 @@ impl WorkspaceSet {
|
|||
|
||||
fn refresh<'a>(&mut self, xdg_activation_state: &XdgActivationState) {
|
||||
if let Some((_, start)) = self.previously_active {
|
||||
if Instant::now().duration_since(start).as_millis() >= ANIMATION_DURATION.as_millis() {
|
||||
self.previously_active = None;
|
||||
match start {
|
||||
WorkspaceDelta::Shortcut(st) => {
|
||||
if Instant::now().duration_since(st).as_millis() as f32
|
||||
>= ANIMATION_DURATION.as_millis() as f32
|
||||
{
|
||||
self.previously_active = None;
|
||||
}
|
||||
}
|
||||
WorkspaceDelta::GestureEnd(st, spring) => {
|
||||
if Instant::now().duration_since(st).as_millis() > spring.duration().as_millis()
|
||||
{
|
||||
self.previously_active = None;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
self.workspaces[self.active].refresh(xdg_activation_state);
|
||||
|
|
@ -855,7 +926,7 @@ impl Workspaces {
|
|||
.and_then(|set| set.workspaces.get_mut(num))
|
||||
}
|
||||
|
||||
pub fn active(&self, output: &Output) -> (Option<(&Workspace, Instant)>, &Workspace) {
|
||||
pub fn active(&self, output: &Output) -> (Option<(&Workspace, WorkspaceDelta)>, &Workspace) {
|
||||
let set = self.sets.get(output).or(self.backup_set.as_ref()).unwrap();
|
||||
(
|
||||
set.previously_active
|
||||
|
|
@ -1095,6 +1166,7 @@ impl Shell {
|
|||
&mut self,
|
||||
output: &Output,
|
||||
idx: usize,
|
||||
workspace_delta: WorkspaceDelta,
|
||||
) -> Result<Option<Point<i32, Global>>, InvalidWorkspaceIndex> {
|
||||
match &mut self.workspaces.mode {
|
||||
WorkspaceMode::OutputBound => {
|
||||
|
|
@ -1105,7 +1177,7 @@ impl Shell {
|
|||
) {
|
||||
set.workspaces[set.active].tiling_layer.cleanup_drag();
|
||||
}
|
||||
set.activate(idx, &mut self.workspace_state.update())?;
|
||||
set.activate(idx, workspace_delta, &mut self.workspace_state.update())?;
|
||||
if let Some(xwm) = self
|
||||
.xwayland_state
|
||||
.as_mut()
|
||||
|
|
@ -1151,7 +1223,143 @@ impl Shell {
|
|||
}
|
||||
WorkspaceMode::Global => {
|
||||
for set in self.workspaces.sets.values_mut() {
|
||||
set.activate(idx, &mut self.workspace_state.update())?;
|
||||
set.activate(idx, workspace_delta, &mut self.workspace_state.update())?;
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_workspace_delta(&mut self, output: &Output, delta: f64) {
|
||||
match &mut self.workspaces.mode {
|
||||
WorkspaceMode::OutputBound => {
|
||||
if let Some(set) = self.workspaces.sets.get_mut(output) {
|
||||
set.update_workspace_delta(delta);
|
||||
}
|
||||
}
|
||||
WorkspaceMode::Global => {
|
||||
for set in self.workspaces.sets.values_mut() {
|
||||
set.update_workspace_delta(delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end_workspace_swipe(
|
||||
&mut self,
|
||||
output: &Output,
|
||||
velocity: f64,
|
||||
) -> Result<Option<Point<i32, Global>>, InvalidWorkspaceIndex> {
|
||||
match &mut self.workspaces.mode {
|
||||
WorkspaceMode::OutputBound => {
|
||||
if let Some(set) = self.workspaces.sets.get_mut(output) {
|
||||
if matches!(
|
||||
self.overview_mode,
|
||||
OverviewMode::Started(Trigger::Pointer(_), _)
|
||||
) {
|
||||
set.workspaces[set.active].tiling_layer.cleanup_drag();
|
||||
}
|
||||
if let Some((_, workspace_delta)) = set.previously_active {
|
||||
match workspace_delta {
|
||||
WorkspaceDelta::Gesture(delta) => {
|
||||
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(),
|
||||
),
|
||||
&mut self.workspace_state.update(),
|
||||
)?;
|
||||
} else {
|
||||
set.activate_previous(
|
||||
WorkspaceDelta::new_gesture_end(
|
||||
1.0 - delta.abs(),
|
||||
velocity.abs(),
|
||||
),
|
||||
&mut self.workspace_state.update(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
_ => {} // Do nothing
|
||||
}
|
||||
}
|
||||
if let Some(xwm) = self
|
||||
.xwayland_state
|
||||
.as_mut()
|
||||
.and_then(|state| state.xwm.as_mut())
|
||||
{
|
||||
{
|
||||
for window in set.workspaces[set.active]
|
||||
.tiling_layer
|
||||
.mapped()
|
||||
.map(|(w, _)| w)
|
||||
.chain(set.workspaces[set.active].floating_layer.space.elements())
|
||||
{
|
||||
if let Some(surf) = window.active_window().x11_surface() {
|
||||
let _ = xwm.raise_window(surf);
|
||||
}
|
||||
}
|
||||
for window in set.sticky_layer.space.elements() {
|
||||
if let Some(surf) = window.active_window().x11_surface() {
|
||||
let _ = xwm.raise_window(surf);
|
||||
}
|
||||
}
|
||||
if let Some(surf) = set.workspaces[set.active]
|
||||
.fullscreen
|
||||
.as_ref()
|
||||
.and_then(|f| f.surface.x11_surface())
|
||||
{
|
||||
let _ = xwm.raise_window(surf);
|
||||
}
|
||||
}
|
||||
for surface in &self.override_redirect_windows {
|
||||
let _ = xwm.raise_window(surface);
|
||||
}
|
||||
}
|
||||
|
||||
let output_geo = output.geometry();
|
||||
Ok(Some(
|
||||
output_geo.loc
|
||||
+ Point::from((output_geo.size.w / 2, output_geo.size.h / 2)),
|
||||
))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
WorkspaceMode::Global => {
|
||||
for set in self.workspaces.sets.values_mut() {
|
||||
if let Some((_, workspace_delta)) = set.previously_active {
|
||||
match workspace_delta {
|
||||
WorkspaceDelta::Gesture(delta) => {
|
||||
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(),
|
||||
),
|
||||
&mut self.workspace_state.update(),
|
||||
)?;
|
||||
} else {
|
||||
set.activate_previous(
|
||||
WorkspaceDelta::new_gesture_end(
|
||||
1.0 - delta.abs(),
|
||||
velocity.abs(),
|
||||
),
|
||||
&mut self.workspace_state.update(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
_ => {} // Do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
|
@ -1923,7 +2131,13 @@ impl Shell {
|
|||
.shell
|
||||
.workspaces
|
||||
.idx_for_handle(&to_output, to)
|
||||
.and_then(|to_idx| state.common.shell.activate(&to_output, to_idx).unwrap())
|
||||
.and_then(|to_idx| {
|
||||
state
|
||||
.common
|
||||
.shell
|
||||
.activate(&to_output, to_idx, WorkspaceDelta::new_shortcut())
|
||||
.unwrap()
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue