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:
parent
dd768fe655
commit
7367b8be6c
4 changed files with 82 additions and 26 deletions
|
|
@ -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`.
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue