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