Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions winit-core/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<PhysicalSize<u32>>;

/// Sets resize increments of the surface.
Expand All @@ -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<Size>);

Expand Down
19 changes: 16 additions & 3 deletions winit-wayland/src/window/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -370,11 +376,18 @@ impl CoreWindow for Window {
}

fn surface_resize_increments(&self) -> Option<PhysicalSize<u32>> {
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<Size>) {
warn!("`set_surface_resize_increments` is not implemented for Wayland");
fn set_surface_resize_increments(&self, increments: Option<Size>) {
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) {
Expand Down
44 changes: 44 additions & 0 deletions winit-wayland/src/window/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ pub struct WindowState {
/// Min size.
min_surface_size: LogicalSize<u32>,
max_surface_size: Option<LogicalSize<u32>>,
resize_increments: Option<LogicalSize<u32>>,

/// 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
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -361,6 +363,36 @@ impl WindowState {
.unwrap_or(new_size.height);
}

// Apply size increments.
// We only apply increments if:
// 1. The compositor let us decide the size (constrain == true).
// 2. OR we are strictly resizing interactively (is_resizing), in which case we want to snap the mouse position.
// We do NOT apply increments if we are maximizing, fullscreening, or tiling (where checks below handle it),
// or if the compositor simply told us to be a specific size without resizing (e.g. corner snap).
Comment on lines +366 to +371
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Write it in a sense of why we do it like that and not what it is. Generally it gets a point though.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

im not sure i understood what you meant. these are the corner cases where snapping the size is undersirable, see my reply about using stateless.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should explain why undesirable.

Like e.g. for maximize, etc we must obey size, for tiling, etc we have gaps, etc, etc.

The problem is that you don't state a reason, but rather saying that you do X, because it's better, but never explain why.

Don't get me wrong, I get the logic, it's just would be nice to explicitly state what issues we try to avoid(hence why), rather than what we do.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, now it makes sense, the discussion we're having about stateless!

if (constrain || configure.is_resizing())
&& !configure.is_maximized()
&& !configure.is_fullscreen()
&& !configure.is_tiled()
Comment on lines +372 to +375
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not quite sure why constant do matter here? First of all, it's resize_increments so if there's no live resize going on, they should have no effect.

Comment on lines +373 to +375
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already covered in stateless, so just use it + resizing.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested it with stateless, and found it that, at least on KDE, when tiling, it leaves a gap in the window area.
image


Copy link
Member

@kchibisov kchibisov Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But stateless should have tiling inside of it, shouldn't it? I guess the issue is that stateless is for present configure, but you need it for a new configure?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stateless has tiling inside it. but im not sure kwin is setting the tiled state flag, it's probably not really tiling, just setting a window to a specific size when you drag it into a corner.

{
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);

Expand Down Expand Up @@ -749,6 +781,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<LogicalSize<u32>>) {
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<LogicalSize<u32>> {
self.resize_increments
}

fn apply_custom_cursor(&self, cursor: &CustomCursor) {
self.apply_on_pointer(|pointer, data| {
let surface = pointer.surface();
Expand Down
Loading