pub mod paint;
pub use paint::*;
use {
super::{
Instance,
LResult,
Message,
Monitor,
MonitorDefault,
UserData,
WindowClass,
WindowPtrIndex,
WindowStyle,
dpi_to_scale_factor,
procedure::{
WindowProcedure,
WindowState,
},
styles::ExtendedWindowStyle,
},
crate::{
Handle,
Rect,
declare_handle,
get_last_error,
last_error,
reset_last_error,
win10_build_version,
},
dpi::{
PhysicalPosition,
PhysicalSize,
PixelUnit,
Position,
Size,
},
std::{
collections::HashMap,
sync::{
LazyLock,
RwLock,
},
thread::ThreadId,
},
widestring::WideCString,
windows_result::{
Error,
Result,
},
windows_sys::Win32::{
Foundation::{
self,
LPARAM,
WPARAM,
},
Graphics::{
Dwm::{
DWMSBT_AUTO,
DWMSBT_MAINWINDOW,
DWMSBT_NONE,
DWMSBT_TABBEDWINDOW,
DWMSBT_TRANSIENTWINDOW,
DWMWA_BORDER_COLOR,
DWMWA_CAPTION_COLOR,
DWMWA_SYSTEMBACKDROP_TYPE,
DWMWA_USE_IMMERSIVE_DARK_MODE,
DWMWINDOWATTRIBUTE,
DwmExtendFrameIntoClientArea,
DwmSetWindowAttribute,
},
Gdi::{
self,
RedrawWindow,
UpdateWindow,
},
},
System::Threading::GetCurrentThreadId,
UI::{
Controls::MARGINS,
HiDpi::{
EnableNonClientDpiScaling,
GetDpiForWindow,
},
WindowsAndMessaging::{
self,
CW_USEDEFAULT,
CreateWindowExW,
DefWindowProcW,
DestroyWindow,
GetClientRect,
GetWindowLongPtrW,
GetWindowRect,
GetWindowTextLengthW,
GetWindowTextW,
GetWindowThreadProcessId,
IsWindow,
PostMessageW,
PostQuitMessage,
SHOW_WINDOW_CMD,
SendMessageW,
SendNotifyMessageW,
SetWindowLongPtrW,
SetWindowPos,
SetWindowTextW,
ShowWindow,
},
},
},
};
declare_handle!(
Window,
alias = "HWND",
doc = "https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types#hwnd"
);
pub(crate) static WINDOW_THREAD_IDS: LazyLock<RwLock<HashMap<Window, ThreadId>>> =
LazyLock::new(|| RwLock::new(HashMap::new()));
pub(crate) fn register_window_thread_id(window: Window) {
WINDOW_THREAD_IDS.write().unwrap().insert(window, std::thread::current().id());
}
pub(crate) fn get_window_thread_id(window: &Window) -> Option<ThreadId> {
WINDOW_THREAD_IDS.read().unwrap().get(window).cloned()
}
pub struct LpParam {
pub create_struct: CreateStruct,
pub wnd_proc: Option<Box<dyn WindowProcedure>>,
}
pub struct CreateStruct {
pub class: WindowClass,
pub name: String,
pub style: WindowStyle,
pub ex_style: ExtendedWindowStyle,
pub position: (Option<PixelUnit>, Option<PixelUnit>),
pub size: (Option<PixelUnit>, Option<PixelUnit>),
pub parent: Option<Window>,
pub menu: Option<*mut ()>,
pub instance: Option<Instance>,
}
impl CreateStruct {
#[inline]
pub fn x(&self) -> i32 {
let scale_factor = Monitor::primary().scale_factor();
self.position.0.map(|p| p.to_physical(scale_factor).0).unwrap_or(CW_USEDEFAULT)
}
#[inline]
pub fn y(&self) -> i32 {
let scale_factor = Monitor::primary().scale_factor();
self.position.1.map(|p| p.to_physical(scale_factor).0).unwrap_or(CW_USEDEFAULT)
}
#[inline]
pub fn width(&self) -> i32 {
let scale_factor = Monitor::primary().scale_factor();
self.size.0.map(|p| p.to_physical(scale_factor).0).unwrap_or(CW_USEDEFAULT)
}
#[inline]
pub fn height(&self) -> i32 {
let scale_factor = Monitor::primary().scale_factor();
self.size.1.map(|p| p.to_physical(scale_factor).0).unwrap_or(CW_USEDEFAULT)
}
}
#[doc = "https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw"]
pub fn create_window(create_struct: CreateStruct, wnd_proc: Box<dyn WindowProcedure>) -> Result<Window> {
let lp_param_ptr = Box::into_raw(Box::new(LpParam { create_struct, wnd_proc: Some(wnd_proc) }));
let lp_param = unsafe { lp_param_ptr.as_ref() }.unwrap();
let name = WideCString::from_str(&lp_param.create_struct.name).unwrap();
let hwnd = unsafe {
CreateWindowExW(
lp_param.create_struct.ex_style.to_raw(),
lp_param.create_struct.class.atom(),
name.as_ptr(),
lp_param.create_struct.style.to_raw(),
lp_param.create_struct.x(),
lp_param.create_struct.y(),
lp_param.create_struct.width(),
lp_param.create_struct.height(),
lp_param.create_struct.parent.unwrap_or_default().to_raw() as _,
lp_param.create_struct.menu.unwrap_or_else(std::ptr::null_mut) as _,
lp_param.create_struct.instance.unwrap_or_default().to_raw() as _,
lp_param_ptr.cast(),
)
};
match hwnd.is_null() {
true => Err(get_last_error().unwrap_or(Error::empty())),
false => Ok(unsafe { Window::from_raw(hwnd as usize) }),
}
}
impl Window {
#[inline]
#[allow(clippy::too_many_arguments)]
pub fn new(create_struct: CreateStruct, wnd_proc: Box<dyn WindowProcedure>) -> Result<Self> {
create_window(create_struct, wnd_proc)
}
pub fn builder() -> WindowBuilder<NoClass, NoProc> {
WindowBuilder::default()
}
}
pub struct NoClass;
pub struct Class(WindowClass);
pub struct NoProc;
pub struct Proc(Box<dyn WindowProcedure>);
pub struct WindowBuilder<WndClass, WndProc> {
class: WndClass,
wnd_proc: WndProc,
name: String,
style: WindowStyle,
ex_style: ExtendedWindowStyle,
position: (Option<PixelUnit>, Option<PixelUnit>),
size: (Option<PixelUnit>, Option<PixelUnit>),
parent: Option<Window>,
menu: Option<*mut ()>,
instance: Option<Instance>,
}
impl Default for WindowBuilder<NoClass, NoProc> {
fn default() -> Self {
WindowBuilder {
class: NoClass,
wnd_proc: NoProc,
name: "Window".to_string(),
style: WindowStyle::OverlappedWindow,
ex_style: ExtendedWindowStyle::default(),
position: (None, None),
size: (None, None),
parent: None,
menu: None,
instance: Some(Instance::get()),
}
}
}
impl<WndProc> WindowBuilder<NoClass, WndProc> {
pub fn with_class(self, class: WindowClass) -> WindowBuilder<Class, WndProc> {
WindowBuilder {
class: Class(class),
wnd_proc: self.wnd_proc,
name: self.name,
style: self.style,
ex_style: self.ex_style,
position: self.position,
size: self.size,
parent: self.parent,
menu: self.menu,
instance: self.instance,
}
}
}
impl<WndClass> WindowBuilder<WndClass, NoProc> {
pub fn with_procedure(self, wndproc: impl 'static + WindowProcedure) -> WindowBuilder<WndClass, Proc> {
WindowBuilder {
class: self.class,
wnd_proc: Proc(Box::new(wndproc)),
name: self.name,
style: self.style,
ex_style: self.ex_style,
position: self.position,
size: self.size,
parent: self.parent,
menu: self.menu,
instance: self.instance,
}
}
}
impl<WndClass, WndProc> WindowBuilder<WndClass, WndProc> {
pub fn with_name(mut self, name: impl Into<String>) -> WindowBuilder<WndClass, WndProc> {
self.name = name.into();
self
}
pub fn with_style(mut self, style: WindowStyle) -> WindowBuilder<WndClass, WndProc> {
self.style = style;
self
}
pub fn with_ex_style(mut self, ex_style: ExtendedWindowStyle) -> WindowBuilder<WndClass, WndProc> {
self.ex_style = ex_style;
self
}
pub fn with_x(mut self, x: Option<PixelUnit>) -> WindowBuilder<WndClass, WndProc> {
self.position.0 = x;
self
}
pub fn with_y(mut self, y: Option<PixelUnit>) -> WindowBuilder<WndClass, WndProc> {
self.position.1 = y;
self
}
pub fn with_position(mut self, position: Option<impl Into<Position>>) -> WindowBuilder<WndClass, WndProc> {
match position {
Some(position) => {
let pos: PhysicalPosition<i32> = position.into().to_physical(1.0);
self.position = (Some(PixelUnit::Physical(pos.x.into())), Some(PixelUnit::Physical(pos.y.into())));
},
None => self.position = (None, None),
};
self
}
pub fn with_width(mut self, width: Option<PixelUnit>) -> WindowBuilder<WndClass, WndProc> {
self.size.0 = width;
self
}
pub fn with_height(mut self, height: Option<PixelUnit>) -> WindowBuilder<WndClass, WndProc> {
self.size.1 = height;
self
}
pub fn with_size(mut self, size: Option<impl Into<Size>>) -> WindowBuilder<WndClass, WndProc> {
match size {
Some(size) => {
let size: PhysicalSize<i32> = size.into().to_physical(1.0);
self.size =
(Some(PixelUnit::Physical(size.width.into())), Some(PixelUnit::Physical(size.height.into())));
},
None => self.size = (None, None),
};
self
}
pub fn with_parent(mut self, parent: Option<Window>) -> WindowBuilder<WndClass, WndProc> {
self.parent = parent;
self
}
pub fn with_menu(mut self, menu: Option<*mut ()>) -> WindowBuilder<WndClass, WndProc> {
self.menu = menu;
self
}
pub fn with_instance(mut self, instance: Option<Instance>) -> WindowBuilder<WndClass, WndProc> {
self.instance = instance;
self
}
}
impl WindowBuilder<Class, Proc> {
pub fn create(self) -> Result<Window> {
Window::new(
CreateStruct {
class: self.class.0,
name: self.name,
style: self.style,
ex_style: self.ex_style,
position: self.position,
size: self.size,
parent: self.parent,
menu: self.menu,
instance: self.instance,
},
self.wnd_proc.0,
)
}
}
impl Window {
#[doc = "https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-iswindow"]
pub fn is_window(&self) -> bool {
self.is_current_thread() && unsafe { IsWindow(self.to_raw() as _) != 0 }
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum CmdShow {
Hide,
Show,
#[default]
ShowDefault,
Restore,
Normal,
Maximize,
Minimize,
ForceMinimize,
ShowMinimized,
ShowMinNoActive,
ShowNoActivate,
ShowNA,
}
impl CmdShow {
pub const fn from_raw(raw: SHOW_WINDOW_CMD) -> Self {
match raw {
WindowsAndMessaging::SW_HIDE => CmdShow::Hide,
WindowsAndMessaging::SW_NORMAL => CmdShow::Normal,
WindowsAndMessaging::SW_SHOWMINIMIZED => CmdShow::ShowMinimized,
WindowsAndMessaging::SW_SHOWMAXIMIZED => CmdShow::Maximize,
WindowsAndMessaging::SW_SHOWNOACTIVATE => CmdShow::ShowNoActivate,
WindowsAndMessaging::SW_SHOW => CmdShow::Show,
WindowsAndMessaging::SW_MINIMIZE => CmdShow::Minimize,
WindowsAndMessaging::SW_SHOWMINNOACTIVE => CmdShow::ShowMinNoActive,
WindowsAndMessaging::SW_SHOWNA => CmdShow::ShowNA,
WindowsAndMessaging::SW_RESTORE => CmdShow::Restore,
WindowsAndMessaging::SW_FORCEMINIMIZE => CmdShow::ForceMinimize,
_ => CmdShow::ShowDefault,
}
}
pub const fn to_raw(&self) -> SHOW_WINDOW_CMD {
match self {
CmdShow::Hide => WindowsAndMessaging::SW_HIDE,
CmdShow::Normal => WindowsAndMessaging::SW_NORMAL,
CmdShow::ShowMinimized => WindowsAndMessaging::SW_SHOWMINIMIZED,
CmdShow::Maximize => WindowsAndMessaging::SW_SHOWMAXIMIZED,
CmdShow::ShowNoActivate => WindowsAndMessaging::SW_SHOWNOACTIVATE,
CmdShow::Show => WindowsAndMessaging::SW_SHOW,
CmdShow::Minimize => WindowsAndMessaging::SW_MINIMIZE,
CmdShow::ShowMinNoActive => WindowsAndMessaging::SW_SHOWMINNOACTIVE,
CmdShow::ShowNA => WindowsAndMessaging::SW_SHOWNA,
CmdShow::Restore => WindowsAndMessaging::SW_RESTORE,
CmdShow::ShowDefault => WindowsAndMessaging::SW_SHOWDEFAULT,
CmdShow::ForceMinimize => WindowsAndMessaging::SW_FORCEMINIMIZE,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ShowWindowResult {
WasVisible,
WasHidden,
}
impl Window {
#[doc = "https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow"]
pub fn show_window(&self, cmd_show: CmdShow) -> ShowWindowResult {
match unsafe { ShowWindow(self.to_raw() as _, cmd_show.to_raw()) } {
0 => ShowWindowResult::WasHidden,
_ => ShowWindowResult::WasVisible,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DwmWindowAttribute {
UseImmersiveDarkMode(bool),
SystemBackdropType(SystemBackdropType),
CaptionColor(u32),
BorderColor(u32),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SystemBackdropType {
Auto,
None,
MainWindow,
TransientWindow,
TabbedWindow,
}
impl SystemBackdropType {
pub const fn to_raw(&self) -> i32 {
match self {
Self::Auto => DWMSBT_AUTO,
Self::None => DWMSBT_NONE,
Self::MainWindow => DWMSBT_MAINWINDOW,
Self::TransientWindow => DWMSBT_TRANSIENTWINDOW,
Self::TabbedWindow => DWMSBT_TABBEDWINDOW,
}
}
}
impl Window {
pub fn enable_non_client_dpi_scaling(&self) {
unsafe { EnableNonClientDpiScaling(self.to_ptr()) };
}
pub fn dwm_set_window_attribute(&self, attribute: DwmWindowAttribute) {
match attribute {
DwmWindowAttribute::UseImmersiveDarkMode(enable) => self.use_immersive_dark_mode(enable),
DwmWindowAttribute::SystemBackdropType(backdrop_type) => {
self.system_backdrop_type(backdrop_type.to_raw())
},
DwmWindowAttribute::CaptionColor(color) => self.caption_color(color),
DwmWindowAttribute::BorderColor(color) => self.border_color(color),
}
}
fn border_color(&self, color: u32) {
unsafe {
DwmSetWindowAttribute(
self.to_ptr(),
DWMWA_CAPTION_COLOR as u32,
&raw const color as *const std::ffi::c_void,
std::mem::size_of::<DWMWINDOWATTRIBUTE>() as u32,
)
};
}
fn caption_color(&self, color: u32) {
unsafe {
DwmSetWindowAttribute(
self.to_ptr(),
DWMWA_BORDER_COLOR as u32,
&raw const color as *const std::ffi::c_void,
std::mem::size_of::<DWMWINDOWATTRIBUTE>() as u32,
)
};
}
fn system_backdrop_type(&self, backdrop_type: i32) {
unsafe {
DwmSetWindowAttribute(
self.to_ptr(),
DWMWA_SYSTEMBACKDROP_TYPE as u32,
&raw const backdrop_type as *const std::ffi::c_void,
std::mem::size_of::<DWMWINDOWATTRIBUTE>() as u32,
)
};
}
pub fn extend_into_client_all(&self) {
let margins = MARGINS { cxLeftWidth: -1, cxRightWidth: -1, cyTopHeight: -1, cyBottomHeight: -1 };
unsafe {
DwmExtendFrameIntoClientArea(self.to_ptr(), &margins);
}
}
pub fn set_acrylic_background(&self, _color: u32) {
todo!()
}
pub fn dpi(&self) -> u32 {
match unsafe { GetDpiForWindow(self.to_ptr()) } {
0 => Monitor::BASE_DPI,
dpi => dpi,
}
}
pub fn scale_factor(&self) -> f64 {
dpi_to_scale_factor(self.dpi())
}
pub fn monitor(&self) -> Monitor {
Monitor::from_window(*self, MonitorDefault::Nearest).unwrap()
}
fn use_immersive_dark_mode(&self, enable: bool) {
let value = enable as windows_sys::core::BOOL;
let Some(version) = win10_build_version() else {
return;
};
let dw_attribute: u32 = if version < 19041 { 19 } else { DWMWA_USE_IMMERSIVE_DARK_MODE as _ };
unsafe {
DwmSetWindowAttribute(
self.to_ptr(),
dw_attribute,
(&raw const value).cast(),
std::mem::size_of::<DWMWINDOWATTRIBUTE>() as u32,
)
};
}
pub fn update(&self) -> Result<()> {
reset_last_error();
match unsafe { UpdateWindow(self.to_ptr()) } {
0 => last_error(),
_ => Ok(()),
}
}
pub fn redraw(&self) -> Result<()> {
reset_last_error();
match unsafe {
RedrawWindow(self.to_ptr(), std::ptr::null(), std::ptr::null_mut(), Gdi::RDW_INTERNALPAINT)
} {
0 => last_error(),
_ => Ok(()),
}
}
pub fn send_message(&self, message: Message) -> LResult {
let lresult = unsafe { SendMessageW(self.to_ptr(), message.id().to_raw(), message.w().0, message.l().0) };
lresult.into()
}
pub fn send_notify_message(&self, message: Message) {
unsafe { SendNotifyMessageW(self.to_ptr(), message.id().to_raw(), message.w().0, message.l().0) };
}
pub fn post_message(&self, message: Message) -> Result<()> {
reset_last_error();
let result = unsafe { PostMessageW(self.to_ptr(), message.id().to_raw(), message.w().0, message.l().0) };
match result != 0 {
true => Ok(()),
false => last_error(),
}
}
pub fn get_thread_id(&self) -> ThreadId {
get_window_thread_id(self).unwrap()
}
pub(crate) fn def_window_proc_raw(&self, msg: u32, w_param: WPARAM, l_param: LPARAM) -> LResult {
LResult(unsafe { DefWindowProcW(self.to_ptr(), msg, w_param, l_param) })
}
pub fn def_window_proc(&self, message: &Message) -> Option<LResult> {
Some(self.def_window_proc_raw(message.id().to_raw(), message.w().0, message.l().0))
}
pub fn destroy(&self) -> Result<()> {
if self.is_window()
&& let Some(data) = self.user_data()
&& let WindowState::Running = data.state
{
data.state = WindowState::Destroying;
reset_last_error();
return match unsafe { DestroyWindow(self.to_ptr()) } {
0 => last_error(),
_ => Ok(()),
};
}
Ok(())
}
pub fn get_platform_thread_id(&self) -> Option<u32> {
let id = unsafe { GetWindowThreadProcessId(self.to_ptr(), std::ptr::null_mut()) };
match id {
0 => None,
_ => Some(id),
}
}
pub fn is_current_thread(&self) -> bool {
let current_thread = unsafe { GetCurrentThreadId() };
let window_thread = self.get_platform_thread_id();
window_thread.is_some_and(|id| id == current_thread)
}
pub fn quit(&self) {
self.quit_with_code(0)
}
pub fn quit_with_code(&self, exit_code: i32) {
unsafe { PostQuitMessage(exit_code) };
}
pub fn set_window_text(&self, text: impl Into<String>) -> Result<()> {
let text = WideCString::from_str(text.into()).unwrap();
reset_last_error();
match unsafe { SetWindowTextW(self.to_ptr(), text.as_ptr()) } {
0 => last_error(),
_ => Ok(()),
}
}
pub fn get_window_text(&self) -> Result<String> {
reset_last_error();
let text_len = unsafe { GetWindowTextLengthW(self.to_ptr()) } as usize;
if text_len == 0 {
last_error()?;
};
let mut buffer = vec![0u16; text_len + 1];
let result = unsafe { GetWindowTextW(self.to_ptr(), buffer.as_mut_ptr(), buffer.len() as i32) };
if result == 0 {
last_error()?;
};
Ok(WideCString::from_vec_truncate(buffer).to_string_lossy())
}
pub fn get_window_rect(&self) -> Result<Rect> {
let mut window_rect = Foundation::RECT::default();
match unsafe { GetWindowRect(self.to_ptr(), &mut window_rect) } {
0 => Err(last_error().unwrap_err()),
_ => Ok(window_rect.into()),
}
}
pub fn get_client_rect(&self) -> Result<Rect> {
let mut window_rect = Foundation::RECT::default();
match unsafe { GetClientRect(self.to_ptr(), &mut window_rect) } {
0 => Err(last_error().unwrap_err()),
_ => Ok(window_rect.into()),
}
}
pub fn window_size(&self) -> PhysicalSize<u32> {
let window_rect = self.get_window_rect().unwrap();
PhysicalSize {
width: (window_rect.right - window_rect.left) as u32,
height: (window_rect.bottom - window_rect.top) as u32,
}
}
pub fn client_size(&self) -> PhysicalSize<u32> {
let client_rect = self.get_client_rect().unwrap();
PhysicalSize {
width: (client_rect.right - client_rect.left) as u32,
height: (client_rect.bottom - client_rect.top) as u32,
}
}
pub fn window_position(&self) -> PhysicalPosition<i32> {
let window_rect = self.get_window_rect().unwrap();
PhysicalPosition { x: window_rect.left, y: window_rect.top }
}
pub fn client_position(&self) -> PhysicalPosition<i32> {
let client_rect = self.get_client_rect().unwrap();
PhysicalPosition { x: client_rect.left, y: client_rect.top }
}
pub fn set_position(
&self,
top_left: impl Into<PhysicalPosition<i32>>,
size: impl Into<PhysicalSize<i32>>,
) -> Result<()> {
let (top_left, size) = (top_left.into(), size.into());
reset_last_error();
unsafe {
SetWindowPos(
self.to_ptr(),
std::ptr::null_mut(),
top_left.x,
top_left.y,
size.width,
size.height,
WindowsAndMessaging::SWP_NOZORDER | WindowsAndMessaging::SWP_NOACTIVATE,
)
};
last_error()
}
pub(crate) fn get_window_ptr(&self, index: WindowPtrIndex) -> isize {
unsafe { GetWindowLongPtrW(self.to_ptr(), index.to_raw()) as _ }
}
pub fn instance(&self) -> Instance {
unsafe { Instance::from_raw(self.get_window_ptr(WindowPtrIndex::Instance) as _) }
}
pub(crate) fn set_window_ptr(&self, index: WindowPtrIndex, value: isize) -> Result<isize> {
reset_last_error();
let result = unsafe { SetWindowLongPtrW(self.to_ptr(), index.to_raw(), value) as _ };
match (result == 0, get_last_error()) {
(true, Some(error)) => Err(error),
_ => Ok(result),
}
}
#[allow(clippy::mut_from_ref)] #[inline]
pub(crate) fn user_data(&self) -> Option<&mut UserData> {
unsafe { (self.get_window_ptr(WindowPtrIndex::UserData) as *mut UserData).as_mut() }
}
}