Skip to content

Commit

Permalink
Update fmt, add example, comments
Browse files Browse the repository at this point in the history
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
  • Loading branch information
eugenesvk committed Feb 16, 2025
1 parent 954c139 commit 09ac3e3
Show file tree
Hide file tree
Showing 6 changed files with 270 additions and 54 deletions.
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(())
}
4 changes: 4 additions & 0 deletions src/changelog/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
20 changes: 12 additions & 8 deletions src/platform_impl/windows/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1138,12 +1138,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 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)
// max wins have no borders
{
result = ProcResult::DefWindowProc(wparam);
let rect = unsafe { &mut *(lparam as *mut RECT) };
let adj_rect = userdata
Expand Down Expand Up @@ -2282,9 +2285,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();
Expand Down
102 changes: 65 additions & 37 deletions src/platform_impl/windows/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,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, 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, 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::utils::Lazy;
Expand Down Expand Up @@ -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<dpi::PhysicalUnit<i32>, 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<dpi::PhysicalInsets<i32>, 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<dpi::PhysicalInsets<i32>, 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::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::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::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
} 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::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.right = offset.left; // resize borders are the same
offset.bottom = offset.left;
Ok(offset)
}
Expand Down
Loading

0 comments on commit 09ac3e3

Please sign in to comment.