From 1e8dd42a1696196c6321bba8f5f114ec2273d5d7 Mon Sep 17 00:00:00 2001 From: jtnunley Date: Wed, 22 Feb 2023 16:33:45 -0800 Subject: [PATCH 1/2] feat: Cross-platform popup windows --- CHANGELOG.md | 1 + FEATURES.md | 2 +- examples/window_popup.rs | 58 +++++++++++++++++++++++++++ src/platform/mod.rs | 3 ++ src/platform/popup.rs | 30 ++++++++++++++ src/platform/windows.rs | 7 +++- src/platform_impl/linux/mod.rs | 4 ++ src/platform_impl/linux/x11/window.rs | 15 +++++++ src/platform_impl/windows/mod.rs | 4 +- src/platform_impl/windows/window.rs | 5 ++- 10 files changed, 124 insertions(+), 5 deletions(-) create mode 100644 examples/window_popup.rs create mode 100644 src/platform/popup.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 4aa0b6de4e..b6ee4a10d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ And please only add new entries to the top of this list, right below the `# Unre - On Web, remove unnecessary `Window::is_dark_mode()`, which was replaced with `Window::theme()`. - On Web, add `WindowBuilderExtWebSys::with_append()` to append the canvas element to the web page on creation. - On Windows, add `drag_resize_window` method support. +- Add the `platform::popup` module, for a cross-platform strategy for creating popup windows. # 0.29.0-beta.0 diff --git a/FEATURES.md b/FEATURES.md index 9e859d8959..f7ccbd6026 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -191,7 +191,7 @@ Legend: |Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |**N/A** | |Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**|**N/A** | |HiDPI support |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❌ | -|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|**N/A** | +|Popup windows |✔ |❌ |✔ |❌ |❌ |❌ |**N/A**|**N/A** | ### System information |Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS| diff --git a/examples/window_popup.rs b/examples/window_popup.rs new file mode 100644 index 0000000000..4a06d36a7d --- /dev/null +++ b/examples/window_popup.rs @@ -0,0 +1,58 @@ +#[cfg(any(x11_platform, windows_platform))] +fn main() { + use winit::{ + dpi::{LogicalPosition, LogicalSize, Position}, + event::{Event, WindowEvent}, + event_loop::EventLoop, + platform::popup::WindowBuilderExtPopup, + window::WindowBuilder, + }; + + let event_loop: EventLoop<()> = EventLoop::new(); + let mut parent_window = Some( + WindowBuilder::new() + .with_title("parent window") + .with_position(Position::Logical(LogicalPosition::new(0.0, 0.0))) + .with_inner_size(LogicalSize::new(640.0f32, 480.0f32)) + .build(&event_loop) + .unwrap(), + ); + + println!("parent window: {parent_window:?})"); + + let mut child_window = Some( + WindowBuilder::new() + .with_title("popup window") + .with_inner_size(LogicalSize::new(200.0f32, 200.0f32)) + .with_position(Position::Logical(LogicalPosition::new(0.0, 0.0))) + .with_transient_parent(parent_window.as_ref().unwrap()) + .build(&event_loop) + .unwrap(), + ); + + event_loop.run(move |event: Event<'_, ()>, _, control_flow| { + control_flow.set_wait(); + + if let Event::WindowEvent { event, window_id } = event { + match event { + WindowEvent::CloseRequested + if Some(window_id) == parent_window.as_ref().map(|w| w.id()) => + { + parent_window.take(); + control_flow.set_exit(); + } + WindowEvent::CloseRequested + if Some(window_id) == child_window.as_ref().map(|w| w.id()) => + { + child_window.take(); + } + _ => (), + } + } + }) +} + +#[cfg(not(any(x11_platform, windows_platform)))] +fn main() { + panic!("This example is supported only on x11 and Windows."); +} diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 9e01c4d993..17200e7505 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -45,3 +45,6 @@ pub mod modifier_supplement; ))] pub mod run_return; pub mod scancode; + +#[cfg(any(windows_platform, x11_platform))] +pub mod popup; diff --git a/src/platform/popup.rs b/src/platform/popup.rs new file mode 100644 index 0000000000..d0e865adfa --- /dev/null +++ b/src/platform/popup.rs @@ -0,0 +1,30 @@ +//! Extension traits for creating popup windows. + +use crate::window::WindowBuilder; +use __private::Sealed; +use raw_window_handle::HasRawWindowHandle; + +/// Additional methods on [`WindowBuilder`] to create popup windows. +pub trait WindowBuilderExtPopup: Sealed { + /// Sets this window to be a popup window for the provided parent window. + /// + /// This method is only available on Windows and X11. This has no effect on Wayland. + fn with_transient_parent(self, parent: impl HasRawWindowHandle) -> WindowBuilder; +} + +impl WindowBuilderExtPopup for WindowBuilder { + fn with_transient_parent(mut self, parent: impl HasRawWindowHandle) -> WindowBuilder { + let hwnd = parent.raw_window_handle(); + self.platform_specific.owner = Some(hwnd); + self + } +} + +mod __private { + use crate::window::WindowBuilder; + + #[doc(hidden)] + pub trait Sealed {} + + impl Sealed for WindowBuilder {} +} diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 626e2fba6f..fcc895a963 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -192,6 +192,7 @@ pub trait WindowBuilderExtWindows { /// - An owned window is hidden when its owner is minimized. /// /// For more information, see + #[deprecated = "Use `WindowBuilderExtPopup::with_transient_parent()` instead"] fn with_owner_window(self, parent: HWND) -> WindowBuilder; /// Sets a menu on the window to be created. @@ -233,7 +234,11 @@ pub trait WindowBuilderExtWindows { impl WindowBuilderExtWindows for WindowBuilder { #[inline] fn with_owner_window(mut self, parent: HWND) -> WindowBuilder { - self.platform_specific.owner = Some(parent); + use raw_window_handle::{RawWindowHandle, Win32WindowHandle}; + + let mut hwnd = Win32WindowHandle::empty(); + hwnd.hwnd = parent as _; + self.platform_specific.owner = Some(RawWindowHandle::Win32(hwnd)); self } diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index fa3ef92843..0364e48d71 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -101,6 +101,8 @@ pub struct PlatformSpecificWindowBuilderAttributes { pub override_redirect: bool, #[cfg(x11_platform)] pub x11_window_types: Vec, + #[cfg(x11_platform)] + pub owner: Option, } impl Default for PlatformSpecificWindowBuilderAttributes { @@ -118,6 +120,8 @@ impl Default for PlatformSpecificWindowBuilderAttributes { override_redirect: false, #[cfg(x11_platform)] x11_window_types: vec![XWindowType::Normal], + #[cfg(x11_platform)] + owner: None, } } } diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 75774f8dcd..c94f0fb7e0 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -454,6 +454,21 @@ impl UnownedWindow { ); leap!(result).ignore_error(); + // Set the transient parent if we don't have a parent already. + if event_loop.root == root { + let owner = pl_attribs.owner.map(|owner| match owner { + RawWindowHandle::Xlib(x) => x.window, + RawWindowHandle::Xcb(x) => x.window as u64, + raw => unreachable!("Invalid raw window handle {raw:?} on X11"), + }); + + if let Some(owner) = owner { + unsafe { + (xconn.xlib.XSetTransientForHint)(xconn.display, window.xwindow, owner); + } + } + } + // Set visibility (map window) if window_attrs.visible { leap!(xconn.xcb_connection().map_window(window.xwindow)).ignore_error(); diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index ee256694c9..63116077b7 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -22,9 +22,11 @@ use crate::event::DeviceId as RootDeviceId; use crate::icon::Icon; use crate::keyboard::Key; +use raw_window_handle::RawWindowHandle; + #[derive(Clone)] pub struct PlatformSpecificWindowBuilderAttributes { - pub owner: Option, + pub owner: Option, pub menu: Option, pub taskbar_icon: Option, pub no_redirection_bitmap: bool, diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index ff07b8eb25..69e357c06a 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -1140,10 +1140,11 @@ where } Some(raw) => unreachable!("Invalid raw window handle {raw:?} on Windows"), None => match pl_attribs.owner { - Some(parent) => { + Some(RawWindowHandle::Win32(parent)) => { window_flags.set(WindowFlags::POPUP, true); - Some(parent) + Some(parent.hwnd as _) } + Some(raw) => unreachable!("Invalid raw window handle {raw:?} on Windows"), None => { window_flags.set(WindowFlags::ON_TASKBAR, true); None From 92630d6d7e6f903a9eef04822ac3cfd85c0d6d0a Mon Sep 17 00:00:00 2001 From: John Nunley Date: Fri, 21 Jul 2023 16:19:59 -0700 Subject: [PATCH 2/2] Re-implement for X11 with x11rb --- examples/window_popup.rs | 17 +++++++++++++++- src/platform_impl/linux/x11/window.rs | 28 ++++++++++++--------------- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/examples/window_popup.rs b/examples/window_popup.rs index 4a06d36a7d..8eb604728c 100644 --- a/examples/window_popup.rs +++ b/examples/window_popup.rs @@ -1,3 +1,7 @@ +#[cfg(any(x11_platform, windows_platform))] +#[path = "util/fill.rs"] +mod fill; + #[cfg(any(x11_platform, windows_platform))] fn main() { use winit::{ @@ -20,11 +24,16 @@ fn main() { println!("parent window: {parent_window:?})"); + let monitor_size = event_loop.primary_monitor().unwrap().size(); + let child_posn = LogicalPosition::new( + (monitor_size.width as f64 - 200.0) / 2.0, + (monitor_size.height as f64 - 200.0) / 2.0, + ); let mut child_window = Some( WindowBuilder::new() .with_title("popup window") .with_inner_size(LogicalSize::new(200.0f32, 200.0f32)) - .with_position(Position::Logical(LogicalPosition::new(0.0, 0.0))) + .with_position(Position::Logical(child_posn)) .with_transient_parent(parent_window.as_ref().unwrap()) .build(&event_loop) .unwrap(), @@ -48,6 +57,12 @@ fn main() { } _ => (), } + } else if let Event::RedrawRequested(wid) = event { + if Some(wid) == parent_window.as_ref().map(|w| w.id()) { + fill::fill_window(parent_window.as_ref().unwrap()); + } else if Some(wid) == child_window.as_ref().map(|w| w.id()) { + fill::fill_window(child_window.as_ref().unwrap()); + } } }) } diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index c94f0fb7e0..dd7da919b4 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -242,6 +242,17 @@ impl UnownedWindow { ), }; + // Set the transient parent if we don't have a parent already. + let owner = if event_loop.root == root { + pl_attribs.owner.map(|owner| match owner { + RawWindowHandle::Xlib(x) => x.window, + RawWindowHandle::Xcb(x) => x.window as u64, + raw => unreachable!("Invalid raw window handle {raw:?} on X11"), + }) + } else { + None + }; + let window_attributes = { use xproto::EventMask; @@ -258,7 +269,7 @@ impl UnownedWindow { aux = aux.event_mask(event_mask).border_pixel(0); - if pl_attribs.override_redirect { + if pl_attribs.override_redirect || owner.is_some() { aux = aux.override_redirect(true as u32); } @@ -454,21 +465,6 @@ impl UnownedWindow { ); leap!(result).ignore_error(); - // Set the transient parent if we don't have a parent already. - if event_loop.root == root { - let owner = pl_attribs.owner.map(|owner| match owner { - RawWindowHandle::Xlib(x) => x.window, - RawWindowHandle::Xcb(x) => x.window as u64, - raw => unreachable!("Invalid raw window handle {raw:?} on X11"), - }); - - if let Some(owner) = owner { - unsafe { - (xconn.xlib.XSetTransientForHint)(xconn.display, window.xwindow, owner); - } - } - } - // Set visibility (map window) if window_attrs.visible { leap!(xconn.xcb_connection().map_window(window.xwindow)).ignore_error();