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`
This commit is contained in:
hatoo 2020-01-03 09:34:14 +09:00 committed by Bogaevsky
parent dd768fe655
commit 7367b8be6c
4 changed files with 82 additions and 26 deletions

View file

@ -1,5 +1,6 @@
# Unreleased # 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 `CursorEntered` and `CursorLeft` events fired at old window size.
- On macOS, fix error when `set_fullscreen` is called during fullscreen transition. - On macOS, fix error when `set_fullscreen` is called during fullscreen transition.
- On all platforms except mobile and WASM, implement `Window::set_minimized`. - On all platforms except mobile and WASM, implement `Window::set_minimized`.

View file

@ -3,7 +3,8 @@ use cocoa::{
base::{id, nil}, base::{id, nil},
foundation::{NSDictionary, NSPoint, NSString}, foundation::{NSDictionary, NSPoint, NSString},
}; };
use objc::runtime::Sel; use objc::{runtime::Sel, runtime::NO};
use std::cell::RefCell;
use crate::window::CursorIcon; use crate::window::CursorIcon;
@ -126,3 +127,38 @@ pub unsafe fn load_webkit_cursor(cursor_name: &str) -> id {
hotSpot:point 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<id> = 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()
})
}

View file

@ -35,9 +35,23 @@ use crate::{
window::WindowId, 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 { struct ViewState {
ns_window: id, ns_window: id,
pub cursor: Arc<Mutex<util::Cursor>>, pub cursor_state: Arc<Mutex<CursorState>>,
ime_spot: Option<(f64, f64)>, ime_spot: Option<(f64, f64)>,
raw_characters: Option<String>, raw_characters: Option<String>,
is_key_down: bool, is_key_down: bool,
@ -45,12 +59,12 @@ struct ViewState {
tracking_rect: Option<NSInteger>, tracking_rect: Option<NSInteger>,
} }
pub fn new_view(ns_window: id) -> (IdRef, Weak<Mutex<util::Cursor>>) { pub fn new_view(ns_window: id) -> (IdRef, Weak<Mutex<CursorState>>) {
let cursor = Default::default(); let cursor_state = Default::default();
let cursor_access = Arc::downgrade(&cursor); let cursor_access = Arc::downgrade(&cursor_state);
let state = ViewState { let state = ViewState {
ns_window, ns_window,
cursor, cursor_state,
ime_spot: None, ime_spot: None,
raw_characters: None, raw_characters: None,
is_key_down: false, 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 state = &mut *(state_ptr as *mut ViewState);
let bounds: NSRect = msg_send![this, bounds]; 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, let _: () = msg_send![this,
addCursorRect:bounds addCursorRect:bounds
cursor:cursor cursor:cursor

View file

@ -20,6 +20,7 @@ use crate::{
ffi, ffi,
monitor::{self, MonitorHandle, VideoMode}, monitor::{self, MonitorHandle, VideoMode},
util::{self, IdRef}, util::{self, IdRef},
view::CursorState,
view::{self, new_view}, view::{self, new_view},
window_delegate::new_delegate, window_delegate::new_delegate,
OsError, OsError,
@ -90,8 +91,8 @@ fn create_app(activation_policy: ActivationPolicy) -> Option<id> {
unsafe fn create_view( unsafe fn create_view(
ns_window: id, ns_window: id,
pl_attribs: &PlatformSpecificWindowBuilderAttributes, pl_attribs: &PlatformSpecificWindowBuilderAttributes,
) -> Option<(IdRef, Weak<Mutex<util::Cursor>>)> { ) -> Option<(IdRef, Weak<Mutex<CursorState>>)> {
let (ns_view, cursor) = new_view(ns_window); let (ns_view, cursor_state) = new_view(ns_window);
ns_view.non_nil().map(|ns_view| { ns_view.non_nil().map(|ns_view| {
if !pl_attribs.disallow_hidpi { if !pl_attribs.disallow_hidpi {
ns_view.setWantsBestResolutionOpenGLSurface_(YES); ns_view.setWantsBestResolutionOpenGLSurface_(YES);
@ -108,7 +109,7 @@ unsafe fn create_view(
ns_window.setContentView_(*ns_view); ns_window.setContentView_(*ns_view);
ns_window.makeFirstResponder_(*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 input_context: IdRef, // never changes
pub shared_state: Arc<Mutex<SharedState>>, pub shared_state: Arc<Mutex<SharedState>>,
decorations: AtomicBool, decorations: AtomicBool,
cursor: Weak<Mutex<util::Cursor>>, cursor_state: Weak<Mutex<CursorState>>,
cursor_visible: AtomicBool,
} }
unsafe impl Send for UnownedWindow {} unsafe impl Send for UnownedWindow {}
@ -320,7 +320,7 @@ impl UnownedWindow {
os_error!(OsError::CreationError("Couldn't create `NSWindow`")) 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 { create_view(*ns_window, &pl_attribs) }.ok_or_else(|| {
unsafe { pool.drain() }; unsafe { pool.drain() };
os_error!(OsError::CreationError("Couldn't create `NSView`")) os_error!(OsError::CreationError("Couldn't create `NSView`"))
@ -368,8 +368,7 @@ impl UnownedWindow {
input_context, input_context,
shared_state: Arc::new(Mutex::new(win_attribs.into())), shared_state: Arc::new(Mutex::new(win_attribs.into())),
decorations: AtomicBool::new(decorations), decorations: AtomicBool::new(decorations),
cursor, cursor_state,
cursor_visible: AtomicBool::new(true),
}); });
let delegate = new_delegate(&window, fullscreen.is_some()); let delegate = new_delegate(&window, fullscreen.is_some());
@ -516,8 +515,8 @@ impl UnownedWindow {
pub fn set_cursor_icon(&self, cursor: CursorIcon) { pub fn set_cursor_icon(&self, cursor: CursorIcon) {
let cursor = util::Cursor::from(cursor); let cursor = util::Cursor::from(cursor);
if let Some(cursor_access) = self.cursor.upgrade() { if let Some(cursor_access) = self.cursor_state.upgrade() {
*cursor_access.lock().unwrap() = cursor; cursor_access.lock().unwrap().cursor = cursor;
} }
unsafe { unsafe {
let _: () = msg_send![*self.ns_window, let _: () = msg_send![*self.ns_window,
@ -535,16 +534,17 @@ impl UnownedWindow {
#[inline] #[inline]
pub fn set_cursor_visible(&self, visible: bool) { pub fn set_cursor_visible(&self, visible: bool) {
let cursor_class = class!(NSCursor); if let Some(cursor_access) = self.cursor_state.upgrade() {
// macOS uses a "hide counter" like Windows does, so we avoid incrementing it more than once. let mut cursor_state = cursor_access.lock().unwrap();
// (otherwise, `hide_cursor(false)` would need to be called n times!) if visible != cursor_state.visible {
if visible != self.cursor_visible.load(Ordering::Acquire) { cursor_state.visible = visible;
if visible { drop(cursor_state);
let _: () = unsafe { msg_send![cursor_class, unhide] }; unsafe {
} else { let _: () = msg_send![*self.ns_window,
let _: () = unsafe { msg_send![cursor_class, hide] }; invalidateCursorRectsForView:*self.ns_view
];
}
} }
self.cursor_visible.store(visible, Ordering::Release);
} }
} }