mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-21 02:01:55 +07:00
Make vertical touchpad swipe inertial
Values and implementation are heavily inspired by AdwSwipeTracker.
This commit is contained in:
+7
-1
@@ -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);
|
||||
|
||||
+9
-4
@@ -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);
|
||||
|
||||
+21
-10
@@ -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(),
|
||||
)));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user