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