window.rs

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