From 70759a188f3b3d033b782a91c425906fa0c185a8 Mon Sep 17 00:00:00 2001 From: Joshua Austill Date: Tue, 20 Jan 2026 15:28:19 -0700 Subject: [PATCH 1/2] Handle TileWindow action for keyboard window snapping Implement handler for the TileWindow action that snaps floating windows to screen edges and corners (8 zones: Top, TopRight, Right, BottomRight, Bottom, BottomLeft, Left, TopLeft). - Add TileWindow action handler in input/actions.rs - Add Shell::tile_window() method in shell/mod.rs - Add FloatingLayout::tile_window() method in shell/layout/floating/mod.rs The implementation reuses the existing TiledCorners enum and animation system for smooth transitions. Depends on: pop-os/cosmic-settings-daemon#124 Co-Authored-By: Claude Opus 4.5 --- src/input/actions.rs | 19 ++++++++++++ src/shell/layout/floating/mod.rs | 50 ++++++++++++++++++++++++++++++++ src/shell/mod.rs | 20 +++++++++++++ 3 files changed, 89 insertions(+) diff --git a/src/input/actions.rs b/src/input/actions.rs index 4cecbe28a..45fcc2c75 100644 --- a/src/input/actions.rs +++ b/src/input/actions.rs @@ -893,6 +893,25 @@ impl State { } } + Action::TileWindow(zone) => { + use cosmic_settings_config::shortcuts::action::TilingZone; + use crate::shell::layout::floating::TiledCorners; + + let corner = match zone { + TilingZone::Top => TiledCorners::Top, + TilingZone::TopRight => TiledCorners::TopRight, + TilingZone::Right => TiledCorners::Right, + TilingZone::BottomRight => TiledCorners::BottomRight, + TilingZone::Bottom => TiledCorners::Bottom, + TilingZone::BottomLeft => TiledCorners::BottomLeft, + TilingZone::Left => TiledCorners::Left, + TilingZone::TopLeft => TiledCorners::TopLeft, + }; + + let mut shell = self.common.shell.write(); + shell.tile_window(corner, seat); + } + Action::Fullscreen => { let Some(focused_output) = seat.focused_output() else { return; diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index 1f3a7334e..2bc093f6c 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -1099,6 +1099,56 @@ impl FloatingLayout { self.toggle_stacking(&elem, focus_stack) } + pub fn tile_window( + &mut self, + window: &CosmicMapped, + corner: TiledCorners, + theme: &cosmic::Theme, + ) { + // Store previous geometry for animation + if let Some(geo) = self.space.element_geometry(window) { + let previous_geo = geo.as_local(); + + // Set the tiled corner state + *window.floating_tiled.lock().unwrap() = Some(corner); + + // Clear any maximized state + *window.maximized_state.lock().unwrap() = None; + + // Get output geometry for calculating new position + let output = self.space.outputs().next().unwrap().clone(); + let layers = layer_map_for_output(&output); + let output_geometry = layers.non_exclusive_zone(); + + // Calculate new geometry based on corner + let gaps = (theme.cosmic().gaps.0 as i32, theme.cosmic().gaps.1 as i32); + let new_geo = corner.relative_geometry(output_geometry, gaps); + + // Set window properties for tiled state + window.set_tiled(true); + window.set_maximized(false); + window.set_geometry(new_geo.to_global(&output)); + window.configure(); + + window.moved_since_mapped.store(true, Ordering::SeqCst); + + // Trigger animation + self.animations.insert( + window.clone(), + Animation::Tiled { + start: Instant::now(), + previous_geometry: previous_geo, + }, + ); + self.dirty.store(true, Ordering::SeqCst); + + // Update space mapping + self.space + .map_element(window.clone(), new_geo.loc.as_logical(), true); + self.space.refresh(); + } + } + pub fn move_element( &mut self, direction: Direction, diff --git a/src/shell/mod.rs b/src/shell/mod.rs index e28098619..a3814cc73 100644 --- a/src/shell/mod.rs +++ b/src/shell/mod.rs @@ -4124,6 +4124,26 @@ impl Shell { } } + pub fn tile_window(&mut self, corner: layout::floating::TiledCorners, seat: &Seat) { + let output = seat.active_output(); + let Some(workspace) = self.workspaces.active_mut(&output) else { + return; + }; + + let Some(KeyboardFocusTarget::Element(window)) = + seat.get_keyboard().and_then(|k| k.current_focus()) + else { + return; + }; + + // Only works on floating windows + if workspace.floating_layer.mapped().any(|w| w == &window) { + workspace + .floating_layer + .tile_window(&window, corner, &self.theme); + } + } + pub fn minimize_request(&mut self, surface: &S) where CosmicSurface: PartialEq, From 83d38ad25674e1289fe78de53aba8121802990c3 Mon Sep 17 00:00:00 2001 From: Leon Breukelman Date: Tue, 24 Feb 2026 10:54:27 -0600 Subject: [PATCH 2/2] Add Center zone support for keyboard window snapping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds TiledCorners::Center that snaps a floating window to a horizontally centered, full-height column (50% width). This is especially useful on ultrawide monitors where left/right halves are too wide for focused single-window work. Geometry: centered at 25% from left edge, 50% screen width, full height (minus gaps). Direction transitions from Center: - Left → Left half - Right → Right half - Up → Maximize (already full height, expand to full width) - Down → Bottom half Extends the TileWindow handler from #2009. Co-Authored-By: Claude Opus 4.6 --- src/input/actions.rs | 1 + src/shell/layout/floating/mod.rs | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/input/actions.rs b/src/input/actions.rs index 45fcc2c75..0d3efa64f 100644 --- a/src/input/actions.rs +++ b/src/input/actions.rs @@ -906,6 +906,7 @@ impl State { TilingZone::BottomLeft => TiledCorners::BottomLeft, TilingZone::Left => TiledCorners::Left, TilingZone::TopLeft => TiledCorners::TopLeft, + TilingZone::Center => TiledCorners::Center, }; let mut shell = self.common.shell.write(); diff --git a/src/shell/layout/floating/mod.rs b/src/shell/layout/floating/mod.rs index 2bc093f6c..d4bac30c7 100644 --- a/src/shell/layout/floating/mod.rs +++ b/src/shell/layout/floating/mod.rs @@ -177,6 +177,7 @@ pub enum TiledCorners { BottomLeft, Left, TopLeft, + Center, } impl TiledCorners { @@ -258,6 +259,16 @@ impl TiledCorners { output_geometry.size.h - inner * 2, )), ), + TiledCorners::Center => ( + Point::from(( + output_geometry.loc.x + output_geometry.size.w / 4 + inner, + output_geometry.loc.y + inner, + )), + Size::from(( + output_geometry.size.w / 2 - inner * 2, + output_geometry.size.h - inner * 2, + )), + ), }; Rectangle::new(loc, size).as_local() @@ -1243,7 +1254,8 @@ impl FloatingLayout { (Direction::Up, Some(TiledCorners::Bottom)) | (Direction::Down, Some(TiledCorners::Top)) | (Direction::Left, Some(TiledCorners::Right)) - | (Direction::Right, Some(TiledCorners::Left)) => { + | (Direction::Right, Some(TiledCorners::Left)) + | (Direction::Up, Some(TiledCorners::Center)) => { std::mem::drop(tiled_state); let mut maximized_state = element.maximized_state.lock().unwrap(); @@ -1257,6 +1269,11 @@ impl FloatingLayout { return MoveResult::Done; } + // center transitions + (Direction::Left, Some(TiledCorners::Center)) => TiledCorners::Left, + (Direction::Right, Some(TiledCorners::Center)) => TiledCorners::Right, + (Direction::Down, Some(TiledCorners::Center)) => TiledCorners::Bottom, + // figure out if we need to quater tile (Direction::Up, Some(TiledCorners::Left)) | (Direction::Left, Some(TiledCorners::Top)) => TiledCorners::TopLeft,