mirror of
https://github.com/rust-windowing/winit.git
synced 2026-08-29 04:40:04 -04:00
Wayland, Windows, MacOS: Popup Implementation (#4543)
Implement proper decorationless popups by specifying the type of the child window with with_type() With this commit, different kind of child windows can be created - Popups: Special windows without any decoration which can be positioned relative to the parent - Window: Normal window with a parent or not The type can be specified during creation of the Window using the window_attributes and the with_type() function. As default a normal Window is used. If Popup is choosen a parent must be specified, otherwise the Popup creation fails with an Error returned by the new() function. Related issues: #403 and #4256
This commit is contained in:
@@ -15,7 +15,7 @@ use winit_core::icon::Icon;
|
||||
use winit_core::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle};
|
||||
use winit_core::window::{
|
||||
ImeCapabilities, ImeRequest, ImeRequestError, Theme, UserAttentionType, Window as CoreWindow,
|
||||
WindowAttributes, WindowButtons, WindowId, WindowLevel,
|
||||
WindowAttributes, WindowButtons, WindowId, WindowLevel, WindowType,
|
||||
};
|
||||
|
||||
use super::event_loop::ActiveEventLoop;
|
||||
@@ -26,6 +26,7 @@ pub(crate) struct Window {
|
||||
window: MainThreadBound<Retained<NSWindow>>,
|
||||
/// The window only keeps a weak reference to this, so we must keep it around here.
|
||||
delegate: MainThreadBound<Retained<WindowDelegate>>,
|
||||
window_type: WindowType,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
@@ -34,12 +35,14 @@ impl Window {
|
||||
attributes: WindowAttributes,
|
||||
) -> Result<Self, RequestError> {
|
||||
let mtm = window_target.mtm;
|
||||
let window_type = attributes.window_type;
|
||||
let delegate =
|
||||
autoreleasepool(|_| WindowDelegate::new(&window_target.app_state, attributes, mtm))?;
|
||||
window_target.app_state.register_window(&delegate, mtm);
|
||||
Ok(Window {
|
||||
window: MainThreadBound::new(delegate.window().retain(), mtm),
|
||||
delegate: MainThreadBound::new(delegate, mtm),
|
||||
window_type,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -95,6 +98,10 @@ impl rwh_06::HasWindowHandle for Window {
|
||||
}
|
||||
|
||||
impl CoreWindow for Window {
|
||||
fn window_type(&self) -> WindowType {
|
||||
self.window_type
|
||||
}
|
||||
|
||||
fn id(&self) -> winit_core::window::WindowId {
|
||||
self.maybe_wait_on_main(|delegate| delegate.id())
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ use winit_core::icon::Icon;
|
||||
use winit_core::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle, MonitorHandleProvider};
|
||||
use winit_core::window::{
|
||||
CursorGrabMode, ImeCapabilities, ImeRequest, ImeRequestError, ResizeDirection, Theme,
|
||||
UserAttentionType, WindowAttributes, WindowButtons, WindowId, WindowLevel,
|
||||
UserAttentionType, WindowAttributes, WindowButtons, WindowId, WindowLevel, WindowType,
|
||||
};
|
||||
|
||||
use super::app_state::AppState;
|
||||
@@ -112,6 +112,7 @@ pub(crate) struct State {
|
||||
is_simple_fullscreen: Cell<bool>,
|
||||
saved_style: Cell<Option<NSWindowStyleMask>>,
|
||||
is_borderless_game: Cell<bool>,
|
||||
is_popup: Cell<bool>,
|
||||
}
|
||||
|
||||
define_class!(
|
||||
@@ -655,6 +656,7 @@ fn new_window(
|
||||
app_state: &Rc<AppState>,
|
||||
attrs: &WindowAttributes,
|
||||
macos_attrs: &WindowAttributesMacOS,
|
||||
is_popup: bool,
|
||||
mtm: MainThreadMarker,
|
||||
) -> Option<Retained<NSWindow>> {
|
||||
autoreleasepool(|_| {
|
||||
@@ -681,6 +683,10 @@ fn new_window(
|
||||
None => NSSize::new(800.0, 600.0),
|
||||
};
|
||||
let position = match attrs.position {
|
||||
// A popup's position is parent-relative; it's applied in `WindowDelegate::new`
|
||||
// (after the delegate exists) via the shared translation in
|
||||
// `set_outer_position`.
|
||||
_ if is_popup => NSPoint::new(0.0, 0.0),
|
||||
Some(position) => {
|
||||
let position = position.to_logical(scale_factor);
|
||||
flip_window_screen_coordinates(NSRect::new(
|
||||
@@ -738,6 +744,8 @@ fn new_window(
|
||||
// confusing issues with the window not being properly activated.
|
||||
//
|
||||
// Winit ensures this by not allowing access to `ActiveEventLoop` before handling events.
|
||||
// Panels (including popups) are non-activating so they don't steal key focus
|
||||
// from their parent (matching menu/combobox semantics).
|
||||
let window: Retained<NSWindow> = if macos_attrs.panel {
|
||||
masks |= NSWindowStyleMask::NonactivatingPanel;
|
||||
|
||||
@@ -821,7 +829,8 @@ fn new_window(
|
||||
if !macos_attrs.has_shadow {
|
||||
window.setHasShadow(false);
|
||||
}
|
||||
if attrs.position.is_none() {
|
||||
// Popups are positioned relative to their parent in `WindowDelegate::new`.
|
||||
if attrs.position.is_none() && !is_popup {
|
||||
window.center();
|
||||
}
|
||||
|
||||
@@ -884,13 +893,27 @@ impl WindowDelegate {
|
||||
mut attrs: WindowAttributes,
|
||||
mtm: MainThreadMarker,
|
||||
) -> Result<Retained<Self>, RequestError> {
|
||||
let macos_attrs = attrs
|
||||
let mut macos_attrs = attrs
|
||||
.platform
|
||||
.take()
|
||||
.and_then(|attrs| attrs.cast::<WindowAttributesMacOS>().ok())
|
||||
.unwrap_or_default();
|
||||
|
||||
let window = new_window(app_state, &attrs, &macos_attrs, mtm)
|
||||
let is_popup = matches!(attrs.window_type(), WindowType::Popup);
|
||||
if is_popup {
|
||||
// A popup is an undecorated, non-activating panel with no titlebar buttons. Model it
|
||||
// as such so it flows through the existing borderless + panel paths in `new_window`
|
||||
// instead of needing dedicated branches.
|
||||
attrs.decorations = false;
|
||||
attrs.enabled_buttons = WindowButtons::empty();
|
||||
// Unless grab_keyboard is requested, use a non-activating panel so the popup doesn't
|
||||
// steal keyboard focus from the parent window.
|
||||
if !attrs.active {
|
||||
macos_attrs.panel = true;
|
||||
}
|
||||
}
|
||||
|
||||
let window = new_window(app_state, &attrs, &macos_attrs, is_popup, mtm)
|
||||
.ok_or_else(|| os_error!("couldn't create `NSWindow`"))?;
|
||||
|
||||
match attrs.parent_window() {
|
||||
@@ -909,6 +932,11 @@ impl WindowDelegate {
|
||||
unsafe { parent.addChildWindow_ordered(&window, NSWindowOrderingMode::Above) };
|
||||
},
|
||||
Some(raw) => panic!("invalid raw window handle {raw:?} on macOS"),
|
||||
None if is_popup => {
|
||||
return Err(RequestError::NotSupported(NotSupportedError::new(
|
||||
"a popup window requires a parent window",
|
||||
)));
|
||||
},
|
||||
None => (),
|
||||
}
|
||||
|
||||
@@ -946,6 +974,7 @@ impl WindowDelegate {
|
||||
is_simple_fullscreen: Cell::new(false),
|
||||
saved_style: Cell::new(None),
|
||||
is_borderless_game: Cell::new(macos_attrs.borderless_game),
|
||||
is_popup: Cell::new(is_popup),
|
||||
});
|
||||
let delegate: Retained<WindowDelegate> = unsafe { msg_send![super(delegate), init] };
|
||||
|
||||
@@ -992,6 +1021,14 @@ impl WindowDelegate {
|
||||
|
||||
delegate.set_window_level(attrs.window_level);
|
||||
|
||||
// The popup position is relative to the parent window, and the parent is only
|
||||
// attached above, so apply the (translated) position now. Default to the parent's
|
||||
// content top-left when no position was given.
|
||||
if is_popup {
|
||||
let position = attrs.position.unwrap_or_else(|| LogicalPosition::new(0.0, 0.0).into());
|
||||
delegate.set_outer_position(position);
|
||||
}
|
||||
|
||||
delegate.set_cursor(attrs.cursor);
|
||||
|
||||
// Set fullscreen mode after we setup everything
|
||||
@@ -1147,7 +1184,9 @@ impl WindowDelegate {
|
||||
|
||||
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, RequestError> {
|
||||
let position = flip_window_screen_coordinates(self.window().frame());
|
||||
Ok(LogicalPosition::new(position.x, position.y).to_physical(self.scale_factor()))
|
||||
let position =
|
||||
self.translate_popup_position_to_parent(LogicalPosition::new(position.x, position.y));
|
||||
Ok(position.to_physical(self.scale_factor()))
|
||||
}
|
||||
|
||||
pub fn surface_position(&self) -> PhysicalPosition<i32> {
|
||||
@@ -1171,6 +1210,7 @@ impl WindowDelegate {
|
||||
|
||||
pub fn set_outer_position(&self, position: Position) {
|
||||
let position = position.to_logical(self.scale_factor());
|
||||
let position = self.translate_popup_position(position);
|
||||
let point = flip_window_screen_coordinates(NSRect::new(
|
||||
NSPoint::new(position.x, position.y),
|
||||
self.window().frame().size,
|
||||
@@ -1178,6 +1218,40 @@ impl WindowDelegate {
|
||||
self.window().setFrameOrigin(point);
|
||||
}
|
||||
|
||||
/// Popups receive their position relative to the top-left of the parent window's
|
||||
/// content area (matching the Win32 and Wayland backends). macOS positions windows
|
||||
/// in global screen coordinates, so add the parent content area's origin.
|
||||
fn translate_popup_position(&self, position: LogicalPosition<f64>) -> LogicalPosition<f64> {
|
||||
if !self.ivars().is_popup.get() {
|
||||
return position;
|
||||
}
|
||||
let Some(parent) = self.window().parentWindow() else {
|
||||
return position;
|
||||
};
|
||||
let parent_origin =
|
||||
flip_window_screen_coordinates(parent.contentRectForFrameRect(parent.frame()));
|
||||
LogicalPosition::new(parent_origin.x + position.x, parent_origin.y + position.y)
|
||||
}
|
||||
|
||||
/// Inverse of [`Self::translate_popup_position`]. Popups report their position
|
||||
/// relative to the top-left of the parent window's content area (matching the
|
||||
/// Win32 and Wayland backends), so subtract the parent content area's origin from
|
||||
/// the global screen coordinates. Non-popup windows are returned unchanged.
|
||||
fn translate_popup_position_to_parent(
|
||||
&self,
|
||||
position: LogicalPosition<f64>,
|
||||
) -> LogicalPosition<f64> {
|
||||
if !self.ivars().is_popup.get() {
|
||||
return position;
|
||||
}
|
||||
let Some(parent) = self.window().parentWindow() else {
|
||||
return position;
|
||||
};
|
||||
let parent_origin =
|
||||
flip_window_screen_coordinates(parent.contentRectForFrameRect(parent.frame()));
|
||||
LogicalPosition::new(position.x - parent_origin.x, position.y - parent_origin.y)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn surface_size(&self) -> PhysicalSize<u32> {
|
||||
self.view().surface_size()
|
||||
|
||||
Reference in New Issue
Block a user