From 76f158d3102aeb54e6c1c2ce2fd34dbe6b0daab1 Mon Sep 17 00:00:00 2001 From: Markus Siglreithmaier Date: Mon, 15 Aug 2022 02:36:37 +0200 Subject: [PATCH] On Windows, improve support for undecorated windows (#2419) --- CHANGELOG.md | 3 + src/platform/windows.rs | 22 +++ src/platform_impl/windows/event_loop.rs | 116 ++++++++------- .../windows/event_loop/runner.rs | 17 ++- src/platform_impl/windows/mod.rs | 8 ++ src/platform_impl/windows/util.rs | 136 ++++-------------- src/platform_impl/windows/window.rs | 46 ++++-- src/platform_impl/windows/window_state.rs | 131 ++++++++++++----- 8 files changed, 274 insertions(+), 205 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4aba396e..bae9bd74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ And please only add new entries to the top of this list, right below the `# Unre # Unreleased +- On Windows, added `WindowExtWindows::set_undecorated_shadow` and `WindowBuilderExtWindows::with_undecorated_shadow` to draw the drop shadow behind a borderless window. +- On Windows, fixed default window features (ie snap, animations, shake, etc.) when decorations are disabled. + # 0.27.2 (2022-8-12) - On macOS, fixed touch phase reporting when scrolling. diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 2e5d05a7..63c90ff7 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -143,6 +143,11 @@ pub trait WindowExtWindows { /// Whether to show or hide the window icon in the taskbar. fn set_skip_taskbar(&self, skip: bool); + + /// Shows or hides the background drop shadow for undecorated windows. + /// + /// Enabling the shadow causes a thin 1px line to appear on the top of the window. + fn set_undecorated_shadow(&self, shadow: bool); } impl WindowExtWindows for Window { @@ -175,6 +180,11 @@ impl WindowExtWindows for Window { fn set_skip_taskbar(&self, skip: bool) { self.window.set_skip_taskbar(skip) } + + #[inline] + fn set_undecorated_shadow(&self, shadow: bool) { + self.window.set_undecorated_shadow(shadow) + } } /// Additional methods on `WindowBuilder` that are specific to Windows. @@ -229,6 +239,12 @@ pub trait WindowBuilderExtWindows { /// Whether show or hide the window icon in the taskbar. fn with_skip_taskbar(self, skip: bool) -> WindowBuilder; + + /// Shows or hides the background drop shadow for undecorated windows. + /// + /// The shadow is hidden by default. + /// Enabling the shadow causes a thin 1px line to appear on the top of the window. + fn with_undecorated_shadow(self, shadow: bool) -> WindowBuilder; } impl WindowBuilderExtWindows for WindowBuilder { @@ -279,6 +295,12 @@ impl WindowBuilderExtWindows for WindowBuilder { self.platform_specific.skip_taskbar = skip; self } + + #[inline] + fn with_undecorated_shadow(mut self, shadow: bool) -> WindowBuilder { + self.platform_specific.decoration_shadow = shadow; + self + } } /// Additional methods on `MonitorHandle` that are specific to Windows. diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 8c6fdfbd..06f885b6 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -25,9 +25,9 @@ use windows_sys::Win32::{ Devices::HumanInterfaceDevice::MOUSE_MOVE_RELATIVE, Foundation::{BOOL, HANDLE, HWND, LPARAM, LRESULT, POINT, RECT, WAIT_TIMEOUT, WPARAM}, Graphics::Gdi::{ - ClientToScreen, GetMonitorInfoW, GetUpdateRect, MonitorFromRect, MonitorFromWindow, - RedrawWindow, ScreenToClient, ValidateRect, MONITORINFO, MONITOR_DEFAULTTONULL, - RDW_INTERNALPAINT, SC_SCREENSAVE, + GetMonitorInfoW, GetUpdateRect, MonitorFromRect, MonitorFromWindow, RedrawWindow, + ScreenToClient, ValidateRect, MONITORINFO, MONITOR_DEFAULTTONULL, RDW_INTERNALPAINT, + SC_SCREENSAVE, }, Media::{timeBeginPeriod, timeEndPeriod, timeGetDevCaps, TIMECAPS, TIMERR_NOERROR}, System::{Ole::RevokeDragDrop, Threading::GetCurrentThreadId, WindowsProgramming::INFINITE}, @@ -50,20 +50,20 @@ use windows_sys::Win32::{ RIM_TYPEKEYBOARD, RIM_TYPEMOUSE, }, WindowsAndMessaging::{ - CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, GetClientRect, - GetCursorPos, GetMessageW, GetWindowLongW, LoadCursorW, MsgWaitForMultipleObjectsEx, - PeekMessageW, PostMessageW, PostThreadMessageW, RegisterClassExW, - RegisterWindowMessageA, SetCursor, SetWindowPos, TranslateMessage, CREATESTRUCTW, - GIDC_ARRIVAL, GIDC_REMOVAL, GWL_EXSTYLE, GWL_STYLE, GWL_USERDATA, HTCAPTION, HTCLIENT, - MAPVK_VK_TO_VSC, MINMAXINFO, MSG, MWMO_INPUTAVAILABLE, PM_NOREMOVE, PM_QS_PAINT, - PM_REMOVE, PT_PEN, PT_TOUCH, QS_ALLEVENTS, RI_KEY_E0, RI_KEY_E1, RI_MOUSE_WHEEL, - SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, - SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS, WM_CAPTURECHANGED, WM_CHAR, WM_CLOSE, WM_CREATE, - WM_DESTROY, WM_DPICHANGED, WM_DROPFILES, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, - WM_GETMINMAXINFO, WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT, - WM_IME_STARTCOMPOSITION, WM_INPUT, WM_INPUT_DEVICE_CHANGE, WM_KEYDOWN, WM_KEYUP, - WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, - WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE, WM_NCCREATE, WM_NCDESTROY, + CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, GetCursorPos, + GetMessageW, LoadCursorW, MsgWaitForMultipleObjectsEx, PeekMessageW, PostMessageW, + PostThreadMessageW, RegisterClassExW, RegisterWindowMessageA, SetCursor, SetWindowPos, + TranslateMessage, CREATESTRUCTW, GIDC_ARRIVAL, GIDC_REMOVAL, GWL_STYLE, GWL_USERDATA, + HTCAPTION, HTCLIENT, MAPVK_VK_TO_VSC, MINMAXINFO, MSG, MWMO_INPUTAVAILABLE, + NCCALCSIZE_PARAMS, PM_NOREMOVE, PM_QS_PAINT, PM_REMOVE, PT_PEN, PT_TOUCH, QS_ALLEVENTS, + RI_KEY_E0, RI_KEY_E1, RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED, + SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS, + WM_CAPTURECHANGED, WM_CHAR, WM_CLOSE, WM_CREATE, WM_DESTROY, WM_DPICHANGED, + WM_DROPFILES, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION, + WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT, + WM_INPUT_DEVICE_CHANGE, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, + WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEHWHEEL, WM_MOUSEMOVE, + WM_MOUSEWHEEL, WM_NCACTIVATE, WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE, WM_SYSCHAR, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH, WM_WINDOWPOSCHANGED, @@ -633,10 +633,12 @@ pub static TASKBAR_CREATED: Lazy = Lazy::new(|| unsafe { RegisterWindowMessageA("TaskbarCreated\0".as_ptr()) }); fn create_event_target_window() -> HWND { + use windows_sys::Win32::UI::WindowsAndMessaging::CS_HREDRAW; + use windows_sys::Win32::UI::WindowsAndMessaging::CS_VREDRAW; unsafe { let class = WNDCLASSEXW { cbSize: mem::size_of::() as u32, - style: 0, + style: CS_HREDRAW | CS_VREDRAW, lpfnWndProc: Some(thread_event_target_callback::), cbClsExtra: 0, cbWndExtra: 0, @@ -968,6 +970,32 @@ unsafe fn public_window_callback_inner( // the closure to catch_unwind directly so that the match body indendation wouldn't change and // the git blame and history would be preserved. let callback = || match msg { + WM_NCCALCSIZE => { + let window_flags = userdata.window_state.lock().window_flags; + if wparam == 0 || window_flags.contains(WindowFlags::MARKER_DECORATIONS) { + return DefWindowProcW(window, msg, wparam, lparam); + } + + // Extend the client area to cover the whole non-client area. + // https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize#remarks + // + // HACK(msiglreith): To add the drop shadow we slightly tweak the non-client area. + // This leads to a small black 1px border on the top. Adding a margin manually + // on all 4 borders would result in the caption getting drawn by the DWM. + // + // Another option would be to allow the DWM to paint inside the client area. + // Unfortunately this results in janky resize behavior, where the compositor is + // ahead of the window surface. Currently, there seems no option to achieve this + // with the Windows API. + if window_flags.contains(WindowFlags::MARKER_UNDECORATED_SHADOW) { + let params = &mut *(lparam as *mut NCCALCSIZE_PARAMS); + params.rgrc[0].top += 1; + params.rgrc[0].bottom += 1; + } + + 0 + } + WM_ENTERSIZEMOVE => { userdata .window_state @@ -1049,7 +1077,7 @@ unsafe fn public_window_callback_inner( const NOMOVE_OR_NOSIZE: u32 = SWP_NOMOVE | SWP_NOSIZE; let new_rect = if window_pos.flags & NOMOVE_OR_NOSIZE != 0 { - let cur_rect = util::get_window_rect(window) + let cur_rect = util::WindowArea::Outer.get_rect(window) .expect("Unexpected GetWindowRect failure; please report this error to https://github.com/rust-windowing/winit"); match window_pos.flags & NOMOVE_OR_NOSIZE { @@ -1921,11 +1949,13 @@ unsafe fn public_window_callback_inner( let mmi = lparam as *mut MINMAXINFO; let window_state = userdata.window_state.lock(); + let window_flags = window_state.window_flags; if window_state.min_size.is_some() || window_state.max_size.is_some() { if let Some(min_size) = window_state.min_size { let min_size = min_size.to_physical(window_state.scale_factor); - let (width, height): (u32, u32) = util::adjust_size(window, min_size).into(); + let (width, height): (u32, u32) = + window_flags.adjust_size(window, min_size).into(); (*mmi).ptMinTrackSize = POINT { x: width as i32, y: height as i32, @@ -1933,7 +1963,8 @@ unsafe fn public_window_callback_inner( } if let Some(max_size) = window_state.max_size { let max_size = max_size.to_physical(window_state.scale_factor); - let (width, height): (u32, u32) = util::adjust_size(window, max_size).into(); + let (width, height): (u32, u32) = + window_flags.adjust_size(window, max_size).into(); (*mmi).ptMaxTrackSize = POINT { x: width as i32, y: height as i32, @@ -1957,7 +1988,7 @@ unsafe fn public_window_callback_inner( let new_scale_factor = dpi_to_scale_factor(new_dpi_x); let old_scale_factor: f64; - let allow_resize = { + let (allow_resize, window_flags) = { let mut window_state = userdata.window_state.lock(); old_scale_factor = window_state.scale_factor; window_state.scale_factor = new_scale_factor; @@ -1966,12 +1997,11 @@ unsafe fn public_window_callback_inner( return 0; } - window_state.fullscreen.is_none() - && !window_state.window_flags().contains(WindowFlags::MAXIMIZED) - }; + let allow_resize = window_state.fullscreen.is_none() + && !window_state.window_flags().contains(WindowFlags::MAXIMIZED); - let style = GetWindowLongW(window, GWL_STYLE) as u32; - let style_ex = GetWindowLongW(window, GWL_EXSTYLE) as u32; + (allow_resize, window_state.window_flags) + }; // New size as suggested by Windows. let suggested_rect = *(lparam as *const RECT); @@ -1985,28 +2015,18 @@ unsafe fn public_window_callback_inner( // let margin_right: i32; // let margin_bottom: i32; { - let adjusted_rect = - util::adjust_window_rect_with_styles(window, style, style_ex, suggested_rect) - .unwrap_or(suggested_rect); + let adjusted_rect = window_flags + .adjust_rect(window, suggested_rect) + .unwrap_or(suggested_rect); margin_left = suggested_rect.left - adjusted_rect.left; margin_top = suggested_rect.top - adjusted_rect.top; // margin_right = adjusted_rect.right - suggested_rect.right; // margin_bottom = adjusted_rect.bottom - suggested_rect.bottom; } - let old_physical_inner_rect = { - let mut old_physical_inner_rect = mem::zeroed(); - GetClientRect(window, &mut old_physical_inner_rect); - let mut origin = mem::zeroed(); - ClientToScreen(window, &mut origin); - - old_physical_inner_rect.left += origin.x; - old_physical_inner_rect.right += origin.x; - old_physical_inner_rect.top += origin.y; - old_physical_inner_rect.bottom += origin.y; - - old_physical_inner_rect - }; + let old_physical_inner_rect = util::WindowArea::Inner + .get_rect(window) + .expect("failed to query (old) inner window area"); let old_physical_inner_size = PhysicalSize::new( (old_physical_inner_rect.right - old_physical_inner_rect.left) as u32, (old_physical_inner_rect.bottom - old_physical_inner_rect.top) as u32, @@ -2060,13 +2080,9 @@ unsafe fn public_window_callback_inner( bottom: suggested_ul.1 + new_physical_inner_size.height as i32, }; - conservative_rect = util::adjust_window_rect_with_styles( - window, - style, - style_ex, - conservative_rect, - ) - .unwrap_or(conservative_rect); + conservative_rect = window_flags + .adjust_rect(window, conservative_rect) + .unwrap_or(conservative_rect); // If we're dragging the window, offset the window so that the cursor's // relative horizontal position in the title bar is preserved. diff --git a/src/platform_impl/windows/event_loop/runner.rs b/src/platform_impl/windows/event_loop/runner.rs index af030055..945d306f 100644 --- a/src/platform_impl/windows/event_loop/runner.rs +++ b/src/platform_impl/windows/event_loop/runner.rs @@ -16,7 +16,10 @@ use crate::{ dpi::PhysicalSize, event::{Event, StartCause, WindowEvent}, event_loop::ControlFlow, - platform_impl::platform::util, + platform_impl::platform::{ + event_loop::{WindowData, GWL_USERDATA}, + get_window_long, + }, window::WindowId, }; @@ -434,11 +437,13 @@ impl BufferedEvent { new_inner_size: &mut new_inner_size, }, }); - util::set_inner_size_physical( - (window_id.0).0, - new_inner_size.width as _, - new_inner_size.height as _, - ); + + let window_flags = unsafe { + let userdata = + get_window_long(window_id.0.into(), GWL_USERDATA) as *mut WindowData; + (*userdata).window_state.lock().window_flags + }; + window_flags.set_size((window_id.0).0, new_inner_size); } } } diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 84bf917d..3a385422 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -36,6 +36,7 @@ pub struct PlatformSpecificWindowBuilderAttributes { pub drag_and_drop: bool, pub preferred_theme: Option, pub skip_taskbar: bool, + pub decoration_shadow: bool, } impl Default for PlatformSpecificWindowBuilderAttributes { @@ -48,6 +49,7 @@ impl Default for PlatformSpecificWindowBuilderAttributes { drag_and_drop: true, preferred_theme: None, skip_taskbar: false, + decoration_shadow: false, } } } @@ -106,6 +108,12 @@ impl From for u64 { } } +impl From for HWND { + fn from(window_id: WindowId) -> Self { + window_id.0 + } +} + impl From for WindowId { fn from(raw_id: u64) -> Self { Self(raw_id as HWND) diff --git a/src/platform_impl/windows/util.rs b/src/platform_impl/windows/util.rs index fd74b674..5f09bee6 100644 --- a/src/platform_impl/windows/util.rs +++ b/src/platform_impl/windows/util.rs @@ -14,7 +14,7 @@ use windows_sys::{ core::{HRESULT, PCWSTR}, Win32::{ Foundation::{BOOL, HINSTANCE, HWND, RECT}, - Graphics::Gdi::{ClientToScreen, InvalidateRgn, HMONITOR}, + Graphics::Gdi::{ClientToScreen, HMONITOR}, System::{ LibraryLoader::{GetProcAddress, LoadLibraryA}, SystemServices::IMAGE_DOS_HEADER, @@ -23,19 +23,16 @@ use windows_sys::{ HiDpi::{DPI_AWARENESS_CONTEXT, MONITOR_DPI_TYPE, PROCESS_DPI_AWARENESS}, Input::KeyboardAndMouse::GetActiveWindow, WindowsAndMessaging::{ - AdjustWindowRectEx, ClipCursor, GetClientRect, GetClipCursor, GetMenu, - GetSystemMetrics, GetWindowLongW, GetWindowRect, SetWindowPos, ShowCursor, - GWL_EXSTYLE, 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, SWP_ASYNCWINDOWPOS, SWP_NOACTIVATE, SWP_NOMOVE, - SWP_NOREPOSITION, SWP_NOZORDER, WINDOW_EX_STYLE, WINDOW_STYLE, + ClipCursor, GetClientRect, GetClipCursor, GetSystemMetrics, GetWindowRect, + 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, }, }, }, }; -use crate::{dpi::PhysicalSize, window::CursorIcon}; +use crate::window::CursorIcon; pub fn encode_wide(string: impl AsRef) -> Vec { string.as_ref().encode_wide().chain(once(0)).collect() @@ -56,114 +53,43 @@ where bitset & flag == flag } -pub unsafe fn status_map BOOL>(mut fun: F) -> Option { - let mut data: T = mem::zeroed(); - if fun(&mut data) != false.into() { - Some(data) - } else { - None - } -} - -fn win_to_err BOOL>(f: F) -> Result<(), io::Error> { - if f() != false.into() { +pub(crate) fn win_to_err(result: BOOL) -> Result<(), io::Error> { + if result != false.into() { Ok(()) } else { Err(io::Error::last_os_error()) } } -pub fn get_window_rect(hwnd: HWND) -> Option { - unsafe { status_map(|rect| GetWindowRect(hwnd, rect)) } +pub enum WindowArea { + Outer, + Inner, } -pub fn get_client_rect(hwnd: HWND) -> Result { - unsafe { - let mut rect = mem::zeroed(); - let mut top_left = mem::zeroed(); +impl WindowArea { + pub fn get_rect(self, hwnd: HWND) -> Result { + let mut rect = unsafe { mem::zeroed() }; - win_to_err(|| ClientToScreen(hwnd, &mut top_left))?; - win_to_err(|| GetClientRect(hwnd, &mut rect))?; - rect.left += top_left.x; - rect.top += top_left.y; - rect.right += top_left.x; - rect.bottom += top_left.y; + match self { + WindowArea::Outer => { + win_to_err(unsafe { GetWindowRect(hwnd, &mut rect) })?; + } + WindowArea::Inner => unsafe { + let mut top_left = mem::zeroed(); + + win_to_err(ClientToScreen(hwnd, &mut top_left))?; + win_to_err(GetClientRect(hwnd, &mut rect))?; + rect.left += top_left.x; + rect.top += top_left.y; + rect.right += top_left.x; + rect.bottom += top_left.y; + }, + } Ok(rect) } } -pub fn adjust_size(hwnd: HWND, size: PhysicalSize) -> PhysicalSize { - let (width, height): (u32, u32) = size.into(); - let rect = RECT { - left: 0, - right: width as i32, - top: 0, - bottom: height as i32, - }; - let rect = adjust_window_rect(hwnd, rect).unwrap_or(rect); - PhysicalSize::new((rect.right - rect.left) as _, (rect.bottom - rect.top) as _) -} - -pub(crate) fn set_inner_size_physical(window: HWND, x: u32, y: u32) { - unsafe { - let rect = adjust_window_rect( - window, - RECT { - top: 0, - left: 0, - bottom: y as i32, - right: x as i32, - }, - ) - .expect("adjust_window_rect failed"); - - let outer_x = (rect.right - rect.left).abs() as _; - let outer_y = (rect.top - rect.bottom).abs() as _; - SetWindowPos( - window, - 0, - 0, - 0, - outer_x, - outer_y, - SWP_ASYNCWINDOWPOS | SWP_NOZORDER | SWP_NOREPOSITION | SWP_NOMOVE | SWP_NOACTIVATE, - ); - InvalidateRgn(window, 0, false.into()); - } -} - -pub fn adjust_window_rect(hwnd: HWND, rect: RECT) -> Option { - unsafe { - let style = GetWindowLongW(hwnd, GWL_STYLE) as u32; - let style_ex = GetWindowLongW(hwnd, GWL_EXSTYLE) as u32; - adjust_window_rect_with_styles(hwnd, style, style_ex, rect) - } -} - -pub fn adjust_window_rect_with_styles( - hwnd: HWND, - style: WINDOW_STYLE, - style_ex: WINDOW_EX_STYLE, - rect: RECT, -) -> Option { - unsafe { - status_map(|r| { - *r = rect; - - let b_menu = GetMenu(hwnd) != 0; - 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(r, style, b_menu.into(), style_ex, dpi) - } else { - AdjustWindowRectEx(r, style, b_menu.into(), style_ex) - } - }) - } -} - pub fn set_cursor_hidden(hidden: bool) { static HIDDEN: AtomicBool = AtomicBool::new(false); let changed = HIDDEN.swap(hidden, Ordering::SeqCst) ^ hidden; @@ -175,7 +101,7 @@ pub fn set_cursor_hidden(hidden: bool) { pub fn get_cursor_clip() -> Result { unsafe { let mut rect: RECT = mem::zeroed(); - win_to_err(|| GetClipCursor(&mut rect)).map(|_| rect) + win_to_err(GetClipCursor(&mut rect)).map(|_| rect) } } @@ -188,7 +114,7 @@ pub fn set_cursor_clip(rect: Option) -> Result<(), io::Error> { .as_ref() .map(|r| r as *const RECT) .unwrap_or(ptr::null()); - win_to_err(|| ClipCursor(rect_ptr)) + win_to_err(ClipCursor(rect_ptr)) } } diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index e63075a8..63e41dff 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -132,7 +132,7 @@ impl Window { #[inline] pub fn outer_position(&self) -> Result, NotSupportedError> { - util::get_window_rect(self.hwnd()) + util::WindowArea::Outer.get_rect(self.hwnd()) .map(|rect| Ok(PhysicalPosition::new(rect.left as i32, rect.top as i32))) .expect("Unexpected GetWindowRect failure; please report this error to https://github.com/rust-windowing/winit") } @@ -187,7 +187,8 @@ impl Window { #[inline] pub fn outer_size(&self) -> PhysicalSize { - util::get_window_rect(self.hwnd()) + util::WindowArea::Outer + .get_rect(self.hwnd()) .map(|rect| { PhysicalSize::new( (rect.right - rect.left) as u32, @@ -200,7 +201,7 @@ impl Window { #[inline] pub fn set_inner_size(&self, size: Size) { let scale_factor = self.scale_factor(); - let (width, height) = size.to_physical::(scale_factor).into(); + let physical_size = size.to_physical::(scale_factor); let window_state = Arc::clone(&self.window_state); let window = self.window.clone(); @@ -211,7 +212,8 @@ impl Window { }); }); - util::set_inner_size_physical(self.hwnd(), width, height); + let window_flags = self.window_state.lock().window_flags; + window_flags.set_size(self.hwnd(), physical_size); } #[inline] @@ -577,7 +579,7 @@ impl Window { self.thread_executor.execute_in_thread(move || { let _ = &window; WindowState::set_window_flags(window_state.lock(), window.0, |f| { - f.set(WindowFlags::DECORATIONS, decorations) + f.set(WindowFlags::MARKER_DECORATIONS, decorations) }); }); } @@ -585,7 +587,9 @@ impl Window { #[inline] pub fn is_decorated(&self) -> bool { let window_state = self.window_state.lock(); - window_state.window_flags.contains(WindowFlags::DECORATIONS) + window_state + .window_flags + .contains(WindowFlags::MARKER_DECORATIONS) } #[inline] @@ -691,6 +695,19 @@ impl Window { unsafe { set_skip_taskbar(self.hwnd(), skip) }; } + #[inline] + pub fn set_undecorated_shadow(&self, shadow: bool) { + let window = self.window.clone(); + let window_state = Arc::clone(&self.window_state); + + self.thread_executor.execute_in_thread(move || { + let _ = &window; + WindowState::set_window_flags(window_state.lock(), window.0, |f| { + f.set(WindowFlags::MARKER_UNDECORATED_SHADOW, shadow) + }); + }); + } + #[inline] pub fn focus_window(&self) { let window = self.window.clone(); @@ -898,6 +915,14 @@ impl<'a, T: 'static> InitData<'a, T> { } } + // let margins = MARGINS { + // cxLeftWidth: 1, + // cxRightWidth: 1, + // cyTopHeight: 1, + // cyBottomHeight: 1, + // }; + // dbg!(DwmExtendFrameIntoClientArea(win.hwnd(), &margins as *const _)); + if let Some(position) = attributes.position { win.set_outer_position(position); } @@ -916,7 +941,11 @@ where let class_name = register_window_class::(&attributes.window_icon, &pl_attribs.taskbar_icon); let mut window_flags = WindowFlags::empty(); - window_flags.set(WindowFlags::DECORATIONS, attributes.decorations); + window_flags.set(WindowFlags::MARKER_DECORATIONS, attributes.decorations); + window_flags.set( + WindowFlags::MARKER_UNDECORATED_SHADOW, + pl_attribs.decoration_shadow, + ); window_flags.set(WindowFlags::ALWAYS_ON_TOP, attributes.always_on_top); window_flags.set( WindowFlags::NO_BACK_BUFFER, @@ -997,6 +1026,7 @@ unsafe fn register_window_class( .map(|icon| icon.inner.as_raw_handle()) .unwrap_or(0); + use windows_sys::Win32::UI::WindowsAndMessaging::COLOR_WINDOWFRAME; let class = WNDCLASSEXW { cbSize: mem::size_of::() as u32, style: CS_HREDRAW | CS_VREDRAW, @@ -1006,7 +1036,7 @@ unsafe fn register_window_class( hInstance: util::get_instance_handle(), hIcon: h_icon, hCursor: 0, // must be null in order for cursor state to work properly - hbrBackground: 0, + hbrBackground: COLOR_WINDOWFRAME as _, lpszMenuName: ptr::null(), lpszClassName: class_name.as_ptr(), hIconSm: h_icon_small, diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 21841f07..bf0589c8 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -1,5 +1,5 @@ use crate::{ - dpi::{PhysicalPosition, Size}, + dpi::{PhysicalPosition, PhysicalSize, Size}, event::ModifiersState, icon::Icon, platform_impl::platform::{event_loop, util}, @@ -11,14 +11,15 @@ use windows_sys::Win32::{ Foundation::{HWND, RECT}, Graphics::Gdi::InvalidateRgn, UI::WindowsAndMessaging::{ - SendMessageW, SetWindowLongW, SetWindowPos, ShowWindow, GWL_EXSTYLE, GWL_STYLE, - HWND_NOTOPMOST, HWND_TOPMOST, SWP_ASYNCWINDOWPOS, SWP_FRAMECHANGED, SWP_NOACTIVATE, - SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, SW_HIDE, SW_MAXIMIZE, SW_MINIMIZE, SW_RESTORE, - SW_SHOW, WINDOWPLACEMENT, WINDOW_EX_STYLE, WINDOW_STYLE, WS_BORDER, WS_CAPTION, WS_CHILD, - WS_CLIPCHILDREN, WS_CLIPSIBLINGS, WS_EX_ACCEPTFILES, WS_EX_APPWINDOW, WS_EX_LAYERED, - WS_EX_LEFT, WS_EX_NOREDIRECTIONBITMAP, WS_EX_TOPMOST, WS_EX_TRANSPARENT, WS_EX_WINDOWEDGE, - WS_MAXIMIZE, WS_MAXIMIZEBOX, WS_MINIMIZE, WS_MINIMIZEBOX, WS_OVERLAPPED, - WS_OVERLAPPEDWINDOW, WS_POPUP, WS_SIZEBOX, WS_SYSMENU, WS_VISIBLE, + AdjustWindowRectEx, GetMenu, GetWindowLongW, SendMessageW, SetWindowLongW, SetWindowPos, + ShowWindow, GWL_EXSTYLE, GWL_STYLE, HWND_NOTOPMOST, HWND_TOPMOST, SWP_ASYNCWINDOWPOS, + SWP_FRAMECHANGED, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOREPOSITION, SWP_NOSIZE, SWP_NOZORDER, + SW_HIDE, SW_MAXIMIZE, SW_MINIMIZE, SW_RESTORE, SW_SHOW, WINDOWPLACEMENT, WINDOW_EX_STYLE, + WINDOW_STYLE, WS_BORDER, WS_CAPTION, WS_CHILD, WS_CLIPCHILDREN, WS_CLIPSIBLINGS, + WS_EX_ACCEPTFILES, WS_EX_APPWINDOW, WS_EX_LAYERED, WS_EX_NOREDIRECTIONBITMAP, + WS_EX_TOPMOST, WS_EX_TRANSPARENT, WS_EX_WINDOWEDGE, WS_MAXIMIZE, WS_MAXIMIZEBOX, + WS_MINIMIZE, WS_MINIMIZEBOX, WS_OVERLAPPEDWINDOW, WS_POPUP, WS_SIZEBOX, WS_SYSMENU, + WS_VISIBLE, }, }; @@ -76,36 +77,39 @@ bitflags! { bitflags! { pub struct WindowFlags: u32 { const RESIZABLE = 1 << 0; - const DECORATIONS = 1 << 1; - const VISIBLE = 1 << 2; - const ON_TASKBAR = 1 << 3; - const ALWAYS_ON_TOP = 1 << 4; - const NO_BACK_BUFFER = 1 << 5; - const TRANSPARENT = 1 << 6; - const CHILD = 1 << 7; - const MAXIMIZED = 1 << 8; - const POPUP = 1 << 14; + const VISIBLE = 1 << 1; + const ON_TASKBAR = 1 << 2; + const ALWAYS_ON_TOP = 1 << 3; + const NO_BACK_BUFFER = 1 << 4; + const TRANSPARENT = 1 << 5; + const CHILD = 1 << 6; + const MAXIMIZED = 1 << 7; + const POPUP = 1 << 8; /// Marker flag for fullscreen. Should always match `WindowState::fullscreen`, but is /// included here to make masking easier. const MARKER_EXCLUSIVE_FULLSCREEN = 1 << 9; - const MARKER_BORDERLESS_FULLSCREEN = 1 << 13; + const MARKER_BORDERLESS_FULLSCREEN = 1 << 10; /// The `WM_SIZE` event contains some parameters that can effect the state of `WindowFlags`. /// In most cases, it's okay to let those parameters change the state. However, when we're /// running the `WindowFlags::apply_diff` function, we *don't* want those parameters to /// effect our stored state, because the purpose of `apply_diff` is to update the actual /// window's state to match our stored state. This controls whether to accept those changes. - const MARKER_RETAIN_STATE_ON_SIZE = 1 << 10; + const MARKER_RETAIN_STATE_ON_SIZE = 1 << 11; - const MARKER_IN_SIZE_MOVE = 1 << 11; + const MARKER_IN_SIZE_MOVE = 1 << 12; - const MINIMIZED = 1 << 12; + const MINIMIZED = 1 << 13; - const IGNORE_CURSOR_EVENT = 1 << 15; + const IGNORE_CURSOR_EVENT = 1 << 14; + + /// Fully decorated window (incl. caption, border and drop shadow). + const MARKER_DECORATIONS = 1 << 15; + /// Drop shadow for undecorated windows. + const MARKER_UNDECORATED_SHADOW = 1 << 16; const EXCLUSIVE_FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits; - const NO_DECORATIONS_AND_MASK = !WindowFlags::RESIZABLE.bits; } } @@ -228,22 +232,22 @@ impl WindowFlags { if self.contains(WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN) { self |= WindowFlags::EXCLUSIVE_FULLSCREEN_OR_MASK; } - if !self.contains(WindowFlags::DECORATIONS) { - self &= WindowFlags::NO_DECORATIONS_AND_MASK; - } self } pub fn to_window_styles(self) -> (WINDOW_STYLE, WINDOW_EX_STYLE) { - let (mut style, mut style_ex) = (WS_OVERLAPPED, WS_EX_LEFT); + // Required styles to properly support common window functionality like aero snap. + let mut style = WS_CAPTION + | WS_MINIMIZEBOX + | WS_BORDER + | WS_CLIPSIBLINGS + | WS_CLIPCHILDREN + | WS_SYSMENU; + let mut style_ex = WS_EX_WINDOWEDGE | WS_EX_ACCEPTFILES; if self.contains(WindowFlags::RESIZABLE) { style |= WS_SIZEBOX | WS_MAXIMIZEBOX; } - if self.contains(WindowFlags::DECORATIONS) { - style |= WS_CAPTION | WS_MINIMIZEBOX | WS_BORDER; - style_ex = WS_EX_WINDOWEDGE; - } if self.contains(WindowFlags::VISIBLE) { style |= WS_VISIBLE; } @@ -272,9 +276,6 @@ impl WindowFlags { style_ex |= WS_EX_TRANSPARENT | WS_EX_LAYERED; } - style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU; - style_ex |= WS_EX_ACCEPTFILES; - if self.intersects( WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN | WindowFlags::MARKER_BORDERLESS_FULLSCREEN, ) { @@ -379,11 +380,69 @@ impl WindowFlags { } } } + + pub fn adjust_rect(self, hwnd: HWND, mut rect: RECT) -> Result { + unsafe { + let mut style = GetWindowLongW(hwnd, GWL_STYLE) as u32; + let style_ex = GetWindowLongW(hwnd, GWL_EXSTYLE) as u32; + + // Frameless style implemented by manually overriding the non-client area in `WM_NCCALCSIZE`. + if !self.contains(WindowFlags::MARKER_DECORATIONS) { + style &= !(WS_CAPTION | WS_SIZEBOX); + } + + util::win_to_err({ + let b_menu = GetMenu(hwnd) != 0; + if let (Some(get_dpi_for_window), Some(adjust_window_rect_ex_for_dpi)) = ( + *util::GET_DPI_FOR_WINDOW, + *util::ADJUST_WINDOW_RECT_EX_FOR_DPI, + ) { + let dpi = get_dpi_for_window(hwnd); + adjust_window_rect_ex_for_dpi(&mut rect, style, b_menu.into(), style_ex, dpi) + } else { + AdjustWindowRectEx(&mut rect, style, b_menu.into(), style_ex) + } + })?; + Ok(rect) + } + } + + pub fn adjust_size(self, hwnd: HWND, size: PhysicalSize) -> PhysicalSize { + let (width, height): (u32, u32) = size.into(); + let rect = RECT { + left: 0, + right: width as i32, + top: 0, + bottom: height as i32, + }; + let rect = self.adjust_rect(hwnd, rect).unwrap_or(rect); + + let outer_x = (rect.right - rect.left).abs(); + let outer_y = (rect.top - rect.bottom).abs(); + + PhysicalSize::new(outer_x as _, outer_y as _) + } + + pub fn set_size(self, hwnd: HWND, size: PhysicalSize) { + unsafe { + let (width, height): (u32, u32) = self.adjust_size(hwnd, size).into(); + SetWindowPos( + hwnd, + 0, + 0, + 0, + width as _, + height as _, + SWP_ASYNCWINDOWPOS | SWP_NOZORDER | SWP_NOREPOSITION | SWP_NOMOVE | SWP_NOACTIVATE, + ); + InvalidateRgn(hwnd, 0, false.into()); + } + } } impl CursorFlags { fn refresh_os_cursor(self, window: HWND) -> Result<(), io::Error> { - let client_rect = util::get_client_rect(window)?; + let client_rect = util::WindowArea::Inner.get_rect(window)?; if util::is_focused(window) { let cursor_clip = match self.contains(CursorFlags::GRABBED) {