From 7367b8be6c23d4be391c69634b41eeb7c223b364 Mon Sep 17 00:00:00 2001 From: hatoo Date: Fri, 3 Jan 2020 09:34:14 +0900 Subject: [PATCH] On Macos, Hide cursor only inside window (#1348) * On MacOS, hide cursor by invisible cursor * Add CHANGELOG * Fix variable name * Add comments for `CURSOR_BYTES` --- CHANGELOG.md | 1 + src/platform_impl/macos/util/cursor.rs | 38 +++++++++++++++++++++++++- src/platform_impl/macos/view.rs | 31 +++++++++++++++++---- src/platform_impl/macos/window.rs | 38 +++++++++++++------------- 4 files changed, 82 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7023ed6..ad7bfed0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- On macOS, fix `set_cursor_visible` hides cursor outside of window. - On macOS, fix `CursorEntered` and `CursorLeft` events fired at old window size. - On macOS, fix error when `set_fullscreen` is called during fullscreen transition. - On all platforms except mobile and WASM, implement `Window::set_minimized`. diff --git a/src/platform_impl/macos/util/cursor.rs b/src/platform_impl/macos/util/cursor.rs index 7f0b57fe..5c4d1537 100644 --- a/src/platform_impl/macos/util/cursor.rs +++ b/src/platform_impl/macos/util/cursor.rs @@ -3,7 +3,8 @@ use cocoa::{ base::{id, nil}, foundation::{NSDictionary, NSPoint, NSString}, }; -use objc::runtime::Sel; +use objc::{runtime::Sel, runtime::NO}; +use std::cell::RefCell; use crate::window::CursorIcon; @@ -126,3 +127,38 @@ pub unsafe fn load_webkit_cursor(cursor_name: &str) -> id { hotSpot:point ] } + +pub unsafe fn invisible_cursor() -> id { + // 16x16 GIF data for invisible cursor + // You can reproduce this via ImageMagick. + // $ convert -size 16x16 xc:none cursor.gif + static CURSOR_BYTES: &[u8] = &[ + 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x10, 0x00, 0x10, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x0E, 0x84, 0x8F, 0xA9, 0xCB, 0xED, 0x0F, + 0xA3, 0x9C, 0xB4, 0xDA, 0x8B, 0xB3, 0x3E, 0x05, 0x00, 0x3B, + ]; + + thread_local! { + // We can't initialize this at startup. + static CURSOR_OBJECT: RefCell = RefCell::new(nil); + } + + CURSOR_OBJECT.with(|cursor_obj| { + if *cursor_obj.borrow() == nil { + // Create a cursor from `CURSOR_BYTES` + let cursor_data: id = msg_send![class!(NSData), + dataWithBytesNoCopy:CURSOR_BYTES as *const [u8] + length:CURSOR_BYTES.len() + freeWhenDone:NO + ]; + + let ns_image: id = msg_send![class!(NSImage), alloc]; + let _: id = msg_send![ns_image, initWithData: cursor_data]; + let cursor: id = msg_send![class!(NSCursor), alloc]; + *cursor_obj.borrow_mut() = + msg_send![cursor, initWithImage:ns_image hotSpot: NSPoint::new(0.0, 0.0)]; + } + *cursor_obj.borrow() + }) +} diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index 5e448cc7..8795ea18 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -35,9 +35,23 @@ use crate::{ window::WindowId, }; +pub struct CursorState { + pub visible: bool, + pub cursor: util::Cursor, +} + +impl Default for CursorState { + fn default() -> Self { + Self { + visible: true, + cursor: Default::default(), + } + } +} + struct ViewState { ns_window: id, - pub cursor: Arc>, + pub cursor_state: Arc>, ime_spot: Option<(f64, f64)>, raw_characters: Option, is_key_down: bool, @@ -45,12 +59,12 @@ struct ViewState { tracking_rect: Option, } -pub fn new_view(ns_window: id) -> (IdRef, Weak>) { - let cursor = Default::default(); - let cursor_access = Arc::downgrade(&cursor); +pub fn new_view(ns_window: id) -> (IdRef, Weak>) { + let cursor_state = Default::default(); + let cursor_access = Arc::downgrade(&cursor_state); let state = ViewState { ns_window, - cursor, + cursor_state, ime_spot: None, raw_characters: None, is_key_down: false, @@ -349,7 +363,12 @@ extern "C" fn reset_cursor_rects(this: &Object, _sel: Sel) { let state = &mut *(state_ptr as *mut ViewState); let bounds: NSRect = msg_send![this, bounds]; - let cursor = state.cursor.lock().unwrap().load(); + let cursor_state = state.cursor_state.lock().unwrap(); + let cursor = if cursor_state.visible { + cursor_state.cursor.load() + } else { + util::invisible_cursor() + }; let _: () = msg_send![this, addCursorRect:bounds cursor:cursor diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index ee95f17a..ff66f82a 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -20,6 +20,7 @@ use crate::{ ffi, monitor::{self, MonitorHandle, VideoMode}, util::{self, IdRef}, + view::CursorState, view::{self, new_view}, window_delegate::new_delegate, OsError, @@ -90,8 +91,8 @@ fn create_app(activation_policy: ActivationPolicy) -> Option { unsafe fn create_view( ns_window: id, pl_attribs: &PlatformSpecificWindowBuilderAttributes, -) -> Option<(IdRef, Weak>)> { - let (ns_view, cursor) = new_view(ns_window); +) -> Option<(IdRef, Weak>)> { + let (ns_view, cursor_state) = new_view(ns_window); ns_view.non_nil().map(|ns_view| { if !pl_attribs.disallow_hidpi { ns_view.setWantsBestResolutionOpenGLSurface_(YES); @@ -108,7 +109,7 @@ unsafe fn create_view( ns_window.setContentView_(*ns_view); ns_window.makeFirstResponder_(*ns_view); - (ns_view, cursor) + (ns_view, cursor_state) }) } @@ -290,8 +291,7 @@ pub struct UnownedWindow { input_context: IdRef, // never changes pub shared_state: Arc>, decorations: AtomicBool, - cursor: Weak>, - cursor_visible: AtomicBool, + cursor_state: Weak>, } unsafe impl Send for UnownedWindow {} @@ -320,7 +320,7 @@ impl UnownedWindow { os_error!(OsError::CreationError("Couldn't create `NSWindow`")) })?; - let (ns_view, cursor) = + let (ns_view, cursor_state) = unsafe { create_view(*ns_window, &pl_attribs) }.ok_or_else(|| { unsafe { pool.drain() }; os_error!(OsError::CreationError("Couldn't create `NSView`")) @@ -368,8 +368,7 @@ impl UnownedWindow { input_context, shared_state: Arc::new(Mutex::new(win_attribs.into())), decorations: AtomicBool::new(decorations), - cursor, - cursor_visible: AtomicBool::new(true), + cursor_state, }); let delegate = new_delegate(&window, fullscreen.is_some()); @@ -516,8 +515,8 @@ impl UnownedWindow { pub fn set_cursor_icon(&self, cursor: CursorIcon) { let cursor = util::Cursor::from(cursor); - if let Some(cursor_access) = self.cursor.upgrade() { - *cursor_access.lock().unwrap() = cursor; + if let Some(cursor_access) = self.cursor_state.upgrade() { + cursor_access.lock().unwrap().cursor = cursor; } unsafe { let _: () = msg_send![*self.ns_window, @@ -535,16 +534,17 @@ impl UnownedWindow { #[inline] pub fn set_cursor_visible(&self, visible: bool) { - let cursor_class = class!(NSCursor); - // macOS uses a "hide counter" like Windows does, so we avoid incrementing it more than once. - // (otherwise, `hide_cursor(false)` would need to be called n times!) - if visible != self.cursor_visible.load(Ordering::Acquire) { - if visible { - let _: () = unsafe { msg_send![cursor_class, unhide] }; - } else { - let _: () = unsafe { msg_send![cursor_class, hide] }; + if let Some(cursor_access) = self.cursor_state.upgrade() { + let mut cursor_state = cursor_access.lock().unwrap(); + if visible != cursor_state.visible { + cursor_state.visible = visible; + drop(cursor_state); + unsafe { + let _: () = msg_send![*self.ns_window, + invalidateCursorRectsForView:*self.ns_view + ]; + } } - self.cursor_visible.store(visible, Ordering::Release); } }