aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2024-03-02 14:33:22 +0400
committerIvan Molodetskikh <yalterz@gmail.com>2024-03-02 14:33:22 +0400
commite0ec6e5b11bc222c252157beb064104f4c60dfbe (patch)
tree04fd4807424fa75b1dd36e87f9e3bc5b94d91c05 /src
parent93243d77728c3a0f7d314ed916b5f1a273861990 (diff)
downloadniri-e0ec6e5b11bc222c252157beb064104f4c60dfbe.tar.gz
niri-e0ec6e5b11bc222c252157beb064104f4c60dfbe.tar.bz2
niri-e0ec6e5b11bc222c252157beb064104f4c60dfbe.zip
Make vertical touchpad swipe inertial
Values and implementation are heavily inspired by AdwSwipeTracker.
Diffstat (limited to 'src')
-rw-r--r--src/input.rs8
-rw-r--r--src/layout/mod.rs13
-rw-r--r--src/layout/monitor.rs31
-rw-r--r--src/lib.rs1
-rw-r--r--src/swipe_tracker.rs87
5 files changed, 125 insertions, 15 deletions
diff --git a/src/input.rs b/src/input.rs
index d832195a..232cd77f 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -1,5 +1,6 @@
use std::any::Any;
use std::collections::HashSet;
+use std::time::Duration;
use niri_config::{Action, Binds, Modifiers};
use niri_ipc::LayoutSwitchTarget;
@@ -1242,8 +1243,13 @@ impl State {
}
}
+ let timestamp = Duration::from_micros(event.time());
+
let mut handled = false;
- let res = self.niri.layout.workspace_switch_gesture_update(delta_y);
+ let res = self
+ .niri
+ .layout
+ .workspace_switch_gesture_update(delta_y, timestamp);
if let Some(output) = res {
if let Some(output) = output {
self.niri.queue_redraw(output);
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index fb86ccac..6ec3ca8c 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -1603,14 +1603,18 @@ impl<W: LayoutElement> Layout<W> {
}
}
- pub fn workspace_switch_gesture_update(&mut self, delta_y: f64) -> Option<Option<Output>> {
+ pub fn workspace_switch_gesture_update(
+ &mut self,
+ delta_y: f64,
+ timestamp: Duration,
+ ) -> Option<Option<Output>> {
let monitors = match &mut self.monitor_set {
MonitorSet::Normal { monitors, .. } => monitors,
MonitorSet::NoOutputs { .. } => return None,
};
for monitor in monitors {
- if let Some(refresh) = monitor.workspace_switch_gesture_update(delta_y) {
+ if let Some(refresh) = monitor.workspace_switch_gesture_update(delta_y, timestamp) {
if refresh {
return Some(Some(monitor.output.clone()));
} else {
@@ -2041,6 +2045,7 @@ mod tests {
WorkspaceSwitchGestureUpdate {
#[proptest(strategy = "-400f64..400f64")]
delta: f64,
+ timestamp: Duration,
},
WorkspaceSwitchGestureEnd {
cancelled: bool,
@@ -2303,8 +2308,8 @@ mod tests {
layout.workspace_switch_gesture_begin(&output);
}
- Op::WorkspaceSwitchGestureUpdate { delta } => {
- layout.workspace_switch_gesture_update(delta);
+ Op::WorkspaceSwitchGestureUpdate { delta, timestamp } => {
+ layout.workspace_switch_gesture_update(delta, timestamp);
}
Op::WorkspaceSwitchGestureEnd { cancelled } => {
layout.workspace_switch_gesture_end(cancelled);
diff --git a/src/layout/monitor.rs b/src/layout/monitor.rs
index 70a6981d..ee02a204 100644
--- a/src/layout/monitor.rs
+++ b/src/layout/monitor.rs
@@ -15,6 +15,7 @@ use super::workspace::{
use super::{LayoutElement, Options};
use crate::animation::Animation;
use crate::render_helpers::renderer::NiriRenderer;
+use crate::swipe_tracker::SwipeTracker;
use crate::utils::output_size;
#[derive(Debug)]
@@ -43,6 +44,7 @@ pub struct WorkspaceSwitchGesture {
pub center_idx: usize,
/// Current, fractional workspace index.
pub current_idx: f64,
+ pub tracker: SwipeTracker,
}
pub type MonitorRenderElement<R> =
@@ -703,21 +705,28 @@ impl<W: LayoutElement> Monitor<W> {
let gesture = WorkspaceSwitchGesture {
center_idx,
current_idx,
+ tracker: SwipeTracker::new(),
};
self.workspace_switch = Some(WorkspaceSwitch::Gesture(gesture));
}
- pub fn workspace_switch_gesture_update(&mut self, delta_y: f64) -> Option<bool> {
+ pub fn workspace_switch_gesture_update(
+ &mut self,
+ delta_y: f64,
+ timestamp: Duration,
+ ) -> Option<bool> {
let Some(WorkspaceSwitch::Gesture(gesture)) = &mut self.workspace_switch else {
return None;
};
+ gesture.tracker.push(delta_y, timestamp);
+
// Normalize like GNOME Shell's workspace switching.
- let delta_y = delta_y / 400.;
+ let pos = gesture.tracker.pos() / 400.;
let min = gesture.center_idx.saturating_sub(1) as f64;
let max = (gesture.center_idx + 1).min(self.workspaces.len() - 1) as f64;
- let new_idx = (gesture.current_idx + delta_y).clamp(min, max);
+ let new_idx = (gesture.center_idx as f64 + pos).clamp(min, max);
if gesture.current_idx == new_idx {
return Some(false);
@@ -738,15 +747,17 @@ impl<W: LayoutElement> Monitor<W> {
return true;
}
- // FIXME: keep track of gesture velocity and use it to compute the final point and to
- // animate to it.
- let current_idx = gesture.current_idx;
- let idx = current_idx.round() as usize;
+ let pos = gesture.tracker.projected_end_pos() / 400.;
- self.active_workspace_idx = idx;
+ let min = gesture.center_idx.saturating_sub(1) as f64;
+ let max = (gesture.center_idx + 1).min(self.workspaces.len() - 1) as f64;
+ let new_idx = (gesture.center_idx as f64 + pos).clamp(min, max);
+ let new_idx = new_idx.round() as usize;
+
+ self.active_workspace_idx = new_idx;
self.workspace_switch = Some(WorkspaceSwitch::Animation(Animation::new(
- current_idx,
- idx as f64,
+ gesture.current_idx,
+ new_idx as f64,
self.options.animations.workspace_switch,
niri_config::Animation::default_workspace_switch(),
)));
diff --git a/src/lib.rs b/src/lib.rs
index fdf588b3..9ccb0c2e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -15,6 +15,7 @@ pub mod layout;
pub mod niri;
pub mod protocols;
pub mod render_helpers;
+pub mod swipe_tracker;
pub mod ui;
pub mod utils;
pub mod window;
diff --git a/src/swipe_tracker.rs b/src/swipe_tracker.rs
new file mode 100644
index 00000000..d7f42778
--- /dev/null
+++ b/src/swipe_tracker.rs
@@ -0,0 +1,87 @@
+use std::collections::VecDeque;
+use std::time::Duration;
+
+const HISTORY_LIMIT: Duration = Duration::from_millis(150);
+const DECELERATION_TOUCHPAD: f64 = 0.997;
+
+#[derive(Debug)]
+pub struct SwipeTracker {
+ history: VecDeque<Event>,
+ pos: f64,
+}
+
+#[derive(Debug, Clone, Copy)]
+struct Event {
+ delta: f64,
+ timestamp: Duration,
+}
+
+impl SwipeTracker {
+ #[allow(clippy::new_without_default)]
+ pub fn new() -> Self {
+ Self {
+ history: VecDeque::new(),
+ pos: 0.,
+ }
+ }
+
+ /// Pushes a new reading into the tracker.
+ pub 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(Event { delta, timestamp });
+ self.pos += delta;
+
+ self.retain_recent();
+ }
+
+ /// Returns the current gesture position.
+ pub fn pos(&self) -> f64 {
+ self.pos
+ }
+
+ /// 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.pos - vel / (1000. * DECELERATION_TOUCHPAD.ln())
+ }
+
+ fn retain_recent(&mut self) {
+ let Some(&Event { 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();
+ }
+ }
+}