use crate::bindings::*;
use std::sync::OnceLock;
use windows_core::*;
type MessageHandler = Box<dyn FnMut(*mut core::ffi::c_void, u32, usize, isize) -> Option<isize>>;
type ResizeHandler = Box<dyn FnMut(i32, i32)>;
struct State {
message: Option<MessageHandler>,
resize: Option<ResizeHandler>,
}
pub struct Window(HWND);
impl Window {
#[allow(clippy::new_ret_no_self)]
pub fn new(title: &str) -> WindowBuilder {
WindowBuilder {
title: title.to_string(),
width: CW_USEDEFAULT,
height: CW_USEDEFAULT,
style: WS_OVERLAPPEDWINDOW as u32,
ex_style: 0,
state: State {
message: None,
resize: None,
},
}
}
pub fn hwnd(&self) -> *mut core::ffi::c_void {
self.0
}
pub fn client_size(&self) -> (i32, i32) {
let mut rect = RECT::default();
unsafe {
if GetClientRect(self.0, &mut rect).as_bool() {
(rect.right - rect.left, rect.bottom - rect.top)
} else {
(0, 0)
}
}
}
}
impl Drop for Window {
fn drop(&mut self) {
unsafe {
if IsWindow(self.0).as_bool() {
_ = DestroyWindow(self.0);
}
}
}
}
pub struct WindowBuilder {
title: String,
width: i32,
height: i32,
style: u32,
ex_style: u32,
state: State,
}
impl WindowBuilder {
pub fn size(mut self, width: i32, height: i32) -> Self {
self.width = width;
self.height = height;
self
}
pub fn style(mut self, style: u32) -> Self {
self.style = style;
self
}
pub fn ex_style(mut self, ex_style: u32) -> Self {
self.ex_style = ex_style;
self
}
pub fn on_message<F>(mut self, handler: F) -> Self
where
F: FnMut(*mut core::ffi::c_void, u32, usize, isize) -> Option<isize> + 'static,
{
self.state.message = Some(Box::new(handler));
self
}
pub fn on_resize<F>(mut self, handler: F) -> Self
where
F: FnMut(i32, i32) + 'static,
{
self.state.resize = Some(Box::new(handler));
self
}
pub fn create(self) -> Result<Window> {
unsafe {
register_class();
let mut title: Vec<u16> = self.title.encode_utf16().collect();
title.push(0);
let hwnd = CreateWindowExW(
self.ex_style,
class_name(),
PCWSTR(title.as_ptr()),
self.style,
CW_USEDEFAULT,
CW_USEDEFAULT,
self.width,
self.height,
core::ptr::null_mut(),
core::ptr::null_mut(),
core::ptr::null_mut(),
core::ptr::null(),
);
if hwnd.is_null() {
return Err(Error::from_thread());
}
let state = Box::new(self.state);
SetWindowLongPtrW(hwnd, GWLP_USERDATA, Box::into_raw(state) as _);
_ = ShowWindow(hwnd, SW_SHOWNORMAL);
Ok(Window(hwnd))
}
}
}
pub fn run() {
unsafe {
let mut message = MSG::default();
while GetMessageW(&mut message, core::ptr::null_mut(), 0, 0).as_bool() {
_ = TranslateMessage(&message);
DispatchMessageW(&message);
}
}
}
pub fn run_with<F>(mut render: F) -> Result<()>
where
F: FnMut() -> Result<bool>,
{
unsafe {
let mut message = MSG::default();
let mut animating = true;
loop {
if animating {
while PeekMessageW(&mut message, core::ptr::null_mut(), 0, 0, PM_REMOVE as u32)
.as_bool()
{
if message.message == WM_QUIT as u32 {
return Ok(());
}
_ = TranslateMessage(&message);
DispatchMessageW(&message);
}
} else if GetMessageW(&mut message, core::ptr::null_mut(), 0, 0).as_bool() {
if message.message == WM_QUIT as u32 {
return Ok(());
}
_ = TranslateMessage(&message);
DispatchMessageW(&message);
} else {
return Ok(());
}
animating = render()?;
}
}
}
pub fn quit() {
unsafe { PostQuitMessage(0) };
}
pub fn pump() -> bool {
unsafe {
let mut message = MSG::default();
while PeekMessageW(&mut message, core::ptr::null_mut(), 0, 0, PM_REMOVE as u32).as_bool() {
if message.message == WM_QUIT as u32 {
return false;
}
_ = TranslateMessage(&message);
DispatchMessageW(&message);
}
true
}
}
fn class_name() -> PCWSTR {
static NAME: OnceLock<Vec<u16>> = OnceLock::new();
let name = NAME.get_or_init(|| "windows-window.Window\0".encode_utf16().collect());
PCWSTR(name.as_ptr())
}
unsafe fn register_class() {
static REGISTER: OnceLock<()> = OnceLock::new();
REGISTER.get_or_init(|| unsafe {
_ = SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
let wc = WNDCLASSW {
style: (CS_HREDRAW | CS_VREDRAW) as u32,
lpfnWndProc: Some(wndproc),
hCursor: LoadCursorW(core::ptr::null_mut(), IDC_ARROW),
lpszClassName: class_name(),
..Default::default()
};
RegisterClassW(&wc);
});
}
unsafe extern "system" fn wndproc(
hwnd: HWND,
message: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
unsafe {
let state = GetWindowLongPtrW(hwnd, GWLP_USERDATA) as *mut State;
let mut handled = None;
if !state.is_null() {
let mut message_handler = (*state).message.take();
let mut resize_handler = (*state).resize.take();
if let Some(handler) = message_handler.as_mut() {
handled = handler(hwnd, message, wparam, lparam);
}
if handled.is_none()
&& message == WM_SIZE as u32
&& let Some(handler) = resize_handler.as_mut()
{
let width = (lparam & 0xffff) as i32;
let height = ((lparam >> 16) & 0xffff) as i32;
handler(width, height);
handled = Some(0);
}
let state = GetWindowLongPtrW(hwnd, GWLP_USERDATA) as *mut State;
if !state.is_null() {
(*state).message = message_handler;
(*state).resize = resize_handler;
}
}
if message == WM_NCDESTROY as u32 {
let state = GetWindowLongPtrW(hwnd, GWLP_USERDATA) as *mut State;
if !state.is_null() {
SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0);
drop(Box::from_raw(state));
}
}
if let Some(result) = handled {
return result;
}
match message as i32 {
WM_DESTROY => {
PostQuitMessage(0);
0
}
_ => DefWindowProcW(hwnd, message, wparam, lparam),
}
}
}