From bbcd3019e8f87744a81c2fb8cd6355f6382da1fc Mon Sep 17 00:00:00 2001 From: Osspial Date: Fri, 23 Mar 2018 05:35:35 -0400 Subject: [PATCH] Add ability to change the min/max size of windows at runtime (#405) * Add min/max size setting for win32 and wayland backends * Implement dynamic min/max size on macos * Add min/max size setting for x11 * Add empty functions for remaining platforms * Improved min/max size setting for x11 * Added CHANGELOG entry for new min/max methods * Added documentation for new min/max methods * On win32, bound window size to min/max dimensions on window creation * On win32, force re-check of window size when changing min/max dimensions * Fix freeze when setting min and max size --- CHANGELOG.md | 1 + examples/min_max_size.rs | 7 +- src/platform/android/mod.rs | 6 ++ src/platform/emscripten/mod.rs | 6 ++ src/platform/ios/mod.rs | 6 ++ src/platform/linux/mod.rs | 16 ++++ src/platform/linux/wayland/window.rs | 14 +++- src/platform/linux/x11/util.rs | 43 +++++++++++ src/platform/linux/x11/window.rs | 107 ++++++++++++++++++++++----- src/platform/macos/window.rs | 14 ++++ src/platform/windows/window.rs | 58 ++++++++++++++- src/window.rs | 16 ++++ 12 files changed, 270 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38cc82a8..87c6c17f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # Unreleased +- Added `set_min_dimensions` and `set_max_dimensions` methods to `Window`, and implemented on Windows, X11, Wayland, and OSX. - On X11, dropping a `Window` actually closes it now, and clicking the window's × button (or otherwise having the WM signal to close it) will result in the window closing. - Added `WindowBuilderExt` methods for macos: `with_titlebar_transparent`, diff --git a/examples/min_max_size.rs b/examples/min_max_size.rs index 7500e893..342be20b 100644 --- a/examples/min_max_size.rs +++ b/examples/min_max_size.rs @@ -3,12 +3,13 @@ extern crate winit; fn main() { let mut events_loop = winit::EventsLoop::new(); - let _window = winit::WindowBuilder::new() - .with_min_dimensions(400, 200) - .with_max_dimensions(800, 400) + let window = winit::WindowBuilder::new() .build(&events_loop) .unwrap(); + window.set_min_dimensions(Some((400, 200))); + window.set_max_dimensions(Some((800, 400))); + events_loop.run_forever(|event| { println!("{:?}", event); diff --git a/src/platform/android/mod.rs b/src/platform/android/mod.rs index c9861c24..52ef3903 100644 --- a/src/platform/android/mod.rs +++ b/src/platform/android/mod.rs @@ -247,6 +247,12 @@ impl Window { pub fn set_position(&self, _x: i32, _y: i32) { } + #[inline] + pub fn set_min_dimensions(&self, _dimensions: Option<(u32, u32)>) { } + + #[inline] + pub fn set_max_dimensions(&self, _dimensions: Option<(u32, u32)>) { } + #[inline] pub fn get_inner_size(&self) -> Option<(u32, u32)> { if self.native_window.is_null() { diff --git a/src/platform/emscripten/mod.rs b/src/platform/emscripten/mod.rs index b879bf50..90591957 100644 --- a/src/platform/emscripten/mod.rs +++ b/src/platform/emscripten/mod.rs @@ -452,6 +452,12 @@ impl Window { } } + #[inline] + pub fn set_min_dimensions(&self, _dimensions: Option<(u32, u32)>) { } + + #[inline] + pub fn set_max_dimensions(&self, _dimensions: Option<(u32, u32)>) { } + #[inline] pub fn show(&self) {} #[inline] diff --git a/src/platform/ios/mod.rs b/src/platform/ios/mod.rs index 6fbaff25..c83a1fc0 100644 --- a/src/platform/ios/mod.rs +++ b/src/platform/ios/mod.rs @@ -306,6 +306,12 @@ impl Window { pub fn set_inner_size(&self, _x: u32, _y: u32) { } + #[inline] + pub fn set_min_dimensions(&self, _dimensions: Option<(u32, u32)>) { } + + #[inline] + pub fn set_max_dimensions(&self, _dimensions: Option<(u32, u32)>) { } + #[inline] pub fn platform_display(&self) -> *mut libc::c_void { unimplemented!(); diff --git a/src/platform/linux/mod.rs b/src/platform/linux/mod.rs index a84f1364..27d9f064 100644 --- a/src/platform/linux/mod.rs +++ b/src/platform/linux/mod.rs @@ -194,6 +194,22 @@ impl Window { } } + #[inline] + pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) { + match self { + &Window::X(ref w) => w.set_min_dimensions(dimensions), + &Window::Wayland(ref w) => w.set_min_dimensions(dimensions) + } + } + + #[inline] + pub fn set_max_dimensions(&self, dimensions: Option<(u32, u32)>) { + match self { + &Window::X(ref w) => w.set_max_dimensions(dimensions), + &Window::Wayland(ref w) => w.set_max_dimensions(dimensions) + } + } + #[inline] pub fn set_cursor(&self, cursor: MouseCursor) { match self { diff --git a/src/platform/linux/wayland/window.rs b/src/platform/linux/wayland/window.rs index fce940e0..6cfa10d3 100644 --- a/src/platform/linux/wayland/window.rs +++ b/src/platform/linux/wayland/window.rs @@ -136,6 +136,16 @@ impl Window { *(self.size.lock().unwrap()) = (x, y); } + #[inline] + pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) { + self.frame.lock().unwrap().set_min_size(dimensions.map(|(w, h)| (w as i32, h as i32))); + } + + #[inline] + pub fn set_max_dimensions(&self, dimensions: Option<(u32, u32)>) { + self.frame.lock().unwrap().set_max_size(dimensions.map(|(w, h)| (w as i32, h as i32))); + } + #[inline] pub fn set_cursor(&self, _cursor: MouseCursor) { // TODO @@ -190,11 +200,11 @@ impl Window { // TODO: not yet possible on wayland Err(()) } - + pub fn get_display(&self) -> &wl_display::WlDisplay { &*self.display } - + pub fn get_surface(&self) -> &wl_surface::WlSurface { &self.surface } diff --git a/src/platform/linux/x11/util.rs b/src/platform/linux/x11/util.rs index 4f9ecd3e..37e07db9 100644 --- a/src/platform/linux/x11/util.rs +++ b/src/platform/linux/x11/util.rs @@ -1,11 +1,54 @@ use std::mem; use std::ptr; use std::sync::Arc; +use std::ops::{Deref, DerefMut}; use std::os::raw::{c_char, c_double, c_int, c_long, c_short, c_uchar, c_uint, c_ulong}; use super::{ffi, XConnection, XError}; use events::ModifiersState; +pub struct XSmartPointer<'a, T> { + xconn: &'a Arc, + pub ptr: *mut T, +} + +impl<'a, T> XSmartPointer<'a, T> { + // You're responsible for only passing things to this that should be XFree'd. + // Returns None if ptr is null. + pub fn new(xconn: &'a Arc, ptr: *mut T) -> Option { + if !ptr.is_null() { + Some(XSmartPointer { + xconn, + ptr, + }) + } else { + None + } + } +} + +impl<'a, T> Deref for XSmartPointer<'a, T> { + type Target = T; + + fn deref(&self) -> &T { + unsafe { &*self.ptr } + } +} + +impl<'a, T> DerefMut for XSmartPointer<'a, T> { + fn deref_mut(&mut self) -> &mut T { + unsafe { &mut *self.ptr } + } +} + +impl<'a, T> Drop for XSmartPointer<'a, T> { + fn drop(&mut self) { + unsafe { + (self.xconn.xlib.XFree)(self.ptr as *mut _); + } + } +} + pub unsafe fn get_atom(xconn: &Arc, name: &[u8]) -> Result { let atom_name: *const c_char = name.as_ptr() as _; let atom = (xconn.xlib.XInternAtom)(xconn.display, atom_name, ffi::False); diff --git a/src/platform/linux/x11/window.rs b/src/platform/linux/x11/window.rs index 14e16145..a42f0888 100644 --- a/src/platform/linux/x11/window.rs +++ b/src/platform/linux/x11/window.rs @@ -19,7 +19,7 @@ use window::MonitorId as RootMonitorId; use platform::x11::monitor::get_available_monitors; -use super::{ffi, util, XConnection, WindowId, EventsLoop}; +use super::{ffi, util, XConnection, XError, WindowId, EventsLoop}; // TODO: remove me fn with_c_str(s: &str, f: F) -> T where F: FnOnce(*const libc::c_char) -> T { @@ -230,23 +230,33 @@ impl Window2 { } // set size hints - let mut size_hints: ffi::XSizeHints = unsafe { mem::zeroed() }; - size_hints.flags = ffi::PSize; - size_hints.width = dimensions.0 as i32; - size_hints.height = dimensions.1 as i32; - if let Some(dimensions) = window_attrs.min_dimensions { - size_hints.flags |= ffi::PMinSize; - size_hints.min_width = dimensions.0 as i32; - size_hints.min_height = dimensions.1 as i32; - } - if let Some(dimensions) = window_attrs.max_dimensions { - size_hints.flags |= ffi::PMaxSize; - size_hints.max_width = dimensions.0 as i32; - size_hints.max_height = dimensions.1 as i32; - } - unsafe { - (display.xlib.XSetNormalHints)(display.display, x_window.window, &mut size_hints); - display.check_errors().expect("Failed to call XSetNormalHints"); + { + let mut size_hints = { + let size_hints = unsafe { (display.xlib.XAllocSizeHints)() }; + util::XSmartPointer::new(&display, size_hints) + .expect("XAllocSizeHints returned null; out of memory") + }; + (*size_hints).flags = ffi::PSize; + (*size_hints).width = dimensions.0 as c_int; + (*size_hints).height = dimensions.1 as c_int; + if let Some(dimensions) = window_attrs.min_dimensions { + (*size_hints).flags |= ffi::PMinSize; + (*size_hints).min_width = dimensions.0 as c_int; + (*size_hints).min_height = dimensions.1 as c_int; + } + if let Some(dimensions) = window_attrs.max_dimensions { + (*size_hints).flags |= ffi::PMaxSize; + (*size_hints).max_width = dimensions.0 as c_int; + (*size_hints).max_height = dimensions.1 as c_int; + } + unsafe { + (display.xlib.XSetWMNormalHints)( + display.display, + x_window.window, + size_hints.ptr, + ); + } + display.check_errors().expect("Failed to call XSetWMNormalHints"); } // Opt into handling window close @@ -603,6 +613,67 @@ impl Window2 { self.x.display.check_errors().expect("Failed to call XResizeWindow"); } + unsafe fn update_normal_hints(&self, callback: F) -> Result<(), XError> + where F: FnOnce(*mut ffi::XSizeHints) -> () + { + let xconn = &self.x.display; + + let size_hints = { + let size_hints = (xconn.xlib.XAllocSizeHints)(); + util::XSmartPointer::new(&xconn, size_hints) + .expect("XAllocSizeHints returned null; out of memory") + }; + + let mut flags: c_long = mem::uninitialized(); + + (xconn.xlib.XGetWMNormalHints)( + xconn.display, + self.x.window, + size_hints.ptr, + &mut flags, + ); + xconn.check_errors()?; + + callback(size_hints.ptr); + + (xconn.xlib.XSetWMNormalHints)( + xconn.display, + self.x.window, + size_hints.ptr, + ); + xconn.check_errors()?; + + Ok(()) + } + + pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) { + unsafe { + self.update_normal_hints(|size_hints| { + if let Some((width, height)) = dimensions { + (*size_hints).flags |= ffi::PMinSize; + (*size_hints).min_width = width as c_int; + (*size_hints).min_height = height as c_int; + } else { + (*size_hints).flags &= !ffi::PMinSize; + } + }) + }.expect("Failed to call XSetWMNormalHints"); + } + + pub fn set_max_dimensions(&self, dimensions: Option<(u32, u32)>) { + unsafe { + self.update_normal_hints(|size_hints| { + if let Some((width, height)) = dimensions { + (*size_hints).flags |= ffi::PMaxSize; + (*size_hints).max_width = width as c_int; + (*size_hints).max_height = height as c_int; + } else { + (*size_hints).flags &= !ffi::PMaxSize; + } + }) + }.expect("Failed to call XSetWMNormalHints"); + } + #[inline] pub fn get_xlib_display(&self) -> *mut c_void { self.x.display.display as _ diff --git a/src/platform/macos/window.rs b/src/platform/macos/window.rs index b5ba31ea..c2476744 100644 --- a/src/platform/macos/window.rs +++ b/src/platform/macos/window.rs @@ -571,6 +571,20 @@ impl Window2 { } } + pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) { + unsafe { + let (width, height) = dimensions.unwrap_or((0, 0)); + nswindow_set_min_dimensions(self.window.0, width.into(), height.into()); + } + } + + pub fn set_max_dimensions(&self, dimensions: Option<(u32, u32)>) { + unsafe { + let (width, height) = dimensions.unwrap_or((!0, !0)); + nswindow_set_max_dimensions(self.window.0, width.into(), height.into()); + } + } + #[inline] pub fn platform_display(&self) -> *mut libc::c_void { unimplemented!() diff --git a/src/platform/windows/window.rs b/src/platform/windows/window.rs index b7949115..764008f0 100644 --- a/src/platform/windows/window.rs +++ b/src/platform/windows/window.rs @@ -153,6 +153,52 @@ impl Window { } } + /// See the docs in the crate root file. + #[inline] + pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) { + let mut window_state = self.window_state.lock().unwrap(); + window_state.attributes.min_dimensions = dimensions; + + // Make windows re-check the window size bounds. + if let Some(inner_size) = self.get_inner_size() { + unsafe { + let mut rect = RECT { top: 0, left: 0, bottom: inner_size.1 as LONG, right: inner_size.0 as LONG }; + let dw_style = winuser::GetWindowLongA(self.window.0, winuser::GWL_STYLE) as DWORD; + let b_menu = !winuser::GetMenu(self.window.0).is_null() as BOOL; + let dw_style_ex = winuser::GetWindowLongA(self.window.0, winuser::GWL_EXSTYLE) as DWORD; + winuser::AdjustWindowRectEx(&mut rect, dw_style, b_menu, dw_style_ex); + let outer_x = (rect.right - rect.left).abs() as raw::c_int; + let outer_y = (rect.top - rect.bottom).abs() as raw::c_int; + + winuser::SetWindowPos(self.window.0, ptr::null_mut(), 0, 0, outer_x, outer_y, + winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER | winuser::SWP_NOREPOSITION | winuser::SWP_NOMOVE); + } + } + } + + /// See the docs in the crate root file. + #[inline] + pub fn set_max_dimensions(&self, dimensions: Option<(u32, u32)>) { + let mut window_state = self.window_state.lock().unwrap(); + window_state.attributes.max_dimensions = dimensions; + + // Make windows re-check the window size bounds. + if let Some(inner_size) = self.get_inner_size() { + unsafe { + let mut rect = RECT { top: 0, left: 0, bottom: inner_size.1 as LONG, right: inner_size.0 as LONG }; + let dw_style = winuser::GetWindowLongA(self.window.0, winuser::GWL_STYLE) as DWORD; + let b_menu = !winuser::GetMenu(self.window.0).is_null() as BOOL; + let dw_style_ex = winuser::GetWindowLongA(self.window.0, winuser::GWL_EXSTYLE) as DWORD; + winuser::AdjustWindowRectEx(&mut rect, dw_style, b_menu, dw_style_ex); + let outer_x = (rect.right - rect.left).abs() as raw::c_int; + let outer_y = (rect.top - rect.bottom).abs() as raw::c_int; + + winuser::SetWindowPos(self.window.0, ptr::null_mut(), 0, 0, outer_x, outer_y, + winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER | winuser::SWP_NOREPOSITION | winuser::SWP_NOMOVE); + } + } + } + // TODO: remove pub fn platform_display(&self) -> *mut ::libc::c_void { panic!() // Deprecated function ; we don't care anymore @@ -370,7 +416,17 @@ unsafe fn init(window: WindowAttributes, pl_attribs: PlatformSpecificWindowBuild // creating the real window this time, by using the functions in `extra_functions` let real_window = { let (width, height) = if fullscreen || window.dimensions.is_some() { - (Some(rect.right - rect.left), Some(rect.bottom - rect.top)) + let min_dimensions = window.min_dimensions + .map(|d| (d.0 as raw::c_int, d.1 as raw::c_int)) + .unwrap_or((0, 0)); + let max_dimensions = window.max_dimensions + .map(|d| (d.0 as raw::c_int, d.1 as raw::c_int)) + .unwrap_or((raw::c_int::max_value(), raw::c_int::max_value())); + + ( + Some((rect.right - rect.left).min(max_dimensions.0).max(min_dimensions.0)), + Some((rect.bottom - rect.top).min(max_dimensions.1).max(min_dimensions.1)) + ) } else { (None, None) }; diff --git a/src/window.rs b/src/window.rs index d57c6181..84ac6756 100644 --- a/src/window.rs +++ b/src/window.rs @@ -255,6 +255,22 @@ impl Window { self.window.set_inner_size(x, y) } + /// Sets a minimum dimension size for the window. + /// + /// Width and height are in pixels. + #[inline] + pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) { + self.window.set_min_dimensions(dimensions) + } + + /// Sets a maximum dimension size for the window. + /// + /// Width and height are in pixels. + #[inline] + pub fn set_max_dimensions(&self, dimensions: Option<(u32, u32)>) { + self.window.set_max_dimensions(dimensions) + } + /// DEPRECATED. Gets the native platform specific display for this window. /// This is typically only required when integrating with /// other libraries that need this information.