1#![deny(unsafe_op_in_unsafe_fn)]
2
3use std::{
4 cell::{Cell, RefCell},
5 iter::once,
6 num::NonZeroIsize,
7 path::PathBuf,
8 rc::{Rc, Weak},
9 str::FromStr,
10 sync::{Arc, Once},
11 time::{Duration, Instant},
12};
13
14use ::util::ResultExt;
15use anyhow::Context;
16use blade_graphics as gpu;
17use futures::channel::oneshot::{self, Receiver};
18use itertools::Itertools;
19use raw_window_handle as rwh;
20use smallvec::SmallVec;
21use std::result::Result;
22use windows::{
23 core::*,
24 Win32::{
25 Foundation::*,
26 Graphics::Gdi::*,
27 System::{Com::*, LibraryLoader::*, Ole::*, SystemServices::*},
28 UI::{
29 Controls::*,
30 HiDpi::*,
31 Input::{Ime::*, KeyboardAndMouse::*},
32 Shell::*,
33 WindowsAndMessaging::*,
34 },
35 },
36};
37
38use crate::platform::blade::{BladeRenderer, BladeSurfaceConfig};
39use crate::*;
40
41pub(crate) struct WindowsWindowInner {
42 hwnd: HWND,
43 origin: Cell<Point<DevicePixels>>,
44 physical_size: Cell<Size<DevicePixels>>,
45 scale_factor: Cell<f32>,
46 input_handler: Cell<Option<PlatformInputHandler>>,
47 renderer: RefCell<BladeRenderer>,
48 callbacks: RefCell<Callbacks>,
49 platform_inner: Rc<WindowsPlatformInner>,
50 pub(crate) handle: AnyWindowHandle,
51 hide_title_bar: bool,
52 display: RefCell<Rc<WindowsDisplay>>,
53 click_state: RefCell<ClickState>,
54 fullscreen: Cell<Option<StyleAndBounds>>,
55}
56
57impl WindowsWindowInner {
58 fn new(
59 hwnd: HWND,
60 cs: &CREATESTRUCTW,
61 platform_inner: Rc<WindowsPlatformInner>,
62 handle: AnyWindowHandle,
63 hide_title_bar: bool,
64 display: Rc<WindowsDisplay>,
65 transparent: bool,
66 ) -> Self {
67 let monitor_dpi = unsafe { GetDpiForWindow(hwnd) } as f32;
68 let origin = Cell::new(Point {
69 x: DevicePixels(cs.x),
70 y: DevicePixels(cs.y),
71 });
72 let physical_size = Cell::new(Size {
73 width: DevicePixels(cs.cx),
74 height: DevicePixels(cs.cy),
75 });
76 let scale_factor = Cell::new(monitor_dpi / USER_DEFAULT_SCREEN_DPI as f32);
77 let input_handler = Cell::new(None);
78 struct RawWindow {
79 hwnd: isize,
80 }
81 impl rwh::HasWindowHandle for RawWindow {
82 fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
83 Ok(unsafe {
84 let hwnd = NonZeroIsize::new_unchecked(self.hwnd);
85 let mut handle = rwh::Win32WindowHandle::new(hwnd);
86 let hinstance = get_window_long(HWND(self.hwnd), GWLP_HINSTANCE);
87 handle.hinstance = NonZeroIsize::new(hinstance);
88 rwh::WindowHandle::borrow_raw(handle.into())
89 })
90 }
91 }
92 impl rwh::HasDisplayHandle for RawWindow {
93 fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
94 let handle = rwh::WindowsDisplayHandle::new();
95 Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
96 }
97 }
98
99 let raw = RawWindow { hwnd: hwnd.0 };
100 let gpu = Arc::new(
101 unsafe {
102 gpu::Context::init_windowed(
103 &raw,
104 gpu::ContextDesc {
105 validation: false,
106 capture: false,
107 overlay: false,
108 },
109 )
110 }
111 .unwrap(),
112 );
113 let config = BladeSurfaceConfig {
114 size: gpu::Extent::default(),
115 transparent,
116 };
117 let renderer = RefCell::new(BladeRenderer::new(gpu, config));
118 let callbacks = RefCell::new(Callbacks::default());
119 let display = RefCell::new(display);
120 let click_state = RefCell::new(ClickState::new());
121 let fullscreen = Cell::new(None);
122 Self {
123 hwnd,
124 origin,
125 physical_size,
126 scale_factor,
127 input_handler,
128 renderer,
129 callbacks,
130 platform_inner,
131 handle,
132 hide_title_bar,
133 display,
134 click_state,
135 fullscreen,
136 }
137 }
138
139 fn is_maximized(&self) -> bool {
140 !self.is_fullscreen() && unsafe { IsZoomed(self.hwnd) }.as_bool()
141 }
142
143 fn is_minimized(&self) -> bool {
144 unsafe { IsIconic(self.hwnd) }.as_bool()
145 }
146
147 fn is_fullscreen(&self) -> bool {
148 let fullscreen = self.fullscreen.take();
149 let is_fullscreen = fullscreen.is_some();
150 self.fullscreen.set(fullscreen);
151 is_fullscreen
152 }
153
154 async fn toggle_fullscreen(self: Rc<Self>) {
155 let StyleAndBounds {
156 style,
157 x,
158 y,
159 cx,
160 cy,
161 } = if let Some(state) = self.fullscreen.take() {
162 state
163 } else {
164 let style = WINDOW_STYLE(unsafe { get_window_long(self.hwnd, GWL_STYLE) } as _);
165 let mut rc = RECT::default();
166 unsafe { GetWindowRect(self.hwnd, &mut rc) }.log_err();
167 self.fullscreen.set(Some(StyleAndBounds {
168 style,
169 x: rc.left,
170 y: rc.top,
171 cx: rc.right - rc.left,
172 cy: rc.bottom - rc.top,
173 }));
174 let style = style
175 & !(WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_CAPTION);
176 let bounds = self.display.borrow().clone().bounds();
177 StyleAndBounds {
178 style,
179 x: bounds.left().0,
180 y: bounds.top().0,
181 cx: bounds.size.width.0,
182 cy: bounds.size.height.0,
183 }
184 };
185 unsafe { set_window_long(self.hwnd, GWL_STYLE, style.0 as isize) };
186 unsafe {
187 SetWindowPos(
188 self.hwnd,
189 HWND::default(),
190 x,
191 y,
192 cx,
193 cy,
194 SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOZORDER,
195 )
196 }
197 .log_err();
198 }
199
200 pub(crate) fn title_bar_padding(&self) -> Pixels {
201 // using USER_DEFAULT_SCREEN_DPI because GPUI handles the scale with the scale factor
202 let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, USER_DEFAULT_SCREEN_DPI) };
203 px(padding as f32)
204 }
205
206 pub(crate) fn title_bar_top_offset(&self) -> Pixels {
207 if self.is_maximized() {
208 self.title_bar_padding() * 2
209 } else {
210 px(0.)
211 }
212 }
213
214 pub(crate) fn title_bar_height(&self) -> Pixels {
215 // todo(windows) this is hard set to match the ui title bar
216 // in the future the ui title bar component will report the size
217 px(32.) + self.title_bar_top_offset()
218 }
219
220 pub(crate) fn caption_button_width(&self) -> Pixels {
221 // todo(windows) this is hard set to match the ui title bar
222 // in the future the ui title bar component will report the size
223 px(36.)
224 }
225
226 fn get_titlebar_rect(&self) -> anyhow::Result<RECT> {
227 let height = self.title_bar_height();
228 let mut rect = RECT::default();
229 unsafe { GetClientRect(self.hwnd, &mut rect) }?;
230 rect.bottom = rect.top + ((height.0 * self.scale_factor.get()).round() as i32);
231 Ok(rect)
232 }
233
234 fn is_virtual_key_pressed(&self, vkey: VIRTUAL_KEY) -> bool {
235 unsafe { GetKeyState(vkey.0 as i32) < 0 }
236 }
237
238 fn current_modifiers(&self) -> Modifiers {
239 Modifiers {
240 control: self.is_virtual_key_pressed(VK_CONTROL),
241 alt: self.is_virtual_key_pressed(VK_MENU),
242 shift: self.is_virtual_key_pressed(VK_SHIFT),
243 platform: self.is_virtual_key_pressed(VK_LWIN) || self.is_virtual_key_pressed(VK_RWIN),
244 function: false,
245 }
246 }
247
248 /// mark window client rect to be re-drawn
249 /// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-invalidaterect
250 pub(crate) fn invalidate_client_area(&self) {
251 unsafe { InvalidateRect(self.hwnd, None, FALSE) };
252 }
253
254 fn handle_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
255 let handled = match msg {
256 WM_ACTIVATE => self.handle_activate_msg(wparam),
257 WM_CREATE => self.handle_create_msg(lparam),
258 WM_MOVE => self.handle_move_msg(lparam),
259 WM_SIZE => self.handle_size_msg(lparam),
260 WM_ENTERSIZEMOVE | WM_ENTERMENULOOP => self.handle_size_move_loop(),
261 WM_EXITSIZEMOVE | WM_EXITMENULOOP => self.handle_size_move_loop_exit(),
262 WM_TIMER => self.handle_timer_msg(wparam),
263 WM_NCCALCSIZE => self.handle_calc_client_size(wparam, lparam),
264 WM_DPICHANGED => self.handle_dpi_changed_msg(wparam, lparam),
265 WM_NCHITTEST => self.handle_hit_test_msg(msg, wparam, lparam),
266 WM_PAINT => self.handle_paint_msg(),
267 WM_CLOSE => self.handle_close_msg(),
268 WM_DESTROY => self.handle_destroy_msg(),
269 WM_MOUSEMOVE => self.handle_mouse_move_msg(lparam, wparam),
270 WM_NCMOUSEMOVE => self.handle_nc_mouse_move_msg(lparam),
271 WM_NCLBUTTONDOWN => self.handle_nc_mouse_down_msg(MouseButton::Left, wparam, lparam),
272 WM_NCRBUTTONDOWN => self.handle_nc_mouse_down_msg(MouseButton::Right, wparam, lparam),
273 WM_NCMBUTTONDOWN => self.handle_nc_mouse_down_msg(MouseButton::Middle, wparam, lparam),
274 WM_NCLBUTTONUP => self.handle_nc_mouse_up_msg(MouseButton::Left, wparam, lparam),
275 WM_NCRBUTTONUP => self.handle_nc_mouse_up_msg(MouseButton::Right, wparam, lparam),
276 WM_NCMBUTTONUP => self.handle_nc_mouse_up_msg(MouseButton::Middle, wparam, lparam),
277 WM_LBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Left, lparam),
278 WM_RBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Right, lparam),
279 WM_MBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Middle, lparam),
280 WM_XBUTTONDOWN => self.handle_xbutton_msg(wparam, lparam, Self::handle_mouse_down_msg),
281 WM_LBUTTONUP => self.handle_mouse_up_msg(MouseButton::Left, lparam),
282 WM_RBUTTONUP => self.handle_mouse_up_msg(MouseButton::Right, lparam),
283 WM_MBUTTONUP => self.handle_mouse_up_msg(MouseButton::Middle, lparam),
284 WM_XBUTTONUP => self.handle_xbutton_msg(wparam, lparam, Self::handle_mouse_up_msg),
285 WM_MOUSEWHEEL => self.handle_mouse_wheel_msg(wparam, lparam),
286 WM_MOUSEHWHEEL => self.handle_mouse_horizontal_wheel_msg(wparam, lparam),
287 WM_SYSKEYDOWN => self.handle_syskeydown_msg(wparam, lparam),
288 WM_SYSKEYUP => self.handle_syskeyup_msg(wparam),
289 WM_KEYDOWN => self.handle_keydown_msg(msg, wparam, lparam),
290 WM_KEYUP => self.handle_keyup_msg(msg, wparam),
291 WM_CHAR => self.handle_char_msg(msg, wparam, lparam),
292 WM_IME_STARTCOMPOSITION => self.handle_ime_position(),
293 WM_IME_COMPOSITION => self.handle_ime_composition(lparam),
294 WM_SETCURSOR => self.handle_set_cursor(lparam),
295 _ => None,
296 };
297 if let Some(n) = handled {
298 LRESULT(n)
299 } else {
300 unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) }
301 }
302 }
303
304 fn handle_move_msg(&self, lparam: LPARAM) -> Option<isize> {
305 let x = lparam.signed_loword() as i32;
306 let y = lparam.signed_hiword() as i32;
307 self.origin.set(Point {
308 x: DevicePixels(x),
309 y: DevicePixels(y),
310 });
311 let size = self.physical_size.get();
312 let center_x = x + size.width.0 / 2;
313 let center_y = y + size.height.0 / 2;
314 let monitor_bounds = self.display.borrow().bounds();
315 if center_x < monitor_bounds.left().0
316 || center_x > monitor_bounds.right().0
317 || center_y < monitor_bounds.top().0
318 || center_y > monitor_bounds.bottom().0
319 {
320 // center of the window may have moved to another monitor
321 let monitor = unsafe { MonitorFromWindow(self.hwnd, MONITOR_DEFAULTTONULL) };
322 if !monitor.is_invalid() && self.display.borrow().handle != monitor {
323 // we will get the same monitor if we only have one
324 (*self.display.borrow_mut()) = Rc::new(WindowsDisplay::new_with_handle(monitor));
325 }
326 }
327 let mut callbacks = self.callbacks.borrow_mut();
328 if let Some(callback) = callbacks.moved.as_mut() {
329 callback()
330 }
331 Some(0)
332 }
333
334 fn handle_size_msg(&self, lparam: LPARAM) -> Option<isize> {
335 let width = lparam.loword().max(1) as i32;
336 let height = lparam.hiword().max(1) as i32;
337 let scale_factor = self.scale_factor.get();
338 let new_physical_size = Size {
339 width: DevicePixels(width),
340 height: DevicePixels(height),
341 };
342 self.physical_size.set(new_physical_size);
343 self.renderer.borrow_mut().update_drawable_size(Size {
344 width: width as f64,
345 height: height as f64,
346 });
347 let mut callbacks = self.callbacks.borrow_mut();
348 if let Some(callback) = callbacks.resize.as_mut() {
349 let logical_size = logical_size(new_physical_size, scale_factor);
350 callback(logical_size, scale_factor);
351 }
352 Some(0)
353 }
354
355 fn handle_size_move_loop(&self) -> Option<isize> {
356 unsafe {
357 let ret = SetTimer(self.hwnd, SIZE_MOVE_LOOP_TIMER_ID, USER_TIMER_MINIMUM, None);
358 if ret == 0 {
359 log::error!(
360 "unable to create timer: {}",
361 std::io::Error::last_os_error()
362 );
363 }
364 }
365 None
366 }
367
368 fn handle_size_move_loop_exit(&self) -> Option<isize> {
369 unsafe {
370 KillTimer(self.hwnd, SIZE_MOVE_LOOP_TIMER_ID).log_err();
371 }
372 None
373 }
374
375 fn handle_timer_msg(&self, wparam: WPARAM) -> Option<isize> {
376 if wparam.0 == SIZE_MOVE_LOOP_TIMER_ID {
377 self.platform_inner.run_foreground_tasks();
378 self.handle_paint_msg();
379 return Some(0);
380 }
381 None
382 }
383
384 fn handle_paint_msg(&self) -> Option<isize> {
385 let mut paint_struct = PAINTSTRUCT::default();
386 let _hdc = unsafe { BeginPaint(self.hwnd, &mut paint_struct) };
387 let mut callbacks = self.callbacks.borrow_mut();
388 if let Some(request_frame) = callbacks.request_frame.as_mut() {
389 request_frame();
390 }
391 unsafe { EndPaint(self.hwnd, &paint_struct) };
392 Some(0)
393 }
394
395 fn handle_close_msg(&self) -> Option<isize> {
396 let mut callbacks = self.callbacks.borrow_mut();
397 if let Some(callback) = callbacks.should_close.as_mut() {
398 if callback() {
399 return Some(0);
400 }
401 }
402 None
403 }
404
405 fn handle_destroy_msg(&self) -> Option<isize> {
406 let mut callbacks = self.callbacks.borrow_mut();
407 if let Some(callback) = callbacks.close.take() {
408 callback()
409 }
410 let index = self
411 .platform_inner
412 .raw_window_handles
413 .read()
414 .iter()
415 .position(|handle| *handle == self.hwnd)
416 .unwrap();
417 self.platform_inner.raw_window_handles.write().remove(index);
418 if self.platform_inner.raw_window_handles.read().is_empty() {
419 self.platform_inner
420 .foreground_executor
421 .spawn(async {
422 unsafe { PostQuitMessage(0) };
423 })
424 .detach();
425 }
426 Some(1)
427 }
428
429 fn handle_mouse_move_msg(&self, lparam: LPARAM, wparam: WPARAM) -> Option<isize> {
430 let mut callbacks = self.callbacks.borrow_mut();
431 if let Some(callback) = callbacks.input.as_mut() {
432 let pressed_button = match MODIFIERKEYS_FLAGS(wparam.loword() as u32) {
433 flags if flags.contains(MK_LBUTTON) => Some(MouseButton::Left),
434 flags if flags.contains(MK_RBUTTON) => Some(MouseButton::Right),
435 flags if flags.contains(MK_MBUTTON) => Some(MouseButton::Middle),
436 flags if flags.contains(MK_XBUTTON1) => {
437 Some(MouseButton::Navigate(NavigationDirection::Back))
438 }
439 flags if flags.contains(MK_XBUTTON2) => {
440 Some(MouseButton::Navigate(NavigationDirection::Forward))
441 }
442 _ => None,
443 };
444 let x = lparam.signed_loword() as f32;
445 let y = lparam.signed_hiword() as f32;
446 let scale_factor = self.scale_factor.get();
447 let event = MouseMoveEvent {
448 position: logical_point(x, y, scale_factor),
449 pressed_button,
450 modifiers: self.current_modifiers(),
451 };
452 if callback(PlatformInput::MouseMove(event)).default_prevented {
453 return Some(0);
454 }
455 }
456 Some(1)
457 }
458
459 fn parse_syskeydown_msg_keystroke(&self, wparam: WPARAM) -> Option<Keystroke> {
460 let modifiers = self.current_modifiers();
461 if !modifiers.alt {
462 // on Windows, F10 can trigger this event, not just the alt key
463 // and we just don't care about F10
464 return None;
465 }
466
467 let vk_code = wparam.loword();
468 let basic_key = basic_vkcode_to_string(vk_code, modifiers);
469 if basic_key.is_some() {
470 return basic_key;
471 }
472
473 let key = match VIRTUAL_KEY(vk_code) {
474 VK_BACK => Some("backspace"),
475 VK_RETURN => Some("enter"),
476 VK_TAB => Some("tab"),
477 VK_UP => Some("up"),
478 VK_DOWN => Some("down"),
479 VK_RIGHT => Some("right"),
480 VK_LEFT => Some("left"),
481 VK_HOME => Some("home"),
482 VK_END => Some("end"),
483 VK_PRIOR => Some("pageup"),
484 VK_NEXT => Some("pagedown"),
485 VK_ESCAPE => Some("escape"),
486 VK_INSERT => Some("insert"),
487 _ => None,
488 };
489
490 if let Some(key) = key {
491 Some(Keystroke {
492 modifiers,
493 key: key.to_string(),
494 ime_key: None,
495 })
496 } else {
497 None
498 }
499 }
500
501 fn parse_keydown_msg_keystroke(&self, wparam: WPARAM) -> Option<Keystroke> {
502 let vk_code = wparam.loword();
503
504 let modifiers = self.current_modifiers();
505 if modifiers.control || modifiers.alt {
506 let basic_key = basic_vkcode_to_string(vk_code, modifiers);
507 if basic_key.is_some() {
508 return basic_key;
509 }
510 }
511
512 if vk_code >= VK_F1.0 && vk_code <= VK_F24.0 {
513 let offset = vk_code - VK_F1.0;
514 return Some(Keystroke {
515 modifiers,
516 key: format!("f{}", offset + 1),
517 ime_key: None,
518 });
519 }
520
521 let key = match VIRTUAL_KEY(vk_code) {
522 VK_BACK => Some("backspace"),
523 VK_RETURN => Some("enter"),
524 VK_TAB => Some("tab"),
525 VK_UP => Some("up"),
526 VK_DOWN => Some("down"),
527 VK_RIGHT => Some("right"),
528 VK_LEFT => Some("left"),
529 VK_HOME => Some("home"),
530 VK_END => Some("end"),
531 VK_PRIOR => Some("pageup"),
532 VK_NEXT => Some("pagedown"),
533 VK_ESCAPE => Some("escape"),
534 VK_INSERT => Some("insert"),
535 VK_DELETE => Some("delete"),
536 _ => None,
537 };
538
539 if let Some(key) = key {
540 Some(Keystroke {
541 modifiers,
542 key: key.to_string(),
543 ime_key: None,
544 })
545 } else {
546 None
547 }
548 }
549
550 fn parse_char_msg_keystroke(&self, wparam: WPARAM) -> Option<Keystroke> {
551 let src = [wparam.0 as u16];
552 let Ok(first_char) = char::decode_utf16(src).collect::<Vec<_>>()[0] else {
553 return None;
554 };
555 if first_char.is_control() {
556 None
557 } else {
558 let mut modifiers = self.current_modifiers();
559 // for characters that use 'shift' to type it is expected that the
560 // shift is not reported if the uppercase/lowercase are the same and instead only the key is reported
561 if first_char.to_lowercase().to_string() == first_char.to_uppercase().to_string() {
562 modifiers.shift = false;
563 }
564 let key = match first_char {
565 ' ' => "space".to_string(),
566 first_char => first_char.to_lowercase().to_string(),
567 };
568 Some(Keystroke {
569 modifiers,
570 key,
571 ime_key: Some(first_char.to_string()),
572 })
573 }
574 }
575
576 fn handle_syskeydown_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
577 // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
578 // shortcuts.
579 let Some(keystroke) = self.parse_syskeydown_msg_keystroke(wparam) else {
580 return None;
581 };
582 let Some(ref mut func) = self.callbacks.borrow_mut().input else {
583 return None;
584 };
585 let event = KeyDownEvent {
586 keystroke,
587 is_held: lparam.0 & (0x1 << 30) > 0,
588 };
589 if func(PlatformInput::KeyDown(event)).default_prevented {
590 self.invalidate_client_area();
591 return Some(0);
592 }
593 None
594 }
595
596 fn handle_syskeyup_msg(&self, wparam: WPARAM) -> Option<isize> {
597 // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
598 // shortcuts.
599 let Some(keystroke) = self.parse_syskeydown_msg_keystroke(wparam) else {
600 return None;
601 };
602 let Some(ref mut func) = self.callbacks.borrow_mut().input else {
603 return None;
604 };
605 let event = KeyUpEvent { keystroke };
606 if func(PlatformInput::KeyUp(event)).default_prevented {
607 self.invalidate_client_area();
608 return Some(0);
609 }
610 None
611 }
612
613 fn handle_keydown_msg(&self, _msg: u32, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
614 let Some(keystroke) = self.parse_keydown_msg_keystroke(wparam) else {
615 return Some(1);
616 };
617 let Some(ref mut func) = self.callbacks.borrow_mut().input else {
618 return Some(1);
619 };
620 let event = KeyDownEvent {
621 keystroke,
622 is_held: lparam.0 & (0x1 << 30) > 0,
623 };
624 if func(PlatformInput::KeyDown(event)).default_prevented {
625 self.invalidate_client_area();
626 return Some(0);
627 }
628 Some(1)
629 }
630
631 fn handle_keyup_msg(&self, _msg: u32, wparam: WPARAM) -> Option<isize> {
632 let Some(keystroke) = self.parse_keydown_msg_keystroke(wparam) else {
633 return Some(1);
634 };
635 let Some(ref mut func) = self.callbacks.borrow_mut().input else {
636 return Some(1);
637 };
638 let event = KeyUpEvent { keystroke };
639 if func(PlatformInput::KeyUp(event)).default_prevented {
640 self.invalidate_client_area();
641 return Some(0);
642 }
643 Some(1)
644 }
645
646 fn handle_char_msg(&self, _msg: u32, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
647 let Some(keystroke) = self.parse_char_msg_keystroke(wparam) else {
648 return Some(1);
649 };
650 let mut callbacks = self.callbacks.borrow_mut();
651 let Some(ref mut func) = callbacks.input else {
652 return Some(1);
653 };
654 let ime_key = keystroke.ime_key.clone();
655 let event = KeyDownEvent {
656 keystroke,
657 is_held: lparam.0 & (0x1 << 30) > 0,
658 };
659
660 let dispatch_event_result = func(PlatformInput::KeyDown(event));
661 if dispatch_event_result.default_prevented || !dispatch_event_result.propagate {
662 self.invalidate_client_area();
663 return Some(0);
664 }
665 drop(callbacks);
666 let Some(ime_char) = ime_key else {
667 return Some(1);
668 };
669 let Some(mut input_handler) = self.input_handler.take() else {
670 return Some(1);
671 };
672 input_handler.replace_text_in_range(None, &ime_char);
673 self.input_handler.set(Some(input_handler));
674 self.invalidate_client_area();
675 Some(0)
676 }
677
678 fn handle_mouse_down_msg(&self, button: MouseButton, lparam: LPARAM) -> Option<isize> {
679 let mut callbacks = self.callbacks.borrow_mut();
680 if let Some(callback) = callbacks.input.as_mut() {
681 let x = lparam.signed_loword() as f32;
682 let y = lparam.signed_hiword() as f32;
683 let physical_point = point(DevicePixels(x as i32), DevicePixels(y as i32));
684 let click_count = self.click_state.borrow_mut().update(button, physical_point);
685 let scale_factor = self.scale_factor.get();
686 let event = MouseDownEvent {
687 button,
688 position: logical_point(x, y, scale_factor),
689 modifiers: self.current_modifiers(),
690 click_count,
691 first_mouse: false,
692 };
693 if callback(PlatformInput::MouseDown(event)).default_prevented {
694 return Some(0);
695 }
696 }
697 Some(1)
698 }
699
700 fn handle_mouse_up_msg(&self, button: MouseButton, lparam: LPARAM) -> Option<isize> {
701 let mut callbacks = self.callbacks.borrow_mut();
702 if let Some(callback) = callbacks.input.as_mut() {
703 let x = lparam.signed_loword() as f32;
704 let y = lparam.signed_hiword() as f32;
705 let click_count = self.click_state.borrow().current_count;
706 let scale_factor = self.scale_factor.get();
707 let event = MouseUpEvent {
708 button,
709 position: logical_point(x, y, scale_factor),
710 modifiers: self.current_modifiers(),
711 click_count,
712 };
713 if callback(PlatformInput::MouseUp(event)).default_prevented {
714 return Some(0);
715 }
716 }
717 Some(1)
718 }
719
720 fn handle_xbutton_msg(
721 &self,
722 wparam: WPARAM,
723 lparam: LPARAM,
724 handler: impl Fn(&Self, MouseButton, LPARAM) -> Option<isize>,
725 ) -> Option<isize> {
726 let nav_dir = match wparam.hiword() {
727 XBUTTON1 => NavigationDirection::Back,
728 XBUTTON2 => NavigationDirection::Forward,
729 _ => return Some(1),
730 };
731 handler(self, MouseButton::Navigate(nav_dir), lparam)
732 }
733
734 fn handle_mouse_wheel_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
735 let mut callbacks = self.callbacks.borrow_mut();
736 if let Some(callback) = callbacks.input.as_mut() {
737 let wheel_distance = (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32)
738 * self.platform_inner.settings.borrow().wheel_scroll_lines as f32;
739 let mut cursor_point = POINT {
740 x: lparam.signed_loword().into(),
741 y: lparam.signed_hiword().into(),
742 };
743 unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
744 let scale_factor = self.scale_factor.get();
745 let event = crate::ScrollWheelEvent {
746 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
747 delta: ScrollDelta::Lines(Point {
748 x: 0.0,
749 y: wheel_distance,
750 }),
751 modifiers: self.current_modifiers(),
752 touch_phase: TouchPhase::Moved,
753 };
754 callback(PlatformInput::ScrollWheel(event));
755 return Some(0);
756 }
757 Some(1)
758 }
759
760 fn handle_mouse_horizontal_wheel_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
761 let mut callbacks = self.callbacks.borrow_mut();
762 if let Some(callback) = callbacks.input.as_mut() {
763 let wheel_distance = (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32)
764 * self.platform_inner.settings.borrow().wheel_scroll_chars as f32;
765 let mut cursor_point = POINT {
766 x: lparam.signed_loword().into(),
767 y: lparam.signed_hiword().into(),
768 };
769 unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
770 let scale_factor = self.scale_factor.get();
771 let event = crate::ScrollWheelEvent {
772 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
773 delta: ScrollDelta::Lines(Point {
774 x: wheel_distance,
775 y: 0.0,
776 }),
777 modifiers: self.current_modifiers(),
778 touch_phase: TouchPhase::Moved,
779 };
780 if callback(PlatformInput::ScrollWheel(event)).default_prevented {
781 return Some(0);
782 }
783 }
784 Some(1)
785 }
786
787 fn handle_ime_position(&self) -> Option<isize> {
788 unsafe {
789 let ctx = ImmGetContext(self.hwnd);
790 let Some(mut input_handler) = self.input_handler.take() else {
791 return Some(1);
792 };
793 let caret_range = input_handler.selected_text_range().unwrap_or_default();
794 let caret_position = input_handler.bounds_for_range(caret_range).unwrap();
795 self.input_handler.set(Some(input_handler));
796 let scale_factor = self.scale_factor.get();
797 let config = CANDIDATEFORM {
798 dwStyle: CFS_CANDIDATEPOS,
799 // logical to physical
800 ptCurrentPos: POINT {
801 x: (caret_position.origin.x.0 * scale_factor) as i32,
802 y: (caret_position.origin.y.0 * scale_factor) as i32
803 + ((caret_position.size.height.0 * scale_factor) as i32 / 2),
804 },
805 ..Default::default()
806 };
807 ImmSetCandidateWindow(ctx, &config as _);
808 ImmReleaseContext(self.hwnd, ctx);
809 Some(0)
810 }
811 }
812
813 fn parse_ime_compostion_string(&self) -> Option<(String, usize)> {
814 unsafe {
815 let ctx = ImmGetContext(self.hwnd);
816 let string_len = ImmGetCompositionStringW(ctx, GCS_COMPSTR, None, 0);
817 let result = if string_len >= 0 {
818 let mut buffer = vec![0u8; string_len as usize + 2];
819 ImmGetCompositionStringW(
820 ctx,
821 GCS_COMPSTR,
822 Some(buffer.as_mut_ptr() as _),
823 string_len as _,
824 );
825 let wstring = std::slice::from_raw_parts::<u16>(
826 buffer.as_mut_ptr().cast::<u16>(),
827 string_len as usize / 2,
828 );
829 let string = String::from_utf16_lossy(wstring);
830 Some((string, string_len as usize / 2))
831 } else {
832 None
833 };
834 ImmReleaseContext(self.hwnd, ctx);
835 result
836 }
837 }
838
839 fn retrieve_composition_cursor_position(&self) -> usize {
840 unsafe {
841 let ctx = ImmGetContext(self.hwnd);
842 let ret = ImmGetCompositionStringW(ctx, GCS_CURSORPOS, None, 0);
843 ImmReleaseContext(self.hwnd, ctx);
844 ret as usize
845 }
846 }
847
848 fn parse_ime_compostion_result(&self) -> Option<String> {
849 unsafe {
850 let ctx = ImmGetContext(self.hwnd);
851 let string_len = ImmGetCompositionStringW(ctx, GCS_RESULTSTR, None, 0);
852 let result = if string_len >= 0 {
853 let mut buffer = vec![0u8; string_len as usize + 2];
854 ImmGetCompositionStringW(
855 ctx,
856 GCS_RESULTSTR,
857 Some(buffer.as_mut_ptr() as _),
858 string_len as _,
859 );
860 let wstring = std::slice::from_raw_parts::<u16>(
861 buffer.as_mut_ptr().cast::<u16>(),
862 string_len as usize / 2,
863 );
864 let string = String::from_utf16_lossy(wstring);
865 Some(string)
866 } else {
867 None
868 };
869 ImmReleaseContext(self.hwnd, ctx);
870 result
871 }
872 }
873
874 fn handle_ime_composition(&self, lparam: LPARAM) -> Option<isize> {
875 let mut ime_input = None;
876 if lparam.0 as u32 & GCS_COMPSTR.0 > 0 {
877 let Some((string, string_len)) = self.parse_ime_compostion_string() else {
878 return None;
879 };
880 let Some(mut input_handler) = self.input_handler.take() else {
881 return None;
882 };
883 input_handler.replace_and_mark_text_in_range(
884 None,
885 string.as_str(),
886 Some(0..string_len),
887 );
888 self.input_handler.set(Some(input_handler));
889 ime_input = Some(string);
890 }
891 if lparam.0 as u32 & GCS_CURSORPOS.0 > 0 {
892 let Some(ref comp_string) = ime_input else {
893 return None;
894 };
895 let caret_pos = self.retrieve_composition_cursor_position();
896 let Some(mut input_handler) = self.input_handler.take() else {
897 return None;
898 };
899 input_handler.replace_and_mark_text_in_range(None, comp_string, Some(0..caret_pos));
900 self.input_handler.set(Some(input_handler));
901 }
902 if lparam.0 as u32 & GCS_RESULTSTR.0 > 0 {
903 let Some(comp_result) = self.parse_ime_compostion_result() else {
904 return None;
905 };
906 let Some(mut input_handler) = self.input_handler.take() else {
907 return Some(1);
908 };
909 input_handler.replace_text_in_range(None, &comp_result);
910 self.input_handler.set(Some(input_handler));
911 self.invalidate_client_area();
912 return Some(0);
913 }
914 // currently, we don't care other stuff
915 None
916 }
917
918 fn handle_drag_drop(&self, input: PlatformInput) {
919 let mut callbacks = self.callbacks.borrow_mut();
920 let Some(ref mut func) = callbacks.input else {
921 return;
922 };
923 func(input);
924 }
925
926 /// SEE: https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize
927 fn handle_calc_client_size(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
928 if !self.hide_title_bar || self.is_fullscreen() {
929 return None;
930 }
931
932 if wparam.0 == 0 {
933 return None;
934 }
935
936 let dpi = unsafe { GetDpiForWindow(self.hwnd) };
937
938 let frame_x = unsafe { GetSystemMetricsForDpi(SM_CXFRAME, dpi) };
939 let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
940 let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
941
942 // wparam is TRUE so lparam points to an NCCALCSIZE_PARAMS structure
943 let mut params = lparam.0 as *mut NCCALCSIZE_PARAMS;
944 let mut requested_client_rect = unsafe { &mut ((*params).rgrc) };
945
946 requested_client_rect[0].right -= frame_x + padding;
947 requested_client_rect[0].left += frame_x + padding;
948 requested_client_rect[0].bottom -= frame_y + padding;
949
950 Some(0)
951 }
952
953 fn handle_activate_msg(&self, wparam: WPARAM) -> Option<isize> {
954 if self.hide_title_bar {
955 if let Some(titlebar_rect) = self.get_titlebar_rect().log_err() {
956 unsafe { InvalidateRect(self.hwnd, Some(&titlebar_rect), FALSE) };
957 }
958 }
959 let activated = wparam.loword() > 0;
960 let mut callbacks = self.callbacks.borrow_mut();
961 if let Some(mut cb) = callbacks.active_status_change.as_mut() {
962 cb(activated);
963 }
964 None
965 }
966
967 fn handle_create_msg(&self, _lparam: LPARAM) -> Option<isize> {
968 let mut size_rect = RECT::default();
969 unsafe { GetWindowRect(self.hwnd, &mut size_rect).log_err() };
970
971 let width = size_rect.right - size_rect.left;
972 let height = size_rect.bottom - size_rect.top;
973
974 self.physical_size.set(Size {
975 width: DevicePixels(width),
976 height: DevicePixels(height),
977 });
978
979 if self.hide_title_bar {
980 // Inform the application of the frame change to force redrawing with the new
981 // client area that is extended into the title bar
982 unsafe {
983 SetWindowPos(
984 self.hwnd,
985 HWND::default(),
986 size_rect.left,
987 size_rect.top,
988 width,
989 height,
990 SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE,
991 )
992 .log_err()
993 };
994 }
995
996 Some(0)
997 }
998
999 fn handle_dpi_changed_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
1000 let new_dpi = wparam.loword() as f32;
1001 let scale_factor = new_dpi / USER_DEFAULT_SCREEN_DPI as f32;
1002 self.scale_factor.set(scale_factor);
1003 let rect = unsafe { &*(lparam.0 as *const RECT) };
1004 let width = rect.right - rect.left;
1005 let height = rect.bottom - rect.top;
1006 // this will emit `WM_SIZE` and `WM_MOVE` right here
1007 // even before this function returns
1008 // the new size is handled in `WM_SIZE`
1009 unsafe {
1010 SetWindowPos(
1011 self.hwnd,
1012 None,
1013 rect.left,
1014 rect.top,
1015 width,
1016 height,
1017 SWP_NOZORDER | SWP_NOACTIVATE,
1018 )
1019 .context("unable to set window position after dpi has changed")
1020 .log_err();
1021 }
1022 self.invalidate_client_area();
1023 Some(0)
1024 }
1025
1026 fn handle_hit_test_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
1027 if !self.hide_title_bar {
1028 return None;
1029 }
1030
1031 // default handler for resize areas
1032 let hit = unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) };
1033 if matches!(
1034 hit.0 as u32,
1035 HTNOWHERE
1036 | HTRIGHT
1037 | HTLEFT
1038 | HTTOPLEFT
1039 | HTTOP
1040 | HTTOPRIGHT
1041 | HTBOTTOMRIGHT
1042 | HTBOTTOM
1043 | HTBOTTOMLEFT
1044 ) {
1045 return Some(hit.0);
1046 }
1047
1048 if self.is_fullscreen() {
1049 return Some(HTCLIENT as _);
1050 }
1051
1052 let dpi = unsafe { GetDpiForWindow(self.hwnd) };
1053 let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
1054 let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
1055
1056 let mut cursor_point = POINT {
1057 x: lparam.signed_loword().into(),
1058 y: lparam.signed_hiword().into(),
1059 };
1060 unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
1061 if cursor_point.y > 0 && cursor_point.y < frame_y + padding {
1062 return Some(HTTOP as _);
1063 }
1064
1065 let titlebar_rect = self.get_titlebar_rect();
1066 if let Ok(titlebar_rect) = titlebar_rect {
1067 if cursor_point.y < titlebar_rect.bottom {
1068 let caption_btn_width =
1069 (self.caption_button_width().0 * self.scale_factor.get()) as i32;
1070 if cursor_point.x >= titlebar_rect.right - caption_btn_width {
1071 return Some(HTCLOSE as _);
1072 } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 2 {
1073 return Some(HTMAXBUTTON as _);
1074 } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 3 {
1075 return Some(HTMINBUTTON as _);
1076 }
1077
1078 return Some(HTCAPTION as _);
1079 }
1080 }
1081
1082 Some(HTCLIENT as _)
1083 }
1084
1085 fn handle_nc_mouse_move_msg(&self, lparam: LPARAM) -> Option<isize> {
1086 if !self.hide_title_bar {
1087 return None;
1088 }
1089
1090 let mut callbacks = self.callbacks.borrow_mut();
1091 if let Some(callback) = callbacks.input.as_mut() {
1092 let mut cursor_point = POINT {
1093 x: lparam.signed_loword().into(),
1094 y: lparam.signed_hiword().into(),
1095 };
1096 unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
1097 let scale_factor = self.scale_factor.get();
1098 let event = MouseMoveEvent {
1099 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
1100 pressed_button: None,
1101 modifiers: self.current_modifiers(),
1102 };
1103 if callback(PlatformInput::MouseMove(event)).default_prevented {
1104 return Some(0);
1105 }
1106 }
1107 None
1108 }
1109
1110 fn handle_nc_mouse_down_msg(
1111 &self,
1112 button: MouseButton,
1113 wparam: WPARAM,
1114 lparam: LPARAM,
1115 ) -> Option<isize> {
1116 if !self.hide_title_bar {
1117 return None;
1118 }
1119
1120 let mut callbacks = self.callbacks.borrow_mut();
1121 if let Some(callback) = callbacks.input.as_mut() {
1122 let mut cursor_point = POINT {
1123 x: lparam.signed_loword().into(),
1124 y: lparam.signed_hiword().into(),
1125 };
1126 unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
1127 let physical_point = point(DevicePixels(cursor_point.x), DevicePixels(cursor_point.y));
1128 let click_count = self.click_state.borrow_mut().update(button, physical_point);
1129 let scale_factor = self.scale_factor.get();
1130 let event = MouseDownEvent {
1131 button,
1132 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
1133 modifiers: self.current_modifiers(),
1134 click_count,
1135 first_mouse: false,
1136 };
1137 if callback(PlatformInput::MouseDown(event)).default_prevented {
1138 return Some(0);
1139 }
1140 }
1141
1142 // Since these are handled in handle_nc_mouse_up_msg we must prevent the default window proc
1143 matches!(wparam.0 as u32, HTMINBUTTON | HTMAXBUTTON | HTCLOSE).then_some(0)
1144 }
1145
1146 fn handle_nc_mouse_up_msg(
1147 &self,
1148 button: MouseButton,
1149 wparam: WPARAM,
1150 lparam: LPARAM,
1151 ) -> Option<isize> {
1152 if !self.hide_title_bar {
1153 return None;
1154 }
1155
1156 let mut callbacks = self.callbacks.borrow_mut();
1157 if let Some(callback) = callbacks.input.as_mut() {
1158 let mut cursor_point = POINT {
1159 x: lparam.signed_loword().into(),
1160 y: lparam.signed_hiword().into(),
1161 };
1162 unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
1163 let scale_factor = self.scale_factor.get();
1164 let event = MouseUpEvent {
1165 button,
1166 position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
1167 modifiers: self.current_modifiers(),
1168 click_count: 1,
1169 };
1170 if callback(PlatformInput::MouseUp(event)).default_prevented {
1171 return Some(0);
1172 }
1173 }
1174 drop(callbacks);
1175
1176 if button == MouseButton::Left {
1177 match wparam.0 as u32 {
1178 HTMINBUTTON => unsafe {
1179 ShowWindowAsync(self.hwnd, SW_MINIMIZE);
1180 },
1181 HTMAXBUTTON => unsafe {
1182 if self.is_maximized() {
1183 ShowWindowAsync(self.hwnd, SW_NORMAL);
1184 } else {
1185 ShowWindowAsync(self.hwnd, SW_MAXIMIZE);
1186 }
1187 },
1188 HTCLOSE => unsafe {
1189 PostMessageW(self.hwnd, WM_CLOSE, WPARAM::default(), LPARAM::default())
1190 .log_err();
1191 },
1192 _ => return None,
1193 };
1194 return Some(0);
1195 }
1196
1197 None
1198 }
1199
1200 fn handle_set_cursor(&self, lparam: LPARAM) -> Option<isize> {
1201 if matches!(
1202 lparam.loword() as u32,
1203 HTLEFT
1204 | HTRIGHT
1205 | HTTOP
1206 | HTTOPLEFT
1207 | HTTOPRIGHT
1208 | HTBOTTOM
1209 | HTBOTTOMLEFT
1210 | HTBOTTOMRIGHT
1211 ) {
1212 return None;
1213 }
1214 unsafe { SetCursor(self.platform_inner.current_cursor.get()) };
1215 Some(1)
1216 }
1217}
1218
1219#[derive(Default)]
1220struct Callbacks {
1221 request_frame: Option<Box<dyn FnMut()>>,
1222 input: Option<Box<dyn FnMut(crate::PlatformInput) -> DispatchEventResult>>,
1223 active_status_change: Option<Box<dyn FnMut(bool)>>,
1224 resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
1225 moved: Option<Box<dyn FnMut()>>,
1226 should_close: Option<Box<dyn FnMut() -> bool>>,
1227 close: Option<Box<dyn FnOnce()>>,
1228 appearance_changed: Option<Box<dyn FnMut()>>,
1229}
1230
1231pub(crate) struct WindowsWindow {
1232 inner: Rc<WindowsWindowInner>,
1233 drag_drop_handler: IDropTarget,
1234}
1235
1236struct WindowCreateContext {
1237 inner: Option<Rc<WindowsWindowInner>>,
1238 platform_inner: Rc<WindowsPlatformInner>,
1239 handle: AnyWindowHandle,
1240 hide_title_bar: bool,
1241 display: Rc<WindowsDisplay>,
1242 transparent: bool,
1243}
1244
1245impl WindowsWindow {
1246 pub(crate) fn new(
1247 platform_inner: Rc<WindowsPlatformInner>,
1248 handle: AnyWindowHandle,
1249 options: WindowParams,
1250 ) -> Self {
1251 let classname = register_wnd_class(platform_inner.icon);
1252 let hide_title_bar = options
1253 .titlebar
1254 .as_ref()
1255 .map(|titlebar| titlebar.appears_transparent)
1256 .unwrap_or(false);
1257 let windowname = HSTRING::from(
1258 options
1259 .titlebar
1260 .as_ref()
1261 .and_then(|titlebar| titlebar.title.as_ref())
1262 .map(|title| title.as_ref())
1263 .unwrap_or(""),
1264 );
1265 let dwstyle = WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX;
1266 let x = options.bounds.origin.x.0;
1267 let y = options.bounds.origin.y.0;
1268 let nwidth = options.bounds.size.width.0;
1269 let nheight = options.bounds.size.height.0;
1270 let hwndparent = HWND::default();
1271 let hmenu = HMENU::default();
1272 let hinstance = get_module_handle();
1273 let mut context = WindowCreateContext {
1274 inner: None,
1275 platform_inner: platform_inner.clone(),
1276 handle,
1277 hide_title_bar,
1278 // todo(windows) move window to target monitor
1279 // options.display_id
1280 display: Rc::new(WindowsDisplay::primary_monitor().unwrap()),
1281 transparent: options.window_background != WindowBackgroundAppearance::Opaque,
1282 };
1283 let lpparam = Some(&context as *const _ as *const _);
1284 unsafe {
1285 CreateWindowExW(
1286 WS_EX_APPWINDOW,
1287 classname,
1288 &windowname,
1289 dwstyle,
1290 x,
1291 y,
1292 nwidth,
1293 nheight,
1294 hwndparent,
1295 hmenu,
1296 hinstance,
1297 lpparam,
1298 )
1299 };
1300 let drag_drop_handler = {
1301 let inner = context.inner.as_ref().unwrap();
1302 let handler = WindowsDragDropHandler(Rc::clone(inner));
1303 let drag_drop_handler: IDropTarget = handler.into();
1304 unsafe {
1305 RegisterDragDrop(inner.hwnd, &drag_drop_handler)
1306 .expect("unable to register drag-drop event")
1307 };
1308 drag_drop_handler
1309 };
1310 let wnd = Self {
1311 inner: context.inner.unwrap(),
1312 drag_drop_handler,
1313 };
1314 platform_inner
1315 .raw_window_handles
1316 .write()
1317 .push(wnd.inner.hwnd);
1318
1319 unsafe { ShowWindow(wnd.inner.hwnd, SW_SHOW) };
1320 wnd
1321 }
1322}
1323
1324impl rwh::HasWindowHandle for WindowsWindow {
1325 fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
1326 let raw =
1327 rwh::Win32WindowHandle::new(unsafe { NonZeroIsize::new_unchecked(self.inner.hwnd.0) })
1328 .into();
1329 Ok(unsafe { rwh::WindowHandle::borrow_raw(raw) })
1330 }
1331}
1332
1333// todo(windows)
1334impl rwh::HasDisplayHandle for WindowsWindow {
1335 fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
1336 unimplemented!()
1337 }
1338}
1339
1340impl Drop for WindowsWindow {
1341 fn drop(&mut self) {
1342 unsafe {
1343 let _ = RevokeDragDrop(self.inner.hwnd);
1344 self.inner.renderer.borrow_mut().destroy();
1345 }
1346 }
1347}
1348
1349impl PlatformWindow for WindowsWindow {
1350 fn bounds(&self) -> Bounds<DevicePixels> {
1351 Bounds {
1352 origin: self.inner.origin.get(),
1353 size: self.inner.physical_size.get(),
1354 }
1355 }
1356
1357 fn is_maximized(&self) -> bool {
1358 self.inner.is_maximized()
1359 }
1360
1361 fn is_minimized(&self) -> bool {
1362 self.inner.is_minimized()
1363 }
1364
1365 /// get the logical size of the app's drawable area.
1366 ///
1367 /// Currently, GPUI uses logical size of the app to handle mouse interactions (such as
1368 /// whether the mouse collides with other elements of GPUI).
1369 fn content_size(&self) -> Size<Pixels> {
1370 logical_size(
1371 self.inner.physical_size.get(),
1372 self.inner.scale_factor.get(),
1373 )
1374 }
1375
1376 fn scale_factor(&self) -> f32 {
1377 self.inner.scale_factor.get()
1378 }
1379
1380 // todo(windows)
1381 fn appearance(&self) -> WindowAppearance {
1382 WindowAppearance::Dark
1383 }
1384
1385 fn display(&self) -> Rc<dyn PlatformDisplay> {
1386 self.inner.display.borrow().clone()
1387 }
1388
1389 fn mouse_position(&self) -> Point<Pixels> {
1390 let point = unsafe {
1391 let mut point: POINT = std::mem::zeroed();
1392 GetCursorPos(&mut point)
1393 .context("unable to get cursor position")
1394 .log_err();
1395 ScreenToClient(self.inner.hwnd, &mut point);
1396 point
1397 };
1398 logical_point(
1399 point.x as f32,
1400 point.y as f32,
1401 self.inner.scale_factor.get(),
1402 )
1403 }
1404
1405 // todo(windows)
1406 fn modifiers(&self) -> Modifiers {
1407 Modifiers::none()
1408 }
1409
1410 // todo(windows)
1411 fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
1412 self.inner.input_handler.set(Some(input_handler));
1413 }
1414
1415 // todo(windows)
1416 fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
1417 self.inner.input_handler.take()
1418 }
1419
1420 fn prompt(
1421 &self,
1422 level: PromptLevel,
1423 msg: &str,
1424 detail: Option<&str>,
1425 answers: &[&str],
1426 ) -> Option<Receiver<usize>> {
1427 let (done_tx, done_rx) = oneshot::channel();
1428 let msg = msg.to_string();
1429 let detail_string = match detail {
1430 Some(info) => Some(info.to_string()),
1431 None => None,
1432 };
1433 let answers = answers.iter().map(|s| s.to_string()).collect::<Vec<_>>();
1434 let handle = self.inner.hwnd;
1435 self.inner
1436 .platform_inner
1437 .foreground_executor
1438 .spawn(async move {
1439 unsafe {
1440 let mut config;
1441 config = std::mem::zeroed::<TASKDIALOGCONFIG>();
1442 config.cbSize = std::mem::size_of::<TASKDIALOGCONFIG>() as _;
1443 config.hwndParent = handle;
1444 let title;
1445 let main_icon;
1446 match level {
1447 crate::PromptLevel::Info => {
1448 title = windows::core::w!("Info");
1449 main_icon = TD_INFORMATION_ICON;
1450 }
1451 crate::PromptLevel::Warning => {
1452 title = windows::core::w!("Warning");
1453 main_icon = TD_WARNING_ICON;
1454 }
1455 crate::PromptLevel::Critical | crate::PromptLevel::Destructive => {
1456 title = windows::core::w!("Critical");
1457 main_icon = TD_ERROR_ICON;
1458 }
1459 };
1460 config.pszWindowTitle = title;
1461 config.Anonymous1.pszMainIcon = main_icon;
1462 let instruction = msg.encode_utf16().chain(once(0)).collect_vec();
1463 config.pszMainInstruction = PCWSTR::from_raw(instruction.as_ptr());
1464 let hints_encoded;
1465 if let Some(ref hints) = detail_string {
1466 hints_encoded = hints.encode_utf16().chain(once(0)).collect_vec();
1467 config.pszContent = PCWSTR::from_raw(hints_encoded.as_ptr());
1468 };
1469 let mut buttons = Vec::new();
1470 let mut btn_encoded = Vec::new();
1471 for (index, btn_string) in answers.iter().enumerate() {
1472 let encoded = btn_string.encode_utf16().chain(once(0)).collect_vec();
1473 buttons.push(TASKDIALOG_BUTTON {
1474 nButtonID: index as _,
1475 pszButtonText: PCWSTR::from_raw(encoded.as_ptr()),
1476 });
1477 btn_encoded.push(encoded);
1478 }
1479 config.cButtons = buttons.len() as _;
1480 config.pButtons = buttons.as_ptr();
1481
1482 config.pfCallback = None;
1483 let mut res = std::mem::zeroed();
1484 let _ = TaskDialogIndirect(&config, Some(&mut res), None, None)
1485 .inspect_err(|e| log::error!("unable to create task dialog: {}", e));
1486
1487 let _ = done_tx.send(res as usize);
1488 }
1489 })
1490 .detach();
1491
1492 Some(done_rx)
1493 }
1494
1495 fn activate(&self) {
1496 unsafe { SetActiveWindow(self.inner.hwnd) };
1497 unsafe { SetFocus(self.inner.hwnd) };
1498 unsafe { SetForegroundWindow(self.inner.hwnd) };
1499 }
1500
1501 fn is_active(&self) -> bool {
1502 self.inner.hwnd == unsafe { GetActiveWindow() }
1503 }
1504
1505 // todo(windows)
1506 fn set_title(&mut self, title: &str) {
1507 unsafe { SetWindowTextW(self.inner.hwnd, &HSTRING::from(title)) }
1508 .inspect_err(|e| log::error!("Set title failed: {e}"))
1509 .ok();
1510 }
1511
1512 fn set_app_id(&mut self, _app_id: &str) {}
1513
1514 fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance) {
1515 self.inner
1516 .renderer
1517 .borrow_mut()
1518 .update_transparency(background_appearance != WindowBackgroundAppearance::Opaque);
1519 }
1520
1521 // todo(windows)
1522 fn set_edited(&mut self, _edited: bool) {}
1523
1524 // todo(windows)
1525 fn show_character_palette(&self) {}
1526
1527 fn minimize(&self) {
1528 unsafe { ShowWindowAsync(self.inner.hwnd, SW_MINIMIZE) };
1529 }
1530
1531 fn zoom(&self) {
1532 unsafe { ShowWindowAsync(self.inner.hwnd, SW_MAXIMIZE) };
1533 }
1534
1535 fn toggle_fullscreen(&self) {
1536 self.inner
1537 .platform_inner
1538 .foreground_executor
1539 .spawn(self.inner.clone().toggle_fullscreen())
1540 .detach();
1541 }
1542
1543 fn is_fullscreen(&self) -> bool {
1544 self.inner.is_fullscreen()
1545 }
1546
1547 // todo(windows)
1548 fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
1549 self.inner.callbacks.borrow_mut().request_frame = Some(callback);
1550 }
1551
1552 // todo(windows)
1553 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>) {
1554 self.inner.callbacks.borrow_mut().input = Some(callback);
1555 }
1556
1557 // todo(windows)
1558 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
1559 self.inner.callbacks.borrow_mut().active_status_change = Some(callback);
1560 }
1561
1562 // todo(windows)
1563 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
1564 self.inner.callbacks.borrow_mut().resize = Some(callback);
1565 }
1566
1567 // todo(windows)
1568 fn on_moved(&self, callback: Box<dyn FnMut()>) {
1569 self.inner.callbacks.borrow_mut().moved = Some(callback);
1570 }
1571
1572 // todo(windows)
1573 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
1574 self.inner.callbacks.borrow_mut().should_close = Some(callback);
1575 }
1576
1577 // todo(windows)
1578 fn on_close(&self, callback: Box<dyn FnOnce()>) {
1579 self.inner.callbacks.borrow_mut().close = Some(callback);
1580 }
1581
1582 // todo(windows)
1583 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
1584 self.inner.callbacks.borrow_mut().appearance_changed = Some(callback);
1585 }
1586
1587 // todo(windows)
1588 fn draw(&self, scene: &Scene) {
1589 self.inner.renderer.borrow_mut().draw(scene)
1590 }
1591
1592 // todo(windows)
1593 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
1594 self.inner.renderer.borrow().sprite_atlas().clone()
1595 }
1596
1597 fn get_raw_handle(&self) -> HWND {
1598 self.inner.hwnd
1599 }
1600}
1601
1602#[implement(IDropTarget)]
1603struct WindowsDragDropHandler(pub Rc<WindowsWindowInner>);
1604
1605#[allow(non_snake_case)]
1606impl IDropTarget_Impl for WindowsDragDropHandler {
1607 fn DragEnter(
1608 &self,
1609 pdataobj: Option<&IDataObject>,
1610 _grfkeystate: MODIFIERKEYS_FLAGS,
1611 pt: &POINTL,
1612 pdweffect: *mut DROPEFFECT,
1613 ) -> windows::core::Result<()> {
1614 unsafe {
1615 let Some(idata_obj) = pdataobj else {
1616 log::info!("no dragging file or directory detected");
1617 return Ok(());
1618 };
1619 let config = FORMATETC {
1620 cfFormat: CF_HDROP.0,
1621 ptd: std::ptr::null_mut() as _,
1622 dwAspect: DVASPECT_CONTENT.0,
1623 lindex: -1,
1624 tymed: TYMED_HGLOBAL.0 as _,
1625 };
1626 if idata_obj.QueryGetData(&config as _) == S_OK {
1627 *pdweffect = DROPEFFECT_LINK;
1628 let Some(mut idata) = idata_obj.GetData(&config as _).log_err() else {
1629 return Ok(());
1630 };
1631 if idata.u.hGlobal.is_invalid() {
1632 return Ok(());
1633 }
1634 let hdrop = idata.u.hGlobal.0 as *mut HDROP;
1635 let mut paths = SmallVec::<[PathBuf; 2]>::new();
1636 let file_count = DragQueryFileW(*hdrop, DRAGDROP_GET_FILES_COUNT, None);
1637 for file_index in 0..file_count {
1638 let filename_length = DragQueryFileW(*hdrop, file_index, None) as usize;
1639 let mut buffer = vec![0u16; filename_length + 1];
1640 let ret = DragQueryFileW(*hdrop, file_index, Some(buffer.as_mut_slice()));
1641 if ret == 0 {
1642 log::error!("unable to read file name");
1643 continue;
1644 }
1645 if let Some(file_name) =
1646 String::from_utf16(&buffer[0..filename_length]).log_err()
1647 {
1648 if let Some(path) = PathBuf::from_str(&file_name).log_err() {
1649 paths.push(path);
1650 }
1651 }
1652 }
1653 ReleaseStgMedium(&mut idata);
1654 let mut cursor_position = POINT { x: pt.x, y: pt.y };
1655 ScreenToClient(self.0.hwnd, &mut cursor_position);
1656 let scale_factor = self.0.scale_factor.get();
1657 let input = PlatformInput::FileDrop(FileDropEvent::Entered {
1658 position: logical_point(
1659 cursor_position.x as f32,
1660 cursor_position.y as f32,
1661 scale_factor,
1662 ),
1663 paths: ExternalPaths(paths),
1664 });
1665 self.0.handle_drag_drop(input);
1666 } else {
1667 *pdweffect = DROPEFFECT_NONE;
1668 }
1669 }
1670 Ok(())
1671 }
1672
1673 fn DragOver(
1674 &self,
1675 _grfkeystate: MODIFIERKEYS_FLAGS,
1676 pt: &POINTL,
1677 _pdweffect: *mut DROPEFFECT,
1678 ) -> windows::core::Result<()> {
1679 let mut cursor_position = POINT { x: pt.x, y: pt.y };
1680 unsafe {
1681 ScreenToClient(self.0.hwnd, &mut cursor_position);
1682 }
1683 let scale_factor = self.0.scale_factor.get();
1684 let input = PlatformInput::FileDrop(FileDropEvent::Pending {
1685 position: logical_point(
1686 cursor_position.x as f32,
1687 cursor_position.y as f32,
1688 scale_factor,
1689 ),
1690 });
1691 self.0.handle_drag_drop(input);
1692
1693 Ok(())
1694 }
1695
1696 fn DragLeave(&self) -> windows::core::Result<()> {
1697 let input = PlatformInput::FileDrop(FileDropEvent::Exited);
1698 self.0.handle_drag_drop(input);
1699
1700 Ok(())
1701 }
1702
1703 fn Drop(
1704 &self,
1705 _pdataobj: Option<&IDataObject>,
1706 _grfkeystate: MODIFIERKEYS_FLAGS,
1707 pt: &POINTL,
1708 _pdweffect: *mut DROPEFFECT,
1709 ) -> windows::core::Result<()> {
1710 let mut cursor_position = POINT { x: pt.x, y: pt.y };
1711 unsafe {
1712 ScreenToClient(self.0.hwnd, &mut cursor_position);
1713 }
1714 let scale_factor = self.0.scale_factor.get();
1715 let input = PlatformInput::FileDrop(FileDropEvent::Submit {
1716 position: logical_point(
1717 cursor_position.x as f32,
1718 cursor_position.y as f32,
1719 scale_factor,
1720 ),
1721 });
1722 self.0.handle_drag_drop(input);
1723
1724 Ok(())
1725 }
1726}
1727
1728#[derive(Debug)]
1729struct ClickState {
1730 button: MouseButton,
1731 last_click: Instant,
1732 last_position: Point<DevicePixels>,
1733 current_count: usize,
1734}
1735
1736impl ClickState {
1737 pub fn new() -> Self {
1738 ClickState {
1739 button: MouseButton::Left,
1740 last_click: Instant::now(),
1741 last_position: Point::default(),
1742 current_count: 0,
1743 }
1744 }
1745
1746 /// update self and return the needed click count
1747 pub fn update(&mut self, button: MouseButton, new_position: Point<DevicePixels>) -> usize {
1748 if self.button == button && self.is_double_click(new_position) {
1749 self.current_count += 1;
1750 } else {
1751 self.current_count = 1;
1752 }
1753 self.last_click = Instant::now();
1754 self.last_position = new_position;
1755 self.button = button;
1756
1757 self.current_count
1758 }
1759
1760 #[inline]
1761 fn is_double_click(&self, new_position: Point<DevicePixels>) -> bool {
1762 let diff = self.last_position - new_position;
1763
1764 self.last_click.elapsed() < DOUBLE_CLICK_INTERVAL
1765 && diff.x.0.abs() <= DOUBLE_CLICK_SPATIAL_TOLERANCE
1766 && diff.y.0.abs() <= DOUBLE_CLICK_SPATIAL_TOLERANCE
1767 }
1768}
1769
1770fn register_wnd_class(icon_handle: HICON) -> PCWSTR {
1771 const CLASS_NAME: PCWSTR = w!("Zed::Window");
1772
1773 static ONCE: Once = Once::new();
1774 ONCE.call_once(|| {
1775 let wc = WNDCLASSW {
1776 lpfnWndProc: Some(wnd_proc),
1777 hIcon: icon_handle,
1778 lpszClassName: PCWSTR(CLASS_NAME.as_ptr()),
1779 style: CS_HREDRAW | CS_VREDRAW,
1780 hInstance: get_module_handle().into(),
1781 ..Default::default()
1782 };
1783 unsafe { RegisterClassW(&wc) };
1784 });
1785
1786 CLASS_NAME
1787}
1788
1789unsafe extern "system" fn wnd_proc(
1790 hwnd: HWND,
1791 msg: u32,
1792 wparam: WPARAM,
1793 lparam: LPARAM,
1794) -> LRESULT {
1795 if msg == WM_NCCREATE {
1796 let cs = lparam.0 as *const CREATESTRUCTW;
1797 let cs = unsafe { &*cs };
1798 let ctx = cs.lpCreateParams as *mut WindowCreateContext;
1799 let ctx = unsafe { &mut *ctx };
1800 let inner = Rc::new(WindowsWindowInner::new(
1801 hwnd,
1802 cs,
1803 ctx.platform_inner.clone(),
1804 ctx.handle,
1805 ctx.hide_title_bar,
1806 ctx.display.clone(),
1807 ctx.transparent,
1808 ));
1809 let weak = Box::new(Rc::downgrade(&inner));
1810 unsafe { set_window_long(hwnd, GWLP_USERDATA, Box::into_raw(weak) as isize) };
1811 ctx.inner = Some(inner);
1812 return LRESULT(1);
1813 }
1814 let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak<WindowsWindowInner>;
1815 if ptr.is_null() {
1816 return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) };
1817 }
1818 let inner = unsafe { &*ptr };
1819 let r = if let Some(inner) = inner.upgrade() {
1820 inner.handle_msg(msg, wparam, lparam)
1821 } else {
1822 unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }
1823 };
1824 if msg == WM_NCDESTROY {
1825 unsafe { set_window_long(hwnd, GWLP_USERDATA, 0) };
1826 unsafe { drop(Box::from_raw(ptr)) };
1827 }
1828 r
1829}
1830
1831pub(crate) fn try_get_window_inner(hwnd: HWND) -> Option<Rc<WindowsWindowInner>> {
1832 if hwnd == HWND(0) {
1833 return None;
1834 }
1835
1836 let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak<WindowsWindowInner>;
1837 if !ptr.is_null() {
1838 let inner = unsafe { &*ptr };
1839 inner.upgrade()
1840 } else {
1841 None
1842 }
1843}
1844
1845fn basic_vkcode_to_string(code: u16, modifiers: Modifiers) -> Option<Keystroke> {
1846 match code {
1847 // VK_0 - VK_9
1848 48..=57 => Some(Keystroke {
1849 modifiers,
1850 key: format!("{}", code - VK_0.0),
1851 ime_key: None,
1852 }),
1853 // VK_A - VK_Z
1854 65..=90 => Some(Keystroke {
1855 modifiers,
1856 key: format!("{}", (b'a' + code as u8 - VK_A.0 as u8) as char),
1857 ime_key: None,
1858 }),
1859 // VK_F1 - VK_F24
1860 112..=135 => Some(Keystroke {
1861 modifiers,
1862 key: format!("f{}", code - VK_F1.0 + 1),
1863 ime_key: None,
1864 }),
1865 // OEM3: `/~, OEM_MINUS: -/_, OEM_PLUS: =/+, ...
1866 _ => {
1867 if let Some(key) = oemkey_vkcode_to_string(code) {
1868 Some(Keystroke {
1869 modifiers,
1870 key,
1871 ime_key: None,
1872 })
1873 } else {
1874 None
1875 }
1876 }
1877 }
1878}
1879
1880fn oemkey_vkcode_to_string(code: u16) -> Option<String> {
1881 match code {
1882 186 => Some(";".to_string()), // VK_OEM_1
1883 187 => Some("=".to_string()), // VK_OEM_PLUS
1884 188 => Some(",".to_string()), // VK_OEM_COMMA
1885 189 => Some("-".to_string()), // VK_OEM_MINUS
1886 190 => Some(".".to_string()), // VK_OEM_PERIOD
1887 // https://kbdlayout.info/features/virtualkeys/VK_ABNT_C1
1888 191 | 193 => Some("/".to_string()), // VK_OEM_2 VK_ABNT_C1
1889 192 => Some("`".to_string()), // VK_OEM_3
1890 219 => Some("[".to_string()), // VK_OEM_4
1891 220 => Some("\\".to_string()), // VK_OEM_5
1892 221 => Some("]".to_string()), // VK_OEM_6
1893 222 => Some("'".to_string()), // VK_OEM_7
1894 _ => None,
1895 }
1896}
1897
1898#[inline]
1899fn logical_size(physical_size: Size<DevicePixels>, scale_factor: f32) -> Size<Pixels> {
1900 Size {
1901 width: px(physical_size.width.0 as f32 / scale_factor),
1902 height: px(physical_size.height.0 as f32 / scale_factor),
1903 }
1904}
1905
1906#[inline]
1907fn logical_point(x: f32, y: f32, scale_factor: f32) -> Point<Pixels> {
1908 Point {
1909 x: px(x / scale_factor),
1910 y: px(y / scale_factor),
1911 }
1912}
1913
1914struct StyleAndBounds {
1915 style: WINDOW_STYLE,
1916 x: i32,
1917 y: i32,
1918 cx: i32,
1919 cy: i32,
1920}
1921
1922fn get_module_handle() -> HMODULE {
1923 unsafe {
1924 let mut h_module = std::mem::zeroed();
1925 GetModuleHandleExW(
1926 GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
1927 windows::core::w!("ZedModule"),
1928 &mut h_module,
1929 )
1930 .expect("Unable to get module handle"); // this should never fail
1931
1932 h_module
1933 }
1934}
1935
1936// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-dragqueryfilew
1937const DRAGDROP_GET_FILES_COUNT: u32 = 0xFFFFFFFF;
1938// https://learn.microsoft.com/en-us/windows/win32/controls/ttm-setdelaytime?redirectedfrom=MSDN
1939const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(500);
1940// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsystemmetrics
1941const DOUBLE_CLICK_SPATIAL_TOLERANCE: i32 = 4;
1942const SIZE_MOVE_LOOP_TIMER_ID: usize = 1;
1943
1944#[cfg(test)]
1945mod tests {
1946 use super::ClickState;
1947 use crate::{point, DevicePixels, MouseButton};
1948 use std::time::Duration;
1949
1950 #[test]
1951 fn test_double_click_interval() {
1952 let mut state = ClickState::new();
1953 assert_eq!(
1954 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1955 1
1956 );
1957 assert_eq!(
1958 state.update(MouseButton::Right, point(DevicePixels(0), DevicePixels(0))),
1959 1
1960 );
1961 assert_eq!(
1962 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1963 1
1964 );
1965 assert_eq!(
1966 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1967 2
1968 );
1969 state.last_click -= Duration::from_millis(700);
1970 assert_eq!(
1971 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1972 1
1973 );
1974 }
1975
1976 #[test]
1977 fn test_double_click_spatial_tolerance() {
1978 let mut state = ClickState::new();
1979 assert_eq!(
1980 state.update(MouseButton::Left, point(DevicePixels(-3), DevicePixels(0))),
1981 1
1982 );
1983 assert_eq!(
1984 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(3))),
1985 2
1986 );
1987 assert_eq!(
1988 state.update(MouseButton::Right, point(DevicePixels(3), DevicePixels(2))),
1989 1
1990 );
1991 assert_eq!(
1992 state.update(MouseButton::Right, point(DevicePixels(10), DevicePixels(0))),
1993 1
1994 );
1995 }
1996}