Checkpoint

Nathan Sobo created

Change summary

assets/keymaps/default.json                   |   2 
crates/gpui3/src/geometry.rs                  |  90 +++++++++
crates/gpui3/src/gpui3.rs                     |   1 
crates/gpui3/src/platform.rs                  |  47 ++--
crates/gpui3/src/platform/mac.rs              |  42 +++-
crates/gpui3/src/platform/mac/display.rs      | 126 ++++++++++++++
crates/gpui3/src/platform/mac/display_link.rs | 182 +++++++++++++++++++++
crates/gpui3/src/platform/mac/platform.rs     |  70 ++++----
crates/gpui3/src/platform/mac/screen.rs       | 156 ------------------
crates/gpui3/src/platform/mac/window.rs       |  75 +++++--
crates/gpui3/src/platform/test.rs             |   6 
crates/storybook2/src/storybook2.rs           |   2 
12 files changed, 537 insertions(+), 262 deletions(-)

Detailed changes

assets/keymaps/default.json 🔗

@@ -533,7 +533,7 @@
       // TODO: Move this to a dock open action
       "cmd-shift-c": "collab_panel::ToggleFocus",
       "cmd-alt-i": "zed::DebugElements",
-      "ctrl-:": "editor::ToggleInlayHints",
+      "ctrl-:": "editor::ToggleInlayHints"
     }
   },
   {

crates/gpui3/src/geometry.rs 🔗

@@ -199,11 +199,20 @@ impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Size<T> {
 
 impl<T: Eq + Debug + Clone> Eq for Size<T> {}
 
-impl From<Size<Option<Pixels>>> for Size<Option<f32>> {
-    fn from(size: Size<Option<Pixels>>) -> Self {
+// impl From<Size<Option<Pixels>>> for Size<Option<f32>> {
+//     fn from(size: Size<Option<Pixels>>) -> Self {
+//         Size {
+//             width: size.width.map(|p| p.0 as f32),
+//             height: size.height.map(|p| p.0 as f32),
+//         }
+//     }
+// }
+
+impl From<Size<Pixels>> for Size<GlobalPixels> {
+    fn from(size: Size<Pixels>) -> Self {
         Size {
-            width: size.width.map(|p| p.0 as f32),
-            height: size.height.map(|p| p.0 as f32),
+            width: GlobalPixels(size.width.0),
+            height: GlobalPixels(size.height.0),
         }
     }
 }
@@ -257,6 +266,18 @@ impl<T: Clone + Debug + Sub<Output = T>> Bounds<T> {
     }
 }
 
+impl<T: Clone + Debug + PartialOrd + Add<T, Output = T> + Sub<Output = T>> Bounds<T> {
+    pub fn intersects(&self, other: &Bounds<T>) -> bool {
+        let my_lower_right = self.lower_right();
+        let their_lower_right = other.lower_right();
+
+        self.origin.x < their_lower_right.x
+            && my_lower_right.x > other.origin.x
+            && self.origin.y < their_lower_right.y
+            && my_lower_right.y > other.origin.y
+    }
+}
+
 impl<T: Clone + Debug + PartialOrd + Add<T, Output = T> + Sub<Output = T>> Bounds<T> {
     pub fn intersect(&self, other: &Self) -> Self {
         let upper_left = self.origin.max(&other.origin);
@@ -698,6 +719,28 @@ impl From<DevicePixels> for ScaledPixels {
     }
 }
 
+#[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd)]
+#[repr(transparent)]
+pub struct GlobalPixels(pub(crate) f32);
+
+impl Debug for GlobalPixels {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{} px (global coordinate space)", self.0)
+    }
+}
+
+impl From<GlobalPixels> for f64 {
+    fn from(global_pixels: GlobalPixels) -> Self {
+        global_pixels.0 as f64
+    }
+}
+
+impl From<f64> for GlobalPixels {
+    fn from(global_pixels: f64) -> Self {
+        GlobalPixels(global_pixels as f32)
+    }
+}
+
 #[derive(Clone, Copy, Default, Add, Sub, Mul, Div)]
 pub struct Rems(f32);
 
@@ -964,3 +1007,42 @@ impl<T: IsZero + Debug + Clone> IsZero for Corners<T> {
             && self.bottom_left.is_zero()
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_bounds_intersects() {
+        let bounds1 = Bounds {
+            origin: Point { x: 0.0, y: 0.0 },
+            size: Size {
+                width: 5.0,
+                height: 5.0,
+            },
+        };
+        let bounds2 = Bounds {
+            origin: Point { x: 4.0, y: 4.0 },
+            size: Size {
+                width: 5.0,
+                height: 5.0,
+            },
+        };
+        let bounds3 = Bounds {
+            origin: Point { x: 10.0, y: 10.0 },
+            size: Size {
+                width: 5.0,
+                height: 5.0,
+            },
+        };
+
+        // Test Case 1: Intersecting bounds
+        assert_eq!(bounds1.intersects(&bounds2), true);
+
+        // Test Case 2: Non-Intersecting bounds
+        assert_eq!(bounds1.intersects(&bounds3), false);
+
+        // Test Case 3: Bounds intersecting with themselves
+        assert_eq!(bounds1.intersects(&bounds1), true);
+    }
+}

crates/gpui3/src/gpui3.rs 🔗

@@ -27,6 +27,7 @@ pub use elements::*;
 pub use executor::*;
 pub use geometry::*;
 pub use gpui3_macros::*;
+pub use image_cache::*;
 pub use platform::*;
 pub use refineable::*;
 pub use scene::*;

crates/gpui3/src/platform.rs 🔗

@@ -5,10 +5,10 @@ mod mac;
 #[cfg(any(test, feature = "test"))]
 mod test;
 
-use crate::image_cache::RenderImageParams;
 use crate::{
-    AnyWindowHandle, Bounds, DevicePixels, Executor, Font, FontId, FontMetrics, GlyphId, Pixels,
-    Point, RenderGlyphParams, RenderSvgParams, Result, Scene, ShapedLine, SharedString, Size,
+    AnyWindowHandle, Bounds, DevicePixels, Executor, Font, FontId, FontMetrics, GlobalPixels,
+    GlyphId, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene,
+    ShapedLine, SharedString, Size,
 };
 use anyhow::anyhow;
 use async_task::Runnable;
@@ -16,7 +16,6 @@ use futures::channel::oneshot;
 use seahash::SeaHasher;
 use serde::{Deserialize, Serialize};
 use std::borrow::Cow;
-use std::ffi::c_void;
 use std::hash::{Hash, Hasher};
 use std::{
     any::Any,
@@ -27,7 +26,6 @@ use std::{
     str::FromStr,
     sync::Arc,
 };
-use uuid::Uuid;
 
 pub use events::*;
 pub use keystroke::*;
@@ -54,8 +52,8 @@ pub trait Platform: 'static {
     fn hide_other_apps(&self);
     fn unhide_other_apps(&self);
 
-    fn screens(&self) -> Vec<Rc<dyn PlatformScreen>>;
-    fn screen_by_id(&self, id: ScreenId) -> Option<Rc<dyn PlatformScreen>>;
+    fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
+    fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
     fn main_window(&self) -> Option<AnyWindowHandle>;
     fn open_window(
         &self,
@@ -97,23 +95,23 @@ pub trait Platform: 'static {
     fn delete_credentials(&self, url: &str) -> Result<()>;
 }
 
-pub trait PlatformScreen: Debug {
-    fn id(&self) -> Option<ScreenId>;
-    fn handle(&self) -> PlatformScreenHandle;
+pub trait PlatformDisplay: Debug {
+    fn id(&self) -> DisplayId;
     fn as_any(&self) -> &dyn Any;
-    fn bounds(&self) -> Bounds<Pixels>;
-    fn content_bounds(&self) -> Bounds<Pixels>;
+    fn bounds(&self) -> Bounds<GlobalPixels>;
+    fn link(&self) -> Box<dyn PlatformDisplayLink>;
 }
 
-pub struct PlatformScreenHandle(pub *mut c_void);
+#[derive(PartialEq, Eq, Hash, Copy, Clone)]
+pub struct DisplayId(pub(crate) u32);
 
-impl Debug for PlatformScreenHandle {
+impl Debug for DisplayId {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "PlatformScreenHandle({:p})", self.0)
+        write!(f, "DisplayId({})", self.0)
     }
 }
 
-unsafe impl Send for PlatformScreenHandle {}
+unsafe impl Send for DisplayId {}
 
 pub trait PlatformWindow {
     fn bounds(&self) -> WindowBounds;
@@ -121,7 +119,7 @@ pub trait PlatformWindow {
     fn scale_factor(&self) -> f32;
     fn titlebar_height(&self) -> Pixels;
     fn appearance(&self) -> WindowAppearance;
-    fn screen(&self) -> Rc<dyn PlatformScreen>;
+    fn display(&self) -> Rc<dyn PlatformDisplay>;
     fn mouse_position(&self) -> Point<Pixels>;
     fn as_any_mut(&mut self) -> &mut dyn Any;
     fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>);
@@ -158,6 +156,12 @@ pub trait PlatformDispatcher: Send + Sync {
     fn dispatch_on_main_thread(&self, task: Runnable);
 }
 
+pub trait PlatformDisplayLink {
+    fn set_output_callback(&mut self, callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>);
+    fn start(&mut self);
+    fn stop(&mut self);
+}
+
 pub trait PlatformTextSystem: Send + Sync {
     fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()>;
     fn all_font_families(&self) -> Vec<String>;
@@ -266,9 +270,6 @@ pub trait PlatformInputHandler {
     fn bounds_for_range(&self, range_utf16: Range<usize>) -> Option<Bounds<f32>>;
 }
 
-#[derive(Copy, Clone, Debug, PartialEq)]
-pub struct ScreenId(pub(crate) Uuid);
-
 #[derive(Debug)]
 pub struct WindowOptions {
     pub bounds: WindowBounds,
@@ -278,7 +279,7 @@ pub struct WindowOptions {
     pub show: bool,
     pub kind: WindowKind,
     pub is_movable: bool,
-    pub screen: Option<PlatformScreenHandle>,
+    pub display_id: Option<DisplayId>,
 }
 
 impl Default for WindowOptions {
@@ -295,7 +296,7 @@ impl Default for WindowOptions {
             show: true,
             kind: WindowKind::Normal,
             is_movable: true,
-            screen: None,
+            display_id: None,
         }
     }
 }
@@ -332,7 +333,7 @@ pub enum WindowBounds {
     Fullscreen,
     #[default]
     Maximized,
-    Fixed(Bounds<Pixels>),
+    Fixed(Bounds<GlobalPixels>),
 }
 
 #[derive(Copy, Clone, Debug)]

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

@@ -1,17 +1,18 @@
 ///! Macos screen have a y axis that goings up from the bottom of the screen and
 ///! an origin at the bottom left of the main display.
 mod dispatcher;
+mod display;
+mod display_link;
 mod events;
 mod metal_atlas;
 mod metal_renderer;
 mod open_type;
 mod platform;
-mod screen;
 mod text_system;
 mod window;
 mod window_appearence;
 
-use crate::{px, size, Pixels, Size};
+use crate::{px, size, GlobalPixels, Pixels, Size};
 use anyhow::anyhow;
 use cocoa::{
     base::{id, nil},
@@ -31,9 +32,10 @@ use std::{
 };
 
 pub use dispatcher::*;
+pub use display::*;
+pub use display_link::*;
 pub use metal_atlas::*;
 pub use platform::*;
-pub use screen::*;
 pub use text_system::*;
 pub use window::*;
 
@@ -119,23 +121,33 @@ pub trait NSRectExt {
     fn intersects(&self, other: Self) -> bool;
 }
 
-impl NSRectExt for NSRect {
-    fn size(&self) -> Size<Pixels> {
-        size(px(self.size.width as f32), px(self.size.height as f32))
+impl From<NSRect> for Size<Pixels> {
+    fn from(rect: NSRect) -> Self {
+        let NSSize { width, height } = rect.size;
+        size(width.into(), height.into())
     }
+}
 
-    fn intersects(&self, other: Self) -> bool {
-        self.size.width > 0.
-            && self.size.height > 0.
-            && other.size.width > 0.
-            && other.size.height > 0.
-            && self.origin.x <= other.origin.x + other.size.width
-            && self.origin.x + self.size.width >= other.origin.x
-            && self.origin.y <= other.origin.y + other.size.height
-            && self.origin.y + self.size.height >= other.origin.y
+impl From<NSRect> for Size<GlobalPixels> {
+    fn from(rect: NSRect) -> Self {
+        let NSSize { width, height } = rect.size;
+        size(width.into(), height.into())
     }
 }
 
+// impl NSRectExt for NSRect {
+//     fn intersects(&self, other: Self) -> bool {
+//         self.size.width > 0.
+//             && self.size.height > 0.
+//             && other.size.width > 0.
+//             && other.size.height > 0.
+//             && self.origin.x <= other.origin.x + other.size.width
+//             && self.origin.x + self.size.width >= other.origin.x
+//             && self.origin.y <= other.origin.y + other.size.height
+//             && self.origin.y + self.size.height >= other.origin.y
+//     }
+// }
+
 // todo!
 #[allow(unused)]
 unsafe fn ns_url_to_path(url: id) -> crate::Result<PathBuf> {

crates/gpui3/src/platform/mac/display.rs 🔗

@@ -0,0 +1,126 @@
+use crate::{point, size, Bounds, DisplayId, GlobalPixels, MacDisplayLink, PlatformDisplay};
+
+use core_graphics::{
+    display::{CGDirectDisplayID, CGGetActiveDisplayList},
+    geometry::{CGPoint, CGRect, CGSize},
+};
+use std::any::Any;
+
+#[derive(Debug)]
+pub struct MacDisplay(pub(crate) CGDirectDisplayID);
+
+unsafe impl Send for MacDisplay {}
+
+impl MacDisplay {
+    /// Get the screen with the given UUID.
+    pub fn find_by_id(id: DisplayId) -> Option<Self> {
+        Self::all().find(|screen| screen.id() == id)
+    }
+
+    /// Get the primary screen - the one with the menu bar, and whose bottom left
+    /// corner is at the origin of the AppKit coordinate system.
+    pub fn primary() -> Self {
+        Self::all().next().unwrap()
+    }
+
+    pub fn all() -> impl Iterator<Item = Self> {
+        unsafe {
+            let mut display_count: u32 = 0;
+            let result = CGGetActiveDisplayList(0, std::ptr::null_mut(), &mut display_count);
+
+            if result == 0 {
+                let mut displays = Vec::with_capacity(display_count as usize);
+                CGGetActiveDisplayList(display_count, displays.as_mut_ptr(), &mut display_count);
+                displays.set_len(display_count as usize);
+
+                displays.into_iter().map(|display| MacDisplay(display))
+            } else {
+                panic!("Failed to get active display list");
+            }
+        }
+    }
+}
+
+/// Convert the given rectangle from CoreGraphics' native coordinate space to GPUI's coordinate space.
+///
+/// CoreGraphics' coordinate space has its origin at the bottom left of the primary screen,
+/// with the Y axis pointing upwards.
+///
+/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
+/// screen, with the Y axis pointing downwards.
+pub(crate) fn display_bounds_from_native(rect: CGRect) -> Bounds<GlobalPixels> {
+    let primary_screen_height = MacDisplay::primary().bounds().size.height;
+    Bounds {
+        origin: point(
+            GlobalPixels(rect.origin.x as f32),
+            primary_screen_height
+                - GlobalPixels(rect.origin.y as f32)
+                - GlobalPixels(rect.size.height as f32),
+        ),
+        size: size(
+            GlobalPixels(rect.size.width as f32),
+            GlobalPixels(rect.size.height as f32),
+        ),
+    }
+}
+
+/// Convert the given rectangle from GPUI's coordinate system to CoreGraphics' native coordinate space.
+///
+/// CoreGraphics' coordinate space has its origin at the bottom left of the primary screen,
+/// with the Y axis pointing upwards.
+///
+/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
+/// screen, with the Y axis pointing downwards.
+pub(crate) fn display_bounds_to_native(bounds: Bounds<GlobalPixels>) -> CGRect {
+    let primary_screen_height = MacDisplay::primary().bounds().size.height;
+
+    CGRect::new(
+        &CGPoint::new(
+            bounds.origin.x.into(),
+            (primary_screen_height - bounds.origin.y - bounds.size.height).into(),
+        ),
+        &CGSize::new(bounds.size.width.into(), bounds.size.height.into()),
+    )
+}
+
+impl PlatformDisplay for MacDisplay {
+    fn id(&self) -> DisplayId {
+        DisplayId(self.0)
+    }
+
+    fn as_any(&self) -> &dyn Any {
+        self
+    }
+
+    fn bounds(&self) -> Bounds<GlobalPixels> {
+        unsafe {
+            use core_graphics::display::*;
+
+            let display_id = self.0;
+            // The `CGDisplayBounds` function gets the display bounds
+            // for this display. The bounds are returned as a CGRect
+            // and specify the display's location and size in
+            // pixel units, in the global coordinate space.
+            // // The global coordinate space is a coordinate system used by macOS. In this
+            // coordinate space, the origin {0, 0} represents the top-left corner of the primary
+            // display, and the positive X and Y axes extend from the origin to the right and downward,
+            // respectively, towards the bottom-right corner of the primary display. For any display
+            // connected to the system, the global coordinate space identifies the position and size
+            // of the display with respect to the primary display.
+
+            // The coordinates in this coordinate space are typically in the form of a CGRect,
+            // which represents the rectangle bounding the display in terms of pixels. The CGRect
+            // holds the origin for the rect's bottom-left corner and a CGSize, which
+            // represent width and height.
+
+            // With respect to the above `bounds` function in `PlatformDisplay` trait implementation,
+            // this coordinate space is used to fetch a display ID's CGRect and position of origin, and size.
+            let native_bounds = CGDisplayBounds(display_id);
+            display_bounds_from_native(native_bounds)
+        }
+    }
+
+    fn link(&self) -> Box<dyn crate::PlatformDisplayLink> {
+        Box::new(unsafe { MacDisplayLink::new(self.0) })
+    }
+}

crates/gpui3/src/platform/mac/display_link.rs 🔗

@@ -0,0 +1,182 @@
+use crate::PlatformDisplayLink;
+use std::ffi::c_void;
+
+pub use sys::CVTimeStamp as VideoTimestamp;
+
+pub struct MacDisplayLink {
+    sys_link: sys::DisplayLink,
+    output_callback: Option<Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>>,
+}
+
+impl MacDisplayLink {
+    pub unsafe fn new(display_id: u32) -> Self {
+        Self {
+            sys_link: sys::DisplayLink::on_display(display_id).unwrap(),
+            output_callback: None,
+        }
+    }
+}
+
+impl PlatformDisplayLink for MacDisplayLink {
+    fn set_output_callback(&mut self, callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>) {
+        unsafe {
+            self.sys_link.set_output_callback(
+                trampoline,
+                self.output_callback.as_mut().unwrap()
+                    as *mut dyn FnMut(&VideoTimestamp, &VideoTimestamp)
+                    as *mut c_void,
+            );
+        }
+        self.output_callback = Some(callback);
+    }
+
+    fn start(&mut self) {
+        unsafe { self.sys_link.start() }
+    }
+
+    fn stop(&mut self) {
+        unsafe { self.sys_link.stop() }
+    }
+}
+
+unsafe extern "C" fn trampoline(
+    _display_link_out: *mut sys::CVDisplayLink,
+    current_time: *const sys::CVTimeStamp,
+    output_time: *const sys::CVTimeStamp,
+    _flags_in: i64,
+    _flags_out: *mut i64,
+    context: *mut c_void,
+) -> i32 {
+    let output_callback = &mut (*(context as *mut MacDisplayLink)).output_callback;
+    if let Some(callback) = output_callback {
+        if let Some((current_time, output_time)) = current_time.as_ref().zip(output_time.as_ref()) {
+            // convert sys::CVTimeStamp to VideoTimestamp
+            callback(&current_time, &output_time);
+        }
+    }
+    0
+}
+
+mod sys {
+    //! Derived from display-link crate under the fololwing license:
+    //! https://github.com/BrainiumLLC/display-link/blob/master/LICENSE-MIT
+    //! Apple docs: [CVDisplayLink](https://developer.apple.com/documentation/corevideo/cvdisplaylinkoutputcallback?language=objc)
+    #![allow(dead_code)]
+
+    pub use cocoa::quartzcore::CVTimeStamp;
+    use foreign_types::{foreign_type, ForeignType};
+    use std::{
+        ffi::c_void,
+        fmt::{Debug, Formatter, Result},
+    };
+
+    #[derive(Debug)]
+    pub enum CVDisplayLink {}
+
+    foreign_type! {
+        type CType = CVDisplayLink;
+        fn drop = CVDisplayLinkRelease;
+        fn clone = CVDisplayLinkRetain;
+        pub struct DisplayLink;
+        pub struct DisplayLinkRef;
+    }
+
+    impl Debug for DisplayLink {
+        fn fmt(&self, formatter: &mut Formatter) -> Result {
+            formatter
+                .debug_tuple("DisplayLink")
+                .field(&self.as_ptr())
+                .finish()
+        }
+    }
+
+    pub type CVDisplayLinkOutputCallback = unsafe extern "C" fn(
+        display_link_out: *mut CVDisplayLink,
+        // A pointer to the current timestamp. This represents the timestamp when the callback is called.
+        current_time: *const CVTimeStamp,
+        // A pointer to the output timestamp. This represents the timestamp for when the frame will be displayed.
+        output_time: *const CVTimeStamp,
+        // Unused
+        flags_in: i64,
+        // Unused
+        flags_out: *mut i64,
+        // A pointer to app-defined data.
+        display_link_context: *mut c_void,
+    ) -> i32;
+
+    #[link(name = "CoreFoundation", kind = "framework")]
+    #[link(name = "CoreVideo", kind = "framework")]
+    #[allow(improper_ctypes)]
+    extern "C" {
+        pub fn CVDisplayLinkCreateWithActiveCGDisplays(
+            display_link_out: *mut *mut CVDisplayLink,
+        ) -> i32;
+        pub fn CVDisplayLinkCreateWithCGDisplay(
+            display_id: u32,
+            display_link_out: *mut *mut CVDisplayLink,
+        ) -> i32;
+        pub fn CVDisplayLinkSetOutputCallback(
+            display_link: &mut DisplayLinkRef,
+            callback: CVDisplayLinkOutputCallback,
+            user_info: *mut c_void,
+        ) -> i32;
+        pub fn CVDisplayLinkSetCurrentCGDisplay(
+            display_link: &mut DisplayLinkRef,
+            display_id: u32,
+        ) -> i32;
+        pub fn CVDisplayLinkStart(display_link: &mut DisplayLinkRef) -> i32;
+        pub fn CVDisplayLinkStop(display_link: &mut DisplayLinkRef) -> i32;
+        pub fn CVDisplayLinkRelease(display_link: *mut CVDisplayLink);
+        pub fn CVDisplayLinkRetain(display_link: *mut CVDisplayLink) -> *mut CVDisplayLink;
+    }
+
+    impl DisplayLink {
+        /// Apple docs: [CVDisplayLinkCreateWithActiveCGDisplays](https://developer.apple.com/documentation/corevideo/1456863-cvdisplaylinkcreatewithactivecgd?language=objc)
+        pub unsafe fn new() -> Option<Self> {
+            let mut display_link: *mut CVDisplayLink = 0 as _;
+            let code = CVDisplayLinkCreateWithActiveCGDisplays(&mut display_link);
+            if code == 0 {
+                Some(DisplayLink::from_ptr(display_link))
+            } else {
+                None
+            }
+        }
+
+        /// Apple docs: [CVDisplayLinkCreateWithCGDisplay](https://developer.apple.com/documentation/corevideo/1456981-cvdisplaylinkcreatewithcgdisplay?language=objc)
+        pub unsafe fn on_display(display_id: u32) -> Option<Self> {
+            let mut display_link: *mut CVDisplayLink = 0 as _;
+            let code = CVDisplayLinkCreateWithCGDisplay(display_id, &mut display_link);
+            if code == 0 {
+                Some(DisplayLink::from_ptr(display_link))
+            } else {
+                None
+            }
+        }
+    }
+
+    impl DisplayLinkRef {
+        /// Apple docs: [CVDisplayLinkSetOutputCallback](https://developer.apple.com/documentation/corevideo/1457096-cvdisplaylinksetoutputcallback?language=objc)
+        pub unsafe fn set_output_callback(
+            &mut self,
+            callback: CVDisplayLinkOutputCallback,
+            user_info: *mut c_void,
+        ) {
+            assert_eq!(CVDisplayLinkSetOutputCallback(self, callback, user_info), 0);
+        }
+
+        /// Apple docs: [CVDisplayLinkSetCurrentCGDisplay](https://developer.apple.com/documentation/corevideo/1456768-cvdisplaylinksetcurrentcgdisplay?language=objc)
+        pub unsafe fn set_current_display(&mut self, display_id: u32) {
+            assert_eq!(CVDisplayLinkSetCurrentCGDisplay(self, display_id), 0);
+        }
+
+        /// Apple docs: [CVDisplayLinkStart](https://developer.apple.com/documentation/corevideo/1457193-cvdisplaylinkstart?language=objc)
+        pub unsafe fn start(&mut self) {
+            assert_eq!(CVDisplayLinkStart(self), 0);
+        }
+
+        /// Apple docs: [CVDisplayLinkStop](https://developer.apple.com/documentation/corevideo/1457281-cvdisplaylinkstop?language=objc)
+        pub unsafe fn stop(&mut self) {
+            assert_eq!(CVDisplayLinkStop(self), 0);
+        }
+    }
+}

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

@@ -1,8 +1,8 @@
 use super::BoolExt;
 use crate::{
-    AnyWindowHandle, ClipboardItem, CursorStyle, Event, Executor, MacDispatcher, MacScreen,
-    MacTextSystem, MacWindow, PathPromptOptions, Platform, PlatformScreen, PlatformTextSystem,
-    PlatformWindow, Result, ScreenId, SemanticVersion, WindowOptions,
+    AnyWindowHandle, ClipboardItem, CursorStyle, DisplayId, Event, Executor, MacDispatcher,
+    MacDisplay, MacTextSystem, MacWindow, PathPromptOptions, Platform, PlatformDisplay,
+    PlatformTextSystem, PlatformWindow, Result, SemanticVersion, WindowOptions,
 };
 use anyhow::anyhow;
 use block::ConcreteBlock;
@@ -455,21 +455,21 @@ impl Platform for MacPlatform {
         }
     }
 
-    fn screens(&self) -> Vec<Rc<dyn PlatformScreen>> {
-        MacScreen::all()
+    fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
+        MacDisplay::all()
             .into_iter()
             .map(|screen| Rc::new(screen) as Rc<_>)
             .collect()
     }
 
-    fn screen_by_id(&self, id: ScreenId) -> Option<Rc<dyn PlatformScreen>> {
-        MacScreen::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
-    }
-
     // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> {
     //     Box::new(StatusItem::add(self.fonts()))
     // }
 
+    fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
+        MacDisplay::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
+    }
+
     fn main_window(&self) -> Option<AnyWindowHandle> {
         MacWindow::main_window()
     }
@@ -736,6 +736,32 @@ impl Platform for MacPlatform {
         }
     }
 
+    // fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>) {
+    //     self.0.lock().menu_command = Some(callback);
+    // }
+
+    // fn on_will_open_menu(&self, callback: Box<dyn FnMut()>) {
+    //     self.0.lock().will_open_menu = Some(callback);
+    // }
+
+    // fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
+    //     self.0.lock().validate_menu_command = Some(callback);
+    // }
+
+    // fn set_menus(&self, menus: Vec<Menu>, keystroke_matcher: &KeymapMatcher) {
+    //     unsafe {
+    //         let app: id = msg_send![APP_CLASS, sharedApplication];
+    //         let mut state = self.0.lock();
+    //         let actions = &mut state.menu_actions;
+    //         app.setMainMenu_(self.create_menu_bar(
+    //             menus,
+    //             app.delegate(),
+    //             actions,
+    //             keystroke_matcher,
+    //         ));
+    //     }
+    // }
+
     fn read_from_clipboard(&self) -> Option<ClipboardItem> {
         let state = self.0.lock();
         unsafe {
@@ -773,32 +799,6 @@ impl Platform for MacPlatform {
         }
     }
 
-    // fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>) {
-    //     self.0.lock().menu_command = Some(callback);
-    // }
-
-    // fn on_will_open_menu(&self, callback: Box<dyn FnMut()>) {
-    //     self.0.lock().will_open_menu = Some(callback);
-    // }
-
-    // fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
-    //     self.0.lock().validate_menu_command = Some(callback);
-    // }
-
-    // fn set_menus(&self, menus: Vec<Menu>, keystroke_matcher: &KeymapMatcher) {
-    //     unsafe {
-    //         let app: id = msg_send![APP_CLASS, sharedApplication];
-    //         let mut state = self.0.lock();
-    //         let actions = &mut state.menu_actions;
-    //         app.setMainMenu_(self.create_menu_bar(
-    //             menus,
-    //             app.delegate(),
-    //             actions,
-    //             keystroke_matcher,
-    //         ));
-    //     }
-    // }
-
     fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
         let url = CFString::from(url);
         let username = CFString::from(username);

crates/gpui3/src/platform/mac/screen.rs 🔗

@@ -1,156 +0,0 @@
-use super::ns_string;
-use crate::{point, px, size, Bounds, Pixels, PlatformScreen, PlatformScreenHandle, ScreenId};
-use cocoa::{
-    appkit::NSScreen,
-    base::{id, nil},
-    foundation::{NSArray, NSDictionary, NSPoint, NSRect, NSSize},
-};
-use core_foundation::{
-    number::{kCFNumberIntType, CFNumberGetValue, CFNumberRef},
-    uuid::{CFUUIDGetUUIDBytes, CFUUIDRef},
-};
-use core_graphics::display::CGDirectDisplayID;
-use objc::runtime::Object;
-use std::{any::Any, ffi::c_void};
-use uuid::Uuid;
-
-#[link(name = "ApplicationServices", kind = "framework")]
-extern "C" {
-    pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
-}
-
-#[derive(Debug)]
-pub struct MacScreen {
-    pub(crate) native_screen: id,
-}
-
-unsafe impl Send for MacScreen {}
-
-impl MacScreen {
-    pub(crate) fn from_handle(handle: PlatformScreenHandle) -> Self {
-        Self {
-            native_screen: handle.0 as *mut Object,
-        }
-    }
-
-    /// Get the screen with the given UUID.
-    pub fn find_by_id(id: ScreenId) -> Option<Self> {
-        Self::all().find(|screen| screen.id() == Some(id))
-    }
-
-    /// Get the primary screen - the one with the menu bar, and whose bottom left
-    /// corner is at the origin of the AppKit coordinate system.
-    fn primary() -> Self {
-        Self::all().next().unwrap()
-    }
-
-    pub fn all() -> impl Iterator<Item = Self> {
-        unsafe {
-            let native_screens = NSScreen::screens(nil);
-            (0..NSArray::count(native_screens)).map(move |ix| MacScreen {
-                native_screen: native_screens.objectAtIndex(ix),
-            })
-        }
-    }
-
-    /// Convert the given rectangle in screen coordinates from GPUI's
-    /// coordinate system to the AppKit coordinate system.
-    ///
-    /// In GPUI's coordinates, the origin is at the top left of the primary screen, with
-    /// the Y axis pointing downward. In the AppKit coordindate system, the origin is at the
-    /// bottom left of the primary screen, with the Y axis pointing upward.
-    pub(crate) fn screen_bounds_to_native(bounds: Bounds<Pixels>) -> NSRect {
-        let primary_screen_height =
-            px(unsafe { Self::primary().native_screen.frame().size.height } as f32);
-
-        NSRect::new(
-            NSPoint::new(
-                bounds.origin.x.into(),
-                (primary_screen_height - bounds.origin.y - bounds.size.height).into(),
-            ),
-            NSSize::new(bounds.size.width.into(), bounds.size.height.into()),
-        )
-    }
-
-    /// Convert the given rectangle in screen coordinates from the AppKit
-    /// coordinate system to GPUI's coordinate system.
-    ///
-    /// In GPUI's coordinates, the origin is at the top left of the primary screen, with
-    /// the Y axis pointing downward. In the AppKit coordindate system, the origin is at the
-    /// bottom left of the primary screen, with the Y axis pointing upward.
-    pub(crate) fn screen_bounds_from_native(rect: NSRect) -> Bounds<Pixels> {
-        let primary_screen_height = unsafe { Self::primary().native_screen.frame().size.height };
-        Bounds {
-            origin: point(
-                px(rect.origin.x as f32),
-                px((primary_screen_height - rect.origin.y - rect.size.height) as f32),
-            ),
-            size: size(px(rect.size.width as f32), px(rect.size.height as f32)),
-        }
-    }
-}
-
-impl PlatformScreen for MacScreen {
-    fn id(&self) -> Option<ScreenId> {
-        unsafe {
-            // This approach is similar to that which winit takes
-            // https://github.com/rust-windowing/winit/blob/402cbd55f932e95dbfb4e8b5e8551c49e56ff9ac/src/platform_impl/macos/monitor.rs#L99
-            let device_description = self.native_screen.deviceDescription();
-
-            let key = ns_string("NSScreenNumber");
-            let device_id_obj = device_description.objectForKey_(key);
-            if device_id_obj.is_null() {
-                // Under some circumstances, especially display re-arrangements or display locking, we seem to get a null pointer
-                // to the device id. See: https://linear.app/zed-industries/issue/Z-257/lock-screen-crash-with-multiple-monitors
-                return None;
-            }
-
-            let mut device_id: u32 = 0;
-            CFNumberGetValue(
-                device_id_obj as CFNumberRef,
-                kCFNumberIntType,
-                (&mut device_id) as *mut _ as *mut c_void,
-            );
-            let cfuuid = CGDisplayCreateUUIDFromDisplayID(device_id as CGDirectDisplayID);
-            if cfuuid.is_null() {
-                return None;
-            }
-
-            let bytes = CFUUIDGetUUIDBytes(cfuuid);
-            Some(ScreenId(Uuid::from_bytes([
-                bytes.byte0,
-                bytes.byte1,
-                bytes.byte2,
-                bytes.byte3,
-                bytes.byte4,
-                bytes.byte5,
-                bytes.byte6,
-                bytes.byte7,
-                bytes.byte8,
-                bytes.byte9,
-                bytes.byte10,
-                bytes.byte11,
-                bytes.byte12,
-                bytes.byte13,
-                bytes.byte14,
-                bytes.byte15,
-            ])))
-        }
-    }
-
-    fn handle(&self) -> PlatformScreenHandle {
-        PlatformScreenHandle(self.native_screen as *mut c_void)
-    }
-
-    fn as_any(&self) -> &dyn Any {
-        self
-    }
-
-    fn bounds(&self) -> Bounds<Pixels> {
-        unsafe { Self::screen_bounds_from_native(self.native_screen.frame()) }
-    }
-
-    fn content_bounds(&self) -> Bounds<Pixels> {
-        unsafe { Self::screen_bounds_from_native(self.native_screen.visibleFrame()) }
-    }
-}

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

@@ -1,10 +1,10 @@
-use super::{ns_string, MetalRenderer, NSRange};
+use super::{display_bounds_from_native, ns_string, MacDisplay, MetalRenderer, NSRange};
 use crate::{
-    point, px, size, AnyWindowHandle, Bounds, Event, Executor, KeyDownEvent, Keystroke, MacScreen,
-    Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMovedEvent, MouseUpEvent,
-    NSRectExt, Pixels, PlatformAtlas, PlatformInputHandler, PlatformScreen, PlatformWindow, Point,
-    Scene, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions,
-    WindowPromptLevel,
+    display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, Event, Executor,
+    GlobalPixels, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton,
+    MouseDownEvent, MouseMovedEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay,
+    PlatformInputHandler, PlatformWindow, Point, Scene, Size, Timer, WindowAppearance,
+    WindowBounds, WindowKind, WindowOptions, WindowPromptLevel,
 };
 use block::ConcreteBlock;
 use cocoa::{
@@ -14,7 +14,9 @@ use cocoa::{
         NSWindowStyleMask, NSWindowTitleVisibility,
     },
     base::{id, nil},
-    foundation::{NSAutoreleasePool, NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger},
+    foundation::{
+        NSAutoreleasePool, NSDictionary, NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger,
+    },
 };
 use core_graphics::display::CGRect;
 use ctor::ctor;
@@ -365,7 +367,7 @@ impl MacWindowState {
             }
 
             let frame = self.frame();
-            let screen_size = self.native_window.screen().visibleFrame().size();
+            let screen_size = self.native_window.screen().visibleFrame().into();
             if frame.size == screen_size {
                 WindowBounds::Maximized
             } else {
@@ -374,10 +376,10 @@ impl MacWindowState {
         }
     }
 
-    fn frame(&self) -> Bounds<Pixels> {
+    fn frame(&self) -> Bounds<GlobalPixels> {
         unsafe {
             let frame = NSWindow::frame(self.native_window);
-            MacScreen::screen_bounds_from_native(frame)
+            display_bounds_from_native(mem::transmute::<NSRect, CGRect>(frame))
         }
     }
 
@@ -441,15 +443,33 @@ impl MacWindow {
                     msg_send![PANEL_CLASS, alloc]
                 }
             };
+
+            let display = options
+                .display_id
+                .and_then(|display_id| MacDisplay::all().find(|display| display.id() == display_id))
+                .unwrap_or_else(|| MacDisplay::primary());
+
+            let mut target_screen = nil;
+            let screens = NSScreen::screens(nil);
+            let count: u64 = cocoa::foundation::NSArray::count(screens);
+            for i in 0..count {
+                let screen = cocoa::foundation::NSArray::objectAtIndex(screens, i);
+                let device_description = NSScreen::deviceDescription(screen);
+                let screen_number_key: id = NSString::alloc(nil).init_str("NSScreenNumber");
+                let screen_number =
+                    NSDictionary::objectForKey_(device_description, screen_number_key);
+                if (*(screen_number as *const u32)) == display.id().0 {
+                    target_screen = screen;
+                    break;
+                }
+            }
+
             let native_window = native_window.initWithContentRect_styleMask_backing_defer_screen_(
                 NSRect::new(NSPoint::new(0., 0.), NSSize::new(1024., 768.)),
                 style_mask,
                 NSBackingStoreBuffered,
                 NO,
-                options
-                    .screen
-                    .map(|screen| MacScreen::from_handle(screen).native_screen)
-                    .unwrap_or(nil),
+                target_screen,
             );
             assert!(!native_window.is_null());
 
@@ -462,13 +482,13 @@ impl MacWindow {
                     native_window.setFrame_display_(screen.visibleFrame(), YES);
                 }
                 WindowBounds::Fixed(bounds) => {
-                    let bounds = MacScreen::screen_bounds_to_native(bounds);
-                    let screen_bounds = screen.visibleFrame();
-                    if bounds.intersects(screen_bounds) {
-                        native_window.setFrame_display_(bounds, YES);
+                    let display_bounds = display.bounds();
+                    let frame = if bounds.intersects(&display_bounds) {
+                        display_bounds_to_native(bounds)
                     } else {
-                        native_window.setFrame_display_(screen_bounds, YES);
-                    }
+                        display_bounds_to_native(display_bounds)
+                    };
+                    native_window.setFrame_display_(mem::transmute::<CGRect, NSRect>(frame), YES);
                 }
             }
 
@@ -649,11 +669,18 @@ impl PlatformWindow for MacWindow {
         }
     }
 
-    fn screen(&self) -> Rc<dyn PlatformScreen> {
+    fn display(&self) -> Rc<dyn PlatformDisplay> {
         unsafe {
-            Rc::new(MacScreen {
-                native_screen: self.0.as_ref().lock().native_window.screen(),
-            })
+            let screen = self.0.lock().native_window.screen();
+            let device_description: id = msg_send![screen, deviceDescription];
+            let screen_number: id = NSDictionary::valueForKey_(
+                device_description,
+                NSString::alloc(nil).init_str("NSScreenNumber"),
+            );
+
+            let screen_number: u32 = msg_send![screen_number, unsignedIntValue];
+
+            Rc::new(MacDisplay(screen_number))
         }
     }
 

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

@@ -1,5 +1,5 @@
 use super::Platform;
-use crate::{Executor, ScreenId};
+use crate::{DisplayId, Executor};
 
 pub struct TestPlatform;
 
@@ -47,11 +47,11 @@ impl Platform for TestPlatform {
         unimplemented!()
     }
 
-    fn screens(&self) -> Vec<std::rc::Rc<dyn crate::PlatformScreen>> {
+    fn displays(&self) -> Vec<std::rc::Rc<dyn crate::PlatformDisplay>> {
         unimplemented!()
     }
 
-    fn screen_by_id(&self, _id: ScreenId) -> Option<std::rc::Rc<dyn crate::PlatformScreen>> {
+    fn display(&self, _id: DisplayId) -> Option<std::rc::Rc<dyn crate::PlatformDisplay>> {
         unimplemented!()
     }
 

crates/storybook2/src/storybook2.rs 🔗

@@ -29,7 +29,7 @@ fn main() {
             WindowOptions {
                 bounds: WindowBounds::Fixed(Bounds {
                     origin: Default::default(),
-                    size: size(px(800.), px(600.)),
+                    size: size(px(800.), px(600.)).into(),
                 }),
                 ..Default::default()
             },