grabs: Snap Window Edges to Close Output Edges

This commit is contained in:
Daniel 2025-02-14 21:58:09 +11:00 committed by GitHub
parent 2678cf41b2
commit 2553810621
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 148 additions and 13 deletions

View file

@ -46,6 +46,8 @@ pub struct CosmicCompConfig {
pub focus_follows_cursor_delay: u64, pub focus_follows_cursor_delay: u64,
/// Let X11 applications scale themselves /// Let X11 applications scale themselves
pub descale_xwayland: bool, pub descale_xwayland: bool,
/// The threshold before windows snap themselves to output edges
pub edge_snap_threshold: u32,
} }
impl Default for CosmicCompConfig { impl Default for CosmicCompConfig {
@ -76,6 +78,7 @@ impl Default for CosmicCompConfig {
cursor_follows_focus: false, cursor_follows_focus: false,
focus_follows_cursor_delay: 250, focus_follows_cursor_delay: 250,
descale_xwayland: false, descale_xwayland: false,
edge_snap_threshold: 0,
} }
} }
} }

View file

@ -846,6 +846,12 @@ fn config_changed(config: cosmic_config::Config, keys: Vec<String>, state: &mut
state.common.config.cosmic_conf.focus_follows_cursor_delay = new; state.common.config.cosmic_conf.focus_follows_cursor_delay = new;
} }
} }
"edge_snap_threshold" => {
let new = get_config::<u32>(&config, "edge_snap_threshold");
if new != state.common.config.cosmic_conf.edge_snap_threshold {
state.common.config.cosmic_conf.edge_snap_threshold = new;
}
}
_ => {} _ => {}
} }
} }

View file

@ -760,6 +760,11 @@ impl State {
&seat_clone, &seat_clone,
serial, serial,
edge, edge,
state
.common
.config
.cosmic_conf
.edge_snap_threshold,
false, false,
); );
drop(shell); drop(shell);

View file

@ -1369,6 +1369,7 @@ impl PointerTarget<State> for CosmicStack {
Focus::ResizeRight => ResizeEdge::RIGHT, Focus::ResizeRight => ResizeEdge::RIGHT,
Focus::Header => unreachable!(), Focus::Header => unreachable!(),
}, },
state.common.config.cosmic_conf.edge_snap_threshold,
false, false,
); );
if let Some((grab, focus)) = res { if let Some((grab, focus)) = res {

View file

@ -759,6 +759,7 @@ impl PointerTarget<State> for CosmicWindow {
Focus::ResizeRight => ResizeEdge::RIGHT, Focus::ResizeRight => ResizeEdge::RIGHT,
Focus::Header => unreachable!(), Focus::Header => unreachable!(),
}, },
state.common.config.cosmic_conf.edge_snap_threshold,
false, false,
); );

View file

@ -288,7 +288,12 @@ pub fn window_items(
let _ = handle.insert_idle(move |state| { let _ = handle.insert_idle(move |state| {
let mut shell = state.common.shell.write().unwrap(); let mut shell = state.common.shell.write().unwrap();
let seat = shell.seats.last_active().clone(); 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); std::mem::drop(shell);
if let Some(((target, loc), (grab, focus))) = res { if let Some(((target, loc), (grab, focus))) = res {
@ -318,7 +323,12 @@ pub fn window_items(
let _ = handle.insert_idle(move |state| { let _ = handle.insert_idle(move |state| {
let mut shell = state.common.shell.write().unwrap(); let mut shell = state.common.shell.write().unwrap();
let seat = shell.seats.last_active().clone(); 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); std::mem::drop(shell);
if let Some(((target, loc), (grab, focus))) = res { if let Some(((target, loc), (grab, focus))) = res {
@ -348,8 +358,12 @@ pub fn window_items(
let _ = handle.insert_idle(move |state| { let _ = handle.insert_idle(move |state| {
let mut shell = state.common.shell.write().unwrap(); let mut shell = state.common.shell.write().unwrap();
let seat = shell.seats.last_active().clone(); let seat = shell.seats.last_active().clone();
let res = let res = shell.menu_resize_request(
shell.menu_resize_request(&resize_clone, &seat, ResizeEdge::RIGHT); &resize_clone,
&seat,
ResizeEdge::RIGHT,
state.common.config.cosmic_conf.edge_snap_threshold,
);
std::mem::drop(shell); std::mem::drop(shell);
if let Some(((target, loc), (grab, focus))) = res { if let Some(((target, loc), (grab, focus))) = res {
@ -379,8 +393,12 @@ pub fn window_items(
let _ = handle.insert_idle(move |state| { let _ = handle.insert_idle(move |state| {
let mut shell = state.common.shell.write().unwrap(); let mut shell = state.common.shell.write().unwrap();
let seat = shell.seats.last_active().clone(); let seat = shell.seats.last_active().clone();
let res = let res = shell.menu_resize_request(
shell.menu_resize_request(&resize_clone, &seat, ResizeEdge::BOTTOM); &resize_clone,
&seat,
ResizeEdge::BOTTOM,
state.common.config.cosmic_conf.edge_snap_threshold,
);
std::mem::drop(shell); std::mem::drop(shell);
if let Some(((target, loc), (grab, focus))) = res { if let Some(((target, loc), (grab, focus))) = res {

View file

@ -342,6 +342,7 @@ pub struct MoveGrab {
window_outputs: HashSet<Output>, window_outputs: HashSet<Output>,
previous: ManagedLayer, previous: ManagedLayer,
release: ReleaseMode, release: ReleaseMode,
window_snap_threshold: f64,
// SAFETY: This is only used on drop which will always be on the main thread // SAFETY: This is only used on drop which will always be on the main thread
evlh: NotSend<LoopHandle<'static, State>>, evlh: NotSend<LoopHandle<'static, State>>,
} }
@ -383,6 +384,40 @@ impl MoveGrab {
let mut window_geo = self.window.geometry(); let mut window_geo = self.window.geometry();
window_geo.loc += location.to_i32_round() + grab_state.window_offset; 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() { for output in shell.outputs() {
if let Some(overlap) = output.geometry().as_logical().intersection(window_geo) { if let Some(overlap) = output.geometry().as_logical().intersection(window_geo) {
if self.window_outputs.insert(output.clone()) { if self.window_outputs.insert(output.clone()) {
@ -681,6 +716,7 @@ impl MoveGrab {
initial_window_location: Point<i32, Global>, initial_window_location: Point<i32, Global>,
cursor_output: Output, cursor_output: Output,
indicator_thickness: u8, indicator_thickness: u8,
window_snap_threshold: f64,
previous_layer: ManagedLayer, previous_layer: ManagedLayer,
release: ReleaseMode, release: ReleaseMode,
evlh: LoopHandle<'static, State>, evlh: LoopHandle<'static, State>,
@ -720,10 +756,11 @@ impl MoveGrab {
window, window,
start_data, start_data,
seat: seat.clone(), seat: seat.clone(),
window_outputs: outputs,
cursor_output, cursor_output,
window_outputs: outputs,
previous: previous_layer, previous: previous_layer,
release, release,
window_snap_threshold,
evlh: NotSend(evlh), evlh: NotSend(evlh),
} }
} }

View file

@ -58,6 +58,8 @@ pub struct ResizeSurfaceGrab {
window: CosmicMapped, window: CosmicMapped,
edges: ResizeEdge, edges: ResizeEdge,
output: Output, output: Output,
edge_snap_threshold: u32,
initial_window_location: Point<i32, Local>,
initial_window_size: Size<i32, Logical>, initial_window_size: Size<i32, Logical>,
last_window_size: Size<i32, Logical>, last_window_size: Size<i32, Logical>,
release: ReleaseMode, release: ReleaseMode,
@ -91,6 +93,27 @@ impl ResizeSurfaceGrab {
} }
new_window_width = (self.initial_window_size.w as f64 + dx) as i32; 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) { 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; 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()); let (min_size, max_size) = (self.window.min_size(), self.window.max_size());
@ -375,6 +419,7 @@ impl ResizeSurfaceGrab {
mapped: CosmicMapped, mapped: CosmicMapped,
edges: ResizeEdge, edges: ResizeEdge,
output: Output, output: Output,
edge_snap_threshold: u32,
initial_window_location: Point<i32, Local>, initial_window_location: Point<i32, Local>,
initial_window_size: Size<i32, Logical>, initial_window_size: Size<i32, Logical>,
seat: &Seat<State>, seat: &Seat<State>,
@ -414,9 +459,11 @@ impl ResizeSurfaceGrab {
window: mapped, window: mapped,
edges, edges,
output, output,
initial_window_location,
initial_window_size, initial_window_size,
last_window_size: initial_window_size, last_window_size: initial_window_size,
release, release,
edge_snap_threshold,
} }
} }

View file

@ -888,6 +888,7 @@ impl FloatingLayout {
seat: &Seat<State>, seat: &Seat<State>,
start_data: GrabStartData, start_data: GrabStartData,
edges: ResizeEdge, edges: ResizeEdge,
edge_snap_threshold: u32,
release: ReleaseMode, release: ReleaseMode,
) -> Option<ResizeSurfaceGrab> { ) -> Option<ResizeSurfaceGrab> {
if seat.get_pointer().is_some() { if seat.get_pointer().is_some() {
@ -900,6 +901,7 @@ impl FloatingLayout {
mapped.clone(), mapped.clone(),
edges, edges,
self.space.outputs().next().cloned().unwrap(), self.space.outputs().next().cloned().unwrap(),
edge_snap_threshold,
location, location,
size, size,
seat, seat,

View file

@ -2896,6 +2896,7 @@ impl Shell {
initial_window_location, initial_window_location,
cursor_output, cursor_output,
active_hint, active_hint,
config.cosmic_conf.edge_snap_threshold as f64,
layer, layer,
release, release,
evlh.clone(), evlh.clone(),
@ -3150,6 +3151,7 @@ impl Shell {
mapped: &CosmicMapped, mapped: &CosmicMapped,
seat: &Seat<State>, seat: &Seat<State>,
edge: ResizeEdge, edge: ResizeEdge,
edge_snap_threshold: u32,
) -> Option<( ) -> Option<(
( (
Option<(PointerFocusTarget, Point<f64, Logical>)>, Option<(PointerFocusTarget, Point<f64, Logical>)>,
@ -3217,6 +3219,7 @@ impl Shell {
seat, seat,
start_data.clone(), start_data.clone(),
edge, edge,
edge_snap_threshold,
ReleaseMode::Click, ReleaseMode::Click,
) { ) {
grab.into() grab.into()
@ -3392,6 +3395,7 @@ impl Shell {
seat: &Seat<State>, seat: &Seat<State>,
serial: impl Into<Option<Serial>>, serial: impl Into<Option<Serial>>,
edges: ResizeEdge, edges: ResizeEdge,
edge_snap_threshold: u32,
client_initiated: bool, client_initiated: bool,
) -> Option<(ResizeGrab, Focus)> { ) -> Option<(ResizeGrab, Focus)> {
let serial = serial.into(); let serial = serial.into();
@ -3419,6 +3423,7 @@ impl Shell {
seat, seat,
start_data.clone(), start_data.clone(),
edges, edges,
edge_snap_threshold,
ReleaseMode::NoMouseButtons, ReleaseMode::NoMouseButtons,
) { ) {
grab.into() grab.into()

View file

@ -197,9 +197,14 @@ impl XdgShellHandler for State {
) { ) {
let seat = Seat::from_resource(&seat).unwrap(); let seat = Seat::from_resource(&seat).unwrap();
let mut shell = self.common.shell.write().unwrap(); let mut shell = self.common.shell.write().unwrap();
if let Some((grab, focus)) = if let Some((grab, focus)) = shell.resize_request(
shell.resize_request(surface.wl_surface(), &seat, serial, edges.into(), true) surface.wl_surface(),
{ &seat,
serial,
edges.into(),
self.common.config.cosmic_conf.edge_snap_threshold,
true,
) {
std::mem::drop(shell); std::mem::drop(shell);
if grab.is_touch_grab() { if grab.is_touch_grab() {
seat.get_touch().unwrap().set_grab(self, grab, serial) seat.get_touch().unwrap().set_grab(self, grab, serial)

View file

@ -556,9 +556,14 @@ impl XwmHandler for State {
if let Some(wl_surface) = window.wl_surface() { if let Some(wl_surface) = window.wl_surface() {
let mut shell = self.common.shell.write().unwrap(); let mut shell = self.common.shell.write().unwrap();
let seat = shell.seats.last_active().clone(); let seat = shell.seats.last_active().clone();
if let Some((grab, focus)) = if let Some((grab, focus)) = shell.resize_request(
shell.resize_request(&wl_surface, &seat, None, resize_edge.into(), true) &wl_surface,
{ &seat,
None,
resize_edge.into(),
self.common.config.cosmic_conf.edge_snap_threshold,
true,
) {
std::mem::drop(shell); std::mem::drop(shell);
if grab.is_touch_grab() { if grab.is_touch_grab() {
seat.get_touch() seat.get_touch()