From a4f300631348cc9c64aba9d069986519a7d3b3a8 Mon Sep 17 00:00:00 2001 From: Ryan Brue Date: Mon, 18 Mar 2024 00:12:58 -0500 Subject: [PATCH] feat: maximize/half tiling drag zones --- src/backend/render/mod.rs | 1 + src/shell/grabs/moving.rs | 133 ++++++++++++++++++++++++++++++- src/shell/layout/floating/mod.rs | 89 ++++++++++++--------- 3 files changed, 182 insertions(+), 41 deletions(-) diff --git a/src/backend/render/mod.rs b/src/backend/render/mod.rs index 6090d9b8..ee57e424 100644 --- a/src/backend/render/mod.rs +++ b/src/backend/render/mod.rs @@ -108,6 +108,7 @@ pub enum Usage { MoveGrabIndicator, FocusIndicator, PotentialGroupIndicator, + SnappingIndicator, } #[derive(Clone)] diff --git a/src/shell/grabs/moving.rs b/src/shell/grabs/moving.rs index ff695db5..1fa94313 100644 --- a/src/shell/grabs/moving.rs +++ b/src/shell/grabs/moving.rs @@ -12,7 +12,8 @@ use crate::{ CosmicMappedRenderElement, }, focus::target::{KeyboardFocusTarget, PointerFocusTarget}, - CosmicMapped, CosmicSurface, ManagedLayer, + layout::floating::TiledCorners, + CosmicMapped, CosmicSurface, Direction, ManagedLayer, }, utils::prelude::*, }; @@ -27,7 +28,7 @@ use smithay::{ ImportAll, ImportMem, Renderer, }, }, - desktop::space::SpaceElement, + desktop::{layer_map_for_output, space::SpaceElement}, input::{ pointer::{ AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent, @@ -55,6 +56,7 @@ pub struct MoveGrabState { indicator_thickness: u8, start: Instant, previous: ManagedLayer, + snapping_zone: Option, stacking_indicator: Option<(StackHover, Point)>, } @@ -141,6 +143,30 @@ impl MoveGrabState { None }; + let non_exclusive_geometry = { + let layers = layer_map_for_output(&output); + layers.non_exclusive_zone() + }; + + let snapping_indicator = match &self.snapping_zone { + Some(t) => vec![IndicatorShader::element( + renderer, + Key::Window(Usage::SnappingIndicator, self.window.clone()), + t.overlay_geometry(non_exclusive_geometry), + 4, + 8, + 0.5, + output_scale.x, + [ + active_window_hint.red, + active_window_hint.green, + active_window_hint.blue, + ], + ) + .into()], + None => vec![], + }; + let (window_elements, popup_elements) = self .window .split_render_elements::>( @@ -188,6 +214,7 @@ impl MoveGrabState { } x => x, })) + .chain(snapping_indicator) .map(I::from) .collect() } @@ -200,6 +227,58 @@ impl MoveGrabState { struct NotSend(pub T); unsafe impl Send for NotSend {} +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SnappingZone { + TopMaxim, + TopSnap, + Left, + Right, + Bottom, +} + +const SNAPPING_RANGE: i32 = 12; + +impl SnappingZone { + pub fn contains( + &self, + point: Point, + non_exclusive_geometry: Rectangle, + output_geometry: Rectangle, + ) -> bool { + if !output_geometry.contains(point) { + return false; + } + match self { + SnappingZone::TopMaxim => point.y < non_exclusive_geometry.loc.y + SNAPPING_RANGE, + SnappingZone::TopSnap => { + point.y < non_exclusive_geometry.loc.y + SNAPPING_RANGE * 2 + && point.y >= non_exclusive_geometry.loc.y + SNAPPING_RANGE + } + SnappingZone::Bottom => { + point.y + > non_exclusive_geometry.loc.y + non_exclusive_geometry.size.h - SNAPPING_RANGE + } + SnappingZone::Left => point.x < non_exclusive_geometry.loc.x + SNAPPING_RANGE, + SnappingZone::Right => { + point.x + > non_exclusive_geometry.loc.x + non_exclusive_geometry.size.w - SNAPPING_RANGE + } + } + } + pub fn overlay_geometry( + &self, + non_exclusive_geometry: Rectangle, + ) -> Rectangle { + match self { + SnappingZone::TopMaxim => non_exclusive_geometry.as_local(), + SnappingZone::TopSnap => TiledCorners::Top.relative_geometry(non_exclusive_geometry), + SnappingZone::Left => TiledCorners::Left.relative_geometry(non_exclusive_geometry), + SnappingZone::Right => TiledCorners::Right.relative_geometry(non_exclusive_geometry), + SnappingZone::Bottom => TiledCorners::Bottom.relative_geometry(non_exclusive_geometry), + } + } +} + pub struct MoveGrab { window: CosmicMapped, start_data: PointerGrabStartData, @@ -297,6 +376,31 @@ impl PointerGrab for MoveGrab { (element, geo.loc.as_logical()) }); } + + // Check for overlapping with zones + if grab_state.previous == ManagedLayer::Floating { + let non_exclusive_geometry = { + let layers = layer_map_for_output(¤t_output); + layers.non_exclusive_zone() + }; + let total_geometry = current_output.geometry().as_logical(); + grab_state.snapping_zone = vec![ + SnappingZone::TopMaxim, + SnappingZone::TopSnap, + SnappingZone::Left, + SnappingZone::Right, + SnappingZone::Bottom, + ] + .iter() + .find(|&x| { + x.contains( + handle.current_location().to_i32_floor(), + non_exclusive_geometry, + total_geometry, + ) + }) + .cloned(); + } } drop(borrow); @@ -454,6 +558,7 @@ impl MoveGrab { indicator_thickness, start: Instant::now(), stacking_indicator: None, + snapping_zone: None, previous: previous_layer, }; @@ -555,11 +660,35 @@ impl Drop for MoveGrab { window_location, grab_state.window.geometry().size.as_global(), )); + let theme = state.common.shell.theme.clone(); let workspace = state.common.shell.active_space_mut(&output); let (window, location) = workspace.floating_layer.drop_window( grab_state.window, window_location.to_local(&workspace.output), ); + + if previous == ManagedLayer::Floating { + if let Some(sz) = grab_state.snapping_zone { + if sz == SnappingZone::TopMaxim { + state.common.shell.maximize_toggle(&window, &seat); + } else { + let direction = match sz { + SnappingZone::TopSnap => Direction::Up, + SnappingZone::Bottom => Direction::Down, + SnappingZone::Left => Direction::Left, + SnappingZone::Right => Direction::Right, + _ => Direction::Up, + }; + workspace.floating_layer.move_element( + direction, + &seat, + ManagedLayer::Floating, + theme, + &window, + ); + } + } + } Some((window, location.to_global(&output))) } } diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index 524a2d4e..6161e5f0 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -894,41 +894,20 @@ impl FloatingLayout { res } - pub fn move_current_element<'a>( + pub fn move_element<'a>( &mut self, direction: Direction, seat: &Seat, layer: ManagedLayer, theme: cosmic::Theme, + element: &CosmicMapped, ) -> MoveResult { - let Some(target) = seat.get_keyboard().unwrap().current_focus() else { - return MoveResult::None; - }; - - let Some(focused) = (match target { - KeyboardFocusTarget::Popup(popup) => { - let Some(toplevel_surface) = (match popup { - PopupKind::Xdg(xdg) => get_popup_toplevel(&xdg), - PopupKind::InputMethod(_) => unreachable!(), - }) else { - return MoveResult::None; - }; - self.space - .elements() - .find(|elem| elem.wl_surface().as_ref() == Some(&toplevel_surface)) - } - KeyboardFocusTarget::Element(elem) => self.space.elements().find(|e| *e == &elem), - _ => None, - }) else { - return MoveResult::None; - }; - - match focused.handle_move(direction) { + match element.handle_move(direction) { StackMoveResult::Handled => MoveResult::Done, StackMoveResult::MoveOut(surface, loop_handle) => { let mapped: CosmicMapped = CosmicWindow::new(surface, loop_handle, theme).into(); let output = seat.active_output(); - let pos = self.space.element_geometry(focused).unwrap().loc + let pos = self.space.element_geometry(element).unwrap().loc + match direction { Direction::Up => Point::from((5, -10)), Direction::Down => Point::from((5, 10)), @@ -950,7 +929,7 @@ impl FloatingLayout { MoveResult::ShiftFocus(KeyboardFocusTarget::Element(mapped)) } StackMoveResult::Default => { - let mut tiled_state = focused.floating_tiled.lock().unwrap(); + let mut tiled_state = element.floating_tiled.lock().unwrap(); let output = self.space.outputs().next().unwrap().clone(); let layers = layer_map_for_output(&output); @@ -959,10 +938,10 @@ impl FloatingLayout { let current_geometry = self .space - .element_geometry(focused) + .element_geometry(element) .map(RectExt::as_local) .unwrap(); - let start_rectangle = if let Some(anim) = self.animations.remove(focused) { + let start_rectangle = if let Some(anim) = self.animations.remove(element) { anim.geometry(output_geometry, current_geometry, tiled_state.as_ref()) } else { current_geometry @@ -995,7 +974,7 @@ impl FloatingLayout { | Some(TiledCorners::BottomRight), ) => { return MoveResult::MoveFurther(KeyboardFocusTarget::Element( - focused.clone(), + element.clone(), )); } @@ -1006,14 +985,14 @@ impl FloatingLayout { | (Direction::Right, Some(TiledCorners::Left)) => { std::mem::drop(tiled_state); - let mut maximized_state = focused.maximized_state.lock().unwrap(); + let mut maximized_state = element.maximized_state.lock().unwrap(); *maximized_state = Some(MaximizedState { original_geometry: start_rectangle, original_layer: layer, }); std::mem::drop(maximized_state); - self.map_maximized(focused.clone(), start_rectangle, true); + self.map_maximized(element.clone(), start_rectangle, true); return MoveResult::Done; } @@ -1044,28 +1023,28 @@ impl FloatingLayout { let new_geo = new_state.relative_geometry(output_geometry); let (new_pos, new_size) = (new_geo.loc, new_geo.size); - focused.set_tiled(true); // TODO: More fine grained? - focused.set_maximized(false); + element.set_tiled(true); // TODO: More fine grained? + element.set_maximized(false); if tiled_state.is_none() { - let last_geometry = focused + let last_geometry = element .maximized_state .lock() .unwrap() .take() .map(|state| state.original_geometry) - .or_else(|| self.space.element_geometry(focused).map(RectExt::as_local)); + .or_else(|| self.space.element_geometry(element).map(RectExt::as_local)); - *focused.last_geometry.lock().unwrap() = last_geometry; + *element.last_geometry.lock().unwrap() = last_geometry; } *tiled_state = Some(new_state); std::mem::drop(tiled_state); - focused.moved_since_mapped.store(true, Ordering::SeqCst); - let focused = focused.clone(); + element.moved_since_mapped.store(true, Ordering::SeqCst); + let element = element.clone(); self.map_internal( - focused, + element, Some(new_pos), Some(new_size.as_logical()), Some(start_rectangle), @@ -1076,6 +1055,38 @@ impl FloatingLayout { } } + pub fn move_current_element<'a>( + &mut self, + direction: Direction, + seat: &Seat, + layer: ManagedLayer, + theme: cosmic::Theme, + ) -> MoveResult { + let Some(target) = seat.get_keyboard().unwrap().current_focus() else { + return MoveResult::None; + }; + + let Some(focused) = (match target { + KeyboardFocusTarget::Popup(popup) => { + let Some(toplevel_surface) = (match popup { + PopupKind::Xdg(xdg) => get_popup_toplevel(&xdg), + PopupKind::InputMethod(_) => unreachable!(), + }) else { + return MoveResult::None; + }; + self.space + .elements() + .find(|elem| elem.wl_surface().as_ref() == Some(&toplevel_surface)) + } + KeyboardFocusTarget::Element(elem) => self.space.elements().find(|x| *x == &elem), + _ => None, + }) else { + return MoveResult::None; + }; + + self.move_element(direction, seat, layer, theme, &focused.clone()) + } + pub fn mapped(&self) -> impl Iterator { self.space.elements().rev() }