1use crate::bindings::*;
2use std::sync::OnceLock;
3use windows_core::*;
4
5type MessageHandler = Box<dyn FnMut(*mut core::ffi::c_void, u32, usize, isize) -> Option<isize>>;
9
10type ResizeHandler = Box<dyn FnMut(i32, i32)>;
12
13struct State {
14 message: Option<MessageHandler>,
15 resize: Option<ResizeHandler>,
16}
17
18pub struct Window(HWND);
23
24impl Window {
25 #[allow(clippy::new_ret_no_self)]
27 pub fn new(title: &str) -> WindowBuilder {
28 WindowBuilder {
29 title: title.to_string(),
30 width: CW_USEDEFAULT,
31 height: CW_USEDEFAULT,
32 style: WS_OVERLAPPEDWINDOW as u32,
33 ex_style: 0,
34 state: State {
35 message: None,
36 resize: None,
37 },
38 }
39 }
40
41 pub fn hwnd(&self) -> *mut core::ffi::c_void {
43 self.0
44 }
45
46 pub fn client_size(&self) -> (i32, i32) {
48 let mut rect = RECT::default();
49 unsafe {
50 if GetClientRect(self.0, &mut rect).as_bool() {
51 (rect.right - rect.left, rect.bottom - rect.top)
52 } else {
53 (0, 0)
54 }
55 }
56 }
57}
58
59impl Drop for Window {
60 fn drop(&mut self) {
61 unsafe {
62 if IsWindow(self.0).as_bool() {
63 _ = DestroyWindow(self.0);
64 }
65 }
66 }
67}
68
69pub struct WindowBuilder {
71 title: String,
72 width: i32,
73 height: i32,
74 style: u32,
75 ex_style: u32,
76 state: State,
77}
78
79impl WindowBuilder {
80 pub fn size(mut self, width: i32, height: i32) -> Self {
82 self.width = width;
83 self.height = height;
84 self
85 }
86
87 pub fn style(mut self, style: u32) -> Self {
89 self.style = style;
90 self
91 }
92
93 pub fn ex_style(mut self, ex_style: u32) -> Self {
95 self.ex_style = ex_style;
96 self
97 }
98
99 pub fn on_message<F>(mut self, handler: F) -> Self
102 where
103 F: FnMut(*mut core::ffi::c_void, u32, usize, isize) -> Option<isize> + 'static,
104 {
105 self.state.message = Some(Box::new(handler));
106 self
107 }
108
109 pub fn on_resize<F>(mut self, handler: F) -> Self
112 where
113 F: FnMut(i32, i32) + 'static,
114 {
115 self.state.resize = Some(Box::new(handler));
116 self
117 }
118
119 pub fn create(self) -> Result<Window> {
121 unsafe {
122 register_class();
123
124 let mut title: Vec<u16> = self.title.encode_utf16().collect();
125 title.push(0);
126
127 let hwnd = CreateWindowExW(
128 self.ex_style,
129 class_name(),
130 PCWSTR(title.as_ptr()),
131 self.style,
132 CW_USEDEFAULT,
133 CW_USEDEFAULT,
134 self.width,
135 self.height,
136 core::ptr::null_mut(),
137 core::ptr::null_mut(),
138 core::ptr::null_mut(),
139 core::ptr::null(),
140 );
141
142 if hwnd.is_null() {
143 return Err(Error::from_thread());
144 }
145
146 let state = Box::new(self.state);
147 SetWindowLongPtrW(hwnd, GWLP_USERDATA, Box::into_raw(state) as _);
148
149 _ = ShowWindow(hwnd, SW_SHOWNORMAL);
150 Ok(Window(hwnd))
151 }
152 }
153}
154
155pub fn run() {
157 unsafe {
158 let mut message = MSG::default();
159 while GetMessageW(&mut message, core::ptr::null_mut(), 0, 0).as_bool() {
160 _ = TranslateMessage(&message);
161 DispatchMessageW(&message);
162 }
163 }
164}
165
166pub fn run_with<F>(mut render: F) -> Result<()>
172where
173 F: FnMut() -> Result<bool>,
174{
175 unsafe {
176 let mut message = MSG::default();
177 let mut animating = true;
178 loop {
179 if animating {
180 while PeekMessageW(&mut message, core::ptr::null_mut(), 0, 0, PM_REMOVE as u32)
181 .as_bool()
182 {
183 if message.message == WM_QUIT as u32 {
184 return Ok(());
185 }
186 _ = TranslateMessage(&message);
187 DispatchMessageW(&message);
188 }
189 } else if GetMessageW(&mut message, core::ptr::null_mut(), 0, 0).as_bool() {
190 if message.message == WM_QUIT as u32 {
191 return Ok(());
192 }
193 _ = TranslateMessage(&message);
194 DispatchMessageW(&message);
195 } else {
196 return Ok(());
197 }
198 animating = render()?;
199 }
200 }
201}
202
203pub fn quit() {
205 unsafe { PostQuitMessage(0) };
206}
207
208pub fn pump() -> bool {
216 unsafe {
217 let mut message = MSG::default();
218 while PeekMessageW(&mut message, core::ptr::null_mut(), 0, 0, PM_REMOVE as u32).as_bool() {
219 if message.message == WM_QUIT as u32 {
220 return false;
221 }
222 _ = TranslateMessage(&message);
223 DispatchMessageW(&message);
224 }
225 true
226 }
227}
228
229fn class_name() -> PCWSTR {
230 static NAME: OnceLock<Vec<u16>> = OnceLock::new();
231 let name = NAME.get_or_init(|| "windows-window.Window\0".encode_utf16().collect());
232 PCWSTR(name.as_ptr())
233}
234
235unsafe fn register_class() {
236 static REGISTER: OnceLock<()> = OnceLock::new();
237 REGISTER.get_or_init(|| unsafe {
238 _ = SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
239 let wc = WNDCLASSW {
240 style: (CS_HREDRAW | CS_VREDRAW) as u32,
241 lpfnWndProc: Some(wndproc),
242 hCursor: LoadCursorW(core::ptr::null_mut(), IDC_ARROW),
243 lpszClassName: class_name(),
244 ..Default::default()
245 };
246 RegisterClassW(&wc);
247 });
248}
249
250unsafe extern "system" fn wndproc(
251 hwnd: HWND,
252 message: u32,
253 wparam: WPARAM,
254 lparam: LPARAM,
255) -> LRESULT {
256 unsafe {
257 let state = GetWindowLongPtrW(hwnd, GWLP_USERDATA) as *mut State;
258 let mut handled = None;
259
260 if !state.is_null() {
261 let mut message_handler = (*state).message.take();
266 let mut resize_handler = (*state).resize.take();
267
268 if let Some(handler) = message_handler.as_mut() {
273 handled = handler(hwnd, message, wparam, lparam);
274 }
275
276 if handled.is_none()
277 && message == WM_SIZE as u32
278 && let Some(handler) = resize_handler.as_mut()
279 {
280 let width = (lparam & 0xffff) as i32;
281 let height = ((lparam >> 16) & 0xffff) as i32;
282 handler(width, height);
283 handled = Some(0);
284 }
285
286 let state = GetWindowLongPtrW(hwnd, GWLP_USERDATA) as *mut State;
293 if !state.is_null() {
294 (*state).message = message_handler;
295 (*state).resize = resize_handler;
296 }
297 }
298
299 if message == WM_NCDESTROY as u32 {
300 let state = GetWindowLongPtrW(hwnd, GWLP_USERDATA) as *mut State;
301 if !state.is_null() {
302 SetWindowLongPtrW(hwnd, GWLP_USERDATA, 0);
303 drop(Box::from_raw(state));
304 }
305 }
306
307 if let Some(result) = handled {
308 return result;
309 }
310
311 match message as i32 {
312 WM_DESTROY => {
313 PostQuitMessage(0);
314 0
315 }
316 _ => DefWindowProcW(hwnd, message, wparam, lparam),
317 }
318 }
319}