Merge pull request #2054 from zed-industries/notification-mouse-events

Mikayla Maki created

Notification mouse events

Change summary

crates/gpui/src/platform/event.rs      | 17 ++++++++++
crates/gpui/src/platform/mac/event.rs  | 12 ++++++
crates/gpui/src/platform/mac/window.rs | 46 +++++++++++++++++++++++----
crates/gpui/src/presenter.rs           | 15 +++++++++
4 files changed, 81 insertions(+), 9 deletions(-)

Detailed changes

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

@@ -178,6 +178,21 @@ impl MouseMovedEvent {
     }
 }
 
+#[derive(Clone, Copy, Debug, Default)]
+pub struct MouseExitedEvent {
+    pub position: Vector2F,
+    pub pressed_button: Option<MouseButton>,
+    pub modifiers: Modifiers,
+}
+
+impl Deref for MouseExitedEvent {
+    type Target = Modifiers;
+
+    fn deref(&self) -> &Self::Target {
+        &self.modifiers
+    }
+}
+
 #[derive(Clone, Debug)]
 pub enum Event {
     KeyDown(KeyDownEvent),
@@ -186,6 +201,7 @@ pub enum Event {
     MouseDown(MouseButtonEvent),
     MouseUp(MouseButtonEvent),
     MouseMoved(MouseMovedEvent),
+    MouseExited(MouseExitedEvent),
     ScrollWheel(ScrollWheelEvent),
 }
 
@@ -197,6 +213,7 @@ impl Event {
             Event::ModifiersChanged { .. } => None,
             Event::MouseDown(event) | Event::MouseUp(event) => Some(event.position),
             Event::MouseMoved(event) => Some(event.position),
+            Event::MouseExited(event) => Some(event.position),
             Event::ScrollWheel(event) => Some(event.position),
         }
     }

crates/gpui/src/platform/mac/event.rs 🔗

@@ -3,7 +3,7 @@ use crate::{
     keymap_matcher::Keystroke,
     platform::{Event, NavigationDirection},
     KeyDownEvent, KeyUpEvent, Modifiers, ModifiersChangedEvent, MouseButton, MouseButtonEvent,
-    MouseMovedEvent, ScrollDelta, ScrollWheelEvent, TouchPhase,
+    MouseExitedEvent, MouseMovedEvent, ScrollDelta, ScrollWheelEvent, TouchPhase,
 };
 use cocoa::{
     appkit::{NSEvent, NSEventModifierFlags, NSEventPhase, NSEventType},
@@ -221,6 +221,16 @@ impl Event {
                     modifiers: read_modifiers(native_event),
                 })
             }),
+            NSEventType::NSMouseExited => window_height.map(|window_height| {
+                Self::MouseExited(MouseExitedEvent {
+                    position: vec2f(
+                        native_event.locationInWindow().x as f32,
+                        window_height - native_event.locationInWindow().y as f32,
+                    ),
+                    pressed_button: None,
+                    modifiers: read_modifiers(native_event),
+                })
+            }),
             _ => None,
         }
     }

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

@@ -66,6 +66,14 @@ const NSNormalWindowLevel: NSInteger = 0;
 #[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;
 
 #[repr(C)]
@@ -164,6 +172,10 @@ unsafe fn build_classes() {
             sel!(mouseMoved:),
             handle_view_event as extern "C" fn(&Object, Sel, id),
         );
+        decl.add_method(
+            sel!(mouseExited:),
+            handle_view_event as extern "C" fn(&Object, Sel, id),
+        );
         decl.add_method(
             sel!(mouseDragged:),
             handle_view_event as extern "C" fn(&Object, Sel, id),
@@ -316,6 +328,7 @@ enum ImeState {
 struct WindowState {
     id: usize,
     native_window: id,
+    kind: WindowKind,
     event_callback: Option<Box<dyn FnMut(Event) -> bool>>,
     activate_callback: Option<Box<dyn FnMut(bool)>>,
     resize_callback: Option<Box<dyn FnMut()>>,
@@ -337,7 +350,6 @@ struct WindowState {
     ime_state: ImeState,
     //Retains the last IME Text
     ime_text: Option<String>,
-    accepts_first_mouse: bool,
 }
 
 struct InsertText {
@@ -422,6 +434,7 @@ impl Window {
             let window = Self(Rc::new(RefCell::new(WindowState {
                 id,
                 native_window,
+                kind: options.kind,
                 event_callback: None,
                 resize_callback: None,
                 should_close_callback: None,
@@ -437,7 +450,6 @@ impl Window {
                 scene_to_render: Default::default(),
                 renderer: Renderer::new(true, fonts),
                 last_fresh_keydown: None,
-                accepts_first_mouse: options.kind == WindowKind::PopUp,
                 traffic_light_position: options
                     .titlebar
                     .as_ref()
@@ -470,8 +482,6 @@ impl Window {
                 native_window.setTitlebarAppearsTransparent_(YES);
             }
 
-            native_window.setAcceptsMouseMovedEvents_(YES);
-
             native_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable);
             native_view.setWantsBestResolutionOpenGLSurface_(YES);
 
@@ -494,8 +504,25 @@ impl Window {
             }
 
             match options.kind {
-                WindowKind::Normal => native_window.setLevel_(NSNormalWindowLevel),
+                WindowKind::Normal => {
+                    native_window.setLevel_(NSNormalWindowLevel);
+                    native_window.setAcceptsMouseMovedEvents_(YES);
+                }
                 WindowKind::PopUp => {
+                    // 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()];
+
                     native_window.setLevel_(NSPopUpWindowLevel);
                     let _: () = msg_send![
                         native_window,
@@ -965,7 +992,6 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
 
     let window_height = window_state_borrow.content_size().y();
     let event = unsafe { Event::from_native(native_event, Some(window_height)) };
-
     if let Some(event) = event {
         match &event {
             Event::MouseMoved(
@@ -985,7 +1011,11 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
                     .detach();
             }
 
-            Event::MouseMoved(_) if !is_active => return,
+            Event::MouseMoved(_)
+                if !(is_active || window_state_borrow.kind == WindowKind::PopUp) =>
+            {
+                return
+            }
 
             Event::MouseUp(MouseButtonEvent {
                 button: MouseButton::Left,
@@ -1408,7 +1438,7 @@ extern "C" fn accepts_first_mouse(this: &Object, _: Sel, _: id) -> BOOL {
     unsafe {
         let state = get_window_state(this);
         let state_borrow = state.as_ref().borrow();
-        return state_borrow.accepts_first_mouse as BOOL;
+        return state_borrow.kind == WindowKind::PopUp;
     }
 }
 

crates/gpui/src/presenter.rs 🔗

@@ -360,6 +360,21 @@ impl Presenter {
                 self.last_mouse_moved_event = Some(event.clone());
             }
 
+            Event::MouseExited(event) => {
+                // When the platform sends a MouseExited event, synthesize
+                // a MouseMoved event whose position is outside the window's
+                // bounds so that hover and cursor state can be updated.
+                return self.dispatch_event(
+                    Event::MouseMoved(MouseMovedEvent {
+                        position: event.position,
+                        pressed_button: event.pressed_button,
+                        modifiers: event.modifiers,
+                    }),
+                    event_reused,
+                    cx,
+                );
+            }
+
             Event::ScrollWheel(e) => mouse_events.push(MouseEvent::ScrollWheel(MouseScrollWheel {
                 region: Default::default(),
                 platform_event: e.clone(),