From 1dd20fb87b52a1934e00b23fe2cb2ac86b03ea98 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Tue, 1 Oct 2024 10:29:10 +0200 Subject: [PATCH] tree: implement workspace dragging --- src/compositor.rs | 1 + src/config/handler.rs | 2 + src/ifs/wl_seat.rs | 4 ++ src/ifs/wl_seat/pointer_owner.rs | 98 +++++++++++++++++++++++++++++++- src/tasks/connector.rs | 5 ++ src/tree/display.rs | 18 +++++- src/tree/output.rs | 91 ++++++++++++++++++++++++++++- src/tree/workspace.rs | 23 +++++++- 8 files changed, 235 insertions(+), 7 deletions(-) diff --git a/src/compositor.rs b/src/compositor.rs index 193024d4..c8b55322 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -558,6 +558,7 @@ fn create_dummy_output(state: &Rc) { status: Default::default(), scroll: Default::default(), pointer_positions: Default::default(), + pointer_down: Default::default(), lock_surface: Default::default(), hardware_cursor: Default::default(), update_render_data_scheduled: Cell::new(false), diff --git a/src/config/handler.rs b/src/config/handler.rs index 0477b79b..681f6d8b 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -882,8 +882,10 @@ impl ConfigProxyHandler { Some(l) => l.to_ref(), }; let config = WsMoveConfig { + make_visible_always: false, make_visible_if_empty: true, source_is_destroyed: false, + before: None, }; move_ws_to_output(&link, &output, config); ws.desired_output.set(output.global.output_id.clone()); diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index c7610826..4027c6ff 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -779,6 +779,10 @@ impl WlSeatGlobal { self.pointer_owner.start_tile_drag(self, tl); } + pub fn start_workspace_drag(self: &Rc, ws: &Rc) { + self.pointer_owner.start_workspace_drag(self, ws); + } + pub fn cancel_dnd(self: &Rc) { self.pointer_owner.cancel_dnd(self); } diff --git a/src/ifs/wl_seat/pointer_owner.rs b/src/ifs/wl_seat/pointer_owner.rs index c4139be9..5d88ae24 100644 --- a/src/ifs/wl_seat/pointer_owner.rs +++ b/src/ifs/wl_seat/pointer_owner.rs @@ -15,8 +15,9 @@ use { }, rect::Rect, tree::{ - ContainerNode, ContainerSplit, ContainingNode, FindTreeUsecase, FoundNode, Node, - PlaceholderNode, TddType, ToplevelNode, WorkspaceNode, + move_ws_to_output, ContainerNode, ContainerSplit, ContainingNode, FindTreeUsecase, + FoundNode, Node, PlaceholderNode, TddType, ToplevelNode, WorkspaceDragDestination, + WorkspaceNode, WsMoveConfig, }, utils::{clonecell::CloneCell, smallmap::SmallMap}, }, @@ -210,6 +211,10 @@ impl PointerOwnerHolder { pub fn start_tile_drag(&self, seat: &Rc, tl: &Rc) { self.owner.get().start_tile_drag(seat, tl); } + + pub fn start_workspace_drag(&self, seat: &Rc, ws: &Rc) { + self.owner.get().start_workspace_drag(seat, ws); + } } trait PointerOwner { @@ -264,6 +269,11 @@ trait PointerOwner { let _ = seat; let _ = tl; } + + fn start_workspace_drag(&self, seat: &Rc, ws: &Rc) { + let _ = seat; + let _ = ws; + } } struct SimplePointerOwner { @@ -477,6 +487,10 @@ impl PointerOwner for SimpleGrabPointerOwner { fn start_tile_drag(&self, seat: &Rc, tl: &Rc) { self.usecase.start_tile_drag(self, seat, tl); } + + fn start_workspace_drag(&self, seat: &Rc, ws: &Rc) { + self.usecase.start_workspace_drag(self, seat, ws); + } } impl PointerOwner for DndPointerOwner { @@ -646,6 +660,17 @@ trait SimplePointerOwnerUsecase: Sized + Clone + 'static { let _ = seat; let _ = tl; } + + fn start_workspace_drag( + &self, + grab: &SimpleGrabPointerOwner, + seat: &Rc, + ws: &Rc, + ) { + let _ = grab; + let _ = seat; + let _ = ws; + } } impl DefaultPointerUsecase { @@ -762,6 +787,22 @@ impl SimplePointerOwnerUsecase for DefaultPointerUsecase { }, ); } + + fn start_workspace_drag( + &self, + grab: &SimpleGrabPointerOwner, + seat: &Rc, + ws: &Rc, + ) { + self.start_ui_drag( + grab, + seat, + WorkspaceDragUsecase { + ws: ws.clone(), + destination: Default::default(), + }, + ); + } } trait NodeSelectorUsecase: Sized + 'static { @@ -1283,3 +1324,56 @@ impl UiDragUsecase for TileDragUsecase { } } } + +struct WorkspaceDragUsecase { + ws: Rc, + destination: Cell>, +} + +impl UiDragUsecase for WorkspaceDragUsecase { + fn node_seat_state(&self) -> &NodeSeatState { + self.ws.node_seat_state() + } + + fn left_button_up(&self, _seat: &Rc) { + let Some(dest) = self.destination.take() else { + return; + }; + let ws = self.ws.clone(); + let output = dest.output.clone(); + if ws.is_dummy || output.is_dummy { + return; + } + let link = match &*ws.output_link.borrow() { + None => return, + Some(l) => l.to_ref(), + }; + let config = WsMoveConfig { + make_visible_always: true, + make_visible_if_empty: true, + source_is_destroyed: false, + before: dest.before.clone(), + }; + move_ws_to_output(&link, &output, config); + ws.desired_output.set(output.global.output_id.clone()); + } + + fn apply_changes(&self, seat: &Rc) -> Option { + let (x, y) = seat.pointer_cursor.position(); + let dest = + seat.state + .root + .workspace_drag_destination(self.ws.id, x.round_down(), y.round_down()); + match dest { + None => { + self.destination.take(); + None + } + Some(d) => { + let hl = d.highlight; + self.destination.set(Some(d)); + Some(hl) + } + } + } +} diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index 48e74020..494764ce 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -170,6 +170,7 @@ impl ConnectorHandler { status: self.state.status.clone(), scroll: Default::default(), pointer_positions: Default::default(), + pointer_down: Default::default(), lock_surface: Default::default(), hardware_cursor: Default::default(), jay_outputs: Default::default(), @@ -231,8 +232,10 @@ impl ConnectorHandler { && ws.desired_output.get() == output_id) || ws_to_move.is_empty(); let config = WsMoveConfig { + make_visible_always: false, make_visible_if_empty: make_visible, source_is_destroyed: false, + before: None, }; move_ws_to_output(&ws, &on, config); } @@ -304,8 +307,10 @@ impl ConnectorHandler { ws.visible_on_desired_output.set(ws.visible.get()); } let config = WsMoveConfig { + make_visible_always: false, make_visible_if_empty: ws.visible.get(), source_is_destroyed: true, + before: None, }; move_ws_to_output(&ws, &target, config); } diff --git a/src/tree/display.rs b/src/tree/display.rs index 736ca545..b47aba55 100644 --- a/src/tree/display.rs +++ b/src/tree/display.rs @@ -9,7 +9,8 @@ use { state::State, tree::{ walker::NodeVisitor, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId, - OutputNode, StackedNode, TileDragDestination, + OutputNode, StackedNode, TileDragDestination, WorkspaceDragDestination, + WorkspaceNodeId, }, utils::{copyhashmap::CopyHashMap, linkedlist::LinkedList}, }, @@ -98,6 +99,21 @@ impl DisplayNode { } None } + + pub fn workspace_drag_destination( + &self, + source: WorkspaceNodeId, + x: i32, + y: i32, + ) -> Option { + for output in self.outputs.lock().values() { + let pos = output.node_absolute_position(); + if pos.contains(x, y) { + return output.workspace_drag_destination(source, x, y); + } + } + None + } } impl Node for DisplayNode { diff --git a/src/tree/output.rs b/src/tree/output.rs index 8157349a..1df39b9e 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -33,7 +33,8 @@ use { text::TextTexture, tree::{ walker::NodeVisitor, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, - NodeId, StackedNode, TddType, TileDragDestination, WorkspaceNode, + NodeId, StackedNode, TddType, TileDragDestination, WorkspaceDragDestination, + WorkspaceNode, WorkspaceNodeId, }, utils::{ asyncevent::AsyncEvent, clonecell::CloneCell, copyhashmap::CopyHashMap, @@ -73,6 +74,7 @@ pub struct OutputNode { pub status: CloneCell>, pub scroll: Scroller, pub pointer_positions: CopyHashMap, + pub pointer_down: CopyHashMap, pub lock_surface: CloneCell>>, pub hardware_cursor: CloneCell>>, pub hardware_cursor_needs_render: Cell, @@ -858,6 +860,9 @@ impl OutputNode { Some(p) => p, _ => return, }; + if let PointerType::Seat(s) = id { + self.pointer_down.set(s, (x, y)); + } let (x, y) = self.non_exclusive_rect_rel.get().translate(x, y); if y >= self.state.theme.sizes.title_height.get() { return; @@ -1020,6 +1025,64 @@ impl OutputNode { }; c.tile_drag_destination(source, rect, x_abs, y_abs) } + + pub fn workspace_drag_destination( + self: &Rc, + source: WorkspaceNodeId, + x_abs: i32, + y_abs: i32, + ) -> Option { + let rect = self.non_exclusive_rect.get(); + if !rect.contains(x_abs, y_abs) { + return None; + } + let th = self.state.theme.sizes.title_height.get(); + if y_abs - rect.y1() > th + 1 { + return None; + } + let rd = &*self.render_data.borrow(); + let (x, _) = rect.translate(x_abs, y_abs); + let mut prev_is_source = false; + let mut prev_center = 0; + for t in &rd.titles { + if t.ws.id == source { + prev_is_source = true; + continue; + } + let center = (t.x1 + t.x2) / 2; + if x < center { + return if prev_is_source { + None + } else { + Some(WorkspaceDragDestination { + highlight: Rect::new_sized( + rect.x1() + prev_center, + rect.y1(), + center - prev_center, + th, + )?, + output: self.clone(), + before: Some(t.ws.clone()), + }) + }; + } + prev_center = center; + prev_is_source = false; + } + if prev_is_source { + return None; + } + return Some(WorkspaceDragDestination { + highlight: Rect::new_sized( + rect.x1() + prev_center, + rect.y1(), + rect.x2() - prev_center, + th, + )?, + output: self.clone(), + before: None, + }); + } } pub struct OutputTitle { @@ -1214,7 +1277,11 @@ impl Node for OutputNode { state: KeyState, _serial: u32, ) { - if state != KeyState::Pressed || button != BTN_LEFT { + if button != BTN_LEFT { + return; + } + if state != KeyState::Pressed { + self.pointer_down.remove(&seat.id()); return; } self.button(PointerType::Seat(seat.id())); @@ -1258,6 +1325,10 @@ impl Node for OutputNode { self.state.tree_changed(); } + fn node_on_leave(&self, seat: &WlSeatGlobal) { + self.pointer_down.remove(&seat.id()); + } + fn node_on_pointer_enter(self: Rc, seat: &Rc, x: Fixed, y: Fixed) { self.pointer_move(PointerType::Seat(seat.id()), x, y); } @@ -1269,6 +1340,22 @@ impl Node for OutputNode { fn node_on_pointer_motion(self: Rc, seat: &Rc, x: Fixed, y: Fixed) { self.pointer_move(PointerType::Seat(seat.id()), x, y); + if let Some((down_x, down_y)) = self.pointer_down.get(&seat.id()) { + const DRAG_DIST: i32 = 10; + let dx = x.round_down() - down_x; + let dy = y.round_down() - down_y; + if dx * dx + dy * dy > DRAG_DIST * DRAG_DIST { + let rd = self.render_data.borrow_mut(); + for title in &rd.titles { + if down_x >= title.x1 && down_x < title.x2 { + let ws = title.ws.clone(); + drop(rd); + seat.start_workspace_drag(&ws); + break; + } + } + } + } } fn node_on_tablet_tool_leave(&self, tool: &Rc, _time_usec: u64) { diff --git a/src/tree/workspace.rs b/src/tree/workspace.rs index 6e96682e..1f86f63c 100644 --- a/src/tree/workspace.rs +++ b/src/tree/workspace.rs @@ -379,8 +379,10 @@ impl ContainingNode for WorkspaceNode { } pub struct WsMoveConfig { + pub make_visible_always: bool, pub make_visible_if_empty: bool, pub source_is_destroyed: bool, + pub before: Option>, } pub fn move_ws_to_output( @@ -390,8 +392,19 @@ pub fn move_ws_to_output( ) { let source = ws.output.get(); ws.set_output(&target); - target.workspaces.add_last_existing(&ws); - if config.make_visible_if_empty && target.workspace.is_none() && !target.is_dummy { + 'link: { + if let Some(before) = config.before { + if let Some(link) = &*before.output_link.borrow() { + link.prepend_existing(ws); + break 'link; + } + } + target.workspaces.add_last_existing(&ws); + } + let make_visible = !target.is_dummy + && (config.make_visible_always + || (config.make_visible_if_empty && target.workspace.is_none())); + if make_visible { target.show_workspace(&ws); } else { ws.set_visible(false); @@ -423,3 +436,9 @@ pub fn move_ws_to_output( target.state.damage(target.global.pos.get()); } } + +pub struct WorkspaceDragDestination { + pub highlight: Rect, + pub output: Rc, + pub before: Option>, +}