window.rs

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