Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Windows: Fix invisible gap bug in window positioning at screen edges #4111

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 168 additions & 0 deletions examples/topless.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
#![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<dyn Error>> {
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<Box<dyn Window>>,
}

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)));
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<dyn Error>> {
println!("This example is only supported on Windows.");
Ok(())
}
6 changes: 6 additions & 0 deletions src/changelog/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.

Expand Down
62 changes: 62 additions & 0 deletions src/platform/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,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.
Expand Down Expand Up @@ -393,6 +412,30 @@ 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::<crate::platform_impl::Window>().unwrap();
window.set_titlebar(titlebar)
}

#[inline]
fn is_titlebar(&self) -> bool {
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.is_titlebar()
}

#[inline]
fn set_top_resize_border(&self, top_resize_border: bool) {
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.set_top_resize_border(top_resize_border)
}

#[inline]
fn is_top_resize_border(&self) -> bool {
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.is_top_resize_border()
}

#[inline]
fn set_corner_preference(&self, preference: CornerPreference) {
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
Expand Down Expand Up @@ -483,6 +526,13 @@ pub trait WindowAttributesExtWindows {
/// This sets `WS_EX_NOREDIRECTIONBITMAP`.
fn with_no_redirection_bitmap(self, flag: bool) -> Self;

/// Enables/disables the window titlebar by setting `WS_CAPTION`.
fn with_titlebar(self, titlebar: bool) -> Self;

/// Enables/disables the window's top resize border by setting its height to 0.
/// Only for windows without a title bar.
fn with_top_resize_border(self, top_resize_border: bool) -> 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
Expand Down Expand Up @@ -557,6 +607,18 @@ impl WindowAttributesExtWindows for WindowAttributes {
self
}

#[inline]
fn with_titlebar(mut self, titlebar: bool) -> Self {
self.platform_specific.titlebar = titlebar;
self
}

#[inline]
fn with_top_resize_border(mut self, top_resize_border: bool) -> Self {
self.platform_specific.top_resize_border = top_resize_border;
self
}

#[inline]
fn with_drag_and_drop(mut self, flag: bool) -> Self {
self.platform_specific.drag_and_drop = flag;
Expand Down
30 changes: 29 additions & 1 deletion src/platform_impl/windows/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1094,7 +1094,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);
Expand Down Expand Up @@ -1138,6 +1138,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 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)
// max 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;
Expand Down Expand Up @@ -2263,6 +2284,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 {
Expand Down
4 changes: 4 additions & 0 deletions src/platform_impl/windows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ pub struct PlatformSpecificWindowAttributes {
pub title_background_color: Option<Color>,
pub title_text_color: Option<Color>,
pub corner_preference: Option<CornerPreference>,
pub titlebar: bool,
pub top_resize_border: bool,
}

impl Default for PlatformSpecificWindowAttributes {
Expand All @@ -50,6 +52,8 @@ impl Default for PlatformSpecificWindowAttributes {
title_background_color: None,
title_text_color: None,
corner_preference: None,
titlebar: true,
top_resize_border: true,
}
}
}
Expand Down
Loading
Loading