On Windows, improve support for undecorated windows (#2419)

This commit is contained in:
Markus Siglreithmaier 2022-08-15 02:36:37 +02:00 committed by GitHub
parent 2e4338bb8d
commit 76f158d310
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 274 additions and 205 deletions

View file

@ -8,6 +8,9 @@ And please only add new entries to the top of this list, right below the `# Unre
# Unreleased # 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) # 0.27.2 (2022-8-12)
- On macOS, fixed touch phase reporting when scrolling. - On macOS, fixed touch phase reporting when scrolling.

View file

@ -143,6 +143,11 @@ pub trait WindowExtWindows {
/// Whether to show or hide the window icon in the taskbar. /// Whether to show or hide the window icon in the taskbar.
fn set_skip_taskbar(&self, skip: bool); 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 { impl WindowExtWindows for Window {
@ -175,6 +180,11 @@ impl WindowExtWindows for Window {
fn set_skip_taskbar(&self, skip: bool) { fn set_skip_taskbar(&self, skip: bool) {
self.window.set_skip_taskbar(skip) 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. /// 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. /// Whether show or hide the window icon in the taskbar.
fn with_skip_taskbar(self, skip: bool) -> WindowBuilder; 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 { impl WindowBuilderExtWindows for WindowBuilder {
@ -279,6 +295,12 @@ impl WindowBuilderExtWindows for WindowBuilder {
self.platform_specific.skip_taskbar = skip; self.platform_specific.skip_taskbar = skip;
self 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. /// Additional methods on `MonitorHandle` that are specific to Windows.

View file

@ -25,9 +25,9 @@ use windows_sys::Win32::{
Devices::HumanInterfaceDevice::MOUSE_MOVE_RELATIVE, Devices::HumanInterfaceDevice::MOUSE_MOVE_RELATIVE,
Foundation::{BOOL, HANDLE, HWND, LPARAM, LRESULT, POINT, RECT, WAIT_TIMEOUT, WPARAM}, Foundation::{BOOL, HANDLE, HWND, LPARAM, LRESULT, POINT, RECT, WAIT_TIMEOUT, WPARAM},
Graphics::Gdi::{ Graphics::Gdi::{
ClientToScreen, GetMonitorInfoW, GetUpdateRect, MonitorFromRect, MonitorFromWindow, GetMonitorInfoW, GetUpdateRect, MonitorFromRect, MonitorFromWindow, RedrawWindow,
RedrawWindow, ScreenToClient, ValidateRect, MONITORINFO, MONITOR_DEFAULTTONULL, ScreenToClient, ValidateRect, MONITORINFO, MONITOR_DEFAULTTONULL, RDW_INTERNALPAINT,
RDW_INTERNALPAINT, SC_SCREENSAVE, SC_SCREENSAVE,
}, },
Media::{timeBeginPeriod, timeEndPeriod, timeGetDevCaps, TIMECAPS, TIMERR_NOERROR}, Media::{timeBeginPeriod, timeEndPeriod, timeGetDevCaps, TIMECAPS, TIMERR_NOERROR},
System::{Ole::RevokeDragDrop, Threading::GetCurrentThreadId, WindowsProgramming::INFINITE}, System::{Ole::RevokeDragDrop, Threading::GetCurrentThreadId, WindowsProgramming::INFINITE},
@ -50,20 +50,20 @@ use windows_sys::Win32::{
RIM_TYPEKEYBOARD, RIM_TYPEMOUSE, RIM_TYPEKEYBOARD, RIM_TYPEMOUSE,
}, },
WindowsAndMessaging::{ WindowsAndMessaging::{
CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, GetClientRect, CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, GetCursorPos,
GetCursorPos, GetMessageW, GetWindowLongW, LoadCursorW, MsgWaitForMultipleObjectsEx, GetMessageW, LoadCursorW, MsgWaitForMultipleObjectsEx, PeekMessageW, PostMessageW,
PeekMessageW, PostMessageW, PostThreadMessageW, RegisterClassExW, PostThreadMessageW, RegisterClassExW, RegisterWindowMessageA, SetCursor, SetWindowPos,
RegisterWindowMessageA, SetCursor, SetWindowPos, TranslateMessage, CREATESTRUCTW, TranslateMessage, CREATESTRUCTW, GIDC_ARRIVAL, GIDC_REMOVAL, GWL_STYLE, GWL_USERDATA,
GIDC_ARRIVAL, GIDC_REMOVAL, GWL_EXSTYLE, GWL_STYLE, GWL_USERDATA, HTCAPTION, HTCLIENT, HTCAPTION, HTCLIENT, MAPVK_VK_TO_VSC, MINMAXINFO, MSG, MWMO_INPUTAVAILABLE,
MAPVK_VK_TO_VSC, MINMAXINFO, MSG, MWMO_INPUTAVAILABLE, PM_NOREMOVE, PM_QS_PAINT, NCCALCSIZE_PARAMS, PM_NOREMOVE, PM_QS_PAINT, PM_REMOVE, PT_PEN, PT_TOUCH, QS_ALLEVENTS,
PM_REMOVE, PT_PEN, PT_TOUCH, QS_ALLEVENTS, RI_KEY_E0, RI_KEY_E1, RI_MOUSE_WHEEL, RI_KEY_E0, RI_KEY_E1, RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED,
SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS,
SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS, WM_CAPTURECHANGED, WM_CHAR, WM_CLOSE, WM_CREATE, WM_CAPTURECHANGED, WM_CHAR, WM_CLOSE, WM_CREATE, WM_DESTROY, WM_DPICHANGED,
WM_DESTROY, WM_DPICHANGED, WM_DROPFILES, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_DROPFILES, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION,
WM_GETMINMAXINFO, WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT, WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT,
WM_IME_STARTCOMPOSITION, WM_INPUT, WM_INPUT_DEVICE_CHANGE, WM_KEYDOWN, WM_KEYUP, WM_INPUT_DEVICE_CHANGE, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN,
WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEHWHEEL, WM_MOUSEMOVE,
WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE, WM_NCCREATE, WM_NCDESTROY, WM_MOUSEWHEEL, WM_NCACTIVATE, WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY,
WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, WM_POINTERUP, WM_POINTERUPDATE, WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, WM_POINTERUP, WM_POINTERUPDATE,
WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE,
WM_SYSCHAR, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH, WM_WINDOWPOSCHANGED, WM_SYSCHAR, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH, WM_WINDOWPOSCHANGED,
@ -633,10 +633,12 @@ pub static TASKBAR_CREATED: Lazy<u32> =
Lazy::new(|| unsafe { RegisterWindowMessageA("TaskbarCreated\0".as_ptr()) }); Lazy::new(|| unsafe { RegisterWindowMessageA("TaskbarCreated\0".as_ptr()) });
fn create_event_target_window<T: 'static>() -> HWND { fn create_event_target_window<T: 'static>() -> HWND {
use windows_sys::Win32::UI::WindowsAndMessaging::CS_HREDRAW;
use windows_sys::Win32::UI::WindowsAndMessaging::CS_VREDRAW;
unsafe { unsafe {
let class = WNDCLASSEXW { let class = WNDCLASSEXW {
cbSize: mem::size_of::<WNDCLASSEXW>() as u32, cbSize: mem::size_of::<WNDCLASSEXW>() as u32,
style: 0, style: CS_HREDRAW | CS_VREDRAW,
lpfnWndProc: Some(thread_event_target_callback::<T>), lpfnWndProc: Some(thread_event_target_callback::<T>),
cbClsExtra: 0, cbClsExtra: 0,
cbWndExtra: 0, cbWndExtra: 0,
@ -968,6 +970,32 @@ unsafe fn public_window_callback_inner<T: 'static>(
// the closure to catch_unwind directly so that the match body indendation wouldn't change and // the closure to catch_unwind directly so that the match body indendation wouldn't change and
// the git blame and history would be preserved. // the git blame and history would be preserved.
let callback = || match msg { 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 => { WM_ENTERSIZEMOVE => {
userdata userdata
.window_state .window_state
@ -1049,7 +1077,7 @@ unsafe fn public_window_callback_inner<T: 'static>(
const NOMOVE_OR_NOSIZE: u32 = SWP_NOMOVE | SWP_NOSIZE; const NOMOVE_OR_NOSIZE: u32 = SWP_NOMOVE | SWP_NOSIZE;
let new_rect = if window_pos.flags & NOMOVE_OR_NOSIZE != 0 { 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"); .expect("Unexpected GetWindowRect failure; please report this error to https://github.com/rust-windowing/winit");
match window_pos.flags & NOMOVE_OR_NOSIZE { match window_pos.flags & NOMOVE_OR_NOSIZE {
@ -1921,11 +1949,13 @@ unsafe fn public_window_callback_inner<T: 'static>(
let mmi = lparam as *mut MINMAXINFO; let mmi = lparam as *mut MINMAXINFO;
let window_state = userdata.window_state.lock(); 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 window_state.min_size.is_some() || window_state.max_size.is_some() {
if let Some(min_size) = window_state.min_size { if let Some(min_size) = window_state.min_size {
let min_size = min_size.to_physical(window_state.scale_factor); 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 { (*mmi).ptMinTrackSize = POINT {
x: width as i32, x: width as i32,
y: height as i32, y: height as i32,
@ -1933,7 +1963,8 @@ unsafe fn public_window_callback_inner<T: 'static>(
} }
if let Some(max_size) = window_state.max_size { if let Some(max_size) = window_state.max_size {
let max_size = max_size.to_physical(window_state.scale_factor); 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 { (*mmi).ptMaxTrackSize = POINT {
x: width as i32, x: width as i32,
y: height as i32, y: height as i32,
@ -1957,7 +1988,7 @@ unsafe fn public_window_callback_inner<T: 'static>(
let new_scale_factor = dpi_to_scale_factor(new_dpi_x); let new_scale_factor = dpi_to_scale_factor(new_dpi_x);
let old_scale_factor: f64; let old_scale_factor: f64;
let allow_resize = { let (allow_resize, window_flags) = {
let mut window_state = userdata.window_state.lock(); let mut window_state = userdata.window_state.lock();
old_scale_factor = window_state.scale_factor; old_scale_factor = window_state.scale_factor;
window_state.scale_factor = new_scale_factor; window_state.scale_factor = new_scale_factor;
@ -1966,12 +1997,11 @@ unsafe fn public_window_callback_inner<T: 'static>(
return 0; return 0;
} }
window_state.fullscreen.is_none() let allow_resize = window_state.fullscreen.is_none()
&& !window_state.window_flags().contains(WindowFlags::MAXIMIZED) && !window_state.window_flags().contains(WindowFlags::MAXIMIZED);
};
let style = GetWindowLongW(window, GWL_STYLE) as u32; (allow_resize, window_state.window_flags)
let style_ex = GetWindowLongW(window, GWL_EXSTYLE) as u32; };
// New size as suggested by Windows. // New size as suggested by Windows.
let suggested_rect = *(lparam as *const RECT); let suggested_rect = *(lparam as *const RECT);
@ -1985,28 +2015,18 @@ unsafe fn public_window_callback_inner<T: 'static>(
// let margin_right: i32; // let margin_right: i32;
// let margin_bottom: i32; // let margin_bottom: i32;
{ {
let adjusted_rect = let adjusted_rect = window_flags
util::adjust_window_rect_with_styles(window, style, style_ex, suggested_rect) .adjust_rect(window, suggested_rect)
.unwrap_or(suggested_rect); .unwrap_or(suggested_rect);
margin_left = suggested_rect.left - adjusted_rect.left; margin_left = suggested_rect.left - adjusted_rect.left;
margin_top = suggested_rect.top - adjusted_rect.top; margin_top = suggested_rect.top - adjusted_rect.top;
// margin_right = adjusted_rect.right - suggested_rect.right; // margin_right = adjusted_rect.right - suggested_rect.right;
// margin_bottom = adjusted_rect.bottom - suggested_rect.bottom; // margin_bottom = adjusted_rect.bottom - suggested_rect.bottom;
} }
let old_physical_inner_rect = { let old_physical_inner_rect = util::WindowArea::Inner
let mut old_physical_inner_rect = mem::zeroed(); .get_rect(window)
GetClientRect(window, &mut old_physical_inner_rect); .expect("failed to query (old) inner window area");
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_size = PhysicalSize::new( let old_physical_inner_size = PhysicalSize::new(
(old_physical_inner_rect.right - old_physical_inner_rect.left) as u32, (old_physical_inner_rect.right - old_physical_inner_rect.left) as u32,
(old_physical_inner_rect.bottom - old_physical_inner_rect.top) as u32, (old_physical_inner_rect.bottom - old_physical_inner_rect.top) as u32,
@ -2060,13 +2080,9 @@ unsafe fn public_window_callback_inner<T: 'static>(
bottom: suggested_ul.1 + new_physical_inner_size.height as i32, bottom: suggested_ul.1 + new_physical_inner_size.height as i32,
}; };
conservative_rect = util::adjust_window_rect_with_styles( conservative_rect = window_flags
window, .adjust_rect(window, conservative_rect)
style, .unwrap_or(conservative_rect);
style_ex,
conservative_rect,
)
.unwrap_or(conservative_rect);
// If we're dragging the window, offset the window so that the cursor's // If we're dragging the window, offset the window so that the cursor's
// relative horizontal position in the title bar is preserved. // relative horizontal position in the title bar is preserved.

View file

@ -16,7 +16,10 @@ use crate::{
dpi::PhysicalSize, dpi::PhysicalSize,
event::{Event, StartCause, WindowEvent}, event::{Event, StartCause, WindowEvent},
event_loop::ControlFlow, event_loop::ControlFlow,
platform_impl::platform::util, platform_impl::platform::{
event_loop::{WindowData, GWL_USERDATA},
get_window_long,
},
window::WindowId, window::WindowId,
}; };
@ -434,11 +437,13 @@ impl<T> BufferedEvent<T> {
new_inner_size: &mut new_inner_size, new_inner_size: &mut new_inner_size,
}, },
}); });
util::set_inner_size_physical(
(window_id.0).0, let window_flags = unsafe {
new_inner_size.width as _, let userdata =
new_inner_size.height as _, get_window_long(window_id.0.into(), GWL_USERDATA) as *mut WindowData<T>;
); (*userdata).window_state.lock().window_flags
};
window_flags.set_size((window_id.0).0, new_inner_size);
} }
} }
} }

View file

@ -36,6 +36,7 @@ pub struct PlatformSpecificWindowBuilderAttributes {
pub drag_and_drop: bool, pub drag_and_drop: bool,
pub preferred_theme: Option<Theme>, pub preferred_theme: Option<Theme>,
pub skip_taskbar: bool, pub skip_taskbar: bool,
pub decoration_shadow: bool,
} }
impl Default for PlatformSpecificWindowBuilderAttributes { impl Default for PlatformSpecificWindowBuilderAttributes {
@ -48,6 +49,7 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
drag_and_drop: true, drag_and_drop: true,
preferred_theme: None, preferred_theme: None,
skip_taskbar: false, skip_taskbar: false,
decoration_shadow: false,
} }
} }
} }
@ -106,6 +108,12 @@ impl From<WindowId> for u64 {
} }
} }
impl From<WindowId> for HWND {
fn from(window_id: WindowId) -> Self {
window_id.0
}
}
impl From<u64> for WindowId { impl From<u64> for WindowId {
fn from(raw_id: u64) -> Self { fn from(raw_id: u64) -> Self {
Self(raw_id as HWND) Self(raw_id as HWND)

View file

@ -14,7 +14,7 @@ use windows_sys::{
core::{HRESULT, PCWSTR}, core::{HRESULT, PCWSTR},
Win32::{ Win32::{
Foundation::{BOOL, HINSTANCE, HWND, RECT}, Foundation::{BOOL, HINSTANCE, HWND, RECT},
Graphics::Gdi::{ClientToScreen, InvalidateRgn, HMONITOR}, Graphics::Gdi::{ClientToScreen, HMONITOR},
System::{ System::{
LibraryLoader::{GetProcAddress, LoadLibraryA}, LibraryLoader::{GetProcAddress, LoadLibraryA},
SystemServices::IMAGE_DOS_HEADER, SystemServices::IMAGE_DOS_HEADER,
@ -23,19 +23,16 @@ use windows_sys::{
HiDpi::{DPI_AWARENESS_CONTEXT, MONITOR_DPI_TYPE, PROCESS_DPI_AWARENESS}, HiDpi::{DPI_AWARENESS_CONTEXT, MONITOR_DPI_TYPE, PROCESS_DPI_AWARENESS},
Input::KeyboardAndMouse::GetActiveWindow, Input::KeyboardAndMouse::GetActiveWindow,
WindowsAndMessaging::{ WindowsAndMessaging::{
AdjustWindowRectEx, ClipCursor, GetClientRect, GetClipCursor, GetMenu, ClipCursor, GetClientRect, GetClipCursor, GetSystemMetrics, GetWindowRect,
GetSystemMetrics, GetWindowLongW, GetWindowRect, SetWindowPos, ShowCursor, ShowCursor, IDC_APPSTARTING, IDC_ARROW, IDC_CROSS, IDC_HAND, IDC_HELP, IDC_IBEAM,
GWL_EXSTYLE, GWL_STYLE, IDC_APPSTARTING, IDC_ARROW, IDC_CROSS, IDC_HAND, IDC_HELP, IDC_NO, IDC_SIZEALL, IDC_SIZENESW, IDC_SIZENS, IDC_SIZENWSE, IDC_SIZEWE, IDC_WAIT,
IDC_IBEAM, IDC_NO, IDC_SIZEALL, IDC_SIZENESW, IDC_SIZENS, IDC_SIZENWSE, IDC_SIZEWE, SM_CXVIRTUALSCREEN, SM_CYVIRTUALSCREEN, SM_XVIRTUALSCREEN, SM_YVIRTUALSCREEN,
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,
}, },
}, },
}, },
}; };
use crate::{dpi::PhysicalSize, window::CursorIcon}; use crate::window::CursorIcon;
pub fn encode_wide(string: impl AsRef<OsStr>) -> Vec<u16> { pub fn encode_wide(string: impl AsRef<OsStr>) -> Vec<u16> {
string.as_ref().encode_wide().chain(once(0)).collect() string.as_ref().encode_wide().chain(once(0)).collect()
@ -56,114 +53,43 @@ where
bitset & flag == flag bitset & flag == flag
} }
pub unsafe fn status_map<T, F: FnMut(&mut T) -> BOOL>(mut fun: F) -> Option<T> { pub(crate) fn win_to_err(result: BOOL) -> Result<(), io::Error> {
let mut data: T = mem::zeroed(); if result != false.into() {
if fun(&mut data) != false.into() {
Some(data)
} else {
None
}
}
fn win_to_err<F: FnOnce() -> BOOL>(f: F) -> Result<(), io::Error> {
if f() != false.into() {
Ok(()) Ok(())
} else { } else {
Err(io::Error::last_os_error()) Err(io::Error::last_os_error())
} }
} }
pub fn get_window_rect(hwnd: HWND) -> Option<RECT> { pub enum WindowArea {
unsafe { status_map(|rect| GetWindowRect(hwnd, rect)) } Outer,
Inner,
} }
pub fn get_client_rect(hwnd: HWND) -> Result<RECT, io::Error> { impl WindowArea {
unsafe { pub fn get_rect(self, hwnd: HWND) -> Result<RECT, io::Error> {
let mut rect = mem::zeroed(); let mut rect = unsafe { mem::zeroed() };
let mut top_left = mem::zeroed();
win_to_err(|| ClientToScreen(hwnd, &mut top_left))?; match self {
win_to_err(|| GetClientRect(hwnd, &mut rect))?; WindowArea::Outer => {
rect.left += top_left.x; win_to_err(unsafe { GetWindowRect(hwnd, &mut rect) })?;
rect.top += top_left.y; }
rect.right += top_left.x; WindowArea::Inner => unsafe {
rect.bottom += top_left.y; 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) Ok(rect)
} }
} }
pub fn adjust_size(hwnd: HWND, size: PhysicalSize<u32>) -> PhysicalSize<u32> {
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<RECT> {
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<RECT> {
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) { pub fn set_cursor_hidden(hidden: bool) {
static HIDDEN: AtomicBool = AtomicBool::new(false); static HIDDEN: AtomicBool = AtomicBool::new(false);
let changed = HIDDEN.swap(hidden, Ordering::SeqCst) ^ hidden; 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<RECT, io::Error> { pub fn get_cursor_clip() -> Result<RECT, io::Error> {
unsafe { unsafe {
let mut rect: RECT = mem::zeroed(); 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<RECT>) -> Result<(), io::Error> {
.as_ref() .as_ref()
.map(|r| r as *const RECT) .map(|r| r as *const RECT)
.unwrap_or(ptr::null()); .unwrap_or(ptr::null());
win_to_err(|| ClipCursor(rect_ptr)) win_to_err(ClipCursor(rect_ptr))
} }
} }

View file

@ -132,7 +132,7 @@ impl Window {
#[inline] #[inline]
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> { pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, 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))) .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") .expect("Unexpected GetWindowRect failure; please report this error to https://github.com/rust-windowing/winit")
} }
@ -187,7 +187,8 @@ impl Window {
#[inline] #[inline]
pub fn outer_size(&self) -> PhysicalSize<u32> { pub fn outer_size(&self) -> PhysicalSize<u32> {
util::get_window_rect(self.hwnd()) util::WindowArea::Outer
.get_rect(self.hwnd())
.map(|rect| { .map(|rect| {
PhysicalSize::new( PhysicalSize::new(
(rect.right - rect.left) as u32, (rect.right - rect.left) as u32,
@ -200,7 +201,7 @@ impl Window {
#[inline] #[inline]
pub fn set_inner_size(&self, size: Size) { pub fn set_inner_size(&self, size: Size) {
let scale_factor = self.scale_factor(); let scale_factor = self.scale_factor();
let (width, height) = size.to_physical::<u32>(scale_factor).into(); let physical_size = size.to_physical::<u32>(scale_factor);
let window_state = Arc::clone(&self.window_state); let window_state = Arc::clone(&self.window_state);
let window = self.window.clone(); 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] #[inline]
@ -577,7 +579,7 @@ impl Window {
self.thread_executor.execute_in_thread(move || { self.thread_executor.execute_in_thread(move || {
let _ = &window; let _ = &window;
WindowState::set_window_flags(window_state.lock(), window.0, |f| { 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] #[inline]
pub fn is_decorated(&self) -> bool { pub fn is_decorated(&self) -> bool {
let window_state = self.window_state.lock(); let window_state = self.window_state.lock();
window_state.window_flags.contains(WindowFlags::DECORATIONS) window_state
.window_flags
.contains(WindowFlags::MARKER_DECORATIONS)
} }
#[inline] #[inline]
@ -691,6 +695,19 @@ impl Window {
unsafe { set_skip_taskbar(self.hwnd(), skip) }; 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] #[inline]
pub fn focus_window(&self) { pub fn focus_window(&self) {
let window = self.window.clone(); 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 { if let Some(position) = attributes.position {
win.set_outer_position(position); win.set_outer_position(position);
} }
@ -916,7 +941,11 @@ where
let class_name = register_window_class::<T>(&attributes.window_icon, &pl_attribs.taskbar_icon); let class_name = register_window_class::<T>(&attributes.window_icon, &pl_attribs.taskbar_icon);
let mut window_flags = WindowFlags::empty(); 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::ALWAYS_ON_TOP, attributes.always_on_top);
window_flags.set( window_flags.set(
WindowFlags::NO_BACK_BUFFER, WindowFlags::NO_BACK_BUFFER,
@ -997,6 +1026,7 @@ unsafe fn register_window_class<T: 'static>(
.map(|icon| icon.inner.as_raw_handle()) .map(|icon| icon.inner.as_raw_handle())
.unwrap_or(0); .unwrap_or(0);
use windows_sys::Win32::UI::WindowsAndMessaging::COLOR_WINDOWFRAME;
let class = WNDCLASSEXW { let class = WNDCLASSEXW {
cbSize: mem::size_of::<WNDCLASSEXW>() as u32, cbSize: mem::size_of::<WNDCLASSEXW>() as u32,
style: CS_HREDRAW | CS_VREDRAW, style: CS_HREDRAW | CS_VREDRAW,
@ -1006,7 +1036,7 @@ unsafe fn register_window_class<T: 'static>(
hInstance: util::get_instance_handle(), hInstance: util::get_instance_handle(),
hIcon: h_icon, hIcon: h_icon,
hCursor: 0, // must be null in order for cursor state to work properly hCursor: 0, // must be null in order for cursor state to work properly
hbrBackground: 0, hbrBackground: COLOR_WINDOWFRAME as _,
lpszMenuName: ptr::null(), lpszMenuName: ptr::null(),
lpszClassName: class_name.as_ptr(), lpszClassName: class_name.as_ptr(),
hIconSm: h_icon_small, hIconSm: h_icon_small,

View file

@ -1,5 +1,5 @@
use crate::{ use crate::{
dpi::{PhysicalPosition, Size}, dpi::{PhysicalPosition, PhysicalSize, Size},
event::ModifiersState, event::ModifiersState,
icon::Icon, icon::Icon,
platform_impl::platform::{event_loop, util}, platform_impl::platform::{event_loop, util},
@ -11,14 +11,15 @@ use windows_sys::Win32::{
Foundation::{HWND, RECT}, Foundation::{HWND, RECT},
Graphics::Gdi::InvalidateRgn, Graphics::Gdi::InvalidateRgn,
UI::WindowsAndMessaging::{ UI::WindowsAndMessaging::{
SendMessageW, SetWindowLongW, SetWindowPos, ShowWindow, GWL_EXSTYLE, GWL_STYLE, AdjustWindowRectEx, GetMenu, GetWindowLongW, SendMessageW, SetWindowLongW, SetWindowPos,
HWND_NOTOPMOST, HWND_TOPMOST, SWP_ASYNCWINDOWPOS, SWP_FRAMECHANGED, SWP_NOACTIVATE, ShowWindow, GWL_EXSTYLE, GWL_STYLE, HWND_NOTOPMOST, HWND_TOPMOST, SWP_ASYNCWINDOWPOS,
SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, SW_HIDE, SW_MAXIMIZE, SW_MINIMIZE, SW_RESTORE, SWP_FRAMECHANGED, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOREPOSITION, SWP_NOSIZE, SWP_NOZORDER,
SW_SHOW, WINDOWPLACEMENT, WINDOW_EX_STYLE, WINDOW_STYLE, WS_BORDER, WS_CAPTION, WS_CHILD, SW_HIDE, SW_MAXIMIZE, SW_MINIMIZE, SW_RESTORE, SW_SHOW, WINDOWPLACEMENT, WINDOW_EX_STYLE,
WS_CLIPCHILDREN, WS_CLIPSIBLINGS, WS_EX_ACCEPTFILES, WS_EX_APPWINDOW, WS_EX_LAYERED, WINDOW_STYLE, WS_BORDER, WS_CAPTION, WS_CHILD, WS_CLIPCHILDREN, WS_CLIPSIBLINGS,
WS_EX_LEFT, WS_EX_NOREDIRECTIONBITMAP, WS_EX_TOPMOST, WS_EX_TRANSPARENT, WS_EX_WINDOWEDGE, WS_EX_ACCEPTFILES, WS_EX_APPWINDOW, WS_EX_LAYERED, WS_EX_NOREDIRECTIONBITMAP,
WS_MAXIMIZE, WS_MAXIMIZEBOX, WS_MINIMIZE, WS_MINIMIZEBOX, WS_OVERLAPPED, WS_EX_TOPMOST, WS_EX_TRANSPARENT, WS_EX_WINDOWEDGE, WS_MAXIMIZE, WS_MAXIMIZEBOX,
WS_OVERLAPPEDWINDOW, WS_POPUP, WS_SIZEBOX, WS_SYSMENU, WS_VISIBLE, WS_MINIMIZE, WS_MINIMIZEBOX, WS_OVERLAPPEDWINDOW, WS_POPUP, WS_SIZEBOX, WS_SYSMENU,
WS_VISIBLE,
}, },
}; };
@ -76,36 +77,39 @@ bitflags! {
bitflags! { bitflags! {
pub struct WindowFlags: u32 { pub struct WindowFlags: u32 {
const RESIZABLE = 1 << 0; const RESIZABLE = 1 << 0;
const DECORATIONS = 1 << 1; const VISIBLE = 1 << 1;
const VISIBLE = 1 << 2; const ON_TASKBAR = 1 << 2;
const ON_TASKBAR = 1 << 3; const ALWAYS_ON_TOP = 1 << 3;
const ALWAYS_ON_TOP = 1 << 4; const NO_BACK_BUFFER = 1 << 4;
const NO_BACK_BUFFER = 1 << 5; const TRANSPARENT = 1 << 5;
const TRANSPARENT = 1 << 6; const CHILD = 1 << 6;
const CHILD = 1 << 7; const MAXIMIZED = 1 << 7;
const MAXIMIZED = 1 << 8; const POPUP = 1 << 8;
const POPUP = 1 << 14;
/// Marker flag for fullscreen. Should always match `WindowState::fullscreen`, but is /// Marker flag for fullscreen. Should always match `WindowState::fullscreen`, but is
/// included here to make masking easier. /// included here to make masking easier.
const MARKER_EXCLUSIVE_FULLSCREEN = 1 << 9; 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`. /// 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 /// 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 /// 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 /// 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. /// 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 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) { if self.contains(WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN) {
self |= WindowFlags::EXCLUSIVE_FULLSCREEN_OR_MASK; self |= WindowFlags::EXCLUSIVE_FULLSCREEN_OR_MASK;
} }
if !self.contains(WindowFlags::DECORATIONS) {
self &= WindowFlags::NO_DECORATIONS_AND_MASK;
}
self self
} }
pub fn to_window_styles(self) -> (WINDOW_STYLE, WINDOW_EX_STYLE) { 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) { if self.contains(WindowFlags::RESIZABLE) {
style |= WS_SIZEBOX | WS_MAXIMIZEBOX; 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) { if self.contains(WindowFlags::VISIBLE) {
style |= WS_VISIBLE; style |= WS_VISIBLE;
} }
@ -272,9 +276,6 @@ impl WindowFlags {
style_ex |= WS_EX_TRANSPARENT | WS_EX_LAYERED; style_ex |= WS_EX_TRANSPARENT | WS_EX_LAYERED;
} }
style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU;
style_ex |= WS_EX_ACCEPTFILES;
if self.intersects( if self.intersects(
WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN | WindowFlags::MARKER_BORDERLESS_FULLSCREEN, 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<RECT, io::Error> {
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<u32>) -> PhysicalSize<u32> {
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<u32>) {
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 { impl CursorFlags {
fn refresh_os_cursor(self, window: HWND) -> Result<(), io::Error> { 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) { if util::is_focused(window) {
let cursor_clip = match self.contains(CursorFlags::GRABBED) { let cursor_clip = match self.contains(CursorFlags::GRABBED) {