diff --git a/CHANGELOG.md b/CHANGELOG.md index b28a464e..228c399d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ And please only add new entries to the top of this list, right below the `# Unre - On Android, fix `DeviceId` to contain device id's. - Add `Window::set_blur` to request a blur behind the window; implemented on Wayland for now. - On Web, fix `ControlFlow::WaitUntil` to never wake up **before** the given time. +- Add `Window::show_window_menu` to request a titlebar/system menu; implemented on Windows for now. # 0.29.1-beta diff --git a/examples/drag_window.rs b/examples/drag_window.rs index 49373298..2d13ce43 100644 --- a/examples/drag_window.rs +++ b/examples/drag_window.rs @@ -20,6 +20,7 @@ fn main() -> Result<(), impl std::error::Error> { let mut switched = false; let mut entered_id = window_2.id(); + let mut cursor_location = None; event_loop.run(move |event, elwt| match event { Event::NewEvents(StartCause::Init) => { @@ -27,11 +28,8 @@ fn main() -> Result<(), impl std::error::Error> { } Event::WindowEvent { event, window_id } => match event { WindowEvent::CloseRequested => elwt.exit(), - WindowEvent::MouseInput { - state: ElementState::Pressed, - button: MouseButton::Left, - .. - } => { + WindowEvent::CursorMoved { position, .. } => cursor_location = Some(position), + WindowEvent::MouseInput { state, button, .. } => { let window = if (window_id == window_1.id() && switched) || (window_id == window_2.id() && !switched) { @@ -40,7 +38,15 @@ fn main() -> Result<(), impl std::error::Error> { &window_1 }; - window.drag_window().unwrap() + match (button, state) { + (MouseButton::Left, ElementState::Pressed) => window.drag_window().unwrap(), + (MouseButton::Right, ElementState::Released) => { + if let Some(position) = cursor_location { + window.show_window_menu(position); + } + } + _ => (), + } } WindowEvent::CursorEntered { .. } => { entered_id = window_id; @@ -54,11 +60,25 @@ fn main() -> Result<(), impl std::error::Error> { .. }, .. - } if c == "x" => { - switched = !switched; - name_windows(entered_id, switched, &window_1, &window_2); - println!("Switched!") - } + } => match c.as_str() { + "x" => { + switched = !switched; + name_windows(entered_id, switched, &window_1, &window_2); + println!("Switched!") + } + "d" => { + let window = if (window_id == window_1.id() && switched) + || (window_id == window_2.id() && !switched) + { + &window_2 + } else { + &window_1 + }; + + window.set_decorations(!window.is_decorated()); + } + _ => (), + }, WindowEvent::RedrawRequested => { if window_id == window_1.id() { fill::fill_window(&window_1); diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index aa9ad2d5..62b9bf52 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -929,6 +929,9 @@ impl Window { )) } + #[inline] + pub fn show_window_menu(&self, _position: Position) {} + pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), error::ExternalError> { Err(error::ExternalError::NotSupported( error::NotSupportedError::new(), diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 667e0172..46eb6435 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -198,6 +198,9 @@ impl Inner { Err(ExternalError::NotSupported(NotSupportedError::new())) } + #[inline] + pub fn show_window_menu(&self, _position: Position) {} + pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> { Err(ExternalError::NotSupported(NotSupportedError::new())) } diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index e8a7586f..8fb14856 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -444,6 +444,9 @@ impl Window { x11_or_wayland!(match self; Window(window) => window.drag_resize_window(direction)) } + #[inline] + pub fn show_window_menu(&self, _position: Position) {} + #[inline] pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { x11_or_wayland!(match self; Window(w) => w.set_cursor_hittest(hittest)) diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 35ce8b52..0d58a55f 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -893,6 +893,9 @@ impl WinitWindow { Err(ExternalError::NotSupported(NotSupportedError::new())) } + #[inline] + pub fn show_window_menu(&self, _position: Position) {} + #[inline] pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { self.setIgnoresMouseEvents(!hittest); diff --git a/src/platform_impl/orbital/window.rs b/src/platform_impl/orbital/window.rs index 1f0f633b..f08df9c8 100644 --- a/src/platform_impl/orbital/window.rs +++ b/src/platform_impl/orbital/window.rs @@ -390,6 +390,9 @@ impl Window { )) } + #[inline] + pub fn show_window_menu(&self, _position: Position) {} + #[inline] pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), error::ExternalError> { Err(error::ExternalError::NotSupported( diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 3cbb9bb4..2285d9f7 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -244,6 +244,9 @@ impl Inner { Err(ExternalError::NotSupported(NotSupportedError::new())) } + #[inline] + pub fn show_window_menu(&self, _position: Position) {} + #[inline] pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> { Err(ExternalError::NotSupported(NotSupportedError::new())) diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index bffba5a3..ebfd3b0e 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -40,15 +40,19 @@ use windows_sys::Win32::{ Touch::{RegisterTouchWindow, TWF_WANTPALM}, }, WindowsAndMessaging::{ - CreateWindowExW, FlashWindowEx, GetClientRect, GetCursorPos, GetForegroundWindow, - GetSystemMetrics, GetWindowPlacement, GetWindowTextLengthW, GetWindowTextW, - IsWindowVisible, LoadCursorW, PeekMessageW, PostMessageW, RegisterClassExW, SetCursor, - SetCursorPos, SetForegroundWindow, SetWindowDisplayAffinity, SetWindowPlacement, - SetWindowPos, SetWindowTextW, CS_HREDRAW, CS_VREDRAW, CW_USEDEFAULT, FLASHWINFO, + CreateWindowExW, EnableMenuItem, FlashWindowEx, GetClientRect, GetCursorPos, + GetForegroundWindow, GetSystemMenu, GetSystemMetrics, GetWindowPlacement, + GetWindowTextLengthW, GetWindowTextW, IsWindowVisible, LoadCursorW, PeekMessageW, + PostMessageW, RegisterClassExW, SetCursor, SetCursorPos, SetForegroundWindow, + SetMenuDefaultItem, SetWindowDisplayAffinity, SetWindowPlacement, SetWindowPos, + SetWindowTextW, TrackPopupMenu, CS_HREDRAW, CS_VREDRAW, CW_USEDEFAULT, FLASHWINFO, FLASHW_ALL, FLASHW_STOP, FLASHW_TIMERNOFG, FLASHW_TRAY, GWLP_HINSTANCE, HTBOTTOM, HTBOTTOMLEFT, HTBOTTOMRIGHT, HTCAPTION, HTLEFT, HTRIGHT, HTTOP, HTTOPLEFT, HTTOPRIGHT, - NID_READY, PM_NOREMOVE, SM_DIGITIZER, SWP_ASYNCWINDOWPOS, SWP_NOACTIVATE, SWP_NOSIZE, - SWP_NOZORDER, WDA_EXCLUDEFROMCAPTURE, WDA_NONE, WM_NCLBUTTONDOWN, WNDCLASSEXW, + MENU_ITEM_STATE, MFS_DISABLED, MFS_ENABLED, MF_BYCOMMAND, NID_READY, PM_NOREMOVE, + SC_CLOSE, SC_MAXIMIZE, SC_MINIMIZE, SC_MOVE, SC_RESTORE, SC_SIZE, SM_DIGITIZER, + SWP_ASYNCWINDOWPOS, SWP_NOACTIVATE, SWP_NOSIZE, SWP_NOZORDER, TPM_LEFTALIGN, + TPM_RETURNCMD, WDA_EXCLUDEFROMCAPTURE, WDA_NONE, WM_NCLBUTTONDOWN, WM_SYSCOMMAND, + WNDCLASSEXW, }, }, }; @@ -475,6 +479,83 @@ impl Window { Ok(()) } + unsafe fn handle_showing_window_menu(&self, position: Position) { + unsafe { + let point = { + let mut point = POINT { x: 0, y: 0 }; + let scale_factor = self.scale_factor(); + let (x, y) = position.to_physical::(scale_factor).into(); + point.x = x; + point.y = y; + if ClientToScreen(self.hwnd(), &mut point) == false.into() { + warn!("Can't convert client-area coordinates to screen coordinates when showing window menu."); + return; + } + point + }; + + // get the current system menu + let h_menu = GetSystemMenu(self.hwnd(), 0); + if h_menu == 0 { + warn!("The corresponding window doesn't have a system menu"); + // This situation should not be treated as an error so just return without showing menu. + return; + } + + fn enable(b: bool) -> MENU_ITEM_STATE { + if b { + MFS_ENABLED + } else { + MFS_DISABLED + } + } + + // Change the menu items according to the current window status. + + let restore_btn = enable(self.is_maximized() && self.is_resizable()); + let size_btn = enable(!self.is_maximized() && self.is_resizable()); + let maximize_btn = enable(!self.is_maximized() && self.is_resizable()); + + EnableMenuItem(h_menu, SC_RESTORE, MF_BYCOMMAND | restore_btn); + EnableMenuItem(h_menu, SC_MOVE, MF_BYCOMMAND | enable(!self.is_maximized())); + EnableMenuItem(h_menu, SC_SIZE, MF_BYCOMMAND | size_btn); + EnableMenuItem(h_menu, SC_MINIMIZE, MF_BYCOMMAND | MFS_ENABLED); + EnableMenuItem(h_menu, SC_MAXIMIZE, MF_BYCOMMAND | maximize_btn); + EnableMenuItem(h_menu, SC_CLOSE, MF_BYCOMMAND | MFS_ENABLED); + + // Set the default menu item. + SetMenuDefaultItem(h_menu, SC_CLOSE, 0); + + // Popup the system menu at the position. + let result = TrackPopupMenu( + h_menu, + TPM_RETURNCMD | TPM_LEFTALIGN, // for now im using LTR, but we have to use user layout direction + point.x, + point.y, + 0, + self.hwnd(), + std::ptr::null_mut(), + ); + + if result == 0 { + // User canceled the menu, no need to continue. + return; + } + + // Send the command that the user select to the corresponding window. + if PostMessageW(self.hwnd(), WM_SYSCOMMAND, result as _, 0) == 0 { + warn!("Can't post the system menu message to the window."); + } + } + } + + #[inline] + pub fn show_window_menu(&self, position: Position) { + unsafe { + self.handle_showing_window_menu(position); + } + } + #[inline] pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { let window = self.window.clone(); diff --git a/src/window.rs b/src/window.rs index 59cd9985..f49aa237 100644 --- a/src/window.rs +++ b/src/window.rs @@ -40,7 +40,7 @@ pub use raw_window_handle; /// ```no_run /// use winit::{ /// event::{Event, WindowEvent}, -/// event_loop::EventLoop, +/// event_loop::{ControlFlow, EventLoop}, /// window::Window, /// }; /// @@ -1423,6 +1423,21 @@ impl Window { .maybe_wait_on_main(|w| w.drag_resize_window(direction)) } + /// Show [window menu] at a specified position . + /// + /// This is the context menu that is normally shown when interacting with + /// the title bar. This is useful when implementing custom decorations. + /// + /// ## Platform-specific + /// **Android / iOS / macOS / Orbital / Wayland / Web / X11:** Unsupported. + /// + /// [window menu]: https://en.wikipedia.org/wiki/Common_menus_in_Microsoft_Windows#System_menu + pub fn show_window_menu(&self, position: impl Into) { + let position = position.into(); + self.window + .maybe_queue_on_main(move |w| w.show_window_menu(position)) + } + /// Modifies whether the window catches cursor events. /// /// If `true`, the window will catch the cursor events. If `false`, events are passed through