diff --git a/winit-appkit/src/window_delegate.rs b/winit-appkit/src/window_delegate.rs index e316973e7c..86aa568b3d 100644 --- a/winit-appkit/src/window_delegate.rs +++ b/winit-appkit/src/window_delegate.rs @@ -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; @@ -107,6 +107,7 @@ pub(crate) struct State { is_simple_fullscreen: Cell, saved_style: Cell>, is_borderless_game: Cell, + is_popup: Cell, } define_class!( @@ -537,6 +538,7 @@ fn new_window( app_state: &Rc, attrs: &WindowAttributes, macos_attrs: &WindowAttributesMacOS, + is_popup: bool, mtm: MainThreadMarker, ) -> Option> { autoreleasepool(|_| { @@ -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( @@ -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 = if macos_attrs.panel { masks |= NSWindowStyleMask::NonactivatingPanel; @@ -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(); } @@ -770,13 +779,23 @@ impl WindowDelegate { mut attrs: WindowAttributes, mtm: MainThreadMarker, ) -> Result, RequestError> { - let macos_attrs = attrs + let mut macos_attrs = attrs .platform .take() .and_then(|attrs| attrs.cast::().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() { @@ -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 => (), } @@ -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 = unsafe { msg_send![super(delegate), init] }; @@ -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 @@ -1031,6 +1064,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, @@ -1038,6 +1072,21 @@ 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) -> LogicalPosition { + 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 { let content_rect = self.window().contentRectForFrameRect(self.window().frame()); diff --git a/winit-core/src/window.rs b/winit-core/src/window.rs index 5355369acc..2200551616 100644 --- a/winit-core/src/window.rs +++ b/winit-core/src/window.rs @@ -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 { + /// 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] @@ -72,6 +92,7 @@ pub struct WindowAttributes { pub(crate) parent_window: Option, pub fullscreen: Option, pub platform: Option>, + pub window_type: WindowType, } impl WindowAttributes { @@ -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>(mut self, position: P) -> Self { @@ -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 { @@ -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, } } } @@ -435,6 +479,7 @@ impl Default for WindowAttributes { platform: Default::default(), cursor: Cursor::default(), blur: Default::default(), + window_type: Default::default(), } } } diff --git a/winit-wayland/src/event_loop/mod.rs b/winit-wayland/src/event_loop/mod.rs index 3a6ca6982b..5283aa797e 100644 --- a/winit-wayland/src/event_loop/mod.rs +++ b/winit-wayland/src/event_loop/mod.rs @@ -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; @@ -651,8 +651,17 @@ impl RootActiveEventLoop for ActiveEventLoop { &self, window_attributes: winit_core::window::WindowAttributes, ) -> Result, 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> { diff --git a/winit-wayland/src/lib.rs b/winit-wayland/src/lib.rs index 930562b380..267b6e47b4 100644 --- a/winit-wayland/src/lib.rs +++ b/winit-wayland/src/lib.rs @@ -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. diff --git a/winit-wayland/src/popup.rs b/winit-wayland/src/popup.rs new file mode 100644 index 0000000000..0765a3b65f --- /dev/null +++ b/winit-wayland/src/popup.rs @@ -0,0 +1,534 @@ +use core::sync::atomic::Ordering; +use std::sync::atomic::AtomicBool; +use std::sync::{Arc, Mutex}; + +use dpi::{LogicalPosition, PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size}; +use rwh_06::RawWindowHandle; +use sctk::compositor::SurfaceData; +use sctk::reexports::client::protocol::wl_surface::WlSurface; +use sctk::shell::WaylandSurface; +use sctk::shell::xdg::popup::Popup as SctkPopup; +use sctk::shell::xdg::{XdgPositioner, XdgSurface}; +use wayland_client::Proxy; +use wayland_client::protocol::wl_display::WlDisplay; +use wayland_protocols::xdg::shell::client::xdg_positioner::{Anchor, Gravity}; +use winit_core::cursor::Cursor; +use winit_core::error::{NotSupportedError, RequestError}; +use winit_core::event::{Ime, WindowEvent}; +use winit_core::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle}; +use winit_core::window::{ + CursorGrabMode, ImeCapabilities, ImeRequest, ImeRequestError, ResizeDirection, Theme, + UserAttentionType, Window as CoreWindow, WindowAttributes, WindowButtons, WindowId, + WindowLevel, +}; + +use super::ActiveEventLoop; +use super::output::MonitorHandle; +use crate::WindowAttributesWayland; +use crate::window::Handles; +use crate::window::handles::WindowRequests; +use crate::window::state::{WindowState, WindowType}; + +#[derive(Debug)] +pub struct Popup { + /// Reference to the underlying SCTK popup + popup: SctkPopup, + + /// The state of the popup. + popup_state: Arc>, + + /// Window id. + window_id: WindowId, + + /// The wayland display used solely for raw window handle. + #[allow(dead_code)] + display: WlDisplay, + + handles: Handles, +} + +impl Popup { + pub(crate) fn new( + event_loop_window_target: &ActiveEventLoop, + mut attributes: WindowAttributes, + ) -> Result { + macro_rules! error { + ($e:literal) => { + RequestError::NotSupported(NotSupportedError::new($e)) + }; + } + + let parent_window_handle = + attributes.parent_window().ok_or(error!("Popup without a parent is not supported!"))?; + if let RawWindowHandle::Wayland(parent_window_handle) = parent_window_handle { + let queue_handle = event_loop_window_target.queue_handle.clone(); + let mut state = event_loop_window_target.state.borrow_mut(); + let monitors = state.monitors.clone(); + let xdg_activation = state + .xdg_activation + .as_ref() + .map(|activation_state| activation_state.global().clone()); + let positioner = XdgPositioner::new(&state.xdg_shell) + .map_err(|_| error!("Failed to create positioner"))?; + let (popup, popup_state) = if let Some(parent_window_state) = state + .windows + .borrow() + .get(&WindowId::from_raw(parent_window_handle.surface.as_ptr() as usize)) + { + let size = attributes.surface_size.ok_or(error!("Invalid size for popup"))?; + + let parent_window_state = parent_window_state.lock().unwrap(); + + // Use the scale factor and xdg geometry of the parent. + let scale_factor = parent_window_state.scale_factor(); + let position: LogicalPosition = attributes + .position + .ok_or(error!("No position specified"))? + .to_logical(scale_factor); + let geometry_origin = parent_window_state.content_surface_origin(); + // The anchor rect is relative to the parent window geometry, so we need to subtract + // the geometry origin from the position to get the correct anchor rect. + // This is important for client side decorations + let anchor_position = LogicalPosition::new(-geometry_origin.x, -geometry_origin.y); + + positioner.set_anchor(Anchor::TopLeft); + positioner.set_gravity(Gravity::BottomRight); // Otherwise the child surface will be centered over the anchor point + positioner.set_anchor_rect(anchor_position.x, anchor_position.y, 1, 1); + positioner.set_offset(position.x, position.y); + positioner.set_size( + size.to_logical(scale_factor).width, + size.to_logical(scale_factor).height, + ); + + let parent_surface = parent_window_state.window.xdg_surface(); + let surface = state.compositor_state.create_surface(&queue_handle); + let popup = SctkPopup::from_surface( + Some(parent_surface), + &positioner, + &queue_handle, + surface.clone(), + &state.xdg_shell, + ) + .map_err(|_| error!("Failed to create popup"))?; + drop(parent_window_state); + + let popup_state = WindowState::new( + event_loop_window_target, + &state, + size, + WindowType::Popup((popup.clone(), positioner, None)), + attributes.preferred_theme, + false, + scale_factor, + ); + + let WindowAttributesWayland { activation_token, .. } = *attributes + .platform + .take() + .and_then(|p| p.cast::().ok()) + .unwrap_or_default(); + + // Activate the window when the token is passed. + if let (Some(xdg_activation), Some(token)) = + (xdg_activation.as_ref(), activation_token) + { + xdg_activation.activate(token.into_raw(), &surface); + } + + popup.wl_surface().commit(); + // popup.commit(); Trait not implemented in Sctk + + let popup_state = Arc::new(Mutex::new(popup_state)); + + (popup, popup_state) + } else { + return Err(error!("Parent window id unknown")); + }; + + let window_id = super::make_wid(popup.wl_surface()); + state.windows.get_mut().insert(window_id, popup_state.clone()); + + let window_requests = WindowRequests { + redraw_requested: AtomicBool::new(true), + closed: AtomicBool::new(false), + }; + let window_requests = Arc::new(window_requests); + state.window_requests.get_mut().insert(window_id, window_requests.clone()); + + // Setup the event sync to insert `WindowEvents` right from the window. + let window_events_sink = state.window_events_sink.clone(); + + let mut wayland_source = event_loop_window_target.wayland_dispatcher.as_source_mut(); + let event_queue = wayland_source.queue(); + // Do a roundtrip. + event_queue.roundtrip(&mut state).map_err(|err| os_error!(err))?; + + // XXX Wait for the initial configure to arrive. + while !popup_state.lock().unwrap().is_configured() { + event_queue.blocking_dispatch(&mut state).map_err(|err| os_error!(err))?; + } + + // Wake-up event loop, so it'll send initial redraw requested. + let event_loop_awakener = event_loop_window_target.event_loop_awakener.clone(); + event_loop_awakener.ping(); + + Ok(Self { + popup, + popup_state, + window_id, + display: event_loop_window_target.handle.connection.display().clone(), + handles: Handles { + queue_handle, + window_requests, + monitors, + event_loop_awakener, + window_events_sink, + + xdg_activation, + attention_requested: Arc::new(AtomicBool::new(false)), + + compositor: state.compositor_state.clone(), + }, + }) + } else { + Err(RequestError::NotSupported(NotSupportedError::new( + "A Popup requires a parent wayland window handle", + ))) + } + } + + #[inline] + pub fn surface(&self) -> &WlSurface { + self.popup.wl_surface() + } +} + +impl CoreWindow for Popup { + fn id(&self) -> WindowId { + self.window_id + } + + fn request_redraw(&self) { + self.handles.request_redraw(); + } + + #[inline] + fn title(&self) -> String { + self.popup_state.lock().unwrap().title().to_owned() + } + + fn pre_present_notify(&self) { + self.popup_state.lock().unwrap().request_frame_callback(); + } + + fn reset_dead_keys(&self) { + winit_common::xkb::reset_dead_keys() + } + + fn surface_position(&self) -> PhysicalPosition { + (0, 0).into() + } + + fn outer_position(&self) -> Result, RequestError> { + Err(NotSupportedError::new("window position information is not available on Wayland") + .into()) + } + + fn set_outer_position(&self, position: Position) { + let state = self.popup_state.lock().unwrap(); + if let WindowType::Popup((popup, positioner, _)) = &state.window { + let position = position.to_logical(state.scale_factor()); + positioner.set_offset(position.x, position.y); + popup.reposition(positioner, 0); + } + } + + fn surface_size(&self) -> PhysicalSize { + let popup_state = self.popup_state.lock().unwrap(); + let scale_factor = popup_state.scale_factor(); + super::logical_to_physical_rounded(popup_state.surface_size(), scale_factor) + } + + fn request_surface_size(&self, size: Size) -> Option> { + let mut popup_state = self.popup_state.lock().unwrap(); + popup_state.request_surface_size(size); + self.request_redraw(); + Some(size.to_physical(popup_state.scale_factor())) + } + + fn outer_size(&self) -> PhysicalSize { + let popup_state = self.popup_state.lock().unwrap(); + let scale_factor = popup_state.scale_factor(); + super::logical_to_physical_rounded(popup_state.outer_size(), scale_factor) + } + + fn safe_area(&self) -> PhysicalInsets { + PhysicalInsets::new(0, 0, 0, 0) + } + + fn set_min_surface_size(&self, min_size: Option) { + let scale_factor = self.scale_factor(); + let min_size = min_size.map(|size| size.to_logical(scale_factor)); + self.popup_state.lock().unwrap().set_min_surface_size(min_size); + // NOTE: Requires commit to be applied. + self.request_redraw(); + } + + /// Set the maximum surface size for the window. + #[inline] + fn set_max_surface_size(&self, max_size: Option) { + let scale_factor = self.scale_factor(); + let max_size = max_size.map(|size| size.to_logical(scale_factor)); + self.popup_state.lock().unwrap().set_max_surface_size(max_size); + // NOTE: Requires commit to be applied. + self.request_redraw(); + } + + fn surface_resize_increments(&self) -> Option> { + let popup_state = self.popup_state.lock().unwrap(); + let scale_factor = popup_state.scale_factor(); + popup_state + .resize_increments() + .map(|size| super::logical_to_physical_rounded(size, scale_factor)) + } + + fn set_surface_resize_increments(&self, increments: Option) { + let mut popup_state = self.popup_state.lock().unwrap(); + let scale_factor = popup_state.scale_factor(); + let increments = increments.map(|size| size.to_logical(scale_factor)); + popup_state.set_resize_increments(increments); + } + + fn set_title(&self, title: &str) { + self.popup_state.lock().unwrap().set_title(title.to_owned()); + } + + #[inline] + fn set_transparent(&self, transparent: bool) { + self.popup_state.lock().unwrap().set_transparent(transparent); + } + + fn set_visible(&self, _visible: bool) { + // Not possible on Wayland. + } + + fn is_visible(&self) -> Option { + None + } + + fn set_resizable(&self, _resizable: bool) { + // A popup cannot be resized with the mouse + } + + fn is_resizable(&self) -> bool { + // A popup cannot be resized with the mouse + false + } + + fn set_enabled_buttons(&self, _buttons: WindowButtons) { + // TODO(kchibisov) v5 of the xdg_shell allows that. + } + + fn enabled_buttons(&self) -> WindowButtons { + // TODO(kchibisov) v5 of the xdg_shell allows that. + WindowButtons::all() + } + + fn set_minimized(&self, _minimized: bool) { + // Not possible for popups + } + + fn is_minimized(&self) -> Option { + // XXX clients don't know whether they are minimized or not. + None + } + + fn set_maximized(&self, _maximized: bool) { + // Not possible for popups + } + + fn is_maximized(&self) -> bool { + // Not possible for popups + false + } + + fn set_fullscreen(&self, _fullscreen: Option) { + // Not possible for popups + } + + fn fullscreen(&self) -> Option { + None + } + + #[inline] + fn scale_factor(&self) -> f64 { + self.popup_state.lock().unwrap().scale_factor() + } + + #[inline] + fn set_blur(&self, blur: bool) { + if self.popup_state.lock().unwrap().set_blur(blur) { + self.request_redraw(); + } + } + + #[inline] + fn set_decorations(&self, _decorate: bool) { + // Popup does not support decorations + } + + #[inline] + fn is_decorated(&self) -> bool { + // Popup does not support decorations + false + } + + fn set_window_level(&self, _level: WindowLevel) { + // Popup does not have a window level + } + + fn set_window_icon(&self, _window_icon: Option) { + // Popup does not have a window icon + } + + #[inline] + fn request_ime_update(&self, request: ImeRequest) -> Result<(), ImeRequestError> { + let state_changed = self.popup_state.lock().unwrap().request_ime_update(request)?; + + if let Some(allowed) = state_changed { + let event = WindowEvent::Ime(if allowed { Ime::Enabled } else { Ime::Disabled }); + self.handles.push_window_event(event, self.window_id); + } + + Ok(()) + } + + #[inline] + fn ime_capabilities(&self) -> Option { + self.popup_state.lock().unwrap().ime_allowed() + } + + fn focus_window(&self) {} + + fn has_focus(&self) -> bool { + self.popup_state.lock().unwrap().has_focus() + } + + fn request_user_attention(&self, request_type: Option) { + self.handles.request_user_attention(self.surface(), request_type); + } + + fn set_theme(&self, _theme: Option) { + // A popup does not have a frame + } + + fn theme(&self) -> Option { + // A popup does not have a frame + None + } + + fn set_content_protected(&self, _protected: bool) {} + + fn set_cursor(&self, cursor: Cursor) { + let popup_state = &mut self.popup_state.lock().unwrap(); + + match cursor { + Cursor::Icon(icon) => popup_state.set_cursor(icon), + Cursor::Custom(cursor) => popup_state.set_custom_cursor(cursor), + } + } + + fn set_cursor_position(&self, position: Position) -> Result<(), RequestError> { + let scale_factor = self.scale_factor(); + let position = position.to_logical(scale_factor); + self.popup_state + .lock() + .unwrap() + .set_cursor_position(position) + // Request redraw on success, since the state is double buffered. + .map(|_| self.request_redraw()) + } + + fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), RequestError> { + self.popup_state.lock().unwrap().set_cursor_grab(mode) + } + + fn set_cursor_visible(&self, visible: bool) { + self.popup_state.lock().unwrap().set_cursor_visible(visible); + } + + fn drag_window(&self) -> Result<(), RequestError> { + // Popup does not support dragging + Err(RequestError::Ignored) + } + + fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), RequestError> { + // Popup does not support dragging + Err(RequestError::Ignored) + } + + fn show_window_menu(&self, _position: Position) { + // A popup does not have a menu + } + + fn set_cursor_hittest(&self, hittest: bool) -> Result<(), RequestError> { + self.handles.set_cursor_hittest(self.surface(), hittest) + } + + fn current_monitor(&self) -> Option { + let data = self.surface().data::()?; + data.outputs() + .next() + .map(MonitorHandle::new) + .map(|monitor| CoreMonitorHandle(Arc::new(monitor))) + } + + fn available_monitors(&self) -> Box> { + self.handles.available_monitors() + } + + fn primary_monitor(&self) -> Option { + // NOTE: There's no such concept on Wayland. + None + } + + /// Get the raw-window-handle v0.6 display handle. + fn rwh_06_display_handle(&self) -> &dyn rwh_06::HasDisplayHandle { + self + } + + /// Get the raw-window-handle v0.6 window handle. + fn rwh_06_window_handle(&self) -> &dyn rwh_06::HasWindowHandle { + self + } +} + +impl Drop for Popup { + fn drop(&mut self) { + self.handles.window_requests.closed.store(true, Ordering::Relaxed); + self.handles.event_loop_awakener.ping(); + } +} + +impl rwh_06::HasWindowHandle for Popup { + fn window_handle(&self) -> Result, rwh_06::HandleError> { + let raw = rwh_06::WaylandWindowHandle::new({ + let state = self.popup_state.lock().unwrap(); + let ptr = state.window.wl_surface().id().as_ptr(); + std::ptr::NonNull::new(ptr as *mut _).expect("wl_surface will never be null") + }); + + unsafe { Ok(rwh_06::WindowHandle::borrow_raw(raw.into())) } + } +} + +impl rwh_06::HasDisplayHandle for Popup { + fn display_handle(&self) -> Result, rwh_06::HandleError> { + let raw = rwh_06::WaylandDisplayHandle::new({ + let ptr = self.display.id().as_ptr(); + std::ptr::NonNull::new(ptr as *mut _).expect("wl_proxy should never be null") + }); + + unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw.into())) } + } +} diff --git a/winit-wayland/src/state.rs b/winit-wayland/src/state.rs index 577c8c42ec..3cbc845d28 100644 --- a/winit-wayland/src/state.rs +++ b/winit-wayland/src/state.rs @@ -16,6 +16,7 @@ use sctk::seat::SeatState; use sctk::seat::pointer::ThemedPointer; use sctk::shell::WaylandSurface; use sctk::shell::xdg::XdgShell; +use sctk::shell::xdg::popup::{Popup as XdgPopup, PopupConfigure, PopupHandler}; use sctk::shell::xdg::window::{Window, WindowConfigure, WindowHandler}; use sctk::shm::slot::SlotPool; use sctk::shm::{Shm, ShmHandler}; @@ -35,7 +36,8 @@ use crate::types::wp_tablet_input_v2::TabletManager; use crate::types::wp_viewporter::ViewporterState; use crate::types::xdg_activation::XdgActivationState; use crate::types::xdg_toplevel_icon_manager::XdgToplevelIconManagerState; -use crate::window::{WindowRequests, WindowState}; +use crate::window::WindowState; +use crate::window::handles::WindowRequests; /// Winit's Wayland state. #[derive(Debug)] @@ -292,24 +294,24 @@ impl WindowHandler for WinitState { ) { let window_id = super::make_wid(window.wl_surface()); - let pos = if let Some(pos) = + let index = if let Some(index) = self.window_compositor_updates.iter().position(|update| update.window_id == window_id) { - pos + index } else { self.window_compositor_updates.push(WindowCompositorUpdate::new(window_id)); self.window_compositor_updates.len() - 1 }; // Populate the configure to the window. - self.window_compositor_updates[pos].resized |= self + self.window_compositor_updates[index].resized |= self .windows .get_mut() .get_mut(&window_id) .expect("got configure for dead window.") .lock() .unwrap() - .configure(configure, &self.shm, &self.subcompositor_state); + .configure_window(configure, &self.shm, &self.subcompositor_state); // NOTE: configure demands wl_surface::commit, however winit doesn't commit on behalf of the // users, since it can break a lot of things, thus it'll ask users to redraw instead. @@ -325,6 +327,54 @@ impl WindowHandler for WinitState { } } +impl PopupHandler for WinitState { + fn configure( + &mut self, + _: &Connection, + _: &QueueHandle, + popup: &XdgPopup, + configure: PopupConfigure, + ) { + let window_id = super::make_wid(popup.wl_surface()); + + if let Some(index) = + self.window_compositor_updates.iter().position(|update| update.window_id == window_id) + { + index + } else { + self.window_compositor_updates.push(WindowCompositorUpdate::new(window_id)); + self.window_compositor_updates.len() - 1 + }; + + // Populate the configure to the window. + // self.window_compositor_updates[index].resized |= false; + self.windows + .get_mut() + .get_mut(&window_id) + .expect("got configure for dead window.") + .lock() + .unwrap() + .configure_popup(configure); + + // NOTE: configure demands wl_surface::commit, however winit doesn't commit on behalf of the + // users, since it can break a lot of things, thus it'll ask users to redraw instead + self.window_requests + .get_mut() + .get(&window_id) + .unwrap() + .redraw_requested + .store(true, Ordering::Relaxed); + + // Manually mark that we've got an event, since configure may not generate a resize. + self.dispatched_events = true; + } + + fn done(&mut self, _: &Connection, _: &QueueHandle, popup: &XdgPopup) { + let window_id = super::make_wid(popup.wl_surface()); + Self::queue_close(&mut self.window_compositor_updates, window_id); + } +} + impl OutputHandler for WinitState { fn output_state(&mut self) -> &mut OutputState { &mut self.output_state @@ -451,4 +501,5 @@ sctk::delegate_output!(WinitState); sctk::delegate_registry!(WinitState); sctk::delegate_shm!(WinitState); sctk::delegate_xdg_shell!(WinitState); +sctk::delegate_xdg_popup!(WinitState); sctk::delegate_xdg_window!(WinitState); diff --git a/winit-wayland/src/window/handles.rs b/winit-wayland/src/window/handles.rs new file mode 100644 index 0000000000..5e33aa8185 --- /dev/null +++ b/winit-wayland/src/window/handles.rs @@ -0,0 +1,142 @@ +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex}; + +use sctk::compositor::{CompositorState, Region}; +use sctk::reexports::client::QueueHandle; +use sctk::reexports::client::protocol::wl_surface::WlSurface; +use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_v1::XdgActivationV1; +use tracing::warn; +use winit_core::error::RequestError; +use winit_core::event::WindowEvent; +use winit_core::monitor::MonitorHandle as CoreMonitorHandle; +use winit_core::window::{UserAttentionType, WindowId}; + +use super::super::event_loop::sink::EventSink; +use super::super::output::MonitorHandle; +use super::super::state::WinitState; +use super::super::types::xdg_activation::XdgActivationTokenData; + +#[derive(Debug)] +pub struct Handles { + /// Handle to the main queue to perform requests. + pub(crate) queue_handle: QueueHandle, + + /// Window requests to the event loop. + pub(crate) window_requests: Arc, + + /// Observed monitors. + pub(crate) monitors: Arc>>, + + /// Source to wake-up the event-loop for window requests. + pub(crate) event_loop_awakener: calloop::ping::Ping, + + /// The event sink to deliver synthetic events. + pub(crate) window_events_sink: Arc>, + + /// Xdg activation to request user attention. + pub(crate) xdg_activation: Option, + + /// The state of the requested attention from the `xdg_activation`. + pub(crate) attention_requested: Arc, + + /// Compositor to handle WlRegion stuff. + pub(crate) compositor: Arc, +} + +impl Handles { + pub(crate) fn request_redraw(&self) { + // NOTE: try to not wake up the loop when the event was already scheduled and not yet + // processed by the loop, because if at this point the value was `true` it could only + // mean that the loop still haven't dispatched the value to the client and will do + // eventually, resetting it to `false`. + if self + .window_requests + .redraw_requested + .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) + .is_ok() + { + self.event_loop_awakener.ping(); + } + } + + pub(crate) fn push_window_event(&self, event: WindowEvent, id: WindowId) { + self.window_events_sink.lock().unwrap().push_window_event(event, id); + self.event_loop_awakener.ping(); + } + + pub(crate) fn available_monitors(&self) -> Box> { + Box::new( + self.monitors + .lock() + .unwrap() + .clone() + .into_iter() + .map(|inner| CoreMonitorHandle(Arc::new(inner))), + ) + } + + pub(crate) fn request_user_attention( + &self, + surface: &WlSurface, + request_type: Option, + ) { + let xdg_activation = match self.xdg_activation.as_ref() { + Some(xdg_activation) => xdg_activation, + None => { + warn!("`request_user_attention` isn't supported"); + return; + }, + }; + + // Urgency is only removed by the compositor and there's no need to raise urgency when it + // was already raised. + if request_type.is_none() || self.attention_requested.load(Ordering::Relaxed) { + return; + } + + self.attention_requested.store(true, Ordering::Relaxed); + let data = XdgActivationTokenData::Attention(( + surface.clone(), + Arc::downgrade(&self.attention_requested), + )); + let xdg_activation_token = xdg_activation.get_activation_token(&self.queue_handle, data); + xdg_activation_token.set_surface(surface); + xdg_activation_token.commit(); + } + + pub(crate) fn set_cursor_hittest( + &self, + surface: &WlSurface, + hittest: bool, + ) -> Result<(), RequestError> { + if hittest { + surface.set_input_region(None); + Ok(()) + } else { + let region = Region::new(&*self.compositor).map_err(|err| os_error!(err))?; + region.add(0, 0, 0, 0); + surface.set_input_region(Some(region.wl_region())); + Ok(()) + } + } +} + +/// The request from the window to the event loop. +#[derive(Debug)] +pub struct WindowRequests { + /// The window was closed. + pub closed: AtomicBool, + + /// Redraw Requested. + pub redraw_requested: AtomicBool, +} + +impl WindowRequests { + pub fn take_closed(&self) -> bool { + self.closed.swap(false, Ordering::Relaxed) + } + + pub fn take_redraw_requested(&self) -> bool { + self.redraw_requested.swap(false, Ordering::Relaxed) + } +} diff --git a/winit-wayland/src/window/mod.rs b/winit-wayland/src/window/mod.rs index 2f2d973219..44eec7770b 100644 --- a/winit-wayland/src/window/mod.rs +++ b/winit-wayland/src/window/mod.rs @@ -6,11 +6,11 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; use dpi::{LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size}; -use sctk::compositor::{CompositorState, Region, SurfaceData}; +use rwh_06::RawWindowHandle; +use sctk::compositor::SurfaceData; +use sctk::reexports::client::Proxy; use sctk::reexports::client::protocol::wl_display::WlDisplay; use sctk::reexports::client::protocol::wl_surface::WlSurface; -use sctk::reexports::client::{Proxy, QueueHandle}; -use sctk::reexports::protocols::xdg::activation::v1::client::xdg_activation_v1::XdgActivationV1; use sctk::shell::WaylandSurface; use sctk::shell::xdg::window::{Window as SctkWindow, WindowDecorations}; use tracing::warn; @@ -26,15 +26,15 @@ use winit_core::window::{ }; use super::ActiveEventLoop; -use super::event_loop::sink::EventSink; use super::output::MonitorHandle; -use super::state::WinitState; use super::types::xdg_activation::XdgActivationTokenData; +use crate::window::state::WindowType; use crate::{WindowAttributesWayland, output}; - pub(crate) mod state; - pub use state::WindowState; +pub(crate) mod handles; +pub use handles::Handles; +use handles::WindowRequests; /// The Wayland window. #[derive(Debug)] @@ -48,33 +48,12 @@ pub struct Window { /// The state of the window. window_state: Arc>, - /// Compositor to handle WlRegion stuff. - compositor: Arc, - /// The wayland display used solely for raw window handle. #[allow(dead_code)] display: WlDisplay, - /// Xdg activation to request user attention. - xdg_activation: Option, - - /// The state of the requested attention from the `xdg_activation`. - attention_requested: Arc, - - /// Handle to the main queue to perform requests. - queue_handle: QueueHandle, - - /// Window requests to the event loop. - window_requests: Arc, - - /// Observed monitors. - monitors: Arc>>, - - /// Source to wake-up the event-loop for window requests. - event_loop_awakener: calloop::ping::Ping, - - /// The event sink to deliver synthetic events. - window_events_sink: Arc>, + /// Common handles like queue, window requests, monitors and so on + handles: Handles, } impl Window { @@ -112,14 +91,24 @@ impl Window { .and_then(|p| p.cast::().ok()) .unwrap_or_default(); + let mut scale_factor = None; + if let Some(RawWindowHandle::Wayland(handle)) = attributes.parent_window() { + if let Some(s) = + state.windows.borrow().get(&WindowId::from_raw(handle.surface.as_ptr() as usize)) + { + scale_factor = Some(s.lock().unwrap().scale_factor()); + } + } + let scale_factor = scale_factor.unwrap_or(1.0); + let mut window_state = WindowState::new( - event_loop_window_target.handle.clone(), - &event_loop_window_target.queue_handle, + event_loop_window_target, &state, size, - window.clone(), + state::WindowType::Window((window.clone(), None)), attributes.preferred_theme, prefer_csd, + scale_factor, ); window_state.set_window_icon(attributes.window_icon); @@ -219,16 +208,22 @@ impl Window { Ok(Self { window, display, - monitors, + window_id, - compositor, window_state, - queue_handle, - xdg_activation, - attention_requested: Arc::new(AtomicBool::new(false)), - event_loop_awakener, - window_requests, - window_events_sink, + + handles: Handles { + queue_handle, + window_requests, + monitors, + event_loop_awakener, + window_events_sink, + + compositor, + + xdg_activation, + attention_requested: Arc::new(AtomicBool::new(false)), + }, }) } @@ -239,7 +234,7 @@ impl Window { impl Window { pub fn request_activation_token(&self) -> Result { - let xdg_activation = match self.xdg_activation.as_ref() { + let xdg_activation = match self.handles.xdg_activation.as_ref() { Some(xdg_activation) => xdg_activation, None => return Err(NotSupportedError::new("xdg_activation_v1 is not available").into()), }; @@ -247,7 +242,8 @@ impl Window { let serial = AsyncRequestSerial::get(); let data = XdgActivationTokenData::Obtain((self.window_id, serial)); - let xdg_activation_token = xdg_activation.get_activation_token(&self.queue_handle, data); + let xdg_activation_token = + xdg_activation.get_activation_token(&self.handles.queue_handle, data); xdg_activation_token.set_surface(self.surface()); xdg_activation_token.commit(); @@ -262,8 +258,8 @@ impl Window { impl Drop for Window { fn drop(&mut self) { - self.window_requests.closed.store(true, Ordering::Relaxed); - self.event_loop_awakener.ping(); + self.handles.window_requests.closed.store(true, Ordering::Relaxed); + self.handles.event_loop_awakener.ping(); } } @@ -295,18 +291,7 @@ impl CoreWindow for Window { } fn request_redraw(&self) { - // NOTE: try to not wake up the loop when the event was already scheduled and not yet - // processed by the loop, because if at this point the value was `true` it could only - // mean that the loop still haven't dispatched the value to the client and will do - // eventually, resetting it to `false`. - if self - .window_requests - .redraw_requested - .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) - .is_ok() - { - self.event_loop_awakener.ping(); - } + self.handles.request_redraw(); } #[inline] @@ -449,13 +434,14 @@ impl CoreWindow for Window { } fn is_maximized(&self) -> bool { - self.window_state - .lock() - .unwrap() - .last_configure - .as_ref() - .map(|last_configure| last_configure.is_maximized()) - .unwrap_or_default() + if let WindowType::Window((_, last_configure)) = &self.window_state.lock().unwrap().window { + last_configure + .as_ref() + .map(|last_configure| last_configure.is_maximized()) + .unwrap_or_default() + } else { + false + } } fn set_fullscreen(&self, fullscreen: Option) { @@ -475,14 +461,16 @@ impl CoreWindow for Window { } fn fullscreen(&self) -> Option { - let is_fullscreen = self - .window_state - .lock() - .unwrap() - .last_configure - .as_ref() - .map(|last_configure| last_configure.is_fullscreen()) - .unwrap_or_default(); + let is_fullscreen = if let WindowType::Window((_, last_configure)) = + &self.window_state.lock().unwrap().window + { + last_configure + .as_ref() + .map(|last_configure| last_configure.is_fullscreen()) + .unwrap_or_default() + } else { + false + }; if is_fullscreen { let current_monitor = self.current_monitor(); @@ -526,8 +514,7 @@ impl CoreWindow for Window { if let Some(allowed) = state_changed { let event = WindowEvent::Ime(if allowed { Ime::Enabled } else { Ime::Disabled }); - self.window_events_sink.lock().unwrap().push_window_event(event, self.window_id); - self.event_loop_awakener.ping(); + self.handles.push_window_event(event, self.window_id); } Ok(()) @@ -545,29 +532,7 @@ impl CoreWindow for Window { } fn request_user_attention(&self, request_type: Option) { - let xdg_activation = match self.xdg_activation.as_ref() { - Some(xdg_activation) => xdg_activation, - None => { - warn!("`request_user_attention` isn't supported"); - return; - }, - }; - - // Urgency is only removed by the compositor and there's no need to raise urgency when it - // was already raised. - if request_type.is_none() || self.attention_requested.load(Ordering::Relaxed) { - return; - } - - self.attention_requested.store(true, Ordering::Relaxed); - let surface = self.surface().clone(); - let data = XdgActivationTokenData::Attention(( - surface.clone(), - Arc::downgrade(&self.attention_requested), - )); - let xdg_activation_token = xdg_activation.get_activation_token(&self.queue_handle, data); - xdg_activation_token.set_surface(&surface); - xdg_activation_token.commit(); + self.handles.request_user_attention(self.surface(), request_type); } fn set_theme(&self, theme: Option) { @@ -623,21 +588,11 @@ impl CoreWindow for Window { } fn set_cursor_hittest(&self, hittest: bool) -> Result<(), RequestError> { - let surface = self.window.wl_surface(); - - if hittest { - surface.set_input_region(None); - Ok(()) - } else { - let region = Region::new(&*self.compositor).map_err(|err| os_error!(err))?; - region.add(0, 0, 0, 0); - surface.set_input_region(Some(region.wl_region())); - Ok(()) - } + self.handles.set_cursor_hittest(self.surface(), hittest) } fn current_monitor(&self) -> Option { - let data = self.window.wl_surface().data::()?; + let data = self.surface().data::()?; data.outputs() .next() .map(MonitorHandle::new) @@ -645,14 +600,7 @@ impl CoreWindow for Window { } fn available_monitors(&self) -> Box> { - Box::new( - self.monitors - .lock() - .unwrap() - .clone() - .into_iter() - .map(|inner| CoreMonitorHandle(Arc::new(inner))), - ) + self.handles.available_monitors() } fn primary_monitor(&self) -> Option { @@ -670,23 +618,3 @@ impl CoreWindow for Window { self } } - -/// The request from the window to the event loop. -#[derive(Debug)] -pub struct WindowRequests { - /// The window was closed. - pub closed: AtomicBool, - - /// Redraw Requested. - pub redraw_requested: AtomicBool, -} - -impl WindowRequests { - pub fn take_closed(&self) -> bool { - self.closed.swap(false, Ordering::Relaxed) - } - - pub fn take_redraw_requested(&self) -> bool { - self.redraw_requested.swap(false, Ordering::Relaxed) - } -} diff --git a/winit-wayland/src/window/state.rs b/winit-wayland/src/window/state.rs index dcac4b69d1..ba6e16ce26 100644 --- a/winit-wayland/src/window/state.rs +++ b/winit-wayland/src/window/state.rs @@ -22,8 +22,9 @@ use sctk::reexports::protocols::wp::viewporter::client::wp_viewport::WpViewport; use sctk::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge as XdgResizeEdge; use sctk::seat::pointer::{PointerDataExt, ThemedPointer}; use sctk::shell::WaylandSurface; -use sctk::shell::xdg::XdgSurface; +use sctk::shell::xdg::popup::{ConfigureKind, Popup, PopupConfigure}; use sctk::shell::xdg::window::{DecorationMode, Window, WindowConfigure}; +use sctk::shell::xdg::{XdgPositioner, XdgSurface}; use sctk::shm::Shm; use sctk::shm::slot::SlotPool; use sctk::subcompositor::SubcompositorState; @@ -36,7 +37,6 @@ use winit_core::window::{ }; use crate::event_loop::OwnedDisplayHandle; -use crate::logical_to_physical_rounded; use crate::seat::{ PointerConstraintsState, TextInputClientState, WinitPointerData, WinitPointerDataExt, ZwpTextInputV3Ext, @@ -45,6 +45,7 @@ use crate::state::{WindowCompositorUpdate, WinitState}; use crate::types::bgr_effects::{BgrEffectManager, SurfaceBlurEffect}; use crate::types::cursor::{CustomCursor, SelectedCursor, WaylandCustomCursor}; use crate::types::xdg_toplevel_icon_manager::ToplevelIcon; +use crate::{ActiveEventLoop, logical_to_physical_rounded}; #[cfg(feature = "sctk-adwaita")] pub type WinitFrame = sctk_adwaita::AdwaitaFrame; @@ -54,6 +55,40 @@ pub type WinitFrame = sctk::shell::xdg::fallback_frame::FallbackFrame = LogicalSize::new(2, 1); +#[derive(Debug)] +pub enum WindowType { + // The option is the last received configure + Window((Window, Option)), + Popup((Popup, XdgPositioner, Option)), +} + +impl WindowType { + pub fn is_configured(&self) -> bool { + match self { + Self::Window((_, last_configure)) => last_configure.is_some(), + Self::Popup((_, _, last_configure)) => last_configure.is_some(), + } + } +} + +impl WaylandSurface for WindowType { + fn wl_surface(&self) -> &wayland_client::protocol::wl_surface::WlSurface { + match self { + Self::Window((window, _)) => window.wl_surface(), + Self::Popup((popup, ..)) => popup.wl_surface(), + } + } +} + +impl XdgSurface for WindowType { + fn xdg_surface(&self) -> &wayland_protocols::xdg::shell::client::xdg_surface::XdgSurface { + match self { + Self::Window((window, _)) => window.xdg_surface(), + Self::Popup((popup, ..)) => popup.xdg_surface(), + } + } +} + /// The state of the window which is being updated from the [`WinitState`]. #[derive(Debug)] pub struct WindowState { @@ -63,9 +98,6 @@ pub struct WindowState { /// The `Shm` to set cursor. pub shm: WlShm, - /// The last received configure. - pub last_configure: Option, - /// The pointers observed on the window. pub pointers: Vec>>, @@ -164,7 +196,7 @@ pub struct WindowState { has_pending_move: Option, /// The underlying SCTK window. - pub window: Window, + pub window: WindowType, // NOTE: The spec says that destroying parent(`window` in our case), will unmap the // subsurfaces. Thus to achieve atomic unmap of the client, drop the decorations @@ -177,14 +209,16 @@ pub struct WindowState { impl WindowState { /// Create new window state. pub fn new( - handle: Arc, - queue_handle: &QueueHandle, + active_event_loop: &ActiveEventLoop, winit_state: &WinitState, initial_size: Size, - window: Window, + window: WindowType, theme: Option, prefer_csd: bool, + scale_factor: f64, ) -> Self { + let handle = active_event_loop.handle.clone(); + let queue_handle = &active_event_loop.queue_handle; let compositor = winit_state.compositor_state.clone(); let pointer_constraints = winit_state.pointer_constraints.clone(); let viewport = winit_state @@ -220,7 +254,6 @@ impl WindowState { seat_focus: Default::default(), has_pending_move: None, text_input_state: None, - last_configure: None, max_surface_size: None, min_surface_size: MIN_WINDOW_SIZE, resize_increments: None, @@ -228,7 +261,7 @@ impl WindowState { pointers: Default::default(), queue_handle: queue_handle.clone(), resizable: true, - scale_factor: 1., + scale_factor, shm: winit_state.shm.wl_shm().clone(), image_pool: winit_state.image_pool.clone(), size: initial_size.to_logical(1.), @@ -280,8 +313,46 @@ impl WindowState { FrameCallbackState::Requested => (), } } + pub fn configure_popup(&mut self, configure: PopupConfigure) { + // NOTE: when using fractional scaling or wl_compositor@v6 the scaling + // should be delivered before the first configure, thus apply it to + // properly scale the physical sizes provided by the users. + if let Some(initial_size) = self.initial_size.take() { + self.size = initial_size.to_logical(self.scale_factor()); + } + + // The popup was constrained to a different size by the compositor + assert!(configure.width >= 0); + assert!(configure.height >= 0); + let constrained = self.size.width != configure.width as u32 + || self.size.height != configure.height as u32; + let new_size = + LogicalSize { width: configure.width as u32, height: configure.height as u32 }; + + // NOTE: Set the configure before doing a resize, since we query it during it. + if let WindowType::Popup((_, _, last_configure)) = &mut self.window { + let kind = configure.kind.clone(); + *last_configure = Some(configure); + + // Always resize on the initial configure to properly initialize the viewport + // destination and window geometry. This is required for fractional scaling + // to work correctly: without calling resize(), viewport.set_destination() + // is never called, and the compositor would interpret the buffer size as + // logical pixels, making the popup appear at the wrong size. Also resize + // when the compositor constrained us to a different size than requested. + if matches!(kind, ConfigureKind::Initial) || constrained { + self.resize(new_size); + } + } else { + tracing::error!( + "configure_popup called for window type unequal of popup. This should never \ + happen, because we start configuring with a popup" + ); + return; + } + } - pub fn configure( + pub fn configure_window( &mut self, configure: WindowConfigure, shm: &Shm, @@ -399,25 +470,33 @@ impl WindowState { } let new_state = configure.state; - let old_state = self.last_configure.as_ref().map(|configure| configure.state); - - let state_change_requires_resize = old_state - .map(|old_state| { - !old_state - .symmetric_difference(new_state) - .difference(XdgWindowState::ACTIVATED | XdgWindowState::SUSPENDED) - .is_empty() - }) - // NOTE: `None` is present for the initial configure, thus we must always resize. - .unwrap_or(true); - - // NOTE: Set the configure before doing a resize, since we query it during it. - self.last_configure = Some(configure); - - if state_change_requires_resize || new_size != self.surface_size() { - self.resize(new_size); - true + if let WindowType::Window((_, last_configure)) = &mut self.window { + let old_state = last_configure.as_ref().map(|configure| configure.state); + + let state_change_requires_resize = old_state + .map(|old_state| { + !old_state + .symmetric_difference(new_state) + .difference(XdgWindowState::ACTIVATED | XdgWindowState::SUSPENDED) + .is_empty() + }) + // NOTE: `None` is present for the initial configure, thus we must always resize. + .unwrap_or(true); + + // NOTE: Set the configure before doing a resize, since we query it during it. + *last_configure = Some(configure); + + if state_change_requires_resize || new_size != self.surface_size() { + self.resize(new_size); + true + } else { + false + } } else { + tracing::error!( + "configure_window called for window type unequal of `Window`. This should never \ + happen, because we start configuring with a `Window`" + ); false } } @@ -450,29 +529,42 @@ impl WindowState { /// Start interacting drag resize. pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), RequestError> { - let xdg_toplevel = self.window.xdg_toplevel(); + match &self.window { + WindowType::Window((window, _)) => { + let xdg_toplevel = window.xdg_toplevel(); - // TODO(kchibisov) handle touch serials. - self.apply_on_pointer(|_, data| { - let serial = data.latest_button_serial(); - let seat = data.seat(); - xdg_toplevel.resize(seat, serial, resize_direction_to_xdg(direction)); - }); - - Ok(()) + // TODO(kchibisov) handle touch serials. + self.apply_on_pointer(|_, data| { + let serial = data.latest_button_serial(); + let seat = data.seat(); + xdg_toplevel.resize(seat, serial, resize_direction_to_xdg(direction)); + }); + Ok(()) + }, + WindowType::Popup(..) => Err(RequestError::NotSupported(NotSupportedError::new( + "Drag resize for popup not supported", + ))), + } } /// Start the window drag. pub fn drag_window(&self) -> Result<(), RequestError> { - let xdg_toplevel = self.window.xdg_toplevel(); - // TODO(kchibisov) handle touch serials. - self.apply_on_pointer(|_, data| { - let serial = data.latest_button_serial(); - let seat = data.seat(); - xdg_toplevel._move(seat, serial); - }); + match &self.window { + WindowType::Window((window, ..)) => { + let xdg_toplevel = window.xdg_toplevel(); + // TODO(kchibisov) handle touch serials. + self.apply_on_pointer(|_, data| { + let serial = data.latest_button_serial(); + let seat = data.seat(); + xdg_toplevel._move(seat, serial); + }); - Ok(()) + Ok(()) + }, + WindowType::Popup(..) => Err(RequestError::NotSupported(NotSupportedError::new( + "Drag for popup not supported", + ))), + } } /// Tells whether the window should be closed. @@ -487,32 +579,37 @@ impl WindowState { window_id: WindowId, updates: &mut Vec, ) -> Option { - match self.frame.as_mut()?.on_click(timestamp, click, pressed)? { - FrameAction::Minimize => self.window.set_minimized(), - FrameAction::Maximize => self.window.set_maximized(), - FrameAction::UnMaximize => self.window.unset_maximized(), - FrameAction::Close => WinitState::queue_close(updates, window_id), - FrameAction::Move => self.has_pending_move = Some(serial), - FrameAction::Resize(edge) => { - let edge = match edge { - ResizeEdge::None => XdgResizeEdge::None, - ResizeEdge::Top => XdgResizeEdge::Top, - ResizeEdge::Bottom => XdgResizeEdge::Bottom, - ResizeEdge::Left => XdgResizeEdge::Left, - ResizeEdge::TopLeft => XdgResizeEdge::TopLeft, - ResizeEdge::BottomLeft => XdgResizeEdge::BottomLeft, - ResizeEdge::Right => XdgResizeEdge::Right, - ResizeEdge::TopRight => XdgResizeEdge::TopRight, - ResizeEdge::BottomRight => XdgResizeEdge::BottomRight, - _ => return None, + match &self.window { + WindowType::Window((window, ..)) => { + match self.frame.as_mut()?.on_click(timestamp, click, pressed)? { + FrameAction::Minimize => window.set_minimized(), + FrameAction::Maximize => window.set_maximized(), + FrameAction::UnMaximize => window.unset_maximized(), + FrameAction::Close => WinitState::queue_close(updates, window_id), + FrameAction::Move => self.has_pending_move = Some(serial), + FrameAction::Resize(edge) => { + let edge = match edge { + ResizeEdge::None => XdgResizeEdge::None, + ResizeEdge::Top => XdgResizeEdge::Top, + ResizeEdge::Bottom => XdgResizeEdge::Bottom, + ResizeEdge::Left => XdgResizeEdge::Left, + ResizeEdge::TopLeft => XdgResizeEdge::TopLeft, + ResizeEdge::BottomLeft => XdgResizeEdge::BottomLeft, + ResizeEdge::Right => XdgResizeEdge::Right, + ResizeEdge::TopRight => XdgResizeEdge::TopRight, + ResizeEdge::BottomRight => XdgResizeEdge::BottomRight, + _ => return None, + }; + window.resize(seat, serial, edge); + }, + FrameAction::ShowMenu(x, y) => window.show_window_menu(seat, serial, (x, y)), + _ => (), }; - self.window.resize(seat, serial, edge); - }, - FrameAction::ShowMenu(x, y) => self.window.show_window_menu(seat, serial, (x, y)), - _ => (), - }; - Some(false) + Some(false) + }, + WindowType::Popup(..) => None, + } } pub fn frame_point_left(&mut self) { @@ -530,21 +627,26 @@ impl WindowState { x: f64, y: f64, ) -> Option { - // Take the serial if we had any, so it doesn't stick around. - let serial = self.has_pending_move.take(); - - if let Some(frame) = self.frame.as_mut() { - let cursor = frame.click_point_moved(timestamp, &surface.id(), x, y); - // If we have a cursor change, that means that cursor is over the decorations, - // so try to apply move. - if let Some(serial) = cursor.is_some().then_some(serial).flatten() { - self.window.move_(seat, serial); - None - } else { - cursor - } - } else { - None + match &self.window { + WindowType::Window((window, ..)) => { + // Take the serial if we had any, so it doesn't stick around. + let serial = self.has_pending_move.take(); + + if let Some(frame) = self.frame.as_mut() { + let cursor = frame.click_point_moved(timestamp, &surface.id(), x, y); + // If we have a cursor change, that means that cursor is over the decorations, + // so try to apply move. + if let Some(serial) = cursor.is_some().then_some(serial).flatten() { + window.move_(seat, serial); + None + } else { + cursor + } + } else { + None + } + }, + WindowType::Popup(..) => None, } } @@ -605,21 +707,25 @@ impl WindowState { /// Whether the window received initial configure event from the compositor. #[inline] pub fn is_configured(&self) -> bool { - self.last_configure.is_some() + self.window.is_configured() } #[inline] pub fn is_decorated(&mut self) -> bool { - let csd = self - .last_configure - .as_ref() - .map(|configure| configure.decoration_mode == DecorationMode::Client) - .unwrap_or(false); - if let Some(frame) = csd.then_some(self.frame.as_ref()).flatten() { - !frame.is_hidden() - } else { - // Server side decorations. - true + match &mut self.window { + WindowType::Window((_, last_configuration)) => { + let csd = last_configuration + .as_ref() + .map(|configure| configure.decoration_mode == DecorationMode::Client) + .unwrap_or(false); + if let Some(frame) = csd.then_some(self.frame.as_ref()).flatten() { + !frame.is_hidden() + } else { + // Server side decorations. + true + } + }, + WindowType::Popup(..) => false, // Popup window does not have any decoration } } @@ -632,6 +738,13 @@ impl WindowState { .unwrap_or(self.size) } + /// Get the origin of the content surface by considering the client side decoration if available + /// This is required for example when creating a popup, because as parent a xdg_surface must be + /// passed but the frame is only a wl_surface + pub fn content_surface_origin(&self) -> LogicalPosition { + self.frame.as_ref().map(|frame| frame.location().into()).unwrap_or_else(|| (0, 0).into()) + } + /// Register pointer on the top-level. pub fn pointer_entered(&mut self, added: Weak>) { self.pointers.push(added); @@ -694,8 +807,17 @@ impl WindowState { /// Try to resize the window when the user can do so. pub fn request_surface_size(&mut self, surface_size: Size) -> PhysicalSize { - if self.last_configure.as_ref().map(Self::is_stateless).unwrap_or(true) { - self.resize(surface_size.to_logical(self.scale_factor())) + match &self.window { + WindowType::Window((_, last_configure)) => { + if last_configure.as_ref().map(Self::is_stateless).unwrap_or(true) { + self.resize(surface_size.to_logical(self.scale_factor())) + } + }, + WindowType::Popup((popup, positioner, _)) => { + let size = surface_size.to_logical(self.scale_factor()); + positioner.set_size(size.width, size.height); + popup.reposition(positioner, 0); + }, } logical_to_physical_rounded(self.surface_size(), self.scale_factor()) @@ -706,8 +828,10 @@ impl WindowState { self.size = surface_size; // Update the stateless size. - if Some(true) == self.last_configure.as_ref().map(Self::is_stateless) { - self.stateless_size = surface_size; + if let WindowType::Window((_, last_configure)) = &mut self.window { + if let Some(true) = last_configure.as_ref().map(Self::is_stateless) { + self.stateless_size = surface_size; + } } // Update the inner frame. @@ -843,33 +967,37 @@ impl WindowState { /// Set maximum inner window size. pub fn set_min_surface_size(&mut self, size: Option>) { - // Ensure that the window has the right minimum size. - let mut size = size.unwrap_or(MIN_WINDOW_SIZE); - size.width = size.width.max(MIN_WINDOW_SIZE.width); - size.height = size.height.max(MIN_WINDOW_SIZE.height); - - // Add the borders. - let size = self - .frame - .as_ref() - .map(|frame| frame.add_borders(size.width, size.height).into()) - .unwrap_or(size); + if let WindowType::Window((window, _)) = &self.window { + // Ensure that the window has the right minimum size. + let mut size = size.unwrap_or(MIN_WINDOW_SIZE); + size.width = size.width.max(MIN_WINDOW_SIZE.width); + size.height = size.height.max(MIN_WINDOW_SIZE.height); + + // Add the borders. + let size = self + .frame + .as_ref() + .map(|frame| frame.add_borders(size.width, size.height).into()) + .unwrap_or(size); - self.min_surface_size = size; - self.window.set_min_size(Some(size.into())); + self.min_surface_size = size; + window.set_min_size(Some(size.into())); + } } /// Set maximum inner window size. pub fn set_max_surface_size(&mut self, size: Option>) { - let size = size.map(|size| { - self.frame - .as_ref() - .map(|frame| frame.add_borders(size.width, size.height).into()) - .unwrap_or(size) - }); - - self.max_surface_size = size; - self.window.set_max_size(size.map(Into::into)); + if let WindowType::Window((window, _)) = &self.window { + let size = size.map(|size| { + self.frame + .as_ref() + .map(|frame| frame.add_borders(size.width, size.height).into()) + .unwrap_or(size) + }); + + self.max_surface_size = size; + window.set_max_size(size.map(Into::into)); + } } /// Set the CSD theme. @@ -966,12 +1094,14 @@ impl WindowState { } pub fn show_window_menu(&self, position: LogicalPosition) { - // TODO(kchibisov) handle touch serials. - self.apply_on_pointer(|_, data| { - let serial = data.latest_button_serial(); - let seat = data.seat(); - self.window.show_window_menu(seat, serial, position.into()); - }); + if let WindowType::Window((window, _)) = &self.window { + // TODO(kchibisov) handle touch serials. + self.apply_on_pointer(|_, data| { + let serial = data.latest_button_serial(); + let seat = data.seat(); + window.show_window_menu(seat, serial, position.into()); + }); + } } /// Set the position of the cursor. @@ -1022,22 +1152,29 @@ impl WindowState { self.decorate = decorate; - match self.last_configure.as_ref().map(|configure| configure.decoration_mode) { - Some(DecorationMode::Server) if !self.decorate => { - // To disable decorations we should request client and hide the frame. - self.window.request_decoration_mode(Some(DecorationMode::Client)) - }, - _ if self.decorate && self.prefer_csd => { - self.window.request_decoration_mode(Some(DecorationMode::Client)) - }, - _ if self.decorate => self.window.request_decoration_mode(Some(DecorationMode::Server)), - _ => (), - } + match &self.window { + WindowType::Window((window, last_configure)) => { + match last_configure.as_ref().map(|configure| configure.decoration_mode) { + Some(DecorationMode::Server) if !self.decorate => { + // To disable decorations we should request client and hide the frame. + window.request_decoration_mode(Some(DecorationMode::Client)) + }, + _ if self.decorate && self.prefer_csd => { + window.request_decoration_mode(Some(DecorationMode::Client)) + }, + _ if self.decorate => { + window.request_decoration_mode(Some(DecorationMode::Server)) + }, + _ => (), + } - if let Some(frame) = self.frame.as_mut() { - frame.set_hidden(!decorate); - // Force the resize. - self.resize(self.size); + if let Some(frame) = self.frame.as_mut() { + frame.set_hidden(!decorate); + // Force the resize. + self.resize(self.size); + } + }, + WindowType::Popup(..) => (), // Popup does not have any decoration } } @@ -1172,46 +1309,51 @@ impl WindowState { frame.set_title(&title); } - self.window.set_title(&title); + match &self.window { + WindowType::Window((window, ..)) => window.set_title(&title), + WindowType::Popup(..) => (), // Popup does not have any title + } self.title = title; } /// Set the window's icon pub fn set_window_icon(&mut self, window_icon: Option) { - let xdg_toplevel_icon_manager = match self.xdg_toplevel_icon_manager.as_ref() { - Some(xdg_toplevel_icon_manager) => xdg_toplevel_icon_manager, - None => { - warn!("`xdg_toplevel_icon_manager_v1` is not supported"); - return; - }, - }; + if let WindowType::Window((window, _)) = &self.window { + let xdg_toplevel_icon_manager = match self.xdg_toplevel_icon_manager.as_ref() { + Some(xdg_toplevel_icon_manager) => xdg_toplevel_icon_manager, + None => { + warn!("`xdg_toplevel_icon_manager_v1` is not supported"); + return; + }, + }; - let (toplevel_icon, xdg_toplevel_icon) = match window_icon { - Some(icon) => { - let mut image_pool = self.image_pool.lock().unwrap(); - let toplevel_icon = match ToplevelIcon::new(icon, &mut image_pool) { - Ok(toplevel_icon) => toplevel_icon, - Err(error) => { - warn!("Error setting window icon: {error}"); - return; - }, - }; + let (toplevel_icon, xdg_toplevel_icon) = match window_icon { + Some(icon) => { + let mut image_pool = self.image_pool.lock().unwrap(); + let toplevel_icon = match ToplevelIcon::new(icon, &mut image_pool) { + Ok(toplevel_icon) => toplevel_icon, + Err(error) => { + warn!("Error setting window icon: {error}"); + return; + }, + }; - let xdg_toplevel_icon = - xdg_toplevel_icon_manager.create_icon(&self.queue_handle, GlobalData); + let xdg_toplevel_icon = + xdg_toplevel_icon_manager.create_icon(&self.queue_handle, GlobalData); - toplevel_icon.add_buffer(&xdg_toplevel_icon); + toplevel_icon.add_buffer(&xdg_toplevel_icon); - (Some(toplevel_icon), Some(xdg_toplevel_icon)) - }, - None => (None, None), - }; + (Some(toplevel_icon), Some(xdg_toplevel_icon)) + }, + None => (None, None), + }; - xdg_toplevel_icon_manager.set_icon(self.window.xdg_toplevel(), xdg_toplevel_icon.as_ref()); - self.toplevel_icon = toplevel_icon; + xdg_toplevel_icon_manager.set_icon(window.xdg_toplevel(), xdg_toplevel_icon.as_ref()); + self.toplevel_icon = toplevel_icon; - if let Some(xdg_toplevel_icon) = xdg_toplevel_icon { - xdg_toplevel_icon.destroy(); + if let Some(xdg_toplevel_icon) = xdg_toplevel_icon { + xdg_toplevel_icon.destroy(); + } } } diff --git a/winit-win32/src/window.rs b/winit-win32/src/window.rs index dba95e9bd8..3c14cad6ba 100644 --- a/winit-win32/src/window.rs +++ b/winit-win32/src/window.rs @@ -36,7 +36,7 @@ use windows_sys::Win32::UI::Input::Touch::{RegisterTouchWindow, TWF_WANTPALM}; use windows_sys::Win32::UI::WindowsAndMessaging::{ CS_HREDRAW, CS_VREDRAW, CW_USEDEFAULT, CreateWindowExW, EnableMenuItem, FLASHW_ALL, FLASHW_STOP, FLASHW_TIMERNOFG, FLASHW_TRAY, FLASHWINFO, FlashWindowEx, GWLP_HINSTANCE, - GetClientRect, GetCursorPos, GetForegroundWindow, GetSystemMenu, GetSystemMetrics, + GetClientRect, GetCursorPos, GetForegroundWindow, GetParent, GetSystemMenu, GetSystemMetrics, GetWindowPlacement, GetWindowTextLengthW, GetWindowTextW, HTBOTTOM, HTBOTTOMLEFT, HTBOTTOMRIGHT, HTCAPTION, HTLEFT, HTRIGHT, HTTOP, HTTOPLEFT, HTTOPRIGHT, IsWindowVisible, LoadCursorW, MENU_ITEM_STATE, MF_BYCOMMAND, MFS_DISABLED, MFS_ENABLED, NID_READY, PM_NOREMOVE, @@ -54,7 +54,7 @@ use winit_core::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle, Monito use winit_core::window::{ CursorGrabMode, ImeCapabilities, ImeRequest, ImeRequestError, ResizeDirection, Theme, UserAttentionType, Window as CoreWindow, WindowAttributes, WindowButtons, WindowId, - WindowLevel, + WindowLevel, WindowType, }; use crate::dark_mode::try_theme; @@ -118,6 +118,26 @@ impl Window { self.window_state.lock().unwrap() } + // If we have a popup the position is relative to the parent window and not + // relative to the screen. Therefore we have to translate it from the parent + // coordinate system to the display coordinate system + fn translate_outer_position(&self, position: Position) -> (i32, i32) { + let position = position.to_physical::(self.scale_factor()); + let mut point = POINT { x: position.x, y: position.y }; + + let window_flags = self.window_state_lock().window_flags; + if window_flags.contains(WindowFlags::POPUP) && !window_flags.contains(WindowFlags::CHILD) { + let parent = unsafe { GetParent(self.hwnd()) }; + if !parent.is_null() { + unsafe { + ClientToScreen(parent, &mut point); + } + } + } + + (point.x, point.y) + } + /// Returns the `hwnd` of this window. pub fn hwnd(&self) -> HWND { self.window.hwnd() @@ -483,7 +503,7 @@ impl CoreWindow for Window { } fn set_outer_position(&self, position: Position) { - let (x, y): (i32, i32) = position.to_physical::(self.scale_factor()).into(); + let (x, y) = self.translate_outer_position(position); let window_state = Arc::clone(&self.window_state); let window = self.window; @@ -1367,8 +1387,10 @@ unsafe fn init( let class_name = util::encode_wide(&win_attributes.class_name); unsafe { register_window_class(&class_name) }; + let is_popup = attributes.window_type == WindowType::Popup; let mut window_flags = WindowFlags::empty(); window_flags.set(WindowFlags::MARKER_DECORATIONS, attributes.decorations); + window_flags.set(WindowFlags::POPUP, is_popup); window_flags.set(WindowFlags::MARKER_UNDECORATED_SHADOW, win_attributes.decoration_shadow); window_flags .set(WindowFlags::ALWAYS_ON_TOP, attributes.window_level == WindowLevel::AlwaysOnTop); @@ -1397,7 +1419,9 @@ unsafe fn init( let parent = match attributes.parent_window() { Some(rwh_06::RawWindowHandle::Win32(handle)) => { - window_flags.set(WindowFlags::CHILD, true); + if !is_popup { + window_flags.set(WindowFlags::CHILD, true); + } if win_attributes.menu.is_some() { warn!("Setting a menu on a child window is unsupported"); } diff --git a/winit-win32/src/window_state.rs b/winit-win32/src/window_state.rs index ed06d76042..32ea9dc9f0 100644 --- a/winit-win32/src/window_state.rs +++ b/winit-win32/src/window_state.rs @@ -275,8 +275,13 @@ impl WindowFlags { pub fn to_window_styles(self) -> (WINDOW_STYLE, WINDOW_EX_STYLE) { // Required styles to properly support common window functionality like aero snap. - let mut style = WS_CAPTION | WS_BORDER | WS_CLIPSIBLINGS | WS_SYSMENU; + let mut style = WS_BORDER | WS_CLIPSIBLINGS; let mut style_ex = WS_EX_WINDOWEDGE | WS_EX_ACCEPTFILES; + if self.contains(WindowFlags::POPUP) { + style |= WS_POPUP; + } else { + style |= WS_CAPTION | WS_SYSMENU; + }; if self.contains(WindowFlags::RESIZABLE) { style |= WS_SIZEBOX; @@ -299,7 +304,7 @@ impl WindowFlags { if self.contains(WindowFlags::NO_BACK_BUFFER) { style_ex |= WS_EX_NOREDIRECTIONBITMAP; } - if self.contains(WindowFlags::CHILD) { + if self.contains(WindowFlags::CHILD) && !self.contains(WindowFlags::POPUP) { style |= WS_CHILD; // This is incompatible with WS_POPUP if that gets added eventually. // Remove decorations window styles for child @@ -308,9 +313,6 @@ impl WindowFlags { style_ex &= !WS_EX_WINDOWEDGE; } } - if self.contains(WindowFlags::POPUP) { - style |= WS_POPUP; - } if self.contains(WindowFlags::MINIMIZED) { style |= WS_MINIMIZE; } diff --git a/winit/examples/child_window.rs b/winit/examples/child_window.rs index 4077bca36b..80953cd109 100644 --- a/winit/examples/child_window.rs +++ b/winit/examples/child_window.rs @@ -1,4 +1,4 @@ -#[cfg(any(x11_platform, macos_platform, windows_platform))] +#[cfg(any(wayland_platform, x11_platform, macos_platform, windows_platform))] #[allow(deprecated)] fn main() -> Result<(), impl std::error::Error> { use std::collections::HashMap; @@ -9,7 +9,7 @@ fn main() -> Result<(), impl std::error::Error> { use winit::event::{ElementState, KeyEvent, WindowEvent}; use winit::event_loop::{ActiveEventLoop, EventLoop}; use winit::raw_window_handle::HasRawWindowHandle; - use winit::window::{Window, WindowAttributes, WindowId}; + use winit::window::{Window, WindowAttributes, WindowId, WindowType}; #[path = "util/fill.rs"] mod fill; @@ -36,6 +36,7 @@ fn main() -> Result<(), impl std::error::Error> { fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { let attributes = WindowAttributes::default() .with_title("parent window") + .with_type(WindowType::Window) .with_position(Position::Logical(LogicalPosition::new(0.0, 0.0))) .with_surface_size(LogicalSize::new(640.0f32, 480.0f32)); let window = event_loop.create_window(attributes).unwrap(); @@ -121,10 +122,10 @@ fn main() -> Result<(), impl std::error::Error> { event_loop.run_app(Application::default()) } -#[cfg(not(any(x11_platform, macos_platform, windows_platform)))] +#[cfg(not(any(wayland_platform, x11_platform, macos_platform, windows_platform)))] fn main() { panic!( - "This example is supported only on x11, macOS, and Windows, with the `rwh_06` feature \ - enabled." + "This example is supported only on wayland, x11, macOS, and Windows, with the `rwh_06` \ + feature enabled." ); } diff --git a/winit/src/changelog/unreleased.md b/winit/src/changelog/unreleased.md index 729bfd639b..4a01afcc19 100644 --- a/winit/src/changelog/unreleased.md +++ b/winit/src/changelog/unreleased.md @@ -16,6 +16,7 @@ on how to add them: - On X11, add `Window::even_more_rare_api`. - On Wayland, add `Window::common_api`. - On Windows, add `Window::some_rare_api`. +- Native popups for Wayland, Windows and MacOS ``` When the change requires non-trivial amount of work for users to comply