diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index fa62a74f3f7e27485cb0f36abd4c5ab5ab82ea38..2fb1c6f5fcaa8baf4c3a128644b372408260c501 100644 --- a/assets/keymaps/default.json +++ b/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" } }, { diff --git a/crates/gpui3/src/geometry.rs b/crates/gpui3/src/geometry.rs index d60866816d6e3513929f530d2ae9bd50eb97966a..478598daba2150ba3cea8c8aee7b0231fa75633a 100644 --- a/crates/gpui3/src/geometry.rs +++ b/crates/gpui3/src/geometry.rs @@ -199,11 +199,20 @@ impl, S: Clone> MulAssign for Size { impl Eq for Size {} -impl From>> for Size> { - fn from(size: Size>) -> Self { +// impl From>> for Size> { +// fn from(size: Size>) -> Self { +// Size { +// width: size.width.map(|p| p.0 as f32), +// height: size.height.map(|p| p.0 as f32), +// } +// } +// } + +impl From> for Size { + fn from(size: Size) -> 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> Bounds { } } +impl + Sub> Bounds { + pub fn intersects(&self, other: &Bounds) -> 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 + Sub> Bounds { pub fn intersect(&self, other: &Self) -> Self { let upper_left = self.origin.max(&other.origin); @@ -698,6 +719,28 @@ impl From 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 for f64 { + fn from(global_pixels: GlobalPixels) -> Self { + global_pixels.0 as f64 + } +} + +impl From 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 IsZero for Corners { && 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); + } +} diff --git a/crates/gpui3/src/gpui3.rs b/crates/gpui3/src/gpui3.rs index 53d2fd89f88712ae0a2f3171d11ca0194008fe80..92a8043b7c9e8e49d98964f0a6b110fb8096b768 100644 --- a/crates/gpui3/src/gpui3.rs +++ b/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::*; diff --git a/crates/gpui3/src/platform.rs b/crates/gpui3/src/platform.rs index 9feec5a9c50dad1437377e31d128c5249a417787..d6eacf4e80e97927c6fd99175bb926f2854f6206 100644 --- a/crates/gpui3/src/platform.rs +++ b/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>; - fn screen_by_id(&self, id: ScreenId) -> Option>; + fn displays(&self) -> Vec>; + fn display(&self, id: DisplayId) -> Option>; fn main_window(&self) -> Option; 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; - fn handle(&self) -> PlatformScreenHandle; +pub trait PlatformDisplay: Debug { + fn id(&self) -> DisplayId; fn as_any(&self) -> &dyn Any; - fn bounds(&self) -> Bounds; - fn content_bounds(&self) -> Bounds; + fn bounds(&self) -> Bounds; + fn link(&self) -> Box; } -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; + fn display(&self) -> Rc; fn mouse_position(&self) -> Point; fn as_any_mut(&mut self) -> &mut dyn Any; fn set_input_handler(&mut self, input_handler: Box); @@ -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); + fn start(&mut self); + fn stop(&mut self); +} + pub trait PlatformTextSystem: Send + Sync { fn add_fonts(&self, fonts: &[Arc>]) -> Result<()>; fn all_font_families(&self) -> Vec; @@ -266,9 +270,6 @@ pub trait PlatformInputHandler { fn bounds_for_range(&self, range_utf16: Range) -> Option>; } -#[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, + pub display_id: Option, } 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), + Fixed(Bounds), } #[derive(Copy, Clone, Debug)] diff --git a/crates/gpui3/src/platform/mac.rs b/crates/gpui3/src/platform/mac.rs index dcbf8f887f2d5ee663ccb2d1383adaa1b55d5919..27cc48108f17e835814c9158dc2006a4098a9193 100644 --- a/crates/gpui3/src/platform/mac.rs +++ b/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 { - size(px(self.size.width as f32), px(self.size.height as f32)) +impl From for Size { + 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 for Size { + 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 { diff --git a/crates/gpui3/src/platform/mac/display.rs b/crates/gpui3/src/platform/mac/display.rs new file mode 100644 index 0000000000000000000000000000000000000000..e1308ece6aeaa7df8a1f9e99d64407771fe5fd3e --- /dev/null +++ b/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::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 { + 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 { + 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) -> 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 { + 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 { + Box::new(unsafe { MacDisplayLink::new(self.0) }) + } +} diff --git a/crates/gpui3/src/platform/mac/display_link.rs b/crates/gpui3/src/platform/mac/display_link.rs new file mode 100644 index 0000000000000000000000000000000000000000..72961bd28a7a1400f2cb3348e099c0accb257a93 --- /dev/null +++ b/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>, +} + +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) { + 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(¤t_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 { + 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 { + 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); + } + } +} diff --git a/crates/gpui3/src/platform/mac/platform.rs b/crates/gpui3/src/platform/mac/platform.rs index d4110a114ed6f9dfacc4e77827b40faab6d3563d..a499f9087730739b69db6b12b9e76ba31947daac 100644 --- a/crates/gpui3/src/platform/mac/platform.rs +++ b/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> { - MacScreen::all() + fn displays(&self) -> Vec> { + MacDisplay::all() .into_iter() .map(|screen| Rc::new(screen) as Rc<_>) .collect() } - fn screen_by_id(&self, id: ScreenId) -> Option> { - MacScreen::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>) - } - // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box { // Box::new(StatusItem::add(self.fonts())) // } + fn display(&self, id: DisplayId) -> Option> { + MacDisplay::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>) + } + fn main_window(&self) -> Option { MacWindow::main_window() } @@ -736,6 +736,32 @@ impl Platform for MacPlatform { } } + // fn on_menu_command(&self, callback: Box) { + // self.0.lock().menu_command = Some(callback); + // } + + // fn on_will_open_menu(&self, callback: Box) { + // self.0.lock().will_open_menu = Some(callback); + // } + + // fn on_validate_menu_command(&self, callback: Box bool>) { + // self.0.lock().validate_menu_command = Some(callback); + // } + + // fn set_menus(&self, menus: Vec, 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 { let state = self.0.lock(); unsafe { @@ -773,32 +799,6 @@ impl Platform for MacPlatform { } } - // fn on_menu_command(&self, callback: Box) { - // self.0.lock().menu_command = Some(callback); - // } - - // fn on_will_open_menu(&self, callback: Box) { - // self.0.lock().will_open_menu = Some(callback); - // } - - // fn on_validate_menu_command(&self, callback: Box bool>) { - // self.0.lock().validate_menu_command = Some(callback); - // } - - // fn set_menus(&self, menus: Vec, 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); diff --git a/crates/gpui3/src/platform/mac/screen.rs b/crates/gpui3/src/platform/mac/screen.rs deleted file mode 100644 index 048b9dd6fd45b0a2cbf86f7c9a789d9cfef25d45..0000000000000000000000000000000000000000 --- a/crates/gpui3/src/platform/mac/screen.rs +++ /dev/null @@ -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::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 { - 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) -> 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 { - 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 { - 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 { - unsafe { Self::screen_bounds_from_native(self.native_screen.frame()) } - } - - fn content_bounds(&self) -> Bounds { - unsafe { Self::screen_bounds_from_native(self.native_screen.visibleFrame()) } - } -} diff --git a/crates/gpui3/src/platform/mac/window.rs b/crates/gpui3/src/platform/mac/window.rs index 080aa74350551728a2226bb32d8da16cb9db4811..984ec8c9d359585e719d6de39c41dd6b03ea03a1 100644 --- a/crates/gpui3/src/platform/mac/window.rs +++ b/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 { + fn frame(&self) -> Bounds { unsafe { let frame = NSWindow::frame(self.native_window); - MacScreen::screen_bounds_from_native(frame) + display_bounds_from_native(mem::transmute::(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::(frame), YES); } } @@ -649,11 +669,18 @@ impl PlatformWindow for MacWindow { } } - fn screen(&self) -> Rc { + fn display(&self) -> Rc { 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)) } } diff --git a/crates/gpui3/src/platform/test.rs b/crates/gpui3/src/platform/test.rs index e170e21e9554ff3faed657936fca613e80ba2cd4..562e91b2b2decb12072b472520649885f66cc5b6 100644 --- a/crates/gpui3/src/platform/test.rs +++ b/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> { + fn displays(&self) -> Vec> { unimplemented!() } - fn screen_by_id(&self, _id: ScreenId) -> Option> { + fn display(&self, _id: DisplayId) -> Option> { unimplemented!() } diff --git a/crates/storybook2/src/storybook2.rs b/crates/storybook2/src/storybook2.rs index 4774cfada6d81cb1a4128c3ef238bafcb9e7aa7b..719ce348ea772b3c93078fa73803244b8096d0a5 100644 --- a/crates/storybook2/src/storybook2.rs +++ b/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() },