From 25538106215a5413ce469d0aa5bc4e7a2954edc9 Mon Sep 17 00:00:00 2001 From: Daniel <101683475+Koranir@users.noreply.github.com> Date: Fri, 14 Feb 2025 21:58:09 +1100 Subject: [PATCH] grabs: Snap Window Edges to Close Output Edges --- cosmic-comp-config/src/lib.rs | 3 ++ src/config/mod.rs | 6 +++ src/input/mod.rs | 5 +++ src/shell/element/stack.rs | 1 + src/shell/element/window.rs | 1 + src/shell/grabs/menu/default.rs | 30 ++++++++++++--- src/shell/grabs/moving.rs | 39 ++++++++++++++++++- src/shell/layout/floating/grabs/resize.rs | 47 +++++++++++++++++++++++ src/shell/layout/floating/mod.rs | 2 + src/shell/mod.rs | 5 +++ src/wayland/handlers/xdg_shell/mod.rs | 11 ++++-- src/xwayland.rs | 11 ++++-- 12 files changed, 148 insertions(+), 13 deletions(-) diff --git a/cosmic-comp-config/src/lib.rs b/cosmic-comp-config/src/lib.rs index b77a5e215..1f9415008 100644 --- a/cosmic-comp-config/src/lib.rs +++ b/cosmic-comp-config/src/lib.rs @@ -46,6 +46,8 @@ pub struct CosmicCompConfig { pub focus_follows_cursor_delay: u64, /// Let X11 applications scale themselves pub descale_xwayland: bool, + /// The threshold before windows snap themselves to output edges + pub edge_snap_threshold: u32, } impl Default for CosmicCompConfig { @@ -76,6 +78,7 @@ impl Default for CosmicCompConfig { cursor_follows_focus: false, focus_follows_cursor_delay: 250, descale_xwayland: false, + edge_snap_threshold: 0, } } } diff --git a/src/config/mod.rs b/src/config/mod.rs index 406027f16..0f080c5eb 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -846,6 +846,12 @@ fn config_changed(config: cosmic_config::Config, keys: Vec, state: &mut state.common.config.cosmic_conf.focus_follows_cursor_delay = new; } } + "edge_snap_threshold" => { + let new = get_config::(&config, "edge_snap_threshold"); + if new != state.common.config.cosmic_conf.edge_snap_threshold { + state.common.config.cosmic_conf.edge_snap_threshold = new; + } + } _ => {} } } diff --git a/src/input/mod.rs b/src/input/mod.rs index 2a948c4d5..327f23efe 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -760,6 +760,11 @@ impl State { &seat_clone, serial, edge, + state + .common + .config + .cosmic_conf + .edge_snap_threshold, false, ); drop(shell); diff --git a/src/shell/element/stack.rs b/src/shell/element/stack.rs index e14b8a583..9ee25a6d2 100644 --- a/src/shell/element/stack.rs +++ b/src/shell/element/stack.rs @@ -1369,6 +1369,7 @@ impl PointerTarget for CosmicStack { Focus::ResizeRight => ResizeEdge::RIGHT, Focus::Header => unreachable!(), }, + state.common.config.cosmic_conf.edge_snap_threshold, false, ); if let Some((grab, focus)) = res { diff --git a/src/shell/element/window.rs b/src/shell/element/window.rs index 57b525789..ac27add13 100644 --- a/src/shell/element/window.rs +++ b/src/shell/element/window.rs @@ -759,6 +759,7 @@ impl PointerTarget for CosmicWindow { Focus::ResizeRight => ResizeEdge::RIGHT, Focus::Header => unreachable!(), }, + state.common.config.cosmic_conf.edge_snap_threshold, false, ); diff --git a/src/shell/grabs/menu/default.rs b/src/shell/grabs/menu/default.rs index 9a976de9b..4d821d259 100644 --- a/src/shell/grabs/menu/default.rs +++ b/src/shell/grabs/menu/default.rs @@ -288,7 +288,12 @@ pub fn window_items( let _ = handle.insert_idle(move |state| { let mut shell = state.common.shell.write().unwrap(); let seat = shell.seats.last_active().clone(); - let res = shell.menu_resize_request(&resize_clone, &seat, ResizeEdge::TOP); + let res = shell.menu_resize_request( + &resize_clone, + &seat, + ResizeEdge::TOP, + state.common.config.cosmic_conf.edge_snap_threshold, + ); std::mem::drop(shell); if let Some(((target, loc), (grab, focus))) = res { @@ -318,7 +323,12 @@ pub fn window_items( let _ = handle.insert_idle(move |state| { let mut shell = state.common.shell.write().unwrap(); let seat = shell.seats.last_active().clone(); - let res = shell.menu_resize_request(&resize_clone, &seat, ResizeEdge::LEFT); + let res = shell.menu_resize_request( + &resize_clone, + &seat, + ResizeEdge::LEFT, + state.common.config.cosmic_conf.edge_snap_threshold, + ); std::mem::drop(shell); if let Some(((target, loc), (grab, focus))) = res { @@ -348,8 +358,12 @@ pub fn window_items( let _ = handle.insert_idle(move |state| { let mut shell = state.common.shell.write().unwrap(); let seat = shell.seats.last_active().clone(); - let res = - shell.menu_resize_request(&resize_clone, &seat, ResizeEdge::RIGHT); + let res = shell.menu_resize_request( + &resize_clone, + &seat, + ResizeEdge::RIGHT, + state.common.config.cosmic_conf.edge_snap_threshold, + ); std::mem::drop(shell); if let Some(((target, loc), (grab, focus))) = res { @@ -379,8 +393,12 @@ pub fn window_items( let _ = handle.insert_idle(move |state| { let mut shell = state.common.shell.write().unwrap(); let seat = shell.seats.last_active().clone(); - let res = - shell.menu_resize_request(&resize_clone, &seat, ResizeEdge::BOTTOM); + let res = shell.menu_resize_request( + &resize_clone, + &seat, + ResizeEdge::BOTTOM, + state.common.config.cosmic_conf.edge_snap_threshold, + ); std::mem::drop(shell); if let Some(((target, loc), (grab, focus))) = res { diff --git a/src/shell/grabs/moving.rs b/src/shell/grabs/moving.rs index 4cfc9680e..e798ecefd 100644 --- a/src/shell/grabs/moving.rs +++ b/src/shell/grabs/moving.rs @@ -342,6 +342,7 @@ pub struct MoveGrab { window_outputs: HashSet, previous: ManagedLayer, release: ReleaseMode, + window_snap_threshold: f64, // SAFETY: This is only used on drop which will always be on the main thread evlh: NotSend>, } @@ -383,6 +384,40 @@ impl MoveGrab { let mut window_geo = self.window.geometry(); window_geo.loc += location.to_i32_round() + grab_state.window_offset; + + if matches!(self.previous, ManagedLayer::Floating | ManagedLayer::Sticky) { + let loc = (grab_state.window_offset.to_f64() + grab_state.location).as_local(); + let size = window_geo.size.to_f64().as_local(); + let output_geom = self + .cursor_output + .geometry() + .to_f64() + .to_local(&self.cursor_output); + let output_loc = output_geom.loc; + let output_size = output_geom.size; + + grab_state.location.x = if (loc.x - output_loc.x).abs() < self.window_snap_threshold + { + output_loc.x - grab_state.window_offset.x as f64 + } else if ((loc.x + size.w) - (output_loc.x + output_size.w)).abs() + < self.window_snap_threshold + { + output_loc.x + output_size.w - grab_state.window_offset.x as f64 - size.w + } else { + grab_state.location.x + }; + grab_state.location.y = if (loc.y - output_loc.y).abs() < self.window_snap_threshold + { + output_loc.y - grab_state.window_offset.y as f64 + } else if ((loc.y + size.h) - (output_loc.y + output_size.h)).abs() + < self.window_snap_threshold + { + output_loc.y + output_size.h - grab_state.window_offset.y as f64 - size.h + } else { + grab_state.location.y + }; + } + for output in shell.outputs() { if let Some(overlap) = output.geometry().as_logical().intersection(window_geo) { if self.window_outputs.insert(output.clone()) { @@ -681,6 +716,7 @@ impl MoveGrab { initial_window_location: Point, cursor_output: Output, indicator_thickness: u8, + window_snap_threshold: f64, previous_layer: ManagedLayer, release: ReleaseMode, evlh: LoopHandle<'static, State>, @@ -720,10 +756,11 @@ impl MoveGrab { window, start_data, seat: seat.clone(), - window_outputs: outputs, cursor_output, + window_outputs: outputs, previous: previous_layer, release, + window_snap_threshold, evlh: NotSend(evlh), } } diff --git a/src/shell/layout/floating/grabs/resize.rs b/src/shell/layout/floating/grabs/resize.rs index 9e2a0dd98..52d61880a 100644 --- a/src/shell/layout/floating/grabs/resize.rs +++ b/src/shell/layout/floating/grabs/resize.rs @@ -58,6 +58,8 @@ pub struct ResizeSurfaceGrab { window: CosmicMapped, edges: ResizeEdge, output: Output, + edge_snap_threshold: u32, + initial_window_location: Point, initial_window_size: Size, last_window_size: Size, release: ReleaseMode, @@ -91,6 +93,27 @@ impl ResizeSurfaceGrab { } new_window_width = (self.initial_window_size.w as f64 + dx) as i32; + + // If the resizing vertical edge is close to our output's edge in the same direction, snap to it. + let output_geom = self.output.geometry().to_local(&self.output); + if self.edges.intersects(ResizeEdge::LEFT) { + if ((self.initial_window_location.x - dx as i32 - output_geom.loc.x).abs() as u32) + < self.edge_snap_threshold + { + new_window_width = self.initial_window_size.w - output_geom.loc.x + + self.initial_window_location.x; + } + } else { + if ((self.initial_window_location.x + self.initial_window_size.w + dx as i32 + - output_geom.loc.x + - output_geom.size.w) + .abs() as u32) + < self.edge_snap_threshold + { + new_window_width = + output_geom.loc.x - self.initial_window_location.x + output_geom.size.w; + } + } } if self.edges.intersects(top_bottom) { @@ -99,6 +122,27 @@ impl ResizeSurfaceGrab { } new_window_height = (self.initial_window_size.h as f64 + dy) as i32; + + // If the resizing horizontal edge is close to our output's edge in the same direction, snap to it. + let output_geom = self.output.geometry().to_local(&self.output); + if self.edges.intersects(ResizeEdge::TOP) { + if ((self.initial_window_location.y - dy as i32 - output_geom.loc.y).abs() as u32) + < self.edge_snap_threshold + { + new_window_height = self.initial_window_size.h - output_geom.loc.y + + self.initial_window_location.y; + } + } else { + if ((self.initial_window_location.y + self.initial_window_size.h + dy as i32 + - output_geom.loc.y + - output_geom.size.h) + .abs() as u32) + < self.edge_snap_threshold + { + new_window_height = + output_geom.loc.y - self.initial_window_location.y + output_geom.size.h; + } + } } let (min_size, max_size) = (self.window.min_size(), self.window.max_size()); @@ -375,6 +419,7 @@ impl ResizeSurfaceGrab { mapped: CosmicMapped, edges: ResizeEdge, output: Output, + edge_snap_threshold: u32, initial_window_location: Point, initial_window_size: Size, seat: &Seat, @@ -414,9 +459,11 @@ impl ResizeSurfaceGrab { window: mapped, edges, output, + initial_window_location, initial_window_size, last_window_size: initial_window_size, release, + edge_snap_threshold, } } diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index daa65739f..e86ed5450 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -888,6 +888,7 @@ impl FloatingLayout { seat: &Seat, start_data: GrabStartData, edges: ResizeEdge, + edge_snap_threshold: u32, release: ReleaseMode, ) -> Option { if seat.get_pointer().is_some() { @@ -900,6 +901,7 @@ impl FloatingLayout { mapped.clone(), edges, self.space.outputs().next().cloned().unwrap(), + edge_snap_threshold, location, size, seat, diff --git a/src/shell/mod.rs b/src/shell/mod.rs index 825ef3517..e5a2c68fd 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -2896,6 +2896,7 @@ impl Shell { initial_window_location, cursor_output, active_hint, + config.cosmic_conf.edge_snap_threshold as f64, layer, release, evlh.clone(), @@ -3150,6 +3151,7 @@ impl Shell { mapped: &CosmicMapped, seat: &Seat, edge: ResizeEdge, + edge_snap_threshold: u32, ) -> Option<( ( Option<(PointerFocusTarget, Point)>, @@ -3217,6 +3219,7 @@ impl Shell { seat, start_data.clone(), edge, + edge_snap_threshold, ReleaseMode::Click, ) { grab.into() @@ -3392,6 +3395,7 @@ impl Shell { seat: &Seat, serial: impl Into>, edges: ResizeEdge, + edge_snap_threshold: u32, client_initiated: bool, ) -> Option<(ResizeGrab, Focus)> { let serial = serial.into(); @@ -3419,6 +3423,7 @@ impl Shell { seat, start_data.clone(), edges, + edge_snap_threshold, ReleaseMode::NoMouseButtons, ) { grab.into() diff --git a/src/wayland/handlers/xdg_shell/mod.rs b/src/wayland/handlers/xdg_shell/mod.rs index 3e5dbba2b..c8695e22d 100644 --- a/src/wayland/handlers/xdg_shell/mod.rs +++ b/src/wayland/handlers/xdg_shell/mod.rs @@ -197,9 +197,14 @@ impl XdgShellHandler for State { ) { let seat = Seat::from_resource(&seat).unwrap(); let mut shell = self.common.shell.write().unwrap(); - if let Some((grab, focus)) = - shell.resize_request(surface.wl_surface(), &seat, serial, edges.into(), true) - { + if let Some((grab, focus)) = shell.resize_request( + surface.wl_surface(), + &seat, + serial, + edges.into(), + self.common.config.cosmic_conf.edge_snap_threshold, + true, + ) { std::mem::drop(shell); if grab.is_touch_grab() { seat.get_touch().unwrap().set_grab(self, grab, serial) diff --git a/src/xwayland.rs b/src/xwayland.rs index b74668d11..fd146aae2 100644 --- a/src/xwayland.rs +++ b/src/xwayland.rs @@ -556,9 +556,14 @@ impl XwmHandler for State { if let Some(wl_surface) = window.wl_surface() { let mut shell = self.common.shell.write().unwrap(); let seat = shell.seats.last_active().clone(); - if let Some((grab, focus)) = - shell.resize_request(&wl_surface, &seat, None, resize_edge.into(), true) - { + if let Some((grab, focus)) = shell.resize_request( + &wl_surface, + &seat, + None, + resize_edge.into(), + self.common.config.cosmic_conf.edge_snap_threshold, + true, + ) { std::mem::drop(shell); if grab.is_touch_grab() { seat.get_touch()