From 9e6bc4ddd0118a46bef76001438a73287df58ae3 Mon Sep 17 00:00:00 2001 From: eugenesvk Date: Fri, 16 May 2025 22:25:44 +0700 Subject: [PATCH 01/14] Windows: add a separate way to set titlebar set WS_CAPTION to allow implementing custom titlebar while maintaining resize borders. Currently MARKER_DECORATIONS internal window flag is used for both WS_CAPTION and WS_BORDER --- src/changelog/unreleased.md | 1 + src/platform/windows.rs | 30 +++++++++++++++++++++++ src/platform_impl/windows/window.rs | 20 +++++++++++++++ src/platform_impl/windows/window_state.rs | 13 ++++++++++ 4 files changed, 64 insertions(+) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index a8df3c34e8..6848d1211d 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -16,6 +16,7 @@ on how to add them: - On X11, add `Window::even_more_rare_api`. - On Wayland, add `Window::common_api`. - On Windows, add `Window::some_rare_api`. +- On Windows, add `WindowAttributesExtWindows::with_titlebar`, `Window::set_titlebar` to allow enabling/disabling titlebar separately from the resize border (currently combined in decorations). ``` When the change requires non-trivial amount of work for users to comply diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 55471db39c..a55cf8d5cd 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -284,6 +284,16 @@ 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; + /// Sets the preferred style of the window corners. /// /// Supported starting with Windows 11 Build 22000. @@ -396,6 +406,18 @@ impl WindowExtWindows for dyn Window + '_ { window.set_title_text_color(color) } + #[inline] + fn set_titlebar(&self, titlebar: bool) { + let window = self.as_any().downcast_ref::().unwrap(); + window.set_titlebar(titlebar) + } + + #[inline] + fn is_titlebar(&self) -> bool { + let window = self.as_any().downcast_ref::().unwrap(); + window.is_titlebar() + } + #[inline] fn set_corner_preference(&self, preference: CornerPreference) { let window = self.cast_ref::().unwrap(); @@ -463,6 +485,7 @@ pub struct WindowAttributesWindows { pub(crate) title_background_color: Option, pub(crate) title_text_color: Option, pub(crate) corner_preference: Option, + pub(crate) titlebar: bool, } impl Default for WindowAttributesWindows { @@ -482,6 +505,7 @@ impl Default for WindowAttributesWindows { title_background_color: None, title_text_color: None, corner_preference: None, + titlebar: true, } } } @@ -542,6 +566,12 @@ 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 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/window.rs b/src/platform_impl/windows/window.rs index 0c9f1a2dd6..ca7661a2cf 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -340,6 +340,25 @@ 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, |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_corner_preference(&self, preference: CornerPreference) { unsafe { @@ -1342,6 +1361,7 @@ 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::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..4ecde3e7d9 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -134,6 +134,8 @@ bitflags! { const CLIP_CHILDREN = 1 << 22; + const TITLE_BAR = 1 << 23; //0x__800000 on Windows sets WS_CAPTION (0x__C00000) + const EXCLUSIVE_FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits(); } } @@ -297,6 +299,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 +318,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 +455,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(); From f0d5fa372756bddba2e38ada41dfa178a190abab Mon Sep 17 00:00:00 2001 From: eugenesvk Date: Fri, 16 May 2025 22:38:42 +0700 Subject: [PATCH 02/14] Windows: add a separate way to set the top resize border for windows without title bars to allow implementing custom title bars that would handle their own resizing from the top. via TOP_RESIZE_BORDER flag and the corresponding methods to set it. Requires manuall override of WM_NCCALCSIZE and WM_NCACTIVATE to change the border of the non-client window area to 0 and prevent it from being redrawn on window activation events --- src/changelog/unreleased.md | 1 + src/platform/windows.rs | 30 +++++++++++++++++++++++ src/platform_impl/windows/window.rs | 20 +++++++++++++++ src/platform_impl/windows/window_state.rs | 4 +++ 4 files changed, 55 insertions(+) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 6848d1211d..355525dd5a 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -17,6 +17,7 @@ on how to add them: - 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 diff --git a/src/platform/windows.rs b/src/platform/windows.rs index a55cf8d5cd..5c3f919c26 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -294,6 +294,15 @@ pub trait WindowExtWindows { /// 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. @@ -418,6 +427,18 @@ impl WindowExtWindows for dyn Window + '_ { window.is_titlebar() } + #[inline] + fn set_top_resize_border(&self, top_resize_border: bool) { + let window = self.as_any().downcast_ref::().unwrap(); + window.set_top_resize_border(top_resize_border) + } + + #[inline] + fn is_top_resize_border(&self) -> bool { + let window = self.as_any().downcast_ref::().unwrap(); + window.is_top_resize_border() + } + #[inline] fn set_corner_preference(&self, preference: CornerPreference) { let window = self.cast_ref::().unwrap(); @@ -486,6 +507,7 @@ pub struct WindowAttributesWindows { 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 { @@ -506,6 +528,7 @@ impl Default for WindowAttributesWindows { title_text_color: None, corner_preference: None, titlebar: true, + top_resize_border: true, } } } @@ -572,6 +595,13 @@ impl WindowAttributesWindows { 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/window.rs b/src/platform_impl/windows/window.rs index ca7661a2cf..4087ee81cf 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -359,6 +359,25 @@ impl Window { 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, |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 { @@ -1362,6 +1381,7 @@ 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 4ecde3e7d9..d621f3b682 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -136,6 +136,10 @@ bitflags! { 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(); } } From 7e75381c96ebb3d361da807e862f82ae998816a8 Mon Sep 17 00:00:00 2001 From: eugenesvk Date: Fri, 31 Jan 2025 16:13:20 +0700 Subject: [PATCH 03/14] upd ev loop to remove top resize border on size calculations --- src/platform_impl/windows/event_loop.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index b466a3a98f..3a07f3a6aa 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1075,6 +1075,24 @@ 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; From c738194571364dbd3f60e32eb561dccb895fe6a5 Mon Sep 17 00:00:00 2001 From: eugenesvk Date: Fri, 31 Jan 2025 16:32:38 +0700 Subject: [PATCH 04/14] upd ev loop to remove top resize border on window (de)activation --- src/platform_impl/windows/event_loop.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 3a07f3a6aa..9c46472fe4 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); @@ -2149,6 +2149,12 @@ 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 { From ed17a616003086e509b9abcb2217b1895713033a Mon Sep 17 00:00:00 2001 From: eugenesvk Date: Fri, 31 Jan 2025 18:43:53 +0700 Subject: [PATCH 05/14] example: Add topless A minimal example of a window supporting a custom title bar allowing toggling the default title bar on/off, while maintaining control over the resize borders overall and the top resize border specifically The custom title bar itself is not implemented and left as an exercise for the reader --- examples/topless.rs | 127 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 examples/topless.rs diff --git a/examples/topless.rs b/examples/topless.rs new file mode 100644 index 0000000000..faec716399 --- /dev/null +++ b/examples/topless.rs @@ -0,0 +1,127 @@ +#![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}; + +#[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 ) ≝ + − top 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 status + title bar q w e r + resize border a s d f + ─ top 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::modifier_supplement::KeyEventExtModifierSupplement; +#[cfg(windows_platform)] +use winit::platform::windows::WindowAttributesExtWindows; +#[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 = + WindowAttributes::default().with_title("Topless (unless you see this)!") + .with_decorations (true )// decorations ≝true + .with_titlebar (false)// titlebar ≝true + .with_resizable (true )// resizable ≝true + .with_top_resize_border(false)// top_resize_border ≝true + .with_position(dpi::Position::Logical(dpi::LogicalPosition::new(0.0,0.0)))// position: Option + ; + 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}")} + _ => (), + } + } + }, + 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(()) +} From 130b898801d14c5fc7c5c73c930c6d619914f9a8 Mon Sep 17 00:00:00 2001 From: eugenesvk Date: Fri, 16 May 2025 22:42:49 +0700 Subject: [PATCH 06/14] fmt --- examples/topless.rs | 89 ++++++++++++++++++------- src/platform_impl/windows/event_loop.rs | 20 +++--- 2 files changed, 77 insertions(+), 32 deletions(-) diff --git a/examples/topless.rs b/examples/topless.rs index faec716399..09bdfb269c 100644 --- a/examples/topless.rs +++ b/examples/topless.rs @@ -23,19 +23,22 @@ use ::tracing::info; fn main() -> Result<(), Box> { tracing::init(); - println!("Topless mode (Windows only): + println!( + "Topless mode (Windows only): − title bar (WS_CAPTION) via with_titlebar (false) + resize border (WS_SIZEBOX) via with_resizable (true ) ≝ − top 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 + └ 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 status title bar q w e r resize border a s d f ─ top resize border z x c v - "); + " + ); let event_loop = EventLoop::new()?; @@ -65,14 +68,13 @@ 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 = - WindowAttributes::default().with_title("Topless (unless you see this)!") - .with_decorations (true )// decorations ≝true - .with_titlebar (false)// titlebar ≝true - .with_resizable (true )// resizable ≝true - .with_top_resize_border(false)// top_resize_border ≝true - .with_position(dpi::Position::Logical(dpi::LogicalPosition::new(0.0,0.0)))// position: Option - ; + let window_attributes = WindowAttributes::default() + .with_title("Topless (unless you see this)!") + .with_decorations(true) // decorations ≝true + .with_titlebar(false) // titlebar ≝true + .with_resizable(true) // resizable ≝true + .with_top_resize_border(false) // top_resize_border ≝true + .with_position(dpi::Position::Logical(dpi::LogicalPosition::new(0.0, 0.0))); self.window = Some(event_loop.create_window(window_attributes).unwrap()); } @@ -84,25 +86,64 @@ impl ApplicationHandler for Application { ) { let win = match self.window.as_ref() { Some(win) => win, - None => return, + 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::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}") + }, _ => (), } } diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 9c46472fe4..8bbfd3d3d9 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -1075,12 +1075,15 @@ 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 + // 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 + && !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 @@ -2150,9 +2153,10 @@ 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) { + 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(); From 92f7f6838a7b0de08c994cd987c3222c44f16811 Mon Sep 17 00:00:00 2001 From: eugenesvk Date: Sat, 17 May 2025 01:44:19 +0700 Subject: [PATCH 07/14] quit on Esc --- examples/topless.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/topless.rs b/examples/topless.rs index 09bdfb269c..2f2535cced 100644 --- a/examples/topless.rs +++ b/examples/topless.rs @@ -8,6 +8,7 @@ )] use std::error::Error; +use winit_core::keyboard::NamedKey; use winit::application::ApplicationHandler; use winit::event::WindowEvent; use winit::event_loop::{ActiveEventLoop, EventLoop}; @@ -144,6 +145,9 @@ impl ApplicationHandler for Application { let is = win.is_top_resize_border(); info!("is_top_resize_border = {is}") }, + Key::Named(NamedKey::Escape) => { + event_loop.exit(); + }, _ => (), } } From 62effafb67ff4c462dbb6085255d8ea8f55f2256 Mon Sep 17 00:00:00 2001 From: eugenesvk Date: Fri, 16 May 2025 22:51:03 +0700 Subject: [PATCH 08/14] fix update errors --- examples/topless.rs | 2 +- src/platform/windows.rs | 8 ++++---- src/platform_impl/windows/window.rs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/topless.rs b/examples/topless.rs index 2f2535cced..6655428088 100644 --- a/examples/topless.rs +++ b/examples/topless.rs @@ -93,7 +93,7 @@ impl ApplicationHandler for Application { match event { WindowEvent::KeyboardInput { event, .. } => { if event.state == ElementState::Pressed && !event.repeat { - match event.key_without_modifiers().as_ref() { + match event.key_without_modifiers.as_ref() { Key::Character("q") => { win.set_titlebar(true); info!("set_titlebar → true") diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 5c3f919c26..d58671b7ca 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -417,25 +417,25 @@ impl WindowExtWindows for dyn Window + '_ { #[inline] fn set_titlebar(&self, titlebar: bool) { - let window = self.as_any().downcast_ref::().unwrap(); + let window = self.cast_ref::().unwrap(); window.set_titlebar(titlebar) } #[inline] fn is_titlebar(&self) -> bool { - let window = self.as_any().downcast_ref::().unwrap(); + let window = self.cast_ref::().unwrap(); window.is_titlebar() } #[inline] fn set_top_resize_border(&self, top_resize_border: bool) { - let window = self.as_any().downcast_ref::().unwrap(); + 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.as_any().downcast_ref::().unwrap(); + let window = self.cast_ref::().unwrap(); window.is_top_resize_border() } diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 4087ee81cf..76b276ee35 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -347,7 +347,7 @@ impl Window { self.thread_executor.execute_in_thread(move || { let _ = &window; - WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { + WindowState::set_window_flags(window_state.lock().unwrap(), window.hwnd(), |f| { f.set(WindowFlags::TITLE_BAR, titlebar) }); }); @@ -366,7 +366,7 @@ impl Window { self.thread_executor.execute_in_thread(move || { let _ = &window; - WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { + WindowState::set_window_flags(window_state.lock().unwrap(), window.hwnd(), |f| { f.set(WindowFlags::TOP_RESIZE_BORDER, top_resize_border) }); }); From f248fd68b941127da1b7bd63c5a5c761b24ff4df Mon Sep 17 00:00:00 2001 From: eugenesvk Date: Sat, 17 May 2025 01:25:28 +0700 Subject: [PATCH 09/14] fix update errors in the topless example separate platform attributes per updates --- examples/topless.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/topless.rs b/examples/topless.rs index 6655428088..61e9c7bf3b 100644 --- a/examples/topless.rs +++ b/examples/topless.rs @@ -61,20 +61,21 @@ impl Application { use winit::event::ElementState; use winit::keyboard::{Key, ModifiersState}; #[cfg(windows_platform)] -use winit::platform::modifier_supplement::KeyEventExtModifierSupplement; -#[cfg(windows_platform)] -use winit::platform::windows::WindowAttributesExtWindows; +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_titlebar(false) // titlebar ≝true .with_resizable(true) // resizable ≝true - .with_top_resize_border(false) // top_resize_border ≝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()); } From 2e661f7f2d1c57914cc5a8411c8dd1d688e7ae43 Mon Sep 17 00:00:00 2001 From: eugenesvk Date: Sat, 17 May 2025 18:35:51 +0700 Subject: [PATCH 10/14] fmt --- examples/topless.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/topless.rs b/examples/topless.rs index 61e9c7bf3b..c86cbcb1c0 100644 --- a/examples/topless.rs +++ b/examples/topless.rs @@ -8,11 +8,11 @@ )] use std::error::Error; -use winit_core::keyboard::NamedKey; 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; @@ -67,10 +67,11 @@ 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_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 From 254e92768e2a536b1f973334c58c62ec96ef77ed Mon Sep 17 00:00:00 2001 From: eugenesvk Date: Sat, 17 May 2025 21:16:19 +0700 Subject: [PATCH 11/14] fix (set)_outer_position to ignore resize border and position 0,0 window at the corner --- src/platform_impl/windows/window.rs | 17 ++++++++++++++--- winit-core/src/window.rs | 6 ++++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 76b276ee35..87630aed65 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -508,7 +508,13 @@ impl CoreWindow for Window { 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", @@ -528,6 +534,11 @@ impl CoreWindow for Window { fn set_outer_position(&self, position: Position) { let (x, y): (i32, i32) = position.to_physical::(self.scale_factor()).into(); + let (x_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; @@ -542,8 +553,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, diff --git a/winit-core/src/window.rs b/winit-core/src/window.rs index 742ae2db9b..bb0be81bbb 100644 --- a/winit-core/src/window.rs +++ b/winit-core/src/window.rs @@ -139,8 +139,8 @@ 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 when a window has no title bar) on Windows 10. /// - **X11:** The top left corner of the window, the window's "outer" position. /// - **Others:** Ignored. #[inline] @@ -647,6 +647,8 @@ 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 when if a window has no title bar) on Windows 10. /// - **Web:** Returns the top-left coordinates relative to the viewport. /// - **Android / Wayland:** Always returns [`RequestError::NotSupported`]. fn outer_position(&self) -> Result, RequestError>; From d97ec576102582de2c4840e260c005409b764362 Mon Sep 17 00:00:00 2001 From: eugenesvk Date: Sat, 17 May 2025 21:17:21 +0700 Subject: [PATCH 12/14] util: add get_border_size and get_offset_resize_border get_border_size doesn't depend on current window styling to avoid issues with various styling interops and get a consistent border sizes get_offset_resize_border to get the resize border dimensions --- src/platform_impl/windows/util.rs | 69 ++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/src/platform_impl/windows/util.rs b/src/platform_impl/windows/util.rs index 162c40a6b4..c3486cf80c 100644 --- a/src/platform_impl/windows/util.rs +++ b/src/platform_impl/windows/util.rs @@ -21,7 +21,7 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{ 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, + WINDOWPLACEMENT, GWL_STYLE, GetWindowLongW, }; use crate::cursor::CursorIcon; @@ -82,6 +82,73 @@ impl WindowArea { } } +use windows_sys::Win32::UI::WindowsAndMessaging::{ + AdjustWindowRectEx, WS_BORDER, WS_CLIPSIBLINGS, WS_EX_WINDOWEDGE, WS_EX_ACCEPTFILES, WS_SIZEBOX, WS_CAPTION, WS_SYSMENU, WS_DLGFRAME, +}; +use windows_sys::Win32::Foundation::FALSE; + +/// 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::warn!("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::warn!("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::warn!("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(); From 6fee6c4b29f4d9d29053ed9d25d64ea52f4e5c24 Mon Sep 17 00:00:00 2001 From: eugenesvk Date: Sat, 17 May 2025 21:18:25 +0700 Subject: [PATCH 13/14] fnt --- src/platform_impl/windows/util.rs | 102 ++++++++++++++++++---------- src/platform_impl/windows/window.rs | 13 ++-- winit-core/src/window.rs | 4 +- 3 files changed, 76 insertions(+), 43 deletions(-) diff --git a/src/platform_impl/windows/util.rs b/src/platform_impl/windows/util.rs index c3486cf80c..f009df04de 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, GWL_STYLE, GetWindowLongW, + 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,69 +82,97 @@ impl WindowArea { } } +use windows_sys::Win32::Foundation::FALSE; use windows_sys::Win32::UI::WindowsAndMessaging::{ - AdjustWindowRectEx, WS_BORDER, WS_CLIPSIBLINGS, WS_EX_WINDOWEDGE, WS_EX_ACCEPTFILES, WS_SIZEBOX, WS_CAPTION, WS_SYSMENU, WS_DLGFRAME, + AdjustWindowRectEx, WS_BORDER, WS_CAPTION, WS_CLIPSIBLINGS, WS_DLGFRAME, WS_EX_ACCEPTFILES, + WS_EX_WINDOWEDGE, WS_SIZEBOX, WS_SYSMENU, }; -use windows_sys::Win32::Foundation::FALSE; /// 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) +/// 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 = 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{ + 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); + { + 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 ) + 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> { +/// 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::warn!("Window has resize borders, but is configured not to have them");} + 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::warn!("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::warn!("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) + if style & WS_CAPTION != WS_CAPTION { + // no caption (≝title+border) exists + if win_flags.contains(WindowFlags::TITLE_BAR) { + tracing::warn!("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 + } 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::warn!("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) + if style & WS_CAPTION != WS_CAPTION { + // no caption (≝title+border) exists + if win_flags.contains(WindowFlags::TITLE_BAR) { + tracing::warn!("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.right = offset.left; // resize borders are the same offset.bottom = offset.left; Ok(offset) } diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 87630aed65..88700e2f65 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -509,10 +509,13 @@ impl CoreWindow for Window { util::WindowArea::Outer .get_rect(self.hwnd()) .map(|rect| { - if let Ok(offset) = util::get_offset_resize_border(self.hwnd(), self.window_state_lock().window_flags) { + 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 )) + Ok(PhysicalPosition::new(rect.left, rect.top)) } }) .expect( @@ -534,10 +537,12 @@ impl CoreWindow for Window { fn set_outer_position(&self, position: Position) { let (x, y): (i32, i32) = position.to_physical::(self.scale_factor()).into(); - let (x_off, y_off) = if let Ok(offset) = util::get_offset_resize_border(self.hwnd(), self.window_state_lock().window_flags) { + 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) + (0, 0) }; let window_state = Arc::clone(&self.window_state); diff --git a/winit-core/src/window.rs b/winit-core/src/window.rs index bb0be81bbb..ec19f8e0a2 100644 --- a/winit-core/src/window.rs +++ b/winit-core/src/window.rs @@ -647,8 +647,8 @@ 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 when if a window has no title bar) on Windows 10. + /// - **Windows:** Ignores the invisible resize borders (as well as the top visible resize + /// border that appears when if a window has no title bar) on Windows 10. /// - **Web:** Returns the top-left coordinates relative to the viewport. /// - **Android / Wayland:** Always returns [`RequestError::NotSupported`]. fn outer_position(&self) -> Result, RequestError>; From 9641fd82d5e3a43dc9a523cc0fd93d5c087e6617 Mon Sep 17 00:00:00 2001 From: eugenesvk Date: Sat, 17 May 2025 21:21:49 +0700 Subject: [PATCH 14/14] Update fmt, comments --- examples/topless.rs | 12 ++++++------ src/changelog/unreleased.md | 4 ++++ src/platform_impl/windows/util.rs | 6 +++--- src/platform_impl/windows/window.rs | 2 ++ winit-core/src/window.rs | 8 ++++++-- 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/examples/topless.rs b/examples/topless.rs index c86cbcb1c0..9f59b10cae 100644 --- a/examples/topless.rs +++ b/examples/topless.rs @@ -27,17 +27,17 @@ fn main() -> Result<(), Box> { println!( "Topless mode (Windows only): − title bar (WS_CAPTION) via with_titlebar (false) - + resize border (WS_SIZEBOX) via with_resizable (true ) ≝ - − top resize border via with_top_resize_border(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 status - title bar q w e r - resize border a s d f - ─ top resize border z x c v + on off toggle query + title bar q w e r + resize border@↓←→ a s d f + resize border@↑ z x c v " ); diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 355525dd5a..d6ef1cb5f1 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -37,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_impl/windows/util.rs b/src/platform_impl/windows/util.rs index f009df04de..844e1cdb49 100644 --- a/src/platform_impl/windows/util.rs +++ b/src/platform_impl/windows/util.rs @@ -137,7 +137,7 @@ pub fn get_offset_resize_border( if style & WS_SIZEBOX == WS_SIZEBOX { // ...actually exist if !win_flags.contains(WindowFlags::RESIZABLE) { - tracing::warn!("Window has resize borders, but is configured not to have them"); + 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 @@ -145,7 +145,7 @@ pub fn get_offset_resize_border( if style & WS_CAPTION != WS_CAPTION { // no caption (≝title+border) exists if win_flags.contains(WindowFlags::TITLE_BAR) { - tracing::warn!("Window has no title bar, but is configured to have it"); + 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" @@ -162,7 +162,7 @@ pub fn get_offset_resize_border( if style & WS_CAPTION != WS_CAPTION { // no caption (≝title+border) exists if win_flags.contains(WindowFlags::TITLE_BAR) { - tracing::warn!("Window has no title bar, but is configured to have it"); + 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" diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 88700e2f65..9425a70d3a 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -505,6 +505,7 @@ 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()) @@ -535,6 +536,7 @@ 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) = diff --git a/winit-core/src/window.rs b/winit-core/src/window.rs index ec19f8e0a2..a86451ea5e 100644 --- a/winit-core/src/window.rs +++ b/winit-core/src/window.rs @@ -140,9 +140,11 @@ impl WindowAttributes { /// [`Window::set_outer_position`] after creating the window. /// - **Windows:** The top left corner position of the window title bar, the window's "outer" /// position. Ignores the invisible resize borders (as well as the top visible resize border - /// that appears when a window has no title bar) on Windows 10. + /// 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()); @@ -648,9 +650,11 @@ 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 when if a window has no title bar) on Windows 10. + /// 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.