Allow changing resize increments after window creation

This commit is contained in:
Anton Bulakh 2022-09-03 21:50:22 +03:00 committed by GitHub
parent 97d2aaa953
commit ab56e9f57d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 213 additions and 45 deletions

View file

@ -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.

View file

@ -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**|

View file

@ -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(),
_ => (),
}
});
}

View file

@ -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<f64>) -> 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<f64>) -> 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;

View file

@ -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<S: Into<Size>>(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<S: Into<Size>>(mut self, increments: S) -> Self {
self.platform_specific.resize_increments = Some(increments.into());
self
}
#[inline]
fn with_base_size<S: Into<Size>>(mut self, base_size: S) -> Self {
self.platform_specific.base_size = Some(base_size.into());

View file

@ -757,6 +757,12 @@ impl Window {
pub fn set_max_inner_size(&self, _: Option<Size>) {}
pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
None
}
pub fn set_resize_increments(&self, _increments: Option<Size>) {}
pub fn set_title(&self, _title: &str) {}
pub fn set_visible(&self, _visibility: bool) {}

View file

@ -154,6 +154,15 @@ impl Inner {
warn!("`Window::set_max_inner_size` is ignored on iOS")
}
pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
None
}
#[inline]
pub fn set_resize_increments(&self, _increments: Option<Size>) {
warn!("`Window::set_resize_increments` is ignored on iOS")
}
pub fn set_resizable(&self, _resizable: bool) {
warn!("`Window::set_resizable` is ignored on iOS")
}

View file

@ -95,8 +95,6 @@ pub struct PlatformSpecificWindowBuilderAttributes {
#[cfg(feature = "x11")]
pub screen_id: Option<i32>,
#[cfg(feature = "x11")]
pub resize_increments: Option<Size>,
#[cfg(feature = "x11")]
pub base_size: Option<Size>,
#[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<PhysicalSize<u32>> {
x11_or_wayland!(match self; Window(w) => w.resize_increments())
}
#[inline]
pub fn set_resize_increments(&self, increments: Option<Size>) {
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))

View file

@ -404,6 +404,16 @@ impl Window {
self.send_request(WindowRequest::MaxSize(size));
}
#[inline]
pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
None
}
#[inline]
pub fn set_resize_increments(&self, _increments: Option<Size>) {
warn!("`set_resize_increments` is not implemented for Wayland");
}
#[inline]
pub fn set_resizable(&self, resizable: bool) {
self.resizeable.store(resizable, Ordering::Relaxed);

View file

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

View file

@ -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::<u32>(scale_factor).into()),
);
@ -1172,6 +1172,24 @@ impl UnownedWindow {
self.set_max_inner_size_physical(physical_dimensions);
}
#[inline]
pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
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<Size>) {
self.shared_state_lock().resize_increments = increments;
let physical_increments =
increments.map(|increments| increments.to_physical::<u32>(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,

View file

@ -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<PhysicalSize<u32>> {
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<Size>) {
let size = increments
.map(|increments| {
let logical = increments.to_logical::<f64>(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 = {

View file

@ -153,6 +153,16 @@ impl Window {
// Intentionally a no-op: users can't resize canvas elements
}
#[inline]
pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
None
}
#[inline]
pub fn set_resize_increments(&self, _increments: Option<Size>) {
// 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

View file

@ -235,6 +235,14 @@ impl Window {
self.set_inner_size(size.into());
}
#[inline]
pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
None
}
#[inline]
pub fn set_resize_increments(&self, _increments: Option<Size>) {}
#[inline]
pub fn set_resizable(&self, resizable: bool) {
let window = self.window.clone();

View file

@ -134,6 +134,7 @@ pub(crate) struct WindowAttributes {
pub decorations: bool,
pub always_on_top: bool,
pub window_icon: Option<Icon>,
pub resize_increments: Option<Size>,
}
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<S: Into<Size>>(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<S: Into<Size>>(&self, max_size: Option<S>) {
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<PhysicalSize<u32>> {
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<S: Into<Size>>(&self, increments: Option<S>) {
self.window
.set_resize_increments(increments.map(Into::into))
}
}
/// Misc. attribute functions.