window.rs

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