window.rs

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