Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 53 additions & 4 deletions winit-appkit/src/window_delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,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;
Expand Down Expand Up @@ -107,6 +107,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!(
Expand Down Expand Up @@ -537,6 +538,7 @@ fn new_window(
app_state: &Rc<AppState>,
attrs: &WindowAttributes,
macos_attrs: &WindowAttributesMacOS,
is_popup: bool,
mtm: MainThreadMarker,
) -> Option<Retained<NSWindow>> {
autoreleasepool(|_| {
Expand All @@ -563,6 +565,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(
Expand Down Expand Up @@ -620,6 +626,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;

Expand Down Expand Up @@ -703,7 +711,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();
}

Expand Down Expand Up @@ -770,13 +779,23 @@ 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 = 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();
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() {
Expand All @@ -795,6 +814,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 => (),
}

Expand Down Expand Up @@ -832,6 +856,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] };

Expand Down Expand Up @@ -862,6 +887,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
Expand Down Expand Up @@ -1031,13 +1064,29 @@ 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,
));
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)
}

#[inline]
pub fn surface_size(&self) -> PhysicalSize<u32> {
let content_rect = self.window().contentRectForFrameRect(self.window().frame());
Expand Down
45 changes: 45 additions & 0 deletions winit-core/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,26 @@ impl fmt::Debug for WindowId {
}
}

/// The role of a window, used to request platform-specific window behavior.
#[derive(Debug, Clone, Copy, Default, PartialEq)]
#[non_exhaustive]
pub enum WindowType {
Comment thread
Murmele marked this conversation as resolved.
/// A normal, top-level window.
#[default]
Window,
/// A short-lived window anchored to a parent, such as a menu, combo-box dropdown, or
/// tooltip. Requires a parent set via [`WindowAttributes::with_parent_window`], and its
/// position is interpreted relative to that parent.
///
/// ## Platform-specific
///
/// - **macOS:** A borderless, non-activating child window. The system does *not* draw a frame,
/// shadow contour, or rounded corners for it, so it appears with square corners by default.
/// To get a rounded, native-looking popup, create it transparent (via
/// [`WindowAttributes::with_transparent`]) and draw the background and shadow yourself.
Popup,
}

/// Attributes used when creating a window.
#[derive(Debug)]
#[non_exhaustive]
Expand All @@ -72,6 +92,7 @@ pub struct WindowAttributes {
pub(crate) parent_window: Option<SendSyncRawWindowHandle>,
pub fullscreen: Option<Fullscreen>,
pub platform: Option<Box<dyn PlatformWindowAttributes>>,
pub window_type: WindowType,
}

impl WindowAttributes {
Expand Down Expand Up @@ -145,6 +166,8 @@ impl WindowAttributes {
/// position. There may be a small gap between this position and the window due to the
/// specifics of the Window Manager.
/// - **X11:** The top left corner of the window, the window's "outer" position.
/// - **Wayland:** The top left corner of the window if the window type is `WindowType::Popup`
/// otherwise ignored
/// - **Others:** Ignored.
#[inline]
pub fn with_position<P: Into<Position>>(mut self, position: P) -> Self {
Expand Down Expand Up @@ -378,6 +401,26 @@ impl WindowAttributes {
self.platform = Some(platform);
self
}

/// Sets the [`WindowType`] (window vs. popup).
///
/// Used by the Windows, Wayland and macOS backends; on X11 popups are just normal windows.
/// If the type is [`WindowType::Popup`], the parent must also be set via
/// [`with_parent_window`](Self::with_parent_window), and the position is interpreted
/// relative to that parent.
///
/// See [`WindowType::Popup`] for the per-platform behavior, including how to obtain a
/// rounded, native-looking popup on macOS.
pub fn with_type(mut self, window_type: WindowType) -> Self {
self.window_type = window_type;
self
}

/// Returns if the window type is a popup or a normal window
#[inline]
pub fn window_type(&self) -> WindowType {
self.window_type
}
}

impl Clone for WindowAttributes {
Expand Down Expand Up @@ -405,6 +448,7 @@ impl Clone for WindowAttributes {
parent_window: self.parent_window.clone(),
fullscreen: self.fullscreen.clone(),
platform: self.platform.as_ref().map(|platform| platform.box_clone()),
window_type: self.window_type,
}
}
}
Expand Down Expand Up @@ -435,6 +479,7 @@ impl Default for WindowAttributes {
platform: Default::default(),
cursor: Cursor::default(),
blur: Default::default(),
window_type: Default::default(),
}
}
}
Expand Down
15 changes: 12 additions & 3 deletions winit-wayland/src/event_loop/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use winit_core::event_loop::{
OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use winit_core::monitor::MonitorHandle as CoreMonitorHandle;
use winit_core::window::Theme;
use winit_core::window::{Theme, WindowType};

use crate::types::cursor::WaylandCustomCursor;

Expand Down Expand Up @@ -651,8 +651,17 @@ impl RootActiveEventLoop for ActiveEventLoop {
&self,
window_attributes: winit_core::window::WindowAttributes,
) -> Result<Box<dyn winit_core::window::Window>, RequestError> {
let window = crate::Window::new(self, window_attributes)?;
Ok(Box::new(window))
match window_attributes.window_type() {
WindowType::Window => {
let window = crate::Window::new(self, window_attributes)?;
Ok(Box::new(window))
},
WindowType::Popup => {
let popup = crate::Popup::new(self, window_attributes)?;
Ok(Box::new(popup))
},
_ => panic!("Not implemented"),
}
}

fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
Expand Down
2 changes: 2 additions & 0 deletions winit-wayland/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,14 @@ macro_rules! os_error {

mod event_loop;
mod output;
mod popup;
mod seat;
mod state;
mod types;
mod window;

pub use self::event_loop::{ActiveEventLoop, EventLoop};
pub use self::popup::Popup;
pub use self::window::Window;

/// Additional methods on [`ActiveEventLoop`] that are specific to Wayland.
Expand Down
Loading
Loading