mirror of
https://github.com/rust-windowing/winit.git
synced 2026-09-02 14:50: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:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user