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:
@@ -769,9 +769,15 @@ pub struct Window {
|
||||
impl Window {
|
||||
pub(crate) fn new(
|
||||
el: &ActiveEventLoop,
|
||||
_window_attrs: window::WindowAttributes,
|
||||
window_attrs: window::WindowAttributes,
|
||||
) -> Result<Self, RequestError> {
|
||||
// FIXME this ignores requested window attributes
|
||||
if window_attrs.window_type() == window::WindowType::Popup {
|
||||
return Err(RequestError::NotSupported(NotSupportedError::new(
|
||||
"Popups are not implemented for Android",
|
||||
)));
|
||||
}
|
||||
|
||||
// FIXME this ignores the rest of the requested window attributes
|
||||
|
||||
Ok(Self {
|
||||
app: el.app.clone(),
|
||||
@@ -825,6 +831,10 @@ impl rwh_06::HasWindowHandle for Window {
|
||||
}
|
||||
|
||||
impl CoreWindow for Window {
|
||||
fn window_type(&self) -> window::WindowType {
|
||||
window::WindowType::Window
|
||||
}
|
||||
|
||||
fn id(&self) -> WindowId {
|
||||
GLOBAL_WINDOW
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -46,6 +46,26 @@ impl fmt::Debug for WindowId {
|
||||
}
|
||||
}
|
||||
|
||||
/// The role of a window, used to request platform-specific window behavior.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq)]
|
||||
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 rounded
|
||||
/// corners for it. To get a rounded, native-looking popup, create it transparent (via
|
||||
/// [`WindowAttributes::with_transparent`]) and render the round border yourself.
|
||||
/// - **X11, Web, Android, iOS, Orbital:** An error is returned because it is not implemented.
|
||||
Popup,
|
||||
}
|
||||
|
||||
/// Attributes used when creating a window.
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
@@ -54,6 +74,11 @@ pub struct WindowAttributes {
|
||||
pub min_surface_size: Option<Size>,
|
||||
pub max_surface_size: Option<Size>,
|
||||
pub surface_resize_increments: Option<Size>,
|
||||
/// The initial position of the window in screen coordinates.
|
||||
///
|
||||
/// For popups, this position is relative to the parent window.
|
||||
///
|
||||
/// **Wayland:** See `WindowAttributesWayland` for more options to position a popup.
|
||||
pub position: Option<Position>,
|
||||
pub resizable: bool,
|
||||
pub enabled_buttons: WindowButtons,
|
||||
@@ -67,11 +92,19 @@ pub struct WindowAttributes {
|
||||
pub preferred_theme: Option<Theme>,
|
||||
pub content_protected: bool,
|
||||
pub window_level: WindowLevel,
|
||||
/// Whether the window should be activated (focused) when shown.
|
||||
///
|
||||
/// For [`WindowType::Popup`] windows this also controls keyboard grabbing:
|
||||
/// - `true` — the popup captures keyboard input (Win32: omits `WS_EX_NOACTIVATE`, macOS: uses
|
||||
/// an activating `NSWindow`, Wayland: issues `xdg_popup.grab`).
|
||||
/// - `false` — the popup is non-activating and the parent window keeps focus (Win32:
|
||||
/// `WS_EX_NOACTIVATE`, macOS: `NSWindowStyleMask::NonactivatingPanel`, Wayland: no grab).
|
||||
pub active: bool,
|
||||
pub cursor: Cursor,
|
||||
pub(crate) parent_window: Option<SendSyncRawWindowHandle>,
|
||||
pub fullscreen: Option<Fullscreen>,
|
||||
pub platform: Option<Box<dyn PlatformWindowAttributes>>,
|
||||
pub window_type: WindowType,
|
||||
}
|
||||
|
||||
impl WindowAttributes {
|
||||
@@ -145,6 +178,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 {
|
||||
@@ -324,9 +359,13 @@ impl WindowAttributes {
|
||||
/// The window should be assumed as not focused by default
|
||||
/// following by the [`WindowEvent::Focused`].
|
||||
///
|
||||
/// For [`WindowType::Popup`] windows, also controls keyboard grabbing — see
|
||||
/// [`WindowAttributes::active`] for details.
|
||||
///
|
||||
/// ## Platform-specific:
|
||||
///
|
||||
/// **Android / iOS / X11 / Wayland / Orbital:** Unsupported.
|
||||
/// **Android / iOS / X11 / Orbital:** Unsupported.
|
||||
/// **Wayland:** Only supported for [`WindowType::Popup`].
|
||||
///
|
||||
/// [`WindowEvent::Focused`]: crate::event::WindowEvent::Focused
|
||||
#[inline]
|
||||
@@ -378,6 +417,27 @@ impl WindowAttributes {
|
||||
self.platform = Some(platform);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`WindowType`] (window vs. popup).
|
||||
///
|
||||
/// Used by the Windows, Wayland and macOS backends; on X11 [`WindowType::Popup`] is not
|
||||
/// implemented and window creation returns an error.
|
||||
/// 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_window_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 +465,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 +496,7 @@ impl Default for WindowAttributes {
|
||||
platform: Default::default(),
|
||||
cursor: Cursor::default(),
|
||||
blur: Default::default(),
|
||||
window_type: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -476,6 +538,9 @@ impl_dyn_casting!(PlatformWindowAttributes);
|
||||
/// **Web:** The [`Window`], which is represented by a `HTMLElementCanvas`, can
|
||||
/// not be closed by dropping the [`Window`].
|
||||
pub trait Window: AsAny + Send + Sync + fmt::Debug {
|
||||
/// Returns the window type of this window
|
||||
fn window_type(&self) -> WindowType;
|
||||
|
||||
/// Returns an identifier unique to the window.
|
||||
fn id(&self) -> WindowId;
|
||||
|
||||
@@ -648,10 +713,18 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug {
|
||||
/// The coordinates can be negative if the top-left hand corner of the window is outside
|
||||
/// of the visible screen region, or on another monitor than the primary.
|
||||
///
|
||||
/// For a [`WindowType::Popup`] with a parent, the position is instead reported relative to the
|
||||
/// top-left hand corner of the parent window's content area, mirroring the coordinate system
|
||||
/// used by [`Window::set_outer_position`].
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Web:** Returns the top-left coordinates relative to the viewport.
|
||||
/// - **Android / Wayland:** Always returns [`RequestError::NotSupported`].
|
||||
/// - **Android:** Always returns [`RequestError::NotSupported`].
|
||||
/// - **Wayland:** For a top-level window this always returns [`RequestError::NotSupported`],
|
||||
/// since the compositor does not report absolute positions. For a [`WindowType::Popup`] the
|
||||
/// compositor-decided position relative to the parent is returned once the popup has been
|
||||
/// configured (before that, [`RequestError::NotSupported`]).
|
||||
fn outer_position(&self) -> Result<PhysicalPosition<i32>, RequestError>;
|
||||
|
||||
/// Sets the position of the window on the desktop.
|
||||
|
||||
@@ -37,6 +37,12 @@ impl Window {
|
||||
el: &ActiveEventLoop,
|
||||
attrs: window::WindowAttributes,
|
||||
) -> Result<Self, RequestError> {
|
||||
if attrs.window_type() == window::WindowType::Popup {
|
||||
return Err(RequestError::NotSupported(NotSupportedError::new(
|
||||
"Popups are not implemented for Orbital",
|
||||
)));
|
||||
}
|
||||
|
||||
let scale = 1.;
|
||||
|
||||
let (x, y) = if let Some(pos) = attrs.position {
|
||||
@@ -154,6 +160,10 @@ impl Window {
|
||||
}
|
||||
|
||||
impl CoreWindow for Window {
|
||||
fn window_type(&self) -> window::WindowType {
|
||||
window::WindowType::Window
|
||||
}
|
||||
|
||||
fn id(&self) -> WindowId {
|
||||
WindowId::from_raw(self.window_socket.fd())
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ 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,
|
||||
WindowLevel, WindowType,
|
||||
};
|
||||
|
||||
use super::app_state::EventWrapper;
|
||||
@@ -490,6 +490,12 @@ impl Window {
|
||||
event_loop: &ActiveEventLoop,
|
||||
mut window_attributes: WindowAttributes,
|
||||
) -> Result<Window, RequestError> {
|
||||
if window_attributes.window_type() == WindowType::Popup {
|
||||
return Err(RequestError::NotSupported(NotSupportedError::new(
|
||||
"Popups are not implemented for iOS",
|
||||
)));
|
||||
}
|
||||
|
||||
let mtm = event_loop.mtm;
|
||||
|
||||
if window_attributes.min_surface_size.is_some() {
|
||||
@@ -590,6 +596,10 @@ impl rwh_06::HasWindowHandle for Window {
|
||||
}
|
||||
|
||||
impl CoreWindow for Window {
|
||||
fn window_type(&self) -> WindowType {
|
||||
WindowType::Window
|
||||
}
|
||||
|
||||
fn id(&self) -> winit_core::window::WindowId {
|
||||
self.maybe_wait_on_main(|delegate| delegate.id())
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ use winit_core::event_loop::{
|
||||
};
|
||||
use winit_core::icon::RgbaIcon;
|
||||
use winit_core::monitor::MonitorHandle as CoreMonitorHandle;
|
||||
use winit_core::window::Theme;
|
||||
use winit_core::window::{Theme, WindowType};
|
||||
|
||||
use crate::dnd::{MimeData, dnd_action_winit_to_wl};
|
||||
use crate::types::cursor::WaylandCustomCursor;
|
||||
@@ -322,6 +322,35 @@ impl EventLoop {
|
||||
self.single_iteration(app, cause);
|
||||
}
|
||||
|
||||
/// Recursive closing all windows from the child to the parent
|
||||
fn find_windows_to_close(
|
||||
window_id: &WindowId,
|
||||
state: &mut WinitState,
|
||||
out: &mut Vec<WindowId>,
|
||||
) -> bool {
|
||||
if !state.window_requests.get_mut().get(window_id).unwrap().closed.load(Ordering::Relaxed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out.push(*window_id);
|
||||
fn window_to_close(window_id: &WindowId, out: &mut Vec<WindowId>, state: &mut WinitState) {
|
||||
// We don't need to check here if it should be closed, because if the parent should
|
||||
// be closed all children must be closed as well
|
||||
let Some(window_state) = state.windows.get_mut().get(window_id) else {
|
||||
return;
|
||||
};
|
||||
let children = window_state.lock().unwrap().children().clone();
|
||||
// First all children and then all subchildren
|
||||
out.extend(&children);
|
||||
for child in children.clone() {
|
||||
window_to_close(&child, out, state);
|
||||
}
|
||||
}
|
||||
window_to_close(window_id, out, state);
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn single_iteration<A: ApplicationHandler>(&mut self, app: &mut A, cause: StartCause) {
|
||||
// NOTE currently just indented to simplify the diff
|
||||
|
||||
@@ -457,14 +486,33 @@ impl EventLoop {
|
||||
});
|
||||
|
||||
for window_id in window_ids.iter() {
|
||||
let event = self.with_state(|state| {
|
||||
let window_requests = state.window_requests.get_mut();
|
||||
if window_requests.get(window_id).unwrap().take_closed() {
|
||||
mem::drop(window_requests.remove(window_id));
|
||||
mem::drop(state.windows.get_mut().remove(window_id));
|
||||
return Some(WindowEvent::Destroyed);
|
||||
}
|
||||
if self.with_state(|state| state.window_requests.get_mut().get(window_id).is_none()) {
|
||||
continue; // The element might not exist anymore so just ignore
|
||||
}
|
||||
let mut windows_to_close = Vec::new();
|
||||
if self.with_state(|state| {
|
||||
Self::find_windows_to_close(window_id, state, &mut windows_to_close)
|
||||
}) {
|
||||
for w in windows_to_close.into_iter().rev() {
|
||||
self.with_state(|state| {
|
||||
let parent =
|
||||
state.windows.get_mut().get_mut(&w).unwrap().lock().unwrap().parent();
|
||||
|
||||
if let Some(p) = parent.and_then(|p| state.windows.get_mut().get_mut(&p)) {
|
||||
p.lock().unwrap().remove_child(&w)
|
||||
}
|
||||
|
||||
let window_requests = state.window_requests.get_mut();
|
||||
window_requests.get(&w).unwrap().take_closed();
|
||||
mem::drop(window_requests.remove(&w));
|
||||
mem::drop(state.windows.get_mut().remove(&w));
|
||||
});
|
||||
app.window_event(&self.active_event_loop, w, WindowEvent::Destroyed);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
let event = self.with_state(|state| {
|
||||
let mut window =
|
||||
state.windows.get_mut().get_mut(window_id).unwrap().lock().unwrap();
|
||||
|
||||
@@ -474,6 +522,7 @@ impl EventLoop {
|
||||
|
||||
// Reset the frame callbacks state.
|
||||
window.frame_callback_reset();
|
||||
let window_requests = state.window_requests.get_mut();
|
||||
let mut redraw_requested =
|
||||
window_requests.get(window_id).unwrap().take_redraw_requested();
|
||||
|
||||
@@ -662,8 +711,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))
|
||||
},
|
||||
_ => Err(RequestError::NotSupported(NotSupportedError::new("Unsupported window type"))),
|
||||
}
|
||||
}
|
||||
|
||||
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
|
||||
|
||||
@@ -21,7 +21,7 @@ use std::ffi::c_void;
|
||||
use std::hash::BuildHasher;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use dpi::{LogicalSize, PhysicalSize};
|
||||
use dpi::{LogicalSize, PhysicalSize, Position, Size};
|
||||
use sctk::reexports::client::Proxy;
|
||||
use sctk::reexports::client::backend::ObjectId;
|
||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
||||
@@ -40,6 +40,7 @@ macro_rules! os_error {
|
||||
mod dnd;
|
||||
mod event_loop;
|
||||
mod output;
|
||||
mod popup;
|
||||
mod seat;
|
||||
mod state;
|
||||
mod types;
|
||||
@@ -47,6 +48,7 @@ mod window;
|
||||
|
||||
pub use self::dnd::{DataOffer, DragSource, MimeData, MimeType};
|
||||
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.
|
||||
@@ -95,6 +97,120 @@ impl WindowExtWayland for dyn CoreWindow + '_ {
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods on [`Popup`] that are specific to Wayland.
|
||||
pub trait PopupExtWayland {
|
||||
fn anchor_rect(&self) -> Option<(impl Into<Position>, impl Into<Size>)>;
|
||||
|
||||
/// Sets the anchor edge of the parent surface the popup is positioned relative to.
|
||||
///
|
||||
/// See [`PopupAnchor`] for the available edges and corners.
|
||||
fn set_anchor(&self, anchor: PopupAnchor);
|
||||
|
||||
/// Sets the anchor rectangle within the parent surface the popup is positioned relative to.
|
||||
///
|
||||
/// `position` is the top-left corner of the rectangle relative to the parent window's content
|
||||
/// area, and `size` its dimensions.
|
||||
fn set_anchor_rect(&self, position: impl Into<Position>, size: impl Into<Size>);
|
||||
|
||||
/// Sets how the compositor should reposition the popup when it would be constrained by screen
|
||||
/// edges.
|
||||
///
|
||||
/// See [`PopupConstraintAdjustment`] for the available adjustment flags.
|
||||
fn set_constraint_adjustment(&self, constraint_adjustment: PopupConstraintAdjustment);
|
||||
|
||||
/// Sets the direction the popup surface extends from the anchor point.
|
||||
///
|
||||
/// See [`PopupGravity`] for the available directions.
|
||||
fn set_gravity(&self, gravity: PopupGravity);
|
||||
|
||||
/// Set the popup position relative to the anchor rect
|
||||
fn set_positioner_offset(&self, position: impl Into<Position>);
|
||||
}
|
||||
|
||||
impl PopupExtWayland for dyn CoreWindow + '_ {
|
||||
fn set_anchor(&self, anchor: PopupAnchor) {
|
||||
if let Some(popup) = self.cast_ref::<Popup>() {
|
||||
popup.set_anchor(anchor);
|
||||
}
|
||||
}
|
||||
|
||||
fn anchor_rect(&self) -> Option<(impl Into<Position>, impl Into<Size>)> {
|
||||
if let Some(popup) = self.cast_ref::<Popup>() { popup.anchor_rect() } else { None }
|
||||
}
|
||||
|
||||
fn set_anchor_rect(&self, position: impl Into<Position>, size: impl Into<Size>) {
|
||||
if let Some(popup) = self.cast_ref::<Popup>() {
|
||||
popup.set_anchor_rect(position, size);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_constraint_adjustment(&self, constraint_adjustment: PopupConstraintAdjustment) {
|
||||
if let Some(popup) = self.cast_ref::<Popup>() {
|
||||
popup.set_constraint_adjustment(constraint_adjustment);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_gravity(&self, gravity: PopupGravity) {
|
||||
if let Some(popup) = self.cast_ref::<Popup>() {
|
||||
popup.set_gravity(gravity);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_positioner_offset(&self, position: impl Into<Position>) {
|
||||
if let Some(popup) = self.cast_ref::<Popup>() {
|
||||
popup.set_positioner_offset(position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Anchor rect within the parent surface
|
||||
/// See: https://wayland.app/protocols/xdg-shell#xdg_positioner:request:set_anchor_rect
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum PopupAnchor {
|
||||
#[default]
|
||||
None,
|
||||
Top,
|
||||
Bottom,
|
||||
Left,
|
||||
Right,
|
||||
TopLeft,
|
||||
BottomLeft,
|
||||
TopRight,
|
||||
BottomRight,
|
||||
}
|
||||
|
||||
/// Defines in what direction a surface should be positioned
|
||||
/// See: https://wayland.app/protocols/xdg-shell#xdg_positioner:request:set_gravity
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum PopupGravity {
|
||||
#[default]
|
||||
None,
|
||||
Top,
|
||||
Bottom,
|
||||
Left,
|
||||
Right,
|
||||
TopLeft,
|
||||
BottomLeft,
|
||||
TopRight,
|
||||
BottomRight,
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// Specify how the window should be positioned if the originally intended position caused the
|
||||
/// surface to be constrained See: https://wayland.app/protocols/xdg-shell#xdg_positioner:request:set_constraint_adjustment
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
|
||||
pub struct PopupConstraintAdjustment: u32 {
|
||||
const SLIDE_X = 1 << 0;
|
||||
const SLIDE_Y = 1 << 1;
|
||||
const FLIP_X = 1 << 2;
|
||||
const FLIP_Y = 1 << 3;
|
||||
const RESIZE_X = 1 << 4;
|
||||
const RESIZE_Y = 1 << 5;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct ApplicationName {
|
||||
pub(crate) general: String,
|
||||
@@ -107,6 +223,13 @@ pub struct WindowAttributesWayland {
|
||||
pub(crate) name: Option<ApplicationName>,
|
||||
pub(crate) activation_token: Option<ActivationToken>,
|
||||
pub(crate) prefer_csd: bool,
|
||||
pub(crate) anchor: Option<PopupAnchor>,
|
||||
pub(crate) anchor_rect: Option<(Position, Size)>,
|
||||
/// Only for Popup: The offset of the popup to the
|
||||
/// anchor rect
|
||||
pub(crate) positioner_offset: Option<Position>,
|
||||
pub(crate) gravity: Option<PopupGravity>,
|
||||
pub(crate) constraint_adjustment: Option<PopupConstraintAdjustment>,
|
||||
}
|
||||
|
||||
impl WindowAttributesWayland {
|
||||
@@ -123,6 +246,11 @@ impl WindowAttributesWayland {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets an activation token to use when creating the window.
|
||||
///
|
||||
/// The activation token allows the compositor to grant focus to the new window,
|
||||
/// overriding focus-stealing prevention. Obtain a token via
|
||||
/// [`ActiveEventLoop::request_activation_token`].
|
||||
#[inline]
|
||||
pub fn with_activation_token(mut self, token: ActivationToken) -> Self {
|
||||
self.activation_token = Some(token);
|
||||
@@ -140,6 +268,63 @@ impl WindowAttributesWayland {
|
||||
self.prefer_csd = prefer_csd;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the edge or corner of the anchor rectangle the popup is attached to.
|
||||
///
|
||||
/// Combined with [`with_gravity`](Self::with_gravity), this controls which corner/edge of the
|
||||
/// anchor rectangle the popup is pinned to. Has no effect unless the window is created as a
|
||||
/// [`Popup`](winit_core::window::WindowType::Popup).
|
||||
#[inline]
|
||||
pub fn with_anchor(mut self, anchor: PopupAnchor) -> Self {
|
||||
self.anchor = Some(anchor);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the anchor rectangle the popup is positioned relative to.
|
||||
///
|
||||
/// `position` is the top-left corner of the rectangle relative to the parent window's content
|
||||
/// area, and `size` its dimensions. Defaults to a `1x1` rectangle at the content origin.
|
||||
/// This value overwrites the position value set with `with_position` in the window attributes
|
||||
#[inline]
|
||||
pub fn with_anchor_rect(
|
||||
mut self,
|
||||
position: impl Into<Position>,
|
||||
size: impl Into<Size>,
|
||||
) -> Self {
|
||||
self.anchor_rect = Some((position.into(), size.into()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the popup position relative to the anchor rect
|
||||
pub fn with_positioner_offset(mut self, position: impl Into<Position>) -> Self {
|
||||
self.positioner_offset = Some(position.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets how the compositor should reposition the popup when it would be constrained.
|
||||
///
|
||||
/// The flags in [`PopupConstraintAdjustment`] can be combined to allow sliding, flipping,
|
||||
/// and/or resizing the popup independently on each axis. Has no effect unless the window is
|
||||
/// created as a [`Popup`](winit_core::window::WindowType::Popup).
|
||||
#[inline]
|
||||
pub fn with_constraint_adjustment(
|
||||
mut self,
|
||||
constraint_adjustment: PopupConstraintAdjustment,
|
||||
) -> Self {
|
||||
self.constraint_adjustment = Some(constraint_adjustment);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the direction the popup surface extends away from the anchor point.
|
||||
///
|
||||
/// Combined with [`with_anchor`](Self::with_anchor), this determines the final position of the
|
||||
/// popup relative to its anchor rectangle. Has no effect unless the window is created as a
|
||||
/// [`Popup`](winit_core::window::WindowType::Popup).
|
||||
#[inline]
|
||||
pub fn with_gravity(mut self, gravity: PopupGravity) -> Self {
|
||||
self.gravity = Some(gravity);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl PlatformWindowAttributes for WindowAttributesWayland {
|
||||
@@ -195,3 +380,64 @@ fn image_to_buffer(
|
||||
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
impl From<PopupGravity> for wayland_protocols::xdg::shell::client::xdg_positioner::Gravity {
|
||||
fn from(value: PopupGravity) -> Self {
|
||||
use wayland_protocols::xdg::shell::client::xdg_positioner::Gravity;
|
||||
match value {
|
||||
PopupGravity::None => Gravity::None,
|
||||
PopupGravity::Top => Gravity::Top,
|
||||
PopupGravity::Bottom => Gravity::Bottom,
|
||||
PopupGravity::Left => Gravity::Left,
|
||||
PopupGravity::Right => Gravity::Right,
|
||||
PopupGravity::TopLeft => Gravity::TopLeft,
|
||||
PopupGravity::BottomLeft => Gravity::BottomLeft,
|
||||
PopupGravity::TopRight => Gravity::TopRight,
|
||||
PopupGravity::BottomRight => Gravity::BottomRight,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PopupAnchor> for wayland_protocols::xdg::shell::client::xdg_positioner::Anchor {
|
||||
fn from(value: PopupAnchor) -> Self {
|
||||
use wayland_protocols::xdg::shell::client::xdg_positioner::Anchor;
|
||||
match value {
|
||||
PopupAnchor::None => Anchor::None,
|
||||
PopupAnchor::Top => Anchor::Top,
|
||||
PopupAnchor::Bottom => Anchor::Bottom,
|
||||
PopupAnchor::Left => Anchor::Left,
|
||||
PopupAnchor::Right => Anchor::Right,
|
||||
PopupAnchor::TopLeft => Anchor::TopLeft,
|
||||
PopupAnchor::BottomLeft => Anchor::BottomLeft,
|
||||
PopupAnchor::TopRight => Anchor::TopRight,
|
||||
PopupAnchor::BottomRight => Anchor::BottomRight,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PopupConstraintAdjustment>
|
||||
for wayland_protocols::xdg::shell::client::xdg_positioner::ConstraintAdjustment
|
||||
{
|
||||
fn from(value: PopupConstraintAdjustment) -> Self {
|
||||
use wayland_protocols::xdg::shell::client::xdg_positioner::ConstraintAdjustment;
|
||||
|
||||
const _: () = {
|
||||
assert!(
|
||||
PopupConstraintAdjustment::SLIDE_X.bits() == ConstraintAdjustment::SlideX.bits()
|
||||
);
|
||||
assert!(
|
||||
PopupConstraintAdjustment::SLIDE_Y.bits() == ConstraintAdjustment::SlideY.bits()
|
||||
);
|
||||
assert!(PopupConstraintAdjustment::FLIP_X.bits() == ConstraintAdjustment::FlipX.bits());
|
||||
assert!(PopupConstraintAdjustment::FLIP_Y.bits() == ConstraintAdjustment::FlipY.bits());
|
||||
assert!(
|
||||
PopupConstraintAdjustment::RESIZE_X.bits() == ConstraintAdjustment::ResizeX.bits()
|
||||
);
|
||||
assert!(
|
||||
PopupConstraintAdjustment::RESIZE_Y.bits() == ConstraintAdjustment::ResizeY.bits()
|
||||
);
|
||||
};
|
||||
|
||||
ConstraintAdjustment::from_bits_retain(value.bits())
|
||||
}
|
||||
}
|
||||
|
||||
747
winit-wayland/src/popup.rs
Normal file
747
winit-wayland/src/popup.rs
Normal file
@@ -0,0 +1,747 @@
|
||||
use core::sync::atomic::Ordering;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
|
||||
use dpi::{
|
||||
LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size,
|
||||
};
|
||||
use rwh_06::RawWindowHandle;
|
||||
use sctk::compositor::SurfaceData;
|
||||
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 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::window::Handles;
|
||||
use crate::window::handles::WindowRequests;
|
||||
use crate::window::state::{WindowState, WindowType};
|
||||
use crate::{PopupExtWayland, WindowAttributesWayland};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Popup {
|
||||
/// The state of the popup.
|
||||
/// The only single truth of the state is stored
|
||||
/// in the event loop state, because if the server decides to destroy the popup
|
||||
/// we cannot use it anymore
|
||||
popup_state: Weak<Mutex<WindowState>>,
|
||||
|
||||
/// 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<Self, RequestError> {
|
||||
fn error(message: &'static str) -> RequestError {
|
||||
RequestError::NotSupported(NotSupportedError::new(message))
|
||||
}
|
||||
|
||||
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 parent_window_id =
|
||||
WindowId::from_raw(parent_window_handle.surface.as_ptr() as usize);
|
||||
let (popup, popup_state) = if let Some(parent_window_state) =
|
||||
state.windows.borrow().get(&parent_window_id)
|
||||
{
|
||||
let wayland_attributes = attributes
|
||||
.platform
|
||||
.as_ref()
|
||||
.and_then(|p| p.cast_ref::<WindowAttributesWayland>())
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let WindowAttributesWayland {
|
||||
gravity,
|
||||
anchor,
|
||||
anchor_rect,
|
||||
constraint_adjustment,
|
||||
positioner_offset,
|
||||
..
|
||||
} = wayland_attributes;
|
||||
let grab_keyboard = attributes.active;
|
||||
|
||||
let mut 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 size = attributes
|
||||
.surface_size
|
||||
.ok_or(error("Invalid size for popup"))?
|
||||
.to_logical(scale_factor);
|
||||
if size.width == 0_i32 || size.height == 0_i32 {
|
||||
return Err(error("The popups size must not be zero"));
|
||||
}
|
||||
|
||||
// Anchoring
|
||||
// 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 geometry_origin = parent_window_state.content_surface_origin();
|
||||
let anchor_position = LogicalPosition::new(-geometry_origin.x, -geometry_origin.y);
|
||||
positioner.set_anchor(anchor.unwrap_or(crate::PopupAnchor::TopLeft).into());
|
||||
positioner.set_gravity(gravity.unwrap_or(crate::PopupGravity::BottomRight).into());
|
||||
constraint_adjustment
|
||||
.inspect(|c| positioner.set_constraint_adjustment((*c).into()));
|
||||
let (anchor_rect_position, anchor_rect_size) = match anchor_rect {
|
||||
Some((position, size)) => (
|
||||
position.to_logical::<i32>(scale_factor),
|
||||
size.to_logical::<i32>(scale_factor),
|
||||
),
|
||||
None => {
|
||||
// anchor rect was not specified use attributes.position
|
||||
let pos: LogicalPosition<i32> = attributes
|
||||
.position
|
||||
.map(|position| position.to_logical(scale_factor))
|
||||
.unwrap_or_default();
|
||||
(pos, LogicalSize::new(1, 1))
|
||||
},
|
||||
};
|
||||
let anchor_rect = (
|
||||
LogicalPosition::new(
|
||||
anchor_rect_position.x + anchor_position.x,
|
||||
anchor_rect_position.y + anchor_position.y,
|
||||
),
|
||||
LogicalSize::new(anchor_rect_size.width.max(1), anchor_rect_size.height.max(1)),
|
||||
);
|
||||
positioner.set_anchor_rect(
|
||||
anchor_rect.0.x,
|
||||
anchor_rect.0.y,
|
||||
anchor_rect.1.width,
|
||||
anchor_rect.1.height,
|
||||
);
|
||||
positioner_offset.inspect(|o| {
|
||||
let o = o.to_logical(scale_factor);
|
||||
positioner.set_offset(o.x, o.y);
|
||||
});
|
||||
positioner.set_size(size.width, size.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"))?;
|
||||
parent_window_state.add_child(super::make_wid(popup.wl_surface()));
|
||||
drop(parent_window_state);
|
||||
|
||||
let mut popup_state = WindowState::new(
|
||||
event_loop_window_target,
|
||||
&state,
|
||||
size.into(),
|
||||
WindowType::Popup {
|
||||
popup: popup.clone(),
|
||||
positioner,
|
||||
last_configure: None,
|
||||
anchor_rect,
|
||||
parent_origin: geometry_origin,
|
||||
},
|
||||
attributes.preferred_theme,
|
||||
false,
|
||||
scale_factor,
|
||||
Some(parent_window_id),
|
||||
);
|
||||
|
||||
// Set transparency hint.
|
||||
popup_state.set_transparent(attributes.transparent);
|
||||
|
||||
// Set blur.
|
||||
let _ = popup_state.set_blur(attributes.blur);
|
||||
|
||||
let WindowAttributesWayland { activation_token, .. } = *attributes
|
||||
.platform
|
||||
.take()
|
||||
.and_then(|p| p.cast::<WindowAttributesWayland>().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);
|
||||
}
|
||||
|
||||
// Request a keyboard grab so the compositor routes key events to
|
||||
// this popup rather than the parent window. Must happen before the
|
||||
// first commit that maps the surface.
|
||||
if grab_keyboard {
|
||||
// Use the seat with the most recent event
|
||||
let grab = state
|
||||
.seat_state
|
||||
.seats()
|
||||
.filter_map(|seat| {
|
||||
let serial = state.seats.get(&seat.id())?.latest_serial()?;
|
||||
Some((seat, serial))
|
||||
})
|
||||
.max_by_key(|(_, serial)| *serial);
|
||||
|
||||
if let Some((seat, serial)) = grab {
|
||||
popup.xdg_popup().grab(&seat, serial);
|
||||
}
|
||||
}
|
||||
|
||||
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))?;
|
||||
// The compositor may dismiss a popup (e.g. invalid grab serial) by sending
|
||||
// popup_done before configure. Detect that and bail out instead of looping forever.
|
||||
if state
|
||||
.window_compositor_updates
|
||||
.iter()
|
||||
.any(|u| u.window_id == window_id && u.close_window)
|
||||
{
|
||||
return Err(error("Popup was dismissed by the compositor before configure"));
|
||||
}
|
||||
}
|
||||
|
||||
// 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_state: Arc::downgrade(&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",
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CoreWindow for Popup {
|
||||
fn window_type(&self) -> winit_core::window::WindowType {
|
||||
winit_core::window::WindowType::Popup
|
||||
}
|
||||
|
||||
fn id(&self) -> WindowId {
|
||||
self.window_id
|
||||
}
|
||||
|
||||
fn request_redraw(&self) {
|
||||
self.handles.request_redraw();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn title(&self) -> String {
|
||||
let Some(s) = self.popup_state.upgrade() else { return String::new() };
|
||||
s.lock().unwrap().title().to_owned()
|
||||
}
|
||||
|
||||
fn pre_present_notify(&self) {
|
||||
let Some(s) = self.popup_state.upgrade() else { return };
|
||||
s.lock().unwrap().request_frame_callback();
|
||||
}
|
||||
|
||||
fn reset_dead_keys(&self) {
|
||||
winit_common::xkb::reset_dead_keys()
|
||||
}
|
||||
|
||||
fn surface_position(&self) -> PhysicalPosition<i32> {
|
||||
(0, 0).into()
|
||||
}
|
||||
|
||||
fn outer_position(&self) -> Result<PhysicalPosition<i32>, RequestError> {
|
||||
let s = self
|
||||
.popup_state
|
||||
.upgrade()
|
||||
.ok_or_else(|| NotSupportedError::new("the popup has been destroyed"))?;
|
||||
let state = s.lock().unwrap();
|
||||
if let WindowType::Popup { last_configure: Some(configure), .. } = &state.window {
|
||||
let (x, y) = configure.position;
|
||||
return Ok(LogicalPosition::new(x, y).to_physical(state.scale_factor()));
|
||||
}
|
||||
Err(NotSupportedError::new("the popup has not been configured yet").into())
|
||||
}
|
||||
|
||||
fn set_outer_position(&self, position: Position) {
|
||||
let Some(s) = self.popup_state.upgrade() else { return };
|
||||
let mut state = s.lock().unwrap();
|
||||
let scale_factor = state.scale_factor();
|
||||
if let WindowType::Popup { popup, positioner, anchor_rect, parent_origin, .. } =
|
||||
&mut state.window
|
||||
{
|
||||
let position = position.to_logical(scale_factor);
|
||||
anchor_rect.0 = position;
|
||||
positioner.set_anchor_rect(
|
||||
anchor_rect.0.x - parent_origin.x,
|
||||
anchor_rect.0.y - parent_origin.y,
|
||||
anchor_rect.1.width,
|
||||
anchor_rect.1.height,
|
||||
);
|
||||
if popup.xdg_popup().version() >= 3 {
|
||||
popup.reposition(positioner, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn surface_size(&self) -> PhysicalSize<u32> {
|
||||
let Some(s) = self.popup_state.upgrade() else { return PhysicalSize::default() };
|
||||
let popup_state = s.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<PhysicalSize<u32>> {
|
||||
let s = self.popup_state.upgrade()?;
|
||||
let mut popup_state = s.lock().unwrap();
|
||||
let new_size = popup_state.request_surface_size(size);
|
||||
self.request_redraw();
|
||||
Some(new_size)
|
||||
}
|
||||
|
||||
fn outer_size(&self) -> PhysicalSize<u32> {
|
||||
let Some(s) = self.popup_state.upgrade() else { return PhysicalSize::default() };
|
||||
let popup_state = s.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<u32> {
|
||||
PhysicalInsets::new(0, 0, 0, 0)
|
||||
}
|
||||
|
||||
fn set_min_surface_size(&self, min_size: Option<Size>) {
|
||||
let scale_factor = self.scale_factor();
|
||||
let min_size = min_size.map(|size| size.to_logical(scale_factor));
|
||||
let Some(s) = self.popup_state.upgrade() else { return };
|
||||
s.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<Size>) {
|
||||
let scale_factor = self.scale_factor();
|
||||
let max_size = max_size.map(|size| size.to_logical(scale_factor));
|
||||
let Some(s) = self.popup_state.upgrade() else { return };
|
||||
s.lock().unwrap().set_max_surface_size(max_size);
|
||||
// NOTE: Requires commit to be applied.
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
fn surface_resize_increments(&self) -> Option<PhysicalSize<u32>> {
|
||||
let s = self.popup_state.upgrade()?;
|
||||
let popup_state = s.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<Size>) {
|
||||
let Some(s) = self.popup_state.upgrade() else { return };
|
||||
let mut popup_state = s.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) {
|
||||
let Some(s) = self.popup_state.upgrade() else { return };
|
||||
s.lock().unwrap().set_title(title.to_owned());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_transparent(&self, transparent: bool) {
|
||||
let Some(s) = self.popup_state.upgrade() else { return };
|
||||
s.lock().unwrap().set_transparent(transparent);
|
||||
}
|
||||
|
||||
fn set_visible(&self, _visible: bool) {
|
||||
// Not possible on Wayland.
|
||||
}
|
||||
|
||||
fn is_visible(&self) -> Option<bool> {
|
||||
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<bool> {
|
||||
// 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<Fullscreen>) {
|
||||
// Not possible for popups
|
||||
}
|
||||
|
||||
fn fullscreen(&self) -> Option<Fullscreen> {
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn scale_factor(&self) -> f64 {
|
||||
let Some(s) = self.popup_state.upgrade() else { return 1.0 };
|
||||
s.lock().unwrap().scale_factor()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_blur(&self, blur: bool) {
|
||||
let Some(s) = self.popup_state.upgrade() else { return };
|
||||
if s.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<winit_core::icon::Icon>) {
|
||||
// Popup does not have a window icon
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn request_ime_update(&self, request: ImeRequest) -> Result<(), ImeRequestError> {
|
||||
let Some(s) = self.popup_state.upgrade() else { return Ok(()) };
|
||||
let state_changed = s.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<ImeCapabilities> {
|
||||
let s = self.popup_state.upgrade()?;
|
||||
s.lock().unwrap().ime_allowed()
|
||||
}
|
||||
|
||||
fn focus_window(&self) {}
|
||||
|
||||
fn has_focus(&self) -> bool {
|
||||
let Some(s) = self.popup_state.upgrade() else { return false };
|
||||
s.lock().unwrap().has_focus()
|
||||
}
|
||||
|
||||
fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
|
||||
if let Some(state) = self.popup_state.upgrade() {
|
||||
let state = state.lock().unwrap();
|
||||
let surface = state.window.wl_surface();
|
||||
self.handles.request_user_attention(surface, request_type);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_theme(&self, _theme: Option<Theme>) {
|
||||
// A popup does not have a frame
|
||||
}
|
||||
|
||||
fn theme(&self) -> Option<Theme> {
|
||||
// A popup does not have a frame
|
||||
None
|
||||
}
|
||||
|
||||
fn set_content_protected(&self, _protected: bool) {}
|
||||
|
||||
fn set_cursor(&self, cursor: Cursor) {
|
||||
let Some(s) = self.popup_state.upgrade() else { return };
|
||||
let mut popup_state = s.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 Some(s) = self.popup_state.upgrade() else { return Err(RequestError::Ignored) };
|
||||
let scale_factor = s.lock().unwrap().scale_factor();
|
||||
let position = position.to_logical(scale_factor);
|
||||
s.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> {
|
||||
let Some(s) = self.popup_state.upgrade() else { return Err(RequestError::Ignored) };
|
||||
s.lock().unwrap().set_cursor_grab(mode)
|
||||
}
|
||||
|
||||
fn set_cursor_visible(&self, visible: bool) {
|
||||
let Some(s) = self.popup_state.upgrade() else { return };
|
||||
s.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> {
|
||||
let Some(state) = self.popup_state.upgrade() else {
|
||||
return Err(RequestError::Ignored);
|
||||
};
|
||||
|
||||
self.handles.set_cursor_hittest(state.lock().unwrap().window.wl_surface(), hittest)
|
||||
}
|
||||
|
||||
fn current_monitor(&self) -> Option<CoreMonitorHandle> {
|
||||
let state = self.popup_state.upgrade()?;
|
||||
let state = state.lock().unwrap();
|
||||
let data = state.window.wl_surface().data::<SurfaceData<()>>()?;
|
||||
data.outputs()
|
||||
.next()
|
||||
.map(MonitorHandle::new)
|
||||
.map(|monitor| CoreMonitorHandle(Arc::new(monitor)))
|
||||
}
|
||||
|
||||
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
|
||||
self.handles.available_monitors()
|
||||
}
|
||||
|
||||
fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
|
||||
// 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::WindowHandle<'_>, rwh_06::HandleError> {
|
||||
let state = self.popup_state.upgrade().ok_or(rwh_06::HandleError::Unavailable)?;
|
||||
let raw = rwh_06::WaylandWindowHandle::new({
|
||||
let ptr = state.lock().unwrap().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::DisplayHandle<'_>, rwh_06::HandleError> {
|
||||
if self.popup_state.upgrade().is_none() {
|
||||
return Err(rwh_06::HandleError::Unavailable);
|
||||
};
|
||||
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())) }
|
||||
}
|
||||
}
|
||||
|
||||
impl PopupExtWayland for Popup {
|
||||
fn set_anchor(&self, anchor: crate::PopupAnchor) {
|
||||
let Some(state) = self.popup_state.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let WindowType::Popup { popup, positioner, .. } = &state.lock().unwrap().window {
|
||||
positioner.set_anchor(anchor.into());
|
||||
popup.reposition(positioner, 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn anchor_rect(&self) -> Option<(impl Into<Position>, impl Into<Size>)> {
|
||||
let state = self.popup_state.upgrade()?;
|
||||
if let WindowType::Popup { anchor_rect, .. } = &state.lock().unwrap().window {
|
||||
Some(*anchor_rect)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn set_anchor_rect(&self, position: impl Into<Position>, size: impl Into<Size>) {
|
||||
let Some(state) = self.popup_state.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let state = state.lock().unwrap();
|
||||
let scale_factor = state.scale_factor();
|
||||
let size: LogicalSize<i32> = size.into().to_logical(scale_factor);
|
||||
let position: LogicalPosition<i32> = position.into().to_logical(scale_factor);
|
||||
|
||||
if let WindowType::Popup { popup, positioner, parent_origin, .. } = &state.window {
|
||||
positioner.set_anchor_rect(
|
||||
position.x - parent_origin.x,
|
||||
position.y - parent_origin.y,
|
||||
size.width.max(1),
|
||||
size.height.max(1),
|
||||
);
|
||||
popup.reposition(positioner, 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_positioner_offset(&self, position: impl Into<Position>) {
|
||||
let Some(state) = self.popup_state.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let scale_factor = state.lock().unwrap().scale_factor();
|
||||
let position: LogicalPosition<i32> = position.into().to_logical(scale_factor);
|
||||
if let WindowType::Popup { popup, positioner, .. } = &state.lock().unwrap().window {
|
||||
positioner.set_offset(position.x, position.y);
|
||||
popup.reposition(positioner, 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_constraint_adjustment(&self, constraint_adjustment: crate::PopupConstraintAdjustment) {
|
||||
let Some(state) = self.popup_state.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let WindowType::Popup { popup, positioner, .. } = &state.lock().unwrap().window {
|
||||
positioner.set_constraint_adjustment(constraint_adjustment.into());
|
||||
popup.reposition(positioner, 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_gravity(&self, gravity: crate::PopupGravity) {
|
||||
let Some(state) = self.popup_state.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let WindowType::Popup { popup, positioner, .. } = &state.lock().unwrap().window {
|
||||
positioner.set_gravity(gravity.into());
|
||||
popup.reposition(positioner, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,9 +129,10 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
state.events_sink.push_window_event(WindowEvent::Focused(false), window_id);
|
||||
}
|
||||
},
|
||||
WlKeyboardEvent::Key { key, state: WEnum::Value(key_state), .. }
|
||||
WlKeyboardEvent::Key { serial, key, state: WEnum::Value(key_state), .. }
|
||||
if matches!(key_state, WlKeyState::Repeated | WlKeyState::Pressed) =>
|
||||
{
|
||||
seat_state.latest_input_serial.set(Some(serial));
|
||||
let key = key + 8;
|
||||
key_input(
|
||||
keyboard_state,
|
||||
@@ -204,7 +205,10 @@ impl Dispatch<WlKeyboard, KeyboardData, WinitState> for WinitState {
|
||||
})
|
||||
.ok();
|
||||
},
|
||||
WlKeyboardEvent::Key { key, state: WEnum::Value(WlKeyState::Released), .. } => {
|
||||
WlKeyboardEvent::Key {
|
||||
serial, key, state: WEnum::Value(WlKeyState::Released), ..
|
||||
} => {
|
||||
seat_state.latest_input_serial.set(Some(serial));
|
||||
let key = key + 8;
|
||||
|
||||
key_input(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
//! Seat handling.
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::sync::Arc;
|
||||
|
||||
use foldhash::HashMap;
|
||||
@@ -76,6 +77,11 @@ pub struct WinitSeatState {
|
||||
|
||||
/// Whether we have pending modifiers.
|
||||
modifiers_pending: bool,
|
||||
|
||||
/// Serial of the most recent input event (keyboard or pointer) on this seat.
|
||||
/// Written by both keyboard and pointer handlers through a shared reference,
|
||||
/// so interior mutability is required.
|
||||
pub(crate) latest_input_serial: Cell<Option<u32>>,
|
||||
}
|
||||
|
||||
impl WinitSeatState {
|
||||
@@ -83,6 +89,12 @@ impl WinitSeatState {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Returns the serial of the most recent input event on this seat, or `None` if none has
|
||||
/// been received yet.
|
||||
pub fn latest_serial(&self) -> Option<u32> {
|
||||
self.latest_input_serial.get()
|
||||
}
|
||||
|
||||
pub(crate) fn data_device(&self) -> Option<&DataDevice> {
|
||||
self.data_device.as_ref()
|
||||
}
|
||||
|
||||
@@ -166,7 +166,13 @@ impl PointerHandler for WinitState {
|
||||
ref kind @ PointerEventKind::Press { button, serial, .. }
|
||||
| ref kind @ PointerEventKind::Release { button, serial, .. } => {
|
||||
// Update the last button serial.
|
||||
|
||||
pointer.winit_data().data().inner.lock().unwrap().latest_button_serial = serial;
|
||||
if matches!(kind, PointerEventKind::Press { .. }) {
|
||||
// For Gnome (Mutter) and possible others only the Press event serial must
|
||||
// be logged!
|
||||
seat_state.latest_input_serial.set(Some(serial));
|
||||
}
|
||||
|
||||
let button = wayland_button_to_winit(button);
|
||||
let state = if matches!(kind, PointerEventKind::Press { .. }) {
|
||||
|
||||
@@ -17,6 +17,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};
|
||||
@@ -37,7 +38,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)]
|
||||
@@ -314,24 +316,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.
|
||||
@@ -347,6 +349,57 @@ impl WindowHandler for WinitState {
|
||||
}
|
||||
}
|
||||
|
||||
impl PopupHandler for WinitState {
|
||||
fn configure(
|
||||
&mut self,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
popup: &XdgPopup,
|
||||
configure: PopupConfigure,
|
||||
) {
|
||||
let window_id = super::make_wid(popup.wl_surface());
|
||||
|
||||
let index = 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
|
||||
};
|
||||
|
||||
self.window_compositor_updates[index].resized |= 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<Self>, popup: &XdgPopup) {
|
||||
let window_id = super::make_wid(popup.wl_surface());
|
||||
let window_requests = self.window_requests.get_mut().iter().find(|r| *r.0 == window_id);
|
||||
if let Some(window_requests) = window_requests {
|
||||
window_requests.1.closed.store(true, Ordering::Relaxed);
|
||||
}
|
||||
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
|
||||
|
||||
142
winit-wayland/src/window/handles.rs
Normal file
142
winit-wayland/src/window/handles.rs
Normal file
@@ -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<WinitState>,
|
||||
|
||||
/// Window requests to the event loop.
|
||||
pub(crate) window_requests: Arc<WindowRequests>,
|
||||
|
||||
/// Observed monitors.
|
||||
pub(crate) monitors: Arc<Mutex<Vec<MonitorHandle>>>,
|
||||
|
||||
/// 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<Mutex<EventSink>>,
|
||||
|
||||
/// Xdg activation to request user attention.
|
||||
pub(crate) xdg_activation: Option<XdgActivationV1>,
|
||||
|
||||
/// The state of the requested attention from the `xdg_activation`.
|
||||
pub(crate) attention_requested: Arc<AtomicBool>,
|
||||
|
||||
/// Compositor to handle WlRegion stuff.
|
||||
pub(crate) compositor: Arc<CompositorState>,
|
||||
}
|
||||
|
||||
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<dyn Iterator<Item = CoreMonitorHandle>> {
|
||||
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<UserAttentionType>,
|
||||
) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -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<Mutex<WindowState>>,
|
||||
|
||||
/// Compositor to handle WlRegion stuff.
|
||||
compositor: Arc<CompositorState>,
|
||||
|
||||
/// The wayland display used solely for raw window handle.
|
||||
#[allow(dead_code)]
|
||||
display: WlDisplay,
|
||||
|
||||
/// Xdg activation to request user attention.
|
||||
xdg_activation: Option<XdgActivationV1>,
|
||||
|
||||
/// The state of the requested attention from the `xdg_activation`.
|
||||
attention_requested: Arc<AtomicBool>,
|
||||
|
||||
/// Handle to the main queue to perform requests.
|
||||
queue_handle: QueueHandle<WinitState>,
|
||||
|
||||
/// Window requests to the event loop.
|
||||
window_requests: Arc<WindowRequests>,
|
||||
|
||||
/// Observed monitors.
|
||||
monitors: Arc<Mutex<Vec<MonitorHandle>>>,
|
||||
|
||||
/// 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<Mutex<EventSink>>,
|
||||
/// Common handles like queue, window requests, monitors and so on
|
||||
handles: Handles,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
@@ -106,20 +85,32 @@ impl Window {
|
||||
let window =
|
||||
state.xdg_shell.create_window(surface.clone(), default_decorations, &queue_handle);
|
||||
|
||||
let WindowAttributesWayland { name: app_name, activation_token, prefer_csd } = *attributes
|
||||
.platform
|
||||
.take()
|
||||
.and_then(|p| p.cast::<WindowAttributesWayland>().ok())
|
||||
.unwrap_or_default();
|
||||
let WindowAttributesWayland { name: app_name, activation_token, prefer_csd, .. } =
|
||||
*attributes
|
||||
.platform
|
||||
.take()
|
||||
.and_then(|p| p.cast::<WindowAttributesWayland>().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: window.clone(), last_configure: None },
|
||||
attributes.preferred_theme,
|
||||
prefer_csd,
|
||||
scale_factor,
|
||||
None,
|
||||
);
|
||||
|
||||
window_state.set_window_icon(attributes.window_icon);
|
||||
@@ -219,16 +210,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 +236,7 @@ impl Window {
|
||||
|
||||
impl Window {
|
||||
pub fn request_activation_token(&self) -> Result<AsyncRequestSerial, RequestError> {
|
||||
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 +244,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 +260,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,23 +288,16 @@ impl rwh_06::HasDisplayHandle for Window {
|
||||
}
|
||||
|
||||
impl CoreWindow for Window {
|
||||
fn window_type(&self) -> winit_core::window::WindowType {
|
||||
winit_core::window::WindowType::Window
|
||||
}
|
||||
|
||||
fn id(&self) -> WindowId {
|
||||
self.window_id
|
||||
}
|
||||
|
||||
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 +440,15 @@ 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<Fullscreen>) {
|
||||
@@ -475,14 +468,16 @@ impl CoreWindow for Window {
|
||||
}
|
||||
|
||||
fn fullscreen(&self) -> Option<Fullscreen> {
|
||||
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 +521,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 +539,7 @@ impl CoreWindow for Window {
|
||||
}
|
||||
|
||||
fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
|
||||
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<Theme>) {
|
||||
@@ -623,17 +595,7 @@ 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<CoreMonitorHandle> {
|
||||
@@ -645,14 +607,7 @@ impl CoreWindow for Window {
|
||||
}
|
||||
|
||||
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
|
||||
Box::new(
|
||||
self.monitors
|
||||
.lock()
|
||||
.unwrap()
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|inner| CoreMonitorHandle(Arc::new(inner))),
|
||||
)
|
||||
self.handles.available_monitors()
|
||||
}
|
||||
|
||||
fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
|
||||
@@ -670,23 +625,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::{PointerData, 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<WinitState>;
|
||||
@@ -54,6 +55,49 @@ pub type WinitFrame = sctk::shell::xdg::fallback_frame::FallbackFrame<WinitState
|
||||
// Minimum window surface size.
|
||||
const MIN_WINDOW_SIZE: LogicalSize<u32> = LogicalSize::new(2, 1);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WindowType {
|
||||
// The option is the last received configure
|
||||
Window {
|
||||
window: Window,
|
||||
last_configure: Option<WindowConfigure>,
|
||||
},
|
||||
Popup {
|
||||
popup: Popup,
|
||||
positioner: XdgPositioner,
|
||||
last_configure: Option<PopupConfigure>,
|
||||
parent_origin: LogicalPosition<i32>,
|
||||
anchor_rect: (LogicalPosition<i32>, LogicalSize<i32>),
|
||||
},
|
||||
}
|
||||
|
||||
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 +107,6 @@ pub struct WindowState {
|
||||
/// The `Shm` to set cursor.
|
||||
pub shm: WlShm,
|
||||
|
||||
/// The last received configure.
|
||||
pub last_configure: Option<WindowConfigure>,
|
||||
|
||||
/// The pointers observed on the window.
|
||||
pub pointers: Vec<Weak<ThemedPointer<WinitPointerData>>>,
|
||||
|
||||
@@ -164,7 +205,7 @@ pub struct WindowState {
|
||||
has_pending_move: Option<u32>,
|
||||
|
||||
/// 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
|
||||
@@ -172,19 +213,29 @@ pub struct WindowState {
|
||||
// field drop order guarantees.
|
||||
/// The window frame, which is created from the configure request.
|
||||
frame: Option<WinitFrame>,
|
||||
|
||||
/// Parent Window if available
|
||||
parent: Option<WindowId>,
|
||||
|
||||
/// Children of this window like popups, dialogs or other windows
|
||||
children: Vec<WindowId>,
|
||||
}
|
||||
|
||||
impl WindowState {
|
||||
/// Create new window state.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
handle: Arc<OwnedDisplayHandle>,
|
||||
queue_handle: &QueueHandle<WinitState>,
|
||||
active_event_loop: &ActiveEventLoop,
|
||||
winit_state: &WinitState,
|
||||
initial_size: Size,
|
||||
window: Window,
|
||||
window: WindowType,
|
||||
theme: Option<Theme>,
|
||||
prefer_csd: bool,
|
||||
scale_factor: f64,
|
||||
parent: Option<WindowId>,
|
||||
) -> 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 +271,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 +278,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.),
|
||||
@@ -240,6 +290,8 @@ impl WindowState {
|
||||
transparent: false,
|
||||
viewport,
|
||||
window,
|
||||
children: Default::default(),
|
||||
parent,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,8 +340,47 @@ impl WindowState {
|
||||
FrameCallbackState::Requested => (),
|
||||
}
|
||||
}
|
||||
pub fn configure_popup(&mut self, configure: PopupConfigure) -> bool {
|
||||
// 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());
|
||||
}
|
||||
|
||||
pub fn configure(
|
||||
// The popup was constrained to a different size by the compositor
|
||||
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);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
tracing::error!(
|
||||
"configure_popup called for window type unequal of popup. This should never \
|
||||
happen, because we start configuring with a popup"
|
||||
);
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn configure_window(
|
||||
&mut self,
|
||||
configure: WindowConfigure,
|
||||
shm: &Shm,
|
||||
@@ -407,25 +498,33 @@ impl WindowState {
|
||||
}
|
||||
|
||||
let new_state = configure.state;
|
||||
let old_state = self.last_configure.as_ref().map(|configure| configure.state);
|
||||
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);
|
||||
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);
|
||||
// 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
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -458,31 +557,45 @@ 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| {
|
||||
if let Some(serial) = data.latest_button_serial() {
|
||||
let seat = data.seat();
|
||||
xdg_toplevel.resize(seat, serial, resize_direction_to_xdg(direction));
|
||||
}
|
||||
});
|
||||
// TODO(kchibisov) handle touch serials.
|
||||
self.apply_on_pointer(|_, data| {
|
||||
if let Some(serial) = data.latest_button_serial() {
|
||||
let seat = data.seat();
|
||||
xdg_toplevel.resize(seat, serial, resize_direction_to_xdg(direction));
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
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| {
|
||||
if let Some(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| {
|
||||
if let Some(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.
|
||||
@@ -497,32 +610,37 @@ impl WindowState {
|
||||
window_id: WindowId,
|
||||
updates: &mut Vec<WindowCompositorUpdate>,
|
||||
) -> Option<bool> {
|
||||
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) {
|
||||
@@ -540,21 +658,26 @@ impl WindowState {
|
||||
x: f64,
|
||||
y: f64,
|
||||
) -> Option<CursorIcon> {
|
||||
// Take the serial if we had any, so it doesn't stick around.
|
||||
let serial = self.has_pending_move.take();
|
||||
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() {
|
||||
self.window.move_(seat, serial);
|
||||
None
|
||||
} else {
|
||||
cursor
|
||||
}
|
||||
} else {
|
||||
None
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -615,21 +738,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_configure, .. } => {
|
||||
let csd = 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
|
||||
}
|
||||
},
|
||||
WindowType::Popup { .. } => false, // Popup window does not have any decoration
|
||||
}
|
||||
}
|
||||
|
||||
@@ -642,6 +769,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<i32> {
|
||||
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<ThemedPointer<WinitPointerData>>) {
|
||||
self.pointers.push(added);
|
||||
@@ -704,8 +838,19 @@ impl WindowState {
|
||||
|
||||
/// Try to resize the window when the user can do so.
|
||||
pub fn request_surface_size(&mut self, surface_size: Size) -> PhysicalSize<u32> {
|
||||
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);
|
||||
if popup.xdg_popup().version() >= 3 {
|
||||
popup.reposition(positioner, 0);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
logical_to_physical_rounded(self.surface_size(), self.scale_factor())
|
||||
@@ -716,8 +861,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.
|
||||
@@ -855,33 +1002,37 @@ impl WindowState {
|
||||
|
||||
/// Set maximum inner window size.
|
||||
pub fn set_min_surface_size(&mut self, size: Option<LogicalSize<u32>>) {
|
||||
// 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);
|
||||
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);
|
||||
// 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<LogicalSize<u32>>) {
|
||||
let size = size.map(|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 {
|
||||
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));
|
||||
self.max_surface_size = size;
|
||||
window.set_max_size(size.map(Into::into));
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the CSD theme.
|
||||
@@ -983,13 +1134,15 @@ impl WindowState {
|
||||
}
|
||||
|
||||
pub fn show_window_menu(&self, position: LogicalPosition<u32>) {
|
||||
// TODO(kchibisov) handle touch serials.
|
||||
self.apply_on_pointer(|_, data| {
|
||||
if let Some(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| {
|
||||
if let Some(serial) = data.latest_button_serial() {
|
||||
let seat = data.seat();
|
||||
window.show_window_menu(seat, serial, position.into());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the position of the cursor.
|
||||
@@ -1042,22 +1195,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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1192,46 +1352,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<winit_core::icon::Icon>) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1263,6 +1428,22 @@ impl WindowState {
|
||||
pub fn title(&self) -> &str {
|
||||
&self.title
|
||||
}
|
||||
|
||||
pub fn children(&self) -> &Vec<WindowId> {
|
||||
&self.children
|
||||
}
|
||||
|
||||
pub fn parent(&self) -> Option<WindowId> {
|
||||
self.parent
|
||||
}
|
||||
|
||||
pub fn remove_child(&mut self, child: &WindowId) {
|
||||
self.children.retain(|w| w != child);
|
||||
}
|
||||
|
||||
pub fn add_child(&mut self, child: WindowId) {
|
||||
self.children.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WindowState {
|
||||
|
||||
@@ -13,7 +13,7 @@ use winit_core::icon::Icon;
|
||||
use winit_core::monitor::{Fullscreen, MonitorHandle as CoremMonitorHandle};
|
||||
use winit_core::window::{
|
||||
CursorGrabMode, ImeRequestError, ResizeDirection, Theme, UserAttentionType,
|
||||
Window as RootWindow, WindowAttributes, WindowButtons, WindowId, WindowLevel,
|
||||
Window as RootWindow, WindowAttributes, WindowButtons, WindowId, WindowLevel, WindowType,
|
||||
};
|
||||
|
||||
use crate::r#async::Dispatcher;
|
||||
@@ -46,6 +46,12 @@ impl Window {
|
||||
target: &ActiveEventLoop,
|
||||
attr: WindowAttributes,
|
||||
) -> Result<Self, RequestError> {
|
||||
if attr.window_type() == WindowType::Popup {
|
||||
return Err(RequestError::NotSupported(NotSupportedError::new(
|
||||
"Popups are not implemented for Web",
|
||||
)));
|
||||
}
|
||||
|
||||
let id = target.generate_id();
|
||||
|
||||
let window = target.runner.window();
|
||||
@@ -103,6 +109,10 @@ impl Window {
|
||||
}
|
||||
|
||||
impl RootWindow for Window {
|
||||
fn window_type(&self) -> WindowType {
|
||||
WindowType::Window
|
||||
}
|
||||
|
||||
fn id(&self) -> WindowId {
|
||||
self.inner.queue(|inner| inner.id)
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ use windows_sys::Win32::Graphics::Dwm::{
|
||||
use windows_sys::Win32::Graphics::Gdi::{
|
||||
CDS_FULLSCREEN, ChangeDisplaySettingsExW, ClientToScreen, CreateRectRgn, DISP_CHANGE_BADFLAGS,
|
||||
DISP_CHANGE_BADMODE, DISP_CHANGE_BADPARAM, DISP_CHANGE_FAILED, DISP_CHANGE_SUCCESSFUL,
|
||||
DeleteObject, InvalidateRgn, RDW_INTERNALPAINT, RedrawWindow,
|
||||
DeleteObject, InvalidateRgn, RDW_INTERNALPAINT, RedrawWindow, ScreenToClient,
|
||||
};
|
||||
use windows_sys::Win32::System::Com::{
|
||||
CLSCTX_ALL, COINIT_APARTMENTTHREADED, CoCreateInstance, CoInitializeEx, CoUninitialize,
|
||||
@@ -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,
|
||||
@@ -48,13 +48,13 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{
|
||||
WM_SYSCOMMAND, WNDCLASSEXW,
|
||||
};
|
||||
use winit_core::cursor::Cursor;
|
||||
use winit_core::error::RequestError;
|
||||
use winit_core::error::{NotSupportedError, RequestError};
|
||||
use winit_core::icon::{Icon, RgbaIcon};
|
||||
use winit_core::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle, MonitorHandleProvider};
|
||||
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;
|
||||
@@ -100,6 +100,9 @@ pub struct Window {
|
||||
|
||||
// The events loop proxy.
|
||||
thread_executor: event_loop::EventLoopThreadExecutor,
|
||||
|
||||
/// The type of window of this
|
||||
window_type: WindowType,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
@@ -118,6 +121,49 @@ 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) -> PhysicalPosition<i32> {
|
||||
let position = position.to_physical::<i32>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PhysicalPosition::new(point.x, point.y)
|
||||
}
|
||||
|
||||
// Inverse of `translate_outer_position`: if we have a popup the position is
|
||||
// reported relative to the parent window instead of the screen. Therefore we
|
||||
// translate it from the display coordinate system back to the parent
|
||||
// coordinate system. Non-popup windows are left in screen coordinates.
|
||||
fn translate_outer_position_to_parent(
|
||||
&self,
|
||||
position: PhysicalPosition<i32>,
|
||||
) -> PhysicalPosition<i32> {
|
||||
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 {
|
||||
ScreenToClient(parent, &mut point);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PhysicalPosition::new(point.x, point.y)
|
||||
}
|
||||
|
||||
/// Returns the `hwnd` of this window.
|
||||
pub fn hwnd(&self) -> HWND {
|
||||
self.window.hwnd()
|
||||
@@ -416,6 +462,10 @@ impl rwh_06::HasWindowHandle for Window {
|
||||
}
|
||||
|
||||
impl CoreWindow for Window {
|
||||
fn window_type(&self) -> WindowType {
|
||||
self.window_type
|
||||
}
|
||||
|
||||
fn set_title(&self, text: &str) {
|
||||
let wide_text = util::encode_wide(text);
|
||||
unsafe {
|
||||
@@ -464,7 +514,10 @@ impl CoreWindow for Window {
|
||||
fn outer_position(&self) -> Result<PhysicalPosition<i32>, RequestError> {
|
||||
util::WindowArea::Outer
|
||||
.get_rect(self.hwnd())
|
||||
.map(|rect| Ok(PhysicalPosition::new(rect.left, rect.top)))
|
||||
.map(|rect| {
|
||||
Ok(self
|
||||
.translate_outer_position_to_parent(PhysicalPosition::new(rect.left, rect.top)))
|
||||
})
|
||||
.expect(
|
||||
"Unexpected GetWindowRect failure; please report this error to \
|
||||
rust-windowing/winit",
|
||||
@@ -483,7 +536,7 @@ impl CoreWindow for Window {
|
||||
}
|
||||
|
||||
fn set_outer_position(&self, position: Position) {
|
||||
let (x, y): (i32, i32) = position.to_physical::<i32>(self.scale_factor()).into();
|
||||
let position = self.translate_outer_position(position);
|
||||
|
||||
let window_state = Arc::clone(&self.window_state);
|
||||
let window = self.window;
|
||||
@@ -498,8 +551,8 @@ impl CoreWindow for Window {
|
||||
SetWindowPos(
|
||||
self.hwnd(),
|
||||
ptr::null_mut(),
|
||||
x,
|
||||
y,
|
||||
position.x,
|
||||
position.y,
|
||||
0,
|
||||
0,
|
||||
SWP_ASYNCWINDOWPOS | SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE,
|
||||
@@ -1211,6 +1264,7 @@ impl InitData<'_> {
|
||||
window: SyncWindowHandle(window),
|
||||
window_state,
|
||||
thread_executor: self.runner.create_thread_executor(),
|
||||
window_type: self.attributes.window_type,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1369,8 +1423,10 @@ unsafe fn init(
|
||||
let class_name = util::encode_wide(&win_attributes.class_name);
|
||||
unsafe { register_window_class(&class_name) };
|
||||
|
||||
let is_popup = matches!(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);
|
||||
@@ -1380,7 +1436,9 @@ unsafe fn init(
|
||||
window_flags.set(WindowFlags::MARKER_ACTIVATE, attributes.active);
|
||||
window_flags.set(WindowFlags::TRANSPARENT, attributes.transparent);
|
||||
// WindowFlags::VISIBLE and MAXIMIZED are set down below after the window has been configured.
|
||||
window_flags.set(WindowFlags::RESIZABLE, attributes.resizable);
|
||||
// Popups are never resizable, matching the Wayland and macOS backends (and avoiding the
|
||||
// thick `WS_SIZEBOX` resize frame).
|
||||
window_flags.set(WindowFlags::RESIZABLE, attributes.resizable && !is_popup);
|
||||
// Will be changed later using `window.set_enabled_buttons` but we need to set a default here
|
||||
// so the diffing later can work.
|
||||
window_flags.set(WindowFlags::CLOSABLE, true);
|
||||
@@ -1399,14 +1457,23 @@ 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");
|
||||
}
|
||||
Some(handle.hwnd.get() as HWND)
|
||||
},
|
||||
Some(raw) => unreachable!("Invalid raw window handle {raw:?} on Windows"),
|
||||
None => fallback_parent(),
|
||||
None => {
|
||||
if is_popup {
|
||||
return Err(RequestError::NotSupported(NotSupportedError::new(
|
||||
"Popup without a parent is not supported!",
|
||||
)));
|
||||
}
|
||||
fallback_parent()
|
||||
},
|
||||
};
|
||||
|
||||
let menu = win_attributes.menu;
|
||||
|
||||
@@ -13,9 +13,9 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{
|
||||
SWP_NOREPOSITION, SWP_NOSIZE, SWP_NOZORDER, SendMessageW, SetWindowLongW, SetWindowPos,
|
||||
ShowWindow, WINDOW_EX_STYLE, WINDOW_STYLE, WINDOWPLACEMENT, WS_BORDER, WS_CAPTION, WS_CHILD,
|
||||
WS_CLIPCHILDREN, WS_CLIPSIBLINGS, WS_EX_ACCEPTFILES, WS_EX_APPWINDOW, WS_EX_LAYERED,
|
||||
WS_EX_NOREDIRECTIONBITMAP, WS_EX_TOPMOST, WS_EX_TRANSPARENT, WS_EX_WINDOWEDGE, WS_MAXIMIZE,
|
||||
WS_MAXIMIZEBOX, WS_MINIMIZE, WS_MINIMIZEBOX, WS_OVERLAPPEDWINDOW, WS_POPUP, WS_SIZEBOX,
|
||||
WS_SYSMENU, WS_VISIBLE,
|
||||
WS_EX_NOACTIVATE, WS_EX_NOREDIRECTIONBITMAP, WS_EX_TOPMOST, WS_EX_TRANSPARENT,
|
||||
WS_EX_WINDOWEDGE, WS_MAXIMIZE, WS_MAXIMIZEBOX, WS_MINIMIZE, WS_MINIMIZEBOX,
|
||||
WS_OVERLAPPEDWINDOW, WS_POPUP, WS_SIZEBOX, WS_SYSMENU, WS_VISIBLE,
|
||||
};
|
||||
use winit_core::icon::Icon;
|
||||
use winit_core::keyboard::ModifiersState;
|
||||
@@ -276,8 +276,18 @@ 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_CLIPSIBLINGS;
|
||||
let mut style_ex = WS_EX_WINDOWEDGE | WS_EX_ACCEPTFILES;
|
||||
if self.contains(WindowFlags::POPUP) {
|
||||
style |= WS_POPUP;
|
||||
// Don't activate the popup (and thus don't deactivate the parent) when it is shown or
|
||||
// clicked, unless the caller requested activation via `WindowAttributes::active`.
|
||||
if !self.contains(WindowFlags::MARKER_ACTIVATE) {
|
||||
style_ex |= WS_EX_NOACTIVATE;
|
||||
}
|
||||
} else {
|
||||
style |= WS_CAPTION | WS_SYSMENU | WS_BORDER;
|
||||
};
|
||||
|
||||
if self.contains(WindowFlags::RESIZABLE) {
|
||||
style |= WS_SIZEBOX;
|
||||
@@ -300,8 +310,8 @@ impl WindowFlags {
|
||||
if self.contains(WindowFlags::NO_BACK_BUFFER) {
|
||||
style_ex |= WS_EX_NOREDIRECTIONBITMAP;
|
||||
}
|
||||
if self.contains(WindowFlags::CHILD) {
|
||||
style |= WS_CHILD; // This is incompatible with WS_POPUP if that gets added eventually.
|
||||
if self.contains(WindowFlags::CHILD) && !self.contains(WindowFlags::POPUP) {
|
||||
style |= WS_CHILD;
|
||||
|
||||
// Remove decorations window styles for child
|
||||
if !self.contains(WindowFlags::MARKER_DECORATIONS) {
|
||||
@@ -309,9 +319,6 @@ impl WindowFlags {
|
||||
style_ex &= !WS_EX_WINDOWEDGE;
|
||||
}
|
||||
}
|
||||
if self.contains(WindowFlags::POPUP) {
|
||||
style |= WS_POPUP;
|
||||
}
|
||||
if self.contains(WindowFlags::MINIMIZED) {
|
||||
style |= WS_MINIMIZE;
|
||||
}
|
||||
|
||||
@@ -66,13 +66,26 @@ impl Window {
|
||||
event_loop: &ActiveEventLoop,
|
||||
attribs: WindowAttributes,
|
||||
) -> Result<Self, RequestError> {
|
||||
let window = Arc::new(UnownedWindow::new(event_loop, attribs)?);
|
||||
event_loop.windows.borrow_mut().insert(window.id(), Arc::downgrade(&window));
|
||||
Ok(Window(window))
|
||||
use winit_core::window::WindowType;
|
||||
match attribs.window_type() {
|
||||
WindowType::Window => {
|
||||
let window = Arc::new(UnownedWindow::new(event_loop, attribs)?);
|
||||
event_loop.windows.borrow_mut().insert(window.id(), Arc::downgrade(&window));
|
||||
Ok(Window(window))
|
||||
},
|
||||
WindowType::Popup => Err(RequestError::NotSupported(NotSupportedError::new(
|
||||
"Popups are not implemented for X11",
|
||||
))),
|
||||
_ => Err(RequestError::NotSupported(NotSupportedError::new("Unsupported window type"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CoreWindow for Window {
|
||||
fn window_type(&self) -> winit_core::window::WindowType {
|
||||
winit_core::window::WindowType::Window
|
||||
}
|
||||
|
||||
fn id(&self) -> WindowId {
|
||||
self.0.id()
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -10,7 +10,7 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
use winit::event::{ElementState, KeyEvent, WindowEvent};
|
||||
use winit::event_loop::{ActiveEventLoop, EventLoop, OwnedDisplayHandle};
|
||||
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;
|
||||
@@ -39,6 +39,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_window_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();
|
||||
@@ -131,10 +132,10 @@ fn main() -> Result<(), impl std::error::Error> {
|
||||
event_loop.run_app(Application { context, parent_window_id: None, windows: HashMap::new() })
|
||||
}
|
||||
|
||||
#[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."
|
||||
);
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ changelog entry.
|
||||
- On Android, added scancode conversions for more obscure key codes.
|
||||
- On Wayland, added `HoldGesture` event for multi-finger hold gestures
|
||||
- On Wayland, added ext-background-effect-v1 support.
|
||||
- On Wayland, Windows and macOS, added native popups (`WindowType::Popup`).
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
Reference in New Issue
Block a user