gpui_macos: Improve mouse tracking for normal windows (#64234)
To reproduce: 1. Place Safari over part of Zed, then focus Zed. 2. Right-click Safari to open its context menu, then left-click Safari anywhere in it. 3. Move the pointer over Safari where it covers the Zed. Zed shows hover effects underneath. Use a view tracking area and disable `acceptsMouseMovedEvents` for normal macOS windows. This addresses hover effects on covered content when the pointer moves over another window. [Chromium also requests mouse movement through tracking areas](https://github.com/chromium/chromium/blob/5a43095e95ffe566d1512b9d67d534e3ae05b975/components/remote_cocoa/app_shim/bridged_content_view.mm#L436-L465). A search of its macOS window/view code found no explicit use of `acceptsMouseMovedEvents`, which [defaults to `false`](https://developer.apple.com/documentation/appkit/nswindow/acceptsmousemovedevents). Tracking stays enabled in unfocused windows while the app is active. Before: https://github.com/user-attachments/assets/f21cd3e5-04d5-4d34-b495-02f8d0f68ecf After: https://github.com/user-attachments/assets/89a8b9c9-5aa3-4075-aa91-13e200ea3613 Release Notes: - [GPUI] Fixed mouse tracking on macOS so moving the pointer over another app’s window no longer triggers hover effects in covered GPUI windows. - Fixed an issue on macOS where moving the pointer over another app could trigger hover effects in a Zed window underneath it. --------- Co-authored-by: Anthony Eid <hello@anthonyeid.me>
This commit is contained in:
1 parent
a95da07db2
commit
db7f9ceed7
2 files changed
+43
-24
No files matched your search
@@ -708,6 +708,7 @@ objc2-app-kit = { version = "0.3.2", default-features = false, features = [
|
||||
"NSResponder",
|
||||
"NSSavePanel",
|
||||
"NSScreen",
|
||||
"NSTrackingArea",
|
||||
"NSView",
|
||||
"NSWindow",
|
||||
"NSWorkspace",
|
||||
|
||||
@@ -56,8 +56,9 @@ use objc2::{
|
||||
};
|
||||
use objc2_app_kit::{
|
||||
NSAlert, NSAlertStyle, NSBeep, NSButton as Objc2NSButton, NSDraggingImageComponent,
|
||||
NSDraggingImageComponentIconKey, NSDraggingItem, NSPasteboardWriting, NSView as Objc2NSView,
|
||||
NSWindow as Objc2NSWindow, NSWindowButton as Objc2NSWindowButton, NSWorkspace,
|
||||
NSDraggingImageComponentIconKey, NSDraggingItem, NSPasteboardWriting, NSTrackingArea,
|
||||
NSTrackingAreaOptions, NSView as Objc2NSView, NSWindow as Objc2NSWindow,
|
||||
NSWindowButton as Objc2NSWindowButton, NSWorkspace,
|
||||
};
|
||||
use objc2_foundation::{NSPoint as Objc2NSPoint, NSRect as Objc2NSRect, NSURL};
|
||||
use parking_lot::Mutex;
|
||||
@@ -101,14 +102,6 @@ const NSFloatingWindowLevel: NSInteger = 3;
|
||||
#[allow(non_upper_case_globals)]
|
||||
const NSPopUpWindowLevel: NSInteger = 101;
|
||||
#[allow(non_upper_case_globals)]
|
||||
const NSTrackingMouseEnteredAndExited: NSUInteger = 0x01;
|
||||
#[allow(non_upper_case_globals)]
|
||||
const NSTrackingMouseMoved: NSUInteger = 0x02;
|
||||
#[allow(non_upper_case_globals)]
|
||||
const NSTrackingActiveAlways: NSUInteger = 0x80;
|
||||
#[allow(non_upper_case_globals)]
|
||||
const NSTrackingInVisibleRect: NSUInteger = 0x200;
|
||||
#[allow(non_upper_case_globals)]
|
||||
const NSWindowAnimationBehaviorUtilityWindow: NSInteger = 4;
|
||||
#[allow(non_upper_case_globals)]
|
||||
const NSViewLayerContentsRedrawDuringViewResize: NSInteger = 2;
|
||||
@@ -1198,15 +1191,29 @@ impl MacWindow {
|
||||
let main_window: id = msg_send![app, mainWindow];
|
||||
let mut sheet_parent = None;
|
||||
|
||||
// SAFETY: This is the live GPUIView (an NSView subclass) added to the
|
||||
// content view above, and window creation runs on the main thread.
|
||||
let tracking_view = &*native_view.cast::<Objc2NSView>();
|
||||
|
||||
match kind {
|
||||
WindowKind::Normal | WindowKind::Floating => {
|
||||
if kind == WindowKind::Floating {
|
||||
// Let the window float keep above normal windows.
|
||||
native_window.setLevel_(NSFloatingWindowLevel);
|
||||
native_window.setAcceptsMouseMovedEvents_(YES);
|
||||
} else {
|
||||
native_window.setLevel_(NSNormalWindowLevel);
|
||||
native_window.setAcceptsMouseMovedEvents_(NO);
|
||||
add_mouse_tracking_area(
|
||||
tracking_view,
|
||||
NSTrackingAreaOptions::MouseEnteredAndExited
|
||||
| NSTrackingAreaOptions::MouseMoved
|
||||
// Track while this application is active, even if another
|
||||
// window in the application has focus.
|
||||
| NSTrackingAreaOptions::ActiveInActiveApp
|
||||
| NSTrackingAreaOptions::InVisibleRect,
|
||||
);
|
||||
}
|
||||
native_window.setAcceptsMouseMovedEvents_(YES);
|
||||
|
||||
if let Some(tabbing_identifier) = tabbing_identifier {
|
||||
let tabbing_id = ns_string(tabbing_identifier.as_str());
|
||||
@@ -1218,19 +1225,15 @@ impl MacWindow {
|
||||
// `AnchoredPopup` is rejected in `MacPlatform::open_window`, grouped here only
|
||||
// for exhaustiveness.
|
||||
WindowKind::PopUp | WindowKind::AnchoredPopup(_) => {
|
||||
// Use a tracking area to allow receiving MouseMoved events even when
|
||||
// the window or application aren't active, which is often the case
|
||||
// e.g. for notification windows.
|
||||
let tracking_area: id = msg_send![class!(NSTrackingArea), alloc];
|
||||
let _: () = msg_send![
|
||||
tracking_area,
|
||||
initWithRect: NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.))
|
||||
options: NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways | NSTrackingInVisibleRect
|
||||
owner: native_view
|
||||
userInfo: nil
|
||||
];
|
||||
let _: () =
|
||||
msg_send![native_view, addTrackingArea: tracking_area.autorelease()];
|
||||
add_mouse_tracking_area(
|
||||
tracking_view,
|
||||
NSTrackingAreaOptions::MouseEnteredAndExited
|
||||
| NSTrackingAreaOptions::MouseMoved
|
||||
// Track even when another application is active, e.g. so
|
||||
// notification windows can respond to hover.
|
||||
| NSTrackingAreaOptions::ActiveAlways
|
||||
| NSTrackingAreaOptions::InVisibleRect,
|
||||
);
|
||||
|
||||
native_window.setLevel_(NSPopUpWindowLevel);
|
||||
let _: () = msg_send![
|
||||
@@ -2480,6 +2483,21 @@ extern "C" fn dealloc_view(this: &Object, _: Sel) {
|
||||
}
|
||||
}
|
||||
|
||||
fn add_mouse_tracking_area(native_view: &Objc2NSView, options: NSTrackingAreaOptions) {
|
||||
// SAFETY: NSView provides the tracking-event callbacks, and the owner is
|
||||
// the same view that retains the tracking area. No user info is supplied.
|
||||
let tracking_area = unsafe {
|
||||
NSTrackingArea::initWithRect_options_owner_userInfo(
|
||||
NSTrackingArea::alloc(),
|
||||
Objc2NSRect::ZERO,
|
||||
options,
|
||||
Some(native_view),
|
||||
None,
|
||||
)
|
||||
};
|
||||
native_view.addTrackingArea(&tracking_area);
|
||||
}
|
||||
|
||||
extern "C" fn reset_cursor_rects(this: &Object, _: Sel) {
|
||||
// SAFETY: AppKit invokes cursor-rect updates on the main thread for GPUIView instances,
|
||||
// whose WINDOW_STATE_IVAR is initialized when the view is created. The cursor registered
|
||||
|
||||
Reference in new issue
Block a user