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