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
This commit is contained in:
Osspial 2018-03-23 05:35:35 -04:00 committed by Pierre Krieger
parent d667a395b6
commit bbcd3019e8
12 changed files with 270 additions and 24 deletions

View file

@ -1,4 +1,5 @@
# Unreleased # 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. - 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`, - Added `WindowBuilderExt` methods for macos: `with_titlebar_transparent`,

View file

@ -3,12 +3,13 @@ extern crate winit;
fn main() { fn main() {
let mut events_loop = winit::EventsLoop::new(); let mut events_loop = winit::EventsLoop::new();
let _window = winit::WindowBuilder::new() let window = winit::WindowBuilder::new()
.with_min_dimensions(400, 200)
.with_max_dimensions(800, 400)
.build(&events_loop) .build(&events_loop)
.unwrap(); .unwrap();
window.set_min_dimensions(Some((400, 200)));
window.set_max_dimensions(Some((800, 400)));
events_loop.run_forever(|event| { events_loop.run_forever(|event| {
println!("{:?}", event); println!("{:?}", event);

View file

@ -247,6 +247,12 @@ impl Window {
pub fn set_position(&self, _x: i32, _y: i32) { 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] #[inline]
pub fn get_inner_size(&self) -> Option<(u32, u32)> { pub fn get_inner_size(&self) -> Option<(u32, u32)> {
if self.native_window.is_null() { if self.native_window.is_null() {

View file

@ -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] #[inline]
pub fn show(&self) {} pub fn show(&self) {}
#[inline] #[inline]

View file

@ -306,6 +306,12 @@ impl Window {
pub fn set_inner_size(&self, _x: u32, _y: u32) { 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] #[inline]
pub fn platform_display(&self) -> *mut libc::c_void { pub fn platform_display(&self) -> *mut libc::c_void {
unimplemented!(); unimplemented!();

View file

@ -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] #[inline]
pub fn set_cursor(&self, cursor: MouseCursor) { pub fn set_cursor(&self, cursor: MouseCursor) {
match self { match self {

View file

@ -136,6 +136,16 @@ impl Window {
*(self.size.lock().unwrap()) = (x, y); *(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] #[inline]
pub fn set_cursor(&self, _cursor: MouseCursor) { pub fn set_cursor(&self, _cursor: MouseCursor) {
// TODO // TODO

View file

@ -1,11 +1,54 @@
use std::mem; use std::mem;
use std::ptr; use std::ptr;
use std::sync::Arc; 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 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 super::{ffi, XConnection, XError};
use events::ModifiersState; use events::ModifiersState;
pub struct XSmartPointer<'a, T> {
xconn: &'a Arc<XConnection>,
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<XConnection>, ptr: *mut T) -> Option<Self> {
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<XConnection>, name: &[u8]) -> Result<ffi::Atom, XError> { pub unsafe fn get_atom(xconn: &Arc<XConnection>, name: &[u8]) -> Result<ffi::Atom, XError> {
let atom_name: *const c_char = name.as_ptr() as _; let atom_name: *const c_char = name.as_ptr() as _;
let atom = (xconn.xlib.XInternAtom)(xconn.display, atom_name, ffi::False); let atom = (xconn.xlib.XInternAtom)(xconn.display, atom_name, ffi::False);

View file

@ -19,7 +19,7 @@ use window::MonitorId as RootMonitorId;
use platform::x11::monitor::get_available_monitors; 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 // TODO: remove me
fn with_c_str<F, T>(s: &str, f: F) -> T where F: FnOnce(*const libc::c_char) -> T { fn with_c_str<F, T>(s: &str, f: F) -> T where F: FnOnce(*const libc::c_char) -> T {
@ -230,23 +230,33 @@ impl Window2 {
} }
// set size hints // set size hints
let mut size_hints: ffi::XSizeHints = unsafe { mem::zeroed() }; {
size_hints.flags = ffi::PSize; let mut size_hints = {
size_hints.width = dimensions.0 as i32; let size_hints = unsafe { (display.xlib.XAllocSizeHints)() };
size_hints.height = dimensions.1 as i32; util::XSmartPointer::new(&display, size_hints)
if let Some(dimensions) = window_attrs.min_dimensions { .expect("XAllocSizeHints returned null; out of memory")
size_hints.flags |= ffi::PMinSize; };
size_hints.min_width = dimensions.0 as i32; (*size_hints).flags = ffi::PSize;
size_hints.min_height = dimensions.1 as i32; (*size_hints).width = dimensions.0 as c_int;
} (*size_hints).height = dimensions.1 as c_int;
if let Some(dimensions) = window_attrs.max_dimensions { if let Some(dimensions) = window_attrs.min_dimensions {
size_hints.flags |= ffi::PMaxSize; (*size_hints).flags |= ffi::PMinSize;
size_hints.max_width = dimensions.0 as i32; (*size_hints).min_width = dimensions.0 as c_int;
size_hints.max_height = dimensions.1 as i32; (*size_hints).min_height = dimensions.1 as c_int;
} }
unsafe { if let Some(dimensions) = window_attrs.max_dimensions {
(display.xlib.XSetNormalHints)(display.display, x_window.window, &mut size_hints); (*size_hints).flags |= ffi::PMaxSize;
display.check_errors().expect("Failed to call XSetNormalHints"); (*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 // Opt into handling window close
@ -603,6 +613,67 @@ impl Window2 {
self.x.display.check_errors().expect("Failed to call XResizeWindow"); self.x.display.check_errors().expect("Failed to call XResizeWindow");
} }
unsafe fn update_normal_hints<F>(&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] #[inline]
pub fn get_xlib_display(&self) -> *mut c_void { pub fn get_xlib_display(&self) -> *mut c_void {
self.x.display.display as _ self.x.display.display as _

View file

@ -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] #[inline]
pub fn platform_display(&self) -> *mut libc::c_void { pub fn platform_display(&self) -> *mut libc::c_void {
unimplemented!() unimplemented!()

View file

@ -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 // TODO: remove
pub fn platform_display(&self) -> *mut ::libc::c_void { pub fn platform_display(&self) -> *mut ::libc::c_void {
panic!() // Deprecated function ; we don't care anymore 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` // creating the real window this time, by using the functions in `extra_functions`
let real_window = { let real_window = {
let (width, height) = if fullscreen || window.dimensions.is_some() { 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 { } else {
(None, None) (None, None)
}; };

View file

@ -255,6 +255,22 @@ impl Window {
self.window.set_inner_size(x, y) 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. /// DEPRECATED. Gets the native platform specific display for this window.
/// This is typically only required when integrating with /// This is typically only required when integrating with
/// other libraries that need this information. /// other libraries that need this information.