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