diff options
| -rw-r--r-- | src/handlers/mod.rs | 3 | ||||
| -rw-r--r-- | src/input/mod.rs | 37 | ||||
| -rw-r--r-- | src/layout/mod.rs | 100 | ||||
| -rw-r--r-- | src/layout/tests.rs | 19 |
4 files changed, 137 insertions, 22 deletions
diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 334ab762..587d44ea 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -344,6 +344,9 @@ impl ClientDndGrabHandler for State { fn dropped(&mut self, target: Option<WlSurface>, validated: bool, _seat: Seat<Self>) { trace!("client dropped, target: {target:?}, validated: {validated}"); + // End DnD before activating a specific window below so that it takes precedence. + self.niri.layout.dnd_end(); + // Activate the target output, since that's how Firefox drag-tab-into-new-window works for // example. On successful drop, additionally activate the target window. let mut activate_output = true; diff --git a/src/input/mod.rs b/src/input/mod.rs index a2931b85..6c3ec812 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -32,6 +32,7 @@ use smithay::output::Output; use smithay::utils::{Logical, Point, Rectangle, Transform, SERIAL_COUNTER}; use smithay::wayland::keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitor; use smithay::wayland::pointer_constraints::{with_pointer_constraint, PointerConstraint}; +use smithay::wayland::selection::data_device::DnDGrab; use smithay::wayland::tablet_manager::{TabletDescriptor, TabletSeatTrait}; use touch_move_grab::TouchMoveGrab; @@ -1888,6 +1889,18 @@ impl State { // Activate a new confinement if necessary. self.niri.maybe_activate_pointer_constraint(); + // Inform the layout of an ongoing DnD operation. + let mut is_dnd_grab = false; + pointer.with_grab(|_, grab| { + is_dnd_grab = grab.as_any().downcast_ref::<DnDGrab<Self>>().is_some(); + }); + if is_dnd_grab { + if let Some((output, pos_within_output)) = self.niri.output_under(new_pos) { + let output = output.clone(); + self.niri.layout.dnd_update(output, pos_within_output); + } + } + // Redraw to update the cursor position. // FIXME: redraw only outputs overlapping the cursor. self.niri.queue_redraw_all(); @@ -1950,6 +1963,18 @@ impl State { // We moved the regular pointer, so show it now. self.niri.tablet_cursor_location = None; + // Inform the layout of an ongoing DnD operation. + let mut is_dnd_grab = false; + pointer.with_grab(|_, grab| { + is_dnd_grab = grab.as_any().downcast_ref::<DnDGrab<Self>>().is_some(); + }); + if is_dnd_grab { + if let Some((output, pos_within_output)) = self.niri.output_under(pos) { + let output = output.clone(); + self.niri.layout.dnd_update(output, pos_within_output); + } + } + // Redraw to update the cursor position. // FIXME: redraw only outputs overlapping the cursor. self.niri.queue_redraw_all(); @@ -2912,6 +2937,18 @@ impl State { time: evt.time_msec(), }, ); + + // Inform the layout of an ongoing DnD operation. + let mut is_dnd_grab = false; + handle.with_grab(|_, grab| { + is_dnd_grab = grab.as_any().downcast_ref::<DnDGrab<Self>>().is_some(); + }); + if is_dnd_grab { + if let Some((output, pos_within_output)) = self.niri.output_under(touch_location) { + let output = output.clone(); + self.niri.layout.dnd_update(output, pos_within_output); + } + } } fn on_touch_frame<I: InputBackend>(&mut self, _evt: I::TouchFrameEvent) { let Some(handle) = self.niri.seat.get_touch() else { diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 739709f4..248a5c4f 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -278,6 +278,8 @@ pub struct Layout<W: LayoutElement> { last_active_workspace_id: HashMap<String, WorkspaceId>, /// Ongoing interactive move. interactive_move: Option<InteractiveMoveState<W>>, + /// Ongoing drag-and-drop operation. + dnd: Option<DndData>, /// Clock for driving animations. clock: Clock, /// Time that we last updated render elements for. @@ -400,6 +402,14 @@ struct InteractiveMoveData<W: LayoutElement> { pub(self) pointer_ratio_within_window: (f64, f64), } +#[derive(Debug)] +pub struct DndData { + /// Output where the pointer is currently located. + output: Output, + /// Current pointer position within output. + pointer_pos_within_output: Point<f64, Logical>, +} + #[derive(Debug, Clone, Copy)] pub struct InteractiveResizeData { pub(self) edges: ResizeEdge, @@ -600,6 +610,7 @@ impl<W: LayoutElement> Layout<W> { is_active: true, last_active_workspace_id: HashMap::new(), interactive_move: None, + dnd: None, clock, update_render_elements_time: Duration::ZERO, options: Rc::new(options), @@ -622,6 +633,7 @@ impl<W: LayoutElement> Layout<W> { is_active: true, last_active_workspace_id: HashMap::new(), interactive_move: None, + dnd: None, clock, update_render_elements_time: Duration::ZERO, options: opts, @@ -2518,7 +2530,7 @@ impl<W: LayoutElement> Layout<W> { workspace.verify_invariants(move_win_id.as_ref()); let has_view_offset_gesture = workspace.scrolling().view_offset().is_gesture(); - if self.interactive_move.is_some() { + if self.dnd.is_some() || self.interactive_move.is_some() { // We'd like to check that all workspaces have the gesture here, furthermore we // want to check that they have the gesture only if the interactive move // targets the scrolling layout. However, we cannot do that because we start @@ -2545,23 +2557,30 @@ impl<W: LayoutElement> Layout<W> { pub fn advance_animations(&mut self) { let _span = tracy_client::span!("Layout::advance_animations"); + let mut dnd_scroll = None; + if let Some(dnd) = &self.dnd { + dnd_scroll = Some((dnd.output.clone(), dnd.pointer_pos_within_output)); + } + if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move { move_.tile.advance_animations(); - // Scroll the view if needed. - if !move_.is_floating { - let output = move_.output.clone(); - let pos_within_output = move_.pointer_pos_within_output; - if let Some(mon) = self.monitor_for_output_mut(&output) { - if let Some((ws, offset)) = mon.workspace_under(pos_within_output) { - let ws_id = ws.id(); - let ws = mon - .workspaces - .iter_mut() - .find(|ws| ws.id() == ws_id) - .unwrap(); - ws.dnd_scroll_gesture_scroll(pos_within_output - offset); - } + if !move_.is_floating && dnd_scroll.is_none() { + dnd_scroll = Some((move_.output.clone(), move_.pointer_pos_within_output)); + } + } + + // Scroll the view if needed. + if let Some((output, pos_within_output)) = dnd_scroll { + if let Some(mon) = self.monitor_for_output_mut(&output) { + if let Some((ws, offset)) = mon.workspace_under(pos_within_output) { + let ws_id = ws.id(); + let ws = mon + .workspaces + .iter_mut() + .find(|ws| ws.id() == ws_id) + .unwrap(); + ws.dnd_scroll_gesture_scroll(pos_within_output - offset); } } } @@ -2581,6 +2600,13 @@ impl<W: LayoutElement> Layout<W> { } pub fn are_animations_ongoing(&self, output: Option<&Output>) -> bool { + // Keep advancing animations if we might need to scroll the view. + if let Some(dnd) = &self.dnd { + if output.map_or(true, |output| *output == dnd.output) { + return true; + } + } + if let Some(InteractiveMoveState::Moving(move_)) = &self.interactive_move { if output.map_or(true, |output| *output == move_.output) { if move_.tile.are_animations_ongoing() { @@ -3991,6 +4017,33 @@ impl<W: LayoutElement> Layout<W> { move_.output == *output } + pub fn dnd_update(&mut self, output: Output, pointer_pos_within_output: Point<f64, Logical>) { + let begin_gesture = self.dnd.is_none(); + + self.dnd = Some(DndData { + output, + pointer_pos_within_output, + }); + + if begin_gesture { + for ws in self.workspaces_mut() { + ws.dnd_scroll_gesture_begin(); + } + } + } + + pub fn dnd_end(&mut self) { + if self.dnd.is_none() { + return; + } + + self.dnd = None; + + for ws in self.workspaces_mut() { + ws.view_offset_gesture_end(false, None); + } + } + pub fn interactive_resize_begin(&mut self, window: W::Id, edges: ResizeEdge) -> bool { match &mut self.monitor_set { MonitorSet::Normal { monitors, .. } => { @@ -4349,7 +4402,8 @@ impl<W: LayoutElement> Layout<W> { self.is_active = is_active; - let mut ongoing_scrolling_dnd = None; + let mut ongoing_scrolling_dnd = self.dnd.is_some().then_some(true); + if let Some(InteractiveMoveState::Moving(move_)) = &mut self.interactive_move { let win = move_.tile.window_mut(); @@ -4364,15 +4418,17 @@ impl<W: LayoutElement> Layout<W> { win.send_pending_configure(); win.refresh(); - ongoing_scrolling_dnd = Some(!move_.is_floating); + ongoing_scrolling_dnd.get_or_insert(!move_.is_floating); } else if let Some(InteractiveMoveState::Starting { window_id, .. }) = &self.interactive_move { - let (_, _, ws) = self - .workspaces() - .find(|(_, _, ws)| ws.has_window(window_id)) - .unwrap(); - ongoing_scrolling_dnd = Some(!ws.is_floating(window_id)); + ongoing_scrolling_dnd.get_or_insert_with(|| { + let (_, _, ws) = self + .workspaces() + .find(|(_, _, ws)| ws.has_window(window_id)) + .unwrap(); + !ws.is_floating(window_id) + }); } match &mut self.monitor_set { diff --git a/src/layout/tests.rs b/src/layout/tests.rs index ecb9d683..813f4765 100644 --- a/src/layout/tests.rs +++ b/src/layout/tests.rs @@ -589,6 +589,15 @@ enum Op { #[proptest(strategy = "1..=5usize")] window: usize, }, + DndUpdate { + #[proptest(strategy = "1..=5usize")] + output_idx: usize, + #[proptest(strategy = "-20000f64..20000f64")] + px: f64, + #[proptest(strategy = "-20000f64..20000f64")] + py: f64, + }, + DndEnd, InteractiveResizeBegin { #[proptest(strategy = "1..=5usize")] window: usize, @@ -1353,6 +1362,16 @@ impl Op { Op::InteractiveMoveEnd { window } => { layout.interactive_move_end(&window); } + Op::DndUpdate { output_idx, px, py } => { + let name = format!("output{output_idx}"); + let Some(output) = layout.outputs().find(|o| o.name() == name).cloned() else { + return; + }; + layout.dnd_update(output, Point::from((px, py))); + } + Op::DndEnd => { + layout.dnd_end(); + } Op::InteractiveResizeBegin { window, edges } => { layout.interactive_resize_begin(window, edges); } |
