From be5a2b0e87c963aca609afd7b4c7e450b1204c09 Mon Sep 17 00:00:00 2001 From: Danny Fritz Date: Mon, 11 Jun 2018 16:47:50 -0600 Subject: [PATCH] Windows & X11: Window::set_resizable (#558) * Windows: Window::set_resizable * X11: Window::set_resizable * Code style regarding resizable * X11: set_resizable remember max/min window size * Stub out set_resizable on Android, iOS, and emscripten * remove comment block from docs * Windows: set_resizable in fullscreen * Special case Xfwm * Added fun provisos to docs --- CHANGELOG.md | 1 + examples/no_resize.rs | 22 ------------------ examples/resizable.rs | 38 ++++++++++++++++++++++++++++++++ src/lib.rs | 2 +- src/platform/android/mod.rs | 5 +++++ src/platform/emscripten/mod.rs | 5 +++++ src/platform/ios/mod.rs | 5 +++++ src/platform/linux/mod.rs | 8 +++++++ src/platform/linux/x11/window.rs | 35 ++++++++++++++++++++++++++++- src/platform/windows/window.rs | 38 +++++++++++++++++++++++++++++++- src/window.rs | 22 +++++++++++++++++- 11 files changed, 155 insertions(+), 26 deletions(-) delete mode 100644 examples/no_resize.rs create mode 100644 examples/resizable.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ba63570..52c39091 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - On X11, the `Moved` event is no longer sent when the window is resized without changing position. - `MouseCursor` and `CursorState` now implement `Default`. - `WindowBuilder::with_resizable` implemented for Windows, X11, and macOS. +- `Window::set_resizable` implemented for Windows, X11, and macOS. - On X11, if the monitor's width or height in millimeters is reported as 0, the DPI is now 1.0 instead of +inf. - On X11, the environment variable `WINIT_HIDPI_FACTOR` has been added for overriding DPI factor. - On X11, enabling transparency no longer causes the window contents to flicker when resizing. diff --git a/examples/no_resize.rs b/examples/no_resize.rs deleted file mode 100644 index 888c19bd..00000000 --- a/examples/no_resize.rs +++ /dev/null @@ -1,22 +0,0 @@ -extern crate winit; - -fn main() { - let mut events_loop = winit::EventsLoop::new(); - - let _window = winit::WindowBuilder::new() - .with_title("A non-resizable window!") - .with_dimensions(200, 200) - .with_resizable(false) - .build(&events_loop) - .unwrap(); - - events_loop.run_forever(|event| { - match event { - winit::Event::WindowEvent { - event: winit::WindowEvent::CloseRequested, - .. - } => winit::ControlFlow::Break, - _ => winit::ControlFlow::Continue, - } - }); -} diff --git a/examples/resizable.rs b/examples/resizable.rs new file mode 100644 index 00000000..f1557445 --- /dev/null +++ b/examples/resizable.rs @@ -0,0 +1,38 @@ +extern crate winit; + +fn main() { + let mut events_loop = winit::EventsLoop::new(); + + let mut resizable = false; + + let window = winit::WindowBuilder::new() + .with_title("Hit space to toggle resizability.") + .with_dimensions(400, 200) + .with_resizable(resizable) + .build(&events_loop) + .unwrap(); + + events_loop.run_forever(|event| { + match event { + winit::Event::WindowEvent { event, .. } => match event { + winit::WindowEvent::CloseRequested => return winit::ControlFlow::Break, + winit::WindowEvent::KeyboardInput { + input: + winit::KeyboardInput { + virtual_keycode: Some(winit::VirtualKeyCode::Space), + state: winit::ElementState::Released, + .. + }, + .. + } => { + resizable = !resizable; + println!("Resizable: {}", resizable); + window.set_resizable(resizable); + } + _ => (), + }, + _ => (), + }; + winit::ControlFlow::Continue + }); +} diff --git a/src/lib.rs b/src/lib.rs index dd1b8895..5a324333 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -422,7 +422,7 @@ pub struct WindowAttributes { /// The default is `None`. pub max_dimensions: Option<(u32, u32)>, - /// [Windows & X11 only] Whether the window is resizable or not + /// Whether the window is resizable or not. /// /// The default is `true`. pub resizable: bool, diff --git a/src/platform/android/mod.rs b/src/platform/android/mod.rs index 89ab7402..ad08e512 100644 --- a/src/platform/android/mod.rs +++ b/src/platform/android/mod.rs @@ -258,6 +258,11 @@ impl Window { #[inline] pub fn set_max_dimensions(&self, _dimensions: Option<(u32, u32)>) { } + #[inline] + pub fn set_resizable(&self, _resizable: bool) { + // N/A + } + #[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 b54ae1a2..9bd7f636 100644 --- a/src/platform/emscripten/mod.rs +++ b/src/platform/emscripten/mod.rs @@ -462,6 +462,11 @@ impl Window { #[inline] pub fn set_max_dimensions(&self, _dimensions: Option<(u32, u32)>) { } + + #[inline] + pub fn set_resizable(&self, _resizable: bool) { + // N/A + } #[inline] pub fn show(&self) {} diff --git a/src/platform/ios/mod.rs b/src/platform/ios/mod.rs index 5a3645c0..e1355851 100644 --- a/src/platform/ios/mod.rs +++ b/src/platform/ios/mod.rs @@ -317,6 +317,11 @@ impl Window { #[inline] pub fn set_max_dimensions(&self, _dimensions: Option<(u32, u32)>) { } + #[inline] + pub fn set_resizable(&self, _resizable: bool) { + // N/A + } + #[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 020420ad..686ec465 100644 --- a/src/platform/linux/mod.rs +++ b/src/platform/linux/mod.rs @@ -232,6 +232,14 @@ impl Window { &Window::Wayland(ref w) => w.set_max_dimensions(dimensions) } } + + #[inline] + pub fn set_resizable(&self, resizable: bool) { + match self { + &Window::X(ref w) => w.set_resizable(resizable), + &Window::Wayland(ref _w) => unimplemented!(), + } + } #[inline] pub fn set_cursor(&self, cursor: MouseCursor) { diff --git a/src/platform/linux/x11/window.rs b/src/platform/linux/x11/window.rs index 9f64f296..affa075f 100644 --- a/src/platform/linux/x11/window.rs +++ b/src/platform/linux/x11/window.rs @@ -38,6 +38,8 @@ pub struct SharedState { pub last_monitor: Option, pub dpi_adjusted: Option<(f64, f64)>, pub frame_extents: Option, + pub min_dimensions: Option<(u32, u32)>, + pub max_dimensions: Option<(u32, u32)>, } unsafe impl Send for UnownedWindow {} @@ -216,9 +218,11 @@ impl UnownedWindow { // set size hints { + (*window.shared_state.lock()).min_dimensions = window_attrs.min_dimensions; + (*window.shared_state.lock()).max_dimensions = window_attrs.max_dimensions; let mut min_dimensions = window_attrs.min_dimensions; let mut max_dimensions = window_attrs.max_dimensions; - if !window_attrs.resizable { + if !window_attrs.resizable && !util::wm_name_is_one_of(&["Xfwm4"]) { max_dimensions = Some(dimensions); min_dimensions = Some(dimensions); } @@ -699,6 +703,7 @@ impl UnownedWindow { } pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) { + (*self.shared_state.lock()).min_dimensions = dimensions; unsafe { self.update_normal_hints(|size_hints| { if let Some((width, height)) = dimensions { @@ -713,6 +718,7 @@ impl UnownedWindow { } pub fn set_max_dimensions(&self, dimensions: Option<(u32, u32)>) { + (*self.shared_state.lock()).max_dimensions = dimensions; unsafe { self.update_normal_hints(|size_hints| { if let Some((width, height)) = dimensions { @@ -726,6 +732,33 @@ impl UnownedWindow { }.expect("Failed to call XSetWMNormalHints"); } + pub fn set_resizable(&self, resizable: bool) { + if util::wm_name_is_one_of(&["Xfwm4"]) { + // Making the window unresizable on Xfwm prevents further changes to `WM_NORMAL_HINTS` from being detected. + // This makes it impossible for resizing to be re-enabled, and also breaks DPI scaling. As such, we choose + // the lesser of two evils and do nothing. + return; + } + if resizable { + let min_dimensions = (*self.shared_state.lock()).min_dimensions; + let max_dimensions = (*self.shared_state.lock()).max_dimensions; + self.set_min_dimensions(min_dimensions); + self.set_max_dimensions(max_dimensions); + } else { + unsafe { + self.update_normal_hints(|size_hints| { + (*size_hints).flags |= ffi::PMinSize | ffi::PMaxSize; + if let Some((width, height)) = self.get_inner_size() { + (*size_hints).min_width = width as c_int; + (*size_hints).min_height = height as c_int; + (*size_hints).max_width = width as c_int; + (*size_hints).max_height = height as c_int; + } + }) + }.expect("Failed to call XSetWMNormalHints"); + } + } + #[inline] pub fn get_xlib_display(&self) -> *mut c_void { self.xconn.display as _ diff --git a/src/platform/windows/window.rs b/src/platform/windows/window.rs index 5c8987a2..926118c6 100644 --- a/src/platform/windows/window.rs +++ b/src/platform/windows/window.rs @@ -239,6 +239,36 @@ impl Window { } } } + + #[inline] + pub fn set_resizable(&self, resizable: bool) { + if let Ok(mut window_state) = self.window_state.lock() { + if window_state.attributes.resizable == resizable { + return; + } + if window_state.attributes.fullscreen.is_some() { + window_state.attributes.resizable = resizable; + return; + } + let window = self.window.clone(); + let mut style = unsafe { + winuser::GetWindowLongW(self.window.0, winuser::GWL_STYLE) + }; + if resizable { + style |= winuser::WS_SIZEBOX as LONG; + } else { + style &= !winuser::WS_SIZEBOX as LONG; + } + unsafe { + winuser::SetWindowLongW( + window.0, + winuser::GWL_STYLE, + style as _, + ); + }; + window_state.attributes.resizable = resizable; + } + } // TODO: remove pub fn platform_display(&self) -> *mut ::libc::c_void { @@ -478,14 +508,20 @@ impl Window { let rect = saved_window_info.rect.clone(); let window = self.window.clone(); - let (style, ex_style) = (saved_window_info.style, saved_window_info.ex_style); + let (mut style, ex_style) = (saved_window_info.style, saved_window_info.ex_style); let maximized = window_state.attributes.maximized; + let resizable = window_state.attributes.resizable; // On restore, resize to the previous saved rect size. // And because SetWindowPos will resize the window // We call it in the main thread self.events_loop_proxy.execute_in_thread(move |_| { + if resizable { + style |= winuser::WS_SIZEBOX as LONG; + } else { + style &= !winuser::WS_SIZEBOX as LONG; + } winuser::SetWindowLongW(window.0, winuser::GWL_STYLE, style); winuser::SetWindowLongW(window.0, winuser::GWL_EXSTYLE, ex_style); diff --git a/src/window.rs b/src/window.rs index ebf025c0..814fdeba 100644 --- a/src/window.rs +++ b/src/window.rs @@ -51,9 +51,14 @@ impl WindowBuilder { /// Sets whether the window is resizable or not /// + /// Note that making the window unresizable doesn't exempt you from handling `Resized`, as that event can still be + /// triggered by DPI scaling, entering fullscreen mode, etc. + /// /// ## Platform-specific /// - /// This only has an effect on Windows, X11, and macOS. + /// This only has an effect on desktop platforms. + /// + /// Due to a bug in XFCE, this has no effect on Xfwm. #[inline] pub fn with_resizable(mut self, resizable: bool) -> WindowBuilder { self.window.resizable = resizable; @@ -317,6 +322,21 @@ impl Window { self.window.set_max_dimensions(dimensions) } + /// Sets whether the window is resizable or not. + /// + /// Note that making the window unresizable doesn't exempt you from handling `Resized`, as that event can still be + /// triggered by DPI scaling, entering fullscreen mode, etc. + /// + /// ## Platform-specific + /// + /// This only has an effect on desktop platforms. + /// + /// Due to a bug in XFCE, this has no effect on Xfwm. + #[inline] + pub fn set_resizable(&self, resizable: bool) { + self.window.set_resizable(resizable) + } + /// DEPRECATED. Gets the native platform specific display for this window. /// This is typically only required when integrating with /// other libraries that need this information.