1#![deny(unsafe_op_in_unsafe_fn)]
2
3use std::{
4 cell::RefCell,
5 num::NonZeroIsize,
6 path::PathBuf,
7 rc::{Rc, Weak},
8 str::FromStr,
9 sync::{Arc, Once},
10 time::{Duration, Instant},
11};
12
13use ::util::ResultExt;
14use anyhow::{Context as _, Result};
15use async_task::Runnable;
16use futures::channel::oneshot::{self, Receiver};
17use raw_window_handle as rwh;
18use smallvec::SmallVec;
19use windows::{
20 Win32::{
21 Foundation::*,
22 Graphics::Gdi::*,
23 System::{Com::*, LibraryLoader::*, Ole::*, SystemServices::*},
24 UI::{Controls::*, HiDpi::*, Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*},
25 },
26 core::*,
27};
28
29use crate::platform::blade::{BladeContext, BladeRenderer};
30use crate::*;
31
32pub(crate) struct WindowsWindow(pub Rc<WindowsWindowStatePtr>);
33
34pub struct WindowsWindowState {
35 pub origin: Point<Pixels>,
36 pub logical_size: Size<Pixels>,
37 pub min_size: Option<Size<Pixels>>,
38 pub fullscreen_restore_bounds: Bounds<Pixels>,
39 pub border_offset: WindowBorderOffset,
40 pub scale_factor: f32,
41 pub restore_from_minimized: Option<Box<dyn FnMut(RequestFrameOptions)>>,
42
43 pub callbacks: Callbacks,
44 pub input_handler: Option<PlatformInputHandler>,
45 pub last_reported_modifiers: Option<Modifiers>,
46 pub last_reported_capslock: Option<Capslock>,
47 pub system_key_handled: bool,
48 pub hovered: bool,
49
50 pub renderer: BladeRenderer,
51
52 pub click_state: ClickState,
53 pub system_settings: WindowsSystemSettings,
54 pub current_cursor: Option<HCURSOR>,
55 pub nc_button_pressed: Option<u32>,
56
57 pub display: WindowsDisplay,
58 fullscreen: Option<StyleAndBounds>,
59 initial_placement: Option<WindowOpenStatus>,
60 hwnd: HWND,
61}
62
63pub(crate) struct WindowsWindowStatePtr {
64 hwnd: HWND,
65 this: Weak<Self>,
66 pub(crate) state: RefCell<WindowsWindowState>,
67 pub(crate) handle: AnyWindowHandle,
68 pub(crate) hide_title_bar: bool,
69 pub(crate) is_movable: bool,
70 pub(crate) executor: ForegroundExecutor,
71 pub(crate) windows_version: WindowsVersion,
72 pub(crate) validation_number: usize,
73 pub(crate) main_receiver: flume::Receiver<Runnable>,
74 pub(crate) main_thread_id_win32: u32,
75}
76
77impl WindowsWindowState {
78 fn new(
79 hwnd: HWND,
80 transparent: bool,
81 cs: &CREATESTRUCTW,
82 current_cursor: Option<HCURSOR>,
83 display: WindowsDisplay,
84 gpu_context: &BladeContext,
85 min_size: Option<Size<Pixels>>,
86 ) -> Result<Self> {
87 let scale_factor = {
88 let monitor_dpi = unsafe { GetDpiForWindow(hwnd) } as f32;
89 monitor_dpi / USER_DEFAULT_SCREEN_DPI as f32
90 };
91 let origin = logical_point(cs.x as f32, cs.y as f32, scale_factor);
92 let logical_size = {
93 let physical_size = size(DevicePixels(cs.cx), DevicePixels(cs.cy));
94 physical_size.to_pixels(scale_factor)
95 };
96 let fullscreen_restore_bounds = Bounds {
97 origin,
98 size: logical_size,
99 };
100 let border_offset = WindowBorderOffset::default();
101 let restore_from_minimized = None;
102 let renderer = windows_renderer::init(gpu_context, hwnd, transparent)?;
103 let callbacks = Callbacks::default();
104 let input_handler = None;
105 let last_reported_modifiers = None;
106 let last_reported_capslock = None;
107 let system_key_handled = false;
108 let hovered = false;
109 let click_state = ClickState::new();
110 let system_settings = WindowsSystemSettings::new(display);
111 let nc_button_pressed = None;
112 let fullscreen = None;
113 let initial_placement = None;
114
115 Ok(Self {
116 origin,
117 logical_size,
118 fullscreen_restore_bounds,
119 border_offset,
120 scale_factor,
121 restore_from_minimized,
122 min_size,
123 callbacks,
124 input_handler,
125 last_reported_modifiers,
126 last_reported_capslock,
127 system_key_handled,
128 hovered,
129 renderer,
130 click_state,
131 system_settings,
132 current_cursor,
133 nc_button_pressed,
134 display,
135 fullscreen,
136 initial_placement,
137 hwnd,
138 })
139 }
140
141 #[inline]
142 pub(crate) fn is_fullscreen(&self) -> bool {
143 self.fullscreen.is_some()
144 }
145
146 pub(crate) fn is_maximized(&self) -> bool {
147 !self.is_fullscreen() && unsafe { IsZoomed(self.hwnd) }.as_bool()
148 }
149
150 fn bounds(&self) -> Bounds<Pixels> {
151 Bounds {
152 origin: self.origin,
153 size: self.logical_size,
154 }
155 }
156
157 // Calculate the bounds used for saving and whether the window is maximized.
158 fn calculate_window_bounds(&self) -> (Bounds<Pixels>, bool) {
159 let placement = unsafe {
160 let mut placement = WINDOWPLACEMENT {
161 length: std::mem::size_of::<WINDOWPLACEMENT>() as u32,
162 ..Default::default()
163 };
164 GetWindowPlacement(self.hwnd, &mut placement).log_err();
165 placement
166 };
167 (
168 calculate_client_rect(
169 placement.rcNormalPosition,
170 self.border_offset,
171 self.scale_factor,
172 ),
173 placement.showCmd == SW_SHOWMAXIMIZED.0 as u32,
174 )
175 }
176
177 fn window_bounds(&self) -> WindowBounds {
178 let (bounds, maximized) = self.calculate_window_bounds();
179
180 if self.is_fullscreen() {
181 WindowBounds::Fullscreen(self.fullscreen_restore_bounds)
182 } else if maximized {
183 WindowBounds::Maximized(bounds)
184 } else {
185 WindowBounds::Windowed(bounds)
186 }
187 }
188
189 /// get the logical size of the app's drawable area.
190 ///
191 /// Currently, GPUI uses the logical size of the app to handle mouse interactions (such as
192 /// whether the mouse collides with other elements of GPUI).
193 fn content_size(&self) -> Size<Pixels> {
194 self.logical_size
195 }
196}
197
198impl WindowsWindowStatePtr {
199 fn new(context: &WindowCreateContext, hwnd: HWND, cs: &CREATESTRUCTW) -> Result<Rc<Self>> {
200 let state = RefCell::new(WindowsWindowState::new(
201 hwnd,
202 context.transparent,
203 cs,
204 context.current_cursor,
205 context.display,
206 context.gpu_context,
207 context.min_size,
208 )?);
209
210 Ok(Rc::new_cyclic(|this| Self {
211 hwnd,
212 this: this.clone(),
213 state,
214 handle: context.handle,
215 hide_title_bar: context.hide_title_bar,
216 is_movable: context.is_movable,
217 executor: context.executor.clone(),
218 windows_version: context.windows_version,
219 validation_number: context.validation_number,
220 main_receiver: context.main_receiver.clone(),
221 main_thread_id_win32: context.main_thread_id_win32,
222 }))
223 }
224
225 fn toggle_fullscreen(&self) {
226 let Some(state_ptr) = self.this.upgrade() else {
227 log::error!("Unable to toggle fullscreen: window has been dropped");
228 return;
229 };
230 self.executor
231 .spawn(async move {
232 let mut lock = state_ptr.state.borrow_mut();
233 let StyleAndBounds {
234 style,
235 x,
236 y,
237 cx,
238 cy,
239 } = if let Some(state) = lock.fullscreen.take() {
240 state
241 } else {
242 let (window_bounds, _) = lock.calculate_window_bounds();
243 lock.fullscreen_restore_bounds = window_bounds;
244 let style =
245 WINDOW_STYLE(unsafe { get_window_long(state_ptr.hwnd, GWL_STYLE) } as _);
246 let mut rc = RECT::default();
247 unsafe { GetWindowRect(state_ptr.hwnd, &mut rc) }.log_err();
248 let _ = lock.fullscreen.insert(StyleAndBounds {
249 style,
250 x: rc.left,
251 y: rc.top,
252 cx: rc.right - rc.left,
253 cy: rc.bottom - rc.top,
254 });
255 let style = style
256 & !(WS_THICKFRAME
257 | WS_SYSMENU
258 | WS_MAXIMIZEBOX
259 | WS_MINIMIZEBOX
260 | WS_CAPTION);
261 let physical_bounds = lock.display.physical_bounds();
262 StyleAndBounds {
263 style,
264 x: physical_bounds.left().0,
265 y: physical_bounds.top().0,
266 cx: physical_bounds.size.width.0,
267 cy: physical_bounds.size.height.0,
268 }
269 };
270 drop(lock);
271 unsafe { set_window_long(state_ptr.hwnd, GWL_STYLE, style.0 as isize) };
272 unsafe {
273 SetWindowPos(
274 state_ptr.hwnd,
275 None,
276 x,
277 y,
278 cx,
279 cy,
280 SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOZORDER,
281 )
282 }
283 .log_err();
284 })
285 .detach();
286 }
287
288 fn set_window_placement(&self) -> Result<()> {
289 let Some(open_status) = self.state.borrow_mut().initial_placement.take() else {
290 return Ok(());
291 };
292 match open_status.state {
293 WindowOpenState::Maximized => unsafe {
294 SetWindowPlacement(self.hwnd, &open_status.placement)?;
295 ShowWindowAsync(self.hwnd, SW_MAXIMIZE).ok()?;
296 },
297 WindowOpenState::Fullscreen => {
298 unsafe { SetWindowPlacement(self.hwnd, &open_status.placement)? };
299 self.toggle_fullscreen();
300 }
301 WindowOpenState::Windowed => unsafe {
302 SetWindowPlacement(self.hwnd, &open_status.placement)?;
303 },
304 }
305 Ok(())
306 }
307}
308
309#[derive(Default)]
310pub(crate) struct Callbacks {
311 pub(crate) request_frame: Option<Box<dyn FnMut(RequestFrameOptions)>>,
312 pub(crate) input: Option<Box<dyn FnMut(crate::PlatformInput) -> DispatchEventResult>>,
313 pub(crate) active_status_change: Option<Box<dyn FnMut(bool)>>,
314 pub(crate) hovered_status_change: Option<Box<dyn FnMut(bool)>>,
315 pub(crate) resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
316 pub(crate) moved: Option<Box<dyn FnMut()>>,
317 pub(crate) should_close: Option<Box<dyn FnMut() -> bool>>,
318 pub(crate) close: Option<Box<dyn FnOnce()>>,
319 pub(crate) hit_test_window_control: Option<Box<dyn FnMut() -> Option<WindowControlArea>>>,
320 pub(crate) appearance_changed: Option<Box<dyn FnMut()>>,
321}
322
323struct WindowCreateContext<'a> {
324 inner: Option<Result<Rc<WindowsWindowStatePtr>>>,
325 handle: AnyWindowHandle,
326 hide_title_bar: bool,
327 display: WindowsDisplay,
328 transparent: bool,
329 is_movable: bool,
330 min_size: Option<Size<Pixels>>,
331 executor: ForegroundExecutor,
332 current_cursor: Option<HCURSOR>,
333 windows_version: WindowsVersion,
334 validation_number: usize,
335 main_receiver: flume::Receiver<Runnable>,
336 gpu_context: &'a BladeContext,
337 main_thread_id_win32: u32,
338}
339
340impl WindowsWindow {
341 pub(crate) fn new(
342 handle: AnyWindowHandle,
343 params: WindowParams,
344 creation_info: WindowCreationInfo,
345 gpu_context: &BladeContext,
346 ) -> Result<Self> {
347 let WindowCreationInfo {
348 icon,
349 executor,
350 current_cursor,
351 windows_version,
352 validation_number,
353 main_receiver,
354 main_thread_id_win32,
355 } = creation_info;
356 let classname = register_wnd_class(icon);
357 let hide_title_bar = params
358 .titlebar
359 .as_ref()
360 .map(|titlebar| titlebar.appears_transparent)
361 .unwrap_or(true);
362 let windowname = HSTRING::from(
363 params
364 .titlebar
365 .as_ref()
366 .and_then(|titlebar| titlebar.title.as_ref())
367 .map(|title| title.as_ref())
368 .unwrap_or(""),
369 );
370 let (dwexstyle, mut dwstyle) = if params.kind == WindowKind::PopUp {
371 (WS_EX_TOOLWINDOW | WS_EX_LAYERED, WINDOW_STYLE(0x0))
372 } else {
373 (
374 WS_EX_APPWINDOW | WS_EX_LAYERED,
375 WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX,
376 )
377 };
378
379 let hinstance = get_module_handle();
380 let display = if let Some(display_id) = params.display_id {
381 // if we obtain a display_id, then this ID must be valid.
382 WindowsDisplay::new(display_id).unwrap()
383 } else {
384 WindowsDisplay::primary_monitor().unwrap()
385 };
386 let mut context = WindowCreateContext {
387 inner: None,
388 handle,
389 hide_title_bar,
390 display,
391 transparent: true,
392 is_movable: params.is_movable,
393 min_size: params.window_min_size,
394 executor,
395 current_cursor,
396 windows_version,
397 validation_number,
398 main_receiver,
399 gpu_context,
400 main_thread_id_win32,
401 };
402 let lpparam = Some(&context as *const _ as *const _);
403 let creation_result = unsafe {
404 CreateWindowExW(
405 dwexstyle,
406 classname,
407 &windowname,
408 dwstyle,
409 CW_USEDEFAULT,
410 CW_USEDEFAULT,
411 CW_USEDEFAULT,
412 CW_USEDEFAULT,
413 None,
414 None,
415 Some(hinstance.into()),
416 lpparam,
417 )
418 };
419 // We should call `?` on state_ptr first, then call `?` on hwnd.
420 // Or, we will lose the error info reported by `WindowsWindowState::new`
421 let state_ptr = context.inner.take().unwrap()?;
422 let hwnd = creation_result?;
423 register_drag_drop(state_ptr.clone())?;
424 configure_dwm_dark_mode(hwnd);
425 state_ptr.state.borrow_mut().border_offset.update(hwnd)?;
426 let placement = retrieve_window_placement(
427 hwnd,
428 display,
429 params.bounds,
430 state_ptr.state.borrow().scale_factor,
431 state_ptr.state.borrow().border_offset,
432 )?;
433 if params.show {
434 unsafe { SetWindowPlacement(hwnd, &placement)? };
435 } else {
436 state_ptr.state.borrow_mut().initial_placement = Some(WindowOpenStatus {
437 placement,
438 state: WindowOpenState::Windowed,
439 });
440 }
441 // The render pipeline will perform compositing on the GPU when the
442 // swapchain is configured correctly (see downstream of
443 // update_transparency).
444 // The following configuration is a one-time setup to ensure that the
445 // window is going to be composited with per-pixel alpha, but the render
446 // pipeline is responsible for effectively calling UpdateLayeredWindow
447 // at the appropriate time.
448 unsafe { SetLayeredWindowAttributes(hwnd, COLORREF(0), 255, LWA_ALPHA)? };
449
450 Ok(Self(state_ptr))
451 }
452}
453
454impl rwh::HasWindowHandle for WindowsWindow {
455 fn window_handle(&self) -> std::result::Result<rwh::WindowHandle<'_>, rwh::HandleError> {
456 let raw = rwh::Win32WindowHandle::new(unsafe {
457 NonZeroIsize::new_unchecked(self.0.hwnd.0 as isize)
458 })
459 .into();
460 Ok(unsafe { rwh::WindowHandle::borrow_raw(raw) })
461 }
462}
463
464// todo(windows)
465impl rwh::HasDisplayHandle for WindowsWindow {
466 fn display_handle(&self) -> std::result::Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
467 unimplemented!()
468 }
469}
470
471impl Drop for WindowsWindow {
472 fn drop(&mut self) {
473 self.0.state.borrow_mut().renderer.destroy();
474 // clone this `Rc` to prevent early release of the pointer
475 let this = self.0.clone();
476 self.0
477 .executor
478 .spawn(async move {
479 let handle = this.hwnd;
480 unsafe {
481 RevokeDragDrop(handle).log_err();
482 DestroyWindow(handle).log_err();
483 }
484 })
485 .detach();
486 }
487}
488
489impl PlatformWindow for WindowsWindow {
490 fn bounds(&self) -> Bounds<Pixels> {
491 self.0.state.borrow().bounds()
492 }
493
494 fn is_maximized(&self) -> bool {
495 self.0.state.borrow().is_maximized()
496 }
497
498 fn window_bounds(&self) -> WindowBounds {
499 self.0.state.borrow().window_bounds()
500 }
501
502 /// get the logical size of the app's drawable area.
503 ///
504 /// Currently, GPUI uses the logical size of the app to handle mouse interactions (such as
505 /// whether the mouse collides with other elements of GPUI).
506 fn content_size(&self) -> Size<Pixels> {
507 self.0.state.borrow().content_size()
508 }
509
510 fn resize(&mut self, size: Size<Pixels>) {
511 let hwnd = self.0.hwnd;
512 let bounds =
513 crate::bounds(self.bounds().origin, size).to_device_pixels(self.scale_factor());
514 let rect = calculate_window_rect(bounds, self.0.state.borrow().border_offset);
515
516 self.0
517 .executor
518 .spawn(async move {
519 unsafe {
520 SetWindowPos(
521 hwnd,
522 None,
523 bounds.origin.x.0,
524 bounds.origin.y.0,
525 rect.right - rect.left,
526 rect.bottom - rect.top,
527 SWP_NOMOVE,
528 )
529 .context("unable to set window content size")
530 .log_err();
531 }
532 })
533 .detach();
534 }
535
536 fn scale_factor(&self) -> f32 {
537 self.0.state.borrow().scale_factor
538 }
539
540 fn appearance(&self) -> WindowAppearance {
541 system_appearance().log_err().unwrap_or_default()
542 }
543
544 fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
545 Some(Rc::new(self.0.state.borrow().display))
546 }
547
548 fn mouse_position(&self) -> Point<Pixels> {
549 let scale_factor = self.scale_factor();
550 let point = unsafe {
551 let mut point: POINT = std::mem::zeroed();
552 GetCursorPos(&mut point)
553 .context("unable to get cursor position")
554 .log_err();
555 ScreenToClient(self.0.hwnd, &mut point).ok().log_err();
556 point
557 };
558 logical_point(point.x as f32, point.y as f32, scale_factor)
559 }
560
561 fn modifiers(&self) -> Modifiers {
562 current_modifiers()
563 }
564
565 fn capslock(&self) -> Capslock {
566 current_capslock()
567 }
568
569 fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
570 self.0.state.borrow_mut().input_handler = Some(input_handler);
571 }
572
573 fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
574 self.0.state.borrow_mut().input_handler.take()
575 }
576
577 fn prompt(
578 &self,
579 level: PromptLevel,
580 msg: &str,
581 detail: Option<&str>,
582 answers: &[PromptButton],
583 ) -> Option<Receiver<usize>> {
584 let (done_tx, done_rx) = oneshot::channel();
585 let msg = msg.to_string();
586 let detail_string = match detail {
587 Some(info) => Some(info.to_string()),
588 None => None,
589 };
590 let handle = self.0.hwnd;
591 let answers = answers.to_vec();
592 self.0
593 .executor
594 .spawn(async move {
595 unsafe {
596 let mut config = TASKDIALOGCONFIG::default();
597 config.cbSize = std::mem::size_of::<TASKDIALOGCONFIG>() as _;
598 config.hwndParent = handle;
599 let title;
600 let main_icon;
601 match level {
602 crate::PromptLevel::Info => {
603 title = windows::core::w!("Info");
604 main_icon = TD_INFORMATION_ICON;
605 }
606 crate::PromptLevel::Warning => {
607 title = windows::core::w!("Warning");
608 main_icon = TD_WARNING_ICON;
609 }
610 crate::PromptLevel::Critical => {
611 title = windows::core::w!("Critical");
612 main_icon = TD_ERROR_ICON;
613 }
614 };
615 config.pszWindowTitle = title;
616 config.Anonymous1.pszMainIcon = main_icon;
617 let instruction = HSTRING::from(msg);
618 config.pszMainInstruction = PCWSTR::from_raw(instruction.as_ptr());
619 let hints_encoded;
620 if let Some(ref hints) = detail_string {
621 hints_encoded = HSTRING::from(hints);
622 config.pszContent = PCWSTR::from_raw(hints_encoded.as_ptr());
623 };
624 let mut button_id_map = Vec::with_capacity(answers.len());
625 let mut buttons = Vec::new();
626 let mut btn_encoded = Vec::new();
627 for (index, btn) in answers.iter().enumerate() {
628 let encoded = HSTRING::from(btn.label().as_ref());
629 let button_id = if btn.is_cancel() {
630 IDCANCEL.0
631 } else {
632 index as i32 - 100
633 };
634 button_id_map.push(button_id);
635 buttons.push(TASKDIALOG_BUTTON {
636 nButtonID: button_id,
637 pszButtonText: PCWSTR::from_raw(encoded.as_ptr()),
638 });
639 btn_encoded.push(encoded);
640 }
641 config.cButtons = buttons.len() as _;
642 config.pButtons = buttons.as_ptr();
643
644 config.pfCallback = None;
645 let mut res = std::mem::zeroed();
646 let _ = TaskDialogIndirect(&config, Some(&mut res), None, None)
647 .context("unable to create task dialog")
648 .log_err();
649
650 let clicked = button_id_map
651 .iter()
652 .position(|&button_id| button_id == res)
653 .unwrap();
654 let _ = done_tx.send(clicked);
655 }
656 })
657 .detach();
658
659 Some(done_rx)
660 }
661
662 fn activate(&self) {
663 let hwnd = self.0.hwnd;
664 let this = self.0.clone();
665 self.0
666 .executor
667 .spawn(async move {
668 this.set_window_placement().log_err();
669 unsafe { SetActiveWindow(hwnd).log_err() };
670 unsafe { SetFocus(Some(hwnd)).log_err() };
671 // todo(windows)
672 // crate `windows 0.56` reports true as Err
673 unsafe { SetForegroundWindow(hwnd).as_bool() };
674 })
675 .detach();
676 }
677
678 fn is_active(&self) -> bool {
679 self.0.hwnd == unsafe { GetActiveWindow() }
680 }
681
682 fn is_hovered(&self) -> bool {
683 self.0.state.borrow().hovered
684 }
685
686 fn set_title(&mut self, title: &str) {
687 unsafe { SetWindowTextW(self.0.hwnd, &HSTRING::from(title)) }
688 .inspect_err(|e| log::error!("Set title failed: {e}"))
689 .ok();
690 }
691
692 fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
693 let mut window_state = self.0.state.borrow_mut();
694 window_state
695 .renderer
696 .update_transparency(background_appearance != WindowBackgroundAppearance::Opaque);
697
698 match background_appearance {
699 WindowBackgroundAppearance::Opaque => {
700 // ACCENT_DISABLED
701 set_window_composition_attribute(window_state.hwnd, None, 0);
702 }
703 WindowBackgroundAppearance::Transparent => {
704 // Use ACCENT_ENABLE_TRANSPARENTGRADIENT for transparent background
705 set_window_composition_attribute(window_state.hwnd, None, 2);
706 }
707 WindowBackgroundAppearance::Blurred => {
708 // Enable acrylic blur
709 // ACCENT_ENABLE_ACRYLICBLURBEHIND
710 set_window_composition_attribute(window_state.hwnd, Some((0, 0, 0, 0)), 4);
711 }
712 }
713 }
714
715 fn minimize(&self) {
716 unsafe { ShowWindowAsync(self.0.hwnd, SW_MINIMIZE).ok().log_err() };
717 }
718
719 fn zoom(&self) {
720 unsafe {
721 if IsWindowVisible(self.0.hwnd).as_bool() {
722 ShowWindowAsync(self.0.hwnd, SW_MAXIMIZE).ok().log_err();
723 } else if let Some(status) = self.0.state.borrow_mut().initial_placement.as_mut() {
724 status.state = WindowOpenState::Maximized;
725 }
726 }
727 }
728
729 fn toggle_fullscreen(&self) {
730 if unsafe { IsWindowVisible(self.0.hwnd).as_bool() } {
731 self.0.toggle_fullscreen();
732 } else if let Some(status) = self.0.state.borrow_mut().initial_placement.as_mut() {
733 status.state = WindowOpenState::Fullscreen;
734 }
735 }
736
737 fn is_fullscreen(&self) -> bool {
738 self.0.state.borrow().is_fullscreen()
739 }
740
741 fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>) {
742 self.0.state.borrow_mut().callbacks.request_frame = Some(callback);
743 }
744
745 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>) {
746 self.0.state.borrow_mut().callbacks.input = Some(callback);
747 }
748
749 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
750 self.0.state.borrow_mut().callbacks.active_status_change = Some(callback);
751 }
752
753 fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>) {
754 self.0.state.borrow_mut().callbacks.hovered_status_change = Some(callback);
755 }
756
757 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
758 self.0.state.borrow_mut().callbacks.resize = Some(callback);
759 }
760
761 fn on_moved(&self, callback: Box<dyn FnMut()>) {
762 self.0.state.borrow_mut().callbacks.moved = Some(callback);
763 }
764
765 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
766 self.0.state.borrow_mut().callbacks.should_close = Some(callback);
767 }
768
769 fn on_close(&self, callback: Box<dyn FnOnce()>) {
770 self.0.state.borrow_mut().callbacks.close = Some(callback);
771 }
772
773 fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>) {
774 self.0.state.borrow_mut().callbacks.hit_test_window_control = Some(callback);
775 }
776
777 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
778 self.0.state.borrow_mut().callbacks.appearance_changed = Some(callback);
779 }
780
781 fn draw(&self, scene: &Scene) {
782 self.0.state.borrow_mut().renderer.draw(scene)
783 }
784
785 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
786 self.0.state.borrow().renderer.sprite_atlas().clone()
787 }
788
789 fn get_raw_handle(&self) -> HWND {
790 self.0.hwnd
791 }
792
793 fn gpu_specs(&self) -> Option<GpuSpecs> {
794 Some(self.0.state.borrow().renderer.gpu_specs())
795 }
796
797 fn update_ime_position(&self, _bounds: Bounds<ScaledPixels>) {
798 // todo(windows)
799 }
800}
801
802#[implement(IDropTarget)]
803struct WindowsDragDropHandler(pub Rc<WindowsWindowStatePtr>);
804
805impl WindowsDragDropHandler {
806 fn handle_drag_drop(&self, input: PlatformInput) {
807 let mut lock = self.0.state.borrow_mut();
808 if let Some(mut func) = lock.callbacks.input.take() {
809 drop(lock);
810 func(input);
811 self.0.state.borrow_mut().callbacks.input = Some(func);
812 }
813 }
814}
815
816#[allow(non_snake_case)]
817impl IDropTarget_Impl for WindowsDragDropHandler_Impl {
818 fn DragEnter(
819 &self,
820 pdataobj: windows::core::Ref<IDataObject>,
821 _grfkeystate: MODIFIERKEYS_FLAGS,
822 pt: &POINTL,
823 pdweffect: *mut DROPEFFECT,
824 ) -> windows::core::Result<()> {
825 unsafe {
826 let idata_obj = pdataobj.ok()?;
827 let config = FORMATETC {
828 cfFormat: CF_HDROP.0,
829 ptd: std::ptr::null_mut() as _,
830 dwAspect: DVASPECT_CONTENT.0,
831 lindex: -1,
832 tymed: TYMED_HGLOBAL.0 as _,
833 };
834 if idata_obj.QueryGetData(&config as _) == S_OK {
835 *pdweffect = DROPEFFECT_LINK;
836 let Some(mut idata) = idata_obj.GetData(&config as _).log_err() else {
837 return Ok(());
838 };
839 if idata.u.hGlobal.is_invalid() {
840 return Ok(());
841 }
842 let hdrop = idata.u.hGlobal.0 as *mut HDROP;
843 let mut paths = SmallVec::<[PathBuf; 2]>::new();
844 with_file_names(*hdrop, |file_name| {
845 if let Some(path) = PathBuf::from_str(&file_name).log_err() {
846 paths.push(path);
847 }
848 });
849 ReleaseStgMedium(&mut idata);
850 let mut cursor_position = POINT { x: pt.x, y: pt.y };
851 ScreenToClient(self.0.hwnd, &mut cursor_position)
852 .ok()
853 .log_err();
854 let scale_factor = self.0.state.borrow().scale_factor;
855 let input = PlatformInput::FileDrop(FileDropEvent::Entered {
856 position: logical_point(
857 cursor_position.x as f32,
858 cursor_position.y as f32,
859 scale_factor,
860 ),
861 paths: ExternalPaths(paths),
862 });
863 self.handle_drag_drop(input);
864 } else {
865 *pdweffect = DROPEFFECT_NONE;
866 }
867 }
868 Ok(())
869 }
870
871 fn DragOver(
872 &self,
873 _grfkeystate: MODIFIERKEYS_FLAGS,
874 pt: &POINTL,
875 _pdweffect: *mut DROPEFFECT,
876 ) -> windows::core::Result<()> {
877 let mut cursor_position = POINT { x: pt.x, y: pt.y };
878 unsafe {
879 ScreenToClient(self.0.hwnd, &mut cursor_position)
880 .ok()
881 .log_err();
882 }
883 let scale_factor = self.0.state.borrow().scale_factor;
884 let input = PlatformInput::FileDrop(FileDropEvent::Pending {
885 position: logical_point(
886 cursor_position.x as f32,
887 cursor_position.y as f32,
888 scale_factor,
889 ),
890 });
891 self.handle_drag_drop(input);
892
893 Ok(())
894 }
895
896 fn DragLeave(&self) -> windows::core::Result<()> {
897 let input = PlatformInput::FileDrop(FileDropEvent::Exited);
898 self.handle_drag_drop(input);
899
900 Ok(())
901 }
902
903 fn Drop(
904 &self,
905 _pdataobj: windows::core::Ref<IDataObject>,
906 _grfkeystate: MODIFIERKEYS_FLAGS,
907 pt: &POINTL,
908 _pdweffect: *mut DROPEFFECT,
909 ) -> windows::core::Result<()> {
910 let mut cursor_position = POINT { x: pt.x, y: pt.y };
911 unsafe {
912 ScreenToClient(self.0.hwnd, &mut cursor_position)
913 .ok()
914 .log_err();
915 }
916 let scale_factor = self.0.state.borrow().scale_factor;
917 let input = PlatformInput::FileDrop(FileDropEvent::Submit {
918 position: logical_point(
919 cursor_position.x as f32,
920 cursor_position.y as f32,
921 scale_factor,
922 ),
923 });
924 self.handle_drag_drop(input);
925
926 Ok(())
927 }
928}
929
930#[derive(Debug)]
931pub(crate) struct ClickState {
932 button: MouseButton,
933 last_click: Instant,
934 last_position: Point<DevicePixels>,
935 double_click_spatial_tolerance_width: i32,
936 double_click_spatial_tolerance_height: i32,
937 double_click_interval: Duration,
938 pub(crate) current_count: usize,
939}
940
941impl ClickState {
942 pub fn new() -> Self {
943 let double_click_spatial_tolerance_width = unsafe { GetSystemMetrics(SM_CXDOUBLECLK) };
944 let double_click_spatial_tolerance_height = unsafe { GetSystemMetrics(SM_CYDOUBLECLK) };
945 let double_click_interval = Duration::from_millis(unsafe { GetDoubleClickTime() } as u64);
946
947 ClickState {
948 button: MouseButton::Left,
949 last_click: Instant::now(),
950 last_position: Point::default(),
951 double_click_spatial_tolerance_width,
952 double_click_spatial_tolerance_height,
953 double_click_interval,
954 current_count: 0,
955 }
956 }
957
958 /// update self and return the needed click count
959 pub fn update(&mut self, button: MouseButton, new_position: Point<DevicePixels>) -> usize {
960 if self.button == button && self.is_double_click(new_position) {
961 self.current_count += 1;
962 } else {
963 self.current_count = 1;
964 }
965 self.last_click = Instant::now();
966 self.last_position = new_position;
967 self.button = button;
968
969 self.current_count
970 }
971
972 pub fn system_update(&mut self) {
973 self.double_click_spatial_tolerance_width = unsafe { GetSystemMetrics(SM_CXDOUBLECLK) };
974 self.double_click_spatial_tolerance_height = unsafe { GetSystemMetrics(SM_CYDOUBLECLK) };
975 self.double_click_interval = Duration::from_millis(unsafe { GetDoubleClickTime() } as u64);
976 }
977
978 #[inline]
979 fn is_double_click(&self, new_position: Point<DevicePixels>) -> bool {
980 let diff = self.last_position - new_position;
981
982 self.last_click.elapsed() < self.double_click_interval
983 && diff.x.0.abs() <= self.double_click_spatial_tolerance_width
984 && diff.y.0.abs() <= self.double_click_spatial_tolerance_height
985 }
986}
987
988struct StyleAndBounds {
989 style: WINDOW_STYLE,
990 x: i32,
991 y: i32,
992 cx: i32,
993 cy: i32,
994}
995
996#[repr(C)]
997struct WINDOWCOMPOSITIONATTRIBDATA {
998 attrib: u32,
999 pv_data: *mut std::ffi::c_void,
1000 cb_data: usize,
1001}
1002
1003#[repr(C)]
1004struct AccentPolicy {
1005 accent_state: u32,
1006 accent_flags: u32,
1007 gradient_color: u32,
1008 animation_id: u32,
1009}
1010
1011type Color = (u8, u8, u8, u8);
1012
1013#[derive(Debug, Default, Clone, Copy)]
1014pub(crate) struct WindowBorderOffset {
1015 pub(crate) width_offset: i32,
1016 pub(crate) height_offset: i32,
1017}
1018
1019impl WindowBorderOffset {
1020 pub(crate) fn update(&mut self, hwnd: HWND) -> anyhow::Result<()> {
1021 let window_rect = unsafe {
1022 let mut rect = std::mem::zeroed();
1023 GetWindowRect(hwnd, &mut rect)?;
1024 rect
1025 };
1026 let client_rect = unsafe {
1027 let mut rect = std::mem::zeroed();
1028 GetClientRect(hwnd, &mut rect)?;
1029 rect
1030 };
1031 self.width_offset =
1032 (window_rect.right - window_rect.left) - (client_rect.right - client_rect.left);
1033 self.height_offset =
1034 (window_rect.bottom - window_rect.top) - (client_rect.bottom - client_rect.top);
1035 Ok(())
1036 }
1037}
1038
1039struct WindowOpenStatus {
1040 placement: WINDOWPLACEMENT,
1041 state: WindowOpenState,
1042}
1043
1044enum WindowOpenState {
1045 Maximized,
1046 Fullscreen,
1047 Windowed,
1048}
1049
1050fn register_wnd_class(icon_handle: HICON) -> PCWSTR {
1051 const CLASS_NAME: PCWSTR = w!("Zed::Window");
1052
1053 static ONCE: Once = Once::new();
1054 ONCE.call_once(|| {
1055 let wc = WNDCLASSW {
1056 lpfnWndProc: Some(wnd_proc),
1057 hIcon: icon_handle,
1058 lpszClassName: PCWSTR(CLASS_NAME.as_ptr()),
1059 style: CS_HREDRAW | CS_VREDRAW,
1060 hInstance: get_module_handle().into(),
1061 hbrBackground: unsafe { CreateSolidBrush(COLORREF(0x00000000)) },
1062 ..Default::default()
1063 };
1064 unsafe { RegisterClassW(&wc) };
1065 });
1066
1067 CLASS_NAME
1068}
1069
1070unsafe extern "system" fn wnd_proc(
1071 hwnd: HWND,
1072 msg: u32,
1073 wparam: WPARAM,
1074 lparam: LPARAM,
1075) -> LRESULT {
1076 if msg == WM_NCCREATE {
1077 let cs = lparam.0 as *const CREATESTRUCTW;
1078 let cs = unsafe { &*cs };
1079 let ctx = cs.lpCreateParams as *mut WindowCreateContext;
1080 let ctx = unsafe { &mut *ctx };
1081 let creation_result = WindowsWindowStatePtr::new(ctx, hwnd, cs);
1082 if creation_result.is_err() {
1083 ctx.inner = Some(creation_result);
1084 return LRESULT(0);
1085 }
1086 let weak = Box::new(Rc::downgrade(creation_result.as_ref().unwrap()));
1087 unsafe { set_window_long(hwnd, GWLP_USERDATA, Box::into_raw(weak) as isize) };
1088 ctx.inner = Some(creation_result);
1089 return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) };
1090 }
1091 let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak<WindowsWindowStatePtr>;
1092 if ptr.is_null() {
1093 return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) };
1094 }
1095 let inner = unsafe { &*ptr };
1096 let r = if let Some(state) = inner.upgrade() {
1097 handle_msg(hwnd, msg, wparam, lparam, state)
1098 } else {
1099 unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }
1100 };
1101 if msg == WM_NCDESTROY {
1102 unsafe { set_window_long(hwnd, GWLP_USERDATA, 0) };
1103 unsafe { drop(Box::from_raw(ptr)) };
1104 }
1105 r
1106}
1107
1108pub(crate) fn try_get_window_inner(hwnd: HWND) -> Option<Rc<WindowsWindowStatePtr>> {
1109 if hwnd.is_invalid() {
1110 return None;
1111 }
1112
1113 let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak<WindowsWindowStatePtr>;
1114 if !ptr.is_null() {
1115 let inner = unsafe { &*ptr };
1116 inner.upgrade()
1117 } else {
1118 None
1119 }
1120}
1121
1122fn get_module_handle() -> HMODULE {
1123 unsafe {
1124 let mut h_module = std::mem::zeroed();
1125 GetModuleHandleExW(
1126 GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
1127 windows::core::w!("ZedModule"),
1128 &mut h_module,
1129 )
1130 .expect("Unable to get module handle"); // this should never fail
1131
1132 h_module
1133 }
1134}
1135
1136fn register_drag_drop(state_ptr: Rc<WindowsWindowStatePtr>) -> Result<()> {
1137 let window_handle = state_ptr.hwnd;
1138 let handler = WindowsDragDropHandler(state_ptr);
1139 // The lifetime of `IDropTarget` is handled by Windows, it won't release until
1140 // we call `RevokeDragDrop`.
1141 // So, it's safe to drop it here.
1142 let drag_drop_handler: IDropTarget = handler.into();
1143 unsafe {
1144 RegisterDragDrop(window_handle, &drag_drop_handler)
1145 .context("unable to register drag-drop event")?;
1146 }
1147 Ok(())
1148}
1149
1150fn calculate_window_rect(bounds: Bounds<DevicePixels>, border_offset: WindowBorderOffset) -> RECT {
1151 // NOTE:
1152 // The reason we're not using `AdjustWindowRectEx()` here is
1153 // that the size reported by this function is incorrect.
1154 // You can test it, and there are similar discussions online.
1155 // See: https://stackoverflow.com/questions/12423584/how-to-set-exact-client-size-for-overlapped-window-winapi
1156 //
1157 // So we manually calculate these values here.
1158 let mut rect = RECT {
1159 left: bounds.left().0,
1160 top: bounds.top().0,
1161 right: bounds.right().0,
1162 bottom: bounds.bottom().0,
1163 };
1164 let left_offset = border_offset.width_offset / 2;
1165 let top_offset = border_offset.height_offset / 2;
1166 let right_offset = border_offset.width_offset - left_offset;
1167 let bottom_offset = border_offset.height_offset - top_offset;
1168 rect.left -= left_offset;
1169 rect.top -= top_offset;
1170 rect.right += right_offset;
1171 rect.bottom += bottom_offset;
1172 rect
1173}
1174
1175fn calculate_client_rect(
1176 rect: RECT,
1177 border_offset: WindowBorderOffset,
1178 scale_factor: f32,
1179) -> Bounds<Pixels> {
1180 let left_offset = border_offset.width_offset / 2;
1181 let top_offset = border_offset.height_offset / 2;
1182 let right_offset = border_offset.width_offset - left_offset;
1183 let bottom_offset = border_offset.height_offset - top_offset;
1184 let left = rect.left + left_offset;
1185 let top = rect.top + top_offset;
1186 let right = rect.right - right_offset;
1187 let bottom = rect.bottom - bottom_offset;
1188 let physical_size = size(DevicePixels(right - left), DevicePixels(bottom - top));
1189 Bounds {
1190 origin: logical_point(left as f32, top as f32, scale_factor),
1191 size: physical_size.to_pixels(scale_factor),
1192 }
1193}
1194
1195fn retrieve_window_placement(
1196 hwnd: HWND,
1197 display: WindowsDisplay,
1198 initial_bounds: Bounds<Pixels>,
1199 scale_factor: f32,
1200 border_offset: WindowBorderOffset,
1201) -> Result<WINDOWPLACEMENT> {
1202 let mut placement = WINDOWPLACEMENT {
1203 length: std::mem::size_of::<WINDOWPLACEMENT>() as u32,
1204 ..Default::default()
1205 };
1206 unsafe { GetWindowPlacement(hwnd, &mut placement)? };
1207 // the bounds may be not inside the display
1208 let bounds = if display.check_given_bounds(initial_bounds) {
1209 initial_bounds
1210 } else {
1211 display.default_bounds()
1212 };
1213 let bounds = bounds.to_device_pixels(scale_factor);
1214 placement.rcNormalPosition = calculate_window_rect(bounds, border_offset);
1215 Ok(placement)
1216}
1217
1218fn set_window_composition_attribute(hwnd: HWND, color: Option<Color>, state: u32) {
1219 let mut version = unsafe { std::mem::zeroed() };
1220 let status = unsafe { windows::Wdk::System::SystemServices::RtlGetVersion(&mut version) };
1221 if !status.is_ok() || version.dwBuildNumber < 17763 {
1222 return;
1223 }
1224
1225 unsafe {
1226 type SetWindowCompositionAttributeType =
1227 unsafe extern "system" fn(HWND, *mut WINDOWCOMPOSITIONATTRIBDATA) -> BOOL;
1228 let module_name = PCSTR::from_raw(c"user32.dll".as_ptr() as *const u8);
1229 let user32 = GetModuleHandleA(module_name);
1230 if user32.is_ok() {
1231 let func_name = PCSTR::from_raw(c"SetWindowCompositionAttribute".as_ptr() as *const u8);
1232 let set_window_composition_attribute: SetWindowCompositionAttributeType =
1233 std::mem::transmute(GetProcAddress(user32.unwrap(), func_name));
1234 let mut color = color.unwrap_or_default();
1235 let is_acrylic = state == 4;
1236 if is_acrylic && color.3 == 0 {
1237 color.3 = 1;
1238 }
1239 let accent = AccentPolicy {
1240 accent_state: state,
1241 accent_flags: if is_acrylic { 0 } else { 2 },
1242 gradient_color: (color.0 as u32)
1243 | ((color.1 as u32) << 8)
1244 | ((color.2 as u32) << 16)
1245 | ((color.3 as u32) << 24),
1246 animation_id: 0,
1247 };
1248 let mut data = WINDOWCOMPOSITIONATTRIBDATA {
1249 attrib: 0x13,
1250 pv_data: &accent as *const _ as *mut _,
1251 cb_data: std::mem::size_of::<AccentPolicy>(),
1252 };
1253 let _ = set_window_composition_attribute(hwnd, &mut data as *mut _ as _);
1254 } else {
1255 let _ = user32
1256 .inspect_err(|e| log::error!("Error getting module: {e}"))
1257 .ok();
1258 }
1259 }
1260}
1261
1262mod windows_renderer {
1263 use crate::platform::blade::{BladeContext, BladeRenderer, BladeSurfaceConfig};
1264 use raw_window_handle as rwh;
1265 use std::num::NonZeroIsize;
1266 use windows::Win32::{Foundation::HWND, UI::WindowsAndMessaging::GWLP_HINSTANCE};
1267
1268 use crate::{get_window_long, show_error};
1269
1270 pub(super) fn init(
1271 context: &BladeContext,
1272 hwnd: HWND,
1273 transparent: bool,
1274 ) -> anyhow::Result<BladeRenderer> {
1275 let raw = RawWindow { hwnd };
1276 let config = BladeSurfaceConfig {
1277 size: Default::default(),
1278 transparent,
1279 };
1280 BladeRenderer::new(context, &raw, config).inspect_err(|err| {
1281 show_error(
1282 "Error: Zed failed to initialize BladeRenderer",
1283 err.to_string(),
1284 )
1285 })
1286 }
1287
1288 struct RawWindow {
1289 hwnd: HWND,
1290 }
1291
1292 impl rwh::HasWindowHandle for RawWindow {
1293 fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
1294 Ok(unsafe {
1295 let hwnd = NonZeroIsize::new_unchecked(self.hwnd.0 as isize);
1296 let mut handle = rwh::Win32WindowHandle::new(hwnd);
1297 let hinstance = get_window_long(self.hwnd, GWLP_HINSTANCE);
1298 handle.hinstance = NonZeroIsize::new(hinstance);
1299 rwh::WindowHandle::borrow_raw(handle.into())
1300 })
1301 }
1302 }
1303
1304 impl rwh::HasDisplayHandle for RawWindow {
1305 fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
1306 let handle = rwh::WindowsDisplayHandle::new();
1307 Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
1308 }
1309 }
1310}
1311
1312#[cfg(test)]
1313mod tests {
1314 use super::ClickState;
1315 use crate::{DevicePixels, MouseButton, point};
1316 use std::time::Duration;
1317
1318 #[test]
1319 fn test_double_click_interval() {
1320 let mut state = ClickState::new();
1321 assert_eq!(
1322 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1323 1
1324 );
1325 assert_eq!(
1326 state.update(MouseButton::Right, point(DevicePixels(0), DevicePixels(0))),
1327 1
1328 );
1329 assert_eq!(
1330 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1331 1
1332 );
1333 assert_eq!(
1334 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1335 2
1336 );
1337 state.last_click -= Duration::from_millis(700);
1338 assert_eq!(
1339 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1340 1
1341 );
1342 }
1343
1344 #[test]
1345 fn test_double_click_spatial_tolerance() {
1346 let mut state = ClickState::new();
1347 assert_eq!(
1348 state.update(MouseButton::Left, point(DevicePixels(-3), DevicePixels(0))),
1349 1
1350 );
1351 assert_eq!(
1352 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(3))),
1353 2
1354 );
1355 assert_eq!(
1356 state.update(MouseButton::Right, point(DevicePixels(3), DevicePixels(2))),
1357 1
1358 );
1359 assert_eq!(
1360 state.update(MouseButton::Right, point(DevicePixels(10), DevicePixels(0))),
1361 1
1362 );
1363 }
1364}