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