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;
15use async_task::Runnable;
16use futures::channel::oneshot::{self, Receiver};
17use itertools::Itertools;
18use raw_window_handle as rwh;
19use smallvec::SmallVec;
20use windows::{
21 core::*,
22 Win32::{
23 Foundation::*,
24 Graphics::Gdi::*,
25 System::{Com::*, LibraryLoader::*, Ole::*, SystemServices::*},
26 UI::{Controls::*, HiDpi::*, Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*},
27 },
28};
29
30use crate::platform::blade::BladeRenderer;
31use crate::*;
32
33pub(crate) struct WindowsWindow(pub Rc<WindowsWindowStatePtr>);
34
35pub struct WindowsWindowState {
36 pub origin: Point<DevicePixels>,
37 pub physical_size: Size<DevicePixels>,
38 pub fullscreen_restore_bounds: Bounds<DevicePixels>,
39 pub scale_factor: f32,
40
41 pub callbacks: Callbacks,
42 pub input_handler: Option<PlatformInputHandler>,
43
44 pub renderer: BladeRenderer,
45
46 pub click_state: ClickState,
47 pub mouse_wheel_settings: MouseWheelSettings,
48 pub current_cursor: HCURSOR,
49
50 pub display: WindowsDisplay,
51 fullscreen: Option<StyleAndBounds>,
52 hwnd: HWND,
53}
54
55pub(crate) struct WindowsWindowStatePtr {
56 hwnd: HWND,
57 pub(crate) state: RefCell<WindowsWindowState>,
58 pub(crate) handle: AnyWindowHandle,
59 pub(crate) hide_title_bar: bool,
60 pub(crate) executor: ForegroundExecutor,
61 pub(crate) main_receiver: flume::Receiver<Runnable>,
62}
63
64impl WindowsWindowState {
65 fn new(
66 hwnd: HWND,
67 transparent: bool,
68 cs: &CREATESTRUCTW,
69 mouse_wheel_settings: MouseWheelSettings,
70 current_cursor: HCURSOR,
71 display: WindowsDisplay,
72 ) -> Self {
73 let origin = point(cs.x.into(), cs.y.into());
74 let physical_size = size(cs.cx.into(), cs.cy.into());
75 let fullscreen_restore_bounds = Bounds {
76 origin,
77 size: physical_size,
78 };
79 let scale_factor = {
80 let monitor_dpi = unsafe { GetDpiForWindow(hwnd) } as f32;
81 monitor_dpi / USER_DEFAULT_SCREEN_DPI as f32
82 };
83 let renderer = windows_renderer::windows_renderer(hwnd, transparent);
84 let callbacks = Callbacks::default();
85 let input_handler = None;
86 let click_state = ClickState::new();
87 let fullscreen = None;
88
89 Self {
90 origin,
91 physical_size,
92 fullscreen_restore_bounds,
93 scale_factor,
94 callbacks,
95 input_handler,
96 renderer,
97 click_state,
98 mouse_wheel_settings,
99 current_cursor,
100 display,
101 fullscreen,
102 hwnd,
103 }
104 }
105
106 #[inline]
107 pub(crate) fn is_fullscreen(&self) -> bool {
108 self.fullscreen.is_some()
109 }
110
111 pub(crate) fn is_maximized(&self) -> bool {
112 !self.is_fullscreen() && unsafe { IsZoomed(self.hwnd) }.as_bool()
113 }
114
115 fn bounds(&self) -> Bounds<DevicePixels> {
116 Bounds {
117 origin: self.origin,
118 size: self.physical_size,
119 }
120 }
121
122 fn window_bounds(&self) -> WindowBounds {
123 let placement = unsafe {
124 let mut placement = WINDOWPLACEMENT {
125 length: std::mem::size_of::<WINDOWPLACEMENT>() as u32,
126 ..Default::default()
127 };
128 GetWindowPlacement(self.hwnd, &mut placement).log_err();
129 placement
130 };
131 let bounds = Bounds {
132 origin: point(
133 DevicePixels(placement.rcNormalPosition.left),
134 DevicePixels(placement.rcNormalPosition.top),
135 ),
136 size: size(
137 DevicePixels(placement.rcNormalPosition.right - placement.rcNormalPosition.left),
138 DevicePixels(placement.rcNormalPosition.bottom - placement.rcNormalPosition.top),
139 ),
140 };
141
142 if self.is_fullscreen() {
143 WindowBounds::Fullscreen(self.fullscreen_restore_bounds)
144 } else if placement.showCmd == SW_SHOWMAXIMIZED.0 as u32 {
145 WindowBounds::Maximized(bounds)
146 } else {
147 WindowBounds::Windowed(bounds)
148 }
149 }
150
151 /// get the logical size of the app's drawable area.
152 ///
153 /// Currently, GPUI uses logical size of the app to handle mouse interactions (such as
154 /// whether the mouse collides with other elements of GPUI).
155 fn content_size(&self) -> Size<Pixels> {
156 logical_size(self.physical_size, self.scale_factor)
157 }
158
159 fn title_bar_padding(&self) -> Pixels {
160 // using USER_DEFAULT_SCREEN_DPI because GPUI handles the scale with the scale factor
161 let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, USER_DEFAULT_SCREEN_DPI) };
162 px(padding as f32)
163 }
164
165 fn title_bar_top_offset(&self) -> Pixels {
166 if self.is_maximized() {
167 self.title_bar_padding() * 2
168 } else {
169 px(0.)
170 }
171 }
172
173 fn title_bar_height(&self) -> Pixels {
174 // todo(windows) this is hard set to match the ui title bar
175 // in the future the ui title bar component will report the size
176 px(32.) + self.title_bar_top_offset()
177 }
178
179 pub(crate) fn caption_button_width(&self) -> Pixels {
180 // todo(windows) this is hard set to match the ui title bar
181 // in the future the ui title bar component will report the size
182 px(36.)
183 }
184
185 pub(crate) fn get_titlebar_rect(&self) -> anyhow::Result<RECT> {
186 let height = self.title_bar_height();
187 let mut rect = RECT::default();
188 unsafe { GetClientRect(self.hwnd, &mut rect) }?;
189 rect.bottom = rect.top + ((height.0 * self.scale_factor).round() as i32);
190 Ok(rect)
191 }
192}
193
194impl WindowsWindowStatePtr {
195 fn new(context: &WindowCreateContext, hwnd: HWND, cs: &CREATESTRUCTW) -> Rc<Self> {
196 let state = RefCell::new(WindowsWindowState::new(
197 hwnd,
198 context.transparent,
199 cs,
200 context.mouse_wheel_settings,
201 context.current_cursor,
202 context.display,
203 ));
204
205 Rc::new(Self {
206 state,
207 hwnd,
208 handle: context.handle,
209 hide_title_bar: context.hide_title_bar,
210 executor: context.executor.clone(),
211 main_receiver: context.main_receiver.clone(),
212 })
213 }
214}
215
216#[derive(Default)]
217pub(crate) struct Callbacks {
218 pub(crate) request_frame: Option<Box<dyn FnMut()>>,
219 pub(crate) input: Option<Box<dyn FnMut(crate::PlatformInput) -> DispatchEventResult>>,
220 pub(crate) active_status_change: Option<Box<dyn FnMut(bool)>>,
221 pub(crate) resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
222 pub(crate) moved: Option<Box<dyn FnMut()>>,
223 pub(crate) should_close: Option<Box<dyn FnMut() -> bool>>,
224 pub(crate) close: Option<Box<dyn FnOnce()>>,
225 pub(crate) appearance_changed: Option<Box<dyn FnMut()>>,
226}
227
228struct WindowCreateContext {
229 inner: Option<Rc<WindowsWindowStatePtr>>,
230 handle: AnyWindowHandle,
231 hide_title_bar: bool,
232 display: WindowsDisplay,
233 transparent: bool,
234 executor: ForegroundExecutor,
235 main_receiver: flume::Receiver<Runnable>,
236 mouse_wheel_settings: MouseWheelSettings,
237 current_cursor: HCURSOR,
238}
239
240impl WindowsWindow {
241 pub(crate) fn new(
242 handle: AnyWindowHandle,
243 params: WindowParams,
244 icon: HICON,
245 executor: ForegroundExecutor,
246 main_receiver: flume::Receiver<Runnable>,
247 mouse_wheel_settings: MouseWheelSettings,
248 current_cursor: HCURSOR,
249 ) -> Self {
250 let classname = register_wnd_class(icon);
251 let hide_title_bar = params
252 .titlebar
253 .as_ref()
254 .map(|titlebar| titlebar.appears_transparent)
255 .unwrap_or(false);
256 let windowname = HSTRING::from(
257 params
258 .titlebar
259 .as_ref()
260 .and_then(|titlebar| titlebar.title.as_ref())
261 .map(|title| title.as_ref())
262 .unwrap_or(""),
263 );
264 let dwstyle = WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX;
265 let hinstance = get_module_handle();
266 let mut context = WindowCreateContext {
267 inner: None,
268 handle,
269 hide_title_bar,
270 // todo(windows) move window to target monitor
271 // options.display_id
272 display: WindowsDisplay::primary_monitor().unwrap(),
273 transparent: params.window_background != WindowBackgroundAppearance::Opaque,
274 executor,
275 main_receiver,
276 mouse_wheel_settings,
277 current_cursor,
278 };
279 let lpparam = Some(&context as *const _ as *const _);
280 let raw_hwnd = unsafe {
281 CreateWindowExW(
282 WS_EX_APPWINDOW,
283 classname,
284 &windowname,
285 dwstyle,
286 CW_USEDEFAULT,
287 CW_USEDEFAULT,
288 CW_USEDEFAULT,
289 CW_USEDEFAULT,
290 None,
291 None,
292 hinstance,
293 lpparam,
294 )
295 };
296 let state_ptr = Rc::clone(context.inner.as_ref().unwrap());
297 register_drag_drop(state_ptr.clone());
298 let wnd = Self(state_ptr);
299
300 unsafe {
301 let mut placement = WINDOWPLACEMENT {
302 length: std::mem::size_of::<WINDOWPLACEMENT>() as u32,
303 ..Default::default()
304 };
305 GetWindowPlacement(raw_hwnd, &mut placement).log_err();
306 placement.rcNormalPosition.left = params.bounds.left().0;
307 placement.rcNormalPosition.right = params.bounds.right().0;
308 placement.rcNormalPosition.top = params.bounds.top().0;
309 placement.rcNormalPosition.bottom = params.bounds.bottom().0;
310 SetWindowPlacement(raw_hwnd, &placement).log_err();
311 }
312 unsafe { ShowWindow(raw_hwnd, SW_SHOW) };
313
314 wnd
315 }
316}
317
318impl rwh::HasWindowHandle for WindowsWindow {
319 fn window_handle(&self) -> std::result::Result<rwh::WindowHandle<'_>, rwh::HandleError> {
320 let raw =
321 rwh::Win32WindowHandle::new(unsafe { NonZeroIsize::new_unchecked(self.0.hwnd.0) })
322 .into();
323 Ok(unsafe { rwh::WindowHandle::borrow_raw(raw) })
324 }
325}
326
327// todo(windows)
328impl rwh::HasDisplayHandle for WindowsWindow {
329 fn display_handle(&self) -> std::result::Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
330 unimplemented!()
331 }
332}
333
334impl Drop for WindowsWindow {
335 fn drop(&mut self) {
336 self.0.state.borrow_mut().renderer.destroy();
337 // clone this `Rc` to prevent early release of the pointer
338 let this = self.0.clone();
339 self.0
340 .executor
341 .spawn(async move {
342 let handle = this.hwnd;
343 unsafe {
344 RevokeDragDrop(handle).log_err();
345 DestroyWindow(handle).log_err();
346 }
347 })
348 .detach();
349 }
350}
351
352impl PlatformWindow for WindowsWindow {
353 fn bounds(&self) -> Bounds<DevicePixels> {
354 self.0.state.borrow().bounds()
355 }
356
357 fn is_maximized(&self) -> bool {
358 self.0.state.borrow().is_maximized()
359 }
360
361 fn window_bounds(&self) -> WindowBounds {
362 self.0.state.borrow().window_bounds()
363 }
364
365 /// get the logical size of the app's drawable area.
366 ///
367 /// Currently, GPUI uses logical size of the app to handle mouse interactions (such as
368 /// whether the mouse collides with other elements of GPUI).
369 fn content_size(&self) -> Size<Pixels> {
370 self.0.state.borrow().content_size()
371 }
372
373 fn scale_factor(&self) -> f32 {
374 self.0.state.borrow().scale_factor
375 }
376
377 // todo(windows)
378 fn appearance(&self) -> WindowAppearance {
379 WindowAppearance::Dark
380 }
381
382 fn display(&self) -> Rc<dyn PlatformDisplay> {
383 Rc::new(self.0.state.borrow().display)
384 }
385
386 fn mouse_position(&self) -> Point<Pixels> {
387 let scale_factor = self.scale_factor();
388 let point = unsafe {
389 let mut point: POINT = std::mem::zeroed();
390 GetCursorPos(&mut point)
391 .context("unable to get cursor position")
392 .log_err();
393 ScreenToClient(self.0.hwnd, &mut point);
394 point
395 };
396 logical_point(point.x as f32, point.y as f32, scale_factor)
397 }
398
399 // todo(windows)
400 fn modifiers(&self) -> Modifiers {
401 Modifiers::none()
402 }
403
404 fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
405 self.0.state.borrow_mut().input_handler = Some(input_handler);
406 }
407
408 fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
409 self.0.state.borrow_mut().input_handler.take()
410 }
411
412 fn prompt(
413 &self,
414 level: PromptLevel,
415 msg: &str,
416 detail: Option<&str>,
417 answers: &[&str],
418 ) -> Option<Receiver<usize>> {
419 let (done_tx, done_rx) = oneshot::channel();
420 let msg = msg.to_string();
421 let detail_string = match detail {
422 Some(info) => Some(info.to_string()),
423 None => None,
424 };
425 let answers = answers.iter().map(|s| s.to_string()).collect::<Vec<_>>();
426 let handle = self.0.hwnd;
427 self.0
428 .executor
429 .spawn(async move {
430 unsafe {
431 let mut config;
432 config = std::mem::zeroed::<TASKDIALOGCONFIG>();
433 config.cbSize = std::mem::size_of::<TASKDIALOGCONFIG>() as _;
434 config.hwndParent = handle;
435 let title;
436 let main_icon;
437 match level {
438 crate::PromptLevel::Info => {
439 title = windows::core::w!("Info");
440 main_icon = TD_INFORMATION_ICON;
441 }
442 crate::PromptLevel::Warning => {
443 title = windows::core::w!("Warning");
444 main_icon = TD_WARNING_ICON;
445 }
446 crate::PromptLevel::Critical | crate::PromptLevel::Destructive => {
447 title = windows::core::w!("Critical");
448 main_icon = TD_ERROR_ICON;
449 }
450 };
451 config.pszWindowTitle = title;
452 config.Anonymous1.pszMainIcon = main_icon;
453 let instruction = msg.encode_utf16().chain(Some(0)).collect_vec();
454 config.pszMainInstruction = PCWSTR::from_raw(instruction.as_ptr());
455 let hints_encoded;
456 if let Some(ref hints) = detail_string {
457 hints_encoded = hints.encode_utf16().chain(Some(0)).collect_vec();
458 config.pszContent = PCWSTR::from_raw(hints_encoded.as_ptr());
459 };
460 let mut buttons = Vec::new();
461 let mut btn_encoded = Vec::new();
462 for (index, btn_string) in answers.iter().enumerate() {
463 let encoded = btn_string.encode_utf16().chain(Some(0)).collect_vec();
464 buttons.push(TASKDIALOG_BUTTON {
465 nButtonID: index as _,
466 pszButtonText: PCWSTR::from_raw(encoded.as_ptr()),
467 });
468 btn_encoded.push(encoded);
469 }
470 config.cButtons = buttons.len() as _;
471 config.pButtons = buttons.as_ptr();
472
473 config.pfCallback = None;
474 let mut res = std::mem::zeroed();
475 let _ = TaskDialogIndirect(&config, Some(&mut res), None, None)
476 .inspect_err(|e| log::error!("unable to create task dialog: {}", e));
477
478 let _ = done_tx.send(res as usize);
479 }
480 })
481 .detach();
482
483 Some(done_rx)
484 }
485
486 fn activate(&self) {
487 let hwnd = self.0.hwnd;
488 unsafe { SetActiveWindow(hwnd) };
489 unsafe { SetFocus(hwnd) };
490 unsafe { SetForegroundWindow(hwnd) };
491 }
492
493 fn is_active(&self) -> bool {
494 self.0.hwnd == unsafe { GetActiveWindow() }
495 }
496
497 fn set_title(&mut self, title: &str) {
498 unsafe { SetWindowTextW(self.0.hwnd, &HSTRING::from(title)) }
499 .inspect_err(|e| log::error!("Set title failed: {e}"))
500 .ok();
501 }
502
503 fn set_app_id(&mut self, _app_id: &str) {}
504
505 fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance) {
506 self.0
507 .state
508 .borrow_mut()
509 .renderer
510 .update_transparency(background_appearance != WindowBackgroundAppearance::Opaque);
511 }
512
513 // todo(windows)
514 fn set_edited(&mut self, _edited: bool) {}
515
516 // todo(windows)
517 fn show_character_palette(&self) {}
518
519 fn minimize(&self) {
520 unsafe { ShowWindowAsync(self.0.hwnd, SW_MINIMIZE) };
521 }
522
523 fn zoom(&self) {
524 unsafe { ShowWindowAsync(self.0.hwnd, SW_MAXIMIZE) };
525 }
526
527 fn toggle_fullscreen(&self) {
528 let state_ptr = self.0.clone();
529 self.0
530 .executor
531 .spawn(async move {
532 let mut lock = state_ptr.state.borrow_mut();
533 lock.fullscreen_restore_bounds = Bounds {
534 origin: lock.origin,
535 size: lock.physical_size,
536 };
537 let StyleAndBounds {
538 style,
539 x,
540 y,
541 cx,
542 cy,
543 } = if let Some(state) = lock.fullscreen.take() {
544 state
545 } else {
546 let style =
547 WINDOW_STYLE(unsafe { get_window_long(state_ptr.hwnd, GWL_STYLE) } as _);
548 let mut rc = RECT::default();
549 unsafe { GetWindowRect(state_ptr.hwnd, &mut rc) }.log_err();
550 let _ = lock.fullscreen.insert(StyleAndBounds {
551 style,
552 x: rc.left,
553 y: rc.top,
554 cx: rc.right - rc.left,
555 cy: rc.bottom - rc.top,
556 });
557 let style = style
558 & !(WS_THICKFRAME
559 | WS_SYSMENU
560 | WS_MAXIMIZEBOX
561 | WS_MINIMIZEBOX
562 | WS_CAPTION);
563 let bounds = lock.display.bounds();
564 StyleAndBounds {
565 style,
566 x: bounds.left().0,
567 y: bounds.top().0,
568 cx: bounds.size.width.0,
569 cy: bounds.size.height.0,
570 }
571 };
572 drop(lock);
573 unsafe { set_window_long(state_ptr.hwnd, GWL_STYLE, style.0 as isize) };
574 unsafe {
575 SetWindowPos(
576 state_ptr.hwnd,
577 HWND::default(),
578 x,
579 y,
580 cx,
581 cy,
582 SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOZORDER,
583 )
584 }
585 .log_err();
586 })
587 .detach();
588 }
589
590 fn is_fullscreen(&self) -> bool {
591 self.0.state.borrow().is_fullscreen()
592 }
593
594 fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
595 self.0.state.borrow_mut().callbacks.request_frame = Some(callback);
596 }
597
598 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>) {
599 self.0.state.borrow_mut().callbacks.input = Some(callback);
600 }
601
602 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
603 self.0.state.borrow_mut().callbacks.active_status_change = Some(callback);
604 }
605
606 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
607 self.0.state.borrow_mut().callbacks.resize = Some(callback);
608 }
609
610 fn on_moved(&self, callback: Box<dyn FnMut()>) {
611 self.0.state.borrow_mut().callbacks.moved = Some(callback);
612 }
613
614 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
615 self.0.state.borrow_mut().callbacks.should_close = Some(callback);
616 }
617
618 fn on_close(&self, callback: Box<dyn FnOnce()>) {
619 self.0.state.borrow_mut().callbacks.close = Some(callback);
620 }
621
622 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
623 self.0.state.borrow_mut().callbacks.appearance_changed = Some(callback);
624 }
625
626 fn draw(&self, scene: &Scene) {
627 self.0.state.borrow_mut().renderer.draw(scene)
628 }
629
630 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
631 self.0.state.borrow().renderer.sprite_atlas().clone()
632 }
633
634 fn get_raw_handle(&self) -> HWND {
635 self.0.hwnd
636 }
637}
638
639#[implement(IDropTarget)]
640struct WindowsDragDropHandler(pub Rc<WindowsWindowStatePtr>);
641
642impl WindowsDragDropHandler {
643 fn handle_drag_drop(&self, input: PlatformInput) {
644 let mut lock = self.0.state.borrow_mut();
645 if let Some(mut func) = lock.callbacks.input.take() {
646 drop(lock);
647 func(input);
648 self.0.state.borrow_mut().callbacks.input = Some(func);
649 }
650 }
651}
652
653#[allow(non_snake_case)]
654impl IDropTarget_Impl for WindowsDragDropHandler {
655 fn DragEnter(
656 &self,
657 pdataobj: Option<&IDataObject>,
658 _grfkeystate: MODIFIERKEYS_FLAGS,
659 pt: &POINTL,
660 pdweffect: *mut DROPEFFECT,
661 ) -> windows::core::Result<()> {
662 unsafe {
663 let Some(idata_obj) = pdataobj else {
664 log::info!("no dragging file or directory detected");
665 return Ok(());
666 };
667 let config = FORMATETC {
668 cfFormat: CF_HDROP.0,
669 ptd: std::ptr::null_mut() as _,
670 dwAspect: DVASPECT_CONTENT.0,
671 lindex: -1,
672 tymed: TYMED_HGLOBAL.0 as _,
673 };
674 if idata_obj.QueryGetData(&config as _) == S_OK {
675 *pdweffect = DROPEFFECT_LINK;
676 let Some(mut idata) = idata_obj.GetData(&config as _).log_err() else {
677 return Ok(());
678 };
679 if idata.u.hGlobal.is_invalid() {
680 return Ok(());
681 }
682 let hdrop = idata.u.hGlobal.0 as *mut HDROP;
683 let mut paths = SmallVec::<[PathBuf; 2]>::new();
684 let file_count = DragQueryFileW(*hdrop, DRAGDROP_GET_FILES_COUNT, None);
685 for file_index in 0..file_count {
686 let filename_length = DragQueryFileW(*hdrop, file_index, None) as usize;
687 let mut buffer = vec![0u16; filename_length + 1];
688 let ret = DragQueryFileW(*hdrop, file_index, Some(buffer.as_mut_slice()));
689 if ret == 0 {
690 log::error!("unable to read file name");
691 continue;
692 }
693 if let Some(file_name) =
694 String::from_utf16(&buffer[0..filename_length]).log_err()
695 {
696 if let Some(path) = PathBuf::from_str(&file_name).log_err() {
697 paths.push(path);
698 }
699 }
700 }
701 ReleaseStgMedium(&mut idata);
702 let mut cursor_position = POINT { x: pt.x, y: pt.y };
703 ScreenToClient(self.0.hwnd, &mut cursor_position);
704 let scale_factor = self.0.state.borrow().scale_factor;
705 let input = PlatformInput::FileDrop(FileDropEvent::Entered {
706 position: logical_point(
707 cursor_position.x as f32,
708 cursor_position.y as f32,
709 scale_factor,
710 ),
711 paths: ExternalPaths(paths),
712 });
713 self.handle_drag_drop(input);
714 } else {
715 *pdweffect = DROPEFFECT_NONE;
716 }
717 }
718 Ok(())
719 }
720
721 fn DragOver(
722 &self,
723 _grfkeystate: MODIFIERKEYS_FLAGS,
724 pt: &POINTL,
725 _pdweffect: *mut DROPEFFECT,
726 ) -> windows::core::Result<()> {
727 let mut cursor_position = POINT { x: pt.x, y: pt.y };
728 unsafe {
729 ScreenToClient(self.0.hwnd, &mut cursor_position);
730 }
731 let scale_factor = self.0.state.borrow().scale_factor;
732 let input = PlatformInput::FileDrop(FileDropEvent::Pending {
733 position: logical_point(
734 cursor_position.x as f32,
735 cursor_position.y as f32,
736 scale_factor,
737 ),
738 });
739 self.handle_drag_drop(input);
740
741 Ok(())
742 }
743
744 fn DragLeave(&self) -> windows::core::Result<()> {
745 let input = PlatformInput::FileDrop(FileDropEvent::Exited);
746 self.handle_drag_drop(input);
747
748 Ok(())
749 }
750
751 fn Drop(
752 &self,
753 _pdataobj: Option<&IDataObject>,
754 _grfkeystate: MODIFIERKEYS_FLAGS,
755 pt: &POINTL,
756 _pdweffect: *mut DROPEFFECT,
757 ) -> windows::core::Result<()> {
758 let mut cursor_position = POINT { x: pt.x, y: pt.y };
759 unsafe {
760 ScreenToClient(self.0.hwnd, &mut cursor_position);
761 }
762 let scale_factor = self.0.state.borrow().scale_factor;
763 let input = PlatformInput::FileDrop(FileDropEvent::Submit {
764 position: logical_point(
765 cursor_position.x as f32,
766 cursor_position.y as f32,
767 scale_factor,
768 ),
769 });
770 self.handle_drag_drop(input);
771
772 Ok(())
773 }
774}
775
776#[derive(Debug)]
777pub(crate) struct ClickState {
778 button: MouseButton,
779 last_click: Instant,
780 last_position: Point<DevicePixels>,
781 pub(crate) current_count: usize,
782}
783
784impl ClickState {
785 pub fn new() -> Self {
786 ClickState {
787 button: MouseButton::Left,
788 last_click: Instant::now(),
789 last_position: Point::default(),
790 current_count: 0,
791 }
792 }
793
794 /// update self and return the needed click count
795 pub fn update(&mut self, button: MouseButton, new_position: Point<DevicePixels>) -> usize {
796 if self.button == button && self.is_double_click(new_position) {
797 self.current_count += 1;
798 } else {
799 self.current_count = 1;
800 }
801 self.last_click = Instant::now();
802 self.last_position = new_position;
803 self.button = button;
804
805 self.current_count
806 }
807
808 #[inline]
809 fn is_double_click(&self, new_position: Point<DevicePixels>) -> bool {
810 let diff = self.last_position - new_position;
811
812 self.last_click.elapsed() < DOUBLE_CLICK_INTERVAL
813 && diff.x.0.abs() <= DOUBLE_CLICK_SPATIAL_TOLERANCE
814 && diff.y.0.abs() <= DOUBLE_CLICK_SPATIAL_TOLERANCE
815 }
816}
817
818struct StyleAndBounds {
819 style: WINDOW_STYLE,
820 x: i32,
821 y: i32,
822 cx: i32,
823 cy: i32,
824}
825
826fn register_wnd_class(icon_handle: HICON) -> PCWSTR {
827 const CLASS_NAME: PCWSTR = w!("Zed::Window");
828
829 static ONCE: Once = Once::new();
830 ONCE.call_once(|| {
831 let wc = WNDCLASSW {
832 lpfnWndProc: Some(wnd_proc),
833 hIcon: icon_handle,
834 lpszClassName: PCWSTR(CLASS_NAME.as_ptr()),
835 style: CS_HREDRAW | CS_VREDRAW,
836 hInstance: get_module_handle().into(),
837 ..Default::default()
838 };
839 unsafe { RegisterClassW(&wc) };
840 });
841
842 CLASS_NAME
843}
844
845unsafe extern "system" fn wnd_proc(
846 hwnd: HWND,
847 msg: u32,
848 wparam: WPARAM,
849 lparam: LPARAM,
850) -> LRESULT {
851 if msg == WM_NCCREATE {
852 let cs = lparam.0 as *const CREATESTRUCTW;
853 let cs = unsafe { &*cs };
854 let ctx = cs.lpCreateParams as *mut WindowCreateContext;
855 let ctx = unsafe { &mut *ctx };
856 let state_ptr = WindowsWindowStatePtr::new(ctx, hwnd, cs);
857 let weak = Box::new(Rc::downgrade(&state_ptr));
858 unsafe { set_window_long(hwnd, GWLP_USERDATA, Box::into_raw(weak) as isize) };
859 ctx.inner = Some(state_ptr);
860 return LRESULT(1);
861 }
862 let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak<WindowsWindowStatePtr>;
863 if ptr.is_null() {
864 return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) };
865 }
866 let inner = unsafe { &*ptr };
867 let r = if let Some(state) = inner.upgrade() {
868 handle_msg(hwnd, msg, wparam, lparam, state)
869 } else {
870 unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }
871 };
872 if msg == WM_NCDESTROY {
873 unsafe { set_window_long(hwnd, GWLP_USERDATA, 0) };
874 unsafe { drop(Box::from_raw(ptr)) };
875 }
876 r
877}
878
879pub(crate) fn try_get_window_inner(hwnd: HWND) -> Option<Rc<WindowsWindowStatePtr>> {
880 if hwnd == HWND(0) {
881 return None;
882 }
883
884 let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak<WindowsWindowStatePtr>;
885 if !ptr.is_null() {
886 let inner = unsafe { &*ptr };
887 inner.upgrade()
888 } else {
889 None
890 }
891}
892
893fn get_module_handle() -> HMODULE {
894 unsafe {
895 let mut h_module = std::mem::zeroed();
896 GetModuleHandleExW(
897 GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
898 windows::core::w!("ZedModule"),
899 &mut h_module,
900 )
901 .expect("Unable to get module handle"); // this should never fail
902
903 h_module
904 }
905}
906
907fn register_drag_drop(state_ptr: Rc<WindowsWindowStatePtr>) {
908 let window_handle = state_ptr.hwnd;
909 let handler = WindowsDragDropHandler(state_ptr);
910 // The lifetime of `IDropTarget` is handled by Windows, it wont release untill
911 // we call `RevokeDragDrop`.
912 // So, it's safe to drop it here.
913 let drag_drop_handler: IDropTarget = handler.into();
914 unsafe {
915 RegisterDragDrop(window_handle, &drag_drop_handler)
916 .expect("unable to register drag-drop event")
917 };
918}
919
920// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-dragqueryfilew
921const DRAGDROP_GET_FILES_COUNT: u32 = 0xFFFFFFFF;
922// https://learn.microsoft.com/en-us/windows/win32/controls/ttm-setdelaytime?redirectedfrom=MSDN
923const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(500);
924// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsystemmetrics
925const DOUBLE_CLICK_SPATIAL_TOLERANCE: i32 = 4;
926
927mod windows_renderer {
928 use std::{num::NonZeroIsize, sync::Arc};
929
930 use blade_graphics as gpu;
931 use raw_window_handle as rwh;
932 use windows::Win32::{Foundation::HWND, UI::WindowsAndMessaging::GWLP_HINSTANCE};
933
934 use crate::{
935 get_window_long,
936 platform::blade::{BladeRenderer, BladeSurfaceConfig},
937 };
938
939 pub(super) fn windows_renderer(hwnd: HWND, transparent: bool) -> BladeRenderer {
940 let raw = RawWindow { hwnd: hwnd.0 };
941 let gpu: Arc<gpu::Context> = Arc::new(
942 unsafe {
943 gpu::Context::init_windowed(
944 &raw,
945 gpu::ContextDesc {
946 validation: false,
947 capture: false,
948 overlay: false,
949 },
950 )
951 }
952 .unwrap(),
953 );
954 let config = BladeSurfaceConfig {
955 size: gpu::Extent::default(),
956 transparent,
957 };
958
959 BladeRenderer::new(gpu, config)
960 }
961
962 struct RawWindow {
963 hwnd: isize,
964 }
965
966 impl rwh::HasWindowHandle for RawWindow {
967 fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
968 Ok(unsafe {
969 let hwnd = NonZeroIsize::new_unchecked(self.hwnd);
970 let mut handle = rwh::Win32WindowHandle::new(hwnd);
971 let hinstance = get_window_long(HWND(self.hwnd), GWLP_HINSTANCE);
972 handle.hinstance = NonZeroIsize::new(hinstance);
973 rwh::WindowHandle::borrow_raw(handle.into())
974 })
975 }
976 }
977
978 impl rwh::HasDisplayHandle for RawWindow {
979 fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
980 let handle = rwh::WindowsDisplayHandle::new();
981 Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
982 }
983 }
984}
985
986#[cfg(test)]
987mod tests {
988 use super::ClickState;
989 use crate::{point, DevicePixels, MouseButton};
990 use std::time::Duration;
991
992 #[test]
993 fn test_double_click_interval() {
994 let mut state = ClickState::new();
995 assert_eq!(
996 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
997 1
998 );
999 assert_eq!(
1000 state.update(MouseButton::Right, point(DevicePixels(0), DevicePixels(0))),
1001 1
1002 );
1003 assert_eq!(
1004 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1005 1
1006 );
1007 assert_eq!(
1008 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1009 2
1010 );
1011 state.last_click -= Duration::from_millis(700);
1012 assert_eq!(
1013 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1014 1
1015 );
1016 }
1017
1018 #[test]
1019 fn test_double_click_spatial_tolerance() {
1020 let mut state = ClickState::new();
1021 assert_eq!(
1022 state.update(MouseButton::Left, point(DevicePixels(-3), DevicePixels(0))),
1023 1
1024 );
1025 assert_eq!(
1026 state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(3))),
1027 2
1028 );
1029 assert_eq!(
1030 state.update(MouseButton::Right, point(DevicePixels(3), DevicePixels(2))),
1031 1
1032 );
1033 assert_eq!(
1034 state.update(MouseButton::Right, point(DevicePixels(10), DevicePixels(0))),
1035 1
1036 );
1037 }
1038}