Skip to content

Commit 2d73206

Browse files
msiglreithkchibisov
authored andcommitted
On Windows, improve support for undecorated windows (#2419)
1 parent 2e4338b commit 2d73206

File tree

8 files changed

+274
-205
lines changed

8 files changed

+274
-205
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ And please only add new entries to the top of this list, right below the `# Unre
88

99
# Unreleased
1010

11+
- On Windows, added `WindowExtWindows::set_undecorated_shadow` and `WindowBuilderExtWindows::with_undecorated_shadow` to draw the drop shadow behind a borderless window.
12+
- On Windows, fixed default window features (ie snap, animations, shake, etc.) when decorations are disabled.
13+
1114
# 0.27.2 (2022-8-12)
1215

1316
- On macOS, fixed touch phase reporting when scrolling.

src/platform/windows.rs

+22
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ pub trait WindowExtWindows {
143143

144144
/// Whether to show or hide the window icon in the taskbar.
145145
fn set_skip_taskbar(&self, skip: bool);
146+
147+
/// Shows or hides the background drop shadow for undecorated windows.
148+
///
149+
/// Enabling the shadow causes a thin 1px line to appear on the top of the window.
150+
fn set_undecorated_shadow(&self, shadow: bool);
146151
}
147152

148153
impl WindowExtWindows for Window {
@@ -175,6 +180,11 @@ impl WindowExtWindows for Window {
175180
fn set_skip_taskbar(&self, skip: bool) {
176181
self.window.set_skip_taskbar(skip)
177182
}
183+
184+
#[inline]
185+
fn set_undecorated_shadow(&self, shadow: bool) {
186+
self.window.set_undecorated_shadow(shadow)
187+
}
178188
}
179189

180190
/// Additional methods on `WindowBuilder` that are specific to Windows.
@@ -229,6 +239,12 @@ pub trait WindowBuilderExtWindows {
229239

230240
/// Whether show or hide the window icon in the taskbar.
231241
fn with_skip_taskbar(self, skip: bool) -> WindowBuilder;
242+
243+
/// Shows or hides the background drop shadow for undecorated windows.
244+
///
245+
/// The shadow is hidden by default.
246+
/// Enabling the shadow causes a thin 1px line to appear on the top of the window.
247+
fn with_undecorated_shadow(self, shadow: bool) -> WindowBuilder;
232248
}
233249

234250
impl WindowBuilderExtWindows for WindowBuilder {
@@ -279,6 +295,12 @@ impl WindowBuilderExtWindows for WindowBuilder {
279295
self.platform_specific.skip_taskbar = skip;
280296
self
281297
}
298+
299+
#[inline]
300+
fn with_undecorated_shadow(mut self, shadow: bool) -> WindowBuilder {
301+
self.platform_specific.decoration_shadow = shadow;
302+
self
303+
}
282304
}
283305

284306
/// Additional methods on `MonitorHandle` that are specific to Windows.

src/platform_impl/windows/event_loop.rs

+66-50
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ use windows_sys::Win32::{
2525
Devices::HumanInterfaceDevice::MOUSE_MOVE_RELATIVE,
2626
Foundation::{BOOL, HANDLE, HWND, LPARAM, LRESULT, POINT, RECT, WAIT_TIMEOUT, WPARAM},
2727
Graphics::Gdi::{
28-
ClientToScreen, GetMonitorInfoW, GetUpdateRect, MonitorFromRect, MonitorFromWindow,
29-
RedrawWindow, ScreenToClient, ValidateRect, MONITORINFO, MONITOR_DEFAULTTONULL,
30-
RDW_INTERNALPAINT, SC_SCREENSAVE,
28+
GetMonitorInfoW, GetUpdateRect, MonitorFromRect, MonitorFromWindow, RedrawWindow,
29+
ScreenToClient, ValidateRect, MONITORINFO, MONITOR_DEFAULTTONULL, RDW_INTERNALPAINT,
30+
SC_SCREENSAVE,
3131
},
3232
Media::{timeBeginPeriod, timeEndPeriod, timeGetDevCaps, TIMECAPS, TIMERR_NOERROR},
3333
System::{Ole::RevokeDragDrop, Threading::GetCurrentThreadId, WindowsProgramming::INFINITE},
@@ -50,20 +50,20 @@ use windows_sys::Win32::{
5050
RIM_TYPEKEYBOARD, RIM_TYPEMOUSE,
5151
},
5252
WindowsAndMessaging::{
53-
CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, GetClientRect,
54-
GetCursorPos, GetMessageW, GetWindowLongW, LoadCursorW, MsgWaitForMultipleObjectsEx,
55-
PeekMessageW, PostMessageW, PostThreadMessageW, RegisterClassExW,
56-
RegisterWindowMessageA, SetCursor, SetWindowPos, TranslateMessage, CREATESTRUCTW,
57-
GIDC_ARRIVAL, GIDC_REMOVAL, GWL_EXSTYLE, GWL_STYLE, GWL_USERDATA, HTCAPTION, HTCLIENT,
58-
MAPVK_VK_TO_VSC, MINMAXINFO, MSG, MWMO_INPUTAVAILABLE, PM_NOREMOVE, PM_QS_PAINT,
59-
PM_REMOVE, PT_PEN, PT_TOUCH, QS_ALLEVENTS, RI_KEY_E0, RI_KEY_E1, RI_MOUSE_WHEEL,
60-
SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE,
61-
SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS, WM_CAPTURECHANGED, WM_CHAR, WM_CLOSE, WM_CREATE,
62-
WM_DESTROY, WM_DPICHANGED, WM_DROPFILES, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE,
63-
WM_GETMINMAXINFO, WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT,
64-
WM_IME_STARTCOMPOSITION, WM_INPUT, WM_INPUT_DEVICE_CHANGE, WM_KEYDOWN, WM_KEYUP,
65-
WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP,
66-
WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE, WM_NCCREATE, WM_NCDESTROY,
53+
CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, GetCursorPos,
54+
GetMessageW, LoadCursorW, MsgWaitForMultipleObjectsEx, PeekMessageW, PostMessageW,
55+
PostThreadMessageW, RegisterClassExW, RegisterWindowMessageA, SetCursor, SetWindowPos,
56+
TranslateMessage, CREATESTRUCTW, GIDC_ARRIVAL, GIDC_REMOVAL, GWL_STYLE, GWL_USERDATA,
57+
HTCAPTION, HTCLIENT, MAPVK_VK_TO_VSC, MINMAXINFO, MSG, MWMO_INPUTAVAILABLE,
58+
NCCALCSIZE_PARAMS, PM_NOREMOVE, PM_QS_PAINT, PM_REMOVE, PT_PEN, PT_TOUCH, QS_ALLEVENTS,
59+
RI_KEY_E0, RI_KEY_E1, RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED,
60+
SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS,
61+
WM_CAPTURECHANGED, WM_CHAR, WM_CLOSE, WM_CREATE, WM_DESTROY, WM_DPICHANGED,
62+
WM_DROPFILES, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION,
63+
WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT,
64+
WM_INPUT_DEVICE_CHANGE, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN,
65+
WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEHWHEEL, WM_MOUSEMOVE,
66+
WM_MOUSEWHEEL, WM_NCACTIVATE, WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY,
6767
WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, WM_POINTERUP, WM_POINTERUPDATE,
6868
WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE,
6969
WM_SYSCHAR, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH, WM_WINDOWPOSCHANGED,
@@ -633,10 +633,12 @@ pub static TASKBAR_CREATED: Lazy<u32> =
633633
Lazy::new(|| unsafe { RegisterWindowMessageA("TaskbarCreated\0".as_ptr()) });
634634

635635
fn create_event_target_window<T: 'static>() -> HWND {
636+
use windows_sys::Win32::UI::WindowsAndMessaging::CS_HREDRAW;
637+
use windows_sys::Win32::UI::WindowsAndMessaging::CS_VREDRAW;
636638
unsafe {
637639
let class = WNDCLASSEXW {
638640
cbSize: mem::size_of::<WNDCLASSEXW>() as u32,
639-
style: 0,
641+
style: CS_HREDRAW | CS_VREDRAW,
640642
lpfnWndProc: Some(thread_event_target_callback::<T>),
641643
cbClsExtra: 0,
642644
cbWndExtra: 0,
@@ -968,6 +970,32 @@ unsafe fn public_window_callback_inner<T: 'static>(
968970
// the closure to catch_unwind directly so that the match body indendation wouldn't change and
969971
// the git blame and history would be preserved.
970972
let callback = || match msg {
973+
WM_NCCALCSIZE => {
974+
let window_flags = userdata.window_state.lock().window_flags;
975+
if wparam == 0 || window_flags.contains(WindowFlags::MARKER_DECORATIONS) {
976+
return DefWindowProcW(window, msg, wparam, lparam);
977+
}
978+
979+
// Extend the client area to cover the whole non-client area.
980+
// https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize#remarks
981+
//
982+
// HACK(msiglreith): To add the drop shadow we slightly tweak the non-client area.
983+
// This leads to a small black 1px border on the top. Adding a margin manually
984+
// on all 4 borders would result in the caption getting drawn by the DWM.
985+
//
986+
// Another option would be to allow the DWM to paint inside the client area.
987+
// Unfortunately this results in janky resize behavior, where the compositor is
988+
// ahead of the window surface. Currently, there seems no option to achieve this
989+
// with the Windows API.
990+
if window_flags.contains(WindowFlags::MARKER_UNDECORATED_SHADOW) {
991+
let params = &mut *(lparam as *mut NCCALCSIZE_PARAMS);
992+
params.rgrc[0].top += 1;
993+
params.rgrc[0].bottom += 1;
994+
}
995+
996+
0
997+
}
998+
971999
WM_ENTERSIZEMOVE => {
9721000
userdata
9731001
.window_state
@@ -1049,7 +1077,7 @@ unsafe fn public_window_callback_inner<T: 'static>(
10491077
const NOMOVE_OR_NOSIZE: u32 = SWP_NOMOVE | SWP_NOSIZE;
10501078

10511079
let new_rect = if window_pos.flags & NOMOVE_OR_NOSIZE != 0 {
1052-
let cur_rect = util::get_window_rect(window)
1080+
let cur_rect = util::WindowArea::Outer.get_rect(window)
10531081
.expect("Unexpected GetWindowRect failure; please report this error to https://github.com/rust-windowing/winit");
10541082

10551083
match window_pos.flags & NOMOVE_OR_NOSIZE {
@@ -1921,19 +1949,22 @@ unsafe fn public_window_callback_inner<T: 'static>(
19211949
let mmi = lparam as *mut MINMAXINFO;
19221950

19231951
let window_state = userdata.window_state.lock();
1952+
let window_flags = window_state.window_flags;
19241953

19251954
if window_state.min_size.is_some() || window_state.max_size.is_some() {
19261955
if let Some(min_size) = window_state.min_size {
19271956
let min_size = min_size.to_physical(window_state.scale_factor);
1928-
let (width, height): (u32, u32) = util::adjust_size(window, min_size).into();
1957+
let (width, height): (u32, u32) =
1958+
window_flags.adjust_size(window, min_size).into();
19291959
(*mmi).ptMinTrackSize = POINT {
19301960
x: width as i32,
19311961
y: height as i32,
19321962
};
19331963
}
19341964
if let Some(max_size) = window_state.max_size {
19351965
let max_size = max_size.to_physical(window_state.scale_factor);
1936-
let (width, height): (u32, u32) = util::adjust_size(window, max_size).into();
1966+
let (width, height): (u32, u32) =
1967+
window_flags.adjust_size(window, max_size).into();
19371968
(*mmi).ptMaxTrackSize = POINT {
19381969
x: width as i32,
19391970
y: height as i32,
@@ -1957,7 +1988,7 @@ unsafe fn public_window_callback_inner<T: 'static>(
19571988
let new_scale_factor = dpi_to_scale_factor(new_dpi_x);
19581989
let old_scale_factor: f64;
19591990

1960-
let allow_resize = {
1991+
let (allow_resize, window_flags) = {
19611992
let mut window_state = userdata.window_state.lock();
19621993
old_scale_factor = window_state.scale_factor;
19631994
window_state.scale_factor = new_scale_factor;
@@ -1966,12 +1997,11 @@ unsafe fn public_window_callback_inner<T: 'static>(
19661997
return 0;
19671998
}
19681999

1969-
window_state.fullscreen.is_none()
1970-
&& !window_state.window_flags().contains(WindowFlags::MAXIMIZED)
1971-
};
2000+
let allow_resize = window_state.fullscreen.is_none()
2001+
&& !window_state.window_flags().contains(WindowFlags::MAXIMIZED);
19722002

1973-
let style = GetWindowLongW(window, GWL_STYLE) as u32;
1974-
let style_ex = GetWindowLongW(window, GWL_EXSTYLE) as u32;
2003+
(allow_resize, window_state.window_flags)
2004+
};
19752005

19762006
// New size as suggested by Windows.
19772007
let suggested_rect = *(lparam as *const RECT);
@@ -1985,28 +2015,18 @@ unsafe fn public_window_callback_inner<T: 'static>(
19852015
// let margin_right: i32;
19862016
// let margin_bottom: i32;
19872017
{
1988-
let adjusted_rect =
1989-
util::adjust_window_rect_with_styles(window, style, style_ex, suggested_rect)
1990-
.unwrap_or(suggested_rect);
2018+
let adjusted_rect = window_flags
2019+
.adjust_rect(window, suggested_rect)
2020+
.unwrap_or(suggested_rect);
19912021
margin_left = suggested_rect.left - adjusted_rect.left;
19922022
margin_top = suggested_rect.top - adjusted_rect.top;
19932023
// margin_right = adjusted_rect.right - suggested_rect.right;
19942024
// margin_bottom = adjusted_rect.bottom - suggested_rect.bottom;
19952025
}
19962026

1997-
let old_physical_inner_rect = {
1998-
let mut old_physical_inner_rect = mem::zeroed();
1999-
GetClientRect(window, &mut old_physical_inner_rect);
2000-
let mut origin = mem::zeroed();
2001-
ClientToScreen(window, &mut origin);
2002-
2003-
old_physical_inner_rect.left += origin.x;
2004-
old_physical_inner_rect.right += origin.x;
2005-
old_physical_inner_rect.top += origin.y;
2006-
old_physical_inner_rect.bottom += origin.y;
2007-
2008-
old_physical_inner_rect
2009-
};
2027+
let old_physical_inner_rect = util::WindowArea::Inner
2028+
.get_rect(window)
2029+
.expect("failed to query (old) inner window area");
20102030
let old_physical_inner_size = PhysicalSize::new(
20112031
(old_physical_inner_rect.right - old_physical_inner_rect.left) as u32,
20122032
(old_physical_inner_rect.bottom - old_physical_inner_rect.top) as u32,
@@ -2060,13 +2080,9 @@ unsafe fn public_window_callback_inner<T: 'static>(
20602080
bottom: suggested_ul.1 + new_physical_inner_size.height as i32,
20612081
};
20622082

2063-
conservative_rect = util::adjust_window_rect_with_styles(
2064-
window,
2065-
style,
2066-
style_ex,
2067-
conservative_rect,
2068-
)
2069-
.unwrap_or(conservative_rect);
2083+
conservative_rect = window_flags
2084+
.adjust_rect(window, conservative_rect)
2085+
.unwrap_or(conservative_rect);
20702086

20712087
// If we're dragging the window, offset the window so that the cursor's
20722088
// relative horizontal position in the title bar is preserved.

src/platform_impl/windows/event_loop/runner.rs

+11-6
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ use crate::{
1616
dpi::PhysicalSize,
1717
event::{Event, StartCause, WindowEvent},
1818
event_loop::ControlFlow,
19-
platform_impl::platform::util,
19+
platform_impl::platform::{
20+
event_loop::{WindowData, GWL_USERDATA},
21+
get_window_long,
22+
},
2023
window::WindowId,
2124
};
2225

@@ -434,11 +437,13 @@ impl<T> BufferedEvent<T> {
434437
new_inner_size: &mut new_inner_size,
435438
},
436439
});
437-
util::set_inner_size_physical(
438-
(window_id.0).0,
439-
new_inner_size.width as _,
440-
new_inner_size.height as _,
441-
);
440+
441+
let window_flags = unsafe {
442+
let userdata =
443+
get_window_long(window_id.0.into(), GWL_USERDATA) as *mut WindowData<T>;
444+
(*userdata).window_state.lock().window_flags
445+
};
446+
window_flags.set_size((window_id.0).0, new_inner_size);
442447
}
443448
}
444449
}

src/platform_impl/windows/mod.rs

+8
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ pub struct PlatformSpecificWindowBuilderAttributes {
3636
pub drag_and_drop: bool,
3737
pub preferred_theme: Option<Theme>,
3838
pub skip_taskbar: bool,
39+
pub decoration_shadow: bool,
3940
}
4041

4142
impl Default for PlatformSpecificWindowBuilderAttributes {
@@ -48,6 +49,7 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
4849
drag_and_drop: true,
4950
preferred_theme: None,
5051
skip_taskbar: false,
52+
decoration_shadow: false,
5153
}
5254
}
5355
}
@@ -106,6 +108,12 @@ impl From<WindowId> for u64 {
106108
}
107109
}
108110

111+
impl From<WindowId> for HWND {
112+
fn from(window_id: WindowId) -> Self {
113+
window_id.0
114+
}
115+
}
116+
109117
impl From<u64> for WindowId {
110118
fn from(raw_id: u64) -> Self {
111119
Self(raw_id as HWND)

0 commit comments

Comments
 (0)