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