use crate::{
core::{CheckNullError, CheckNumberError, ResultExt},
windows, Null, Zeroed,
};
use std::{cell::Cell, mem};
use windows::{
core::{HSTRING, PCWSTR},
Win32::{
Foundation::{SetLastError, ERROR_SUCCESS, HWND, LPARAM, LRESULT, POINT, SIZE, WPARAM},
System::{LibraryLoader::GetModuleHandleW, Performance::QueryPerformanceCounter},
UI::WindowsAndMessaging::{
CreateWindowExW, DefWindowProcW, DestroyWindow, GetWindowLongPtrW, IsWindow,
RegisterClassExW, SetWindowLongPtrW, UnregisterClassW, CW_USEDEFAULT, GWLP_USERDATA,
HMENU, HWND_MESSAGE, WINDOW_EX_STYLE, WINDOW_STYLE, WNDCLASSEXW,
},
},
};
mod translate;
pub use translate::*;
thread_local! {
static NEXT_WINDOW_USER_DATA_ON_INIT: Cell<isize> = const { Cell::new(0) };
}
pub trait WndProc: FnMut(HWND, u32, WPARAM, LPARAM) -> Option<LRESULT> {}
impl<F> WndProc for F where F: FnMut(HWND, u32, WPARAM, LPARAM) -> Option<LRESULT> {}
pub struct WindowClass<'a> {
atom: u16,
wnd_proc_ptr: *mut Box<dyn WndProc + 'a>,
}
impl<'a> WindowClass<'a> {
pub fn new<F>(wnd_proc: F) -> windows::core::Result<Self>
where
F: WndProc + 'a,
{
Self::with_name(&Self::make_name()?, wnd_proc)
}
pub fn with_name<F>(name: &str, wnd_proc: F) -> windows::core::Result<Self>
where
F: WndProc + 'a,
{
Self::with_details(
WNDCLASSEXW {
cbSize: mem::size_of::<WNDCLASSEXW>() as _,
lpfnWndProc: Some(Self::base_wnd_proc),
hInstance: unsafe { GetModuleHandleW(PCWSTR::NULL)? }.into(),
lpszClassName: PCWSTR(HSTRING::from(name).as_ptr()),
..Default::default()
},
wnd_proc,
)
}
pub fn with_details<F>(
mut wnd_class_ex: WNDCLASSEXW,
wnd_proc: F,
) -> windows::core::Result<Self>
where
F: WndProc + 'a,
{
wnd_class_ex.lpfnWndProc = Some(Self::base_wnd_proc);
Ok(Self {
atom: unsafe { RegisterClassExW(&wnd_class_ex) }.nonzero_or_win32_err()?,
wnd_proc_ptr: Box::into_raw(Box::new(Box::new(wnd_proc))),
})
}
pub fn make_name() -> windows::core::Result<String> {
let mut precise_time = 0;
unsafe { QueryPerformanceCounter(&mut precise_time)? };
Ok(format!("unnamed_{precise_time:x}"))
}
pub fn atom(&self) -> u16 {
self.atom
}
extern "system" fn base_wnd_proc(
hwnd: HWND,
msg_id: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
let mut user_data = unsafe {
SetLastError(ERROR_SUCCESS);
GetWindowLongPtrW(hwnd, GWLP_USERDATA)
};
if user_data == 0 {
user_data = NEXT_WINDOW_USER_DATA_ON_INIT.replace(0);
let result = Result::<(), windows::core::Error>::from_win32().and_then(|_| unsafe {
SetLastError(ERROR_SUCCESS);
SetWindowLongPtrW(hwnd, GWLP_USERDATA, user_data).nonzero_with_win32_or_err()
});
if result.is_err() {
return LRESULT(0);
}
};
let wnd_proc = unsafe { &mut *(user_data as *mut Box<dyn WndProc>) };
if let Some(lresult) = wnd_proc(hwnd, msg_id, wparam, lparam) {
lresult
} else {
unsafe { DefWindowProcW(hwnd, msg_id, wparam, lparam) }
}
}
}
impl Drop for WindowClass<'_> {
fn drop(&mut self) {
unsafe {
if let Ok(h_module) = GetModuleHandleW(PCWSTR::NULL) {
let result = UnregisterClassW(PCWSTR(self.atom as _), h_module);
debug_assert!(
result.is_ok(),
"couldn't unregister window class (did you adhere to proper drop order?): {result:?}"
);
}
drop(Box::from_raw(self.wnd_proc_ptr));
}
}
}
pub struct Window {
hwnd: HWND,
}
impl Window {
pub fn new_msg_only(class: &WindowClass) -> windows::core::Result<Self> {
Self::with_details(
class,
Some(HWND_MESSAGE),
WINDOW_STYLE(0),
None,
None,
None,
None,
)
}
pub fn new_invisible(class: &WindowClass) -> windows::core::Result<Self> {
Self::with_details(
class,
None,
WINDOW_STYLE(0),
None,
Some((POINT::zeroed(), SIZE::zeroed())),
None,
None,
)
}
pub fn with_details(
class: &WindowClass,
parent: Option<HWND>,
style: WINDOW_STYLE,
ex_style: Option<WINDOW_EX_STYLE>,
placement: Option<(POINT, SIZE)>,
text: Option<PCWSTR>,
menu: Option<HMENU>,
) -> windows::core::Result<Self> {
NEXT_WINDOW_USER_DATA_ON_INIT.set(class.wnd_proc_ptr as _);
let (pos, size) = placement.unwrap_or((
POINT {
x: CW_USEDEFAULT,
y: CW_USEDEFAULT,
},
SIZE {
cx: CW_USEDEFAULT,
cy: CW_USEDEFAULT,
},
));
let hwnd = unsafe {
CreateWindowExW(
ex_style.unwrap_or(WINDOW_EX_STYLE(0)),
PCWSTR(class.atom as _),
text.unwrap_or(PCWSTR::NULL),
style,
pos.x,
pos.y,
size.cx,
size.cy,
parent.unwrap_or(HWND::NULL),
menu.unwrap_or(HMENU::NULL),
GetModuleHandleW(PCWSTR::NULL)?,
None,
)
};
#[cfg(any(feature = "windows_v0_48", feature = "windows_v0_52"))]
let hwnd = hwnd.nonnull_or_e_handle()?; #[cfg(not(any(feature = "windows_v0_48", feature = "windows_v0_52")))]
let hwnd = hwnd?;
Ok(Self { hwnd })
}
pub fn hwnd(&self) -> HWND {
self.hwnd
}
pub fn is_valid(&self) -> bool {
unsafe { IsWindow(self.hwnd) }.as_bool()
}
}
impl Drop for Window {
fn drop(&mut self) {
let _ = unsafe { DestroyWindow(self.hwnd) };
}
}
#[cfg(all(test, feature = "windows_latest_compatible_all"))]
mod tests {
use super::{Window, WindowClass};
use crate::{foundation::LParamExt, win32_app::msg_loop, windows, Null};
use std::{cell::RefCell, rc::Rc};
use windows::{
core::{w, HSTRING, PCWSTR},
Win32::{
Foundation::{HWND, LRESULT, POINT, SIZE},
UI::WindowsAndMessaging::{
MessageBoxW, PostQuitMessage, MB_OK, MINMAXINFO, WM_DESTROY, WM_GETMINMAXINFO,
WM_LBUTTONUP, WS_OVERLAPPEDWINDOW, WS_VISIBLE,
},
},
};
#[ignore]
#[test]
fn create_window() -> windows::core::Result<()> {
let counter = Rc::new(RefCell::new(1));
let class = WindowClass::new(|hwnd, msg_id, wparam, mut lparam| {
println!("window msg received: {hwnd:?}, msg 0x{msg_id:04x}, {wparam:?}, {lparam:?}");
match msg_id {
WM_LBUTTONUP => {
*counter.borrow_mut() += 1;
unsafe {
MessageBoxW(
HWND::NULL,
PCWSTR(HSTRING::from(format!("{counter:?}")).as_ptr()),
w!("Message Box"),
MB_OK,
)
};
Some(LRESULT(0))
}
WM_GETMINMAXINFO => {
let min_max_info = unsafe { lparam.cast_to_mut::<MINMAXINFO>() };
min_max_info.ptMaxTrackSize = POINT { x: 300, y: 300 };
Some(LRESULT(0))
}
WM_DESTROY => {
unsafe { PostQuitMessage(0) };
Some(LRESULT(0))
}
_ => None,
}
})?;
*counter.borrow_mut() += 1;
let _window = Window::with_details(
&class,
None,
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
None,
Some((POINT { x: 100, y: 100 }, SIZE { cx: 500, cy: 500 })),
Some(PCWSTR(HSTRING::from("Test Window").as_ptr())),
None,
)?;
*counter.borrow_mut() += 1;
msg_loop::run()?;
Ok(())
}
}