Windows: Better cursor (#9451)

白山風露 created

Moved `SetCursor` calls to `WM_SETCURSOR`, which occurs when OS is
requires set cursor.

Release Notes:

- N/A

Change summary

crates/gpui/src/platform/windows/platform.rs | 32 ++----------------
crates/gpui/src/platform/windows/util.rs     | 37 +++++++++++++++++++++
crates/gpui/src/platform/windows/window.rs   | 20 +++++++++++
3 files changed, 60 insertions(+), 29 deletions(-)

Detailed changes

crates/gpui/src/platform/windows/platform.rs 🔗

@@ -62,6 +62,8 @@ pub(crate) struct WindowsPlatformInner {
     pub(crate) dispatch_event: OwnedHandle,
     pub(crate) settings: RefCell<WindowsPlatformSystemSettings>,
     pub icon: HICON,
+    // NOTE: standard cursor handles don't need to close.
+    pub(crate) current_cursor: Cell<HCURSOR>,
 }
 
 impl WindowsPlatformInner {
@@ -157,6 +159,7 @@ impl WindowsPlatform {
         let raw_window_handles = RwLock::new(SmallVec::new());
         let settings = RefCell::new(WindowsPlatformSystemSettings::new());
         let icon = load_icon().unwrap_or_default();
+        let current_cursor = Cell::new(load_cursor(CursorStyle::Arrow));
         let inner = Rc::new(WindowsPlatformInner {
             background_executor,
             foreground_executor,
@@ -167,6 +170,7 @@ impl WindowsPlatform {
             dispatch_event,
             settings,
             icon,
+            current_cursor,
         });
         Self { inner }
     }
@@ -646,29 +650,7 @@ impl Platform for WindowsPlatform {
     }
 
     fn set_cursor_style(&self, style: CursorStyle) {
-        let handle = match style {
-            CursorStyle::IBeam | CursorStyle::IBeamCursorForVerticalLayout => unsafe {
-                load_cursor(IDC_IBEAM)
-            },
-            CursorStyle::Crosshair => unsafe { load_cursor(IDC_CROSS) },
-            CursorStyle::PointingHand | CursorStyle::DragLink => unsafe { load_cursor(IDC_HAND) },
-            CursorStyle::ResizeLeft | CursorStyle::ResizeRight | CursorStyle::ResizeLeftRight => unsafe {
-                load_cursor(IDC_SIZEWE)
-            },
-            CursorStyle::ResizeUp | CursorStyle::ResizeDown | CursorStyle::ResizeUpDown => unsafe {
-                load_cursor(IDC_SIZENS)
-            },
-            CursorStyle::OperationNotAllowed => unsafe { load_cursor(IDC_NO) },
-            _ => unsafe { load_cursor(IDC_ARROW) },
-        };
-        if handle.is_err() {
-            log::error!(
-                "Error loading cursor image: {}",
-                std::io::Error::last_os_error()
-            );
-            return;
-        }
-        let _ = unsafe { SetCursor(HCURSOR(handle.unwrap().0)) };
+        self.inner.current_cursor.set(load_cursor(style));
     }
 
     // todo(windows)
@@ -773,10 +755,6 @@ impl Drop for WindowsPlatform {
     }
 }
 
-unsafe fn load_cursor(name: PCWSTR) -> Result<HANDLE> {
-    LoadImageW(None, name, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED).map_err(|e| anyhow!(e))
-}
-
 fn open_target(target: &str) {
     unsafe {
         let ret = ShellExecuteW(

crates/gpui/src/platform/windows/util.rs 🔗

@@ -1,6 +1,10 @@
-use util::ResultExt;
+use std::sync::OnceLock;
+
+use ::util::ResultExt;
 use windows::Win32::{Foundation::*, System::Threading::*, UI::WindowsAndMessaging::*};
 
+use crate::*;
+
 pub(crate) trait HiLoWord {
     fn hiword(&self) -> u16;
     fn loword(&self) -> u16;
@@ -100,3 +104,34 @@ pub(crate) fn create_event() -> windows::core::Result<OwnedHandle> {
 pub(crate) fn windows_credentials_target_name(url: &str) -> String {
     format!("zed:url={}", url)
 }
+
+pub(crate) fn load_cursor(style: CursorStyle) -> HCURSOR {
+    static ARROW: OnceLock<HCURSOR> = OnceLock::new();
+    static IBEAM: OnceLock<HCURSOR> = OnceLock::new();
+    static CROSS: OnceLock<HCURSOR> = OnceLock::new();
+    static HAND: OnceLock<HCURSOR> = OnceLock::new();
+    static SIZEWE: OnceLock<HCURSOR> = OnceLock::new();
+    static SIZENS: OnceLock<HCURSOR> = OnceLock::new();
+    static NO: OnceLock<HCURSOR> = OnceLock::new();
+    let (lock, name) = match style {
+        CursorStyle::IBeam | CursorStyle::IBeamCursorForVerticalLayout => (&IBEAM, IDC_IBEAM),
+        CursorStyle::Crosshair => (&CROSS, IDC_CROSS),
+        CursorStyle::PointingHand | CursorStyle::DragLink => (&HAND, IDC_HAND),
+        CursorStyle::ResizeLeft | CursorStyle::ResizeRight | CursorStyle::ResizeLeftRight => {
+            (&SIZEWE, IDC_SIZEWE)
+        }
+        CursorStyle::ResizeUp | CursorStyle::ResizeDown | CursorStyle::ResizeUpDown => {
+            (&SIZENS, IDC_SIZENS)
+        }
+        CursorStyle::OperationNotAllowed => (&NO, IDC_NO),
+        _ => (&ARROW, IDC_ARROW),
+    };
+    *lock.get_or_init(|| {
+        HCURSOR(
+            unsafe { LoadImageW(None, name, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED) }
+                .log_err()
+                .unwrap_or_default()
+                .0,
+        )
+    })
+}

crates/gpui/src/platform/windows/window.rs 🔗

@@ -228,6 +228,7 @@ impl WindowsWindowInner {
             WM_IME_STARTCOMPOSITION => self.handle_ime_position(),
             WM_IME_COMPOSITION => self.handle_ime_composition(lparam),
             WM_IME_CHAR => self.handle_ime_char(wparam),
+            WM_SETCURSOR => self.handle_set_cursor(lparam),
             _ => None,
         };
         if let Some(n) = handled {
@@ -1071,6 +1072,24 @@ impl WindowsWindowInner {
 
         None
     }
+
+    fn handle_set_cursor(&self, lparam: LPARAM) -> Option<isize> {
+        if matches!(
+            lparam.loword() as u32,
+            HTLEFT
+                | HTRIGHT
+                | HTTOP
+                | HTTOPLEFT
+                | HTTOPRIGHT
+                | HTBOTTOM
+                | HTBOTTOMLEFT
+                | HTBOTTOMRIGHT
+        ) {
+            return None;
+        }
+        unsafe { SetCursor(self.platform_inner.current_cursor.get()) };
+        Some(1)
+    }
 }
 
 #[derive(Default)]
@@ -1572,7 +1591,6 @@ fn register_wnd_class(icon_handle: HICON) -> PCWSTR {
         let wc = WNDCLASSW {
             lpfnWndProc: Some(wnd_proc),
             hIcon: icon_handle,
-            hCursor: unsafe { LoadCursorW(None, IDC_ARROW).ok().unwrap() },
             lpszClassName: PCWSTR(CLASS_NAME.as_ptr()),
             style: CS_HREDRAW | CS_VREDRAW,
             ..Default::default()