diff --git a/FEATURES.md b/FEATURES.md index df813490..2ae94770 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -179,7 +179,7 @@ Legend: |Window decorations |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|✔️ | |Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** | |Window resizing |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ | -|Window resize increments |❌ |✔️ |✔️ |❌ |**N/A**|**N/A**|**N/A**|**N/A** | +|Window resize increments |✔️ |✔️ |✔️ |❌ |**N/A**|**N/A**|**N/A**|**N/A** | |Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A |✔️ | |Window blur |❌ |❌ |❌ |✔️ |**N/A**|**N/A**|N/A |❌ | |Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** | diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index ebdd8e47..9e5617c0 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -63,6 +63,7 @@ changelog entry. - On macOS, add services menu. - On Windows, add `with_title_text_color`, and `with_corner_preference` on `WindowAttributesExtWindows`. +- On Windows, implement resize increments. ### Changed @@ -253,4 +254,4 @@ changelog entry. - On Web, fix setting cursor icon overriding cursor visibility. - On Windows, fix cursor not confined to center of window when grabbed and hidden. - On macOS, fix sequence of mouse events being out of order when dragging on the trackpad. -- On Wayland, fix decoration glitch on close with some compositors +- On Wayland, fix decoration glitch on close with some compositors. diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index c19cc386..ae10bec0 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -52,17 +52,19 @@ use windows_sys::Win32::{ HTCAPTION, HTCLIENT, MINMAXINFO, MNC_CLOSE, MSG, NCCALCSIZE_PARAMS, PM_REMOVE, PT_PEN, PT_TOUCH, RI_MOUSE_HWHEEL, RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS, - WM_CAPTURECHANGED, WM_CLOSE, WM_CREATE, WM_DESTROY, WM_DPICHANGED, WM_ENTERSIZEMOVE, - WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION, - WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT, WM_INPUT_DEVICE_CHANGE, - WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, - WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE, - WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, - WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, - WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, - WM_TOUCH, WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP, - WNDCLASSEXW, WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT, - WS_OVERLAPPED, WS_POPUP, WS_VISIBLE, + WMSZ_BOTTOM, WMSZ_BOTTOMLEFT, WMSZ_BOTTOMRIGHT, WMSZ_LEFT, WMSZ_RIGHT, WMSZ_TOP, + WMSZ_TOPLEFT, WMSZ_TOPRIGHT, WM_CAPTURECHANGED, WM_CLOSE, WM_CREATE, WM_DESTROY, + WM_DPICHANGED, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION, + WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT, + WM_INPUT_DEVICE_CHANGE, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, + WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, + WM_MOUSEWHEEL, WM_NCACTIVATE, WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, + WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, WM_POINTERUP, WM_POINTERUPDATE, + WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE, + WM_SIZING, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH, WM_WINDOWPOSCHANGED, + WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSEXW, WS_EX_LAYERED, + WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP, + WS_VISIBLE, }, }, }; @@ -1352,6 +1354,98 @@ unsafe fn public_window_callback_inner( result = ProcResult::Value(0); } + WM_SIZING => { + /// Calculate the amount to add to round `value` to the nearest multiple of `increment`. + fn snap_to_nearest_increment_delta(value: i32, increment: i32) -> i32 { + let half_one = increment / 2; + let half_two = increment - half_one; + half_one - (value - half_two) % increment + } + + let scale_factor = userdata.window_state_lock().scale_factor; + let Some(inc) = userdata + .window_state_lock() + .resize_increments + .map(|inc| inc.to_physical(scale_factor)) + .filter(|inc| inc.width > 0 && inc.height > 0) + else { + result = ProcResult::Value(0); + return; + }; + + let side = wparam as u32; + // The desired new size of the window, decorations included. + let rect = unsafe { &mut *(lparam as *mut RECT) }; + + // We need to calculate the dimensions of the window decorations to get the true + // size of the window's contents + let adj_rect = userdata + .window_state_lock() + .window_flags + .adjust_rect(window, *rect) + .unwrap_or(*rect); + let deco_width = rect.left - adj_rect.left + adj_rect.right - rect.right; + let deco_height = rect.top - adj_rect.top + adj_rect.bottom - rect.bottom; + + let width = rect.right - rect.left - deco_width; + let height = rect.bottom - rect.top - deco_height; + + let mut width_delta = snap_to_nearest_increment_delta(width, inc.width); + let mut height_delta = snap_to_nearest_increment_delta(height, inc.height); + + // Windows won't bound check the value of `rect` after we're done here, so we + // have to check manually. If the width/height we snap to would go out of bounds, just + // set it equal to the min/max bound. + let min_size = userdata + .window_state_lock() + .min_size + .map(|size| size.to_physical(scale_factor)); + let max_size = userdata + .window_state_lock() + .max_size + .map(|size| size.to_physical(scale_factor)); + let final_width = width + width_delta; + let final_height = height + height_delta; + if let Some(min_size) = min_size { + if final_width < min_size.width { + width_delta += min_size.width - final_width; + } + if final_height < min_size.height { + height_delta += min_size.height - final_height; + } + } + if let Some(max_size) = max_size { + if final_width > max_size.width { + width_delta -= final_width - max_size.width; + } + if final_height > max_size.height { + height_delta -= final_height - max_size.height; + } + } + + match side { + WMSZ_LEFT | WMSZ_BOTTOMLEFT | WMSZ_TOPLEFT => { + rect.left -= width_delta; + } + WMSZ_RIGHT | WMSZ_BOTTOMRIGHT | WMSZ_TOPRIGHT => { + rect.right += width_delta; + } + _ => {} + } + + match side { + WMSZ_TOP | WMSZ_TOPLEFT | WMSZ_TOPRIGHT => { + rect.top -= height_delta; + } + WMSZ_BOTTOM | WMSZ_BOTTOMLEFT | WMSZ_BOTTOMRIGHT => { + rect.bottom += height_delta; + } + _ => {} + } + + result = ProcResult::DefWindowProc(wparam); + } + WM_MENUCHAR => { result = ProcResult::Value((MNC_CLOSE << 16) as isize); } diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 454845fa..9c0001f5 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -284,11 +284,16 @@ impl Window { #[inline] pub fn resize_increments(&self) -> Option> { - None + let w = self.window_state_lock(); + let scale_factor = w.scale_factor; + w.resize_increments + .map(|size| size.to_physical(scale_factor)) } #[inline] - pub fn set_resize_increments(&self, _increments: Option) {} + pub fn set_resize_increments(&self, increments: Option) { + self.window_state_lock().resize_increments = increments; + } #[inline] pub fn set_resizable(&self, resizable: bool) { diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 85b70ba8..ab0b484c 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -33,6 +33,8 @@ pub(crate) struct WindowState { pub min_size: Option, pub max_size: Option, + pub resize_increments: Option, + pub window_icon: Option, pub taskbar_icon: Option, @@ -155,6 +157,8 @@ impl WindowState { min_size: attributes.min_inner_size, max_size: attributes.max_inner_size, + resize_increments: attributes.resize_increments, + window_icon: attributes.window_icon.clone(), taskbar_icon: None, diff --git a/src/window.rs b/src/window.rs index 03040889..cddef046 100644 --- a/src/window.rs +++ b/src/window.rs @@ -893,7 +893,7 @@ impl Window { /// /// ## Platform-specific /// - /// - **iOS / Android / Web / Wayland / Windows / Orbital:** Always returns [`None`]. + /// - **iOS / Android / Web / Wayland / Orbital:** Always returns [`None`]. #[inline] pub fn resize_increments(&self) -> Option> { let _span = tracing::debug_span!("winit::Window::resize_increments",).entered(); @@ -908,7 +908,7 @@ impl Window { /// ## Platform-specific /// /// - **macOS:** Increments are converted to logical size and then macOS rounds them to whole numbers. - /// - **Wayland / Windows:** Not implemented. + /// - **Wayland:** Not implemented. /// - **iOS / Android / Web / Orbital:** Unsupported. #[inline] pub fn set_resize_increments>(&self, increments: Option) {