From 7be1d16263be372f33258eb48efbab933f80afe4 Mon Sep 17 00:00:00 2001 From: Osspial Date: Mon, 4 Feb 2019 11:52:00 -0500 Subject: [PATCH] Refactor win32 window state code (#730) * Overhaul win32 window state * Fix warnings * Add CHANGELOG entry * Rephrase CHANGELOG entries * Fix 1.28.0 build * Remove WS_POPUP styling * Slight style correction * Make set_maximized work * Fix rect restore not working after winit set_maximized call * Add a few comments --- CHANGELOG.md | 4 + Cargo.toml | 1 + src/lib.rs | 3 + src/platform/windows/events_loop.rs | 245 +++++------ src/platform/windows/mod.rs | 1 + src/platform/windows/monitor.rs | 2 +- src/platform/windows/util.rs | 92 ++++- src/platform/windows/window.rs | 592 +++++++-------------------- src/platform/windows/window_state.rs | 329 +++++++++++++++ 9 files changed, 661 insertions(+), 608 deletions(-) create mode 100644 src/platform/windows/window_state.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dc7aa9c..adfb5061 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ - On Wayland, add `set_wayland_theme()` to control client decoration color theme - Added serde serialization to `os::unix::XWindowType`. - **Breaking:** `image` crate upgraded to 0.21. This is exposed as part of the `icon_loading` API. +- On Windows, fix malformed function pointer typecast that could invoke undefined behavior. +- Refactored Windows state/flag-setting code. +- On Windows, hiding the cursor no longer hides the cursor for all Winit windows - just the one `hide_cursor` was called on. +- On Windows, cursor grabs used to get perpetually canceled when the grabbing window lost focus. Now, cursor grabs automatically get re-initialized when the window regains focus and the mouse moves over the client area. # Version 0.18.1 (2018-12-30) diff --git a/Cargo.toml b/Cargo.toml index a5a882eb..2e8ddeed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ core-graphics = "0.17.3" [target.'cfg(target_os = "windows")'.dependencies] backtrace = "0.3" +bitflags = "1" [target.'cfg(target_os = "windows")'.dependencies.winapi] version = "0.3.6" diff --git a/src/lib.rs b/src/lib.rs index ece7d35f..d4b61578 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,6 +100,9 @@ extern crate serde; extern crate winapi; #[cfg(target_os = "windows")] extern crate backtrace; +#[macro_use] +#[cfg(target_os = "windows")] +extern crate bitflags; #[cfg(any(target_os = "macos", target_os = "ios"))] #[macro_use] extern crate objc; diff --git a/src/platform/windows/events_loop.rs b/src/platform/windows/events_loop.rs index de4a4326..a8882339 100644 --- a/src/platform/windows/events_loop.rs +++ b/src/platform/windows/events_loop.rs @@ -51,7 +51,7 @@ use { WindowId as SuperWindowId, }; use events::{DeviceEvent, Touch, TouchPhase}; -use platform::platform::{event, Cursor, WindowId, DEVICE_ID, wrap_device_id, util}; +use platform::platform::{event, WindowId, DEVICE_ID, wrap_device_id, util}; use platform::platform::dpi::{ become_dpi_aware, dpi_to_scale_factor, @@ -60,64 +60,9 @@ use platform::platform::dpi::{ }; use platform::platform::drop_handler::FileDropHandler; use platform::platform::event::{handle_extended_keys, process_key_params, vkey_to_winit_vkey}; -use platform::platform::icon::WinIcon; use platform::platform::raw_input::{get_raw_input_data, get_raw_mouse_button_state}; use platform::platform::window::adjust_size; - -/// Contains saved window info for switching between fullscreen -#[derive(Clone)] -pub struct SavedWindowInfo { - /// Window style - pub style: LONG, - /// Window ex-style - pub ex_style: LONG, - /// Window position and size - pub client_rect: RECT, - // Since a window can be fullscreened to a different monitor, a DPI change can be triggered. This could result in - // the window being automitcally resized to smaller/larger than it was supposed to be restored to, so we thus must - // check if the post-fullscreen DPI matches the pre-fullscreen DPI. - pub is_fullscreen: bool, - pub dpi_factor: Option, -} - -/// Contains information about states and the window that the callback is going to use. -#[derive(Clone)] -pub struct WindowState { - /// Cursor to set at the next `WM_SETCURSOR` event received. - pub cursor: Cursor, - pub cursor_grabbed: bool, - pub cursor_hidden: bool, - /// Used by `WM_GETMINMAXINFO`. - pub max_size: Option, - pub min_size: Option, - /// Will contain `true` if the mouse is hovering the window. - pub mouse_in_window: bool, - /// Saved window info for fullscreen restored - pub saved_window_info: Option, - // This is different from the value in `SavedWindowInfo`! That one represents the DPI saved upon entering - // fullscreen. This will always be the most recent DPI for the window. - pub dpi_factor: f64, - pub fullscreen: Option<::MonitorId>, - pub window_icon: Option, - pub taskbar_icon: Option, - pub decorations: bool, - pub always_on_top: bool, - pub maximized: bool, - pub resizable: bool, -} - -impl WindowState { - pub fn update_min_max(&mut self, old_dpi_factor: f64, new_dpi_factor: f64) { - let scale_factor = new_dpi_factor / old_dpi_factor; - let dpi_adjuster = |mut physical_size: PhysicalSize| -> PhysicalSize { - physical_size.width *= scale_factor; - physical_size.height *= scale_factor; - physical_size - }; - self.max_size = self.max_size.map(&dpi_adjuster); - self.min_size = self.min_size.map(&dpi_adjuster); - } -} +use platform::platform::window_state::{CursorFlags, WindowFlags, WindowState}; /// Dummy object that allows inserting a window's state. // We store a pointer in order to !impl Send and Sync. @@ -278,6 +223,7 @@ impl EventsLoop { pub fn create_proxy(&self) -> EventsLoopProxy { EventsLoopProxy { + thread_id: self.thread_id, thread_msg_target: self.thread_msg_target, sender: self.sender.clone(), } @@ -308,6 +254,7 @@ impl Drop for EventsLoop { #[derive(Clone)] pub struct EventsLoopProxy { + thread_id: DWORD, thread_msg_target: HWND, sender: mpsc::Sender, } @@ -333,26 +280,32 @@ impl EventsLoopProxy { /// `WindowState` then you should call this within the lock of `WindowState`. Otherwise the /// events may be sent to the other thread in different order to the one in which you set /// `WindowState`, leaving them out of sync. - pub fn execute_in_thread(&self, function: F) + pub fn execute_in_thread(&self, mut function: F) where F: FnMut(Inserter) + Send + 'static, { - // We are using double-boxing here because it make casting back much easier - let double_box = Box::new(Box::new(function) as Box); - let raw = Box::into_raw(double_box); + if unsafe{ processthreadsapi::GetCurrentThreadId() } == self.thread_id { + function(Inserter(ptr::null_mut())); + } else { + // We are using double-boxing here because it make casting back much easier + let double_box: ThreadExecFn = Box::new(Box::new(function) as Box); + let raw = Box::into_raw(double_box); - let res = unsafe { - winuser::PostMessageW( - self.thread_msg_target, - *EXEC_MSG_ID, - raw as *mut () as usize as WPARAM, - 0, - ) - }; - assert!(res != 0, "PostMessage failed; is the messages queue full?"); + let res = unsafe { + winuser::PostMessageW( + self.thread_msg_target, + *EXEC_MSG_ID, + raw as *mut () as usize as WPARAM, + 0, + ) + }; + assert!(res != 0, "PostMessage failed; is the messages queue full?"); + } } } +type ThreadExecFn = Box>; + lazy_static! { // Message sent when we want to execute a closure in the thread. // WPARAM contains a Box> that must be retrieved with `Box::from_raw`, @@ -376,6 +329,11 @@ lazy_static! { winuser::RegisterWindowMessageA("Winit::InitialDpiMsg\0".as_ptr() as LPCSTR) } }; + // WPARAM is a bool specifying the `WindowFlags::MARKER_RETAIN_STATE_ON_SIZE` flag. See the + // documentation in the `window_state` module for more information. + pub static ref SET_RETAIN_STATE_ON_SIZE_MSG_ID: u32 = unsafe { + winuser::RegisterWindowMessageA("Winit::SetRetainMaximized\0".as_ptr() as LPCSTR) + }; static ref THREAD_EVENT_TARGET_WINDOW_CLASS: Vec = unsafe { use std::ffi::OsStr; use std::os::windows::ffi::OsStrExt; @@ -622,18 +580,28 @@ unsafe fn callback_inner( let w = LOWORD(lparam as DWORD) as u32; let h = HIWORD(lparam as DWORD) as u32; + let dpi_factor = get_hwnd_scale_factor(window); + let logical_size = LogicalSize::from_physical((w, h), dpi_factor); + let event = Event::WindowEvent { + window_id: SuperWindowId(WindowId(window)), + event: Resized(logical_size), + }; + // Wait for the parent thread to process the resize event before returning from the // callback. CONTEXT_STASH.with(|context_stash| { let mut context_stash = context_stash.borrow_mut(); let cstash = context_stash.as_mut().unwrap(); - let dpi_factor = get_hwnd_scale_factor(window); - let logical_size = LogicalSize::from_physical((w, h), dpi_factor); - let event = Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: Resized(logical_size), - }; + if let Some(w) = cstash.windows.get_mut(&window) { + let mut w = w.lock().unwrap(); + + // See WindowFlags::MARKER_RETAIN_STATE_ON_SIZE docs for info on why this `if` check exists. + if !w.window_flags().contains(WindowFlags::MARKER_RETAIN_STATE_ON_SIZE) { + let maximized = wparam == winuser::SIZE_MAXIMIZED; + w.set_window_flags_in_place(|f| f.set(WindowFlags::MAXIMIZED, maximized)); + } + } cstash.sender.send(EventsLoopEvent::WinitEvent(event)).ok(); }); @@ -661,22 +629,26 @@ unsafe fn callback_inner( winuser::WM_MOUSEMOVE => { use events::WindowEvent::{CursorEntered, CursorMoved}; - let mouse_outside_window = CONTEXT_STASH.with(|context_stash| { + let x = windowsx::GET_X_LPARAM(lparam); + let y = windowsx::GET_Y_LPARAM(lparam); + + let mouse_was_outside_window = CONTEXT_STASH.with(|context_stash| { let mut context_stash = context_stash.borrow_mut(); if let Some(context_stash) = context_stash.as_mut() { if let Some(w) = context_stash.windows.get_mut(&window) { let mut w = w.lock().unwrap(); - if !w.mouse_in_window { - w.mouse_in_window = true; - return true; - } + + let was_outside_window = !w.mouse.cursor_flags().contains(CursorFlags::IN_WINDOW); + w.mouse.set_cursor_flags(window, |f| f.set(CursorFlags::IN_WINDOW, true)).ok(); + return was_outside_window; } } false }); - if mouse_outside_window { + + if mouse_was_outside_window { send_event(Event::WindowEvent { window_id: SuperWindowId(WindowId(window)), event: CursorEntered { device_id: DEVICE_ID }, @@ -691,10 +663,8 @@ unsafe fn callback_inner( }); } - let x = windowsx::GET_X_LPARAM(lparam) as f64; - let y = windowsx::GET_Y_LPARAM(lparam) as f64; let dpi_factor = get_hwnd_scale_factor(window); - let position = LogicalPosition::from_physical((x, y), dpi_factor); + let position = LogicalPosition::from_physical((x as f64, y as f64), dpi_factor); send_event(Event::WindowEvent { window_id: SuperWindowId(WindowId(window)), @@ -706,27 +676,21 @@ unsafe fn callback_inner( winuser::WM_MOUSELEAVE => { use events::WindowEvent::CursorLeft; - let mouse_in_window = CONTEXT_STASH.with(|context_stash| { + + CONTEXT_STASH.with(|context_stash| { let mut context_stash = context_stash.borrow_mut(); if let Some(context_stash) = context_stash.as_mut() { if let Some(w) = context_stash.windows.get_mut(&window) { let mut w = w.lock().unwrap(); - if w.mouse_in_window { - w.mouse_in_window = false; - return true; - } + w.mouse.set_cursor_flags(window, |f| f.set(CursorFlags::IN_WINDOW, false)).ok(); } } - - false }); - if mouse_in_window { - send_event(Event::WindowEvent { - window_id: SuperWindowId(WindowId(window)), - event: CursorLeft { device_id: DEVICE_ID } - }); - } + send_event(Event::WindowEvent { + window_id: SuperWindowId(WindowId(window)), + event: CursorLeft { device_id: DEVICE_ID } + }); 0 }, @@ -1101,31 +1065,31 @@ unsafe fn callback_inner( }, winuser::WM_SETCURSOR => { - let call_def_window_proc = CONTEXT_STASH.with(|context_stash| { + let set_cursor_to = CONTEXT_STASH.with(|context_stash| { context_stash .borrow() .as_ref() .and_then(|cstash| cstash.windows.get(&window)) - .map(|window_state_mutex| { + .and_then(|window_state_mutex| { let window_state = window_state_mutex.lock().unwrap(); - if window_state.mouse_in_window { - let cursor = winuser::LoadCursorW( - ptr::null_mut(), - window_state.cursor.0, - ); - winuser::SetCursor(cursor); - false + if window_state.mouse.cursor_flags().contains(CursorFlags::IN_WINDOW) { + Some(window_state.mouse.cursor) } else { - true + None } }) - .unwrap_or(true) }); - if call_def_window_proc { - winuser::DefWindowProcW(window, msg, wparam, lparam) - } else { - 0 + match set_cursor_to { + Some(cursor) => { + let cursor = winuser::LoadCursorW( + ptr::null_mut(), + cursor.to_windows_cursor(), + ); + winuser::SetCursor(cursor); + 0 + }, + None => winuser::DefWindowProcW(window, msg, wparam, lparam) } }, @@ -1148,10 +1112,12 @@ unsafe fn callback_inner( let style = winuser::GetWindowLongA(window, winuser::GWL_STYLE) as DWORD; let ex_style = winuser::GetWindowLongA(window, winuser::GWL_EXSTYLE) as DWORD; if let Some(min_size) = window_state.min_size { + let min_size = min_size.to_physical(window_state.dpi_factor); let (width, height) = adjust_size(min_size, style, ex_style); (*mmi).ptMinTrackSize = POINT { x: width as i32, y: height as i32 }; } if let Some(max_size) = window_state.max_size { + let max_size = max_size.to_physical(window_state.dpi_factor); let (width, height) = adjust_size(max_size, style, ex_style); (*mmi).ptMaxTrackSize = POINT { x: width as i32, y: height as i32 }; } @@ -1175,38 +1141,21 @@ unsafe fn callback_inner( let new_dpi_x = u32::from(LOWORD(wparam as DWORD)); let new_dpi_factor = dpi_to_scale_factor(new_dpi_x); - let suppress_resize = CONTEXT_STASH.with(|context_stash| { - context_stash - .borrow() - .as_ref() - .and_then(|cstash| cstash.windows.get(&window)) - .map(|window_state_mutex| { - let mut window_state = window_state_mutex.lock().unwrap(); - let suppress_resize = window_state.saved_window_info - .as_mut() - .map(|saved_window_info| { - let dpi_changed = if !saved_window_info.is_fullscreen { - saved_window_info.dpi_factor.take() != Some(new_dpi_factor) - } else { - false - }; - !dpi_changed || saved_window_info.is_fullscreen - }) - .unwrap_or(false); - // Now we adjust the min/max dimensions for the new DPI. - if !suppress_resize { - let old_dpi_factor = window_state.dpi_factor; - window_state.update_min_max(old_dpi_factor, new_dpi_factor); - } - window_state.dpi_factor = new_dpi_factor; - suppress_resize - }) - .unwrap_or(false) + let allow_resize = CONTEXT_STASH.with(|context_stash| { + if let Some(wstash) = context_stash.borrow().as_ref().and_then(|cstash| cstash.windows.get(&window)) { + let mut window_state = wstash.lock().unwrap(); + let old_dpi_factor = window_state.dpi_factor; + window_state.dpi_factor = new_dpi_factor; + + new_dpi_factor != old_dpi_factor && window_state.fullscreen.is_none() + } else { + true + } }); // This prevents us from re-applying DPI adjustment to the restored size after exiting // fullscreen (the restored size is already DPI adjusted). - if !suppress_resize { + if allow_resize { // Resize window to the size suggested by Windows. let rect = &*(lparam as *const RECT); winuser::SetWindowPos( @@ -1272,6 +1221,16 @@ unsafe fn callback_inner( | winuser::SWP_NOACTIVATE, ); 0 + } else if msg == *SET_RETAIN_STATE_ON_SIZE_MSG_ID { + CONTEXT_STASH.with(|context_stash| { + if let Some(cstash) = context_stash.borrow().as_ref() { + if let Some(wstash) = cstash.windows.get(&window) { + let mut window_state = wstash.lock().unwrap(); + window_state.set_window_flags_in_place(|f| f.set(WindowFlags::MARKER_RETAIN_STATE_ON_SIZE, wparam != 0)); + } + } + }); + 0 } else { winuser::DefWindowProcW(window, msg, wparam, lparam) } @@ -1289,8 +1248,8 @@ pub unsafe extern "system" fn thread_event_target_callback( run_catch_panic(-1, || { match msg { _ if msg == *EXEC_MSG_ID => { - let mut function: Box> = Box::from_raw(wparam as usize as *mut _); - function(); + let mut function: ThreadExecFn = Box::from_raw(wparam as usize as *mut _); + function(Inserter(ptr::null_mut())); 0 }, _ => winuser::DefWindowProcW(window, msg, wparam, lparam) diff --git a/src/platform/windows/mod.rs b/src/platform/windows/mod.rs index 16f9e148..5ea76b7b 100644 --- a/src/platform/windows/mod.rs +++ b/src/platform/windows/mod.rs @@ -71,3 +71,4 @@ mod monitor; mod raw_input; mod util; mod window; +mod window_state; diff --git a/src/platform/windows/monitor.rs b/src/platform/windows/monitor.rs index 30394de0..d7080ca2 100644 --- a/src/platform/windows/monitor.rs +++ b/src/platform/windows/monitor.rs @@ -99,7 +99,7 @@ impl Window { } } -fn get_monitor_info(hmonitor: HMONITOR) -> Result { +pub(crate) fn get_monitor_info(hmonitor: HMONITOR) -> Result { let mut monitor_info: winuser::MONITORINFOEXW = unsafe { mem::uninitialized() }; monitor_info.cbSize = mem::size_of::() as DWORD; let status = unsafe { diff --git a/src/platform/windows/util.rs b/src/platform/windows/util.rs index a9f00eca..0f7e3887 100644 --- a/src/platform/windows/util.rs +++ b/src/platform/windows/util.rs @@ -1,6 +1,8 @@ -use std::{self, mem, ptr, slice}; +use std::{self, mem, ptr, slice, io}; use std::ops::BitAnd; +use std::sync::atomic::{AtomicBool, Ordering}; +use MouseCursor; use winapi::ctypes::wchar_t; use winapi::shared::minwindef::{BOOL, DWORD}; use winapi::shared::windef::{HWND, POINT, RECT}; @@ -47,6 +49,14 @@ pub unsafe fn status_map BOOL>(mut fun: F) -> Option { } } +fn win_to_err BOOL>(f: F) -> Result<(), io::Error> { + if f() != 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } +} + pub fn get_cursor_pos() -> Option { unsafe { status_map(|cursor_pos| winuser::GetCursorPos(cursor_pos)) } } @@ -55,26 +65,56 @@ pub fn get_window_rect(hwnd: HWND) -> Option { unsafe { status_map(|rect| winuser::GetWindowRect(hwnd, rect)) } } -pub fn get_client_rect(hwnd: HWND) -> Option { - unsafe { status_map(|rect| { +pub fn get_client_rect(hwnd: HWND) -> Result { + unsafe { + let mut rect = mem::uninitialized(); let mut top_left = mem::zeroed(); - if 0 == winuser::ClientToScreen(hwnd, &mut top_left) {return 0;}; - if 0 == winuser::GetClientRect(hwnd, rect) {return 0}; + + win_to_err(|| winuser::ClientToScreen(hwnd, &mut top_left))?; + win_to_err(|| winuser::GetClientRect(hwnd, &mut rect))?; rect.left += top_left.x; rect.top += top_left.y; rect.right += top_left.x; rect.bottom += top_left.y; - 1 + + Ok(rect) + } +} + +pub fn adjust_window_rect(hwnd: HWND, rect: RECT) -> Option { + unsafe { + let style = winuser::GetWindowLongW(hwnd, winuser::GWL_STYLE); + let style_ex = winuser::GetWindowLongW(hwnd, winuser::GWL_EXSTYLE); + adjust_window_rect_with_styles(hwnd, style as _, style_ex as _, rect) + } +} + +pub fn adjust_window_rect_with_styles(hwnd: HWND, style: DWORD, style_ex: DWORD, rect: RECT) -> Option { + unsafe { status_map(|r| { + *r = rect; + + let b_menu = !winuser::GetMenu(hwnd).is_null() as BOOL; + winuser::AdjustWindowRectEx(r, style as _ , b_menu, style_ex as _) }) } } -// This won't be needed anymore if we just add a derive to winapi. -pub fn rect_eq(a: &RECT, b: &RECT) -> bool { - let left_eq = a.left == b.left; - let right_eq = a.right == b.right; - let top_eq = a.top == b.top; - let bottom_eq = a.bottom == b.bottom; - left_eq && right_eq && top_eq && bottom_eq +pub fn set_cursor_hidden(hidden: bool) { + static HIDDEN: AtomicBool = AtomicBool::new(false); + let changed = HIDDEN.swap(hidden, Ordering::SeqCst) ^ hidden; + if changed { + unsafe{ winuser::ShowCursor(!hidden as BOOL) }; + } +} + +pub fn set_cursor_clip(rect: Option) -> Result<(), io::Error> { + unsafe { + let rect_ptr = rect.as_ref().map(|r| r as *const RECT).unwrap_or(ptr::null()); + win_to_err(|| winuser::ClipCursor(rect_ptr)) + } +} + +pub fn is_focused(window: HWND) -> bool { + window == unsafe{ winuser::GetActiveWindow() } } #[derive(Debug, Default, Clone, PartialEq, Eq)] @@ -115,3 +155,29 @@ pub unsafe fn get_last_error() -> Option { } None } + +impl MouseCursor { + pub(crate) fn to_windows_cursor(self) -> *const wchar_t { + match self { + MouseCursor::Arrow | MouseCursor::Default => winuser::IDC_ARROW, + MouseCursor::Hand => winuser::IDC_HAND, + MouseCursor::Crosshair => winuser::IDC_CROSS, + MouseCursor::Text | MouseCursor::VerticalText => winuser::IDC_IBEAM, + MouseCursor::NotAllowed | MouseCursor::NoDrop => winuser::IDC_NO, + MouseCursor::Grab | MouseCursor::Grabbing | + MouseCursor::Move | MouseCursor::AllScroll => winuser::IDC_SIZEALL, + MouseCursor::EResize | MouseCursor::WResize | + MouseCursor::EwResize | MouseCursor::ColResize => winuser::IDC_SIZEWE, + MouseCursor::NResize | MouseCursor::SResize | + MouseCursor::NsResize | MouseCursor::RowResize => winuser::IDC_SIZENS, + MouseCursor::NeResize | MouseCursor::SwResize | + MouseCursor::NeswResize => winuser::IDC_SIZENESW, + MouseCursor::NwResize | MouseCursor::SeResize | + MouseCursor::NwseResize => winuser::IDC_SIZENWSE, + MouseCursor::Wait => winuser::IDC_WAIT, + MouseCursor::Progress => winuser::IDC_APPSTARTING, + MouseCursor::Help => winuser::IDC_HELP, + _ => winuser::IDC_ARROW, // use arrow for the missing cases. + } + } +} diff --git a/src/platform/windows/window.rs b/src/platform/windows/window.rs index e7727429..d1583893 100644 --- a/src/platform/windows/window.rs +++ b/src/platform/windows/window.rs @@ -8,8 +8,8 @@ use std::sync::{Arc, Mutex}; use std::sync::mpsc::channel; use winapi::ctypes::c_int; -use winapi::shared::minwindef::{BOOL, DWORD, FALSE, LPARAM, TRUE, UINT, WORD, WPARAM}; -use winapi::shared::windef::{HWND, LPPOINT, POINT, RECT}; +use winapi::shared::minwindef::{DWORD, LPARAM, UINT, WORD, WPARAM}; +use winapi::shared::windef::{HWND, POINT, RECT}; use winapi::um::{combaseapi, dwmapi, libloaderapi, winuser}; use winapi::um::objbase::COINIT_MULTITHREADED; use winapi::um::shobjidl_core::{CLSID_TaskbarList, ITaskbarList2}; @@ -26,16 +26,14 @@ use { PhysicalSize, WindowAttributes, }; -use platform::platform::{Cursor, PlatformSpecificWindowBuilderAttributes, WindowId}; +use platform::platform::{PlatformSpecificWindowBuilderAttributes, WindowId}; use platform::platform::dpi::{dpi_to_scale_factor, get_hwnd_dpi}; use platform::platform::events_loop::{self, EventsLoop, DESTROY_MSG_ID, INITIAL_DPI_MSG_ID}; -use platform::platform::events_loop::WindowState; use platform::platform::icon::{self, IconType, WinIcon}; use platform::platform::monitor::get_available_monitors; use platform::platform::raw_input::register_all_mice_and_keyboards_for_raw_input; use platform::platform::util; - -const WS_RESIZABLE: DWORD = winuser::WS_SIZEBOX | winuser::WS_MAXIMIZEBOX; +use platform::platform::window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState}; /// The Win32 implementation of the main `Window` object. pub struct Window { @@ -182,16 +180,16 @@ impl Window { pub(crate) fn set_inner_size_physical(&self, x: u32, y: u32) { unsafe { - let mut rect = RECT { - top: 0, - left: 0, - bottom: y as LONG, - right: x as LONG, - }; - let dw_style = winuser::GetWindowLongA(self.window.0, winuser::GWL_STYLE) as DWORD; - let b_menu = !winuser::GetMenu(self.window.0).is_null() as BOOL; - let dw_style_ex = winuser::GetWindowLongA(self.window.0, winuser::GWL_EXSTYLE) as DWORD; - winuser::AdjustWindowRectEx(&mut rect, dw_style, b_menu, dw_style_ex); + let rect = util::adjust_window_rect( + self.window.0, + RECT { + top: 0, + left: 0, + bottom: y as LONG, + right: x as LONG, + } + ).expect("adjust_window_rect failed"); + let outer_x = (rect.right - rect.left).abs() as c_int; let outer_y = (rect.top - rect.bottom).abs() as c_int; winuser::SetWindowPos( @@ -251,25 +249,17 @@ impl Window { #[inline] pub fn set_resizable(&self, resizable: bool) { - let mut window_state = self.window_state.lock().unwrap(); - if mem::replace(&mut window_state.resizable, resizable) != resizable { - // If we're in fullscreen, update stored configuration but don't apply anything. - if window_state.fullscreen.is_none() { - let mut style = unsafe { - winuser::GetWindowLongW(self.window.0, winuser::GWL_STYLE) - }; + let window = self.window.clone(); + let window_state = Arc::clone(&self.window_state); - if resizable { - style |= WS_RESIZABLE as LONG; - } else { - style &= !WS_RESIZABLE as LONG; - } - - unsafe { - winuser::SetWindowLongW(self.window.0, winuser::GWL_STYLE, style as _); - }; - } - } + self.events_loop_proxy.execute_in_thread(move |_| { + WindowState::set_window_flags( + window_state.lock().unwrap(), + window.0, + None, + |f| f.set(WindowFlags::RESIZABLE, resizable), + ); + }); } /// Returns the `hwnd` of this window. @@ -280,123 +270,44 @@ impl Window { #[inline] pub fn set_cursor(&self, cursor: MouseCursor) { - let cursor_id = Cursor(match cursor { - MouseCursor::Arrow | MouseCursor::Default => winuser::IDC_ARROW, - MouseCursor::Hand => winuser::IDC_HAND, - MouseCursor::Crosshair => winuser::IDC_CROSS, - MouseCursor::Text | MouseCursor::VerticalText => winuser::IDC_IBEAM, - MouseCursor::NotAllowed | MouseCursor::NoDrop => winuser::IDC_NO, - MouseCursor::Grab | MouseCursor::Grabbing | - MouseCursor::Move | MouseCursor::AllScroll => winuser::IDC_SIZEALL, - MouseCursor::EResize | MouseCursor::WResize | - MouseCursor::EwResize | MouseCursor::ColResize => winuser::IDC_SIZEWE, - MouseCursor::NResize | MouseCursor::SResize | - MouseCursor::NsResize | MouseCursor::RowResize => winuser::IDC_SIZENS, - MouseCursor::NeResize | MouseCursor::SwResize | - MouseCursor::NeswResize => winuser::IDC_SIZENESW, - MouseCursor::NwResize | MouseCursor::SeResize | - MouseCursor::NwseResize => winuser::IDC_SIZENWSE, - MouseCursor::Wait => winuser::IDC_WAIT, - MouseCursor::Progress => winuser::IDC_APPSTARTING, - MouseCursor::Help => winuser::IDC_HELP, - _ => winuser::IDC_ARROW, // use arrow for the missing cases. - }); - self.window_state.lock().unwrap().cursor = cursor_id; + self.window_state.lock().unwrap().mouse.cursor = cursor; self.events_loop_proxy.execute_in_thread(move |_| unsafe { let cursor = winuser::LoadCursorW( ptr::null_mut(), - cursor_id.0, + cursor.to_windows_cursor(), ); winuser::SetCursor(cursor); }); } - unsafe fn cursor_is_grabbed(&self) -> Result { - let mut client_rect: RECT = mem::uninitialized(); - let mut clip_rect: RECT = mem::uninitialized(); - if winuser::GetClientRect(self.window.0, &mut client_rect) == 0 { - return Err("`GetClientRect` failed".to_owned()); - } - // A `POINT` is two `LONG`s (x, y), and the `RECT` field after `left` is `top`. - if winuser::ClientToScreen(self.window.0, &mut client_rect.left as *mut _ as LPPOINT) == 0 { - return Err("`ClientToScreen` (left, top) failed".to_owned()); - } - if winuser::ClientToScreen(self.window.0, &mut client_rect.right as *mut _ as LPPOINT) == 0 { - return Err("`ClientToScreen` (right, bottom) failed".to_owned()); - } - if winuser::GetClipCursor(&mut clip_rect) == 0 { - return Err("`GetClipCursor` failed".to_owned()); - } - Ok(util::rect_eq(&client_rect, &clip_rect)) - } - - pub(crate) unsafe fn grab_cursor_inner(window: &WindowWrapper, grab: bool) -> Result<(), String> { - if grab { - let mut rect = mem::uninitialized(); - if winuser::GetClientRect(window.0, &mut rect) == 0 { - return Err("`GetClientRect` failed".to_owned()); - } - // A `POINT` is two `LONG`s (x, y), and the `RECT` field after `left` is `top`. - if winuser::ClientToScreen(window.0, &mut rect.left as *mut _ as LPPOINT) == 0 { - return Err("`ClientToScreen` (left, top) failed".to_owned()); - } - if winuser::ClientToScreen(window.0, &mut rect.right as *mut _ as LPPOINT) == 0 { - return Err("`ClientToScreen` (right, bottom) failed".to_owned()); - } - if winuser::ClipCursor(&rect) == 0 { - return Err("`ClipCursor` failed".to_owned()); - } - } else { - if winuser::ClipCursor(ptr::null()) == 0 { - return Err("`ClipCursor` failed".to_owned()); - } - } - Ok(()) - } - #[inline] pub fn grab_cursor(&self, grab: bool) -> Result<(), String> { - let currently_grabbed = unsafe { self.cursor_is_grabbed() }?; - let window_state_lock = self.window_state.lock().unwrap(); - if currently_grabbed == grab && grab == window_state_lock.cursor_grabbed { - return Ok(()); - } let window = self.window.clone(); let window_state = Arc::clone(&self.window_state); let (tx, rx) = channel(); + self.events_loop_proxy.execute_in_thread(move |_| { - let result = unsafe { Self::grab_cursor_inner(&window, grab) }; - if result.is_ok() { - window_state.lock().unwrap().cursor_grabbed = grab; - } + let result = window_state.lock().unwrap().mouse + .set_cursor_flags(window.0, |f| f.set(CursorFlags::GRABBED, grab)) + .map_err(|e| e.to_string()); let _ = tx.send(result); }); - drop(window_state_lock); rx.recv().unwrap() } - pub(crate) unsafe fn hide_cursor_inner(hide: bool) { - if hide { - winuser::ShowCursor(FALSE); - } else { - winuser::ShowCursor(TRUE); - } - } - #[inline] pub fn hide_cursor(&self, hide: bool) { - let window_state_lock = self.window_state.lock().unwrap(); - // We don't want to increment/decrement the display count more than once! - if hide == window_state_lock.cursor_hidden { return; } - let (tx, rx) = channel(); + let window = self.window.clone(); let window_state = Arc::clone(&self.window_state); + let (tx, rx) = channel(); + self.events_loop_proxy.execute_in_thread(move |_| { - unsafe { Self::hide_cursor_inner(hide) }; - window_state.lock().unwrap().cursor_hidden = hide; - let _ = tx.send(()); + let result = window_state.lock().unwrap().mouse + .set_cursor_flags(window.0, |f| f.set(CursorFlags::HIDDEN, hide)) + .map_err(|e| e.to_string()); + let _ = tx.send(result); }); - drop(window_state_lock); - rx.recv().unwrap() + rx.recv().unwrap().ok(); } #[inline] @@ -431,271 +342,107 @@ impl Window { #[inline] pub fn set_maximized(&self, maximized: bool) { - let mut window_state = self.window_state.lock().unwrap(); - if mem::replace(&mut window_state.maximized, maximized) != maximized { - // We only maximize if we're not in fullscreen. - if window_state.fullscreen.is_none() { - let window = self.window.clone(); - unsafe { - // `ShowWindow` resizes the window, so it must be called from the main thread. - self.events_loop_proxy.execute_in_thread(move |_| { - winuser::ShowWindow( - window.0, - if maximized { - winuser::SW_MAXIMIZE - } else { - winuser::SW_RESTORE - }, - ); - }); - } - } - } - } - - unsafe fn set_fullscreen_style(&self, window_state: &mut WindowState) -> (LONG, LONG) { - if window_state.fullscreen.is_none() || window_state.saved_window_info.is_none() { - let client_rect = util::get_client_rect(self.window.0).expect("client rect retrieval failed"); - let dpi_factor = Some(window_state.dpi_factor); - window_state.saved_window_info = Some(events_loop::SavedWindowInfo { - style: winuser::GetWindowLongW(self.window.0, winuser::GWL_STYLE), - ex_style: winuser::GetWindowLongW(self.window.0, winuser::GWL_EXSTYLE), - client_rect, - is_fullscreen: true, - dpi_factor, - }); - } - - // We sync the system maximized state here, it will be used when restoring - let mut placement: winuser::WINDOWPLACEMENT = mem::zeroed(); - placement.length = mem::size_of::() as u32; - winuser::GetWindowPlacement(self.window.0, &mut placement); - window_state.maximized = placement.showCmd == (winuser::SW_SHOWMAXIMIZED as u32); - let saved_window_info = window_state.saved_window_info.as_ref().unwrap(); - - (saved_window_info.style, saved_window_info.ex_style) - } - - unsafe fn restore_saved_window(&self, window_state_lock: &mut WindowState) { - let (client_rect, mut style, ex_style) = { - // 'saved_window_info' can be None if the window has never been - // in fullscreen mode before this method gets called. - if window_state_lock.saved_window_info.is_none() { - return; - } - - let saved_window_info = window_state_lock.saved_window_info.as_mut().unwrap(); - - // Reset original window style and size. The multiple window size/moves - // here are ugly, but if SetWindowPos() doesn't redraw, the taskbar won't be - // repainted. Better-looking methods welcome. - saved_window_info.is_fullscreen = false; - - let client_rect = saved_window_info.client_rect.clone(); - let (style, ex_style) = (saved_window_info.style, saved_window_info.ex_style); - (client_rect, style, ex_style) - }; let window = self.window.clone(); let window_state = Arc::clone(&self.window_state); - let resizable = window_state_lock.resizable; - let decorations = window_state_lock.decorations; - let maximized = window_state_lock.maximized; - - // We're restoring the window to its size and position from before being fullscreened. - // `ShowWindow` resizes the window, so it must be called from the main thread. self.events_loop_proxy.execute_in_thread(move |_| { - let _ = Self::grab_cursor_inner(&window, false); - - if resizable && decorations { - style |= WS_RESIZABLE as LONG; - } else { - style &= !WS_RESIZABLE as LONG; - } - winuser::SetWindowLongW(window.0, winuser::GWL_STYLE, style); - winuser::SetWindowLongW(window.0, winuser::GWL_EXSTYLE, ex_style); - - let mut rect = client_rect; - winuser::AdjustWindowRectEx(&mut rect, style as _, 0, ex_style as _); - - winuser::SetWindowPos( + WindowState::set_window_flags( + window_state.lock().unwrap(), window.0, - ptr::null_mut(), - rect.left, - rect.top, - rect.right - rect.left, - rect.bottom - rect.top, - winuser::SWP_ASYNCWINDOWPOS - | winuser::SWP_NOZORDER - | winuser::SWP_NOACTIVATE - | winuser::SWP_FRAMECHANGED, + None, + |f| f.set(WindowFlags::MAXIMIZED, maximized), ); - - // We apply any requested changes to maximization state that occurred while we were in fullscreen. - winuser::ShowWindow( - window.0, - if maximized { - winuser::SW_MAXIMIZE - } else { - winuser::SW_RESTORE - }, - ); - - mark_fullscreen(window.0, false); - - let window_state_lock = window_state.lock().unwrap(); - let _ = Self::grab_cursor_inner(&window, window_state_lock.cursor_grabbed); }); } #[inline] pub fn set_fullscreen(&self, monitor: Option) { - let mut window_state_lock = self.window_state.lock().unwrap(); unsafe { + let window = self.window.clone(); + let window_state = Arc::clone(&self.window_state); + match &monitor { &Some(RootMonitorId { ref inner }) => { let (x, y): (i32, i32) = inner.get_position().into(); let (width, height): (u32, u32) = inner.get_dimensions().into(); - let window = self.window.clone(); - let window_state = Arc::clone(&self.window_state); - let (style, ex_style) = self.set_fullscreen_style(&mut window_state_lock); + let mut monitor = monitor.clone(); self.events_loop_proxy.execute_in_thread(move |_| { - let _ = Self::grab_cursor_inner(&window, false); + let mut window_state_lock = window_state.lock().unwrap(); - winuser::SetWindowLongW( - window.0, - winuser::GWL_STYLE, - ((style as DWORD) & !(winuser::WS_CAPTION | winuser::WS_THICKFRAME)) - as LONG, - ); + let client_rect = util::get_client_rect(window.0).expect("get client rect failed!"); + window_state_lock.saved_window = Some(SavedWindow { + client_rect, + dpi_factor: window_state_lock.dpi_factor + }); - winuser::SetWindowLongW( + window_state_lock.fullscreen = monitor.take(); + WindowState::refresh_window_state( + window_state_lock, window.0, - winuser::GWL_EXSTYLE, - ((ex_style as DWORD) - & !(winuser::WS_EX_DLGMODALFRAME | winuser::WS_EX_WINDOWEDGE - | winuser::WS_EX_CLIENTEDGE - | winuser::WS_EX_STATICEDGE)) - as LONG, - ); - - winuser::SetWindowPos( - window.0, - ptr::null_mut(), - x as c_int, - y as c_int, - width as c_int, - height as c_int, - winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER - | winuser::SWP_NOACTIVATE - | winuser::SWP_FRAMECHANGED, + Some(RECT { + left: x, + top: y, + right: x + width as c_int, + bottom: y + height as c_int, + }) ); mark_fullscreen(window.0, true); - - let window_state_lock = window_state.lock().unwrap(); - let _ = Self::grab_cursor_inner(&window, window_state_lock.cursor_grabbed); }); } &None => { - self.restore_saved_window(&mut window_state_lock); + self.events_loop_proxy.execute_in_thread(move |_| { + let mut window_state_lock = window_state.lock().unwrap(); + window_state_lock.fullscreen = None; + + if let Some(SavedWindow{client_rect, dpi_factor}) = window_state_lock.saved_window { + window_state_lock.dpi_factor = dpi_factor; + window_state_lock.saved_window = None; + + WindowState::refresh_window_state( + window_state_lock, + window.0, + Some(client_rect) + ); + } + + mark_fullscreen(window.0, false); + }); } } } - - window_state_lock.fullscreen = monitor; } #[inline] pub fn set_decorations(&self, decorations: bool) { - let mut window_state = self.window_state.lock().unwrap(); - if mem::replace(&mut window_state.decorations, decorations) != decorations { - let style_flags = (winuser::WS_CAPTION | winuser::WS_THICKFRAME) as LONG; - let ex_style_flags = (winuser::WS_EX_WINDOWEDGE) as LONG; + let window = self.window.clone(); + let window_state = Arc::clone(&self.window_state); - // if we are in fullscreen mode, we only change the saved window info - if window_state.fullscreen.is_some() { - let resizable = window_state.resizable; - let saved = window_state.saved_window_info.as_mut().unwrap(); - - if decorations { - saved.style = saved.style | style_flags; - saved.ex_style = saved.ex_style | ex_style_flags; - } else { - saved.style = saved.style & !style_flags; - saved.ex_style = saved.ex_style & !ex_style_flags; - } - if resizable { - saved.style |= WS_RESIZABLE as LONG; - } else { - saved.style &= !WS_RESIZABLE as LONG; - } - } else { - unsafe { - let mut rect = util::get_client_rect(self.window.0).expect("Get client rect failed!"); - - let mut style = winuser::GetWindowLongW(self.window.0, winuser::GWL_STYLE); - let mut ex_style = winuser::GetWindowLongW(self.window.0, winuser::GWL_EXSTYLE); - - if decorations { - style = style | style_flags; - ex_style = ex_style | ex_style_flags; - } else { - style = style & !style_flags; - ex_style = ex_style & !ex_style_flags; - } - - let window = self.window.clone(); - - self.events_loop_proxy.execute_in_thread(move |_| { - winuser::SetWindowLongW(window.0, winuser::GWL_STYLE, style); - winuser::SetWindowLongW(window.0, winuser::GWL_EXSTYLE, ex_style); - winuser::AdjustWindowRectEx(&mut rect, style as _, 0, ex_style as _); - - winuser::SetWindowPos( - window.0, - ptr::null_mut(), - rect.left, - rect.top, - rect.right - rect.left, - rect.bottom - rect.top, - winuser::SWP_ASYNCWINDOWPOS - | winuser::SWP_NOZORDER - | winuser::SWP_NOACTIVATE - | winuser::SWP_FRAMECHANGED, - ); - }); - } - } - } + self.events_loop_proxy.execute_in_thread(move |_| { + let client_rect = util::get_client_rect(window.0).expect("get client rect failed!"); + WindowState::set_window_flags( + window_state.lock().unwrap(), + window.0, + Some(client_rect), + |f| f.set(WindowFlags::DECORATIONS, decorations), + ); + }); } #[inline] pub fn set_always_on_top(&self, always_on_top: bool) { - let mut window_state = self.window_state.lock().unwrap(); - if mem::replace(&mut window_state.always_on_top, always_on_top) != always_on_top { - let window = self.window.clone(); - self.events_loop_proxy.execute_in_thread(move |_| { - let insert_after = if always_on_top { - winuser::HWND_TOPMOST - } else { - winuser::HWND_NOTOPMOST - }; - unsafe { - winuser::SetWindowPos( - window.0, - insert_after, - 0, - 0, - 0, - 0, - winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOMOVE | winuser::SWP_NOSIZE, - ); - winuser::UpdateWindow(window.0); - } - }); - } + let window = self.window.clone(); + let window_state = Arc::clone(&self.window_state); + + self.events_loop_proxy.execute_in_thread(move |_| { + WindowState::set_window_flags( + window_state.lock().unwrap(), + window.0, + None, + |f| f.set(WindowFlags::ALWAYS_ON_TOP, always_on_top), + ); + }); } #[inline] @@ -846,84 +593,27 @@ unsafe fn init( info!("Guessed window DPI factor: {}", guessed_dpi_factor); let dimensions = attributes.dimensions.unwrap_or_else(|| (1024, 768).into()); - let (width, height): (u32, u32) = dimensions.to_physical(guessed_dpi_factor).into(); - // building a RECT object with coordinates - let mut rect = RECT { - left: 0, - right: width as LONG, - top: 0, - bottom: height as LONG, - }; - // computing the style and extended style of the window - let (mut ex_style, style) = if !attributes.decorations { - (winuser::WS_EX_APPWINDOW, - //winapi::WS_POPUP is incompatible with winapi::WS_CHILD - if pl_attribs.parent.is_some() { - winuser::WS_CLIPSIBLINGS | winuser::WS_CLIPCHILDREN - } - else { - winuser::WS_POPUP | winuser::WS_CLIPSIBLINGS | winuser::WS_CLIPCHILDREN - } - ) - } else { - (winuser::WS_EX_APPWINDOW | winuser::WS_EX_WINDOWEDGE, - winuser::WS_OVERLAPPEDWINDOW | winuser::WS_CLIPSIBLINGS | winuser::WS_CLIPCHILDREN) - }; - - if attributes.always_on_top { - ex_style |= winuser::WS_EX_TOPMOST; - } - if pl_attribs.no_redirection_bitmap { - ex_style |= winuser::WS_EX_NOREDIRECTIONBITMAP; - } - if attributes.transparent && attributes.decorations { - ex_style |= winuser::WS_EX_LAYERED; - } - - // adjusting the window coordinates using the style - winuser::AdjustWindowRectEx(&mut rect, style, 0, ex_style); + let mut window_flags = WindowFlags::empty(); + window_flags.set(WindowFlags::DECORATIONS, attributes.decorations); + window_flags.set(WindowFlags::ALWAYS_ON_TOP, attributes.always_on_top); + window_flags.set(WindowFlags::NO_BACK_BUFFER, pl_attribs.no_redirection_bitmap); + window_flags.set(WindowFlags::TRANSPARENT, attributes.transparent); + // WindowFlags::VISIBLE and MAXIMIZED are set down below after the window has been configured. + window_flags.set(WindowFlags::RESIZABLE, attributes.resizable); + window_flags.set(WindowFlags::CHILD, pl_attribs.parent.is_some()); + window_flags.set(WindowFlags::ON_TASKBAR, true); // creating the real window this time, by using the functions in `extra_functions` let real_window = { - let (adjusted_width, adjusted_height) = if attributes.dimensions.is_some() { - let min_dimensions = attributes.min_dimensions - .map(|logical_size| PhysicalSize::from_logical(logical_size, guessed_dpi_factor)) - .map(|physical_size| adjust_size(physical_size, style, ex_style)) - .unwrap_or((0, 0)); - let max_dimensions = attributes.max_dimensions - .map(|logical_size| PhysicalSize::from_logical(logical_size, guessed_dpi_factor)) - .map(|physical_size| adjust_size(physical_size, style, ex_style)) - .unwrap_or((c_int::max_value(), c_int::max_value())); - ( - Some((rect.right - rect.left).min(max_dimensions.0).max(min_dimensions.0)), - Some((rect.bottom - rect.top).min(max_dimensions.1).max(min_dimensions.1)) - ) - } else { - (None, None) - }; - - let mut style = if !attributes.visible { - style - } else { - style | winuser::WS_VISIBLE - }; - - if !attributes.resizable { - style &= !WS_RESIZABLE; - } - - if pl_attribs.parent.is_some() { - style |= winuser::WS_CHILD; - } - - let handle = winuser::CreateWindowExW(ex_style | winuser::WS_EX_ACCEPTFILES, + let (style, ex_style) = window_flags.to_window_styles(); + let handle = winuser::CreateWindowExW( + ex_style, class_name.as_ptr(), title.as_ptr() as LPCWSTR, - style | winuser::WS_CLIPSIBLINGS | winuser::WS_CLIPCHILDREN, + style, + winuser::CW_USEDEFAULT, winuser::CW_USEDEFAULT, winuser::CW_USEDEFAULT, winuser::CW_USEDEFAULT, - adjusted_width.unwrap_or(winuser::CW_USEDEFAULT), - adjusted_height.unwrap_or(winuser::CW_USEDEFAULT), pl_attribs.parent.unwrap_or(ptr::null_mut()), ptr::null_mut(), libloaderapi::GetModuleHandleW(ptr::null()), @@ -935,6 +625,9 @@ unsafe fn init( format!("{}", io::Error::last_os_error())))); } + winuser::SetWindowLongW(handle, winuser::GWL_STYLE, 0); + winuser::SetWindowLongW(handle, winuser::GWL_EXSTYLE, 0); + WindowWrapper(handle) }; @@ -966,32 +659,6 @@ unsafe fn init( ); } - let window_state = { - let max_size = attributes.max_dimensions - .map(|logical_size| PhysicalSize::from_logical(logical_size, dpi_factor)); - let min_size = attributes.min_dimensions - .map(|logical_size| PhysicalSize::from_logical(logical_size, dpi_factor)); - let mut window_state = events_loop::WindowState { - cursor: Cursor(winuser::IDC_ARROW), // use arrow by default - cursor_grabbed: false, - cursor_hidden: false, - max_size, - min_size, - mouse_in_window: false, - saved_window_info: None, - dpi_factor, - fullscreen: attributes.fullscreen.clone(), - window_icon, - taskbar_icon, - decorations: attributes.decorations, - maximized: attributes.maximized, - resizable: attributes.resizable, - always_on_top: attributes.always_on_top, - }; - // Creating a mutex to track the current window state - Arc::new(Mutex::new(window_state)) - }; - // making the window transparent if attributes.transparent && !pl_attribs.no_redirection_bitmap { let region = CreateRectRgn(0, 0, -1, -1); // makes the window transparent @@ -1019,18 +686,41 @@ unsafe fn init( } } + window_flags.set(WindowFlags::VISIBLE, attributes.visible); + window_flags.set(WindowFlags::MAXIMIZED, attributes.maximized); + + let window_state = { + let mut window_state = WindowState::new( + &attributes, + window_icon, + taskbar_icon, + dpi_factor, + ); + let window_state = Arc::new(Mutex::new(window_state)); + WindowState::set_window_flags( + window_state.lock().unwrap(), + real_window.0, + None, + |f| *f = window_flags, + ); + window_state + }; + let win = Window { window: real_window, window_state, events_loop_proxy, }; - win.set_maximized(attributes.maximized); if let Some(_) = attributes.fullscreen { win.set_fullscreen(attributes.fullscreen); force_window_active(win.window.0); } + if let Some(dimensions) = attributes.dimensions { + win.set_inner_size(dimensions); + } + inserter.insert(win.window.0, win.window_state.clone()); Ok(win) diff --git a/src/platform/windows/window_state.rs b/src/platform/windows/window_state.rs new file mode 100644 index 00000000..fca04454 --- /dev/null +++ b/src/platform/windows/window_state.rs @@ -0,0 +1,329 @@ +use {MouseCursor, WindowAttributes}; +use std::{io, ptr}; +use std::sync::MutexGuard; +use dpi::LogicalSize; +use platform::platform::{util, events_loop}; +use platform::platform::icon::WinIcon; +use winapi::shared::windef::{RECT, HWND}; +use winapi::shared::minwindef::DWORD; +use winapi::um::winuser; + +/// Contains information about states and the window that the callback is going to use. +#[derive(Clone)] +pub struct WindowState { + pub mouse: MouseProperties, + + /// Used by `WM_GETMINMAXINFO`. + pub min_size: Option, + pub max_size: Option, + + pub window_icon: Option, + pub taskbar_icon: Option, + + pub saved_window: Option, + pub dpi_factor: f64, + + pub fullscreen: Option<::MonitorId>, + window_flags: WindowFlags, +} + +#[derive(Clone)] +pub struct SavedWindow { + pub client_rect: RECT, + pub dpi_factor: f64, +} + +#[derive(Clone)] +pub struct MouseProperties { + pub cursor: MouseCursor, + cursor_flags: CursorFlags, +} + +bitflags! { + pub struct CursorFlags: u8 { + const GRABBED = 1 << 0; + const HIDDEN = 1 << 1; + const IN_WINDOW = 1 << 2; + } +} +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; + + /// Marker flag for fullscreen. Should always match `WindowState::fullscreen`, but is + /// included here to make masking easier. + const MARKER_FULLSCREEN = 1 << 9; + + /// 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 FULLSCREEN_AND_MASK = !( + WindowFlags::DECORATIONS.bits | + WindowFlags::RESIZABLE.bits | + WindowFlags::MAXIMIZED.bits + ); + const NO_DECORATIONS_AND_MASK = !WindowFlags::RESIZABLE.bits; + const INVISIBLE_AND_MASK = !WindowFlags::MAXIMIZED.bits; + } +} + +impl WindowState { + pub fn new( + attributes: &WindowAttributes, + window_icon: Option, + taskbar_icon: Option, + dpi_factor: f64 + ) -> WindowState { + WindowState { + mouse: MouseProperties { + cursor: MouseCursor::default(), + cursor_flags: CursorFlags::empty(), + }, + + min_size: attributes.min_dimensions, + max_size: attributes.max_dimensions, + + window_icon, + taskbar_icon, + + saved_window: None, + dpi_factor, + + fullscreen: None, + window_flags: WindowFlags::empty() + } + } + + pub fn window_flags(&self) -> WindowFlags { + self.window_flags + } + + pub fn set_window_flags(mut this: MutexGuard, window: HWND, set_client_rect: Option, f: F) + where F: FnOnce(&mut WindowFlags) + { + let old_flags = this.window_flags; + f(&mut this.window_flags); + + let is_fullscreen = this.fullscreen.is_some(); + this.window_flags.set(WindowFlags::MARKER_FULLSCREEN, is_fullscreen); + let new_flags = this.window_flags; + + drop(this); + old_flags.apply_diff(window, new_flags, set_client_rect); + } + + pub fn refresh_window_state(this: MutexGuard, window: HWND, set_client_rect: Option) { + Self::set_window_flags(this, window, set_client_rect, |_| ()); + } + + pub fn set_window_flags_in_place(&mut self, f: F) + where F: FnOnce(&mut WindowFlags) + { + f(&mut self.window_flags); + } +} + +impl MouseProperties { + pub fn cursor_flags(&self) -> CursorFlags { + self.cursor_flags + } + + pub fn set_cursor_flags(&mut self, window: HWND, f: F) -> Result<(), io::Error> + where F: FnOnce(&mut CursorFlags) + { + let old_flags = self.cursor_flags; + f(&mut self.cursor_flags); + match self.cursor_flags.refresh_os_cursor(window) { + Ok(()) => (), + Err(e) => { + self.cursor_flags = old_flags; + return Err(e); + } + } + + Ok(()) + } +} + +impl WindowFlags { + fn mask(mut self) -> WindowFlags { + if self.contains(WindowFlags::MARKER_FULLSCREEN) { + self &= WindowFlags::FULLSCREEN_AND_MASK; + } + if !self.contains(WindowFlags::VISIBLE) { + self &= WindowFlags::INVISIBLE_AND_MASK; + } + if !self.contains(WindowFlags::DECORATIONS) { + self &= WindowFlags::NO_DECORATIONS_AND_MASK; + } + self + } + + pub fn to_window_styles(self) -> (DWORD, DWORD) { + use winapi::um::winuser::*; + + let (mut style, mut style_ex) = (0, 0); + + 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; + } + if self.contains(WindowFlags::ON_TASKBAR) { + style_ex |= WS_EX_APPWINDOW; + } + if self.contains(WindowFlags::ALWAYS_ON_TOP) { + style_ex |= WS_EX_TOPMOST; + } + if self.contains(WindowFlags::NO_BACK_BUFFER) { + style_ex |= WS_EX_NOREDIRECTIONBITMAP; + } + if self.contains(WindowFlags::TRANSPARENT) { + // Is this necessary? The docs say that WS_EX_LAYERED requires a windows class without + // CS_OWNDC, and Winit windows have that flag set. + style_ex |= WS_EX_LAYERED; + } + if self.contains(WindowFlags::CHILD) { + style |= WS_CHILD; // This is incompatible with WS_POPUP if that gets added eventually. + } + if self.contains(WindowFlags::MAXIMIZED) { + style |= WS_MAXIMIZE; + } + + style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_SYSMENU; + style_ex |= WS_EX_ACCEPTFILES; + + (style, style_ex) + } + + /// Adjust the window client rectangle to the return value, if present. + fn apply_diff(mut self, window: HWND, mut new: WindowFlags, set_client_rect: Option) { + self = self.mask(); + new = new.mask(); + + let diff = self ^ new; + if diff == WindowFlags::empty() { + return; + } + + if diff.contains(WindowFlags::VISIBLE) { + unsafe { + winuser::ShowWindow( + window, + match new.contains(WindowFlags::VISIBLE) { + true => winuser::SW_SHOW, + false => winuser::SW_HIDE + } + ); + } + } + if diff.contains(WindowFlags::ALWAYS_ON_TOP) { + unsafe { + winuser::SetWindowPos( + window, + match new.contains(WindowFlags::ALWAYS_ON_TOP) { + true => winuser::HWND_TOPMOST, + false => winuser::HWND_NOTOPMOST, + }, + 0, 0, 0, 0, + winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOMOVE | winuser::SWP_NOSIZE, + ); + winuser::UpdateWindow(window); + } + } + + if diff.contains(WindowFlags::MAXIMIZED) || new.contains(WindowFlags::MAXIMIZED) { + unsafe { + winuser::ShowWindow( + window, + match new.contains(WindowFlags::MAXIMIZED) { + true => winuser::SW_MAXIMIZE, + false => winuser::SW_RESTORE + } + ); + } + } + + if diff != WindowFlags::empty() { + let (style, style_ex) = new.to_window_styles(); + + unsafe { + winuser::SendMessageW(window, *events_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 1, 0); + + winuser::SetWindowLongW(window, winuser::GWL_STYLE, style as _); + winuser::SetWindowLongW(window, winuser::GWL_EXSTYLE, style_ex as _); + + match set_client_rect.and_then(|r| util::adjust_window_rect_with_styles(window, style, style_ex, r)) { + Some(client_rect) => { + let (x, y, w, h) = ( + client_rect.left, + client_rect.top, + client_rect.right - client_rect.left, + client_rect.bottom - client_rect.top, + ); + winuser::SetWindowPos( + window, + ptr::null_mut(), + x, y, w, h, + winuser::SWP_NOZORDER + | winuser::SWP_FRAMECHANGED, + ); + }, + None => { + // Refresh the window frame. + winuser::SetWindowPos( + window, + ptr::null_mut(), + 0, 0, 0, 0, + winuser::SWP_NOZORDER + | winuser::SWP_NOMOVE + | winuser::SWP_NOSIZE + | winuser::SWP_FRAMECHANGED, + ); + } + } + winuser::SendMessageW(window, *events_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 0, 0); + } + } + } +} + +impl CursorFlags { + fn refresh_os_cursor(self, window: HWND) -> Result<(), io::Error> { + let client_rect = util::get_client_rect(window)?; + + if util::is_focused(window) { + if self.contains(CursorFlags::GRABBED) { + util::set_cursor_clip(Some(client_rect))?; + } else { + util::set_cursor_clip(None)?; + } + } + + let cursor_in_client = self.contains(CursorFlags::IN_WINDOW); + if cursor_in_client { + util::set_cursor_hidden(self.contains(CursorFlags::HIDDEN)); + } else { + util::set_cursor_hidden(false); + } + + Ok(()) + } +}