use crate::{ dpi::LogicalSize, platform_impl::platform::{event_loop, icon::WinIcon, util}, window::{CursorIcon, Fullscreen, WindowAttributes}, }; use parking_lot::MutexGuard; use std::{io, ptr}; use winapi::{ shared::{ minwindef::DWORD, windef::{HWND, RECT}, }, 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, /// Used to supress duplicate redraw attempts when calling `request_redraw` multiple /// times in `EventsCleared`. pub queued_out_of_band_redraw: bool, pub is_dark_mode: bool, pub high_surrogate: Option, window_flags: WindowFlags, } #[derive(Clone)] pub struct SavedWindow { pub client_rect: RECT, pub dpi_factor: f64, } #[derive(Clone)] pub struct MouseProperties { pub cursor: CursorIcon, pub buttons_down: u32, 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 MINIMIZED = 1 << 11; const FULLSCREEN_AND_MASK = !( WindowFlags::DECORATIONS.bits | WindowFlags::RESIZABLE.bits | WindowFlags::MAXIMIZED.bits ); const FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.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, is_dark_mode: bool, ) -> WindowState { WindowState { mouse: MouseProperties { cursor: CursorIcon::default(), buttons_down: 0, cursor_flags: CursorFlags::empty(), }, min_size: attributes.min_inner_size, max_size: attributes.max_inner_size, window_icon, taskbar_icon, saved_window: None, dpi_factor, fullscreen: None, queued_out_of_band_redraw: false, is_dark_mode, high_surrogate: None, window_flags: WindowFlags::empty(), } } pub fn window_flags(&self) -> WindowFlags { self.window_flags } pub fn set_window_flags(mut this: MutexGuard<'_, Self>, window: HWND, f: F) where F: FnOnce(&mut WindowFlags), { let old_flags = this.window_flags; f(&mut this.window_flags); let new_flags = this.window_flags; drop(this); old_flags.apply_diff(window, new_flags); } 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; self |= WindowFlags::FULLSCREEN_OR_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) && self.contains(WindowFlags::DECORATIONS) { 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::MINIMIZED) { style |= WS_MINIMIZE; } 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) { 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::SWP_NOACTIVATE, ); 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, }, ); } } // Minimize operations should execute after maximize for proper window animations if diff.contains(WindowFlags::MINIMIZED) { unsafe { winuser::ShowWindow( window, match new.contains(WindowFlags::MINIMIZED) { true => winuser::SW_MINIMIZE, false => winuser::SW_RESTORE, }, ); } } if diff != WindowFlags::empty() { let (style, style_ex) = new.to_window_styles(); unsafe { winuser::SendMessageW(window, *event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 1, 0); // This condition is necessary to avoid having an unrestorable window if !new.contains(WindowFlags::MINIMIZED) { winuser::SetWindowLongW(window, winuser::GWL_STYLE, style as _); winuser::SetWindowLongW(window, winuser::GWL_EXSTYLE, style_ex as _); } let mut flags = winuser::SWP_NOZORDER | winuser::SWP_NOMOVE | winuser::SWP_NOSIZE | winuser::SWP_FRAMECHANGED; // We generally don't want style changes here to affect window // focus, but for fullscreen windows they must be activated // (i.e. focused) so that they appear on top of the taskbar if !new.contains(WindowFlags::MARKER_FULLSCREEN) { flags |= winuser::SWP_NOACTIVATE; } // Refresh the window frame winuser::SetWindowPos(window, ptr::null_mut(), 0, 0, 0, 0, flags); winuser::SendMessageW(window, *event_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) { let cursor_clip = match self.contains(CursorFlags::GRABBED) { true => Some(client_rect), false => None, }; let rect_to_tuple = |rect: RECT| (rect.left, rect.top, rect.right, rect.bottom); let active_cursor_clip = rect_to_tuple(util::get_cursor_clip()?); let desktop_rect = rect_to_tuple(util::get_desktop_rect()); let active_cursor_clip = match desktop_rect == active_cursor_clip { true => None, false => Some(active_cursor_clip), }; // We do this check because calling `set_cursor_clip` incessantly will flood the event // loop with `WM_MOUSEMOVE` events, and `refresh_os_cursor` is called by `set_cursor_flags` // which at times gets called once every iteration of the eventloop. if active_cursor_clip != cursor_clip.map(rect_to_tuple) { util::set_cursor_clip(cursor_clip)?; } } 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(()) } }