use crate::{
dpi::{PhysicalPosition, PhysicalSize, Size},
event::ModifiersState,
icon::Icon,
platform_impl::platform::{event_loop, util},
window::{CursorIcon, Fullscreen, Theme, WindowAttributes},
};
use parking_lot::MutexGuard;
use std::io;
use windows_sys::Win32::{
Foundation::{HWND, RECT},
Graphics::Gdi::InvalidateRgn,
UI::WindowsAndMessaging::{
AdjustWindowRectEx, GetMenu, GetWindowLongW, SendMessageW, SetWindowLongW, SetWindowPos,
ShowWindow, GWL_EXSTYLE, GWL_STYLE, HWND_NOTOPMOST, HWND_TOPMOST, SWP_ASYNCWINDOWPOS,
SWP_FRAMECHANGED, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOREPOSITION, SWP_NOSIZE, SWP_NOZORDER,
SW_HIDE, SW_MAXIMIZE, SW_MINIMIZE, SW_RESTORE, SW_SHOW, WINDOWPLACEMENT, WINDOW_EX_STYLE,
WINDOW_STYLE, WS_BORDER, WS_CAPTION, WS_CHILD, WS_CLIPCHILDREN, WS_CLIPSIBLINGS,
WS_EX_ACCEPTFILES, WS_EX_APPWINDOW, WS_EX_LAYERED, WS_EX_NOREDIRECTIONBITMAP,
WS_EX_TOPMOST, WS_EX_TRANSPARENT, WS_EX_WINDOWEDGE, WS_MAXIMIZE, WS_MAXIMIZEBOX,
WS_MINIMIZE, WS_MINIMIZEBOX, WS_OVERLAPPEDWINDOW, WS_POPUP, WS_SIZEBOX, WS_SYSMENU,
WS_VISIBLE,
},
};
pub struct WindowState {
pub mouse: MouseProperties,
pub min_size: Option<Size>,
pub max_size: Option<Size>,
pub window_icon: Option<Icon>,
pub taskbar_icon: Option<Icon>,
pub saved_window: Option<SavedWindow>,
pub scale_factor: f64,
pub modifiers_state: ModifiersState,
pub fullscreen: Option<Fullscreen>,
pub current_theme: Theme,
pub preferred_theme: Option<Theme>,
pub high_surrogate: Option<u16>,
pub window_flags: WindowFlags,
pub ime_state: ImeState,
pub ime_allowed: bool,
pub is_active: bool,
pub is_focused: bool,
pub skip_taskbar: bool,
}
#[derive(Clone)]
pub struct SavedWindow {
pub placement: WINDOWPLACEMENT,
}
#[derive(Clone)]
pub struct MouseProperties {
pub cursor: CursorIcon,
pub capture_count: u32,
cursor_flags: CursorFlags,
pub last_position: Option<PhysicalPosition<f64>>,
}
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 VISIBLE = 1 << 1;
const ON_TASKBAR = 1 << 2;
const ALWAYS_ON_TOP = 1 << 3;
const NO_BACK_BUFFER = 1 << 4;
const TRANSPARENT = 1 << 5;
const CHILD = 1 << 6;
const MAXIMIZED = 1 << 7;
const POPUP = 1 << 8;
const MARKER_EXCLUSIVE_FULLSCREEN = 1 << 9;
const MARKER_BORDERLESS_FULLSCREEN = 1 << 10;
const MARKER_RETAIN_STATE_ON_SIZE = 1 << 11;
const MARKER_IN_SIZE_MOVE = 1 << 12;
const MINIMIZED = 1 << 13;
const IGNORE_CURSOR_EVENT = 1 << 14;
const MARKER_DECORATIONS = 1 << 15;
const MARKER_UNDECORATED_SHADOW = 1 << 16;
const EXCLUSIVE_FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits;
}
}
#[derive(Eq, PartialEq)]
pub enum ImeState {
Disabled,
Enabled,
Preedit,
}
impl WindowState {
pub(crate) fn new(
attributes: &WindowAttributes,
taskbar_icon: Option<Icon>,
scale_factor: f64,
current_theme: Theme,
preferred_theme: Option<Theme>,
) -> WindowState {
WindowState {
mouse: MouseProperties {
cursor: CursorIcon::default(),
capture_count: 0,
cursor_flags: CursorFlags::empty(),
last_position: None,
},
min_size: attributes.min_inner_size,
max_size: attributes.max_inner_size,
window_icon: attributes.window_icon.clone(),
taskbar_icon,
saved_window: None,
scale_factor,
modifiers_state: ModifiersState::default(),
fullscreen: None,
current_theme,
preferred_theme,
high_surrogate: None,
window_flags: WindowFlags::empty(),
ime_state: ImeState::Disabled,
ime_allowed: false,
is_active: false,
is_focused: false,
skip_taskbar: false,
}
}
pub fn window_flags(&self) -> WindowFlags {
self.window_flags
}
pub fn set_window_flags<F>(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<F>(&mut self, f: F)
where
F: FnOnce(&mut WindowFlags),
{
f(&mut self.window_flags);
}
pub fn has_active_focus(&self) -> bool {
self.is_active && self.is_focused
}
pub fn set_active(&mut self, is_active: bool) -> bool {
let old = self.has_active_focus();
self.is_active = is_active;
old != self.has_active_focus()
}
pub fn set_focused(&mut self, is_focused: bool) -> bool {
let old = self.has_active_focus();
self.is_focused = is_focused;
old != self.has_active_focus()
}
}
impl MouseProperties {
pub fn cursor_flags(&self) -> CursorFlags {
self.cursor_flags
}
pub fn set_cursor_flags<F>(&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_EXCLUSIVE_FULLSCREEN) {
self |= WindowFlags::EXCLUSIVE_FULLSCREEN_OR_MASK;
}
self
}
pub fn to_window_styles(self) -> (WINDOW_STYLE, WINDOW_EX_STYLE) {
let mut style = WS_CAPTION
| WS_MINIMIZEBOX
| WS_BORDER
| WS_CLIPSIBLINGS
| WS_CLIPCHILDREN
| WS_SYSMENU;
let mut style_ex = WS_EX_WINDOWEDGE | WS_EX_ACCEPTFILES;
if self.contains(WindowFlags::RESIZABLE) {
style |= WS_SIZEBOX | WS_MAXIMIZEBOX;
}
if self.contains(WindowFlags::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::CHILD) {
style |= WS_CHILD; }
if self.contains(WindowFlags::POPUP) {
style |= WS_POPUP;
}
if self.contains(WindowFlags::MINIMIZED) {
style |= WS_MINIMIZE;
}
if self.contains(WindowFlags::MAXIMIZED) {
style |= WS_MAXIMIZE;
}
if self.contains(WindowFlags::IGNORE_CURSOR_EVENT) {
style_ex |= WS_EX_TRANSPARENT | WS_EX_LAYERED;
}
if self.intersects(
WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN | WindowFlags::MARKER_BORDERLESS_FULLSCREEN,
) {
style &= !WS_OVERLAPPEDWINDOW;
}
(style, style_ex)
}
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 new.contains(WindowFlags::VISIBLE) {
unsafe {
ShowWindow(window, SW_SHOW);
}
}
if diff.contains(WindowFlags::ALWAYS_ON_TOP) {
unsafe {
SetWindowPos(
window,
match new.contains(WindowFlags::ALWAYS_ON_TOP) {
true => HWND_TOPMOST,
false => HWND_NOTOPMOST,
},
0,
0,
0,
0,
SWP_ASYNCWINDOWPOS | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE,
);
InvalidateRgn(window, 0, false.into());
}
}
if diff.contains(WindowFlags::MAXIMIZED) || new.contains(WindowFlags::MAXIMIZED) {
unsafe {
ShowWindow(
window,
match new.contains(WindowFlags::MAXIMIZED) {
true => SW_MAXIMIZE,
false => SW_RESTORE,
},
);
}
}
if diff.contains(WindowFlags::MINIMIZED) {
unsafe {
ShowWindow(
window,
match new.contains(WindowFlags::MINIMIZED) {
true => SW_MINIMIZE,
false => SW_RESTORE,
},
);
}
}
if !new.contains(WindowFlags::VISIBLE) {
unsafe {
ShowWindow(window, SW_HIDE);
}
}
if diff != WindowFlags::empty() {
let (style, style_ex) = new.to_window_styles();
unsafe {
SendMessageW(window, *event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 1, 0);
if !new.contains(WindowFlags::MINIMIZED) {
SetWindowLongW(window, GWL_STYLE, style as i32);
SetWindowLongW(window, GWL_EXSTYLE, style_ex as i32);
}
let mut flags = SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED;
if !new.contains(WindowFlags::MARKER_EXCLUSIVE_FULLSCREEN)
&& !new.contains(WindowFlags::MARKER_BORDERLESS_FULLSCREEN)
{
flags |= SWP_NOACTIVATE;
}
SetWindowPos(window, 0, 0, 0, 0, 0, flags);
SendMessageW(window, *event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 0, 0);
}
}
}
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;
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 {
fn refresh_os_cursor(self, window: HWND) -> Result<(), io::Error> {
let client_rect = util::WindowArea::Inner.get_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),
};
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(())
}
}