diff --git a/examples/topless.rs b/examples/topless.rs new file mode 100644 index 0000000000..9f59b10cae --- /dev/null +++ b/examples/topless.rs @@ -0,0 +1,174 @@ +#![allow( + unused_imports, + unused_mut, + unused_variables, + dead_code, + unused_assignments, + unused_macros +)] +use std::error::Error; + +use winit::application::ApplicationHandler; +use winit::event::WindowEvent; +use winit::event_loop::{ActiveEventLoop, EventLoop}; +use winit::window::{Window, WindowAttributes, WindowId}; +use winit_core::keyboard::NamedKey; + +#[path = "util/fill.rs"] +mod fill; +#[path = "util/tracing.rs"] +mod tracing; + +use ::tracing::info; +#[cfg(windows_platform)] +fn main() -> Result<(), Box> { + tracing::init(); + + println!( + "Topless mode (Windows only): + − title bar (WS_CAPTION) via with_titlebar (false) + + resize border@↓←→ (WS_SIZEBOX) via with_resizable (true ) ≝ + − resize border@↑ via with_top_resize_border(false) + ├ not a separate WS_ window style, 'manual' removal on NonClientArea events + └ only implemented for windows without a title bar, eg, with a custom title bar handling \ + resizing from the top + —————————————————————————————— + Press a key for (un)setting/querying a specific parameter (modifiers are ignored): + on off toggle query + title bar q w e r + resize border@↓←→ a s d f + resize border@↑ z x c v + " + ); + + let event_loop = EventLoop::new()?; + + let app = Application::new(); + Ok(event_loop.run_app(app)?) +} + +/// Application state and event handling. +struct Application { + window: Option>, +} + +impl Application { + fn new() -> Self { + Self { window: None } + } +} + +use winit::event::ElementState; +use winit::keyboard::{Key, ModifiersState}; +#[cfg(windows_platform)] +use winit::platform::windows::WindowAttributesWindows; +#[cfg(windows_platform)] +use winit::platform::windows::WindowExtWindows; +#[cfg(windows_platform)] +impl ApplicationHandler for Application { + fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { + let window_attributes_win = Box::new( + WindowAttributesWindows::default() + .with_titlebar(false) // titlebar ≝true + .with_top_resize_border(false), // top_resize_border ≝true + ); + let window_attributes = WindowAttributes::default() + .with_title("Topless (unless you see this)!") + .with_decorations(true) // decorations ≝true + .with_resizable(true) // resizable ≝true + .with_platform_attributes(window_attributes_win) // + .with_position(dpi::Position::Logical(dpi::LogicalPosition::new(0.0, 0.0))); + self.window = Some(event_loop.create_window(window_attributes).unwrap()); + } + + fn window_event( + &mut self, + event_loop: &dyn ActiveEventLoop, + _window_id: WindowId, + event: WindowEvent, + ) { + let win = match self.window.as_ref() { + Some(win) => win, + None => return, + }; + let _modi = ModifiersState::default(); + match event { + WindowEvent::KeyboardInput { event, .. } => { + if event.state == ElementState::Pressed && !event.repeat { + match event.key_without_modifiers.as_ref() { + Key::Character("q") => { + win.set_titlebar(true); + info!("set_titlebar → true") + }, + Key::Character("w") => { + win.set_titlebar(false); + info!("set_titlebar → false") + }, + Key::Character("e") => { + let flip = !win.is_titlebar(); + win.set_titlebar(flip); + info!("set_titlebar → {flip}") + }, + Key::Character("r") => { + let is = win.is_titlebar(); + info!("is_titlebar = {is}") + }, + Key::Character("a") => { + win.set_resizable(true); + info!("set_resizable → true") + }, + Key::Character("s") => { + win.set_resizable(false); + info!("set_resizable → false") + }, + Key::Character("d") => { + let flip = !win.is_resizable(); + win.set_resizable(flip); + info!("set_resizable → {flip}") + }, + Key::Character("f") => { + let is = win.is_resizable(); + info!("is_resizable = {is}") + }, + Key::Character("z") => { + win.set_top_resize_border(true); + info!("set_top_resize_border→ true") + }, + Key::Character("x") => { + win.set_top_resize_border(false); + info!("set_top_resize_border→ false") + }, + Key::Character("c") => { + let flip = !win.is_top_resize_border(); + win.set_top_resize_border(flip); + info!("set_top_resize_border→ {flip}") + }, + Key::Character("v") => { + let is = win.is_top_resize_border(); + info!("is_top_resize_border = {is}") + }, + Key::Named(NamedKey::Escape) => { + event_loop.exit(); + }, + _ => (), + } + } + }, + WindowEvent::RedrawRequested => { + let window = self.window.as_ref().unwrap(); + window.pre_present_notify(); + fill::fill_window(window.as_ref()); + }, + WindowEvent::CloseRequested => { + event_loop.exit(); + }, + _ => {}, + } + } +} + +#[cfg(not(windows))] +fn main() -> Result<(), Box> { + println!("This example is only supported on Windows."); + Ok(()) +} diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index a8df3c34e8..d6ef1cb5f1 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -16,6 +16,8 @@ on how to add them: - On X11, add `Window::even_more_rare_api`. - On Wayland, add `Window::common_api`. - On Windows, add `Window::some_rare_api`. +- On Windows, add `WindowAttributesExtWindows::with_titlebar`, `Window::set_titlebar` to allow enabling/disabling titlebar separately from the resize border (currently combined in decorations). +- On Windows, add `WindowAttributesExtWindows::is_top_resize_border`, `Window::set_top_resize_border` to allow enabling/disabling the top resize border when title bar is disabled. Allows implementing custom title bars that should handle the top resizing themselves. ``` When the change requires non-trivial amount of work for users to comply @@ -35,6 +37,10 @@ with it, the migration guide should be added below the entry, like: ``` +```md +- (WindowsOS) Fixed (removed) an invisible gap between the screen border and the window's visible border at 0 `x`/`y` coordinates due to the fact that window's invisible resize borders are considered part of a window box for positioning win32 APIs (`SetWindowPos`). Now an invisible resize border is treated the same as a shadow and `with_position` and `set_position` APIs are updated to offset the coordinates before communicating with win32 APIs. In some cases (when a window has no title bar, but does have resize borders), the top resize border becomes visible, but it's still ignored for consistency. +``` + The migration guide could reference other migration examples in the current changelog entry. diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 55471db39c..d58671b7ca 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -284,6 +284,25 @@ pub trait WindowExtWindows { /// Supported starting with Windows 11 Build 22000. fn set_title_text_color(&self, color: Color); + /// Turn window title bar on or off by setting `WS_CAPTION`. + /// By default this is enabled. Note that fullscreen windows + /// naturally do not have title bar. + fn set_titlebar(&self, titlebar: bool); + + /// Gets the window's current titlebar state. + /// + /// Returns `true` when windows have a titlebar (server-side or by Winit). + fn is_titlebar(&self) -> bool; + + /// Turn window top resize border on or off (for windows without a title bar). + /// By default this is enabled. + fn set_top_resize_border(&self, top_resize_border: bool); + + /// Gets the window's current top resize border state (for windows without a title bar). + /// + /// Returns `true` when windows have a top resize border. + fn is_top_resize_border(&self) -> bool; + /// Sets the preferred style of the window corners. /// /// Supported starting with Windows 11 Build 22000. @@ -396,6 +415,30 @@ impl WindowExtWindows for dyn Window + '_ { window.set_title_text_color(color) } + #[inline] + fn set_titlebar(&self, titlebar: bool) { + let window = self.cast_ref::().unwrap(); + window.set_titlebar(titlebar) + } + + #[inline] + fn is_titlebar(&self) -> bool { + let window = self.cast_ref::().unwrap(); + window.is_titlebar() + } + + #[inline] + fn set_top_resize_border(&self, top_resize_border: bool) { + let window = self.cast_ref::().unwrap(); + window.set_top_resize_border(top_resize_border) + } + + #[inline] + fn is_top_resize_border(&self) -> bool { + let window = self.cast_ref::().unwrap(); + window.is_top_resize_border() + } + #[inline] fn set_corner_preference(&self, preference: CornerPreference) { let window = self.cast_ref::().unwrap(); @@ -463,6 +506,8 @@ pub struct WindowAttributesWindows { pub(crate) title_background_color: Option, pub(crate) title_text_color: Option, pub(crate) corner_preference: Option, + pub(crate) titlebar: bool, + pub(crate) top_resize_border: bool, } impl Default for WindowAttributesWindows { @@ -482,6 +527,8 @@ impl Default for WindowAttributesWindows { title_background_color: None, title_text_color: None, corner_preference: None, + titlebar: true, + top_resize_border: true, } } } @@ -542,6 +589,19 @@ impl WindowAttributesWindows { self } + /// Enables/disables the window titlebar by setting `WS_CAPTION`. + pub fn with_titlebar(mut self, titlebar: bool) -> Self { + self.titlebar = titlebar; + self + } + + /// Enables/disables the window's top resize border by setting its height to 0. + /// Only for windows without a title bar. + pub fn with_top_resize_border(mut self, top_resize_border: bool) -> Self { + self.top_resize_border = top_resize_border; + self + } + /// Enables or disables drag and drop support (enabled by default). Will interfere with other /// crates that use multi-threaded COM API (`CoInitializeEx` with `COINIT_MULTITHREADED` /// instead of `COINIT_APARTMENTTHREADED`) on the same thread. Note that winit may still diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index b466a3a98f..8bbfd3d3d9 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1034,7 +1034,7 @@ unsafe fn public_window_callback_inner( window: HWND, msg: u32, wparam: WPARAM, - lparam: LPARAM, + mut lparam: LPARAM, userdata: &WindowData, ) -> LRESULT { let mut result = ProcResult::DefWindowProc(wparam); @@ -1075,6 +1075,27 @@ unsafe fn public_window_callback_inner( let callback = || match msg { WM_NCCALCSIZE => { let window_flags = userdata.window_state_lock().window_flags; + // Remove top resize border from an untitled window t to free up area for, eg, a custom + // title bar, which should then handle resizing events itself + if wparam != 0 + && !window_flags.contains(WindowFlags::TITLE_BAR) + && !window_flags.contains(WindowFlags::TOP_RESIZE_BORDER) + && window_flags.contains(WindowFlags::RESIZABLE) + && !util::is_maximized(window) + { + // maximized wins have no borders + result = ProcResult::DefWindowProc(wparam); + let rect = unsafe { &mut *(lparam as *mut RECT) }; + let adj_rect = userdata + .window_state_lock() + .window_flags + .adjust_rect(window, *rect) + .unwrap_or(*rect); + let border_top = rect.top - adj_rect.top; + let params = unsafe { &mut *(lparam as *mut NCCALCSIZE_PARAMS) }; + params.rgrc[0].top -= border_top; + return; + } if wparam == 0 || window_flags.contains(WindowFlags::MARKER_DECORATIONS) { result = ProcResult::DefWindowProc(wparam); return; @@ -2131,6 +2152,13 @@ unsafe fn public_window_callback_inner( }, WM_NCACTIVATE => { + let window_flags = userdata.window_state_lock().window_flags; + if !window_flags.contains(WindowFlags::TITLE_BAR) + && !window_flags.contains(WindowFlags::TOP_RESIZE_BORDER) + && window_flags.contains(WindowFlags::RESIZABLE) + { + lparam = -1; + } let is_active = wparam != false.into(); let active_focus_changed = userdata.window_state_lock().set_active(is_active); if active_focus_changed { diff --git a/src/platform_impl/windows/util.rs b/src/platform_impl/windows/util.rs index 162c40a6b4..844e1cdb49 100644 --- a/src/platform_impl/windows/util.rs +++ b/src/platform_impl/windows/util.rs @@ -17,11 +17,11 @@ use windows_sys::Win32::UI::HiDpi::{ use windows_sys::Win32::UI::Input::KeyboardAndMouse::GetActiveWindow; use windows_sys::Win32::UI::Input::Pointer::{POINTER_INFO, POINTER_TOUCH_INFO}; use windows_sys::Win32::UI::WindowsAndMessaging::{ - ClipCursor, GetClientRect, GetClipCursor, GetCursorPos, GetSystemMetrics, GetWindowPlacement, - GetWindowRect, IsIconic, ShowCursor, IDC_APPSTARTING, IDC_ARROW, IDC_CROSS, IDC_HAND, IDC_HELP, - IDC_IBEAM, IDC_NO, IDC_SIZEALL, IDC_SIZENESW, IDC_SIZENS, IDC_SIZENWSE, IDC_SIZEWE, IDC_WAIT, - SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN, SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN, SW_MAXIMIZE, - WINDOWPLACEMENT, + ClipCursor, GetClientRect, GetClipCursor, GetCursorPos, GetSystemMetrics, GetWindowLongW, + GetWindowPlacement, GetWindowRect, IsIconic, ShowCursor, GWL_STYLE, IDC_APPSTARTING, IDC_ARROW, + IDC_CROSS, IDC_HAND, IDC_HELP, IDC_IBEAM, IDC_NO, IDC_SIZEALL, IDC_SIZENESW, IDC_SIZENS, + IDC_SIZENWSE, IDC_SIZEWE, IDC_WAIT, SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN, SM_XVIRTUALSCREEN, + SM_YVIRTUALSCREEN, SW_MAXIMIZE, WINDOWPLACEMENT, }; use crate::cursor::CursorIcon; @@ -82,6 +82,101 @@ impl WindowArea { } } +use windows_sys::Win32::Foundation::FALSE; +use windows_sys::Win32::UI::WindowsAndMessaging::{ + AdjustWindowRectEx, WS_BORDER, WS_CAPTION, WS_CLIPSIBLINGS, WS_DLGFRAME, WS_EX_ACCEPTFILES, + WS_EX_WINDOWEDGE, WS_SIZEBOX, WS_SYSMENU, +}; + +/// Get sizes for the invisible resize (`sizing`=`true`) or visible thin borders without having to +/// carefully adjust the actual styles of a real window (since, e.g., removing a single `WS_BORDER` +/// style may lead to other style changes being applied automatically depending on the style +/// combinations, thus resulting in an incorrect estimate for the thin border that `WS_BORDER` sets) +/// `hwnd` is only used for DPI adjustment. +pub fn get_border_size(hwnd: HWND, sizing: bool) -> Result, io::Error> { + let style = if sizing { + WS_SIZEBOX | WS_BORDER | WS_CLIPSIBLINGS | WS_SYSMENU + } else { + WS_BORDER | WS_CLIPSIBLINGS | WS_SYSMENU + }; + let style_no = if sizing { + WS_BORDER | WS_CLIPSIBLINGS | WS_SYSMENU + } else { + WS_CLIPSIBLINGS | WS_SYSMENU + }; + let style_ex = WS_EX_WINDOWEDGE | WS_EX_ACCEPTFILES; + let mut rect_style: RECT = unsafe { mem::zeroed() }; + let mut rect_style_no: RECT = unsafe { mem::zeroed() }; + win_to_err(unsafe { + if let (Some(get_dpi_for_window), Some(adjust_window_rect_ex_for_dpi)) = + (*GET_DPI_FOR_WINDOW, *ADJUST_WINDOW_RECT_EX_FOR_DPI) + { + let dpi = { get_dpi_for_window(hwnd) }; + adjust_window_rect_ex_for_dpi(&mut rect_style, style, FALSE, style_ex, dpi); + adjust_window_rect_ex_for_dpi(&mut rect_style_no, style_no, FALSE, style_ex, dpi) + } else { + AdjustWindowRectEx(&mut rect_style, style, FALSE, style_ex); + AdjustWindowRectEx(&mut rect_style_no, style_no, FALSE, style_ex) + } + })?; + Ok(dpi::PhysicalUnit(rect_style_no.left - rect_style.left)) +} + +use crate::platform_impl::windows::window_state::WindowFlags; +/// Get the size of the resize borders as an offset in physical coordinates. Takes into account +/// various window styles to only return offset if it would prevent placing a 0,0 window in the +/// screen's corner +pub fn get_offset_resize_border( + hwnd: HWND, + win_flags: WindowFlags, +) -> Result, io::Error> { + let mut offset = dpi::PhysicalInsets::new(0, 0, 0, 0); + if !is_maximized(hwnd) { + // resize borders not pushed off-screen + let style = unsafe { GetWindowLongW(hwnd, GWL_STYLE) as u32 }; + if style & WS_SIZEBOX == WS_SIZEBOX { + // ...actually exist + if !win_flags.contains(WindowFlags::RESIZABLE) { + tracing::debug!("Window has resize borders, but is configured not to have them"); + } + let border_sizing = get_border_size(hwnd, true)?; + offset.left = border_sizing.0; // ←left: always offset + + if style & WS_CAPTION != WS_CAPTION { + // no caption (≝title+border) exists + if win_flags.contains(WindowFlags::TITLE_BAR) { + tracing::debug!("Window has no title bar, but is configured to have it"); + } + if win_flags.contains(WindowFlags::TOP_RESIZE_BORDER) { + // top resize border is NOT removed "manually" + offset.top = border_sizing.0; // ↑top: offset if no title bar (border is now + // visible) + } + } + } else if style & WS_DLGFRAME == WS_DLGFRAME { + // or is substituted by dlgFrame in win32's window box, which is an invisible border + // that does nothing + let border_sizing = get_border_size(hwnd, true)?; + offset.left = border_sizing.0; // ←left: always offset + + if style & WS_CAPTION != WS_CAPTION { + // no caption (≝title+border) exists + if win_flags.contains(WindowFlags::TITLE_BAR) { + tracing::debug!("Window has no title bar, but is configured to have it"); + } + if win_flags.contains(WindowFlags::TOP_RESIZE_BORDER) { + // top resize border is NOT removed "manually" + offset.top = border_sizing.0; // ↑top: offset if no title bar (border is now + // visible) + } + } + } + } + offset.right = offset.left; // resize borders are the same + offset.bottom = offset.left; + Ok(offset) +} + pub fn is_maximized(window: HWND) -> bool { unsafe { let mut placement: WINDOWPLACEMENT = mem::zeroed(); diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 0c9f1a2dd6..9425a70d3a 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -340,6 +340,44 @@ impl Window { } } + #[inline] + pub fn set_titlebar(&self, titlebar: bool) { + let window = self.window; + let window_state = Arc::clone(&self.window_state); + + self.thread_executor.execute_in_thread(move || { + let _ = &window; + WindowState::set_window_flags(window_state.lock().unwrap(), window.hwnd(), |f| { + f.set(WindowFlags::TITLE_BAR, titlebar) + }); + }); + } + + #[inline] + pub fn is_titlebar(&self) -> bool { + let window_state = self.window_state_lock(); + window_state.window_flags.contains(WindowFlags::TITLE_BAR) + } + + #[inline] + pub fn set_top_resize_border(&self, top_resize_border: bool) { + let window = self.window; + let window_state = Arc::clone(&self.window_state); + + self.thread_executor.execute_in_thread(move || { + let _ = &window; + WindowState::set_window_flags(window_state.lock().unwrap(), window.hwnd(), |f| { + f.set(WindowFlags::TOP_RESIZE_BORDER, top_resize_border) + }); + }); + } + + #[inline] + pub fn is_top_resize_border(&self) -> bool { + let window_state = self.window_state_lock(); + window_state.window_flags.contains(WindowFlags::TOP_RESIZE_BORDER) + } + #[inline] pub fn set_corner_preference(&self, preference: CornerPreference) { unsafe { @@ -467,10 +505,20 @@ impl CoreWindow for Window { fn pre_present_notify(&self) {} + // TODO: limit to Windows 10 (and maybe 11?) fn outer_position(&self) -> Result, RequestError> { util::WindowArea::Outer .get_rect(self.hwnd()) - .map(|rect| Ok(PhysicalPosition::new(rect.left, rect.top))) + .map(|rect| { + if let Ok(offset) = util::get_offset_resize_border( + self.hwnd(), + self.window_state_lock().window_flags, + ) { + Ok(PhysicalPosition::new(rect.left + offset.left, rect.top + offset.top)) + } else { + Ok(PhysicalPosition::new(rect.left, rect.top)) + } + }) .expect( "Unexpected GetWindowRect failure; please report this error to \ rust-windowing/winit", @@ -488,8 +536,16 @@ impl CoreWindow for Window { PhysicalPosition::new(rect.left, rect.top) } + // TODO: limit to Windows 10 (and maybe 11?) fn set_outer_position(&self, position: Position) { let (x, y): (i32, i32) = position.to_physical::(self.scale_factor()).into(); + let (x_off, y_off) = if let Ok(offset) = + util::get_offset_resize_border(self.hwnd(), self.window_state_lock().window_flags) + { + (offset.left, offset.top) + } else { + (0, 0) + }; let window_state = Arc::clone(&self.window_state); let window = self.window; @@ -504,8 +560,8 @@ impl CoreWindow for Window { SetWindowPos( self.hwnd(), ptr::null_mut(), - x, - y, + x - x_off, + y - y_off, 0, 0, SWP_ASYNCWINDOWPOS | SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE, @@ -1342,6 +1398,8 @@ unsafe fn init( let mut window_flags = WindowFlags::empty(); window_flags.set(WindowFlags::MARKER_DECORATIONS, attributes.decorations); + window_flags.set(WindowFlags::TITLE_BAR, win_attributes.titlebar); + window_flags.set(WindowFlags::TOP_RESIZE_BORDER, win_attributes.top_resize_border); window_flags.set(WindowFlags::MARKER_UNDECORATED_SHADOW, win_attributes.decoration_shadow); window_flags .set(WindowFlags::ALWAYS_ON_TOP, attributes.window_level == WindowLevel::AlwaysOnTop); diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index f2536baf9d..d621f3b682 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -134,6 +134,12 @@ bitflags! { const CLIP_CHILDREN = 1 << 22; + const TITLE_BAR = 1 << 23; //0x__800000 on Windows sets WS_CAPTION (0x__C00000) + + /// Unset to signal that a window without a title bar, eg, with a custom one, + /// does not need a ~6px resize border at the top. No effect on a titled window. + const TOP_RESIZE_BORDER = 1 << 24; //0x_1000000 + const EXCLUSIVE_FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits(); } } @@ -297,6 +303,9 @@ impl WindowFlags { style &= !(WS_CAPTION | WS_BORDER); style_ex &= !WS_EX_WINDOWEDGE; } + if !self.contains(WindowFlags::TITLE_BAR) { + style &= !WS_CAPTION; + } } if self.contains(WindowFlags::POPUP) { style |= WS_POPUP; @@ -313,6 +322,11 @@ impl WindowFlags { if self.contains(WindowFlags::CLIP_CHILDREN) { style |= WS_CLIPCHILDREN; } + if self.contains(WindowFlags::TITLE_BAR) { + style |= WS_CAPTION; + } else { + style &= !WS_CAPTION; + } if self.intersects( WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN | WindowFlags::MARKER_BORDERLESS_FULLSCREEN, @@ -445,6 +459,9 @@ impl WindowFlags { if !self.contains(WindowFlags::MARKER_DECORATIONS) { style &= !(WS_CAPTION | WS_SIZEBOX); } + if !self.contains(WindowFlags::TITLE_BAR) { + style &= !WS_CAPTION; + } util::win_to_err({ let b_menu = !GetMenu(hwnd).is_null(); diff --git a/winit-core/src/window.rs b/winit-core/src/window.rs index 742ae2db9b..a86451ea5e 100644 --- a/winit-core/src/window.rs +++ b/winit-core/src/window.rs @@ -139,10 +139,12 @@ impl WindowAttributes { /// position the top left corner of the whole window you have to use /// [`Window::set_outer_position`] after creating the window. /// - **Windows:** The top left corner position of the window title bar, the window's "outer" - /// position. There may be a small gap between this position and the window due to the - /// specifics of the Window Manager. + /// position. Ignores the invisible resize borders (as well as the top visible resize border + /// that appears if a window has no title bar) on Windows 10. Thin borders are not ignored, + /// though they may also be invisible (and if resize borders exist, also resize). /// - **X11:** The top left corner of the window, the window's "outer" position. /// - **Others:** Ignored. + // TODO: document Windows 11 if it's also part of the limit? #[inline] pub fn with_position>(mut self, position: P) -> Self { self.position = Some(position.into()); @@ -647,8 +649,12 @@ pub trait Window: AsAny + Send + Sync + fmt::Debug { /// /// ## Platform-specific /// + /// - **Windows:** Ignores the invisible resize borders (as well as the top visible resize + /// border that appears if a window has no title bar) on Windows 10. Thin borders are not + /// ignored, though they may also be invisible (and if resize borders exist, also resize). /// - **Web:** Returns the top-left coordinates relative to the viewport. /// - **Android / Wayland:** Always returns [`RequestError::NotSupported`]. + // TODO: document Windows 11 if it's also part of the limit? fn outer_position(&self) -> Result, RequestError>; /// Sets the position of the window on the desktop.