WIP - adds platform APIs for checking the top most window

Mikayla Maki created

Change summary

crates/gpui/src/app.rs                        |  7 ++
crates/gpui/src/platform.rs                   |  3 
crates/gpui/src/platform/mac/platform.rs      | 15 +++++
crates/gpui/src/platform/mac/status_item.rs   |  4 +
crates/gpui/src/platform/mac/window.rs        | 30 +++++++++++
crates/gpui/src/platform/test.rs              |  6 +
crates/gpui/src/presenter.rs                  |  9 +++
crates/gpui/src/presenter/event_dispatcher.rs |  1 
crates/workspace/src/workspace.rs             | 56 ++++++++++++++++++++
9 files changed, 126 insertions(+), 5 deletions(-)

Detailed changes

crates/gpui/src/app.rs 🔗

@@ -21,6 +21,7 @@ use std::{
 use anyhow::{anyhow, Context, Result};
 use lazy_static::lazy_static;
 use parking_lot::Mutex;
+use pathfinder_geometry::vector::Vector2F;
 use postage::oneshot;
 use smallvec::SmallVec;
 use smol::prelude::*;
@@ -865,6 +866,12 @@ impl MutableAppContext {
         }
     }
 
+    pub fn screen_position(&self, window_id: usize, view_position: &Vector2F) -> Option<Vector2F> {
+        self.presenters_and_platform_windows
+            .get(&window_id)
+            .map(|(_, window)| window.screen_position(view_position))
+    }
+
     pub fn window_ids(&self) -> impl Iterator<Item = usize> + '_ {
         self.cx.windows.keys().cloned()
     }

crates/gpui/src/platform.rs 🔗

@@ -64,7 +64,7 @@ pub trait Platform: Send + Sync {
     fn read_credentials(&self, url: &str) -> Result<Option<(String, Vec<u8>)>>;
     fn delete_credentials(&self, url: &str) -> Result<()>;
 
-    fn set_cursor_style(&self, style: CursorStyle);
+    fn set_cursor_style(&self, style: CursorStyle, window_id: usize, screen_position: &Vector2F);
     fn should_auto_hide_scrollbars(&self) -> bool;
 
     fn local_timezone(&self) -> UtcOffset;
@@ -145,6 +145,7 @@ pub trait Window {
     fn present_scene(&mut self, scene: Scene);
     fn appearance(&self) -> Appearance;
     fn on_appearance_changed(&mut self, callback: Box<dyn FnMut()>);
+    fn screen_position(&self, view_position: &Vector2F) -> Vector2F;
 }
 
 #[derive(Debug)]

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

@@ -37,6 +37,7 @@ use objc::{
     runtime::{Class, Object, Sel},
     sel, sel_impl,
 };
+use pathfinder_geometry::vector::Vector2F;
 use postage::oneshot;
 use ptr::null_mut;
 use std::{
@@ -695,7 +696,19 @@ impl platform::Platform for MacPlatform {
         Ok(())
     }
 
-    fn set_cursor_style(&self, style: CursorStyle) {
+    fn set_cursor_style(&self, style: CursorStyle, window_id: usize, screen_position: &Vector2F) {
+        let top_most = Window::window_id_under(screen_position);
+        if top_most.is_none() {
+            return;
+        }
+        if top_most.unwrap() != window_id {
+            return;
+        }
+
+        dbg!(top_most.unwrap(), window_id);
+
+        dbg!(style);
+
         unsafe {
             let cursor: id = match style {
                 CursorStyle::Arrow => msg_send![class!(NSCursor), arrowCursor],

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

@@ -258,6 +258,10 @@ impl platform::Window for StatusItem {
             crate::Appearance::from_native(appearance)
         }
     }
+
+    fn screen_position(&self, _view_position: &Vector2F) -> Vector2F {
+        unimplemented!()
+    }
 }
 
 impl StatusItemState {

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

@@ -559,6 +559,26 @@ impl Window {
             }
         }
     }
+
+    pub fn window_id_under(screen_position: &Vector2F) -> Option<usize> {
+        unsafe {
+            let app = NSApplication::sharedApplication(nil);
+
+            let point = NSPoint::new(screen_position.x() as f64, screen_position.y() as f64);
+            let window_number: NSInteger = msg_send![class!(NSWindow), windowNumberAtPoint:point belowWindowWithWindowNumber:0 as NSInteger];
+            // For some reason this API doesn't work when our two windows are on top of each other
+            let top_most_window: id = msg_send![app, windowWithWindowNumber: window_number];
+            dbg!(top_most_window);
+            let is_panel: BOOL = msg_send![top_most_window, isKindOfClass: PANEL_CLASS];
+            let is_window: BOOL = msg_send![top_most_window, isKindOfClass: WINDOW_CLASS];
+            if is_panel | is_window {
+                let id = get_window_state(&*top_most_window).borrow().id;
+                Some(id)
+            } else {
+                None
+            }
+        }
+    }
 }
 
 impl Drop for Window {
@@ -755,6 +775,16 @@ impl platform::Window for Window {
     fn on_appearance_changed(&mut self, callback: Box<dyn FnMut()>) {
         self.0.borrow_mut().appearance_changed_callback = Some(callback);
     }
+
+    fn screen_position(&self, view_position: &Vector2F) -> Vector2F {
+        let self_borrow = self.0.borrow_mut();
+        unsafe {
+            let point = NSPoint::new(view_position.x() as f64, view_position.y() as f64);
+            let screen_point: NSPoint =
+                msg_send![self_borrow.native_window, convertPointToScreen: point];
+            vec2f(screen_point.x as f32, screen_point.y as f32)
+        }
+    }
 }
 
 impl WindowState {

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

@@ -178,7 +178,7 @@ impl super::Platform for Platform {
         Ok(())
     }
 
-    fn set_cursor_style(&self, style: CursorStyle) {
+    fn set_cursor_style(&self, style: CursorStyle, _window_id: usize, _position: &Vector2F) {
         *self.cursor.lock() = style;
     }
 
@@ -332,6 +332,10 @@ impl super::Window for Window {
     }
 
     fn on_appearance_changed(&mut self, _: Box<dyn FnMut()>) {}
+
+    fn screen_position(&self, view_position: &Vector2F) -> Vector2F {
+        view_position.clone()
+    }
 }
 
 pub fn platform() -> Platform {

crates/gpui/src/presenter.rs 🔗

@@ -316,7 +316,14 @@ impl Presenter {
                         break;
                     }
                 }
-                cx.platform().set_cursor_style(style_to_assign);
+
+                if let Some(screen_position) = cx.screen_position(self.window_id, position) {
+                    cx.platform().set_cursor_style(
+                        style_to_assign,
+                        self.window_id,
+                        &screen_position,
+                    );
+                }
 
                 if !event_reused {
                     if pressed_button.is_some() {

crates/workspace/src/workspace.rs 🔗

@@ -31,8 +31,9 @@ use futures::{
 };
 use gpui::{
     actions,
+    color::Color,
     elements::*,
-    geometry::vector::Vector2F,
+    geometry::vector::{vec2f, Vector2F},
     impl_actions, impl_internal_actions,
     keymap_matcher::KeymapContext,
     platform::{CursorStyle, WindowOptions},
@@ -263,6 +264,59 @@ pub fn init(app_state: Arc<AppState>, cx: &mut MutableAppContext) {
     client.add_view_request_handler(Workspace::handle_follow);
     client.add_view_message_handler(Workspace::handle_unfollow);
     client.add_view_message_handler(Workspace::handle_update_followers);
+
+    // REMEMBER TO DELETE THE SHOW NOTIF
+    cx.add_action(
+        |_workspace: &mut Workspace, _: &ShowNotif, cx: &mut ViewContext<Workspace>| {
+            struct DummyView;
+
+            impl Entity for DummyView {
+                type Event = ();
+            }
+
+            impl View for DummyView {
+                fn ui_name() -> &'static str {
+                    "DummyView"
+                }
+                fn render(&mut self, cx: &mut RenderContext<'_, Self>) -> ElementBox {
+                    MouseEventHandler::<DummyView>::new(0, cx, |state, _cx| {
+                        Empty::new()
+                            .contained()
+                            .with_background_color(if state.hovered() {
+                                Color::red()
+                            } else {
+                                Color::blue()
+                            })
+                            .constrained()
+                            .with_width(200.)
+                            .with_height(200.)
+                            .boxed()
+                    })
+                    .on_click(MouseButton::Left, |_, _| {
+                        println!("click");
+                    })
+                    .with_cursor_style(CursorStyle::ResizeUpDown)
+                    .boxed()
+                }
+            }
+
+            cx.add_window(
+                WindowOptions {
+                    bounds: gpui::WindowBounds::Fixed(gpui::geometry::rect::RectF::new(
+                        vec2f(400., 200.),
+                        vec2f(300., 200.),
+                    )),
+                    titlebar: None,
+                    center: false,
+                    focus: false,
+                    kind: gpui::WindowKind::PopUp,
+                    is_movable: true,
+                    screen: None,
+                },
+                |_cx| DummyView,
+            )
+        },
+    )
 }
 
 type ProjectItemBuilders = HashMap<