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