aboutsummaryrefslogtreecommitdiff
path: root/src/layout
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2025-02-15 13:11:34 +0300
committerIvan Molodetskikh <yalterz@gmail.com>2025-02-15 13:28:57 +0300
commitca1500ae90f33344fe776898ae11137bf7d1ecc1 (patch)
tree75bd257aae6da74f14e6b5f4097c5e7778414f49 /src/layout
parentd7f3ca00c70b264032025c262b3c0b10e67c04be (diff)
downloadniri-ca1500ae90f33344fe776898ae11137bf7d1ecc1.tar.gz
niri-ca1500ae90f33344fe776898ae11137bf7d1ecc1.tar.bz2
niri-ca1500ae90f33344fe776898ae11137bf7d1ecc1.zip
Implement scrolling the view during DnD
DnD is external to the layout, so we just inform it when one is ongoing.
Diffstat (limited to 'src/layout')
-rw-r--r--src/layout/mod.rs100
-rw-r--r--src/layout/tests.rs19
2 files changed, 97 insertions, 22 deletions
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);
}