window.rs

   1#![deny(unsafe_op_in_unsafe_fn)]
   2
   3use std::{
   4    any::Any,
   5    cell::{Cell, RefCell},
   6    ffi::c_void,
   7    iter::once,
   8    num::NonZeroIsize,
   9    path::PathBuf,
  10    rc::{Rc, Weak},
  11    str::FromStr,
  12    sync::{Arc, Once},
  13    time::{Duration, Instant},
  14};
  15
  16use ::util::ResultExt;
  17use anyhow::Context;
  18use blade_graphics as gpu;
  19use futures::channel::oneshot::{self, Receiver};
  20use itertools::Itertools;
  21use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
  22use smallvec::SmallVec;
  23use std::result::Result;
  24use windows::{
  25    core::*,
  26    Win32::{
  27        Foundation::*,
  28        Graphics::Gdi::*,
  29        System::{Com::*, Ole::*, SystemServices::*},
  30        UI::{
  31            Controls::*,
  32            HiDpi::*,
  33            Input::{Ime::*, KeyboardAndMouse::*},
  34            Shell::*,
  35            WindowsAndMessaging::*,
  36        },
  37    },
  38};
  39
  40use crate::platform::blade::BladeRenderer;
  41use crate::*;
  42
  43pub(crate) struct WindowsWindowInner {
  44    hwnd: HWND,
  45    origin: Cell<Point<DevicePixels>>,
  46    physical_size: Cell<Size<DevicePixels>>,
  47    scale_factor: Cell<f32>,
  48    input_handler: Cell<Option<PlatformInputHandler>>,
  49    renderer: RefCell<BladeRenderer>,
  50    callbacks: RefCell<Callbacks>,
  51    platform_inner: Rc<WindowsPlatformInner>,
  52    pub(crate) handle: AnyWindowHandle,
  53    hide_title_bar: bool,
  54    display: RefCell<Rc<WindowsDisplay>>,
  55    last_ime_input: RefCell<Option<String>>,
  56    click_state: RefCell<ClickState>,
  57    fullscreen: Cell<Option<StyleAndBounds>>,
  58}
  59
  60impl WindowsWindowInner {
  61    fn new(
  62        hwnd: HWND,
  63        cs: &CREATESTRUCTW,
  64        platform_inner: Rc<WindowsPlatformInner>,
  65        handle: AnyWindowHandle,
  66        hide_title_bar: bool,
  67        display: Rc<WindowsDisplay>,
  68    ) -> Self {
  69        let monitor_dpi = unsafe { GetDpiForWindow(hwnd) } as f32;
  70        let origin = Cell::new(Point {
  71            x: DevicePixels(cs.x),
  72            y: DevicePixels(cs.y),
  73        });
  74        let physical_size = Cell::new(Size {
  75            width: DevicePixels(cs.cx),
  76            height: DevicePixels(cs.cy),
  77        });
  78        let scale_factor = Cell::new(monitor_dpi / USER_DEFAULT_SCREEN_DPI as f32);
  79        let input_handler = Cell::new(None);
  80        struct RawWindow {
  81            hwnd: *mut c_void,
  82        }
  83        unsafe impl blade_rwh::HasRawWindowHandle for RawWindow {
  84            fn raw_window_handle(&self) -> blade_rwh::RawWindowHandle {
  85                let mut handle = blade_rwh::Win32WindowHandle::empty();
  86                handle.hwnd = self.hwnd;
  87                handle.into()
  88            }
  89        }
  90        unsafe impl blade_rwh::HasRawDisplayHandle for RawWindow {
  91            fn raw_display_handle(&self) -> blade_rwh::RawDisplayHandle {
  92                blade_rwh::WindowsDisplayHandle::empty().into()
  93            }
  94        }
  95        let raw = RawWindow { hwnd: hwnd.0 as _ };
  96        let gpu = Arc::new(
  97            unsafe {
  98                gpu::Context::init_windowed(
  99                    &raw,
 100                    gpu::ContextDesc {
 101                        validation: false,
 102                        capture: false,
 103                        overlay: false,
 104                    },
 105                )
 106            }
 107            .unwrap(),
 108        );
 109        let extent = gpu::Extent {
 110            width: 1,
 111            height: 1,
 112            depth: 1,
 113        };
 114        let renderer = RefCell::new(BladeRenderer::new(gpu, extent));
 115        let callbacks = RefCell::new(Callbacks::default());
 116        let display = RefCell::new(display);
 117        let last_ime_input = RefCell::new(None);
 118        let click_state = RefCell::new(ClickState::new());
 119        let fullscreen = Cell::new(None);
 120        Self {
 121            hwnd,
 122            origin,
 123            physical_size,
 124            scale_factor,
 125            input_handler,
 126            renderer,
 127            callbacks,
 128            platform_inner,
 129            handle,
 130            hide_title_bar,
 131            display,
 132            last_ime_input,
 133            click_state,
 134            fullscreen,
 135        }
 136    }
 137
 138    fn is_maximized(&self) -> bool {
 139        !self.is_fullscreen() && unsafe { IsZoomed(self.hwnd) }.as_bool()
 140    }
 141
 142    fn is_minimized(&self) -> bool {
 143        unsafe { IsIconic(self.hwnd) }.as_bool()
 144    }
 145
 146    fn is_fullscreen(&self) -> bool {
 147        let fullscreen = self.fullscreen.take();
 148        let is_fullscreen = fullscreen.is_some();
 149        self.fullscreen.set(fullscreen);
 150        is_fullscreen
 151    }
 152
 153    async fn toggle_fullscreen(self: Rc<Self>) {
 154        let StyleAndBounds {
 155            style,
 156            x,
 157            y,
 158            cx,
 159            cy,
 160        } = if let Some(state) = self.fullscreen.take() {
 161            state
 162        } else {
 163            let style = WINDOW_STYLE(unsafe { get_window_long(self.hwnd, GWL_STYLE) } as _);
 164            let mut rc = RECT::default();
 165            unsafe { GetWindowRect(self.hwnd, &mut rc) }.log_err();
 166            self.fullscreen.set(Some(StyleAndBounds {
 167                style,
 168                x: rc.left,
 169                y: rc.top,
 170                cx: rc.right - rc.left,
 171                cy: rc.bottom - rc.top,
 172            }));
 173            let style = style
 174                & !(WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_CAPTION);
 175            let bounds = self.display.borrow().clone().bounds();
 176            StyleAndBounds {
 177                style,
 178                x: bounds.left().0,
 179                y: bounds.top().0,
 180                cx: bounds.size.width.0,
 181                cy: bounds.size.height.0,
 182            }
 183        };
 184        unsafe { set_window_long(self.hwnd, GWL_STYLE, style.0 as isize) };
 185        unsafe {
 186            SetWindowPos(
 187                self.hwnd,
 188                HWND::default(),
 189                x,
 190                y,
 191                cx,
 192                cy,
 193                SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOZORDER,
 194            )
 195        }
 196        .log_err();
 197    }
 198
 199    pub(crate) fn title_bar_padding(&self) -> Pixels {
 200        // using USER_DEFAULT_SCREEN_DPI because GPUI handles the scale with the scale factor
 201        let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, USER_DEFAULT_SCREEN_DPI) };
 202        px(padding as f32)
 203    }
 204
 205    pub(crate) fn title_bar_top_offset(&self) -> Pixels {
 206        if self.is_maximized() {
 207            self.title_bar_padding() * 2
 208        } else {
 209            px(0.)
 210        }
 211    }
 212
 213    pub(crate) fn title_bar_height(&self) -> Pixels {
 214        // todo(windows) this is hard set to match the ui title bar
 215        //               in the future the ui title bar component will report the size
 216        px(32.) + self.title_bar_top_offset()
 217    }
 218
 219    pub(crate) fn caption_button_width(&self) -> Pixels {
 220        // todo(windows) this is hard set to match the ui title bar
 221        //               in the future the ui title bar component will report the size
 222        px(36.)
 223    }
 224
 225    fn get_titlebar_rect(&self) -> anyhow::Result<RECT> {
 226        let height = self.title_bar_height();
 227        let mut rect = RECT::default();
 228        unsafe { GetClientRect(self.hwnd, &mut rect) }?;
 229        rect.bottom = rect.top + ((height.0 * self.scale_factor.get()).round() as i32);
 230        Ok(rect)
 231    }
 232
 233    fn is_virtual_key_pressed(&self, vkey: VIRTUAL_KEY) -> bool {
 234        unsafe { GetKeyState(vkey.0 as i32) < 0 }
 235    }
 236
 237    fn current_modifiers(&self) -> Modifiers {
 238        Modifiers {
 239            control: self.is_virtual_key_pressed(VK_CONTROL),
 240            alt: self.is_virtual_key_pressed(VK_MENU),
 241            shift: self.is_virtual_key_pressed(VK_SHIFT),
 242            platform: self.is_virtual_key_pressed(VK_LWIN) || self.is_virtual_key_pressed(VK_RWIN),
 243            function: false,
 244        }
 245    }
 246
 247    /// mark window client rect to be re-drawn
 248    /// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-invalidaterect
 249    pub(crate) fn invalidate_client_area(&self) {
 250        unsafe { InvalidateRect(self.hwnd, None, FALSE) };
 251    }
 252
 253    fn handle_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
 254        let handled = match msg {
 255            WM_ACTIVATE => self.handle_activate_msg(wparam),
 256            WM_CREATE => self.handle_create_msg(lparam),
 257            WM_MOVE => self.handle_move_msg(lparam),
 258            WM_SIZE => self.handle_size_msg(lparam),
 259            WM_NCCALCSIZE => self.handle_calc_client_size(wparam, lparam),
 260            WM_DPICHANGED => self.handle_dpi_changed_msg(wparam, lparam),
 261            WM_NCHITTEST => self.handle_hit_test_msg(msg, wparam, lparam),
 262            WM_PAINT => self.handle_paint_msg(),
 263            WM_CLOSE => self.handle_close_msg(),
 264            WM_DESTROY => self.handle_destroy_msg(),
 265            WM_MOUSEMOVE => self.handle_mouse_move_msg(lparam, wparam),
 266            WM_NCMOUSEMOVE => self.handle_nc_mouse_move_msg(lparam),
 267            WM_NCLBUTTONDOWN => self.handle_nc_mouse_down_msg(MouseButton::Left, wparam, lparam),
 268            WM_NCRBUTTONDOWN => self.handle_nc_mouse_down_msg(MouseButton::Right, wparam, lparam),
 269            WM_NCMBUTTONDOWN => self.handle_nc_mouse_down_msg(MouseButton::Middle, wparam, lparam),
 270            WM_NCLBUTTONUP => self.handle_nc_mouse_up_msg(MouseButton::Left, wparam, lparam),
 271            WM_NCRBUTTONUP => self.handle_nc_mouse_up_msg(MouseButton::Right, wparam, lparam),
 272            WM_NCMBUTTONUP => self.handle_nc_mouse_up_msg(MouseButton::Middle, wparam, lparam),
 273            WM_LBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Left, lparam),
 274            WM_RBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Right, lparam),
 275            WM_MBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Middle, lparam),
 276            WM_XBUTTONDOWN => self.handle_xbutton_msg(wparam, lparam, Self::handle_mouse_down_msg),
 277            WM_LBUTTONUP => self.handle_mouse_up_msg(MouseButton::Left, lparam),
 278            WM_RBUTTONUP => self.handle_mouse_up_msg(MouseButton::Right, lparam),
 279            WM_MBUTTONUP => self.handle_mouse_up_msg(MouseButton::Middle, lparam),
 280            WM_XBUTTONUP => self.handle_xbutton_msg(wparam, lparam, Self::handle_mouse_up_msg),
 281            WM_MOUSEWHEEL => self.handle_mouse_wheel_msg(wparam, lparam),
 282            WM_MOUSEHWHEEL => self.handle_mouse_horizontal_wheel_msg(wparam, lparam),
 283            WM_SYSKEYDOWN => self.handle_syskeydown_msg(wparam, lparam),
 284            WM_SYSKEYUP => self.handle_syskeyup_msg(wparam),
 285            WM_KEYDOWN => self.handle_keydown_msg(msg, wparam, lparam),
 286            WM_KEYUP => self.handle_keyup_msg(msg, wparam),
 287            WM_CHAR => self.handle_char_msg(msg, wparam, lparam),
 288            WM_IME_STARTCOMPOSITION => self.handle_ime_position(),
 289            WM_IME_COMPOSITION => self.handle_ime_composition(lparam),
 290            WM_IME_CHAR => self.handle_ime_char(wparam),
 291            WM_SETCURSOR => self.handle_set_cursor(lparam),
 292            _ => None,
 293        };
 294        if let Some(n) = handled {
 295            LRESULT(n)
 296        } else {
 297            unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) }
 298        }
 299    }
 300
 301    fn handle_move_msg(&self, lparam: LPARAM) -> Option<isize> {
 302        let x = lparam.signed_loword() as i32;
 303        let y = lparam.signed_hiword() as i32;
 304        self.origin.set(Point {
 305            x: DevicePixels(x),
 306            y: DevicePixels(y),
 307        });
 308        let size = self.physical_size.get();
 309        let center_x = x + size.width.0 / 2;
 310        let center_y = y + size.height.0 / 2;
 311        let monitor_bounds = self.display.borrow().bounds();
 312        if center_x < monitor_bounds.left().0
 313            || center_x > monitor_bounds.right().0
 314            || center_y < monitor_bounds.top().0
 315            || center_y > monitor_bounds.bottom().0
 316        {
 317            // center of the window may have moved to another monitor
 318            let monitor = unsafe { MonitorFromWindow(self.hwnd, MONITOR_DEFAULTTONULL) };
 319            if !monitor.is_invalid() && self.display.borrow().handle != monitor {
 320                // we will get the same monitor if we only have one
 321                (*self.display.borrow_mut()) = Rc::new(WindowsDisplay::new_with_handle(monitor));
 322            }
 323        }
 324        let mut callbacks = self.callbacks.borrow_mut();
 325        if let Some(callback) = callbacks.moved.as_mut() {
 326            callback()
 327        }
 328        Some(0)
 329    }
 330
 331    fn handle_size_msg(&self, lparam: LPARAM) -> Option<isize> {
 332        let width = lparam.loword().max(1) as i32;
 333        let height = lparam.hiword().max(1) as i32;
 334        let scale_factor = self.scale_factor.get();
 335        let new_physical_size = Size {
 336            width: DevicePixels(width),
 337            height: DevicePixels(height),
 338        };
 339        self.physical_size.set(new_physical_size);
 340        self.renderer.borrow_mut().update_drawable_size(Size {
 341            width: width as f64,
 342            height: height as f64,
 343        });
 344        let mut callbacks = self.callbacks.borrow_mut();
 345        if let Some(callback) = callbacks.resize.as_mut() {
 346            let logical_size = logical_size(new_physical_size, scale_factor);
 347            callback(logical_size, scale_factor);
 348        }
 349        self.invalidate_client_area();
 350        Some(0)
 351    }
 352
 353    fn handle_paint_msg(&self) -> Option<isize> {
 354        let mut paint_struct = PAINTSTRUCT::default();
 355        let _hdc = unsafe { BeginPaint(self.hwnd, &mut paint_struct) };
 356        let mut callbacks = self.callbacks.borrow_mut();
 357        if let Some(request_frame) = callbacks.request_frame.as_mut() {
 358            request_frame();
 359        }
 360        unsafe { EndPaint(self.hwnd, &paint_struct) };
 361        Some(0)
 362    }
 363
 364    fn handle_close_msg(&self) -> Option<isize> {
 365        let mut callbacks = self.callbacks.borrow_mut();
 366        if let Some(callback) = callbacks.should_close.as_mut() {
 367            if callback() {
 368                return Some(0);
 369            }
 370        }
 371        None
 372    }
 373
 374    fn handle_destroy_msg(&self) -> Option<isize> {
 375        let mut callbacks = self.callbacks.borrow_mut();
 376        if let Some(callback) = callbacks.close.take() {
 377            callback()
 378        }
 379        let index = self
 380            .platform_inner
 381            .raw_window_handles
 382            .read()
 383            .iter()
 384            .position(|handle| *handle == self.hwnd)
 385            .unwrap();
 386        self.platform_inner.raw_window_handles.write().remove(index);
 387        if self.platform_inner.raw_window_handles.read().is_empty() {
 388            self.platform_inner
 389                .foreground_executor
 390                .spawn(async {
 391                    unsafe { PostQuitMessage(0) };
 392                })
 393                .detach();
 394        }
 395        Some(1)
 396    }
 397
 398    fn handle_mouse_move_msg(&self, lparam: LPARAM, wparam: WPARAM) -> Option<isize> {
 399        let mut callbacks = self.callbacks.borrow_mut();
 400        if let Some(callback) = callbacks.input.as_mut() {
 401            let pressed_button = match MODIFIERKEYS_FLAGS(wparam.loword() as u32) {
 402                flags if flags.contains(MK_LBUTTON) => Some(MouseButton::Left),
 403                flags if flags.contains(MK_RBUTTON) => Some(MouseButton::Right),
 404                flags if flags.contains(MK_MBUTTON) => Some(MouseButton::Middle),
 405                flags if flags.contains(MK_XBUTTON1) => {
 406                    Some(MouseButton::Navigate(NavigationDirection::Back))
 407                }
 408                flags if flags.contains(MK_XBUTTON2) => {
 409                    Some(MouseButton::Navigate(NavigationDirection::Forward))
 410                }
 411                _ => None,
 412            };
 413            let x = lparam.signed_loword() as f32;
 414            let y = lparam.signed_hiword() as f32;
 415            let scale_factor = self.scale_factor.get();
 416            let event = MouseMoveEvent {
 417                position: logical_point(x, y, scale_factor),
 418                pressed_button,
 419                modifiers: self.current_modifiers(),
 420            };
 421            if callback(PlatformInput::MouseMove(event)).default_prevented {
 422                return Some(0);
 423            }
 424        }
 425        Some(1)
 426    }
 427
 428    fn parse_syskeydown_msg_keystroke(&self, wparam: WPARAM) -> Option<Keystroke> {
 429        let modifiers = self.current_modifiers();
 430        if !modifiers.alt {
 431            // on Windows, F10 can trigger this event, not just the alt key
 432            // and we just don't care about F10
 433            return None;
 434        }
 435
 436        let vk_code = wparam.loword();
 437        let basic_key = basic_vkcode_to_string(vk_code, modifiers);
 438        if basic_key.is_some() {
 439            return basic_key;
 440        }
 441
 442        let key = match VIRTUAL_KEY(vk_code) {
 443            VK_BACK => Some("backspace"),
 444            VK_RETURN => Some("enter"),
 445            VK_TAB => Some("tab"),
 446            VK_UP => Some("up"),
 447            VK_DOWN => Some("down"),
 448            VK_RIGHT => Some("right"),
 449            VK_LEFT => Some("left"),
 450            VK_HOME => Some("home"),
 451            VK_END => Some("end"),
 452            VK_PRIOR => Some("pageup"),
 453            VK_NEXT => Some("pagedown"),
 454            VK_ESCAPE => Some("escape"),
 455            VK_INSERT => Some("insert"),
 456            _ => None,
 457        };
 458
 459        if let Some(key) = key {
 460            Some(Keystroke {
 461                modifiers,
 462                key: key.to_string(),
 463                ime_key: None,
 464            })
 465        } else {
 466            None
 467        }
 468    }
 469
 470    fn parse_keydown_msg_keystroke(&self, wparam: WPARAM) -> Option<Keystroke> {
 471        let vk_code = wparam.loword();
 472
 473        let modifiers = self.current_modifiers();
 474        if modifiers.control || modifiers.alt {
 475            let basic_key = basic_vkcode_to_string(vk_code, modifiers);
 476            if basic_key.is_some() {
 477                return basic_key;
 478            }
 479        }
 480
 481        if vk_code >= VK_F1.0 && vk_code <= VK_F24.0 {
 482            let offset = vk_code - VK_F1.0;
 483            return Some(Keystroke {
 484                modifiers,
 485                key: format!("f{}", offset + 1),
 486                ime_key: None,
 487            });
 488        }
 489
 490        let key = match VIRTUAL_KEY(vk_code) {
 491            VK_BACK => Some("backspace"),
 492            VK_RETURN => Some("enter"),
 493            VK_TAB => Some("tab"),
 494            VK_UP => Some("up"),
 495            VK_DOWN => Some("down"),
 496            VK_RIGHT => Some("right"),
 497            VK_LEFT => Some("left"),
 498            VK_HOME => Some("home"),
 499            VK_END => Some("end"),
 500            VK_PRIOR => Some("pageup"),
 501            VK_NEXT => Some("pagedown"),
 502            VK_ESCAPE => Some("escape"),
 503            VK_INSERT => Some("insert"),
 504            VK_DELETE => Some("delete"),
 505            _ => None,
 506        };
 507
 508        if let Some(key) = key {
 509            Some(Keystroke {
 510                modifiers,
 511                key: key.to_string(),
 512                ime_key: None,
 513            })
 514        } else {
 515            None
 516        }
 517    }
 518
 519    fn parse_char_msg_keystroke(&self, wparam: WPARAM) -> Option<Keystroke> {
 520        let src = [wparam.0 as u16];
 521        let Ok(first_char) = char::decode_utf16(src).collect::<Vec<_>>()[0] else {
 522            return None;
 523        };
 524        if first_char.is_control() {
 525            None
 526        } else {
 527            let mut modifiers = self.current_modifiers();
 528            // for characters that use 'shift' to type it is expected that the
 529            // shift is not reported if the uppercase/lowercase are the same and instead only the key is reported
 530            if first_char.to_lowercase().to_string() == first_char.to_uppercase().to_string() {
 531                modifiers.shift = false;
 532            }
 533            let key = match first_char {
 534                ' ' => "space".to_string(),
 535                first_char => first_char.to_lowercase().to_string(),
 536            };
 537            Some(Keystroke {
 538                modifiers,
 539                key,
 540                ime_key: Some(first_char.to_string()),
 541            })
 542        }
 543    }
 544
 545    fn handle_syskeydown_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
 546        // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
 547        // shortcuts.
 548        let Some(keystroke) = self.parse_syskeydown_msg_keystroke(wparam) else {
 549            return None;
 550        };
 551        let Some(ref mut func) = self.callbacks.borrow_mut().input else {
 552            return None;
 553        };
 554        let event = KeyDownEvent {
 555            keystroke,
 556            is_held: lparam.0 & (0x1 << 30) > 0,
 557        };
 558        if func(PlatformInput::KeyDown(event)).default_prevented {
 559            self.invalidate_client_area();
 560            return Some(0);
 561        }
 562        None
 563    }
 564
 565    fn handle_syskeyup_msg(&self, wparam: WPARAM) -> Option<isize> {
 566        // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
 567        // shortcuts.
 568        let Some(keystroke) = self.parse_syskeydown_msg_keystroke(wparam) else {
 569            return None;
 570        };
 571        let Some(ref mut func) = self.callbacks.borrow_mut().input else {
 572            return None;
 573        };
 574        let event = KeyUpEvent { keystroke };
 575        if func(PlatformInput::KeyUp(event)).default_prevented {
 576            self.invalidate_client_area();
 577            return Some(0);
 578        }
 579        None
 580    }
 581
 582    fn handle_keydown_msg(&self, _msg: u32, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
 583        let Some(keystroke) = self.parse_keydown_msg_keystroke(wparam) else {
 584            return Some(1);
 585        };
 586        let Some(ref mut func) = self.callbacks.borrow_mut().input else {
 587            return Some(1);
 588        };
 589        let event = KeyDownEvent {
 590            keystroke,
 591            is_held: lparam.0 & (0x1 << 30) > 0,
 592        };
 593        if func(PlatformInput::KeyDown(event)).default_prevented {
 594            self.invalidate_client_area();
 595            return Some(0);
 596        }
 597        Some(1)
 598    }
 599
 600    fn handle_keyup_msg(&self, _msg: u32, wparam: WPARAM) -> Option<isize> {
 601        let Some(keystroke) = self.parse_keydown_msg_keystroke(wparam) else {
 602            return Some(1);
 603        };
 604        let Some(ref mut func) = self.callbacks.borrow_mut().input else {
 605            return Some(1);
 606        };
 607        let event = KeyUpEvent { keystroke };
 608        if func(PlatformInput::KeyUp(event)).default_prevented {
 609            self.invalidate_client_area();
 610            return Some(0);
 611        }
 612        Some(1)
 613    }
 614
 615    fn handle_char_msg(&self, _msg: u32, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
 616        let Some(keystroke) = self.parse_char_msg_keystroke(wparam) else {
 617            return Some(1);
 618        };
 619        let mut callbacks = self.callbacks.borrow_mut();
 620        let Some(ref mut func) = callbacks.input else {
 621            return Some(1);
 622        };
 623        let ime_key = keystroke.ime_key.clone();
 624        let event = KeyDownEvent {
 625            keystroke,
 626            is_held: lparam.0 & (0x1 << 30) > 0,
 627        };
 628
 629        let dispatch_event_result = func(PlatformInput::KeyDown(event));
 630        if dispatch_event_result.default_prevented || !dispatch_event_result.propagate {
 631            self.invalidate_client_area();
 632            return Some(0);
 633        }
 634        drop(callbacks);
 635        let Some(ime_char) = ime_key else {
 636            return Some(1);
 637        };
 638        let Some(mut input_handler) = self.input_handler.take() else {
 639            return Some(1);
 640        };
 641        input_handler.replace_text_in_range(None, &ime_char);
 642        self.input_handler.set(Some(input_handler));
 643        self.invalidate_client_area();
 644        Some(0)
 645    }
 646
 647    fn handle_mouse_down_msg(&self, button: MouseButton, lparam: LPARAM) -> Option<isize> {
 648        let mut callbacks = self.callbacks.borrow_mut();
 649        if let Some(callback) = callbacks.input.as_mut() {
 650            let x = lparam.signed_loword() as f32;
 651            let y = lparam.signed_hiword() as f32;
 652            let physical_point = point(DevicePixels(x as i32), DevicePixels(y as i32));
 653            let click_count = self.click_state.borrow_mut().update(button, physical_point);
 654            let scale_factor = self.scale_factor.get();
 655            let event = MouseDownEvent {
 656                button,
 657                position: logical_point(x, y, scale_factor),
 658                modifiers: self.current_modifiers(),
 659                click_count,
 660                first_mouse: false,
 661            };
 662            if callback(PlatformInput::MouseDown(event)).default_prevented {
 663                return Some(0);
 664            }
 665        }
 666        Some(1)
 667    }
 668
 669    fn handle_mouse_up_msg(&self, button: MouseButton, lparam: LPARAM) -> Option<isize> {
 670        let mut callbacks = self.callbacks.borrow_mut();
 671        if let Some(callback) = callbacks.input.as_mut() {
 672            let x = lparam.signed_loword() as f32;
 673            let y = lparam.signed_hiword() as f32;
 674            let scale_factor = self.scale_factor.get();
 675            let event = MouseUpEvent {
 676                button,
 677                position: logical_point(x, y, scale_factor),
 678                modifiers: self.current_modifiers(),
 679                click_count: 1,
 680            };
 681            if callback(PlatformInput::MouseUp(event)).default_prevented {
 682                return Some(0);
 683            }
 684        }
 685        Some(1)
 686    }
 687
 688    fn handle_xbutton_msg(
 689        &self,
 690        wparam: WPARAM,
 691        lparam: LPARAM,
 692        handler: impl Fn(&Self, MouseButton, LPARAM) -> Option<isize>,
 693    ) -> Option<isize> {
 694        let nav_dir = match wparam.hiword() {
 695            XBUTTON1 => NavigationDirection::Back,
 696            XBUTTON2 => NavigationDirection::Forward,
 697            _ => return Some(1),
 698        };
 699        handler(self, MouseButton::Navigate(nav_dir), lparam)
 700    }
 701
 702    fn handle_mouse_wheel_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
 703        let mut callbacks = self.callbacks.borrow_mut();
 704        if let Some(callback) = callbacks.input.as_mut() {
 705            let wheel_distance = (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32)
 706                * self.platform_inner.settings.borrow().wheel_scroll_lines as f32;
 707            let mut cursor_point = POINT {
 708                x: lparam.signed_loword().into(),
 709                y: lparam.signed_hiword().into(),
 710            };
 711            unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
 712            let scale_factor = self.scale_factor.get();
 713            let event = crate::ScrollWheelEvent {
 714                position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
 715                delta: ScrollDelta::Lines(Point {
 716                    x: 0.0,
 717                    y: wheel_distance,
 718                }),
 719                modifiers: self.current_modifiers(),
 720                touch_phase: TouchPhase::Moved,
 721            };
 722            callback(PlatformInput::ScrollWheel(event));
 723            return Some(0);
 724        }
 725        Some(1)
 726    }
 727
 728    fn handle_mouse_horizontal_wheel_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
 729        let mut callbacks = self.callbacks.borrow_mut();
 730        if let Some(callback) = callbacks.input.as_mut() {
 731            let wheel_distance = (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32)
 732                * self.platform_inner.settings.borrow().wheel_scroll_chars as f32;
 733            let mut cursor_point = POINT {
 734                x: lparam.signed_loword().into(),
 735                y: lparam.signed_hiword().into(),
 736            };
 737            unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
 738            let scale_factor = self.scale_factor.get();
 739            let event = crate::ScrollWheelEvent {
 740                position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
 741                delta: ScrollDelta::Lines(Point {
 742                    x: wheel_distance,
 743                    y: 0.0,
 744                }),
 745                modifiers: self.current_modifiers(),
 746                touch_phase: TouchPhase::Moved,
 747            };
 748            if callback(PlatformInput::ScrollWheel(event)).default_prevented {
 749                return Some(0);
 750            }
 751        }
 752        Some(1)
 753    }
 754
 755    fn handle_ime_position(&self) -> Option<isize> {
 756        unsafe {
 757            let ctx = ImmGetContext(self.hwnd);
 758            let Some(mut input_handler) = self.input_handler.take() else {
 759                return Some(1);
 760            };
 761            // we are composing, this should never fail
 762            let caret_range = input_handler.selected_text_range().unwrap();
 763            let caret_position = input_handler.bounds_for_range(caret_range).unwrap();
 764            self.input_handler.set(Some(input_handler));
 765            let scale_factor = self.scale_factor.get();
 766            let config = CANDIDATEFORM {
 767                dwStyle: CFS_CANDIDATEPOS,
 768                // logical to physical
 769                ptCurrentPos: POINT {
 770                    x: (caret_position.origin.x.0 * scale_factor) as i32,
 771                    y: (caret_position.origin.y.0 * scale_factor) as i32
 772                        + ((caret_position.size.height.0 * scale_factor) as i32 / 2),
 773                },
 774                ..Default::default()
 775            };
 776            ImmSetCandidateWindow(ctx, &config as _);
 777            ImmReleaseContext(self.hwnd, ctx);
 778            Some(0)
 779        }
 780    }
 781
 782    fn parse_ime_compostion_string(&self) -> Option<(String, usize)> {
 783        unsafe {
 784            let ctx = ImmGetContext(self.hwnd);
 785            let string_len = ImmGetCompositionStringW(ctx, GCS_COMPSTR, None, 0);
 786            let result = if string_len >= 0 {
 787                let mut buffer = vec![0u8; string_len as usize + 2];
 788                // let mut buffer = [0u8; MAX_PATH as _];
 789                ImmGetCompositionStringW(
 790                    ctx,
 791                    GCS_COMPSTR,
 792                    Some(buffer.as_mut_ptr() as _),
 793                    string_len as _,
 794                );
 795                let wstring = std::slice::from_raw_parts::<u16>(
 796                    buffer.as_mut_ptr().cast::<u16>(),
 797                    string_len as usize / 2,
 798                );
 799                let string = String::from_utf16_lossy(wstring);
 800                Some((string, string_len as usize / 2))
 801            } else {
 802                None
 803            };
 804            ImmReleaseContext(self.hwnd, ctx);
 805            result
 806        }
 807    }
 808
 809    fn retrieve_composition_cursor_position(&self) -> usize {
 810        unsafe {
 811            let ctx = ImmGetContext(self.hwnd);
 812            let ret = ImmGetCompositionStringW(ctx, GCS_CURSORPOS, None, 0);
 813            ImmReleaseContext(self.hwnd, ctx);
 814            ret as usize
 815        }
 816    }
 817
 818    fn handle_ime_composition(&self, lparam: LPARAM) -> Option<isize> {
 819        if lparam.0 as u32 & GCS_COMPSTR.0 > 0 {
 820            let Some((string, string_len)) = self.parse_ime_compostion_string() else {
 821                return None;
 822            };
 823            let Some(mut input_handler) = self.input_handler.take() else {
 824                return None;
 825            };
 826            input_handler.replace_and_mark_text_in_range(
 827                None,
 828                string.as_str(),
 829                Some(0..string_len),
 830            );
 831            self.input_handler.set(Some(input_handler));
 832            *self.last_ime_input.borrow_mut() = Some(string);
 833        }
 834        if lparam.0 as u32 & GCS_CURSORPOS.0 > 0 {
 835            let Some(ref comp_string) = *self.last_ime_input.borrow() else {
 836                return None;
 837            };
 838            let caret_pos = self.retrieve_composition_cursor_position();
 839            let Some(mut input_handler) = self.input_handler.take() else {
 840                return None;
 841            };
 842            input_handler.replace_and_mark_text_in_range(None, comp_string, Some(0..caret_pos));
 843            self.input_handler.set(Some(input_handler));
 844        }
 845        // currently, we don't care other stuff
 846        None
 847    }
 848
 849    fn parse_ime_char(&self, wparam: WPARAM) -> Option<String> {
 850        let src = [wparam.0 as u16];
 851        let Ok(first_char) = char::decode_utf16(src).collect::<Vec<_>>()[0] else {
 852            return None;
 853        };
 854        Some(first_char.to_string())
 855    }
 856
 857    fn handle_ime_char(&self, wparam: WPARAM) -> Option<isize> {
 858        let Some(ime_char) = self.parse_ime_char(wparam) else {
 859            return Some(1);
 860        };
 861        let Some(mut input_handler) = self.input_handler.take() else {
 862            return Some(1);
 863        };
 864        input_handler.replace_text_in_range(None, &ime_char);
 865        self.input_handler.set(Some(input_handler));
 866        *self.last_ime_input.borrow_mut() = None;
 867        self.invalidate_client_area();
 868        Some(0)
 869    }
 870
 871    fn handle_drag_drop(&self, input: PlatformInput) {
 872        let mut callbacks = self.callbacks.borrow_mut();
 873        let Some(ref mut func) = callbacks.input else {
 874            return;
 875        };
 876        func(input);
 877    }
 878
 879    /// SEE: https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize
 880    fn handle_calc_client_size(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
 881        if !self.hide_title_bar || self.is_fullscreen() {
 882            return None;
 883        }
 884
 885        if wparam.0 == 0 {
 886            return None;
 887        }
 888
 889        let dpi = unsafe { GetDpiForWindow(self.hwnd) };
 890
 891        let frame_x = unsafe { GetSystemMetricsForDpi(SM_CXFRAME, dpi) };
 892        let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
 893        let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
 894
 895        // wparam is TRUE so lparam points to an NCCALCSIZE_PARAMS structure
 896        let mut params = lparam.0 as *mut NCCALCSIZE_PARAMS;
 897        let mut requested_client_rect = unsafe { &mut ((*params).rgrc) };
 898
 899        requested_client_rect[0].right -= frame_x + padding;
 900        requested_client_rect[0].left += frame_x + padding;
 901        requested_client_rect[0].bottom -= frame_y + padding;
 902
 903        Some(0)
 904    }
 905
 906    fn handle_activate_msg(&self, wparam: WPARAM) -> Option<isize> {
 907        if self.hide_title_bar {
 908            if let Some(titlebar_rect) = self.get_titlebar_rect().log_err() {
 909                unsafe { InvalidateRect(self.hwnd, Some(&titlebar_rect), FALSE) };
 910            }
 911        }
 912        let activated = wparam.loword() > 0;
 913        let mut callbacks = self.callbacks.borrow_mut();
 914        if let Some(mut cb) = callbacks.active_status_change.as_mut() {
 915            cb(activated);
 916        }
 917        None
 918    }
 919
 920    fn handle_create_msg(&self, _lparam: LPARAM) -> Option<isize> {
 921        let mut size_rect = RECT::default();
 922        unsafe { GetWindowRect(self.hwnd, &mut size_rect).log_err() };
 923
 924        let width = size_rect.right - size_rect.left;
 925        let height = size_rect.bottom - size_rect.top;
 926
 927        self.physical_size.set(Size {
 928            width: DevicePixels(width),
 929            height: DevicePixels(height),
 930        });
 931
 932        if self.hide_title_bar {
 933            // Inform the application of the frame change to force redrawing with the new
 934            // client area that is extended into the title bar
 935            unsafe {
 936                SetWindowPos(
 937                    self.hwnd,
 938                    HWND::default(),
 939                    size_rect.left,
 940                    size_rect.top,
 941                    width,
 942                    height,
 943                    SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE,
 944                )
 945                .log_err()
 946            };
 947        }
 948
 949        Some(0)
 950    }
 951
 952    fn handle_dpi_changed_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
 953        let new_dpi = wparam.loword() as f32;
 954        let scale_factor = new_dpi / USER_DEFAULT_SCREEN_DPI as f32;
 955        self.scale_factor.set(scale_factor);
 956        let rect = unsafe { &*(lparam.0 as *const RECT) };
 957        let width = rect.right - rect.left;
 958        let height = rect.bottom - rect.top;
 959        // this will emit `WM_SIZE` and `WM_MOVE` right here
 960        // even before this function returns
 961        // the new size is handled in `WM_SIZE`
 962        unsafe {
 963            SetWindowPos(
 964                self.hwnd,
 965                None,
 966                rect.left,
 967                rect.top,
 968                width,
 969                height,
 970                SWP_NOZORDER | SWP_NOACTIVATE,
 971            )
 972            .context("unable to set window position after dpi has changed")
 973            .log_err();
 974        }
 975        self.invalidate_client_area();
 976        Some(0)
 977    }
 978
 979    fn handle_hit_test_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
 980        if !self.hide_title_bar {
 981            return None;
 982        }
 983
 984        // default handler for resize areas
 985        let hit = unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) };
 986        if matches!(
 987            hit.0 as u32,
 988            HTNOWHERE
 989                | HTRIGHT
 990                | HTLEFT
 991                | HTTOPLEFT
 992                | HTTOP
 993                | HTTOPRIGHT
 994                | HTBOTTOMRIGHT
 995                | HTBOTTOM
 996                | HTBOTTOMLEFT
 997        ) {
 998            return Some(hit.0);
 999        }
1000
1001        if self.is_fullscreen() {
1002            return Some(HTCLIENT as _);
1003        }
1004
1005        let dpi = unsafe { GetDpiForWindow(self.hwnd) };
1006        let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
1007        let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
1008
1009        let mut cursor_point = POINT {
1010            x: lparam.signed_loword().into(),
1011            y: lparam.signed_hiword().into(),
1012        };
1013        unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
1014        if cursor_point.y > 0 && cursor_point.y < frame_y + padding {
1015            return Some(HTTOP as _);
1016        }
1017
1018        let titlebar_rect = self.get_titlebar_rect();
1019        if let Ok(titlebar_rect) = titlebar_rect {
1020            if cursor_point.y < titlebar_rect.bottom {
1021                let caption_btn_width =
1022                    (self.caption_button_width().0 * self.scale_factor.get()) as i32;
1023                if cursor_point.x >= titlebar_rect.right - caption_btn_width {
1024                    return Some(HTCLOSE as _);
1025                } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 2 {
1026                    return Some(HTMAXBUTTON as _);
1027                } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 3 {
1028                    return Some(HTMINBUTTON as _);
1029                }
1030
1031                return Some(HTCAPTION as _);
1032            }
1033        }
1034
1035        Some(HTCLIENT as _)
1036    }
1037
1038    fn handle_nc_mouse_move_msg(&self, lparam: LPARAM) -> Option<isize> {
1039        if !self.hide_title_bar {
1040            return None;
1041        }
1042
1043        let mut callbacks = self.callbacks.borrow_mut();
1044        if let Some(callback) = callbacks.input.as_mut() {
1045            let mut cursor_point = POINT {
1046                x: lparam.signed_loword().into(),
1047                y: lparam.signed_hiword().into(),
1048            };
1049            unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
1050            let scale_factor = self.scale_factor.get();
1051            let event = MouseMoveEvent {
1052                position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
1053                pressed_button: None,
1054                modifiers: self.current_modifiers(),
1055            };
1056            if callback(PlatformInput::MouseMove(event)).default_prevented {
1057                return Some(0);
1058            }
1059        }
1060        None
1061    }
1062
1063    fn handle_nc_mouse_down_msg(
1064        &self,
1065        button: MouseButton,
1066        wparam: WPARAM,
1067        lparam: LPARAM,
1068    ) -> Option<isize> {
1069        if !self.hide_title_bar {
1070            return None;
1071        }
1072
1073        let mut callbacks = self.callbacks.borrow_mut();
1074        if let Some(callback) = callbacks.input.as_mut() {
1075            let mut cursor_point = POINT {
1076                x: lparam.signed_loword().into(),
1077                y: lparam.signed_hiword().into(),
1078            };
1079            unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
1080            let physical_point = point(DevicePixels(cursor_point.x), DevicePixels(cursor_point.y));
1081            let click_count = self.click_state.borrow_mut().update(button, physical_point);
1082            let scale_factor = self.scale_factor.get();
1083            let event = MouseDownEvent {
1084                button,
1085                position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
1086                modifiers: self.current_modifiers(),
1087                click_count,
1088                first_mouse: false,
1089            };
1090            if callback(PlatformInput::MouseDown(event)).default_prevented {
1091                return Some(0);
1092            }
1093        }
1094
1095        // Since these are handled in handle_nc_mouse_up_msg we must prevent the default window proc
1096        matches!(wparam.0 as u32, HTMINBUTTON | HTMAXBUTTON | HTCLOSE).then_some(0)
1097    }
1098
1099    fn handle_nc_mouse_up_msg(
1100        &self,
1101        button: MouseButton,
1102        wparam: WPARAM,
1103        lparam: LPARAM,
1104    ) -> Option<isize> {
1105        if !self.hide_title_bar {
1106            return None;
1107        }
1108
1109        let mut callbacks = self.callbacks.borrow_mut();
1110        if let Some(callback) = callbacks.input.as_mut() {
1111            let mut cursor_point = POINT {
1112                x: lparam.signed_loword().into(),
1113                y: lparam.signed_hiword().into(),
1114            };
1115            unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
1116            let scale_factor = self.scale_factor.get();
1117            let event = MouseUpEvent {
1118                button,
1119                position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
1120                modifiers: self.current_modifiers(),
1121                click_count: 1,
1122            };
1123            if callback(PlatformInput::MouseUp(event)).default_prevented {
1124                return Some(0);
1125            }
1126        }
1127        drop(callbacks);
1128
1129        if button == MouseButton::Left {
1130            match wparam.0 as u32 {
1131                HTMINBUTTON => unsafe {
1132                    ShowWindowAsync(self.hwnd, SW_MINIMIZE);
1133                },
1134                HTMAXBUTTON => unsafe {
1135                    if self.is_maximized() {
1136                        ShowWindowAsync(self.hwnd, SW_NORMAL);
1137                    } else {
1138                        ShowWindowAsync(self.hwnd, SW_MAXIMIZE);
1139                    }
1140                },
1141                HTCLOSE => unsafe {
1142                    PostMessageW(self.hwnd, WM_CLOSE, WPARAM::default(), LPARAM::default())
1143                        .log_err();
1144                },
1145                _ => return None,
1146            };
1147            return Some(0);
1148        }
1149
1150        None
1151    }
1152
1153    fn handle_set_cursor(&self, lparam: LPARAM) -> Option<isize> {
1154        if matches!(
1155            lparam.loword() as u32,
1156            HTLEFT
1157                | HTRIGHT
1158                | HTTOP
1159                | HTTOPLEFT
1160                | HTTOPRIGHT
1161                | HTBOTTOM
1162                | HTBOTTOMLEFT
1163                | HTBOTTOMRIGHT
1164        ) {
1165            return None;
1166        }
1167        unsafe { SetCursor(self.platform_inner.current_cursor.get()) };
1168        Some(1)
1169    }
1170}
1171
1172#[derive(Default)]
1173struct Callbacks {
1174    request_frame: Option<Box<dyn FnMut()>>,
1175    input: Option<Box<dyn FnMut(crate::PlatformInput) -> DispatchEventResult>>,
1176    active_status_change: Option<Box<dyn FnMut(bool)>>,
1177    resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
1178    fullscreen: Option<Box<dyn FnMut(bool)>>,
1179    moved: Option<Box<dyn FnMut()>>,
1180    should_close: Option<Box<dyn FnMut() -> bool>>,
1181    close: Option<Box<dyn FnOnce()>>,
1182    appearance_changed: Option<Box<dyn FnMut()>>,
1183}
1184
1185pub(crate) struct WindowsWindow {
1186    inner: Rc<WindowsWindowInner>,
1187    drag_drop_handler: IDropTarget,
1188}
1189
1190struct WindowCreateContext {
1191    inner: Option<Rc<WindowsWindowInner>>,
1192    platform_inner: Rc<WindowsPlatformInner>,
1193    handle: AnyWindowHandle,
1194    hide_title_bar: bool,
1195    display: Rc<WindowsDisplay>,
1196}
1197
1198impl WindowsWindow {
1199    pub(crate) fn new(
1200        platform_inner: Rc<WindowsPlatformInner>,
1201        handle: AnyWindowHandle,
1202        options: WindowParams,
1203    ) -> Self {
1204        let classname = register_wnd_class(platform_inner.icon);
1205        let hide_title_bar = options
1206            .titlebar
1207            .as_ref()
1208            .map(|titlebar| titlebar.appears_transparent)
1209            .unwrap_or(false);
1210        let windowname = HSTRING::from(
1211            options
1212                .titlebar
1213                .as_ref()
1214                .and_then(|titlebar| titlebar.title.as_ref())
1215                .map(|title| title.as_ref())
1216                .unwrap_or(""),
1217        );
1218        let dwstyle = WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX;
1219        let x = options.bounds.origin.x.0;
1220        let y = options.bounds.origin.y.0;
1221        let nwidth = options.bounds.size.width.0;
1222        let nheight = options.bounds.size.height.0;
1223        let hwndparent = HWND::default();
1224        let hmenu = HMENU::default();
1225        let hinstance = HINSTANCE::default();
1226        let mut context = WindowCreateContext {
1227            inner: None,
1228            platform_inner: platform_inner.clone(),
1229            handle,
1230            hide_title_bar,
1231            // todo(windows) move window to target monitor
1232            // options.display_id
1233            display: Rc::new(WindowsDisplay::primary_monitor().unwrap()),
1234        };
1235        let lpparam = Some(&context as *const _ as *const _);
1236        unsafe {
1237            CreateWindowExW(
1238                WS_EX_APPWINDOW,
1239                classname,
1240                &windowname,
1241                dwstyle,
1242                x,
1243                y,
1244                nwidth,
1245                nheight,
1246                hwndparent,
1247                hmenu,
1248                hinstance,
1249                lpparam,
1250            )
1251        };
1252        let drag_drop_handler = {
1253            let inner = context.inner.as_ref().unwrap();
1254            let handler = WindowsDragDropHandler(Rc::clone(inner));
1255            let drag_drop_handler: IDropTarget = handler.into();
1256            unsafe {
1257                RegisterDragDrop(inner.hwnd, &drag_drop_handler)
1258                    .expect("unable to register drag-drop event")
1259            };
1260            drag_drop_handler
1261        };
1262        let wnd = Self {
1263            inner: context.inner.unwrap(),
1264            drag_drop_handler,
1265        };
1266        platform_inner
1267            .raw_window_handles
1268            .write()
1269            .push(wnd.inner.hwnd);
1270
1271        unsafe { ShowWindow(wnd.inner.hwnd, SW_SHOW) };
1272        wnd
1273    }
1274}
1275
1276impl HasWindowHandle for WindowsWindow {
1277    fn window_handle(
1278        &self,
1279    ) -> Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {
1280        let raw = raw_window_handle::Win32WindowHandle::new(unsafe {
1281            NonZeroIsize::new_unchecked(self.inner.hwnd.0)
1282        })
1283        .into();
1284        Ok(unsafe { raw_window_handle::WindowHandle::borrow_raw(raw) })
1285    }
1286}
1287
1288// todo(windows)
1289impl HasDisplayHandle for WindowsWindow {
1290    fn display_handle(
1291        &self,
1292    ) -> Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
1293        unimplemented!()
1294    }
1295}
1296
1297impl Drop for WindowsWindow {
1298    fn drop(&mut self) {
1299        unsafe {
1300            let _ = RevokeDragDrop(self.inner.hwnd);
1301            self.inner.renderer.borrow_mut().destroy();
1302        }
1303    }
1304}
1305
1306impl PlatformWindow for WindowsWindow {
1307    fn bounds(&self) -> Bounds<DevicePixels> {
1308        Bounds {
1309            origin: self.inner.origin.get(),
1310            size: self.inner.physical_size.get(),
1311        }
1312    }
1313
1314    fn is_maximized(&self) -> bool {
1315        self.inner.is_maximized()
1316    }
1317
1318    fn is_minimized(&self) -> bool {
1319        self.inner.is_minimized()
1320    }
1321
1322    /// get the logical size of the app's drawable area.
1323    ///
1324    /// Currently, GPUI uses logical size of the app to handle mouse interactions (such as
1325    /// whether the mouse collides with other elements of GPUI).
1326    fn content_size(&self) -> Size<Pixels> {
1327        logical_size(
1328            self.inner.physical_size.get(),
1329            self.inner.scale_factor.get(),
1330        )
1331    }
1332
1333    fn scale_factor(&self) -> f32 {
1334        self.inner.scale_factor.get()
1335    }
1336
1337    // todo(windows)
1338    fn appearance(&self) -> WindowAppearance {
1339        WindowAppearance::Dark
1340    }
1341
1342    fn display(&self) -> Rc<dyn PlatformDisplay> {
1343        self.inner.display.borrow().clone()
1344    }
1345
1346    fn mouse_position(&self) -> Point<Pixels> {
1347        let point = unsafe {
1348            let mut point: POINT = std::mem::zeroed();
1349            GetCursorPos(&mut point)
1350                .context("unable to get cursor position")
1351                .log_err();
1352            ScreenToClient(self.inner.hwnd, &mut point);
1353            point
1354        };
1355        logical_point(
1356            point.x as f32,
1357            point.y as f32,
1358            self.inner.scale_factor.get(),
1359        )
1360    }
1361
1362    // todo(windows)
1363    fn modifiers(&self) -> Modifiers {
1364        Modifiers::none()
1365    }
1366
1367    fn as_any_mut(&mut self) -> &mut dyn Any {
1368        self
1369    }
1370
1371    // todo(windows)
1372    fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
1373        self.inner.input_handler.set(Some(input_handler));
1374    }
1375
1376    // todo(windows)
1377    fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
1378        self.inner.input_handler.take()
1379    }
1380
1381    fn prompt(
1382        &self,
1383        level: PromptLevel,
1384        msg: &str,
1385        detail: Option<&str>,
1386        answers: &[&str],
1387    ) -> Option<Receiver<usize>> {
1388        let (done_tx, done_rx) = oneshot::channel();
1389        let msg = msg.to_string();
1390        let detail_string = match detail {
1391            Some(info) => Some(info.to_string()),
1392            None => None,
1393        };
1394        let answers = answers.iter().map(|s| s.to_string()).collect::<Vec<_>>();
1395        let handle = self.inner.hwnd;
1396        self.inner
1397            .platform_inner
1398            .foreground_executor
1399            .spawn(async move {
1400                unsafe {
1401                    let mut config;
1402                    config = std::mem::zeroed::<TASKDIALOGCONFIG>();
1403                    config.cbSize = std::mem::size_of::<TASKDIALOGCONFIG>() as _;
1404                    config.hwndParent = handle;
1405                    let title;
1406                    let main_icon;
1407                    match level {
1408                        crate::PromptLevel::Info => {
1409                            title = windows::core::w!("Info");
1410                            main_icon = TD_INFORMATION_ICON;
1411                        }
1412                        crate::PromptLevel::Warning => {
1413                            title = windows::core::w!("Warning");
1414                            main_icon = TD_WARNING_ICON;
1415                        }
1416                        crate::PromptLevel::Critical => {
1417                            title = windows::core::w!("Critical");
1418                            main_icon = TD_ERROR_ICON;
1419                        }
1420                    };
1421                    config.pszWindowTitle = title;
1422                    config.Anonymous1.pszMainIcon = main_icon;
1423                    let instruction = msg.encode_utf16().chain(once(0)).collect_vec();
1424                    config.pszMainInstruction = PCWSTR::from_raw(instruction.as_ptr());
1425                    let hints_encoded;
1426                    if let Some(ref hints) = detail_string {
1427                        hints_encoded = hints.encode_utf16().chain(once(0)).collect_vec();
1428                        config.pszContent = PCWSTR::from_raw(hints_encoded.as_ptr());
1429                    };
1430                    let mut buttons = Vec::new();
1431                    let mut btn_encoded = Vec::new();
1432                    for (index, btn_string) in answers.iter().enumerate() {
1433                        let encoded = btn_string.encode_utf16().chain(once(0)).collect_vec();
1434                        buttons.push(TASKDIALOG_BUTTON {
1435                            nButtonID: index as _,
1436                            pszButtonText: PCWSTR::from_raw(encoded.as_ptr()),
1437                        });
1438                        btn_encoded.push(encoded);
1439                    }
1440                    config.cButtons = buttons.len() as _;
1441                    config.pButtons = buttons.as_ptr();
1442
1443                    config.pfCallback = None;
1444                    let mut res = std::mem::zeroed();
1445                    let _ = TaskDialogIndirect(&config, Some(&mut res), None, None)
1446                        .inspect_err(|e| log::error!("unable to create task dialog: {}", e));
1447
1448                    let _ = done_tx.send(res as usize);
1449                }
1450            })
1451            .detach();
1452
1453        Some(done_rx)
1454    }
1455
1456    fn activate(&self) {
1457        unsafe { SetActiveWindow(self.inner.hwnd) };
1458        unsafe { SetFocus(self.inner.hwnd) };
1459        unsafe { SetForegroundWindow(self.inner.hwnd) };
1460    }
1461
1462    fn is_active(&self) -> bool {
1463        self.inner.hwnd == unsafe { GetActiveWindow() }
1464    }
1465
1466    // todo(windows)
1467    fn set_title(&mut self, title: &str) {
1468        unsafe { SetWindowTextW(self.inner.hwnd, &HSTRING::from(title)) }
1469            .inspect_err(|e| log::error!("Set title failed: {e}"))
1470            .ok();
1471    }
1472
1473    fn set_background_appearance(&mut self, _background_appearance: WindowBackgroundAppearance) {
1474        // todo(windows)
1475    }
1476
1477    // todo(windows)
1478    fn set_edited(&mut self, _edited: bool) {}
1479
1480    // todo(windows)
1481    fn show_character_palette(&self) {}
1482
1483    fn minimize(&self) {
1484        unsafe { ShowWindowAsync(self.inner.hwnd, SW_MINIMIZE) };
1485    }
1486
1487    fn zoom(&self) {
1488        unsafe { ShowWindowAsync(self.inner.hwnd, SW_MAXIMIZE) };
1489    }
1490
1491    fn toggle_fullscreen(&self) {
1492        self.inner
1493            .platform_inner
1494            .foreground_executor
1495            .spawn(self.inner.clone().toggle_fullscreen())
1496            .detach();
1497    }
1498
1499    fn is_fullscreen(&self) -> bool {
1500        self.inner.is_fullscreen()
1501    }
1502
1503    // todo(windows)
1504    fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
1505        self.inner.callbacks.borrow_mut().request_frame = Some(callback);
1506    }
1507
1508    // todo(windows)
1509    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>) {
1510        self.inner.callbacks.borrow_mut().input = Some(callback);
1511    }
1512
1513    // todo(windows)
1514    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
1515        self.inner.callbacks.borrow_mut().active_status_change = Some(callback);
1516    }
1517
1518    // todo(windows)
1519    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
1520        self.inner.callbacks.borrow_mut().resize = Some(callback);
1521    }
1522
1523    // todo(windows)
1524    fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>) {
1525        self.inner.callbacks.borrow_mut().fullscreen = Some(callback);
1526    }
1527
1528    // todo(windows)
1529    fn on_moved(&self, callback: Box<dyn FnMut()>) {
1530        self.inner.callbacks.borrow_mut().moved = Some(callback);
1531    }
1532
1533    // todo(windows)
1534    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
1535        self.inner.callbacks.borrow_mut().should_close = Some(callback);
1536    }
1537
1538    // todo(windows)
1539    fn on_close(&self, callback: Box<dyn FnOnce()>) {
1540        self.inner.callbacks.borrow_mut().close = Some(callback);
1541    }
1542
1543    // todo(windows)
1544    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
1545        self.inner.callbacks.borrow_mut().appearance_changed = Some(callback);
1546    }
1547
1548    // todo(windows)
1549    fn is_topmost_for_position(&self, _position: Point<Pixels>) -> bool {
1550        true
1551    }
1552
1553    // todo(windows)
1554    fn draw(&self, scene: &Scene) {
1555        self.inner.renderer.borrow_mut().draw(scene)
1556    }
1557
1558    // todo(windows)
1559    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
1560        self.inner.renderer.borrow().sprite_atlas().clone()
1561    }
1562
1563    fn get_raw_handle(&self) -> HWND {
1564        self.inner.hwnd
1565    }
1566}
1567
1568#[implement(IDropTarget)]
1569struct WindowsDragDropHandler(pub Rc<WindowsWindowInner>);
1570
1571#[allow(non_snake_case)]
1572impl IDropTarget_Impl for WindowsDragDropHandler {
1573    fn DragEnter(
1574        &self,
1575        pdataobj: Option<&IDataObject>,
1576        _grfkeystate: MODIFIERKEYS_FLAGS,
1577        pt: &POINTL,
1578        pdweffect: *mut DROPEFFECT,
1579    ) -> windows::core::Result<()> {
1580        unsafe {
1581            let Some(idata_obj) = pdataobj else {
1582                log::info!("no dragging file or directory detected");
1583                return Ok(());
1584            };
1585            let config = FORMATETC {
1586                cfFormat: CF_HDROP.0,
1587                ptd: std::ptr::null_mut() as _,
1588                dwAspect: DVASPECT_CONTENT.0,
1589                lindex: -1,
1590                tymed: TYMED_HGLOBAL.0 as _,
1591            };
1592            let mut paths = SmallVec::<[PathBuf; 2]>::new();
1593            if idata_obj.QueryGetData(&config as _) == S_OK {
1594                *pdweffect = DROPEFFECT_LINK;
1595                let Ok(mut idata) = idata_obj.GetData(&config as _) else {
1596                    return Ok(());
1597                };
1598                if idata.u.hGlobal.is_invalid() {
1599                    return Ok(());
1600                }
1601                let hdrop = idata.u.hGlobal.0 as *mut HDROP;
1602                let file_count = DragQueryFileW(*hdrop, DRAGDROP_GET_FILES_COUNT, None);
1603                for file_index in 0..file_count {
1604                    let filename_length = DragQueryFileW(*hdrop, file_index, None) as usize;
1605                    let mut buffer = vec![0u16; filename_length + 1];
1606                    let ret = DragQueryFileW(*hdrop, file_index, Some(buffer.as_mut_slice()));
1607                    if ret == 0 {
1608                        log::error!("unable to read file name");
1609                        continue;
1610                    }
1611                    if let Ok(file_name) = String::from_utf16(&buffer[0..filename_length]) {
1612                        if let Ok(path) = PathBuf::from_str(&file_name) {
1613                            paths.push(path);
1614                        }
1615                    }
1616                }
1617                ReleaseStgMedium(&mut idata);
1618                let input = PlatformInput::FileDrop(crate::FileDropEvent::Entered {
1619                    position: Point {
1620                        x: Pixels(pt.x as _),
1621                        y: Pixels(pt.y as _),
1622                    },
1623                    paths: crate::ExternalPaths(paths),
1624                });
1625                self.0.handle_drag_drop(input);
1626            } else {
1627                *pdweffect = DROPEFFECT_NONE;
1628            }
1629        }
1630        Ok(())
1631    }
1632
1633    fn DragOver(
1634        &self,
1635        _grfkeystate: MODIFIERKEYS_FLAGS,
1636        pt: &POINTL,
1637        _pdweffect: *mut DROPEFFECT,
1638    ) -> windows::core::Result<()> {
1639        let input = PlatformInput::FileDrop(crate::FileDropEvent::Pending {
1640            position: Point {
1641                x: Pixels(pt.x as _),
1642                y: Pixels(pt.y as _),
1643            },
1644        });
1645        self.0.handle_drag_drop(input);
1646
1647        Ok(())
1648    }
1649
1650    fn DragLeave(&self) -> windows::core::Result<()> {
1651        let input = PlatformInput::FileDrop(crate::FileDropEvent::Exited);
1652        self.0.handle_drag_drop(input);
1653
1654        Ok(())
1655    }
1656
1657    fn Drop(
1658        &self,
1659        _pdataobj: Option<&IDataObject>,
1660        _grfkeystate: MODIFIERKEYS_FLAGS,
1661        pt: &POINTL,
1662        _pdweffect: *mut DROPEFFECT,
1663    ) -> windows::core::Result<()> {
1664        let input = PlatformInput::FileDrop(crate::FileDropEvent::Submit {
1665            position: Point {
1666                x: Pixels(pt.x as _),
1667                y: Pixels(pt.y as _),
1668            },
1669        });
1670        self.0.handle_drag_drop(input);
1671
1672        Ok(())
1673    }
1674}
1675
1676#[derive(Debug)]
1677struct ClickState {
1678    button: MouseButton,
1679    last_click: Instant,
1680    last_position: Point<DevicePixels>,
1681    current_count: usize,
1682}
1683
1684impl ClickState {
1685    pub fn new() -> Self {
1686        ClickState {
1687            button: MouseButton::Left,
1688            last_click: Instant::now(),
1689            last_position: Point::default(),
1690            current_count: 0,
1691        }
1692    }
1693
1694    /// update self and return the needed click count
1695    pub fn update(&mut self, button: MouseButton, new_position: Point<DevicePixels>) -> usize {
1696        if self.button == button && self.is_double_click(new_position) {
1697            self.current_count += 1;
1698        } else {
1699            self.current_count = 1;
1700        }
1701        self.last_click = Instant::now();
1702        self.last_position = new_position;
1703        self.button = button;
1704
1705        self.current_count
1706    }
1707
1708    #[inline]
1709    fn is_double_click(&self, new_position: Point<DevicePixels>) -> bool {
1710        let diff = self.last_position - new_position;
1711
1712        self.last_click.elapsed() < DOUBLE_CLICK_INTERVAL
1713            && diff.x.0.abs() <= DOUBLE_CLICK_SPATIAL_TOLERANCE
1714            && diff.y.0.abs() <= DOUBLE_CLICK_SPATIAL_TOLERANCE
1715    }
1716}
1717
1718fn register_wnd_class(icon_handle: HICON) -> PCWSTR {
1719    const CLASS_NAME: PCWSTR = w!("Zed::Window");
1720
1721    static ONCE: Once = Once::new();
1722    ONCE.call_once(|| {
1723        let wc = WNDCLASSW {
1724            lpfnWndProc: Some(wnd_proc),
1725            hIcon: icon_handle,
1726            lpszClassName: PCWSTR(CLASS_NAME.as_ptr()),
1727            style: CS_HREDRAW | CS_VREDRAW,
1728            ..Default::default()
1729        };
1730        unsafe { RegisterClassW(&wc) };
1731    });
1732
1733    CLASS_NAME
1734}
1735
1736unsafe extern "system" fn wnd_proc(
1737    hwnd: HWND,
1738    msg: u32,
1739    wparam: WPARAM,
1740    lparam: LPARAM,
1741) -> LRESULT {
1742    if msg == WM_NCCREATE {
1743        let cs = lparam.0 as *const CREATESTRUCTW;
1744        let cs = unsafe { &*cs };
1745        let ctx = cs.lpCreateParams as *mut WindowCreateContext;
1746        let ctx = unsafe { &mut *ctx };
1747        let inner = Rc::new(WindowsWindowInner::new(
1748            hwnd,
1749            cs,
1750            ctx.platform_inner.clone(),
1751            ctx.handle,
1752            ctx.hide_title_bar,
1753            ctx.display.clone(),
1754        ));
1755        let weak = Box::new(Rc::downgrade(&inner));
1756        unsafe { set_window_long(hwnd, GWLP_USERDATA, Box::into_raw(weak) as isize) };
1757        ctx.inner = Some(inner);
1758        return LRESULT(1);
1759    }
1760    let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak<WindowsWindowInner>;
1761    if ptr.is_null() {
1762        return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) };
1763    }
1764    let inner = unsafe { &*ptr };
1765    let r = if let Some(inner) = inner.upgrade() {
1766        inner.handle_msg(msg, wparam, lparam)
1767    } else {
1768        unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }
1769    };
1770    if msg == WM_NCDESTROY {
1771        unsafe { set_window_long(hwnd, GWLP_USERDATA, 0) };
1772        unsafe { drop(Box::from_raw(ptr)) };
1773    }
1774    r
1775}
1776
1777pub(crate) fn try_get_window_inner(hwnd: HWND) -> Option<Rc<WindowsWindowInner>> {
1778    if hwnd == HWND(0) {
1779        return None;
1780    }
1781
1782    let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak<WindowsWindowInner>;
1783    if !ptr.is_null() {
1784        let inner = unsafe { &*ptr };
1785        inner.upgrade()
1786    } else {
1787        None
1788    }
1789}
1790
1791fn basic_vkcode_to_string(code: u16, modifiers: Modifiers) -> Option<Keystroke> {
1792    match code {
1793        // VK_0 - VK_9
1794        48..=57 => Some(Keystroke {
1795            modifiers,
1796            key: format!("{}", code - VK_0.0),
1797            ime_key: None,
1798        }),
1799        // VK_A - VK_Z
1800        65..=90 => Some(Keystroke {
1801            modifiers,
1802            key: format!("{}", (b'a' + code as u8 - VK_A.0 as u8) as char),
1803            ime_key: None,
1804        }),
1805        // VK_F1 - VK_F24
1806        112..=135 => Some(Keystroke {
1807            modifiers,
1808            key: format!("f{}", code - VK_F1.0 + 1),
1809            ime_key: None,
1810        }),
1811        // OEM3: `/~, OEM_MINUS: -/_, OEM_PLUS: =/+, ...
1812        _ => {
1813            if let Some(key) = oemkey_vkcode_to_string(code) {
1814                Some(Keystroke {
1815                    modifiers,
1816                    key,
1817                    ime_key: None,
1818                })
1819            } else {
1820                None
1821            }
1822        }
1823    }
1824}
1825
1826fn oemkey_vkcode_to_string(code: u16) -> Option<String> {
1827    match code {
1828        186 => Some(";".to_string()), // VK_OEM_1
1829        187 => Some("=".to_string()), // VK_OEM_PLUS
1830        188 => Some(",".to_string()), // VK_OEM_COMMA
1831        189 => Some("-".to_string()), // VK_OEM_MINUS
1832        190 => Some(".".to_string()), // VK_OEM_PERIOD
1833        // https://kbdlayout.info/features/virtualkeys/VK_ABNT_C1
1834        191 | 193 => Some("/".to_string()), // VK_OEM_2 VK_ABNT_C1
1835        192 => Some("`".to_string()),       // VK_OEM_3
1836        219 => Some("[".to_string()),       // VK_OEM_4
1837        220 => Some("\\".to_string()),      // VK_OEM_5
1838        221 => Some("]".to_string()),       // VK_OEM_6
1839        222 => Some("'".to_string()),       // VK_OEM_7
1840        _ => None,
1841    }
1842}
1843
1844#[inline]
1845fn logical_size(physical_size: Size<DevicePixels>, scale_factor: f32) -> Size<Pixels> {
1846    Size {
1847        width: px(physical_size.width.0 as f32 / scale_factor),
1848        height: px(physical_size.height.0 as f32 / scale_factor),
1849    }
1850}
1851
1852#[inline]
1853fn logical_point(x: f32, y: f32, scale_factor: f32) -> Point<Pixels> {
1854    Point {
1855        x: px(x / scale_factor),
1856        y: px(y / scale_factor),
1857    }
1858}
1859
1860struct StyleAndBounds {
1861    style: WINDOW_STYLE,
1862    x: i32,
1863    y: i32,
1864    cx: i32,
1865    cy: i32,
1866}
1867
1868// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-dragqueryfilew
1869const DRAGDROP_GET_FILES_COUNT: u32 = 0xFFFFFFFF;
1870// https://learn.microsoft.com/en-us/windows/win32/controls/ttm-setdelaytime?redirectedfrom=MSDN
1871const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(500);
1872// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsystemmetrics
1873const DOUBLE_CLICK_SPATIAL_TOLERANCE: i32 = 4;
1874
1875#[cfg(test)]
1876mod tests {
1877    use super::ClickState;
1878    use crate::{point, DevicePixels, MouseButton};
1879    use std::time::Duration;
1880
1881    #[test]
1882    fn test_double_click_interval() {
1883        let mut state = ClickState::new();
1884        assert_eq!(
1885            state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1886            1
1887        );
1888        assert_eq!(
1889            state.update(MouseButton::Right, point(DevicePixels(0), DevicePixels(0))),
1890            1
1891        );
1892        assert_eq!(
1893            state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1894            1
1895        );
1896        assert_eq!(
1897            state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1898            2
1899        );
1900        state.last_click -= Duration::from_millis(700);
1901        assert_eq!(
1902            state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1903            1
1904        );
1905    }
1906
1907    #[test]
1908    fn test_double_click_spatial_tolerance() {
1909        let mut state = ClickState::new();
1910        assert_eq!(
1911            state.update(MouseButton::Left, point(DevicePixels(-3), DevicePixels(0))),
1912            1
1913        );
1914        assert_eq!(
1915            state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(3))),
1916            2
1917        );
1918        assert_eq!(
1919            state.update(MouseButton::Right, point(DevicePixels(3), DevicePixels(2))),
1920            1
1921        );
1922        assert_eq!(
1923            state.update(MouseButton::Right, point(DevicePixels(10), DevicePixels(0))),
1924            1
1925        );
1926    }
1927}