use { crate::{ backend::{AXIS_120, AxisSource, ButtonState, ScrollAxis}, cursor::KnownCursor, fixed::Fixed, ifs::{ ipc, ipc::wl_data_source::WlDataSource, wl_seat::{ BTN_LEFT, BTN_RIGHT, CHANGE_CURSOR_MOVED, CHANGE_TREE, Dnd, DroppedDnd, NodeSeatState, WlSeatError, WlSeatGlobal, wl_pointer::PendingScroll, }, wl_surface::{ WlSurface, dnd_icon::DndIcon, xdg_surface::{xdg_popup::XdgPopup, xdg_toplevel::ResizeEdges}, }, xdg_toplevel_drag_v1::XdgToplevelDragV1, }, rect::Rect, time::Time, tree::{ ContainerNode, ContainerSplit, ContainingNode, FindTreeUsecase, FoundNode, Node, PlaceholderNode, TddType, ToplevelNode, WorkspaceDragDestination, WorkspaceNode, WsMoveConfig, move_ws_to_output, toplevel_set_workspace, }, utils::{bitflags::BitflagsExt, clonecell::CloneCell, smallmap::SmallMap}, }, linearize::LinearizeExt, std::{ cell::Cell, rc::{Rc, Weak}, }, }; pub struct PointerOwnerHolder { default: Rc>, owner: CloneCell>, pending_scroll: PendingScroll, } pub trait ToplevelSelector: 'static { fn set(&self, toplevel: Rc); } pub trait WorkspaceSelector: 'static { fn set(&self, ws: Rc); } impl Default for PointerOwnerHolder { fn default() -> Self { let default = Rc::new(SimplePointerOwner { usecase: DefaultPointerUsecase, }); Self { default: default.clone(), owner: CloneCell::new(default.clone()), pending_scroll: Default::default(), } } } impl PointerOwnerHolder { pub fn button(&self, seat: &Rc, time_usec: u64, button: u32, state: ButtonState) { self.owner.get().button(seat, time_usec, button, state) } pub fn axis_source(&self, axis_source: AxisSource) { self.pending_scroll.source.set(Some(axis_source as _)); } pub fn axis_120(&self, delta: i32, axis: ScrollAxis, inverted: bool) { self.pending_scroll.v120[axis as usize].set(Some(delta)); self.pending_scroll.inverted[axis as usize].set(inverted); } pub fn axis_px(&self, delta: Fixed, axis: ScrollAxis, inverted: bool) { self.pending_scroll.px[axis as usize].set(Some(delta)); self.pending_scroll.inverted[axis as usize].set(inverted); } pub fn axis_stop(&self, axis: ScrollAxis) { self.pending_scroll.stop[axis as usize].set(true); } pub fn frame(&self, px_per_scroll_wheel: f64, seat: &Rc, time_usec: u64) { self.pending_scroll.time_usec.set(time_usec); let pending = self.pending_scroll.take(); for axis in ScrollAxis::variants() { let axis = axis as usize; if let Some(dist) = pending.v120[axis].get() { let px = (dist as f64 / AXIS_120 as f64) * px_per_scroll_wheel; pending.px[axis].set(Some(Fixed::from_f64(px))); } } seat.for_each_ei_seat(|ei_seat| { ei_seat.handle_pending_scroll(time_usec, &pending); }); seat.state.for_each_seat_tester(|t| { t.send_axis(seat.id, time_usec, &pending); }); if let Some(node) = self.owner.get().axis_node(seat) { node.node_on_axis_event(seat, &pending); } } pub fn relative_motion( &self, seat: &Rc, time_usec: u64, dx: Fixed, dy: Fixed, dx_unaccelerated: Fixed, dy_unaccelerated: Fixed, ) { if let Some(n) = self.owner.get().axis_node(seat) { n.node_on_pointer_relative_motion( seat, time_usec, dx, dy, dx_unaccelerated, dy_unaccelerated, ); } } pub fn apply_changes(&self, seat: &Rc) { self.owner.get().apply_changes(seat) } pub fn start_drag( &self, seat: &Rc, origin: &Rc, source: Option>, icon: Option>, serial: u64, ) -> Result<(), WlSeatError> { self.owner .get() .start_drag(seat, origin, source, icon, serial) } pub fn cancel_dnd(&self, seat: &Rc) { self.owner.get().cancel_dnd(seat) } pub fn revert_to_default(&self, seat: &Rc) { self.owner.get().revert_to_default(seat) } pub fn grab_node_removed(&self, seat: &Rc) { self.owner.get().grab_node_removed(seat); } pub fn dnd_target_removed(&self, seat: &Rc) { self.owner.get().dnd_target_removed(seat); } pub fn dnd_icon(&self) -> Option> { self.owner.get().dnd_icon() } pub fn toplevel_drag(&self) -> Option> { self.owner.get().toplevel_drag() } pub fn remove_dnd_icon(&self) { self.owner.get().remove_dnd_icon() } pub fn clear(&self) { self.owner.set(self.default.clone()); } fn set_default_pointer_owner(&self, seat: &Rc) { seat.pointer_owner.owner.set(self.default.clone()); seat.changes.or_assign(CHANGE_CURSOR_MOVED); } fn select_element(&self, seat: &Rc, usecase: impl SimplePointerOwnerUsecase) { self.revert_to_default(seat); if let Some(node) = seat.pointer_stack.borrow().last() { usecase.node_focus(seat, node); } self.owner.set(Rc::new(SimplePointerOwner { usecase })); seat.trigger_tree_changed(false); } pub fn select_toplevel(&self, seat: &Rc, selector: impl ToplevelSelector) { let usecase = Rc::new(SelectToplevelUsecase { seat: Rc::downgrade(seat), selector, latest: Default::default(), }); self.select_element(seat, usecase) } pub fn select_workspace(&self, seat: &Rc, selector: impl WorkspaceSelector) { let usecase = Rc::new(SelectWorkspaceUsecase { seat: Rc::downgrade(seat), selector, latest: Default::default(), }); self.select_element(seat, usecase) } pub fn set_window_management_enabled(&self, seat: &Rc, enabled: bool) { let owner = self.owner.get(); if enabled { owner.enable_window_management(seat); } else { owner.disable_window_management(seat); } } 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); } pub fn start_popup_move(&self, seat: &Rc, popup: &Rc, serial: u64) { self.owner.get().start_popup_move(seat, popup, serial); } pub fn start_popup_resize( &self, seat: &Rc, popup: &Rc, edges: ResizeEdges, serial: u64, ) { self.owner .get() .start_popup_resize(seat, popup, edges, serial); } } trait PointerOwner { fn button(&self, seat: &Rc, time_usec: u64, button: u32, state: ButtonState); fn axis_node(&self, seat: &Rc) -> Option> { let _ = seat; None } fn apply_changes(&self, seat: &Rc); fn start_drag( &self, seat: &Rc, origin: &Rc, source: Option>, icon: Option>, serial: u64, ) -> Result<(), WlSeatError> { let _ = origin; let _ = icon; let _ = serial; if let Some(src) = source { src.send_cancelled(seat); } Ok(()) } fn cancel_dnd(&self, seat: &Rc) { seat.dropped_dnd.borrow_mut().take(); } fn revert_to_default(&self, seat: &Rc); fn grab_node_removed(&self, seat: &Rc) { self.revert_to_default(seat); } fn dnd_target_removed(&self, seat: &Rc) { self.cancel_dnd(seat); } fn dnd_icon(&self) -> Option> { None } fn toplevel_drag(&self) -> Option> { None } fn remove_dnd_icon(&self) { // nothing } fn enable_window_management(&self, seat: &Rc) { let _ = seat; } fn disable_window_management(&self, seat: &Rc) { let _ = seat; } fn start_tile_drag(&self, seat: &Rc, tl: &Rc) { let _ = seat; let _ = tl; } fn start_workspace_drag(&self, seat: &Rc, ws: &Rc) { let _ = seat; let _ = ws; } fn start_popup_move(&self, seat: &Rc, popup: &Rc, serial: u64) { let _ = seat; let _ = popup; let _ = serial; } fn start_popup_resize( &self, seat: &Rc, popup: &Rc, edges: ResizeEdges, serial: u64, ) { let _ = seat; let _ = popup; let _ = edges; let _ = serial; } } struct SimplePointerOwner { usecase: T, } struct SimpleGrabPointerOwner { usecase: T, buttons: SmallMap, node: Rc, serial: u64, } struct DndPointerOwner { button: u32, dnd: Dnd, target: CloneCell>, icon: CloneCell>>, pos_x: Cell, pos_y: Cell, } #[derive(Copy, Clone)] struct DefaultPointerUsecase; struct SelectToplevelUsecase { seat: Weak, latest: CloneCell>>, selector: S, } struct SelectWorkspaceUsecase { seat: Weak, latest: CloneCell>>, selector: S, } #[derive(Copy, Clone)] struct WindowManagementUsecase; impl PointerOwner for SimplePointerOwner { fn button(&self, seat: &Rc, time_usec: u64, button: u32, state: ButtonState) { if state != ButtonState::Pressed { return; } let pn = match seat.pointer_node() { Some(n) => n, _ => return, }; if self.usecase.default_button(self, seat, button, &pn) { return; } let serial = seat.state.next_serial(pn.node_client().as_deref()); seat.pointer_owner .owner .set(Rc::new(SimpleGrabPointerOwner { usecase: self.usecase.clone(), buttons: SmallMap::new_with(button, serial), node: pn.clone(), serial, })); pn.node_restack(); pn.node_seat_state().add_pointer_grab(seat); seat.handle_node_button(pn, time_usec, button, state, serial); } fn axis_node(&self, seat: &Rc) -> Option> { seat.pointer_node() } fn apply_changes(&self, seat: &Rc) { let (x, y) = seat.pointer_cursor.position(); let mut found_tree = seat.found_tree.borrow_mut(); let mut stack = seat.pointer_stack.borrow_mut(); let x_int = x.round_down(); let y_int = y.round_down(); found_tree.push(FoundNode { node: seat.state.root.clone(), x: x_int, y: y_int, }); seat.state .root .node_find_tree_at(x_int, y_int, &mut found_tree, T::FIND_TREE_USECASE); let mut divergence = found_tree.len().min(stack.len()); for (i, (found, stack)) in found_tree.iter().zip(stack.iter()).enumerate() { if found.node.node_id() != stack.node_id() { divergence = i; break; } } let psm = seat.pointer_stack_modified.replace(false); if !psm && (stack.len(), found_tree.len()) == (divergence, divergence) { if let Some(node) = found_tree.last() { node.node.clone().node_on_pointer_motion( seat, x.apply_fract(node.x), y.apply_fract(node.y), ); } } else { if let Some(last) = stack.last() { last.node_on_pointer_unfocus(seat); } for old in stack.drain(divergence..).rev() { old.node_on_leave(seat); old.node_seat_state().leave(seat); } if found_tree.len() == divergence { if let Some(node) = found_tree.last() { node.node.clone().node_on_pointer_motion( seat, x.apply_fract(node.x), y.apply_fract(node.y), ); } } else { for new in found_tree.drain(divergence..) { new.node.node_seat_state().enter(seat); new.node.clone().node_on_pointer_enter( seat, x.apply_fract(new.x), y.apply_fract(new.y), ); stack.push(new.node); } } if let Some(node) = stack.last() { node.node_on_pointer_focus(seat); self.usecase.node_focus(seat, node); } } found_tree.clear(); } fn revert_to_default(&self, seat: &Rc) { if !T::IS_DEFAULT { seat.pointer_owner.set_default_pointer_owner(seat); seat.trigger_tree_changed(false); } } fn enable_window_management(&self, seat: &Rc) { if !T::IS_DEFAULT { return; } seat.pointer_owner.owner.set(Rc::new(SimplePointerOwner { usecase: WindowManagementUsecase, })); seat.changes.or_assign(CHANGE_TREE); seat.apply_changes(); } fn disable_window_management(&self, seat: &Rc) { self.usecase.disable_window_management(seat); } } impl SimpleGrabPointerOwner { fn find_button(&self, serial: u64) -> Option { for (button, s) in self.buttons.iter() { if s == serial { return Some(button); } } None } } impl PointerOwner for SimpleGrabPointerOwner { fn button(&self, seat: &Rc, time_usec: u64, button: u32, state: ButtonState) { let serial = seat.state.next_serial(self.node.node_client().as_deref()); match state { ButtonState::Released => { if self.buttons.remove(&button).is_none() { return; } if self.buttons.is_empty() { self.node.node_seat_state().remove_pointer_grab(seat); // log::info!("button"); self.usecase.release_grab(seat); seat.tree_changed.trigger(); } } ButtonState::Pressed => { if self.buttons.insert(button, serial).is_some() { return; } } } seat.handle_node_button(self.node.clone(), time_usec, button, state, serial); } fn axis_node(&self, _seat: &Rc) -> Option> { Some(self.node.clone()) } fn apply_changes(&self, seat: &Rc) { let (x, y) = seat.pointer_cursor.position(); let pos = self.node.node_absolute_position(); let (x_int, y_int) = pos.translate(x.round_down(), y.round_down()); // log::info!("apply_changes"); self.node .clone() .node_on_pointer_motion(seat, x.apply_fract(x_int), y.apply_fract(y_int)); } fn start_drag( &self, seat: &Rc, origin: &Rc, src: Option>, icon: Option>, serial: u64, ) -> Result<(), WlSeatError> { self.usecase .start_drag(self, seat, origin, src, icon, serial) } fn revert_to_default(&self, seat: &Rc) { let serial = seat.state.next_serial(self.node.node_client().as_deref()); let time_usec = Time::now_unchecked().usec(); for (button, _) in &self.buttons { seat.handle_node_button( self.node.clone(), time_usec, button, ButtonState::Released, serial, ); } self.node.node_seat_state().remove_pointer_grab(seat); seat.pointer_owner.set_default_pointer_owner(seat); } 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); } fn start_popup_move(&self, seat: &Rc, popup: &Rc, serial: u64) { let Some(button) = self.find_button(serial) else { return; }; self.usecase.start_popup_move(self, seat, popup, button); } fn start_popup_resize( &self, seat: &Rc, popup: &Rc, edges: ResizeEdges, serial: u64, ) { let Some(button) = self.find_button(serial) else { return; }; self.usecase .start_popup_resize(self, seat, popup, edges, button); } } impl PointerOwner for DndPointerOwner { fn button(&self, seat: &Rc, _time_usec: u64, button: u32, state: ButtonState) { if button != self.button || state != ButtonState::Released { return; } let target = self.target.get(); target.node_on_dnd_drop(&self.dnd); if let Some(src) = &self.dnd.src { src.on_drop(); } let should_drop = match &self.dnd.src { None => true, Some(s) => s.can_drop(), }; if should_drop { *seat.dropped_dnd.borrow_mut() = Some(DroppedDnd { dnd: self.dnd.clone(), }); } target.node_on_dnd_leave(&self.dnd); target.node_seat_state().remove_dnd_target(seat); if !should_drop && let Some(src) = &self.dnd.src { ipc::detach_seat(&**src, seat); } if let Some(icon) = self.icon.get() { icon.disable(); } seat.pointer_owner.set_default_pointer_owner(seat); seat.tree_changed.trigger(); if let Some(src) = &self.dnd.src { src.finish_toplevel_drag(seat); } } fn apply_changes(&self, seat: &Rc) { let (x, y) = seat.pointer_cursor.position(); let FoundNode { node, x: x_int, y: y_int, } = seat.state.node_at(x.round_down(), y.round_down()); let (x, y) = (x.apply_fract(x_int), y.apply_fract(y_int)); let mut target = self.target.get(); if node.node_id() != target.node_id() { target.node_on_dnd_leave(&self.dnd); target.node_seat_state().remove_dnd_target(seat); target = node; target.node_on_dnd_enter( &self.dnd, x, y, seat.state.next_serial(target.node_client().as_deref()), ); target.node_seat_state().add_dnd_target(seat); self.target.set(target); } else if (self.pos_x.get(), self.pos_y.get()) != (x, y) { node.node_on_dnd_motion(&self.dnd, seat.pos_time_usec.get(), x, y); } self.pos_x.set(x); self.pos_y.set(y); } fn cancel_dnd(&self, seat: &Rc) { let target = self.target.get(); target.node_on_dnd_leave(&self.dnd); target.node_seat_state().remove_dnd_target(seat); if let Some(src) = &self.dnd.src { ipc::detach_seat(&**src, seat); } if let Some(icon) = self.icon.get() { icon.disable(); } seat.pointer_owner.set_default_pointer_owner(seat); seat.tree_changed.trigger(); } fn revert_to_default(&self, seat: &Rc) { self.cancel_dnd(seat) } fn dnd_target_removed(&self, seat: &Rc) { self.target.get().node_on_dnd_leave(&self.dnd); self.target.set(seat.state.root.clone()); seat.state.tree_changed(); } fn dnd_icon(&self) -> Option> { self.icon.get() } fn toplevel_drag(&self) -> Option> { if let Some(src) = &self.dnd.src { src.toplevel_drag.get() } else { None } } fn remove_dnd_icon(&self) { self.icon.set(None); } } trait SimplePointerOwnerUsecase: Sized + Clone + 'static { const FIND_TREE_USECASE: FindTreeUsecase; const IS_DEFAULT: bool; fn default_button( &self, spo: &SimplePointerOwner, seat: &Rc, button: u32, pn: &Rc, ) -> bool; fn start_drag( &self, grab: &SimpleGrabPointerOwner, seat: &Rc, origin: &Rc, src: Option>, icon: Option>, serial: u64, ) -> Result<(), WlSeatError> { let _ = grab; let _ = origin; let _ = icon; let _ = serial; if let Some(src) = src { src.send_cancelled(seat); } Ok(()) } fn release_grab(&self, seat: &Rc); fn node_focus(&self, seat: &Rc, node: &Rc) { let _ = seat; let _ = node; } fn disable_window_management(&self, seat: &Rc) { let _ = seat; } fn start_tile_drag( &self, grab: &SimpleGrabPointerOwner, seat: &Rc, tl: &Rc, ) { let _ = grab; let _ = seat; let _ = tl; } fn start_workspace_drag( &self, grab: &SimpleGrabPointerOwner, seat: &Rc, ws: &Rc, ) { let _ = grab; let _ = seat; let _ = ws; } fn start_popup_move( &self, grab: &SimpleGrabPointerOwner, seat: &Rc, popup: &Rc, button: u32, ) { let _ = grab; let _ = seat; let _ = popup; let _ = button; } fn start_popup_resize( &self, grab: &SimpleGrabPointerOwner, seat: &Rc, popup: &Rc, edges: ResizeEdges, button: u32, ) { let _ = grab; let _ = seat; let _ = popup; let _ = edges; let _ = button; } } impl DefaultPointerUsecase { fn prepare_new_usecase(&self, grab: &SimpleGrabPointerOwner, seat: &Rc) { { let mut stack = seat.pointer_stack.borrow_mut(); for node in stack.drain(1..).rev() { node.node_on_leave(seat); node.node_seat_state().leave(seat); } } grab.node.node_seat_state().remove_pointer_grab(seat); } fn start_ui_drag( &self, grab: &SimpleGrabPointerOwner, seat: &Rc, usecase: T, ) { self.prepare_new_usecase(grab, seat); usecase.node_seat_state().add_ui_drag(seat); let pointer_owner = Rc::new(UiDragPointerOwner { usecase }); seat.pointer_owner.owner.set(pointer_owner.clone()); pointer_owner.apply_changes(seat); } fn start_popup_usecase( &self, grab: &SimpleGrabPointerOwner, seat: &Rc, popup: &Rc, button: u32, usecase: U, ) { self.prepare_new_usecase(grab, seat); seat.pointer_owner.owner.set(Rc::new(PopupPointerOwner { popup: popup.clone(), window_management: false, button, usecase, })); popup.node_seat_state().add_pointer_grab(seat); popup.add_interactive_move(seat); } } impl SimplePointerOwnerUsecase for DefaultPointerUsecase { const FIND_TREE_USECASE: FindTreeUsecase = FindTreeUsecase::None; const IS_DEFAULT: bool = true; fn default_button( &self, _spo: &SimplePointerOwner, _seat: &Rc, _button: u32, _pn: &Rc, ) -> bool { false } fn start_drag( &self, grab: &SimpleGrabPointerOwner, seat: &Rc, origin: &Rc, src: Option>, icon: Option>, serial: u64, ) -> Result<(), WlSeatError> { let button = match grab.buttons.iter().next() { Some((b, _)) => b, None => return Ok(()), }; if grab.buttons.len() != 1 { return Ok(()); } if serial != grab.serial { return Ok(()); } if grab.node.node_id() != origin.node_id { return Ok(()); } if let Some(icon) = &icon { icon.enable(); } if let Some(new) = &src { ipc::attach_seat(&**new, seat, ipc::Role::Dnd)?; if let Some(drag) = new.toplevel_drag.get() { drag.start_drag(); } } *seat.dropped_dnd.borrow_mut() = None; let pointer_owner = Rc::new(DndPointerOwner { button, dnd: Dnd { seat: seat.clone(), client: origin.client.clone(), src, }, target: CloneCell::new(seat.state.root.clone()), icon: CloneCell::new(icon), pos_x: Cell::new(Fixed::from_int(0)), pos_y: Cell::new(Fixed::from_int(0)), }); self.prepare_new_usecase(grab, seat); // { // let old = seat.keyboard_node.set(seat.state.root.clone()); // old.seat_state().unfocus(seat); // old.unfocus(seat); // } seat.pointer_owner.owner.set(pointer_owner.clone()); pointer_owner.apply_changes(seat); Ok(()) } fn release_grab(&self, seat: &Rc) { seat.pointer_owner.set_default_pointer_owner(seat); } fn start_tile_drag( &self, grab: &SimpleGrabPointerOwner, seat: &Rc, tl: &Rc, ) { self.start_ui_drag( grab, seat, TileDragUsecase { tl: tl.clone(), destination: Default::default(), }, ); } fn start_workspace_drag( &self, grab: &SimpleGrabPointerOwner, seat: &Rc, ws: &Rc, ) { self.start_ui_drag( grab, seat, WorkspaceDragUsecase { ws: ws.clone(), destination: Default::default(), }, ); } fn start_popup_move( &self, grab: &SimpleGrabPointerOwner, seat: &Rc, popup: &Rc, button: u32, ) { let (x, y) = seat.pointer_cursor.position(); let (dx, dy) = popup .node_absolute_position() .translate(x.round_down(), y.round_down()); self.start_popup_usecase( grab, seat, popup, button, PopupPointerOwnerMoveUsecase { dx, dy }, ); seat.pointer_cursor.set_known(KnownCursor::Move); } fn start_popup_resize( &self, grab: &SimpleGrabPointerOwner, seat: &Rc, popup: &Rc, edges: ResizeEdges, button: u32, ) { let cursor = match (edges.top, edges.left, edges.right, edges.bottom) { (true, false, false, false) => KnownCursor::NsResize, (false, false, false, true) => KnownCursor::NsResize, (false, true, false, false) => KnownCursor::EwResize, (false, false, true, false) => KnownCursor::EwResize, (true, true, false, false) => KnownCursor::NwseResize, (false, false, true, true) => KnownCursor::NwseResize, (false, true, false, true) => KnownCursor::NeswResize, (true, false, true, false) => KnownCursor::NeswResize, _ => return, }; let (x, y) = seat.pointer_cursor.position(); let pos = popup.node_absolute_position(); let (mut dx, mut dy) = pos.translate(x.round_down(), y.round_down()); if edges.right { dx = pos.width() - dx; } if edges.bottom { dy = pos.height() - dy; } self.start_popup_usecase( grab, seat, popup, button, PopupPointerOwnerResizeUsecase { edges, dx, dy }, ); seat.pointer_cursor.set_known(cursor); } } trait NodeSelectorUsecase: Sized + 'static { const FIND_TREE_USECASE: FindTreeUsecase; fn default_button( self: &Rc, spo: &SimplePointerOwner>, seat: &Rc, button: u32, pn: &Rc, ) -> bool; fn node_focus(self: &Rc, seat: &Rc, node: &Rc); } impl SimplePointerOwnerUsecase for Rc { const FIND_TREE_USECASE: FindTreeUsecase = ::FIND_TREE_USECASE; const IS_DEFAULT: bool = false; fn default_button( &self, spo: &SimplePointerOwner, seat: &Rc, button: u32, pn: &Rc, ) -> bool { ::default_button(self, spo, seat, button, pn) } fn release_grab(&self, seat: &Rc) { seat.pointer_owner.owner.set(Rc::new(SimplePointerOwner { usecase: self.clone(), })); seat.changes.or_assign(CHANGE_CURSOR_MOVED); } fn node_focus(&self, seat: &Rc, node: &Rc) { ::node_focus(self, seat, node) } } impl NodeSelectorUsecase for SelectToplevelUsecase { const FIND_TREE_USECASE: FindTreeUsecase = FindTreeUsecase::SelectToplevel; fn default_button( self: &Rc, spo: &SimplePointerOwner>, seat: &Rc, button: u32, pn: &Rc, ) -> bool { let Some(tl) = pn.clone().node_into_toplevel() else { return false; }; let selected_toplevel = button == BTN_RIGHT || (button == BTN_LEFT && !tl.tl_admits_children()); if !selected_toplevel { return false; } self.selector.set(tl); spo.revert_to_default(seat); true } fn node_focus(self: &Rc, seat: &Rc, node: &Rc) { let tl = node.clone().node_into_toplevel(); if let Some(tl) = &tl { tl.tl_data().render_highlight.fetch_add(1); if !tl.tl_admits_children() { seat.pointer_cursor().set_known(KnownCursor::Pointer); } seat.state.damage(tl.node_absolute_position()); } if let Some(prev) = self.latest.set(tl) { prev.tl_data().render_highlight.fetch_sub(1); seat.state.damage(prev.node_absolute_position()); } } } impl Drop for SelectToplevelUsecase { fn drop(&mut self) { if let Some(prev) = self.latest.take() { prev.tl_data().render_highlight.fetch_sub(1); if let Some(seat) = self.seat.upgrade() { seat.state.damage(prev.node_absolute_position()); } } } } impl NodeSelectorUsecase for SelectWorkspaceUsecase { const FIND_TREE_USECASE: FindTreeUsecase = FindTreeUsecase::SelectWorkspace; fn default_button( self: &Rc, spo: &SimplePointerOwner>, seat: &Rc, _button: u32, pn: &Rc, ) -> bool { let Some(ws) = pn.clone().node_into_workspace() else { return false; }; self.selector.set(ws); spo.revert_to_default(seat); true } fn node_focus(self: &Rc, seat: &Rc, node: &Rc) { let ws = node.clone().node_into_workspace(); if let Some(ws) = &ws { ws.render_highlight.fetch_add(1); seat.pointer_cursor().set_known(KnownCursor::Pointer); seat.state.damage(ws.position.get()); } if let Some(prev) = self.latest.set(ws) { prev.render_highlight.fetch_sub(1); seat.state.damage(prev.position.get()); } } } impl Drop for SelectWorkspaceUsecase { fn drop(&mut self) { if let Some(prev) = self.latest.take() { prev.render_highlight.fetch_sub(1); if let Some(seat) = self.seat.upgrade() { seat.state.damage(prev.position.get()); } } } } impl SimplePointerOwnerUsecase for WindowManagementUsecase { const FIND_TREE_USECASE: FindTreeUsecase = FindTreeUsecase::SelectToplevelOrPopup; const IS_DEFAULT: bool = false; fn default_button( &self, _spo: &SimplePointerOwner, seat: &Rc, button: u32, pn: &Rc, ) -> bool { let pos = pn.node_absolute_position(); let (x, y) = seat.pointer_cursor.position(); let (x, y) = (x.round_down(), y.round_down()); let (mut dx, mut dy) = pos.translate(x, y); let owner: Rc = if button == BTN_LEFT { if let Some(tl) = pn.clone().node_into_toplevel() { if tl.tl_data().is_root_container() { return false; } seat.pointer_cursor.set_known(KnownCursor::Move); if tl.tl_data().is_fullscreen.get() { Rc::new(ToplevelGrabPointerOwner { tl, usecase: MoveFullscreenToplevelGrabPointerOwner, }) } else if tl.tl_data().float.is_none() { Rc::new(ToplevelGrabPointerOwner { tl: tl.clone(), usecase: TileDragUsecase { tl, destination: Default::default(), }, }) } else { Rc::new(ToplevelGrabPointerOwner { tl, usecase: MoveToplevelGrabPointerOwner { dx, dy }, }) } } else if let Some(popup) = pn.clone().node_into_popup() { popup.add_interactive_move(seat); seat.pointer_cursor.set_known(KnownCursor::Move); Rc::new(PopupPointerOwner { popup, window_management: true, button, usecase: PopupPointerOwnerMoveUsecase { dx, dy }, }) } else { return false; } } else if button == BTN_RIGHT { let mut top = false; let mut right = false; let mut bottom = false; let mut left = false; if dx <= pos.width() / 2 { left = true; } else { right = true; dx = pos.width() - dx; } if dy <= pos.height() / 2 { top = true; } else { bottom = true; dy = pos.height() - dy; } let cursor = match (top, right, bottom, left) { (true, true, false, false) => KnownCursor::NeResize, (false, true, true, false) => KnownCursor::SeResize, (false, false, true, true) => KnownCursor::SwResize, (true, false, false, true) => KnownCursor::NwResize, _ => KnownCursor::Move, }; if let Some(tl) = pn.clone().node_into_toplevel() { seat.pointer_cursor.set_known(cursor); Rc::new(ToplevelGrabPointerOwner { tl, usecase: ResizeToplevelGrabPointerOwner { top, right, bottom, left, dx, dy, }, }) } else if let Some(popup) = pn.clone().node_into_popup() { popup.add_interactive_move(seat); seat.pointer_cursor.set_known(cursor); Rc::new(PopupPointerOwner { popup, window_management: true, button, usecase: PopupPointerOwnerResizeUsecase { edges: ResizeEdges { top, left, right, bottom, }, dx, dy, }, }) } else { return false; } } else { return false; }; seat.pointer_owner.owner.set(owner); pn.node_seat_state().add_pointer_grab(seat); true } fn release_grab(&self, seat: &Rc) { seat.pointer_owner .owner .set(Rc::new(SimplePointerOwner { usecase: *self })); seat.changes.or_assign(CHANGE_CURSOR_MOVED); } fn disable_window_management(&self, seat: &Rc) { seat.pointer_owner.set_default_pointer_owner(seat); seat.apply_changes(); } } trait WindowManagementGrabUsecase { const BUTTON: u32; fn apply_changes( &self, seat: &Rc, parent: Rc, tl: &Rc, ) { let _ = seat; let _ = parent; let _ = tl; } fn on_drop(&self, seat: &Rc, tl: &Rc) { let _ = seat; let _ = tl; } } struct ToplevelGrabPointerOwner { tl: Rc, usecase: T, } impl PointerOwner for ToplevelGrabPointerOwner where T: WindowManagementGrabUsecase, { fn button(&self, seat: &Rc, _time_usec: u64, button: u32, state: ButtonState) { if button != T::BUTTON || state != ButtonState::Released { return; } self.tl.node_seat_state().remove_pointer_grab(seat); self.usecase.on_drop(seat, &self.tl); self.grab_node_removed(seat); } fn apply_changes(&self, seat: &Rc) { let Some(parent) = self.tl.tl_data().parent.get() else { return; }; self.usecase.apply_changes(seat, parent, &self.tl); } fn revert_to_default(&self, seat: &Rc) { self.tl.node_seat_state().remove_pointer_grab(seat); seat.pointer_owner.set_default_pointer_owner(seat); } fn grab_node_removed(&self, seat: &Rc) { if let Some(rect) = seat.ui_drag_highlight.take() { seat.state.damage(rect); } seat.pointer_cursor.set_known(KnownCursor::Default); seat.pointer_owner.owner.set(Rc::new(SimplePointerOwner { usecase: WindowManagementUsecase, })); seat.changes.or_assign(CHANGE_CURSOR_MOVED); seat.apply_changes(); } fn disable_window_management(&self, seat: &Rc) { self.tl.node_seat_state().remove_pointer_grab(seat); seat.pointer_owner.set_default_pointer_owner(seat); seat.apply_changes(); } } struct MoveToplevelGrabPointerOwner { dx: i32, dy: i32, } impl WindowManagementGrabUsecase for MoveToplevelGrabPointerOwner { const BUTTON: u32 = BTN_LEFT; fn apply_changes( &self, seat: &Rc, parent: Rc, tl: &Rc, ) { let (x, y) = seat.pointer_cursor.position(); let (x, y) = (x.round_down() - self.dx, y.round_down() - self.dy); parent.cnode_set_child_position(&**tl, x, y); } } #[derive(Debug)] struct ResizeToplevelGrabPointerOwner { top: bool, right: bool, bottom: bool, left: bool, dx: i32, dy: i32, } impl WindowManagementGrabUsecase for ResizeToplevelGrabPointerOwner { const BUTTON: u32 = BTN_RIGHT; fn apply_changes( &self, seat: &Rc, parent: Rc, tl: &Rc, ) { let (x, y) = seat.pointer_cursor.position(); let (x, y) = (x.round_down(), y.round_down()); let pos = tl.node_absolute_position(); let mut x1 = None; let mut x2 = None; let mut y1 = None; let mut y2 = None; if self.top { let new_v = y - self.dy; if new_v != pos.y1() { y1 = Some(new_v); } } if self.right { let new_v = x + self.dx; if new_v != pos.x2() { x2 = Some(new_v); } } if self.bottom { let new_v = y + self.dy; if new_v != pos.y2() { y2 = Some(new_v); } } if self.left { let new_v = x - self.dx; if new_v != pos.x1() { x1 = Some(new_v); } } if x1.is_some() || x2.is_some() || y1.is_some() || y2.is_some() { parent.cnode_resize_child(&**tl, x1, y1, x2, y2); } } } struct MoveFullscreenToplevelGrabPointerOwner; impl WindowManagementGrabUsecase for MoveFullscreenToplevelGrabPointerOwner { const BUTTON: u32 = BTN_LEFT; fn on_drop(&self, seat: &Rc, tl: &Rc) { let data = tl.tl_data(); if !data.is_fullscreen.get() { return; } let output = 'output: { let (x, y) = seat.pointer_cursor.position_int(); let outputs = seat.state.root.outputs.lock(); for output in outputs.values() { let pos = output.global.pos.get(); if pos.contains(x, y) { break 'output output.clone(); } } return; }; let ws = output.ensure_workspace(); toplevel_set_workspace(&seat.state, tl.clone(), &ws); } } trait UiDragUsecase: 'static { fn node_seat_state(&self) -> &NodeSeatState; fn left_button_up(&self, seat: &Rc); fn apply_changes(&self, seat: &Rc) -> Option; } struct UiDragPointerOwner { usecase: T, } impl UiDragPointerOwner where T: UiDragUsecase, { fn do_revert_to_default(&self, seat: &Rc, needs_layout: bool) { self.usecase.node_seat_state().remove_ui_drag(seat); if let Some(rect) = seat.ui_drag_highlight.take() { seat.state.damage(rect); } seat.pointer_owner.set_default_pointer_owner(seat); seat.trigger_tree_changed(needs_layout); } } impl PointerOwner for UiDragPointerOwner where T: UiDragUsecase, { fn button(&self, seat: &Rc, _time_usec: u64, button: u32, state: ButtonState) { if button == BTN_RIGHT { self.do_revert_to_default(seat, false); return; } if button != BTN_LEFT || state != ButtonState::Released { return; } self.apply_changes(seat); self.usecase.left_button_up(seat); self.do_revert_to_default(seat, true); } fn apply_changes(&self, seat: &Rc) { let new_highlight = self.usecase.apply_changes(seat); handle_ui_drag_highlight(seat, new_highlight); } fn revert_to_default(&self, seat: &Rc) { self.do_revert_to_default(seat, false); } } fn handle_ui_drag_highlight(seat: &Rc, new_highlight: Option) { let prev_highlight = seat.ui_drag_highlight.replace(new_highlight); if prev_highlight != new_highlight { if let Some(rect) = prev_highlight { seat.state.damage(rect); } if let Some(rect) = new_highlight { seat.state.damage(rect); } } } struct TileDragUsecase { tl: Rc, destination: Cell>, } impl UiDragUsecase for TileDragUsecase { fn node_seat_state(&self) -> &NodeSeatState { self.tl.node_seat_state() } fn left_button_up(&self, seat: &Rc) { let Some(dest) = self.destination.take() else { return; }; let src = self.tl.clone(); let Some(src_parent) = src.tl_data().parent.get() else { return; }; let detach = || { let placeholder = Rc::new_cyclic(|weak| PlaceholderNode::new_empty(&seat.state, weak)); src_parent .clone() .cnode_replace_child(&*src, placeholder.clone()); placeholder }; let new_container = |workspace: &Rc| { src_parent.clone().cnode_remove_child2(&*src, true); let cn = ContainerNode::new( &seat.state, &workspace, src.clone(), ContainerSplit::Horizontal, ); workspace.set_container(&cn); }; match dest { TddType::Replace(dst) => { let Some(dst_parent) = dst.tl_data().parent.get() else { return; }; let placeholder = detach(); dst_parent.cnode_replace_child(&*dst, src); src_parent.cnode_replace_child(&*placeholder, dst); } TddType::Split { node, split, before, } => { let data = node.tl_data(); let Some(pn) = data.parent.get() else { return; }; let Some(ws) = data.workspace.get() else { return; }; let placeholder = detach(); let cn = ContainerNode::new(&seat.state, &ws, node.clone(), split); pn.cnode_replace_child(&*node, cn.clone()); match before { true => cn.add_child_before(&*node, src), false => cn.add_child_after(&*node, src), } src_parent.cnode_remove_child(&*placeholder); } TddType::Insert { container, neighbor, before, } => { let placeholder = detach(); match before { true => container.add_child_before(&*neighbor, src), false => container.add_child_after(&*neighbor, src), }; src_parent.cnode_remove_child(&*placeholder); } TddType::NewWorkspace { output } => { new_container(&output.ensure_workspace()); } TddType::NewContainer { workspace } => { new_container(&workspace); } TddType::MoveToWorkspace { workspace } => { src_parent.cnode_remove_child(&*src); seat.state.map_tiled_on(src, &workspace); } TddType::MoveToNewWorkspace { output } => { let ws = output.generate_workspace(); src_parent.cnode_remove_child(&*src); seat.state.map_tiled_on(src, &ws); } } } fn apply_changes(&self, seat: &Rc) -> Option { if self.tl.tl_data().is_root_container() { self.destination.take(); return None; } let (x, y) = seat.pointer_cursor.position(); let dest = seat.state.root.tile_drag_destination( self.tl.node_id(), x.round_down(), y.round_down(), ); match dest { None => { self.destination.take(); None } Some(d) => { self.destination.set(Some(d.ty)); Some(d.highlight) } } } } impl WindowManagementGrabUsecase for TileDragUsecase { const BUTTON: u32 = BTN_LEFT; fn apply_changes( &self, seat: &Rc, _parent: Rc, _tl: &Rc, ) { let dest = UiDragUsecase::apply_changes(self, seat); handle_ui_drag_highlight(seat, dest); } fn on_drop(&self, seat: &Rc, _tl: &Rc) { UiDragUsecase::left_button_up(self, seat); } } 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) } } } } struct PopupPointerOwner { popup: Rc, window_management: bool, button: u32, usecase: T, } trait PopupPointerOwnerUsecase: Sized + 'static { fn apply_changes(&self, popup: &Rc, seat: &Rc); } impl PopupPointerOwner where T: PopupPointerOwnerUsecase, { fn revert_to_window_management(&self, seat: &Rc) { self.popup.remove_interactive_move(seat); self.popup.node_seat_state().remove_pointer_grab(seat); seat.pointer_cursor.set_known(KnownCursor::Default); seat.pointer_owner.owner.set(Rc::new(SimplePointerOwner { usecase: WindowManagementUsecase, })); seat.changes.or_assign(CHANGE_CURSOR_MOVED); seat.apply_changes(); } fn revert_to_previous(&self, seat: &Rc) { if self.window_management { self.revert_to_window_management(seat); } else { self.revert_to_default(seat); } } } impl PointerOwner for PopupPointerOwner where T: PopupPointerOwnerUsecase, { fn button(&self, seat: &Rc, _time_usec: u64, button: u32, state: ButtonState) { if button != self.button || state != ButtonState::Released { return; } self.revert_to_previous(seat); } fn apply_changes(&self, seat: &Rc) { if seat.changes.get().not_contains(CHANGE_CURSOR_MOVED) { return; } self.usecase.apply_changes(&self.popup, seat); } fn revert_to_default(&self, seat: &Rc) { self.popup.remove_interactive_move(seat); self.popup.node_seat_state().remove_pointer_grab(seat); seat.pointer_owner.set_default_pointer_owner(seat); seat.tree_changed.trigger(); } fn grab_node_removed(&self, seat: &Rc) { self.revert_to_previous(seat); } fn disable_window_management(&self, seat: &Rc) { if self.window_management { self.revert_to_default(seat); } } } struct PopupPointerOwnerMoveUsecase { dx: i32, dy: i32, } impl PopupPointerOwnerUsecase for PopupPointerOwnerMoveUsecase { fn apply_changes(&self, popup: &Rc, seat: &Rc) { let (x, y) = seat.pointer_cursor.position(); let (x, y) = (x.round_down(), y.round_down()); let pos = popup.node_absolute_position(); let (x, y) = pos.translate(x, y); if (x, y) != (self.dx, self.dy) { popup.move_(x - self.dx, y - self.dy); seat.tree_changed.trigger(); } } } struct PopupPointerOwnerResizeUsecase { edges: ResizeEdges, dx: i32, dy: i32, } impl PopupPointerOwnerUsecase for PopupPointerOwnerResizeUsecase { fn apply_changes(&self, popup: &Rc, seat: &Rc) { let (x, y) = seat.pointer_cursor.position(); let (x, y) = (x.round_down(), y.round_down()); let pos = popup.node_absolute_position(); let (x, y) = pos.translate(x, y); let mut dx1 = 0; let mut dx2 = 0; let mut dy1 = 0; let mut dy2 = 0; if self.edges.left { dx1 = x - self.dx; dx1 = dx1.min(pos.width().saturating_sub(1)); } else if self.edges.right { dx2 = self.dx - (pos.width() - x); dx2 = dx2.max(-pos.width().saturating_sub(1)); } if self.edges.top { dy1 = y - self.dy; dy1 = dy1.min(pos.height().saturating_sub(1)); } else if self.edges.bottom { dy2 = self.dy - (pos.height() - y); dy2 = dy2.max(-pos.height().saturating_sub(1)); } if dx1 != 0 || dx2 != 0 || dy1 != 0 || dy2 != 0 { popup.resize(dx1, dy1, dx2, dy2); seat.tree_changed.trigger(); } } }