diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2023-10-08 09:57:59 +0400 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2023-10-08 09:57:59 +0400 |
| commit | e70e660df62d46879f01fc76c55fa09adce9c994 (patch) | |
| tree | 8d989a6ff5c88c7c3b36b847b310a393e839d945 /src | |
| parent | 0411fd8d90f4e80990ca511f7ea65ea680aef185 (diff) | |
| download | niri-e70e660df62d46879f01fc76c55fa09adce9c994.tar.gz niri-e70e660df62d46879f01fc76c55fa09adce9c994.tar.bz2 niri-e70e660df62d46879f01fc76c55fa09adce9c994.zip | |
Add barebones three-finger-swipe workspace switch
Notable omission is velocity tracking.
Diffstat (limited to 'src')
| -rw-r--r-- | src/input.rs | 37 | ||||
| -rw-r--r-- | src/layout.rs | 154 |
2 files changed, 175 insertions, 16 deletions
diff --git a/src/input.rs b/src/input.rs index 033b90f4..955c63f4 100644 --- a/src/input.rs +++ b/src/input.rs @@ -631,6 +631,19 @@ impl State { } } InputEvent::GestureSwipeBegin { event } => { + if event.fingers() == 3 { + if let Some(output) = self.niri.output_under_cursor() { + self.niri.layout.workspace_switch_gesture_begin(&output); + + // FIXME: granular. This one is awkward because this can cancel a gesture on + // multiple other outputs in theory. + self.niri.queue_redraw_all(); + } + + // We handled this event. + return; + } + let serial = SERIAL_COUNTER.next_serial(); let pointer = self.niri.seat.get_pointer().unwrap(); pointer.gesture_swipe_begin( @@ -643,6 +656,19 @@ impl State { ); } InputEvent::GestureSwipeUpdate { event } => { + let res = self + .niri + .layout + .workspace_switch_gesture_update(event.delta_y()); + if let Some(output) = res { + if let Some(output) = output { + self.niri.queue_redraw(output); + } + + // We handled this event. + return; + } + let pointer = self.niri.seat.get_pointer().unwrap(); pointer.gesture_swipe_update( self, @@ -653,6 +679,17 @@ impl State { ); } InputEvent::GestureSwipeEnd { event } => { + let res = self + .niri + .layout + .workspace_switch_gesture_end(event.cancelled()); + if let Some(output) = res { + self.niri.queue_redraw(output); + + // We handled this event. + return; + } + let serial = SERIAL_COUNTER.next_serial(); let pointer = self.niri.seat.get_pointer().unwrap(); pointer.gesture_swipe_end( diff --git a/src/layout.rs b/src/layout.rs index ba362814..003bb1c4 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -110,13 +110,27 @@ pub struct Monitor<W: LayoutElement> { workspaces: Vec<Workspace<W>>, /// Index of the currently active workspace. active_workspace_idx: usize, - /// Animation for workspace switching. - workspace_idx_anim: Option<Animation>, + /// In-progress switch between workspaces. + workspace_switch: Option<WorkspaceSwitch>, /// Configurable properties of the layout. options: Rc<Options>, } #[derive(Debug)] +enum WorkspaceSwitch { + Animation(Animation), + Gesture(WorkspaceSwitchGesture), +} + +#[derive(Debug)] +struct WorkspaceSwitchGesture { + /// Index of the workspace where the gesture was started. + center_idx: usize, + /// Current, fractional workspace index. + current_idx: f64, +} + +#[derive(Debug)] pub struct Workspace<W: LayoutElement> { /// The original output of this workspace. /// @@ -361,6 +375,23 @@ impl FocusRing { } } +impl WorkspaceSwitch { + fn current_idx(&self) -> f64 { + match self { + WorkspaceSwitch::Animation(anim) => anim.value(), + WorkspaceSwitch::Gesture(gesture) => gesture.current_idx, + } + } + + /// Returns `true` if the workspace switch is [`Animation`]. + /// + /// [`Animation`]: WorkspaceSwitch::Animation + #[must_use] + fn is_animation(&self) -> bool { + matches!(self, Self::Animation(..)) + } +} + impl ColumnWidth { fn resolve(self, options: &Options, view_width: i32) -> i32 { match self { @@ -1138,6 +1169,95 @@ impl<W: LayoutElement> Layout<W> { } } } + + pub fn workspace_switch_gesture_begin(&mut self, output: &Output) { + let monitors = match &mut self.monitor_set { + MonitorSet::Normal { monitors, .. } => monitors, + MonitorSet::NoOutputs { .. } => unreachable!(), + }; + + for monitor in monitors { + // Cancel the gesture on other outputs. + if &monitor.output != output { + if let Some(WorkspaceSwitch::Gesture(_)) = monitor.workspace_switch { + monitor.workspace_switch = None; + } + continue; + } + + let center_idx = monitor.active_workspace_idx; + let current_idx = monitor + .workspace_switch + .as_ref() + .map(|s| s.current_idx()) + .unwrap_or(center_idx as f64); + + let gesture = WorkspaceSwitchGesture { + center_idx, + current_idx, + }; + monitor.workspace_switch = Some(WorkspaceSwitch::Gesture(gesture)); + } + } + + pub fn workspace_switch_gesture_update(&mut self, delta_y: f64) -> 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(WorkspaceSwitch::Gesture(gesture)) = &mut monitor.workspace_switch { + // Normalize like GNOME Shell's workspace switching. + let delta_y = -delta_y / 400.; + + let min = gesture.center_idx.saturating_sub(1) as f64; + let max = (gesture.center_idx + 1).min(monitor.workspaces.len() - 1) as f64; + let new_idx = (gesture.current_idx + delta_y).clamp(min, max); + + if gesture.current_idx == new_idx { + return Some(None); + } + + gesture.current_idx = new_idx; + return Some(Some(monitor.output.clone())); + } + } + + None + } + + pub fn workspace_switch_gesture_end(&mut self, cancelled: bool) -> Option<Output> { + let monitors = match &mut self.monitor_set { + MonitorSet::Normal { monitors, .. } => monitors, + MonitorSet::NoOutputs { .. } => return None, + }; + + for monitor in monitors { + if let Some(WorkspaceSwitch::Gesture(gesture)) = &mut monitor.workspace_switch { + if cancelled { + monitor.workspace_switch = None; + return Some(monitor.output.clone()); + } + + // 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; + + monitor.active_workspace_idx = idx; + monitor.workspace_switch = Some(WorkspaceSwitch::Animation(Animation::new( + current_idx, + idx as f64, + Duration::from_millis(250), + ))); + + return Some(monitor.output.clone()); + } + } + + None + } } impl Layout<Window> { @@ -1173,7 +1293,7 @@ impl<W: LayoutElement> Monitor<W> { output, workspaces, active_workspace_idx: 0, - workspace_idx_anim: None, + workspace_switch: None, options, } } @@ -1188,18 +1308,18 @@ impl<W: LayoutElement> Monitor<W> { } let current_idx = self - .workspace_idx_anim + .workspace_switch .as_ref() - .map(|anim| anim.value()) + .map(|s| s.current_idx()) .unwrap_or(self.active_workspace_idx as f64); self.active_workspace_idx = idx; - self.workspace_idx_anim = Some(Animation::new( + self.workspace_switch = Some(WorkspaceSwitch::Animation(Animation::new( current_idx, idx as f64, Duration::from_millis(250), - )); + ))); } pub fn add_window(&mut self, workspace_idx: usize, window: W, activate: bool) { @@ -1222,7 +1342,7 @@ impl<W: LayoutElement> Monitor<W> { } fn clean_up_workspaces(&mut self) { - assert!(self.workspace_idx_anim.is_none()); + assert!(self.workspace_switch.is_none()); for idx in (0..self.workspaces.len() - 1).rev() { if self.active_workspace_idx == idx { @@ -1330,7 +1450,7 @@ impl<W: LayoutElement> Monitor<W> { self.add_window(new_idx, window, true); // Don't animate this action. - self.workspace_idx_anim = None; + self.workspace_switch = None; } pub fn switch_workspace_up(&mut self) { @@ -1350,7 +1470,7 @@ impl<W: LayoutElement> Monitor<W> { self.workspaces.len() - 1, )); // Don't animate this action. - self.workspace_idx_anim = None; + self.workspace_switch = None; } pub fn consume_into_column(&mut self) { @@ -1372,10 +1492,10 @@ impl<W: LayoutElement> Monitor<W> { } pub fn advance_animations(&mut self, current_time: Duration, is_active: bool) { - if let Some(anim) = &mut self.workspace_idx_anim { + if let Some(WorkspaceSwitch::Animation(anim)) = &mut self.workspace_switch { anim.set_current_time(current_time); if anim.is_done() { - self.workspace_idx_anim = None; + self.workspace_switch = None; self.clean_up_workspaces(); } } @@ -1386,7 +1506,9 @@ impl<W: LayoutElement> Monitor<W> { } pub fn are_animations_ongoing(&self) -> bool { - self.workspace_idx_anim.is_some() + self.workspace_switch + .as_ref() + .is_some_and(|s| s.is_animation()) || self.workspaces.iter().any(|ws| ws.are_animations_ongoing()) } @@ -1421,9 +1543,9 @@ impl Monitor<Window> { let output_mode = self.output.current_mode().unwrap(); let output_size = output_transform.transform_size(output_mode.size); - match &self.workspace_idx_anim { - Some(anim) => { - let render_idx = anim.value(); + match &self.workspace_switch { + Some(switch) => { + let render_idx = switch.current_idx(); let below_idx = render_idx.floor() as usize; let above_idx = render_idx.ceil() as usize; |
