feat: workspace switching touchpad gestures
This commit is contained in:
parent
f1d8225ddb
commit
fc2173d028
9 changed files with 767 additions and 89 deletions
1
src/backend/render/animations/mod.rs
Normal file
1
src/backend/render/animations/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
pub mod spring;
|
||||||
138
src/backend/render/animations/spring.rs
Normal file
138
src/backend/render/animations/spring.rs
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
// From Niri (GPL-3.0) https://github.com/YaLTeR/niri/tree/db49deb7fd2fbe805ceec060aa4dec65009ad7a7
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct SpringParams {
|
||||||
|
pub damping: f64,
|
||||||
|
pub mass: f64,
|
||||||
|
pub stiffness: f64,
|
||||||
|
pub epsilon: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Spring {
|
||||||
|
pub from: f64,
|
||||||
|
pub to: f64,
|
||||||
|
pub initial_velocity: f64,
|
||||||
|
pub params: SpringParams,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpringParams {
|
||||||
|
pub fn new(damping_ratio: f64, stiffness: f64, epsilon: f64) -> Self {
|
||||||
|
let damping_ratio = damping_ratio.max(0.);
|
||||||
|
let stiffness = stiffness.max(0.);
|
||||||
|
let epsilon = epsilon.max(0.);
|
||||||
|
|
||||||
|
let mass = 1.;
|
||||||
|
let critical_damping = 2. * (mass * stiffness).sqrt();
|
||||||
|
let damping = damping_ratio * critical_damping;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
damping,
|
||||||
|
mass,
|
||||||
|
stiffness,
|
||||||
|
epsilon,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Spring {
|
||||||
|
pub fn value_at(&self, t: Duration) -> f64 {
|
||||||
|
self.oscillate(t.as_secs_f64())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Based on libadwaita (LGPL-2.1-or-later):
|
||||||
|
// https://gitlab.gnome.org/GNOME/libadwaita/-/blob/1.4.4/src/adw-spring-animation.c,
|
||||||
|
// which itself is based on (MIT):
|
||||||
|
// https://github.com/robb/RBBAnimation/blob/master/RBBAnimation/RBBSpringAnimation.m
|
||||||
|
/// Computes and returns the duration until the spring is at rest.
|
||||||
|
pub fn duration(&self) -> Duration {
|
||||||
|
const DELTA: f64 = 0.001;
|
||||||
|
|
||||||
|
let beta = self.params.damping / (2. * self.params.mass);
|
||||||
|
|
||||||
|
if beta.abs() <= f64::EPSILON || beta < 0. {
|
||||||
|
return Duration::MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
let omega0 = (self.params.stiffness / self.params.mass).sqrt();
|
||||||
|
|
||||||
|
// As first ansatz for the overdamped solution,
|
||||||
|
// and general estimation for the oscillating ones
|
||||||
|
// we take the value of the envelope when it's < epsilon.
|
||||||
|
let mut x0 = -self.params.epsilon.ln() / beta;
|
||||||
|
|
||||||
|
// f64::EPSILON is too small for this specific comparison, so we use
|
||||||
|
// f32::EPSILON even though it's doubles.
|
||||||
|
if (beta - omega0).abs() <= f64::from(f32::EPSILON) || beta < omega0 {
|
||||||
|
return Duration::from_secs_f64(x0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since the overdamped solution decays way slower than the envelope
|
||||||
|
// we need to use the value of the oscillation itself.
|
||||||
|
// Newton's root finding method is a good candidate in this particular case:
|
||||||
|
// https://en.wikipedia.org/wiki/Newton%27s_method
|
||||||
|
let mut y0 = self.oscillate(x0);
|
||||||
|
let m = (self.oscillate(x0 + DELTA) - y0) / DELTA;
|
||||||
|
|
||||||
|
let mut x1 = (self.to - y0 + m * x0) / m;
|
||||||
|
let mut y1 = self.oscillate(x1);
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
while (self.to - y1).abs() > self.params.epsilon {
|
||||||
|
if i > 1000 {
|
||||||
|
return Duration::ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
x0 = x1;
|
||||||
|
y0 = y1;
|
||||||
|
|
||||||
|
let m = (self.oscillate(x0 + DELTA) - y0) / DELTA;
|
||||||
|
|
||||||
|
x1 = (self.to - y0 + m * x0) / m;
|
||||||
|
y1 = self.oscillate(x1);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Duration::from_secs_f64(x1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the spring position at a given time in seconds.
|
||||||
|
fn oscillate(&self, t: f64) -> f64 {
|
||||||
|
let b = self.params.damping;
|
||||||
|
let m = self.params.mass;
|
||||||
|
let k = self.params.stiffness;
|
||||||
|
let v0 = self.initial_velocity;
|
||||||
|
|
||||||
|
let beta = b / (2. * m);
|
||||||
|
let omega0 = (k / m).sqrt();
|
||||||
|
|
||||||
|
let x0 = self.from - self.to;
|
||||||
|
|
||||||
|
let envelope = (-beta * t).exp();
|
||||||
|
|
||||||
|
// Solutions of the form C1*e^(lambda1*x) + C2*e^(lambda2*x)
|
||||||
|
// for the differential equation m*ẍ+b*ẋ+kx = 0
|
||||||
|
|
||||||
|
// f64::EPSILON is too small for this specific comparison, so we use
|
||||||
|
// f32::EPSILON even though it's doubles.
|
||||||
|
if (beta - omega0).abs() <= f64::from(f32::EPSILON) {
|
||||||
|
// Critically damped.
|
||||||
|
self.to + envelope * (x0 + (beta * x0 + v0) * t)
|
||||||
|
} else if beta < omega0 {
|
||||||
|
// Underdamped.
|
||||||
|
let omega1 = ((omega0 * omega0) - (beta * beta)).sqrt();
|
||||||
|
|
||||||
|
self.to
|
||||||
|
+ envelope
|
||||||
|
* (x0 * (omega1 * t).cos() + ((beta * x0 + v0) / omega1) * (omega1 * t).sin())
|
||||||
|
} else {
|
||||||
|
// Overdamped.
|
||||||
|
let omega2 = ((beta * beta) - (omega0 * omega0)).sqrt();
|
||||||
|
|
||||||
|
self.to
|
||||||
|
+ envelope
|
||||||
|
* (x0 * (omega2 * t).cosh() + ((beta * x0 + v0) / omega2) * (omega2 * t).sinh())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,7 +16,7 @@ use crate::{
|
||||||
grabs::{SeatMenuGrabState, SeatMoveGrabState},
|
grabs::{SeatMenuGrabState, SeatMoveGrabState},
|
||||||
layout::tiling::ANIMATION_DURATION,
|
layout::tiling::ANIMATION_DURATION,
|
||||||
CosmicMapped, CosmicMappedRenderElement, OverviewMode, SessionLock, Trigger,
|
CosmicMapped, CosmicMappedRenderElement, OverviewMode, SessionLock, Trigger,
|
||||||
WorkspaceRenderElement,
|
WorkspaceDelta, WorkspaceRenderElement,
|
||||||
},
|
},
|
||||||
state::{Common, Fps},
|
state::{Common, Fps},
|
||||||
utils::prelude::*,
|
utils::prelude::*,
|
||||||
|
|
@ -70,6 +70,8 @@ use smithay::{
|
||||||
};
|
};
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
|
pub mod animations;
|
||||||
|
|
||||||
pub mod cursor;
|
pub mod cursor;
|
||||||
use self::cursor::CursorRenderElement;
|
use self::cursor::CursorRenderElement;
|
||||||
pub mod element;
|
pub mod element;
|
||||||
|
|
@ -468,7 +470,7 @@ pub fn workspace_elements<R>(
|
||||||
renderer: &mut R,
|
renderer: &mut R,
|
||||||
state: &mut Common,
|
state: &mut Common,
|
||||||
output: &Output,
|
output: &Output,
|
||||||
previous: Option<(WorkspaceHandle, usize, Instant)>,
|
previous: Option<(WorkspaceHandle, usize, WorkspaceDelta)>,
|
||||||
current: (WorkspaceHandle, usize),
|
current: (WorkspaceHandle, usize),
|
||||||
cursor_mode: CursorMode,
|
cursor_mode: CursorMode,
|
||||||
_fps: &mut Option<&mut Fps>,
|
_fps: &mut Option<&mut Fps>,
|
||||||
|
|
@ -684,10 +686,18 @@ where
|
||||||
let has_fullscreen = workspace.fullscreen.is_some();
|
let has_fullscreen = workspace.fullscreen.is_some();
|
||||||
let is_active_space = workspace.outputs().any(|o| o == &active_output);
|
let is_active_space = workspace.outputs().any(|o| o == &active_output);
|
||||||
|
|
||||||
let percentage = {
|
let percentage = match start {
|
||||||
let percentage = Instant::now().duration_since(*start).as_millis() as f32
|
WorkspaceDelta::Shortcut(st) => ease(
|
||||||
/ ANIMATION_DURATION.as_millis() as f32;
|
EaseInOutCubic,
|
||||||
ease(EaseInOutCubic, 0.0, 1.0, percentage)
|
0.0,
|
||||||
|
1.0,
|
||||||
|
Instant::now().duration_since(*st).as_millis() as f32
|
||||||
|
/ ANIMATION_DURATION.as_millis() as f32,
|
||||||
|
),
|
||||||
|
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, *previous_idx < current.1) {
|
||||||
(WorkspaceLayout::Vertical, true) => {
|
(WorkspaceLayout::Vertical, true) => {
|
||||||
|
|
@ -1012,7 +1022,7 @@ pub fn render_workspace<R, Target, OffTarget, Source>(
|
||||||
age: usize,
|
age: usize,
|
||||||
state: &mut Common,
|
state: &mut Common,
|
||||||
output: &Output,
|
output: &Output,
|
||||||
previous: Option<(WorkspaceHandle, usize, Instant)>,
|
previous: Option<(WorkspaceHandle, usize, WorkspaceDelta)>,
|
||||||
current: (WorkspaceHandle, usize),
|
current: (WorkspaceHandle, usize),
|
||||||
mut cursor_mode: CursorMode,
|
mut cursor_mode: CursorMode,
|
||||||
screencopy: Option<(Source, &[(ScreencopySession, BufferParams)])>,
|
screencopy: Option<(Source, &[(ScreencopySession, BufferParams)])>,
|
||||||
|
|
|
||||||
176
src/input/gestures/mod.rs
Normal file
176
src/input/gestures/mod.rs
Normal file
|
|
@ -0,0 +1,176 @@
|
||||||
|
use smithay::utils::{Logical, Point};
|
||||||
|
use std::{collections::VecDeque, time::Duration};
|
||||||
|
use tracing::trace;
|
||||||
|
|
||||||
|
use crate::shell::Direction;
|
||||||
|
|
||||||
|
const HISTORY_LIMIT: Duration = Duration::from_millis(150);
|
||||||
|
const DECELERATION_TOUCHPAD: f64 = 0.997;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct SwipeEvent {
|
||||||
|
delta: f64,
|
||||||
|
timestamp: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum SwipeAction {
|
||||||
|
NextWorkspace,
|
||||||
|
PrevWorkspace,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct GestureState {
|
||||||
|
pub fingers: u32,
|
||||||
|
pub direction: Option<Direction>,
|
||||||
|
pub action: Option<SwipeAction>,
|
||||||
|
pub delta: f64,
|
||||||
|
// Delta tracking inspired by Niri (GPL-3.0) https://github.com/YaLTeR/niri/tree/v0.1.3
|
||||||
|
pub history: VecDeque<SwipeEvent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GestureState {
|
||||||
|
pub fn new(fingers: u32) -> Self {
|
||||||
|
GestureState {
|
||||||
|
fingers,
|
||||||
|
direction: None,
|
||||||
|
action: None,
|
||||||
|
delta: 0.0,
|
||||||
|
history: VecDeque::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, movement: Point<f64, Logical>, timestamp: Duration) -> bool {
|
||||||
|
let first_update = self.direction.is_none();
|
||||||
|
|
||||||
|
if first_update {
|
||||||
|
// Find proper direction
|
||||||
|
self.direction = if movement.x.abs() > movement.y.abs() {
|
||||||
|
if movement.x > 0.0 {
|
||||||
|
Some(Direction::Right)
|
||||||
|
} else {
|
||||||
|
Some(Direction::Left)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if movement.y > 0.0 {
|
||||||
|
Some(Direction::Down)
|
||||||
|
} else {
|
||||||
|
Some(Direction::Up)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let delta = match self.direction {
|
||||||
|
Some(Direction::Left) => -movement.x,
|
||||||
|
Some(Direction::Right) => movement.x,
|
||||||
|
Some(Direction::Up) => -movement.y,
|
||||||
|
Some(Direction::Down) => movement.y,
|
||||||
|
None => 0.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.push(delta, timestamp);
|
||||||
|
first_update
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pushes a new reading into the tracker.
|
||||||
|
fn push(&mut self, delta: f64, timestamp: Duration) {
|
||||||
|
// For the events that we care about, timestamps should always increase
|
||||||
|
// monotonically.
|
||||||
|
if let Some(last) = self.history.back() {
|
||||||
|
if timestamp < last.timestamp {
|
||||||
|
trace!(
|
||||||
|
"ignoring event with timestamp {timestamp:?} earlier than last {:?}",
|
||||||
|
last.timestamp
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.history.push_back(SwipeEvent { delta, timestamp });
|
||||||
|
self.delta += delta;
|
||||||
|
|
||||||
|
self.trim_history();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the current gesture velocity.
|
||||||
|
pub fn velocity(&self) -> f64 {
|
||||||
|
let (Some(first), Some(last)) = (self.history.front(), self.history.back()) else {
|
||||||
|
return 0.;
|
||||||
|
};
|
||||||
|
|
||||||
|
let total_time = (last.timestamp - first.timestamp).as_secs_f64();
|
||||||
|
if total_time == 0. {
|
||||||
|
return 0.;
|
||||||
|
}
|
||||||
|
|
||||||
|
let total_delta = self.history.iter().map(|event| event.delta).sum::<f64>();
|
||||||
|
total_delta / total_time
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the gesture end position after decelerating to a halt.
|
||||||
|
pub fn projected_end_pos(&self) -> f64 {
|
||||||
|
let vel = self.velocity();
|
||||||
|
self.delta - vel / (1000. * DECELERATION_TOUCHPAD.ln())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn trim_history(&mut self) {
|
||||||
|
let Some(&SwipeEvent { timestamp, .. }) = self.history.back() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
while let Some(first) = self.history.front() {
|
||||||
|
if timestamp <= first.timestamp + HISTORY_LIMIT {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = self.history.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for GestureState {
|
||||||
|
fn default() -> Self {
|
||||||
|
GestureState::new(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rubber_band.rs from Niri (GPL-3.0) https://github.com/YaLTeR/niri/blob/db49deb7fd2fbe805ceec060aa4dec65009ad7a7/src/rubber_band.rs
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct RubberBand {
|
||||||
|
pub stiffness: f64,
|
||||||
|
pub limit: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RubberBand {
|
||||||
|
pub fn band(&self, x: f64) -> f64 {
|
||||||
|
let c = self.stiffness;
|
||||||
|
let d = self.limit;
|
||||||
|
|
||||||
|
(1. - (1. / (x * c / d + 1.))) * d
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn derivative(&self, x: f64) -> f64 {
|
||||||
|
let c = self.stiffness;
|
||||||
|
let d = self.limit;
|
||||||
|
|
||||||
|
c * d * d / (c * x + d).powi(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clamp(&self, min: f64, max: f64, x: f64) -> f64 {
|
||||||
|
let clamped = x.clamp(min, max);
|
||||||
|
let sign = if x < clamped { -1. } else { 1. };
|
||||||
|
let diff = (x - clamped).abs();
|
||||||
|
|
||||||
|
clamped + sign * self.band(diff)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clamp_derivative(&self, min: f64, max: f64, x: f64) -> f64 {
|
||||||
|
if min <= x && x <= max {
|
||||||
|
return 1.;
|
||||||
|
}
|
||||||
|
|
||||||
|
let clamped = x.clamp(min, max);
|
||||||
|
let diff = (x - clamped).abs();
|
||||||
|
self.derivative(diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
266
src/input/mod.rs
266
src/input/mod.rs
|
|
@ -3,6 +3,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::render::cursor::CursorState,
|
backend::render::cursor::CursorState,
|
||||||
config::{xkb_config_to_wl, Action, Config, KeyModifiers, KeyPattern},
|
config::{xkb_config_to_wl, Action, Config, KeyModifiers, KeyPattern},
|
||||||
|
input::gestures::{GestureState, SwipeAction},
|
||||||
shell::{
|
shell::{
|
||||||
focus::{target::PointerFocusTarget, FocusDirection},
|
focus::{target::PointerFocusTarget, FocusDirection},
|
||||||
grabs::{ResizeEdge, SeatMenuGrabState, SeatMoveGrabState},
|
grabs::{ResizeEdge, SeatMenuGrabState, SeatMoveGrabState},
|
||||||
|
|
@ -10,7 +11,8 @@ use crate::{
|
||||||
floating::ResizeGrabMarker,
|
floating::ResizeGrabMarker,
|
||||||
tiling::{SwapWindowGrab, TilingLayout},
|
tiling::{SwapWindowGrab, TilingLayout},
|
||||||
},
|
},
|
||||||
Direction, FocusResult, MoveResult, OverviewMode, ResizeDirection, ResizeMode, Trigger,
|
Direction, FocusResult, InvalidWorkspaceIndex, MoveResult, OverviewMode, ResizeDirection,
|
||||||
|
ResizeMode, Trigger, WorkspaceDelta,
|
||||||
},
|
},
|
||||||
state::Common,
|
state::Common,
|
||||||
utils::prelude::*,
|
utils::prelude::*,
|
||||||
|
|
@ -68,6 +70,8 @@ use std::{
|
||||||
|
|
||||||
crate::utils::id_gen!(next_seat_id, SEAT_ID, SEAT_IDS);
|
crate::utils::id_gen!(next_seat_id, SEAT_ID, SEAT_IDS);
|
||||||
|
|
||||||
|
pub mod gestures;
|
||||||
|
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct SeatId(pub usize);
|
pub struct SeatId(pub usize);
|
||||||
pub struct ActiveOutput(pub RefCell<Output>);
|
pub struct ActiveOutput(pub RefCell<Output>);
|
||||||
|
|
@ -1071,42 +1075,127 @@ impl State {
|
||||||
}
|
}
|
||||||
InputEvent::GestureSwipeBegin { event, .. } => {
|
InputEvent::GestureSwipeBegin { event, .. } => {
|
||||||
if let Some(seat) = self.common.seat_with_device(&event.device()) {
|
if let Some(seat) = self.common.seat_with_device(&event.device()) {
|
||||||
let serial = SERIAL_COUNTER.next_serial();
|
if event.fingers() >= 3 {
|
||||||
let pointer = seat.get_pointer().unwrap();
|
self.common.gesture_state = Some(GestureState::new(event.fingers()));
|
||||||
pointer.gesture_swipe_begin(
|
} else {
|
||||||
self,
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
&GestureSwipeBeginEvent {
|
let pointer = seat.get_pointer().unwrap();
|
||||||
serial,
|
pointer.gesture_swipe_begin(
|
||||||
time: event.time_msec(),
|
self,
|
||||||
fingers: event.fingers(),
|
&GestureSwipeBeginEvent {
|
||||||
},
|
serial,
|
||||||
);
|
time: event.time_msec(),
|
||||||
|
fingers: event.fingers(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InputEvent::GestureSwipeUpdate { event, .. } => {
|
InputEvent::GestureSwipeUpdate { event, .. } => {
|
||||||
if let Some(seat) = self.common.seat_with_device(&event.device()) {
|
if let Some(seat) = self.common.seat_with_device(&event.device()).cloned() {
|
||||||
let pointer = seat.get_pointer().unwrap();
|
let mut activate_action: Option<SwipeAction> = None;
|
||||||
pointer.gesture_swipe_update(
|
if let Some(ref mut gesture_state) = self.common.gesture_state {
|
||||||
self,
|
let first_update = gesture_state.update(
|
||||||
&GestureSwipeUpdateEvent {
|
event.delta(),
|
||||||
time: event.time_msec(),
|
Duration::from_millis(event.time_msec() as u64),
|
||||||
delta: event.delta(),
|
);
|
||||||
},
|
// Decide on action if first update
|
||||||
);
|
if first_update {
|
||||||
|
activate_action = match gesture_state.fingers {
|
||||||
|
3 => None, // TODO: 3 finger gestures
|
||||||
|
4 => {
|
||||||
|
if self.common.config.cosmic_conf.workspaces.workspace_layout
|
||||||
|
== WorkspaceLayout::Horizontal
|
||||||
|
{
|
||||||
|
match gesture_state.direction {
|
||||||
|
Some(Direction::Left) => {
|
||||||
|
Some(SwipeAction::NextWorkspace)
|
||||||
|
}
|
||||||
|
Some(Direction::Right) => {
|
||||||
|
Some(SwipeAction::PrevWorkspace)
|
||||||
|
}
|
||||||
|
_ => None, // TODO: Other actions
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match gesture_state.direction {
|
||||||
|
Some(Direction::Up) => Some(SwipeAction::NextWorkspace),
|
||||||
|
Some(Direction::Down) => {
|
||||||
|
Some(SwipeAction::PrevWorkspace)
|
||||||
|
}
|
||||||
|
_ => None, // TODO: Other actions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
gesture_state.action = activate_action;
|
||||||
|
}
|
||||||
|
|
||||||
|
match gesture_state.action {
|
||||||
|
Some(SwipeAction::NextWorkspace) | Some(SwipeAction::PrevWorkspace) => {
|
||||||
|
self.common.shell.update_workspace_delta(
|
||||||
|
&seat.active_output(),
|
||||||
|
gesture_state.delta,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let pointer = seat.get_pointer().unwrap();
|
||||||
|
pointer.gesture_swipe_update(
|
||||||
|
self,
|
||||||
|
&GestureSwipeUpdateEvent {
|
||||||
|
time: event.time_msec(),
|
||||||
|
delta: event.delta(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
match activate_action {
|
||||||
|
Some(SwipeAction::NextWorkspace) => {
|
||||||
|
let _ = self.to_next_workspace(&seat, true);
|
||||||
|
}
|
||||||
|
Some(SwipeAction::PrevWorkspace) => {
|
||||||
|
let _ = self.to_previous_workspace(&seat, true);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InputEvent::GestureSwipeEnd { event, .. } => {
|
InputEvent::GestureSwipeEnd { event, .. } => {
|
||||||
if let Some(seat) = self.common.seat_with_device(&event.device()) {
|
if let Some(seat) = self.common.seat_with_device(&event.device()).cloned() {
|
||||||
let serial = SERIAL_COUNTER.next_serial();
|
if let Some(ref gesture_state) = self.common.gesture_state {
|
||||||
let pointer = seat.get_pointer().unwrap();
|
match gesture_state.action {
|
||||||
pointer.gesture_swipe_end(
|
Some(SwipeAction::NextWorkspace) | Some(SwipeAction::PrevWorkspace) => {
|
||||||
self,
|
let velocity = gesture_state.velocity();
|
||||||
&GestureSwipeEndEvent {
|
let norm_velocity =
|
||||||
serial,
|
if self.common.config.cosmic_conf.workspaces.workspace_layout
|
||||||
time: event.time_msec(),
|
== WorkspaceLayout::Horizontal
|
||||||
cancelled: event.cancelled(),
|
{
|
||||||
},
|
velocity / seat.active_output().geometry().size.w as f64
|
||||||
);
|
} else {
|
||||||
|
velocity / seat.active_output().geometry().size.h as f64
|
||||||
|
};
|
||||||
|
let _ = self
|
||||||
|
.common
|
||||||
|
.shell
|
||||||
|
.end_workspace_swipe(&seat.active_output(), norm_velocity);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
self.common.gesture_state = None;
|
||||||
|
} else {
|
||||||
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
|
let pointer = seat.get_pointer().unwrap();
|
||||||
|
pointer.gesture_swipe_end(
|
||||||
|
self,
|
||||||
|
&GestureSwipeEndEvent {
|
||||||
|
serial,
|
||||||
|
time: event.time_msec(),
|
||||||
|
cancelled: event.cancelled(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InputEvent::GesturePinchBegin { event, .. } => {
|
InputEvent::GesturePinchBegin { event, .. } => {
|
||||||
|
|
@ -1471,10 +1560,11 @@ impl State {
|
||||||
0 => 9,
|
0 => 9,
|
||||||
x => x - 1,
|
x => x - 1,
|
||||||
};
|
};
|
||||||
let _ = self
|
let _ = self.common.shell.activate(
|
||||||
.common
|
¤t_output,
|
||||||
.shell
|
workspace as usize,
|
||||||
.activate(¤t_output, workspace as usize);
|
WorkspaceDelta::new_shortcut(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Action::LastWorkspace => {
|
Action::LastWorkspace => {
|
||||||
let current_output = seat.active_output();
|
let current_output = seat.active_output();
|
||||||
|
|
@ -1484,24 +1574,14 @@ impl State {
|
||||||
.workspaces
|
.workspaces
|
||||||
.len(¤t_output)
|
.len(¤t_output)
|
||||||
.saturating_sub(1);
|
.saturating_sub(1);
|
||||||
let _ = self.common.shell.activate(¤t_output, workspace);
|
let _ = self.common.shell.activate(
|
||||||
|
¤t_output,
|
||||||
|
workspace,
|
||||||
|
WorkspaceDelta::new_shortcut(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Action::NextWorkspace => {
|
Action::NextWorkspace => {
|
||||||
let current_output = seat.active_output();
|
if self.to_next_workspace(seat, false).is_err() && propagate {
|
||||||
let workspace = self
|
|
||||||
.common
|
|
||||||
.shell
|
|
||||||
.workspaces
|
|
||||||
.active_num(¤t_output)
|
|
||||||
.1
|
|
||||||
.saturating_add(1);
|
|
||||||
if self
|
|
||||||
.common
|
|
||||||
.shell
|
|
||||||
.activate(¤t_output, workspace)
|
|
||||||
.is_err()
|
|
||||||
&& propagate
|
|
||||||
{
|
|
||||||
if let Some(inferred) = pattern.inferred_direction() {
|
if let Some(inferred) = pattern.inferred_direction() {
|
||||||
self.handle_action(
|
self.handle_action(
|
||||||
Action::SwitchOutput(inferred),
|
Action::SwitchOutput(inferred),
|
||||||
|
|
@ -1516,21 +1596,7 @@ impl State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Action::PreviousWorkspace => {
|
Action::PreviousWorkspace => {
|
||||||
let current_output = seat.active_output();
|
if self.to_previous_workspace(seat, false).is_err() && propagate {
|
||||||
let workspace = self
|
|
||||||
.common
|
|
||||||
.shell
|
|
||||||
.workspaces
|
|
||||||
.active_num(¤t_output)
|
|
||||||
.1
|
|
||||||
.saturating_sub(1);
|
|
||||||
if self
|
|
||||||
.common
|
|
||||||
.shell
|
|
||||||
.activate(¤t_output, workspace)
|
|
||||||
.is_err()
|
|
||||||
&& propagate
|
|
||||||
{
|
|
||||||
if let Some(inferred) = pattern.inferred_direction() {
|
if let Some(inferred) = pattern.inferred_direction() {
|
||||||
self.handle_action(
|
self.handle_action(
|
||||||
Action::SwitchOutput(inferred),
|
Action::SwitchOutput(inferred),
|
||||||
|
|
@ -1663,7 +1729,11 @@ impl State {
|
||||||
|
|
||||||
if let Some(next_output) = next_output {
|
if let Some(next_output) = next_output {
|
||||||
let idx = self.common.shell.workspaces.active_num(&next_output).1;
|
let idx = self.common.shell.workspaces.active_num(&next_output).1;
|
||||||
match self.common.shell.activate(&next_output, idx) {
|
match self.common.shell.activate(
|
||||||
|
&next_output,
|
||||||
|
idx,
|
||||||
|
WorkspaceDelta::new_shortcut(),
|
||||||
|
) {
|
||||||
Ok(Some(new_pos)) => {
|
Ok(Some(new_pos)) => {
|
||||||
seat.set_active_output(&next_output);
|
seat.set_active_output(&next_output);
|
||||||
if let Some(ptr) = seat.get_pointer() {
|
if let Some(ptr) = seat.get_pointer() {
|
||||||
|
|
@ -1726,7 +1796,11 @@ impl State {
|
||||||
.cloned();
|
.cloned();
|
||||||
if let Some(next_output) = next_output {
|
if let Some(next_output) = next_output {
|
||||||
let idx = self.common.shell.workspaces.active_num(&next_output).1;
|
let idx = self.common.shell.workspaces.active_num(&next_output).1;
|
||||||
match self.common.shell.activate(&next_output, idx) {
|
match self.common.shell.activate(
|
||||||
|
&next_output,
|
||||||
|
idx,
|
||||||
|
WorkspaceDelta::new_shortcut(),
|
||||||
|
) {
|
||||||
Ok(Some(new_pos)) => {
|
Ok(Some(new_pos)) => {
|
||||||
seat.set_active_output(&next_output);
|
seat.set_active_output(&next_output);
|
||||||
if let Some(ptr) = seat.get_pointer() {
|
if let Some(ptr) = seat.get_pointer() {
|
||||||
|
|
@ -1762,7 +1836,11 @@ impl State {
|
||||||
.cloned();
|
.cloned();
|
||||||
if let Some(prev_output) = prev_output {
|
if let Some(prev_output) = prev_output {
|
||||||
let idx = self.common.shell.workspaces.active_num(&prev_output).1;
|
let idx = self.common.shell.workspaces.active_num(&prev_output).1;
|
||||||
match self.common.shell.activate(&prev_output, idx) {
|
match self.common.shell.activate(
|
||||||
|
&prev_output,
|
||||||
|
idx,
|
||||||
|
WorkspaceDelta::new_shortcut(),
|
||||||
|
) {
|
||||||
Ok(Some(new_pos)) => {
|
Ok(Some(new_pos)) => {
|
||||||
seat.set_active_output(&prev_output);
|
seat.set_active_output(&prev_output);
|
||||||
if let Some(ptr) = seat.get_pointer() {
|
if let Some(ptr) = seat.get_pointer() {
|
||||||
|
|
@ -2271,6 +2349,56 @@ impl State {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_next_workspace(
|
||||||
|
&mut self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
gesture: bool,
|
||||||
|
) -> Result<Option<Point<i32, Global>>, InvalidWorkspaceIndex> {
|
||||||
|
let current_output = seat.active_output();
|
||||||
|
let workspace = self
|
||||||
|
.common
|
||||||
|
.shell
|
||||||
|
.workspaces
|
||||||
|
.active_num(¤t_output)
|
||||||
|
.1
|
||||||
|
.saturating_add(1);
|
||||||
|
|
||||||
|
self.common.shell.activate(
|
||||||
|
¤t_output,
|
||||||
|
workspace,
|
||||||
|
if gesture {
|
||||||
|
WorkspaceDelta::new_gesture()
|
||||||
|
} else {
|
||||||
|
WorkspaceDelta::new_shortcut()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_previous_workspace(
|
||||||
|
&mut self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
gesture: bool,
|
||||||
|
) -> Result<Option<Point<i32, Global>>, InvalidWorkspaceIndex> {
|
||||||
|
let current_output = seat.active_output();
|
||||||
|
let workspace = self
|
||||||
|
.common
|
||||||
|
.shell
|
||||||
|
.workspaces
|
||||||
|
.active_num(¤t_output)
|
||||||
|
.1
|
||||||
|
.saturating_sub(1);
|
||||||
|
|
||||||
|
self.common.shell.activate(
|
||||||
|
¤t_output,
|
||||||
|
workspace,
|
||||||
|
if gesture {
|
||||||
|
WorkspaceDelta::new_gesture()
|
||||||
|
} else {
|
||||||
|
WorkspaceDelta::new_shortcut()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sessions_for_output(state: &Common, output: &Output) -> impl Iterator<Item = Session> {
|
fn sessions_for_output(state: &Common, output: &Output) -> impl Iterator<Item = Session> {
|
||||||
|
|
|
||||||
232
src/shell/mod.rs
232
src/shell/mod.rs
|
|
@ -46,6 +46,7 @@ use smithay::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
backend::render::animations::spring::{Spring, SpringParams},
|
||||||
config::{Config, KeyModifiers, KeyPattern},
|
config::{Config, KeyModifiers, KeyPattern},
|
||||||
state::client_should_see_privileged_protocols,
|
state::client_should_see_privileged_protocols,
|
||||||
utils::prelude::*,
|
utils::prelude::*,
|
||||||
|
|
@ -93,6 +94,9 @@ use self::{
|
||||||
};
|
};
|
||||||
|
|
||||||
const ANIMATION_DURATION: Duration = Duration::from_millis(200);
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Trigger {
|
pub enum Trigger {
|
||||||
|
|
@ -223,9 +227,41 @@ pub struct SessionLock {
|
||||||
pub surfaces: HashMap<Output, LockSurface>,
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct WorkspaceSet {
|
pub struct WorkspaceSet {
|
||||||
previously_active: Option<(usize, Instant)>,
|
previously_active: Option<(usize, WorkspaceDelta)>,
|
||||||
active: usize,
|
active: usize,
|
||||||
pub group: WorkspaceGroupHandle,
|
pub group: WorkspaceGroupHandle,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
|
|
@ -371,6 +407,7 @@ impl WorkspaceSet {
|
||||||
fn activate(
|
fn activate(
|
||||||
&mut self,
|
&mut self,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
|
workspace_delta: WorkspaceDelta,
|
||||||
state: &mut WorkspaceUpdateGuard<'_, State>,
|
state: &mut WorkspaceUpdateGuard<'_, State>,
|
||||||
) -> Result<bool, InvalidWorkspaceIndex> {
|
) -> Result<bool, InvalidWorkspaceIndex> {
|
||||||
if idx >= self.workspaces.len() {
|
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[old_active].handle, WState::Urgent);
|
||||||
state.remove_workspace_state(&self.workspaces[idx].handle, WState::Urgent);
|
state.remove_workspace_state(&self.workspaces[idx].handle, WState::Urgent);
|
||||||
state.add_workspace_state(&self.workspaces[idx].handle, WState::Active);
|
state.add_workspace_state(&self.workspaces[idx].handle, WState::Active);
|
||||||
|
self.previously_active = Some((old_active, workspace_delta));
|
||||||
self.previously_active = Some((old_active, Instant::now()));
|
|
||||||
self.active = idx;
|
self.active = idx;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
} else {
|
} else {
|
||||||
|
if let Some((p_idx, _)) = self.previously_active {
|
||||||
|
self.previously_active = Some((p_idx, workspace_delta));
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
Ok(false)
|
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(
|
fn set_output(
|
||||||
&mut self,
|
&mut self,
|
||||||
new_output: &Output,
|
new_output: &Output,
|
||||||
|
|
@ -410,8 +468,21 @@ impl WorkspaceSet {
|
||||||
|
|
||||||
fn refresh<'a>(&mut self, xdg_activation_state: &XdgActivationState) {
|
fn refresh<'a>(&mut self, xdg_activation_state: &XdgActivationState) {
|
||||||
if let Some((_, start)) = self.previously_active {
|
if let Some((_, start)) = self.previously_active {
|
||||||
if Instant::now().duration_since(start).as_millis() >= ANIMATION_DURATION.as_millis() {
|
match start {
|
||||||
self.previously_active = None;
|
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 {
|
} else {
|
||||||
self.workspaces[self.active].refresh(xdg_activation_state);
|
self.workspaces[self.active].refresh(xdg_activation_state);
|
||||||
|
|
@ -855,7 +926,7 @@ impl Workspaces {
|
||||||
.and_then(|set| set.workspaces.get_mut(num))
|
.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();
|
let set = self.sets.get(output).or(self.backup_set.as_ref()).unwrap();
|
||||||
(
|
(
|
||||||
set.previously_active
|
set.previously_active
|
||||||
|
|
@ -1095,6 +1166,7 @@ impl Shell {
|
||||||
&mut self,
|
&mut self,
|
||||||
output: &Output,
|
output: &Output,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
|
workspace_delta: WorkspaceDelta,
|
||||||
) -> Result<Option<Point<i32, Global>>, InvalidWorkspaceIndex> {
|
) -> Result<Option<Point<i32, Global>>, InvalidWorkspaceIndex> {
|
||||||
match &mut self.workspaces.mode {
|
match &mut self.workspaces.mode {
|
||||||
WorkspaceMode::OutputBound => {
|
WorkspaceMode::OutputBound => {
|
||||||
|
|
@ -1105,7 +1177,7 @@ impl Shell {
|
||||||
) {
|
) {
|
||||||
set.workspaces[set.active].tiling_layer.cleanup_drag();
|
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
|
if let Some(xwm) = self
|
||||||
.xwayland_state
|
.xwayland_state
|
||||||
.as_mut()
|
.as_mut()
|
||||||
|
|
@ -1151,7 +1223,143 @@ impl Shell {
|
||||||
}
|
}
|
||||||
WorkspaceMode::Global => {
|
WorkspaceMode::Global => {
|
||||||
for set in self.workspaces.sets.values_mut() {
|
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)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
@ -1923,7 +2131,13 @@ impl Shell {
|
||||||
.shell
|
.shell
|
||||||
.workspaces
|
.workspaces
|
||||||
.idx_for_handle(&to_output, to)
|
.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 {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::{kms::KmsState, winit::WinitState, x11::X11State},
|
backend::{kms::KmsState, winit::WinitState, x11::X11State},
|
||||||
config::{Config, OutputConfig},
|
config::{Config, OutputConfig},
|
||||||
input::Devices,
|
input::{gestures::GestureState, Devices},
|
||||||
shell::{grabs::SeatMoveGrabState, Shell},
|
shell::{grabs::SeatMoveGrabState, Shell},
|
||||||
utils::prelude::*,
|
utils::prelude::*,
|
||||||
wayland::protocols::{
|
wayland::protocols::{
|
||||||
|
|
@ -159,6 +159,7 @@ pub struct Common {
|
||||||
pub clock: Clock<Monotonic>,
|
pub clock: Clock<Monotonic>,
|
||||||
pub should_stop: bool,
|
pub should_stop: bool,
|
||||||
pub local_offset: time::UtcOffset,
|
pub local_offset: time::UtcOffset,
|
||||||
|
pub gesture_state: Option<GestureState>,
|
||||||
|
|
||||||
pub theme: cosmic::Theme,
|
pub theme: cosmic::Theme,
|
||||||
|
|
||||||
|
|
@ -423,6 +424,7 @@ impl State {
|
||||||
|
|
||||||
clock,
|
clock,
|
||||||
should_stop: false,
|
should_stop: false,
|
||||||
|
gesture_state: None,
|
||||||
|
|
||||||
theme: cosmic::theme::system_preference(),
|
theme: cosmic::theme::system_preference(),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use smithay::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
shell::{CosmicSurface, Shell},
|
shell::{CosmicSurface, Shell, WorkspaceDelta},
|
||||||
utils::prelude::*,
|
utils::prelude::*,
|
||||||
wayland::protocols::{
|
wayland::protocols::{
|
||||||
toplevel_info::ToplevelInfoHandler,
|
toplevel_info::ToplevelInfoHandler,
|
||||||
|
|
@ -60,7 +60,11 @@ impl ToplevelManagementHandler for State {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
let _ = self.common.shell.activate(&output, idx as usize); // TODO: Move pointer?
|
let _ = self.common.shell.activate(
|
||||||
|
&output,
|
||||||
|
idx as usize,
|
||||||
|
WorkspaceDelta::new_shortcut(),
|
||||||
|
); // TODO: Move pointer?
|
||||||
mapped.focus_window(window);
|
mapped.focus_window(window);
|
||||||
Common::set_focus(self, Some(&mapped.clone().into()), &seat, None);
|
Common::set_focus(self, Some(&mapped.clone().into()), &seat, None);
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
shell::WorkspaceDelta,
|
||||||
state::ClientState,
|
state::ClientState,
|
||||||
utils::prelude::*,
|
utils::prelude::*,
|
||||||
wayland::protocols::workspace::{
|
wayland::protocols::workspace::{
|
||||||
|
|
@ -38,7 +39,11 @@ impl WorkspaceHandler for State {
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some((output, idx)) = maybe {
|
if let Some((output, idx)) = maybe {
|
||||||
let _ = self.common.shell.activate(&output, idx); // TODO: move cursor?
|
let _ = self.common.shell.activate(
|
||||||
|
&output,
|
||||||
|
idx,
|
||||||
|
WorkspaceDelta::new_shortcut(),
|
||||||
|
); // TODO: move cursor?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Request::SetTilingState { workspace, state } => {
|
Request::SetTilingState { workspace, state } => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue