From ab56e9f57dd5d522f4f65cb140b584a273eeb3a9 Mon Sep 17 00:00:00 2001 From: Anton Bulakh Date: Sat, 3 Sep 2022 21:50:22 +0300 Subject: [PATCH] Allow changing resize increments after window creation --- CHANGELOG.md | 2 + FEATURES.md | 2 +- examples/window_resize_increments.rs | 57 +++++++++++++++++++ src/platform/macos.rs | 9 --- src/platform/x11.rs | 20 ------- src/platform_impl/android/mod.rs | 6 ++ src/platform_impl/ios/window.rs | 9 +++ src/platform_impl/linux/mod.rs | 14 +++-- src/platform_impl/linux/wayland/window/mod.rs | 10 ++++ src/platform_impl/linux/x11/util/hint.rs | 16 ++++-- src/platform_impl/linux/x11/window.rs | 22 ++++++- src/platform_impl/macos/window.rs | 34 +++++++++-- src/platform_impl/web/window.rs | 10 ++++ src/platform_impl/windows/window.rs | 8 +++ src/window.rs | 39 +++++++++++++ 15 files changed, 213 insertions(+), 45 deletions(-) create mode 100644 examples/window_resize_increments.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d1724de..cf982763 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ And please only add new entries to the top of this list, right below the `# Unre # Unreleased +- Migrated `WindowBuilderExtUnix::with_resize_increments` to `WindowBuilder`. +- Added `Window::resize_increments`/`Window::set_resize_increments` to update resize increments at runtime for X11/macOS. - macOS/iOS: Use `objc2` instead of `objc` internally. - **Breaking:** Bump MSRV from `1.57` to `1.60`. - **Breaking:** Split the `platform::unix` module into `platform::x11` and `platform::wayland`. The extension types are similarly renamed. diff --git a/FEATURES.md b/FEATURES.md index 3610f4b7..4598e508 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -177,7 +177,7 @@ Legend: |Window decorations |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**| |Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**| |Window resizing |✔️ |▢[#219]|✔️ |▢[#306] |**N/A**|**N/A**|✔️ | -|Window resize increments |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**| +|Window resize increments |❌ |✔️ |✔️ |❌ |**N/A**|**N/A**|**N/A**| |Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A | |Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**| |Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**| diff --git a/examples/window_resize_increments.rs b/examples/window_resize_increments.rs new file mode 100644 index 00000000..ce06daf2 --- /dev/null +++ b/examples/window_resize_increments.rs @@ -0,0 +1,57 @@ +use log::debug; +use simple_logger::SimpleLogger; +use winit::{ + dpi::LogicalSize, + event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event_loop::EventLoop, + window::WindowBuilder, +}; + +fn main() { + SimpleLogger::new().init().unwrap(); + let event_loop = EventLoop::new(); + + let window = WindowBuilder::new() + .with_title("A fantastic window!") + .with_inner_size(LogicalSize::new(128.0, 128.0)) + .with_resize_increments(LogicalSize::new(25.0, 25.0)) + .build(&event_loop) + .unwrap(); + + let mut has_increments = true; + + event_loop.run(move |event, _, control_flow| { + control_flow.set_wait(); + + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + window_id, + } if window_id == window.id() => control_flow.set_exit(), + Event::WindowEvent { + event: + WindowEvent::KeyboardInput { + input: + KeyboardInput { + virtual_keycode: Some(VirtualKeyCode::Space), + state: ElementState::Released, + .. + }, + .. + }, + window_id, + } if window_id == window.id() => { + has_increments = !has_increments; + + let new_increments = match window.resize_increments() { + Some(_) => None, + None => Some(LogicalSize::new(25.0, 25.0)), + }; + debug!("Had increments: {}", new_increments.is_none()); + window.set_resize_increments(new_increments); + } + Event::MainEventsCleared => window.request_redraw(), + _ => (), + } + }); +} diff --git a/src/platform/macos.rs b/src/platform/macos.rs index d7fe84a2..714b8a8f 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -1,7 +1,6 @@ use std::os::raw::c_void; use crate::{ - dpi::LogicalSize, event_loop::{EventLoopBuilder, EventLoopWindowTarget}, monitor::MonitorHandle, window::{Window, WindowBuilder}, @@ -109,8 +108,6 @@ pub trait WindowBuilderExtMacOS { fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> WindowBuilder; /// Makes the window content appear behind the titlebar. fn with_fullsize_content_view(self, fullsize_content_view: bool) -> WindowBuilder; - /// Build window with `resizeIncrements` property. Values must not be 0. - fn with_resize_increments(self, increments: LogicalSize) -> WindowBuilder; fn with_disallow_hidpi(self, disallow_hidpi: bool) -> WindowBuilder; fn with_has_shadow(self, has_shadow: bool) -> WindowBuilder; } @@ -155,12 +152,6 @@ impl WindowBuilderExtMacOS for WindowBuilder { self } - #[inline] - fn with_resize_increments(mut self, increments: LogicalSize) -> WindowBuilder { - self.platform_specific.resize_increments = Some(increments); - self - } - #[inline] fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> WindowBuilder { self.platform_specific.disallow_hidpi = disallow_hidpi; diff --git a/src/platform/x11.rs b/src/platform/x11.rs index 05c57689..655d6281 100644 --- a/src/platform/x11.rs +++ b/src/platform/x11.rs @@ -190,20 +190,6 @@ pub trait WindowBuilderExtX11 { /// Build window with `_GTK_THEME_VARIANT` hint set to the specified value. Currently only relevant on X11. fn with_gtk_theme_variant(self, variant: String) -> Self; - /// Build window with resize increment hint. Only implemented on X11. - /// - /// ``` - /// # use winit::dpi::{LogicalSize, PhysicalSize}; - /// # use winit::window::WindowBuilder; - /// # use winit::platform::x11::WindowBuilderExtX11; - /// // Specify the size in logical dimensions like this: - /// WindowBuilder::new().with_resize_increments(LogicalSize::new(400.0, 200.0)); - /// - /// // Or specify the size in physical dimensions like this: - /// WindowBuilder::new().with_resize_increments(PhysicalSize::new(400, 200)); - /// ``` - fn with_resize_increments>(self, increments: S) -> Self; - /// Build window with base size hint. Only implemented on X11. /// /// ``` @@ -259,12 +245,6 @@ impl WindowBuilderExtX11 for WindowBuilder { self } - #[inline] - fn with_resize_increments>(mut self, increments: S) -> Self { - self.platform_specific.resize_increments = Some(increments.into()); - self - } - #[inline] fn with_base_size>(mut self, base_size: S) -> Self { self.platform_specific.base_size = Some(base_size.into()); diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 9c275e6e..3ed8dfee 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -757,6 +757,12 @@ impl Window { pub fn set_max_inner_size(&self, _: Option) {} + pub fn resize_increments(&self) -> Option> { + None + } + + pub fn set_resize_increments(&self, _increments: Option) {} + pub fn set_title(&self, _title: &str) {} pub fn set_visible(&self, _visibility: bool) {} diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index cf7b55bd..dd62211d 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -154,6 +154,15 @@ impl Inner { warn!("`Window::set_max_inner_size` is ignored on iOS") } + pub fn resize_increments(&self) -> Option> { + None + } + + #[inline] + pub fn set_resize_increments(&self, _increments: Option) { + warn!("`Window::set_resize_increments` is ignored on iOS") + } + pub fn set_resizable(&self, _resizable: bool) { warn!("`Window::set_resizable` is ignored on iOS") } diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index a4ced5af..0b075086 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -95,8 +95,6 @@ pub struct PlatformSpecificWindowBuilderAttributes { #[cfg(feature = "x11")] pub screen_id: Option, #[cfg(feature = "x11")] - pub resize_increments: Option, - #[cfg(feature = "x11")] pub base_size: Option, #[cfg(feature = "x11")] pub override_redirect: bool, @@ -117,8 +115,6 @@ impl Default for PlatformSpecificWindowBuilderAttributes { #[cfg(feature = "x11")] screen_id: None, #[cfg(feature = "x11")] - resize_increments: None, - #[cfg(feature = "x11")] base_size: None, #[cfg(feature = "x11")] override_redirect: false, @@ -391,6 +387,16 @@ impl Window { x11_or_wayland!(match self; Window(w) => w.set_max_inner_size(dimensions)) } + #[inline] + pub fn resize_increments(&self) -> Option> { + x11_or_wayland!(match self; Window(w) => w.resize_increments()) + } + + #[inline] + pub fn set_resize_increments(&self, increments: Option) { + x11_or_wayland!(match self; Window(w) => w.set_resize_increments(increments)) + } + #[inline] pub fn set_resizable(&self, resizable: bool) { x11_or_wayland!(match self; Window(w) => w.set_resizable(resizable)) diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index db63781d..53e0d4a7 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -404,6 +404,16 @@ impl Window { self.send_request(WindowRequest::MaxSize(size)); } + #[inline] + pub fn resize_increments(&self) -> Option> { + None + } + + #[inline] + pub fn set_resize_increments(&self, _increments: Option) { + warn!("`set_resize_increments` is not implemented for Wayland"); + } + #[inline] pub fn set_resizable(&self, resizable: bool) { self.resizeable.store(resizable, Ordering::Relaxed); diff --git a/src/platform_impl/linux/x11/util/hint.rs b/src/platform_impl/linux/x11/util/hint.rs index a2dbe6a1..723dfad4 100644 --- a/src/platform_impl/linux/x11/util/hint.rs +++ b/src/platform_impl/linux/x11/util/hint.rs @@ -197,11 +197,17 @@ impl<'a> NormalHints<'a> { } pub fn get_position(&self) -> Option<(i32, i32)> { - if has_flag(self.size_hints.flags, ffi::PPosition) { - Some((self.size_hints.x as i32, self.size_hints.y as i32)) - } else { - None - } + has_flag(self.size_hints.flags, ffi::PPosition) + .then(|| (self.size_hints.x as i32, self.size_hints.y as i32)) + } + + pub fn get_resize_increments(&self) -> Option<(u32, u32)> { + has_flag(self.size_hints.flags, ffi::PResizeInc).then(|| { + ( + self.size_hints.width_inc as u32, + self.size_hints.height_inc as u32, + ) + }) } pub fn set_position(&mut self, position: Option<(i32, i32)>) { diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 5342f8fb..cd66ebe7 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -378,7 +378,7 @@ impl UnownedWindow { let mut shared_state = window.shared_state.get_mut().unwrap(); shared_state.min_inner_size = min_inner_size.map(Into::into); shared_state.max_inner_size = max_inner_size.map(Into::into); - shared_state.resize_increments = pl_attribs.resize_increments; + shared_state.resize_increments = window_attrs.resize_increments; shared_state.base_size = pl_attribs.base_size; let mut normal_hints = util::NormalHints::new(xconn); @@ -387,7 +387,7 @@ impl UnownedWindow { normal_hints.set_min_size(min_inner_size.map(Into::into)); normal_hints.set_max_size(max_inner_size.map(Into::into)); normal_hints.set_resize_increments( - pl_attribs + window_attrs .resize_increments .map(|size| size.to_physical::(scale_factor).into()), ); @@ -1172,6 +1172,24 @@ impl UnownedWindow { self.set_max_inner_size_physical(physical_dimensions); } + #[inline] + pub fn resize_increments(&self) -> Option> { + self.xconn + .get_normal_hints(self.xwindow) + .ok() + .and_then(|hints| hints.get_resize_increments()) + .map(Into::into) + } + + #[inline] + pub fn set_resize_increments(&self, increments: Option) { + self.shared_state_lock().resize_increments = increments; + let physical_increments = + increments.map(|increments| increments.to_physical::(self.scale_factor()).into()); + self.update_normal_hints(|hints| hints.set_resize_increments(physical_increments)) + .expect("Failed to call `XSetWMNormalHints`"); + } + pub(crate) fn adjust_for_dpi( &self, old_scale_factor: f64, diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 3fa514a3..f2d99e09 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -242,10 +242,14 @@ fn create_window( } if let Some(increments) = pl_attrs.resize_increments { - let (x, y) = (increments.width, increments.height); - if x >= 1.0 && y >= 1.0 { - let size = NSSize::new(x as CGFloat, y as CGFloat); - ns_window.setResizeIncrements_(size); + let (w, h) = (increments.width, increments.height); + if w >= 1.0 && h >= 1.0 { + let size = NSSize::new(w as CGFloat, h as CGFloat); + // It was concluded (#2411) that there is never a use-case for + // "outer" resize increments, hence we set "inner" ones here. + // ("outer" in macOS being just resizeIncrements, and "inner" - contentResizeIncrements) + // This is consistent with X11 size hints behavior + ns_window.setContentResizeIncrements_(size); } } @@ -590,6 +594,28 @@ impl UnownedWindow { } } + pub fn resize_increments(&self) -> Option> { + let increments = unsafe { self.ns_window.contentResizeIncrements() }; + let (x, y) = (increments.width, increments.height); + if x > 1.0 || y > 1.0 { + Some(LogicalSize::new(x, y).to_physical(self.scale_factor())) + } else { + None + } + } + + pub fn set_resize_increments(&self, increments: Option) { + let size = increments + .map(|increments| { + let logical = increments.to_logical::(self.scale_factor()); + NSSize::new(logical.width.max(1.0), logical.height.max(1.0)) + }) + .unwrap_or_else(|| NSSize::new(1.0, 1.0)); + unsafe { + self.ns_window.setContentResizeIncrements_(size); + } + } + #[inline] pub fn set_resizable(&self, resizable: bool) { let fullscreen = { diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 492baefc..6fe5839e 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -153,6 +153,16 @@ impl Window { // Intentionally a no-op: users can't resize canvas elements } + #[inline] + pub fn resize_increments(&self) -> Option> { + None + } + + #[inline] + pub fn set_resize_increments(&self, _increments: Option) { + // Intentionally a no-op: users can't resize canvas elements + } + #[inline] pub fn set_resizable(&self, _resizable: bool) { // Intentionally a no-op: users can't resize canvas elements diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 31294409..5117c629 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -235,6 +235,14 @@ impl Window { self.set_inner_size(size.into()); } + #[inline] + pub fn resize_increments(&self) -> Option> { + None + } + + #[inline] + pub fn set_resize_increments(&self, _increments: Option) {} + #[inline] pub fn set_resizable(&self, resizable: bool) { let window = self.window.clone(); diff --git a/src/window.rs b/src/window.rs index dba514f9..ba7af311 100644 --- a/src/window.rs +++ b/src/window.rs @@ -134,6 +134,7 @@ pub(crate) struct WindowAttributes { pub decorations: bool, pub always_on_top: bool, pub window_icon: Option, + pub resize_increments: Option, } impl Default for WindowAttributes { @@ -153,6 +154,7 @@ impl Default for WindowAttributes { decorations: true, always_on_top: false, window_icon: None, + resize_increments: None, } } } @@ -333,6 +335,17 @@ impl WindowBuilder { self } + /// Build window with resize increments hint. + /// + /// The default is `None`. + /// + /// See [`Window::set_resize_increments`] for details. + #[inline] + pub fn with_resize_increments>(mut self, resize_increments: S) -> Self { + self.window.resize_increments = Some(resize_increments.into()); + self + } + /// Builds the window. /// /// Possible causes of error include denied permission, incompatible system, and lack of memory. @@ -604,6 +617,32 @@ impl Window { pub fn set_max_inner_size>(&self, max_size: Option) { self.window.set_max_inner_size(max_size.map(|s| s.into())) } + + /// Returns window resize increments if any were set. + /// + /// ## Platform-specific + /// + /// - **iOS / Android / Web / Wayland / Windows:** Always returns [`None`]. + #[inline] + pub fn resize_increments(&self) -> Option> { + self.window.resize_increments() + } + + /// Sets window resize increments. + /// + /// This is a niche constraint hint usually employed by terminal emulators + /// and other apps that need "blocky" resizes. + /// + /// ## Platform-specific + /// + /// - **macOS:** Increments are converted to logical size and then macOS rounds them to whole numbers. + /// - **Wayland / Windows:** Not implemented. + /// - **iOS / Android / Web:** Unsupported. + #[inline] + pub fn set_resize_increments>(&self, increments: Option) { + self.window + .set_resize_increments(increments.map(Into::into)) + } } /// Misc. attribute functions.