From 41f42659570b8098054d9a432beda63eeb2ab44c Mon Sep 17 00:00:00 2001 From: Pedro Macedo Date: Sun, 1 Mar 2026 10:28:35 -0300 Subject: [PATCH] wayland: implement resize increments --- winit-core/src/window.rs | 3 +- winit-wayland/src/window/mod.rs | 19 ++++++++++-- winit-wayland/src/window/state.rs | 50 +++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 5 deletions(-) diff --git a/winit-core/src/window.rs b/winit-core/src/window.rs index cfca86280..6f564ba90 100644 --- a/winit-core/src/window.rs +++ b/winit-core/src/window.rs @@ -834,7 +834,7 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug { /// /// ## Platform-specific /// - /// - **iOS / Android / Web / Wayland / Orbital:** Always returns [`None`]. + /// - **iOS / Android / Web / Orbital:** Always returns [`None`]. fn surface_resize_increments(&self) -> Option>; /// Sets resize increments of the surface. @@ -846,7 +846,6 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug { /// /// - **macOS:** Increments are converted to logical size and then macOS rounds them to whole /// numbers. - /// - **Wayland:** Not implemented. /// - **iOS / Android / Web / Orbital:** Unsupported. fn set_surface_resize_increments(&self, increments: Option); diff --git a/winit-wayland/src/window/mod.rs b/winit-wayland/src/window/mod.rs index 37021bb01..81849be78 100644 --- a/winit-wayland/src/window/mod.rs +++ b/winit-wayland/src/window/mod.rs @@ -171,6 +171,12 @@ impl Window { Cursor::Custom(cursor) => window_state.set_custom_cursor(cursor), } + // Apply resize increments. + if let Some(increments) = attributes.surface_resize_increments { + let increments = increments.to_logical(window_state.scale_factor()); + window_state.set_resize_increments(Some(increments)); + } + // Activate the window when the token is passed. if let (Some(xdg_activation), Some(token)) = (xdg_activation.as_ref(), activation_token) { xdg_activation.activate(token.into_raw(), &surface); @@ -370,11 +376,18 @@ impl CoreWindow for Window { } fn surface_resize_increments(&self) -> Option> { - None + let window_state = self.window_state.lock().unwrap(); + let scale_factor = window_state.scale_factor(); + window_state + .resize_increments() + .map(|size| super::logical_to_physical_rounded(size, scale_factor)) } - fn set_surface_resize_increments(&self, _increments: Option) { - warn!("`set_surface_resize_increments` is not implemented for Wayland"); + fn set_surface_resize_increments(&self, increments: Option) { + let mut window_state = self.window_state.lock().unwrap(); + let scale_factor = window_state.scale_factor(); + let increments = increments.map(|size| size.to_logical(scale_factor)); + window_state.set_resize_increments(increments); } fn set_title(&self, title: &str) { diff --git a/winit-wayland/src/window/state.rs b/winit-wayland/src/window/state.rs index d340742a1..3a1a9b527 100644 --- a/winit-wayland/src/window/state.rs +++ b/winit-wayland/src/window/state.rs @@ -140,6 +140,7 @@ pub struct WindowState { /// Min size. min_surface_size: LogicalSize, max_surface_size: Option>, + resize_increments: Option>, /// The size of the window when no states were applied to it. The primary use for it /// is to fallback to original window size, before it was maximized, if the compositor @@ -223,6 +224,7 @@ impl WindowState { last_configure: None, max_surface_size: None, min_surface_size: MIN_WINDOW_SIZE, + resize_increments: None, pointer_constraints, pointers: Default::default(), queue_handle: queue_handle.clone(), @@ -361,6 +363,42 @@ impl WindowState { .unwrap_or(new_size.height); } + // Apply size increments. + // + // We conditionally apply increments to avoid conflicts with the compositor's layout rules: + // 1. If the window is floating (constrain == true), we snap to increments to ensure the + // app's grid alignment. + // 2. If the user is interactively resizing (is_resizing), we snap the size to provide + // feedback. + // + // However, we MUST NOT snap if the compositor enforces a specific size (constrain == false, + // or states like Maximized/Tiled). Snapping in these cases (e.g. corner tiling) would + // shrink the window below the allocated area, creating visible gaps between valid + // windows or screen edges. + if (constrain || configure.is_resizing()) + && !configure.is_maximized() + && !configure.is_fullscreen() + && !configure.is_tiled() + { + if let Some(increments) = self.resize_increments { + // We use min size as a base size for the increments, similar to how X11 does it. + // + // This ensures that we can always reach the min size and the increments are + // calculated from it. + let (delta_width, delta_height) = ( + new_size.width.saturating_sub(self.min_surface_size.width), + new_size.height.saturating_sub(self.min_surface_size.height), + ); + + let width = self.min_surface_size.width + + (delta_width / increments.width) * increments.width; + let height = self.min_surface_size.height + + (delta_height / increments.height) * increments.height; + + new_size = (width, height).into(); + } + } + let new_state = configure.state; let old_state = self.last_configure.as_ref().map(|configure| configure.state); @@ -749,6 +787,18 @@ impl WindowState { self.selected_cursor = SelectedCursor::Custom(cursor); } + /// Set the resize increments of the window. + pub fn set_resize_increments(&mut self, increments: Option>) { + self.resize_increments = increments; + // NOTE: We don't update the window size here, because it will be done on the next resize + // or configure event. + } + + /// Get the resize increments of the window. + pub fn resize_increments(&self) -> Option> { + self.resize_increments + } + fn apply_custom_cursor(&self, cursor: &CustomCursor) { self.apply_on_pointer(|pointer, data| { let surface = pointer.surface();