aboutsummaryrefslogtreecommitdiff
path: root/src/layout
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2024-03-03 09:26:29 +0400
committerIvan Molodetskikh <yalterz@gmail.com>2024-03-03 09:33:00 +0400
commit2e51efd3a3d799862a24bfac2d33ec7b7413f276 (patch)
treed1aa6a1e9ba59d24f378056f53f7472c87ff0c8e /src/layout
parentcaea05433e77cb4523070473e5b50b1831db5608 (diff)
downloadniri-2e51efd3a3d799862a24bfac2d33ec7b7413f276.tar.gz
niri-2e51efd3a3d799862a24bfac2d33ec7b7413f276.tar.bz2
niri-2e51efd3a3d799862a24bfac2d33ec7b7413f276.zip
Remake horizontal gesture to snap with inertia
Diffstat (limited to 'src/layout')
-rw-r--r--src/layout/workspace.rs193
1 files changed, 133 insertions, 60 deletions
diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs
index ec53f887..a9629baa 100644
--- a/src/layout/workspace.rs
+++ b/src/layout/workspace.rs
@@ -20,6 +20,9 @@ use crate::render_helpers::renderer::NiriRenderer;
use crate::swipe_tracker::SwipeTracker;
use crate::utils::output_size;
+/// Amount of touchpad movement to scroll the view for the width of one working area.
+const VIEW_GESTURE_WORKING_AREA_MOVEMENT: f64 = 1200.;
+
#[derive(Debug)]
pub struct Workspace<W: LayoutElement> {
/// The original output of this workspace.
@@ -1250,92 +1253,162 @@ impl<W: LayoutElement> Workspace<W> {
gesture.tracker.push(delta_x, timestamp);
- let view_offset = gesture.tracker.pos() + gesture.delta_from_tracker;
+ let norm_factor = self.working_area.size.w as f64 / VIEW_GESTURE_WORKING_AREA_MOVEMENT;
+ let pos = gesture.tracker.pos() * norm_factor;
+ let view_offset = pos + gesture.delta_from_tracker;
gesture.current_view_offset = view_offset;
+ Some(true)
+ }
+
+ pub fn view_offset_gesture_end(&mut self, _cancelled: bool) -> bool {
+ let Some(ViewOffsetAdjustment::Gesture(gesture)) = &self.view_offset_adj else {
+ return false;
+ };
+
+ // We do not handle cancelling, just like GNOME Shell doesn't. For this gesture, proper
+ // cancelling would require keeping track of the original active column, and then updating
+ // it in all the right places (adding columns, removing columns, etc.) -- quite a bit of
+ // effort and bug potential.
+
+ let norm_factor = self.working_area.size.w as f64 / VIEW_GESTURE_WORKING_AREA_MOVEMENT;
+ let pos = gesture.tracker.pos() * norm_factor;
+ let current_view_offset = pos + gesture.delta_from_tracker;
+
if self.columns.is_empty() {
- return Some(true);
+ self.view_offset = current_view_offset.round() as i32;
+ self.view_offset_adj = None;
+ return true;
}
- // Check if moving too slow to switch focus.
- if delta_x.abs() < 1. {
- return Some(true);
+ // Figure out where the gesture would stop after deceleration.
+ let end_pos = gesture.tracker.projected_end_pos() * norm_factor;
+ let target_view_offset = end_pos + gesture.delta_from_tracker;
+
+ // FIXME: the following logic needs to be different for center column = always.
+
+ // Compute the snapping points. These are where the view aligns with column boundaries on
+ // either side.
+ struct Snap {
+ // View position relative to x = 0 (the first column).
+ view_pos: i32,
+ // Column to activate for this snapping point.
+ col_idx: usize,
}
- // Switch focus to the furthest visible window towards the gesture direction.
+ let mut snapping_points = Vec::new();
- // Make an iterator over column indices towards the gesture direction.
- let mut idxs_forward = 0..self.columns.len();
- let mut idxs_back = idxs_forward.clone().rev();
- let idxs = if delta_x < 0. {
- &mut idxs_back as &mut dyn Iterator<Item = usize>
- } else {
- &mut idxs_forward as &mut dyn Iterator<Item = usize>
+ let view_width = self.view_size.w;
+ let mut push = |col_idx, left, right| {
+ snapping_points.push(Snap {
+ view_pos: left,
+ col_idx,
+ });
+ snapping_points.push(Snap {
+ view_pos: right - view_width,
+ col_idx,
+ });
};
- let active_col_x = self.column_x(self.active_column_idx);
- let mut new_col_idx = self.active_column_idx;
- let mut new_col_x = active_col_x;
+ let left_strut = self.working_area.loc.x;
+ let right_strut = self.view_size.w - self.working_area.size.w - self.working_area.loc.x;
- for col_idx in idxs {
- let col = &self.columns[col_idx];
- let col_x = self.column_x(col_idx);
+ let mut col_x = 0;
+ for (col_idx, col) in self.columns.iter().enumerate() {
let col_w = col.width();
- let mut area_for_col = if col.is_fullscreen {
- Rectangle::from_loc_and_size((0, 0), self.view_size)
+ // Normal columns align with the working area, but fullscreen columns align with the
+ // view size.
+ if col.is_fullscreen {
+ let left = col_x;
+ let right = col_x + col_w;
+ push(col_idx, left, right);
} else {
- self.working_area
- };
-
- area_for_col.loc.x += active_col_x + view_offset.round() as i32;
-
- // Check if the column is already past the working area.
- if (delta_x >= 0. && area_for_col.loc.x + area_for_col.size.w <= col_x)
- || (delta_x < 0. && col_x + col_w <= area_for_col.loc.x)
- {
- break;
+ // Logic from compute_new_view_offset.
+ let padding = ((self.working_area.size.w - col_w) / 2).clamp(0, self.options.gaps);
+ let left = col_x - padding - left_strut;
+ let right = col_x + col_w + padding + right_strut;
+ push(col_idx, left, right);
}
- new_col_idx = col_idx;
- new_col_x = col_x;
+ col_x += col_w + self.options.gaps;
}
- let Some(ViewOffsetAdjustment::Gesture(gesture)) = &mut self.view_offset_adj else {
- unreachable!();
- };
-
- let delta = (active_col_x - new_col_x) as f64;
- gesture.delta_from_tracker += delta;
- gesture.current_view_offset += delta;
- self.active_column_idx = new_col_idx;
+ // Find the closest snapping point.
+ snapping_points.sort_by_key(|snap| snap.view_pos);
- Some(true)
- }
+ let active_col_x = self.column_x(self.active_column_idx);
+ let target_view_pos = (active_col_x as f64 + target_view_offset).round() as i32;
+ let target_snap = snapping_points
+ .iter()
+ .min_by_key(|snap| snap.view_pos.abs_diff(target_view_pos))
+ .unwrap();
- pub fn view_offset_gesture_end(&mut self, _cancelled: bool) -> bool {
- let Some(ViewOffsetAdjustment::Gesture(gesture)) = &self.view_offset_adj else {
- return false;
- };
+ let mut new_col_idx = target_snap.col_idx;
- // We do not handle cancelling, just like GNOME Shell doesn't. For this gesture, proper
- // cancelling would require keeping track of the original active column, and then updating
- // it in all the right places (adding columns, removing columns, etc.) -- quite a bit of
- // effort and bug potential.
+ // Focus the furthest window towards the direction of the gesture.
+ if target_view_offset >= current_view_offset {
+ for col_idx in (new_col_idx + 1)..self.columns.len() {
+ let col = &self.columns[col_idx];
+ let col_x = self.column_x(col_idx);
+ let col_w = col.width();
- // FIXME: keep track of gesture velocity and use it to compute the final point and
- // to animate to it.
- let offset = gesture.tracker.pos() + gesture.delta_from_tracker;
- let offset = offset.round() as i32;
+ if col.is_fullscreen {
+ if target_snap.view_pos + self.view_size.w < col_x + col_w {
+ break;
+ }
+ } else {
+ let padding =
+ ((self.working_area.size.w - col_w) / 2).clamp(0, self.options.gaps);
+ if target_snap.view_pos + left_strut + self.working_area.size.w
+ < col_x + col_w + padding
+ {
+ break;
+ }
+ }
- self.view_offset = offset;
- self.view_offset_adj = None;
+ new_col_idx = col_idx;
+ }
+ } else {
+ for col_idx in (0..new_col_idx).rev() {
+ let col = &self.columns[col_idx];
+ let col_x = self.column_x(col_idx);
+ let col_w = col.width();
+
+ if col.is_fullscreen {
+ if col_x < target_snap.view_pos {
+ break;
+ }
+ } else {
+ let padding =
+ ((self.working_area.size.w - col_w) / 2).clamp(0, self.options.gaps);
+ if col_x - padding < target_snap.view_pos + left_strut {
+ break;
+ }
+ }
- if !self.columns.is_empty() {
- let current_x = self.view_pos();
- self.animate_view_offset_to_column(current_x, self.active_column_idx, None);
+ new_col_idx = col_idx;
+ }
}
+ let new_col_x = self.column_x(new_col_idx);
+ let delta = (active_col_x - new_col_x) as f64;
+ self.view_offset = (current_view_offset + delta).round() as i32;
+ self.active_column_idx = new_col_idx;
+
+ let target_view_offset = target_snap.view_pos - new_col_x;
+
+ self.view_offset_adj = Some(ViewOffsetAdjustment::Animation(Animation::new(
+ current_view_offset + delta,
+ target_view_offset as f64,
+ self.options.animations.horizontal_view_movement,
+ niri_config::Animation::default_horizontal_view_movement(),
+ )));
+
+ // HACK: deal with things like snapping to the right edge of a larger-than-view window and
+ // center column = always.
+ self.animate_view_offset_to_column(self.view_pos(), new_col_idx, None);
+
true
}
}