window.rs

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