Skip to content

Commit 2553810

Browse files
authored
grabs: Snap Window Edges to Close Output Edges
1 parent 2678cf4 commit 2553810

File tree

12 files changed

+148
-13
lines changed

12 files changed

+148
-13
lines changed

cosmic-comp-config/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ pub struct CosmicCompConfig {
4646
pub focus_follows_cursor_delay: u64,
4747
/// Let X11 applications scale themselves
4848
pub descale_xwayland: bool,
49+
/// The threshold before windows snap themselves to output edges
50+
pub edge_snap_threshold: u32,
4951
}
5052

5153
impl Default for CosmicCompConfig {
@@ -76,6 +78,7 @@ impl Default for CosmicCompConfig {
7678
cursor_follows_focus: false,
7779
focus_follows_cursor_delay: 250,
7880
descale_xwayland: false,
81+
edge_snap_threshold: 0,
7982
}
8083
}
8184
}

src/config/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,12 @@ fn config_changed(config: cosmic_config::Config, keys: Vec<String>, state: &mut
846846
state.common.config.cosmic_conf.focus_follows_cursor_delay = new;
847847
}
848848
}
849+
"edge_snap_threshold" => {
850+
let new = get_config::<u32>(&config, "edge_snap_threshold");
851+
if new != state.common.config.cosmic_conf.edge_snap_threshold {
852+
state.common.config.cosmic_conf.edge_snap_threshold = new;
853+
}
854+
}
849855
_ => {}
850856
}
851857
}

src/input/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -760,6 +760,11 @@ impl State {
760760
&seat_clone,
761761
serial,
762762
edge,
763+
state
764+
.common
765+
.config
766+
.cosmic_conf
767+
.edge_snap_threshold,
763768
false,
764769
);
765770
drop(shell);

src/shell/element/stack.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1369,6 +1369,7 @@ impl PointerTarget<State> for CosmicStack {
13691369
Focus::ResizeRight => ResizeEdge::RIGHT,
13701370
Focus::Header => unreachable!(),
13711371
},
1372+
state.common.config.cosmic_conf.edge_snap_threshold,
13721373
false,
13731374
);
13741375
if let Some((grab, focus)) = res {

src/shell/element/window.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,7 @@ impl PointerTarget<State> for CosmicWindow {
759759
Focus::ResizeRight => ResizeEdge::RIGHT,
760760
Focus::Header => unreachable!(),
761761
},
762+
state.common.config.cosmic_conf.edge_snap_threshold,
762763
false,
763764
);
764765

src/shell/grabs/menu/default.rs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,12 @@ pub fn window_items(
288288
let _ = handle.insert_idle(move |state| {
289289
let mut shell = state.common.shell.write().unwrap();
290290
let seat = shell.seats.last_active().clone();
291-
let res = shell.menu_resize_request(&resize_clone, &seat, ResizeEdge::TOP);
291+
let res = shell.menu_resize_request(
292+
&resize_clone,
293+
&seat,
294+
ResizeEdge::TOP,
295+
state.common.config.cosmic_conf.edge_snap_threshold,
296+
);
292297

293298
std::mem::drop(shell);
294299
if let Some(((target, loc), (grab, focus))) = res {
@@ -318,7 +323,12 @@ pub fn window_items(
318323
let _ = handle.insert_idle(move |state| {
319324
let mut shell = state.common.shell.write().unwrap();
320325
let seat = shell.seats.last_active().clone();
321-
let res = shell.menu_resize_request(&resize_clone, &seat, ResizeEdge::LEFT);
326+
let res = shell.menu_resize_request(
327+
&resize_clone,
328+
&seat,
329+
ResizeEdge::LEFT,
330+
state.common.config.cosmic_conf.edge_snap_threshold,
331+
);
322332

323333
std::mem::drop(shell);
324334
if let Some(((target, loc), (grab, focus))) = res {
@@ -348,8 +358,12 @@ pub fn window_items(
348358
let _ = handle.insert_idle(move |state| {
349359
let mut shell = state.common.shell.write().unwrap();
350360
let seat = shell.seats.last_active().clone();
351-
let res =
352-
shell.menu_resize_request(&resize_clone, &seat, ResizeEdge::RIGHT);
361+
let res = shell.menu_resize_request(
362+
&resize_clone,
363+
&seat,
364+
ResizeEdge::RIGHT,
365+
state.common.config.cosmic_conf.edge_snap_threshold,
366+
);
353367

354368
std::mem::drop(shell);
355369
if let Some(((target, loc), (grab, focus))) = res {
@@ -379,8 +393,12 @@ pub fn window_items(
379393
let _ = handle.insert_idle(move |state| {
380394
let mut shell = state.common.shell.write().unwrap();
381395
let seat = shell.seats.last_active().clone();
382-
let res =
383-
shell.menu_resize_request(&resize_clone, &seat, ResizeEdge::BOTTOM);
396+
let res = shell.menu_resize_request(
397+
&resize_clone,
398+
&seat,
399+
ResizeEdge::BOTTOM,
400+
state.common.config.cosmic_conf.edge_snap_threshold,
401+
);
384402

385403
std::mem::drop(shell);
386404
if let Some(((target, loc), (grab, focus))) = res {

src/shell/grabs/moving.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ pub struct MoveGrab {
342342
window_outputs: HashSet<Output>,
343343
previous: ManagedLayer,
344344
release: ReleaseMode,
345+
window_snap_threshold: f64,
345346
// SAFETY: This is only used on drop which will always be on the main thread
346347
evlh: NotSend<LoopHandle<'static, State>>,
347348
}
@@ -383,6 +384,40 @@ impl MoveGrab {
383384

384385
let mut window_geo = self.window.geometry();
385386
window_geo.loc += location.to_i32_round() + grab_state.window_offset;
387+
388+
if matches!(self.previous, ManagedLayer::Floating | ManagedLayer::Sticky) {
389+
let loc = (grab_state.window_offset.to_f64() + grab_state.location).as_local();
390+
let size = window_geo.size.to_f64().as_local();
391+
let output_geom = self
392+
.cursor_output
393+
.geometry()
394+
.to_f64()
395+
.to_local(&self.cursor_output);
396+
let output_loc = output_geom.loc;
397+
let output_size = output_geom.size;
398+
399+
grab_state.location.x = if (loc.x - output_loc.x).abs() < self.window_snap_threshold
400+
{
401+
output_loc.x - grab_state.window_offset.x as f64
402+
} else if ((loc.x + size.w) - (output_loc.x + output_size.w)).abs()
403+
< self.window_snap_threshold
404+
{
405+
output_loc.x + output_size.w - grab_state.window_offset.x as f64 - size.w
406+
} else {
407+
grab_state.location.x
408+
};
409+
grab_state.location.y = if (loc.y - output_loc.y).abs() < self.window_snap_threshold
410+
{
411+
output_loc.y - grab_state.window_offset.y as f64
412+
} else if ((loc.y + size.h) - (output_loc.y + output_size.h)).abs()
413+
< self.window_snap_threshold
414+
{
415+
output_loc.y + output_size.h - grab_state.window_offset.y as f64 - size.h
416+
} else {
417+
grab_state.location.y
418+
};
419+
}
420+
386421
for output in shell.outputs() {
387422
if let Some(overlap) = output.geometry().as_logical().intersection(window_geo) {
388423
if self.window_outputs.insert(output.clone()) {
@@ -681,6 +716,7 @@ impl MoveGrab {
681716
initial_window_location: Point<i32, Global>,
682717
cursor_output: Output,
683718
indicator_thickness: u8,
719+
window_snap_threshold: f64,
684720
previous_layer: ManagedLayer,
685721
release: ReleaseMode,
686722
evlh: LoopHandle<'static, State>,
@@ -720,10 +756,11 @@ impl MoveGrab {
720756
window,
721757
start_data,
722758
seat: seat.clone(),
723-
window_outputs: outputs,
724759
cursor_output,
760+
window_outputs: outputs,
725761
previous: previous_layer,
726762
release,
763+
window_snap_threshold,
727764
evlh: NotSend(evlh),
728765
}
729766
}

src/shell/layout/floating/grabs/resize.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ pub struct ResizeSurfaceGrab {
5858
window: CosmicMapped,
5959
edges: ResizeEdge,
6060
output: Output,
61+
edge_snap_threshold: u32,
62+
initial_window_location: Point<i32, Local>,
6163
initial_window_size: Size<i32, Logical>,
6264
last_window_size: Size<i32, Logical>,
6365
release: ReleaseMode,
@@ -91,6 +93,27 @@ impl ResizeSurfaceGrab {
9193
}
9294

9395
new_window_width = (self.initial_window_size.w as f64 + dx) as i32;
96+
97+
// If the resizing vertical edge is close to our output's edge in the same direction, snap to it.
98+
let output_geom = self.output.geometry().to_local(&self.output);
99+
if self.edges.intersects(ResizeEdge::LEFT) {
100+
if ((self.initial_window_location.x - dx as i32 - output_geom.loc.x).abs() as u32)
101+
< self.edge_snap_threshold
102+
{
103+
new_window_width = self.initial_window_size.w - output_geom.loc.x
104+
+ self.initial_window_location.x;
105+
}
106+
} else {
107+
if ((self.initial_window_location.x + self.initial_window_size.w + dx as i32
108+
- output_geom.loc.x
109+
- output_geom.size.w)
110+
.abs() as u32)
111+
< self.edge_snap_threshold
112+
{
113+
new_window_width =
114+
output_geom.loc.x - self.initial_window_location.x + output_geom.size.w;
115+
}
116+
}
94117
}
95118

96119
if self.edges.intersects(top_bottom) {
@@ -99,6 +122,27 @@ impl ResizeSurfaceGrab {
99122
}
100123

101124
new_window_height = (self.initial_window_size.h as f64 + dy) as i32;
125+
126+
// If the resizing horizontal edge is close to our output's edge in the same direction, snap to it.
127+
let output_geom = self.output.geometry().to_local(&self.output);
128+
if self.edges.intersects(ResizeEdge::TOP) {
129+
if ((self.initial_window_location.y - dy as i32 - output_geom.loc.y).abs() as u32)
130+
< self.edge_snap_threshold
131+
{
132+
new_window_height = self.initial_window_size.h - output_geom.loc.y
133+
+ self.initial_window_location.y;
134+
}
135+
} else {
136+
if ((self.initial_window_location.y + self.initial_window_size.h + dy as i32
137+
- output_geom.loc.y
138+
- output_geom.size.h)
139+
.abs() as u32)
140+
< self.edge_snap_threshold
141+
{
142+
new_window_height =
143+
output_geom.loc.y - self.initial_window_location.y + output_geom.size.h;
144+
}
145+
}
102146
}
103147

104148
let (min_size, max_size) = (self.window.min_size(), self.window.max_size());
@@ -375,6 +419,7 @@ impl ResizeSurfaceGrab {
375419
mapped: CosmicMapped,
376420
edges: ResizeEdge,
377421
output: Output,
422+
edge_snap_threshold: u32,
378423
initial_window_location: Point<i32, Local>,
379424
initial_window_size: Size<i32, Logical>,
380425
seat: &Seat<State>,
@@ -414,9 +459,11 @@ impl ResizeSurfaceGrab {
414459
window: mapped,
415460
edges,
416461
output,
462+
initial_window_location,
417463
initial_window_size,
418464
last_window_size: initial_window_size,
419465
release,
466+
edge_snap_threshold,
420467
}
421468
}
422469

src/shell/layout/floating/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -888,6 +888,7 @@ impl FloatingLayout {
888888
seat: &Seat<State>,
889889
start_data: GrabStartData,
890890
edges: ResizeEdge,
891+
edge_snap_threshold: u32,
891892
release: ReleaseMode,
892893
) -> Option<ResizeSurfaceGrab> {
893894
if seat.get_pointer().is_some() {
@@ -900,6 +901,7 @@ impl FloatingLayout {
900901
mapped.clone(),
901902
edges,
902903
self.space.outputs().next().cloned().unwrap(),
904+
edge_snap_threshold,
903905
location,
904906
size,
905907
seat,

src/shell/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2896,6 +2896,7 @@ impl Shell {
28962896
initial_window_location,
28972897
cursor_output,
28982898
active_hint,
2899+
config.cosmic_conf.edge_snap_threshold as f64,
28992900
layer,
29002901
release,
29012902
evlh.clone(),
@@ -3150,6 +3151,7 @@ impl Shell {
31503151
mapped: &CosmicMapped,
31513152
seat: &Seat<State>,
31523153
edge: ResizeEdge,
3154+
edge_snap_threshold: u32,
31533155
) -> Option<(
31543156
(
31553157
Option<(PointerFocusTarget, Point<f64, Logical>)>,
@@ -3217,6 +3219,7 @@ impl Shell {
32173219
seat,
32183220
start_data.clone(),
32193221
edge,
3222+
edge_snap_threshold,
32203223
ReleaseMode::Click,
32213224
) {
32223225
grab.into()
@@ -3392,6 +3395,7 @@ impl Shell {
33923395
seat: &Seat<State>,
33933396
serial: impl Into<Option<Serial>>,
33943397
edges: ResizeEdge,
3398+
edge_snap_threshold: u32,
33953399
client_initiated: bool,
33963400
) -> Option<(ResizeGrab, Focus)> {
33973401
let serial = serial.into();
@@ -3419,6 +3423,7 @@ impl Shell {
34193423
seat,
34203424
start_data.clone(),
34213425
edges,
3426+
edge_snap_threshold,
34223427
ReleaseMode::NoMouseButtons,
34233428
) {
34243429
grab.into()

0 commit comments

Comments
 (0)