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