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