diff --git a/CHANGELOG.md b/CHANGELOG.md index 5aa8af25..b1bd2d66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On all platforms except mobile and WASM, implement `Window::set_minimized`. - On X11, fix `CursorEntered` event being generated for non-winit windows. - On macOS, fix crash when starting maximized without decorations. - On macOS, fix application not to terminate on `run_return`. @@ -50,6 +51,7 @@ - On X11, return dummy monitor data to avoid panicking when no monitors exist. - On X11, prevent stealing input focus when creating a new window. Only steal input focus when entering fullscreen mode. +- On Wayland, fixed DeviceEvents for relative mouse movement is not always produced - On Wayland, add support for set_cursor_visible and set_cursor_grab. - On Wayland, fixed DeviceEvents for relative mouse movement is not always produced. - Removed `derivative` crate dependency. diff --git a/FEATURES.md b/FEATURES.md index b83caac0..7a64fcb4 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -80,6 +80,7 @@ If your PR makes notable changes to Winit's features, please update this section - **Window maximization**: The windows created by winit can be maximized upon creation. - **Window maximization toggle**: The windows created by winit can be maximized and unmaximized after creation. +- **Window minimization**: The windows created by winit can be minimized after creation. - **Fullscreen**: The windows created by winit can be put into fullscreen mode. - **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after creation. @@ -173,6 +174,7 @@ Legend: |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**| +|Window minimization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**| |Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ | |Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ | |Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**| diff --git a/examples/minimize.rs b/examples/minimize.rs new file mode 100644 index 00000000..5576f53c --- /dev/null +++ b/examples/minimize.rs @@ -0,0 +1,35 @@ +extern crate winit; + +use winit::event::{Event, VirtualKeyCode, WindowEvent}; +use winit::event_loop::{ControlFlow, EventLoop}; +use winit::window::WindowBuilder; + +fn main() { + let event_loop = EventLoop::new(); + + let window = WindowBuilder::new() + .with_title("A fantastic window!") + .build(&event_loop) + .unwrap(); + + event_loop.run(move |event, _, control_flow| match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + + // Keyboard input event to handle minimize via a hotkey + Event::WindowEvent { + event: WindowEvent::KeyboardInput { input, .. }, + window_id, + } => { + if window_id == window.id() { + // Pressing the 'M' key will minimize the window + if input.virtual_keycode == Some(VirtualKeyCode::M) { + window.set_minimized(true); + } + } + } + _ => *control_flow = ControlFlow::Wait, + }); +} diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 5fe4398f..4304e254 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -360,6 +360,11 @@ impl Window { Err(ExternalError::NotSupported(NotSupportedError::new())) } + #[inline] + pub fn set_minimized(&self, _minimized: bool) { + unimplemented!() + } + #[inline] pub fn set_maximized(&self, _maximized: bool) { // N/A diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index eea0c304..d1ad03f5 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -169,6 +169,10 @@ impl Inner { debug!("`Window::set_cursor_visible` is ignored on iOS") } + pub fn set_minimized(&self, _minimized: bool) { + warn!("`Window::set_minimized` is ignored on iOS") + } + pub fn set_maximized(&self, _maximized: bool) { warn!("`Window::set_maximized` is ignored on iOS") } diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index e30ea88d..616c02fe 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -367,6 +367,14 @@ impl Window { } } + #[inline] + pub fn set_minimized(&self, minimized: bool) { + match self { + &Window::X(ref w) => w.set_minimized(minimized), + &Window::Wayland(ref w) => w.set_minimized(minimized), + } + } + #[inline] pub fn fullscreen(&self) -> Option { match self { diff --git a/src/platform_impl/linux/wayland/window.rs b/src/platform_impl/linux/wayland/window.rs index 609430a4..7faed502 100644 --- a/src/platform_impl/linux/wayland/window.rs +++ b/src/platform_impl/linux/wayland/window.rs @@ -259,6 +259,13 @@ impl Window { *(self.need_frame_refresh.lock().unwrap()) = true; } + pub fn set_minimized(&self, minimized: bool) { + // An app cannot un-minimize itself on Wayland + if minimized { + self.frame.lock().unwrap().set_minimized(); + } + } + pub fn set_maximized(&self, maximized: bool) { if maximized { self.frame.lock().unwrap().set_maximized(); diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 5829cc0e..fc12ba0a 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -749,6 +749,35 @@ impl UnownedWindow { self.xconn.primary_monitor() } + fn set_minimized_inner(&self, minimized: bool) -> util::Flusher<'_> { + unsafe { + if minimized { + let screen = (self.xconn.xlib.XDefaultScreen)(self.xconn.display); + + (self.xconn.xlib.XIconifyWindow)(self.xconn.display, self.xwindow, screen); + + util::Flusher::new(&self.xconn) + } else { + let atom = self.xconn.get_atom_unchecked(b"_NET_ACTIVE_WINDOW\0"); + + self.xconn.send_client_msg( + self.xwindow, + self.root, + atom, + Some(ffi::SubstructureRedirectMask | ffi::SubstructureNotifyMask), + [1, ffi::CurrentTime as c_long, 0, 0, 0], + ) + } + } + } + + #[inline] + pub fn set_minimized(&self, minimized: bool) { + self.set_minimized_inner(minimized) + .flush() + .expect("Failed to change window minimization"); + } + fn set_maximized_inner(&self, maximized: bool) -> util::Flusher<'_> { let horz_atom = unsafe { self.xconn diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index ab19fe7a..404eafa5 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -617,6 +617,25 @@ impl UnownedWindow { self.set_maximized(maximized); } + #[inline] + pub fn set_minimized(&self, minimized: bool) { + let is_minimized: BOOL = unsafe { msg_send![*self.ns_window, isMiniaturized] }; + let is_minimized: bool = is_minimized == YES; + if is_minimized == minimized { + return; + } + + if minimized { + unsafe { + NSWindow::miniaturize_(*self.ns_window, *self.ns_window); + } + } else { + unsafe { + NSWindow::deminiaturize_(*self.ns_window, *self.ns_window); + } + } + } + #[inline] pub fn set_maximized(&self, maximized: bool) { let is_zoomed = self.is_zoomed(); diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 8752d8b6..c14df77f 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -199,6 +199,11 @@ impl Window { } } + #[inline] + pub fn set_minimized(&self, _minimized: bool) { + // Intentionally a no-op, as canvases cannot be 'minimized' + } + #[inline] pub fn set_maximized(&self, _maximized: bool) { // Intentionally a no-op, as canvases cannot be 'maximized' diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index c1b1a983..183879b2 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1145,7 +1145,18 @@ unsafe extern "system" fn public_window_callback( 0 } + // this is necessary for us to maintain minimize/restore state winuser::WM_SYSCOMMAND => { + if wparam == winuser::SC_RESTORE { + let mut w = subclass_input.window_state.lock(); + w.set_window_flags_in_place(|f| f.set(WindowFlags::MINIMIZED, false)); + } + if wparam == winuser::SC_MINIMIZE { + let mut w = subclass_input.window_state.lock(); + w.set_window_flags_in_place(|f| f.set(WindowFlags::MINIMIZED, true)); + } + // Send `WindowEvent::Minimized` here if we decide to implement one + if wparam == winuser::SC_SCREENSAVE { let window_state = subclass_input.window_state.lock(); if window_state.fullscreen.is_some() { diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 783ad5e7..f647a88c 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -452,6 +452,18 @@ impl Window { WindowId(self.window.0) } + #[inline] + pub fn set_minimized(&self, minimized: bool) { + let window = self.window.clone(); + let window_state = Arc::clone(&self.window_state); + + self.thread_executor.execute_in_thread(move || { + WindowState::set_window_flags(window_state.lock(), window.0, |f| { + f.set(WindowFlags::MINIMIZED, minimized) + }); + }); + } + #[inline] pub fn set_maximized(&self, maximized: bool) { let window = self.window.clone(); diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index be98bcc9..cee80263 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -79,6 +79,8 @@ bitflags! { /// window's state to match our stored state. This controls whether to accept those changes. const MARKER_RETAIN_STATE_ON_SIZE = 1 << 10; + const MINIMIZED = 1 << 11; + const FULLSCREEN_AND_MASK = !( WindowFlags::DECORATIONS.bits | WindowFlags::RESIZABLE.bits | @@ -212,6 +214,9 @@ impl WindowFlags { if self.contains(WindowFlags::CHILD) { style |= WS_CHILD; // This is incompatible with WS_POPUP if that gets added eventually. } + if self.contains(WindowFlags::MINIMIZED) { + style |= WS_MINIMIZE; + } if self.contains(WindowFlags::MAXIMIZED) { style |= WS_MAXIMIZE; } @@ -276,14 +281,30 @@ impl WindowFlags { } } + // Minimize operations should execute after maximize for proper window animations + if diff.contains(WindowFlags::MINIMIZED) { + unsafe { + winuser::ShowWindow( + window, + match new.contains(WindowFlags::MINIMIZED) { + true => winuser::SW_MINIMIZE, + false => winuser::SW_RESTORE, + }, + ); + } + } + if diff != WindowFlags::empty() { let (style, style_ex) = new.to_window_styles(); unsafe { winuser::SendMessageW(window, *event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 1, 0); - winuser::SetWindowLongW(window, winuser::GWL_STYLE, style as _); - winuser::SetWindowLongW(window, winuser::GWL_EXSTYLE, style_ex as _); + // This condition is necessary to avoid having an unrestorable window + if !new.contains(WindowFlags::MINIMIZED) { + winuser::SetWindowLongW(window, winuser::GWL_STYLE, style as _); + winuser::SetWindowLongW(window, winuser::GWL_EXSTYLE, style_ex as _); + } let mut flags = winuser::SWP_NOZORDER | winuser::SWP_NOMOVE diff --git a/src/window.rs b/src/window.rs index fedd3d0a..a6b25ad3 100644 --- a/src/window.rs +++ b/src/window.rs @@ -572,6 +572,16 @@ impl Window { self.window.set_resizable(resizable) } + /// Sets the window to minimized or back + /// + /// ## Platform-specific + /// + /// - **iOS:** Has no effect + #[inline] + pub fn set_minimized(&self, minimized: bool) { + self.window.set_minimized(minimized); + } + /// Sets the window to maximized or back. /// /// ## Platform-specific