diff --git a/winit-wayland/src/window/state.rs b/winit-wayland/src/window/state.rs index 78f2de75cd..1ea8e55076 100644 --- a/winit-wayland/src/window/state.rs +++ b/winit-wayland/src/window/state.rs @@ -232,7 +232,7 @@ impl WindowState { has_pending_move: None, text_input_state: None, max_surface_size: None, - min_surface_size: MIN_WINDOW_SIZE, + min_surface_size: LogicalSize::new(0, 0), resize_increments: None, pointer_constraints, pointers: Default::default(), @@ -372,13 +372,7 @@ impl WindowState { } self.resizable = resizable; - if resizable { - // Restore min/max sizes of the window. - self.reload_min_max_hints(); - } else { - self.set_min_surface_size(Some(self.size)); - self.set_max_surface_size(Some(self.size)); - } + self.reload_min_max_hints(); // Reload the state on the frame as well. if let Some(frame) = self.frame.as_mut() { @@ -498,6 +492,8 @@ impl WindowState { // the redraw scheduling is done on the caller side. let _ = self.set_blur(true); } + + self.reload_min_max_hints(); } pub(crate) fn set_maximized(&self, maximized: bool) { @@ -571,40 +567,16 @@ impl WindowState { self.resize_increments } - /// Set maximum inner window size. + /// Set minimum inner window size. pub fn set_min_surface_size(&mut self, size: Option>) { - let Some(xdg_toplevel) = self.window.xdg_toplevel() else { return }; - - // Ensure that the window has the right minimum size. - let mut size = size.unwrap_or(MIN_WINDOW_SIZE); - size.width = size.width.max(MIN_WINDOW_SIZE.width); - size.height = size.height.max(MIN_WINDOW_SIZE.height); - - // Add the borders. - let size = self - .frame - .as_ref() - .map(|frame| frame.add_borders(size.width, size.height).into()) - .unwrap_or(size); - - self.min_surface_size = size; - xdg_toplevel.set_min_size(size.width as _, size.height as _); + self.min_surface_size = size.unwrap_or_default(); + self.reload_min_max_hints(); } /// Set maximum inner window size. pub fn set_max_surface_size(&mut self, size: Option>) { - let Some(xdg_toplevel) = self.window.xdg_toplevel() else { return }; - - let size = size.map(|size| { - self.frame - .as_ref() - .map(|frame| frame.add_borders(size.width, size.height).into()) - .unwrap_or(size) - }); - self.max_surface_size = size; - let size = size.unwrap_or_default(); - xdg_toplevel.set_max_size(size.width as _, size.height as _); + self.reload_min_max_hints(); } /// Set the CSD theme. @@ -624,8 +596,30 @@ impl WindowState { /// Reload the hints for minimum and maximum sizes. pub fn reload_min_max_hints(&mut self) { - self.set_min_surface_size(Some(self.min_surface_size)); - self.set_max_surface_size(self.max_surface_size); + let Some(xdg_toplevel) = self.window.xdg_toplevel() else { return }; + + let (mut min, max) = if self.resizable { + (self.min_surface_size, self.max_surface_size) + } else { + (self.stateless_size, Some(self.stateless_size)) + }; + + // Ensure that the window has the right minimum size. + min.width = min.width.max(MIN_WINDOW_SIZE.width); + min.height = min.height.max(MIN_WINDOW_SIZE.height); + + // Add the borders. + let add_borders = |size: LogicalSize| { + self.frame + .as_ref() + .map(|frame| frame.add_borders(size.width, size.height).into()) + .unwrap_or(size) + }; + + let min = add_borders(min); + xdg_toplevel.set_min_size(min.width as _, min.height as _); + let max = max.map(add_borders).unwrap_or_default(); + xdg_toplevel.set_max_size(max.width as _, max.height as _); } pub fn show_window_menu(&self, position: LogicalPosition) { diff --git a/winit-wayland/src/window/state/configure.rs b/winit-wayland/src/window/state/configure.rs index 61d4593944..6404b074e7 100644 --- a/winit-wayland/src/window/state/configure.rs +++ b/winit-wayland/src/window/state/configure.rs @@ -10,7 +10,7 @@ use tracing::warn; #[cfg(feature = "sctk-adwaita")] use super::create_sctk_adwaita_config; -use super::{WindowState, WindowType, WinitFrame}; +use super::{MIN_WINDOW_SIZE, WindowState, WindowType, WinitFrame}; impl WindowState { pub fn configure_popup(&mut self, configure: PopupConfigure) -> bool { @@ -151,7 +151,11 @@ impl WindowState { // 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()) + let was_resizing = matches!( + &self.window, + WindowType::Window { last_configure: Some(last), .. } if last.is_resizing() + ); + if (constrain || configure.is_resizing() || was_resizing) && !configure.is_maximized() && !configure.is_fullscreen() && !configure.is_tiled() @@ -161,16 +165,19 @@ impl WindowState { // // 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 increments = + LogicalSize::new(increments.width.max(1), increments.height.max(1)); + let snap = |size: u32, min: u32, increment: u32, floor: u32| { + let steps = size.saturating_sub(min) / increment; + let floor_steps = floor.saturating_sub(min).div_ceil(increment); + min + steps.max(floor_steps) * increment + }; + let min = self.min_surface_size; + let (width, height) = ( + snap(new_size.width, min.width, increments.width, MIN_WINDOW_SIZE.width), + snap(new_size.height, min.height, increments.height, MIN_WINDOW_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(); } } @@ -189,6 +196,9 @@ impl WindowState { let new_state = configure.state; if let WindowType::Window { last_configure, .. } = &mut self.window { let old_state = last_configure.as_ref().map(|configure| configure.state); + let decoration_mode_changed = last_configure + .as_ref() + .is_some_and(|last| last.decoration_mode != configure.decoration_mode); let state_change_requires_resize = old_state .map(|old_state| { @@ -203,7 +213,10 @@ impl WindowState { // NOTE: Set the configure before doing a resize, since we query it during it. *last_configure = Some(configure); - if state_change_requires_resize || new_size != self.surface_size() { + if state_change_requires_resize + || new_size != self.surface_size() + || decoration_mode_changed + { self.resize(new_size); true } else { diff --git a/winit/src/changelog/unreleased.md b/winit/src/changelog/unreleased.md index a0e7d3a474..79b025d8a2 100644 --- a/winit/src/changelog/unreleased.md +++ b/winit/src/changelog/unreleased.md @@ -45,6 +45,8 @@ changelog entry. - On Windows, fix a crash occurring when trying to create a DXGI surface on a window that is both fullscreen and always on top. - On Wayland, use the touch serial for `Window::drag_window`, `Window::drag_resize_window` and `Window::show_window_menu` when the interaction was started by touch. - On Windows, fix a crash when moving a window that owns windows not created by winit, such as native dialogs or application helper windows. +- On Wayland, fix minimum and maximum size hints being off by the client-side decoration borders, and being lost when toggling `Window::set_resizable`. +- On Wayland, with resize increments: align from the requested minimum size instead of an internal `2x1`, round a surface smaller than one increment up to one, keep the alignment when an interactive resize ends, and ignore a zero increment instead of panicking. ### Changed