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;
  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    ) -> Self {
  66        let monitor_dpi = unsafe { GetDpiForWindow(hwnd) } as f32;
  67        let origin = Cell::new(Point {
  68            x: DevicePixels(cs.x),
  69            y: DevicePixels(cs.y),
  70        });
  71        let physical_size = Cell::new(Size {
  72            width: DevicePixels(cs.cx),
  73            height: DevicePixels(cs.cy),
  74        });
  75        let scale_factor = Cell::new(monitor_dpi / USER_DEFAULT_SCREEN_DPI as f32);
  76        let input_handler = Cell::new(None);
  77        struct RawWindow {
  78            hwnd: isize,
  79        }
  80        impl rwh::HasWindowHandle for RawWindow {
  81            fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
  82                Ok(unsafe {
  83                    let hwnd = NonZeroIsize::new_unchecked(self.hwnd);
  84                    let mut handle = rwh::Win32WindowHandle::new(hwnd);
  85                    let hinstance = get_window_long(HWND(self.hwnd), GWLP_HINSTANCE);
  86                    handle.hinstance = NonZeroIsize::new(hinstance);
  87                    rwh::WindowHandle::borrow_raw(handle.into())
  88                })
  89            }
  90        }
  91        impl rwh::HasDisplayHandle for RawWindow {
  92            fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
  93                let handle = rwh::WindowsDisplayHandle::new();
  94                Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
  95            }
  96        }
  97
  98        let raw = RawWindow { hwnd: hwnd.0 as _ };
  99        let gpu = Arc::new(
 100            unsafe {
 101                gpu::Context::init_windowed(
 102                    &raw,
 103                    gpu::ContextDesc {
 104                        validation: false,
 105                        capture: false,
 106                        overlay: false,
 107                    },
 108                )
 109            }
 110            .unwrap(),
 111        );
 112        let extent = gpu::Extent {
 113            width: 1,
 114            height: 1,
 115            depth: 1,
 116        };
 117        let renderer = RefCell::new(BladeRenderer::new(gpu, extent));
 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}
1245
1246impl WindowsWindow {
1247    pub(crate) fn new(
1248        platform_inner: Rc<WindowsPlatformInner>,
1249        handle: AnyWindowHandle,
1250        options: WindowParams,
1251    ) -> Self {
1252        let classname = register_wnd_class(platform_inner.icon);
1253        let hide_title_bar = options
1254            .titlebar
1255            .as_ref()
1256            .map(|titlebar| titlebar.appears_transparent)
1257            .unwrap_or(false);
1258        let windowname = HSTRING::from(
1259            options
1260                .titlebar
1261                .as_ref()
1262                .and_then(|titlebar| titlebar.title.as_ref())
1263                .map(|title| title.as_ref())
1264                .unwrap_or(""),
1265        );
1266        let dwstyle = WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX;
1267        let x = options.bounds.origin.x.0;
1268        let y = options.bounds.origin.y.0;
1269        let nwidth = options.bounds.size.width.0;
1270        let nheight = options.bounds.size.height.0;
1271        let hwndparent = HWND::default();
1272        let hmenu = HMENU::default();
1273        let hinstance = get_module_handle();
1274        let mut context = WindowCreateContext {
1275            inner: None,
1276            platform_inner: platform_inner.clone(),
1277            handle,
1278            hide_title_bar,
1279            // todo(windows) move window to target monitor
1280            // options.display_id
1281            display: Rc::new(WindowsDisplay::primary_monitor().unwrap()),
1282        };
1283        let lpparam = Some(&context as *const _ as *const _);
1284        unsafe {
1285            CreateWindowExW(
1286                WS_EX_APPWINDOW,
1287                classname,
1288                &windowname,
1289                dwstyle,
1290                x,
1291                y,
1292                nwidth,
1293                nheight,
1294                hwndparent,
1295                hmenu,
1296                hinstance,
1297                lpparam,
1298            )
1299        };
1300        let drag_drop_handler = {
1301            let inner = context.inner.as_ref().unwrap();
1302            let handler = WindowsDragDropHandler(Rc::clone(inner));
1303            let drag_drop_handler: IDropTarget = handler.into();
1304            unsafe {
1305                RegisterDragDrop(inner.hwnd, &drag_drop_handler)
1306                    .expect("unable to register drag-drop event")
1307            };
1308            drag_drop_handler
1309        };
1310        let wnd = Self {
1311            inner: context.inner.unwrap(),
1312            drag_drop_handler,
1313        };
1314        platform_inner
1315            .raw_window_handles
1316            .write()
1317            .push(wnd.inner.hwnd);
1318
1319        unsafe { ShowWindow(wnd.inner.hwnd, SW_SHOW) };
1320        wnd
1321    }
1322}
1323
1324impl rwh::HasWindowHandle for WindowsWindow {
1325    fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
1326        let raw =
1327            rwh::Win32WindowHandle::new(unsafe { NonZeroIsize::new_unchecked(self.inner.hwnd.0) })
1328                .into();
1329        Ok(unsafe { rwh::WindowHandle::borrow_raw(raw) })
1330    }
1331}
1332
1333// todo(windows)
1334impl rwh::HasDisplayHandle for WindowsWindow {
1335    fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
1336        unimplemented!()
1337    }
1338}
1339
1340impl Drop for WindowsWindow {
1341    fn drop(&mut self) {
1342        unsafe {
1343            let _ = RevokeDragDrop(self.inner.hwnd);
1344            self.inner.renderer.borrow_mut().destroy();
1345        }
1346    }
1347}
1348
1349impl PlatformWindow for WindowsWindow {
1350    fn bounds(&self) -> Bounds<DevicePixels> {
1351        Bounds {
1352            origin: self.inner.origin.get(),
1353            size: self.inner.physical_size.get(),
1354        }
1355    }
1356
1357    fn is_maximized(&self) -> bool {
1358        self.inner.is_maximized()
1359    }
1360
1361    fn is_minimized(&self) -> bool {
1362        self.inner.is_minimized()
1363    }
1364
1365    /// get the logical size of the app's drawable area.
1366    ///
1367    /// Currently, GPUI uses logical size of the app to handle mouse interactions (such as
1368    /// whether the mouse collides with other elements of GPUI).
1369    fn content_size(&self) -> Size<Pixels> {
1370        logical_size(
1371            self.inner.physical_size.get(),
1372            self.inner.scale_factor.get(),
1373        )
1374    }
1375
1376    fn scale_factor(&self) -> f32 {
1377        self.inner.scale_factor.get()
1378    }
1379
1380    // todo(windows)
1381    fn appearance(&self) -> WindowAppearance {
1382        WindowAppearance::Dark
1383    }
1384
1385    fn display(&self) -> Rc<dyn PlatformDisplay> {
1386        self.inner.display.borrow().clone()
1387    }
1388
1389    fn mouse_position(&self) -> Point<Pixels> {
1390        let point = unsafe {
1391            let mut point: POINT = std::mem::zeroed();
1392            GetCursorPos(&mut point)
1393                .context("unable to get cursor position")
1394                .log_err();
1395            ScreenToClient(self.inner.hwnd, &mut point);
1396            point
1397        };
1398        logical_point(
1399            point.x as f32,
1400            point.y as f32,
1401            self.inner.scale_factor.get(),
1402        )
1403    }
1404
1405    // todo(windows)
1406    fn modifiers(&self) -> Modifiers {
1407        Modifiers::none()
1408    }
1409
1410    // todo(windows)
1411    fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
1412        self.inner.input_handler.set(Some(input_handler));
1413    }
1414
1415    // todo(windows)
1416    fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
1417        self.inner.input_handler.take()
1418    }
1419
1420    fn prompt(
1421        &self,
1422        level: PromptLevel,
1423        msg: &str,
1424        detail: Option<&str>,
1425        answers: &[&str],
1426    ) -> Option<Receiver<usize>> {
1427        let (done_tx, done_rx) = oneshot::channel();
1428        let msg = msg.to_string();
1429        let detail_string = match detail {
1430            Some(info) => Some(info.to_string()),
1431            None => None,
1432        };
1433        let answers = answers.iter().map(|s| s.to_string()).collect::<Vec<_>>();
1434        let handle = self.inner.hwnd;
1435        self.inner
1436            .platform_inner
1437            .foreground_executor
1438            .spawn(async move {
1439                unsafe {
1440                    let mut config;
1441                    config = std::mem::zeroed::<TASKDIALOGCONFIG>();
1442                    config.cbSize = std::mem::size_of::<TASKDIALOGCONFIG>() as _;
1443                    config.hwndParent = handle;
1444                    let title;
1445                    let main_icon;
1446                    match level {
1447                        crate::PromptLevel::Info => {
1448                            title = windows::core::w!("Info");
1449                            main_icon = TD_INFORMATION_ICON;
1450                        }
1451                        crate::PromptLevel::Warning => {
1452                            title = windows::core::w!("Warning");
1453                            main_icon = TD_WARNING_ICON;
1454                        }
1455                        crate::PromptLevel::Critical | crate::PromptLevel::Destructive => {
1456                            title = windows::core::w!("Critical");
1457                            main_icon = TD_ERROR_ICON;
1458                        }
1459                    };
1460                    config.pszWindowTitle = title;
1461                    config.Anonymous1.pszMainIcon = main_icon;
1462                    let instruction = msg.encode_utf16().chain(once(0)).collect_vec();
1463                    config.pszMainInstruction = PCWSTR::from_raw(instruction.as_ptr());
1464                    let hints_encoded;
1465                    if let Some(ref hints) = detail_string {
1466                        hints_encoded = hints.encode_utf16().chain(once(0)).collect_vec();
1467                        config.pszContent = PCWSTR::from_raw(hints_encoded.as_ptr());
1468                    };
1469                    let mut buttons = Vec::new();
1470                    let mut btn_encoded = Vec::new();
1471                    for (index, btn_string) in answers.iter().enumerate() {
1472                        let encoded = btn_string.encode_utf16().chain(once(0)).collect_vec();
1473                        buttons.push(TASKDIALOG_BUTTON {
1474                            nButtonID: index as _,
1475                            pszButtonText: PCWSTR::from_raw(encoded.as_ptr()),
1476                        });
1477                        btn_encoded.push(encoded);
1478                    }
1479                    config.cButtons = buttons.len() as _;
1480                    config.pButtons = buttons.as_ptr();
1481
1482                    config.pfCallback = None;
1483                    let mut res = std::mem::zeroed();
1484                    let _ = TaskDialogIndirect(&config, Some(&mut res), None, None)
1485                        .inspect_err(|e| log::error!("unable to create task dialog: {}", e));
1486
1487                    let _ = done_tx.send(res as usize);
1488                }
1489            })
1490            .detach();
1491
1492        Some(done_rx)
1493    }
1494
1495    fn activate(&self) {
1496        unsafe { SetActiveWindow(self.inner.hwnd) };
1497        unsafe { SetFocus(self.inner.hwnd) };
1498        unsafe { SetForegroundWindow(self.inner.hwnd) };
1499    }
1500
1501    fn is_active(&self) -> bool {
1502        self.inner.hwnd == unsafe { GetActiveWindow() }
1503    }
1504
1505    // todo(windows)
1506    fn set_title(&mut self, title: &str) {
1507        unsafe { SetWindowTextW(self.inner.hwnd, &HSTRING::from(title)) }
1508            .inspect_err(|e| log::error!("Set title failed: {e}"))
1509            .ok();
1510    }
1511
1512    fn set_app_id(&mut self, _app_id: &str) {}
1513
1514    fn set_background_appearance(&mut self, _background_appearance: WindowBackgroundAppearance) {
1515        // todo(windows)
1516    }
1517
1518    // todo(windows)
1519    fn set_edited(&mut self, _edited: bool) {}
1520
1521    // todo(windows)
1522    fn show_character_palette(&self) {}
1523
1524    fn minimize(&self) {
1525        unsafe { ShowWindowAsync(self.inner.hwnd, SW_MINIMIZE) };
1526    }
1527
1528    fn zoom(&self) {
1529        unsafe { ShowWindowAsync(self.inner.hwnd, SW_MAXIMIZE) };
1530    }
1531
1532    fn toggle_fullscreen(&self) {
1533        self.inner
1534            .platform_inner
1535            .foreground_executor
1536            .spawn(self.inner.clone().toggle_fullscreen())
1537            .detach();
1538    }
1539
1540    fn is_fullscreen(&self) -> bool {
1541        self.inner.is_fullscreen()
1542    }
1543
1544    // todo(windows)
1545    fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
1546        self.inner.callbacks.borrow_mut().request_frame = Some(callback);
1547    }
1548
1549    // todo(windows)
1550    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>) {
1551        self.inner.callbacks.borrow_mut().input = Some(callback);
1552    }
1553
1554    // todo(windows)
1555    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
1556        self.inner.callbacks.borrow_mut().active_status_change = Some(callback);
1557    }
1558
1559    // todo(windows)
1560    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
1561        self.inner.callbacks.borrow_mut().resize = Some(callback);
1562    }
1563
1564    // todo(windows)
1565    fn on_moved(&self, callback: Box<dyn FnMut()>) {
1566        self.inner.callbacks.borrow_mut().moved = Some(callback);
1567    }
1568
1569    // todo(windows)
1570    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
1571        self.inner.callbacks.borrow_mut().should_close = Some(callback);
1572    }
1573
1574    // todo(windows)
1575    fn on_close(&self, callback: Box<dyn FnOnce()>) {
1576        self.inner.callbacks.borrow_mut().close = Some(callback);
1577    }
1578
1579    // todo(windows)
1580    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
1581        self.inner.callbacks.borrow_mut().appearance_changed = Some(callback);
1582    }
1583
1584    // todo(windows)
1585    fn draw(&self, scene: &Scene) {
1586        self.inner.renderer.borrow_mut().draw(scene)
1587    }
1588
1589    // todo(windows)
1590    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
1591        self.inner.renderer.borrow().sprite_atlas().clone()
1592    }
1593
1594    fn get_raw_handle(&self) -> HWND {
1595        self.inner.hwnd
1596    }
1597}
1598
1599#[implement(IDropTarget)]
1600struct WindowsDragDropHandler(pub Rc<WindowsWindowInner>);
1601
1602#[allow(non_snake_case)]
1603impl IDropTarget_Impl for WindowsDragDropHandler {
1604    fn DragEnter(
1605        &self,
1606        pdataobj: Option<&IDataObject>,
1607        _grfkeystate: MODIFIERKEYS_FLAGS,
1608        pt: &POINTL,
1609        pdweffect: *mut DROPEFFECT,
1610    ) -> windows::core::Result<()> {
1611        unsafe {
1612            let Some(idata_obj) = pdataobj else {
1613                log::info!("no dragging file or directory detected");
1614                return Ok(());
1615            };
1616            let config = FORMATETC {
1617                cfFormat: CF_HDROP.0,
1618                ptd: std::ptr::null_mut() as _,
1619                dwAspect: DVASPECT_CONTENT.0,
1620                lindex: -1,
1621                tymed: TYMED_HGLOBAL.0 as _,
1622            };
1623            let mut paths = SmallVec::<[PathBuf; 2]>::new();
1624            if idata_obj.QueryGetData(&config as _) == S_OK {
1625                *pdweffect = DROPEFFECT_LINK;
1626                let Ok(mut idata) = idata_obj.GetData(&config as _) else {
1627                    return Ok(());
1628                };
1629                if idata.u.hGlobal.is_invalid() {
1630                    return Ok(());
1631                }
1632                let hdrop = idata.u.hGlobal.0 as *mut HDROP;
1633                let file_count = DragQueryFileW(*hdrop, DRAGDROP_GET_FILES_COUNT, None);
1634                for file_index in 0..file_count {
1635                    let filename_length = DragQueryFileW(*hdrop, file_index, None) as usize;
1636                    let mut buffer = vec![0u16; filename_length + 1];
1637                    let ret = DragQueryFileW(*hdrop, file_index, Some(buffer.as_mut_slice()));
1638                    if ret == 0 {
1639                        log::error!("unable to read file name");
1640                        continue;
1641                    }
1642                    if let Ok(file_name) = String::from_utf16(&buffer[0..filename_length]) {
1643                        if let Ok(path) = PathBuf::from_str(&file_name) {
1644                            paths.push(path);
1645                        }
1646                    }
1647                }
1648                ReleaseStgMedium(&mut idata);
1649                let input = PlatformInput::FileDrop(crate::FileDropEvent::Entered {
1650                    position: Point {
1651                        x: Pixels(pt.x as _),
1652                        y: Pixels(pt.y as _),
1653                    },
1654                    paths: crate::ExternalPaths(paths),
1655                });
1656                self.0.handle_drag_drop(input);
1657            } else {
1658                *pdweffect = DROPEFFECT_NONE;
1659            }
1660        }
1661        Ok(())
1662    }
1663
1664    fn DragOver(
1665        &self,
1666        _grfkeystate: MODIFIERKEYS_FLAGS,
1667        pt: &POINTL,
1668        _pdweffect: *mut DROPEFFECT,
1669    ) -> windows::core::Result<()> {
1670        let input = PlatformInput::FileDrop(crate::FileDropEvent::Pending {
1671            position: Point {
1672                x: Pixels(pt.x as _),
1673                y: Pixels(pt.y as _),
1674            },
1675        });
1676        self.0.handle_drag_drop(input);
1677
1678        Ok(())
1679    }
1680
1681    fn DragLeave(&self) -> windows::core::Result<()> {
1682        let input = PlatformInput::FileDrop(crate::FileDropEvent::Exited);
1683        self.0.handle_drag_drop(input);
1684
1685        Ok(())
1686    }
1687
1688    fn Drop(
1689        &self,
1690        _pdataobj: Option<&IDataObject>,
1691        _grfkeystate: MODIFIERKEYS_FLAGS,
1692        pt: &POINTL,
1693        _pdweffect: *mut DROPEFFECT,
1694    ) -> windows::core::Result<()> {
1695        let input = PlatformInput::FileDrop(crate::FileDropEvent::Submit {
1696            position: Point {
1697                x: Pixels(pt.x as _),
1698                y: Pixels(pt.y as _),
1699            },
1700        });
1701        self.0.handle_drag_drop(input);
1702
1703        Ok(())
1704    }
1705}
1706
1707#[derive(Debug)]
1708struct ClickState {
1709    button: MouseButton,
1710    last_click: Instant,
1711    last_position: Point<DevicePixels>,
1712    current_count: usize,
1713}
1714
1715impl ClickState {
1716    pub fn new() -> Self {
1717        ClickState {
1718            button: MouseButton::Left,
1719            last_click: Instant::now(),
1720            last_position: Point::default(),
1721            current_count: 0,
1722        }
1723    }
1724
1725    /// update self and return the needed click count
1726    pub fn update(&mut self, button: MouseButton, new_position: Point<DevicePixels>) -> usize {
1727        if self.button == button && self.is_double_click(new_position) {
1728            self.current_count += 1;
1729        } else {
1730            self.current_count = 1;
1731        }
1732        self.last_click = Instant::now();
1733        self.last_position = new_position;
1734        self.button = button;
1735
1736        self.current_count
1737    }
1738
1739    #[inline]
1740    fn is_double_click(&self, new_position: Point<DevicePixels>) -> bool {
1741        let diff = self.last_position - new_position;
1742
1743        self.last_click.elapsed() < DOUBLE_CLICK_INTERVAL
1744            && diff.x.0.abs() <= DOUBLE_CLICK_SPATIAL_TOLERANCE
1745            && diff.y.0.abs() <= DOUBLE_CLICK_SPATIAL_TOLERANCE
1746    }
1747}
1748
1749fn register_wnd_class(icon_handle: HICON) -> PCWSTR {
1750    const CLASS_NAME: PCWSTR = w!("Zed::Window");
1751
1752    static ONCE: Once = Once::new();
1753    ONCE.call_once(|| {
1754        let wc = WNDCLASSW {
1755            lpfnWndProc: Some(wnd_proc),
1756            hIcon: icon_handle,
1757            lpszClassName: PCWSTR(CLASS_NAME.as_ptr()),
1758            style: CS_HREDRAW | CS_VREDRAW,
1759            hInstance: get_module_handle().into(),
1760            ..Default::default()
1761        };
1762        unsafe { RegisterClassW(&wc) };
1763    });
1764
1765    CLASS_NAME
1766}
1767
1768unsafe extern "system" fn wnd_proc(
1769    hwnd: HWND,
1770    msg: u32,
1771    wparam: WPARAM,
1772    lparam: LPARAM,
1773) -> LRESULT {
1774    if msg == WM_NCCREATE {
1775        let cs = lparam.0 as *const CREATESTRUCTW;
1776        let cs = unsafe { &*cs };
1777        let ctx = cs.lpCreateParams as *mut WindowCreateContext;
1778        let ctx = unsafe { &mut *ctx };
1779        let inner = Rc::new(WindowsWindowInner::new(
1780            hwnd,
1781            cs,
1782            ctx.platform_inner.clone(),
1783            ctx.handle,
1784            ctx.hide_title_bar,
1785            ctx.display.clone(),
1786        ));
1787        let weak = Box::new(Rc::downgrade(&inner));
1788        unsafe { set_window_long(hwnd, GWLP_USERDATA, Box::into_raw(weak) as isize) };
1789        ctx.inner = Some(inner);
1790        return LRESULT(1);
1791    }
1792    let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak<WindowsWindowInner>;
1793    if ptr.is_null() {
1794        return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) };
1795    }
1796    let inner = unsafe { &*ptr };
1797    let r = if let Some(inner) = inner.upgrade() {
1798        inner.handle_msg(msg, wparam, lparam)
1799    } else {
1800        unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }
1801    };
1802    if msg == WM_NCDESTROY {
1803        unsafe { set_window_long(hwnd, GWLP_USERDATA, 0) };
1804        unsafe { drop(Box::from_raw(ptr)) };
1805    }
1806    r
1807}
1808
1809pub(crate) fn try_get_window_inner(hwnd: HWND) -> Option<Rc<WindowsWindowInner>> {
1810    if hwnd == HWND(0) {
1811        return None;
1812    }
1813
1814    let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak<WindowsWindowInner>;
1815    if !ptr.is_null() {
1816        let inner = unsafe { &*ptr };
1817        inner.upgrade()
1818    } else {
1819        None
1820    }
1821}
1822
1823fn basic_vkcode_to_string(code: u16, modifiers: Modifiers) -> Option<Keystroke> {
1824    match code {
1825        // VK_0 - VK_9
1826        48..=57 => Some(Keystroke {
1827            modifiers,
1828            key: format!("{}", code - VK_0.0),
1829            ime_key: None,
1830        }),
1831        // VK_A - VK_Z
1832        65..=90 => Some(Keystroke {
1833            modifiers,
1834            key: format!("{}", (b'a' + code as u8 - VK_A.0 as u8) as char),
1835            ime_key: None,
1836        }),
1837        // VK_F1 - VK_F24
1838        112..=135 => Some(Keystroke {
1839            modifiers,
1840            key: format!("f{}", code - VK_F1.0 + 1),
1841            ime_key: None,
1842        }),
1843        // OEM3: `/~, OEM_MINUS: -/_, OEM_PLUS: =/+, ...
1844        _ => {
1845            if let Some(key) = oemkey_vkcode_to_string(code) {
1846                Some(Keystroke {
1847                    modifiers,
1848                    key,
1849                    ime_key: None,
1850                })
1851            } else {
1852                None
1853            }
1854        }
1855    }
1856}
1857
1858fn oemkey_vkcode_to_string(code: u16) -> Option<String> {
1859    match code {
1860        186 => Some(";".to_string()), // VK_OEM_1
1861        187 => Some("=".to_string()), // VK_OEM_PLUS
1862        188 => Some(",".to_string()), // VK_OEM_COMMA
1863        189 => Some("-".to_string()), // VK_OEM_MINUS
1864        190 => Some(".".to_string()), // VK_OEM_PERIOD
1865        // https://kbdlayout.info/features/virtualkeys/VK_ABNT_C1
1866        191 | 193 => Some("/".to_string()), // VK_OEM_2 VK_ABNT_C1
1867        192 => Some("`".to_string()),       // VK_OEM_3
1868        219 => Some("[".to_string()),       // VK_OEM_4
1869        220 => Some("\\".to_string()),      // VK_OEM_5
1870        221 => Some("]".to_string()),       // VK_OEM_6
1871        222 => Some("'".to_string()),       // VK_OEM_7
1872        _ => None,
1873    }
1874}
1875
1876#[inline]
1877fn logical_size(physical_size: Size<DevicePixels>, scale_factor: f32) -> Size<Pixels> {
1878    Size {
1879        width: px(physical_size.width.0 as f32 / scale_factor),
1880        height: px(physical_size.height.0 as f32 / scale_factor),
1881    }
1882}
1883
1884#[inline]
1885fn logical_point(x: f32, y: f32, scale_factor: f32) -> Point<Pixels> {
1886    Point {
1887        x: px(x / scale_factor),
1888        y: px(y / scale_factor),
1889    }
1890}
1891
1892struct StyleAndBounds {
1893    style: WINDOW_STYLE,
1894    x: i32,
1895    y: i32,
1896    cx: i32,
1897    cy: i32,
1898}
1899
1900fn get_module_handle() -> HMODULE {
1901    unsafe {
1902        let mut h_module = std::mem::zeroed();
1903        GetModuleHandleExW(
1904            GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
1905            windows::core::w!("ZedModule"),
1906            &mut h_module,
1907        )
1908        .expect("Unable to get module handle"); // this should never fail
1909
1910        h_module
1911    }
1912}
1913
1914// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-dragqueryfilew
1915const DRAGDROP_GET_FILES_COUNT: u32 = 0xFFFFFFFF;
1916// https://learn.microsoft.com/en-us/windows/win32/controls/ttm-setdelaytime?redirectedfrom=MSDN
1917const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(500);
1918// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsystemmetrics
1919const DOUBLE_CLICK_SPATIAL_TOLERANCE: i32 = 4;
1920const SIZE_MOVE_LOOP_TIMER_ID: usize = 1;
1921
1922#[cfg(test)]
1923mod tests {
1924    use super::ClickState;
1925    use crate::{point, DevicePixels, MouseButton};
1926    use std::time::Duration;
1927
1928    #[test]
1929    fn test_double_click_interval() {
1930        let mut state = ClickState::new();
1931        assert_eq!(
1932            state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1933            1
1934        );
1935        assert_eq!(
1936            state.update(MouseButton::Right, point(DevicePixels(0), DevicePixels(0))),
1937            1
1938        );
1939        assert_eq!(
1940            state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1941            1
1942        );
1943        assert_eq!(
1944            state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1945            2
1946        );
1947        state.last_click -= Duration::from_millis(700);
1948        assert_eq!(
1949            state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1950            1
1951        );
1952    }
1953
1954    #[test]
1955    fn test_double_click_spatial_tolerance() {
1956        let mut state = ClickState::new();
1957        assert_eq!(
1958            state.update(MouseButton::Left, point(DevicePixels(-3), DevicePixels(0))),
1959            1
1960        );
1961        assert_eq!(
1962            state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(3))),
1963            2
1964        );
1965        assert_eq!(
1966            state.update(MouseButton::Right, point(DevicePixels(3), DevicePixels(2))),
1967            1
1968        );
1969        assert_eq!(
1970            state.update(MouseButton::Right, point(DevicePixels(10), DevicePixels(0))),
1971            1
1972        );
1973    }
1974}