diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2025-04-25 10:06:46 +0300 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2025-04-25 02:00:18 -0700 |
| commit | fae3a276418ef1f6f6baad465f160e33a6ac9d8a (patch) | |
| tree | 99fa9626e829e68b5b50f8fa78da459bf7669956 /src/layout/mod.rs | |
| parent | 31e76cf451eaf8c5f2bc139b3867f2153e72ee1d (diff) | |
| download | niri-fae3a276418ef1f6f6baad465f160e33a6ac9d8a.tar.gz niri-fae3a276418ef1f6f6baad465f160e33a6ac9d8a.tar.bz2 niri-fae3a276418ef1f6f6baad465f160e33a6ac9d8a.zip | |
Implement DnD hold to activate window or workspace
Diffstat (limited to 'src/layout/mod.rs')
| -rw-r--r-- | src/layout/mod.rs | 100 |
1 files changed, 96 insertions, 4 deletions
diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 2ce8662e..6adf3ad7 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -298,7 +298,7 @@ pub struct Layout<W: LayoutElement> { /// Ongoing interactive move. interactive_move: Option<InteractiveMoveState<W>>, /// Ongoing drag-and-drop operation. - dnd: Option<DndData>, + dnd: Option<DndData<W>>, /// Clock for driving animations. clock: Clock, /// Time that we last updated render elements for. @@ -433,11 +433,26 @@ struct InteractiveMoveData<W: LayoutElement> { } #[derive(Debug)] -pub struct DndData { +pub struct DndData<W: LayoutElement> { /// Output where the pointer is currently located. output: Output, /// Current pointer position within output. pointer_pos_within_output: Point<f64, Logical>, + /// Ongoing DnD hold to activate something. + hold: Option<DndHold<W>>, +} + +#[derive(Debug)] +struct DndHold<W: LayoutElement> { + /// Time when we started holding on the target. + start_time: Duration, + target: DndHoldTarget<W::Id>, +} + +#[derive(Debug, PartialEq, Eq)] +enum DndHoldTarget<WindowId> { + Window(WindowId), + Workspace(WorkspaceId), } #[derive(Debug, Clone, Copy)] @@ -2755,8 +2770,10 @@ impl<W: LayoutElement> Layout<W> { let _span = tracy_client::span!("Layout::advance_animations"); let mut dnd_scroll = None; + let mut is_dnd = false; if let Some(dnd) = &self.dnd { dnd_scroll = Some((dnd.output.clone(), dnd.pointer_pos_within_output, true)); + is_dnd = true; } if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move { @@ -2771,11 +2788,15 @@ impl<W: LayoutElement> Layout<W> { } } + let is_overview_open = self.overview_open; + // Scroll the view if needed. if let Some((output, pos_within_output, is_scrolling)) = dnd_scroll { if let Some(mon) = self.monitor_for_output_mut(&output) { + let mut scrolled = false; + let zoom = mon.overview_zoom(); - mon.dnd_scroll_gesture_scroll(pos_within_output, 1. / zoom); + scrolled |= mon.dnd_scroll_gesture_scroll(pos_within_output, 1. / zoom); if is_scrolling { if let Some((ws, geo)) = mon.workspace_under(pos_within_output) { @@ -2788,7 +2809,77 @@ impl<W: LayoutElement> Layout<W> { // As far as the DnD scroll gesture is concerned, the workspace spans across // the whole monitor horizontally. let ws_pos = Point::from((0., geo.loc.y)); - ws.dnd_scroll_gesture_scroll(pos_within_output - ws_pos, 1. / zoom); + scrolled |= + ws.dnd_scroll_gesture_scroll(pos_within_output - ws_pos, 1. / zoom); + } + } + + if scrolled { + // Don't trigger DnD hold while scrolling. + if let Some(dnd) = &mut self.dnd { + dnd.hold = None; + } + } else if is_dnd { + let target = mon + .window_under(pos_within_output) + .map(|(win, _)| DndHoldTarget::Window(win.id().clone())) + .or_else(|| { + mon.workspace_under_narrow(pos_within_output) + .map(|ws| DndHoldTarget::Workspace(ws.id())) + }); + + let dnd = self.dnd.as_mut().unwrap(); + if let Some(target) = target { + let now = self.clock.now_unadjusted(); + let start_time = if let Some(hold) = &mut dnd.hold { + if hold.target != target { + hold.start_time = now; + } + hold.target = target; + hold.start_time + } else { + let hold = dnd.hold.insert(DndHold { + start_time: now, + target, + }); + hold.start_time + }; + + // Delay copied from gnome-shell. + let delay = Duration::from_millis(750); + if delay <= now.saturating_sub(start_time) { + let hold = dnd.hold.take().unwrap(); + + // Synchronize workspace switch to overview close to get a monotonic + // animation. + let config = is_overview_open + .then_some(self.options.animations.overview_open_close.0); + + let mon = self.monitor_for_output_mut(&output).unwrap(); + + let ws_idx = match hold.target { + DndHoldTarget::Window(id) => mon + .workspaces + .iter_mut() + .position(|ws| ws.activate_window(&id)) + .unwrap(), + DndHoldTarget::Workspace(id) => { + mon.workspaces.iter().position(|ws| ws.id() == id).unwrap() + } + }; + + mon.dnd_scroll_gesture_end(); + mon.activate_workspace_with_anim_config(ws_idx, config); + + self.focus_output(&output); + + if is_overview_open { + self.close_overview(); + } + } + } else { + // No target, reset the hold timer. + dnd.hold = None; } } } @@ -4459,6 +4550,7 @@ impl<W: LayoutElement> Layout<W> { self.dnd = Some(DndData { output, pointer_pos_within_output, + hold: None, }); if begin_gesture { |
