Skip to content
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
174 changes: 174 additions & 0 deletions examples/topless.rs
Original file line number Diff line number Diff line change
@@ -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<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::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<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
60 changes: 60 additions & 0 deletions src/platform/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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::<crate::platform_impl::Window>().unwrap();
window.set_titlebar(titlebar)
}

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

#[inline]
fn set_top_resize_border(&self, top_resize_border: bool) {
let window = self.cast_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.cast_ref::<crate::platform_impl::Window>().unwrap();
window.is_top_resize_border()
}

#[inline]
fn set_corner_preference(&self, preference: CornerPreference) {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
Expand Down Expand Up @@ -463,6 +506,8 @@ pub struct WindowAttributesWindows {
pub(crate) title_background_color: Option<Color>,
pub(crate) title_text_color: Option<Color>,
pub(crate) corner_preference: Option<CornerPreference>,
pub(crate) titlebar: bool,
pub(crate) top_resize_border: bool,
}

impl Default for WindowAttributesWindows {
Expand All @@ -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,
}
}
}
Expand Down Expand Up @@ -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
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 @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
Loading