From 699a5d294447baf239935912a67899b89bf824c8 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 4 Oct 2023 20:35:20 -0600 Subject: [PATCH 01/17] Checkpoint --- 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(-) create mode 100644 crates/gpui3/src/platform/mac/display.rs create mode 100644 crates/gpui3/src/platform/mac/display_link.rs delete mode 100644 crates/gpui3/src/platform/mac/screen.rs 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() }, From 177e385bb9521f703f84c98d723f42abf3968144 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 4 Oct 2023 22:59:01 -0600 Subject: [PATCH 02/17] Checkpoint: Fix a crash --- crates/gpui3/src/platform.rs | 2 +- crates/gpui3/src/platform/mac.rs | 4 +-- crates/gpui3/src/platform/mac/display.rs | 40 ++++++------------------ crates/gpui3/src/platform/mac/window.rs | 6 ++-- 4 files changed, 16 insertions(+), 36 deletions(-) diff --git a/crates/gpui3/src/platform.rs b/crates/gpui3/src/platform.rs index d6eacf4e80e97927c6fd99175bb926f2854f6206..3b97ef097daa82566716a2827d756540ce31574b 100644 --- a/crates/gpui3/src/platform.rs +++ b/crates/gpui3/src/platform.rs @@ -157,7 +157,7 @@ pub trait PlatformDispatcher: Send + Sync { } pub trait PlatformDisplayLink { - fn set_output_callback(&mut self, callback: Box); + // fn set_output_callback(&mut self, callback: Box); fn start(&mut self); fn stop(&mut self); } diff --git a/crates/gpui3/src/platform/mac.rs b/crates/gpui3/src/platform/mac.rs index 27cc48108f17e835814c9158dc2006a4098a9193..882d5efcd120c1bca586174dcce6fb77d846cd72 100644 --- a/crates/gpui3/src/platform/mac.rs +++ b/crates/gpui3/src/platform/mac.rs @@ -2,7 +2,7 @@ ///! an origin at the bottom left of the main display. mod dispatcher; mod display; -mod display_link; +// mod display_link; mod events; mod metal_atlas; mod metal_renderer; @@ -33,7 +33,7 @@ use std::{ pub use dispatcher::*; pub use display::*; -pub use display_link::*; +// pub use display_link::*; pub use metal_atlas::*; pub use platform::*; pub use text_system::*; diff --git a/crates/gpui3/src/platform/mac/display.rs b/crates/gpui3/src/platform/mac/display.rs index e1308ece6aeaa7df8a1f9e99d64407771fe5fd3e..55148f0e2884e95ea4a2d27cdab3f65e2d73f19c 100644 --- a/crates/gpui3/src/platform/mac/display.rs +++ b/crates/gpui3/src/platform/mac/display.rs @@ -1,7 +1,6 @@ -use crate::{point, size, Bounds, DisplayId, GlobalPixels, MacDisplayLink, PlatformDisplay}; - +use crate::{point, size, Bounds, DisplayId, GlobalPixels, PlatformDisplay}; use core_graphics::{ - display::{CGDirectDisplayID, CGGetActiveDisplayList}, + display::{CGDirectDisplayID, CGDisplayBounds, CGGetActiveDisplayList}, geometry::{CGPoint, CGRect, CGSize}, }; use std::any::Any; @@ -49,13 +48,14 @@ impl MacDisplay { /// 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; + let primary_screen_size = unsafe { CGDisplayBounds(MacDisplay::primary().id().0) }.size; + Bounds { origin: point( GlobalPixels(rect.origin.x as f32), - primary_screen_height - - GlobalPixels(rect.origin.y as f32) - - GlobalPixels(rect.size.height as f32), + GlobalPixels( + primary_screen_size.height as f32 - rect.origin.y as f32 - rect.size.height as f32, + ), ), size: size( GlobalPixels(rect.size.width as f32), @@ -94,33 +94,13 @@ impl PlatformDisplay for MacDisplay { 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); + let native_bounds = CGDisplayBounds(self.0); display_bounds_from_native(native_bounds) } } fn link(&self) -> Box { - Box::new(unsafe { MacDisplayLink::new(self.0) }) + unimplemented!() + // Box::new(unsafe { MacDisplayLink::new(self.0) }) } } diff --git a/crates/gpui3/src/platform/mac/window.rs b/crates/gpui3/src/platform/mac/window.rs index 984ec8c9d359585e719d6de39c41dd6b03ea03a1..b16e85f08df43fc3c22ece34b1eb6a4f67a7f152 100644 --- a/crates/gpui3/src/platform/mac/window.rs +++ b/crates/gpui3/src/platform/mac/window.rs @@ -456,9 +456,9 @@ impl MacWindow { 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 { + let screen_number = device_description.objectForKey_(screen_number_key); + let screen_number: NSUInteger = msg_send![screen_number, unsignedIntegerValue]; + if screen_number as u32 == display.id().0 { target_screen = screen; break; } From 0d0c760d9455f24dae0fa2b340fb5c8cc552c44c Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 4 Oct 2023 22:34:44 -0600 Subject: [PATCH 03/17] Checkpoint --- crates/gpui3/src/app.rs | 15 +- crates/gpui3/src/display_linker.rs | 55 +++++++ crates/gpui3/src/gpui3.rs | 2 + crates/gpui3/src/platform.rs | 14 +- crates/gpui3/src/platform/mac.rs | 4 +- crates/gpui3/src/platform/mac/display.rs | 5 - .../{display_link.rs => display_linker.rs} | 148 ++++++++++++++---- crates/gpui3/src/platform/mac/platform.rs | 9 +- crates/gpui3/src/platform/test.rs | 4 + crates/gpui3/src/window.rs | 20 ++- 10 files changed, 224 insertions(+), 52 deletions(-) create mode 100644 crates/gpui3/src/display_linker.rs rename crates/gpui3/src/platform/mac/{display_link.rs => display_linker.rs} (55%) diff --git a/crates/gpui3/src/app.rs b/crates/gpui3/src/app.rs index 36de3b932b6c9e86e77edc127b7447af95c4f0ff..ec212c7a685a6cc64be4bc5c198fcd8b1193864c 100644 --- a/crates/gpui3/src/app.rs +++ b/crates/gpui3/src/app.rs @@ -8,8 +8,8 @@ pub use model_context::*; use refineable::Refineable; use crate::{ - current_platform, image_cache::ImageCache, AssetSource, Context, Executor, LayoutId, - MainThread, MainThreadOnly, Platform, RootView, SvgRenderer, Task, TextStyle, + current_platform, image_cache::ImageCache, AssetSource, Context, DisplayLinker, Executor, + LayoutId, MainThread, MainThreadOnly, Platform, RootView, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, Window, WindowContext, WindowHandle, WindowId, }; use anyhow::{anyhow, Result}; @@ -51,15 +51,15 @@ impl App { http_client: Arc, ) -> Self { let executor = platform.executor(); - let text_system = Arc::new(TextSystem::new(platform.text_system())); let entities = EntityMap::new(); let unit_entity = entities.insert(entities.reserve(), ()); Self(Arc::new_cyclic(|this| { Mutex::new(AppContext { this: this.clone(), + display_linker: Arc::new(DisplayLinker::new(platform.display_linker())), + text_system: Arc::new(TextSystem::new(platform.text_system())), platform: MainThreadOnly::new(platform, executor.clone()), executor, - text_system, svg_renderer: SvgRenderer::new(asset_source), image_cache: ImageCache::new(http_client), pending_updates: 0, @@ -97,6 +97,7 @@ pub struct AppContext { text_system: Arc, pending_updates: usize, pub(crate) executor: Executor, + pub(crate) display_linker: Arc, pub(crate) svg_renderer: SvgRenderer, pub(crate) image_cache: ImageCache, pub(crate) text_style_stack: Vec, @@ -359,9 +360,15 @@ impl MainThread { let id = cx.windows.insert(None); let handle = WindowHandle::new(id); let mut window = Window::new(handle.into(), options, cx); + let display_id = window.display_id; let root_view = build_root_view(&mut WindowContext::mutable(cx, &mut window)); window.root_view.replace(root_view.into_any()); cx.windows.get_mut(id).unwrap().replace(window); + + cx.display_linker.on_next_frame(display_id, |_, _| { + dbg!("next frame"); + }); + handle }) } diff --git a/crates/gpui3/src/display_linker.rs b/crates/gpui3/src/display_linker.rs new file mode 100644 index 0000000000000000000000000000000000000000..1be32a23e2c0b401592d5ed29896a1d96366075b --- /dev/null +++ b/crates/gpui3/src/display_linker.rs @@ -0,0 +1,55 @@ +use crate::{DisplayId, PlatformDisplayLinker, VideoTimestamp}; +use collections::HashMap; +use parking_lot::Mutex; +use std::sync::Arc; + +type FrameCallback = Box; + +pub struct DisplayLinker { + platform_linker: Arc, + next_frame_callbacks: Arc>>>, +} + +impl DisplayLinker { + pub(crate) fn new(platform_linker: Arc) -> Self { + Self { + platform_linker, + next_frame_callbacks: Default::default(), + } + } + + pub(crate) fn on_next_frame( + &self, + display_id: DisplayId, + callback: impl FnOnce(&VideoTimestamp, &VideoTimestamp) + Send + 'static, + ) { + let next_frame_callbacks = self.next_frame_callbacks.clone(); + let callback = Box::new(callback); + match self.next_frame_callbacks.lock().entry(display_id) { + collections::hash_map::Entry::Occupied(mut entry) => { + if entry.get().is_empty() { + self.platform_linker.start(display_id) + } + entry.get_mut().push(callback) + } + collections::hash_map::Entry::Vacant(entry) => { + // let platform_linker = self.platform_linker.clone(); + self.platform_linker.set_output_callback( + display_id, + Box::new(move |current_time, output_time| { + for callback in next_frame_callbacks + .lock() + .get_mut(&display_id) + .unwrap() + .drain(..) + { + callback(current_time, output_time); + } + // platform_linker.stop(display_id); + }), + ); + entry.insert(vec![callback]); + } + } + } +} diff --git a/crates/gpui3/src/gpui3.rs b/crates/gpui3/src/gpui3.rs index 92a8043b7c9e8e49d98964f0a6b110fb8096b768..e8276488e40d06d63357c638982b26be414fd3c9 100644 --- a/crates/gpui3/src/gpui3.rs +++ b/crates/gpui3/src/gpui3.rs @@ -1,6 +1,7 @@ mod app; mod assets; mod color; +mod display_linker; mod element; mod elements; mod executor; @@ -22,6 +23,7 @@ pub use anyhow::Result; pub use app::*; pub use assets::*; pub use color::*; +pub use display_linker::*; pub use element::*; pub use elements::*; pub use executor::*; diff --git a/crates/gpui3/src/platform.rs b/crates/gpui3/src/platform.rs index 3b97ef097daa82566716a2827d756540ce31574b..53a35fadcc9717753ef7248099af252f9908795b 100644 --- a/crates/gpui3/src/platform.rs +++ b/crates/gpui3/src/platform.rs @@ -42,6 +42,7 @@ pub(crate) fn current_platform() -> Arc { pub trait Platform: 'static { fn executor(&self) -> Executor; + fn display_linker(&self) -> Arc; fn text_system(&self) -> Arc; fn run(&self, on_finish_launching: Box); @@ -99,7 +100,6 @@ pub trait PlatformDisplay: Debug { fn id(&self) -> DisplayId; fn as_any(&self) -> &dyn Any; fn bounds(&self) -> Bounds; - fn link(&self) -> Box; } #[derive(PartialEq, Eq, Hash, Copy, Clone)] @@ -156,10 +156,14 @@ 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 PlatformDisplayLinker: Send + Sync { + fn set_output_callback( + &self, + display_id: DisplayId, + callback: Box, + ); + fn start(&self, display_id: DisplayId); + fn stop(&self, display_id: DisplayId); } pub trait PlatformTextSystem: Send + Sync { diff --git a/crates/gpui3/src/platform/mac.rs b/crates/gpui3/src/platform/mac.rs index 882d5efcd120c1bca586174dcce6fb77d846cd72..462aa209ccb040f2df4d1a70466c1e48eb78e9a9 100644 --- a/crates/gpui3/src/platform/mac.rs +++ b/crates/gpui3/src/platform/mac.rs @@ -2,7 +2,7 @@ ///! an origin at the bottom left of the main display. mod dispatcher; mod display; -// mod display_link; +mod display_linker; mod events; mod metal_atlas; mod metal_renderer; @@ -33,7 +33,7 @@ use std::{ pub use dispatcher::*; pub use display::*; -// pub use display_link::*; +pub use display_linker::*; pub use metal_atlas::*; pub use platform::*; pub use text_system::*; diff --git a/crates/gpui3/src/platform/mac/display.rs b/crates/gpui3/src/platform/mac/display.rs index 55148f0e2884e95ea4a2d27cdab3f65e2d73f19c..dc064293f342a9106f49ecf500dc8d3bb140efc0 100644 --- a/crates/gpui3/src/platform/mac/display.rs +++ b/crates/gpui3/src/platform/mac/display.rs @@ -98,9 +98,4 @@ impl PlatformDisplay for MacDisplay { display_bounds_from_native(native_bounds) } } - - fn link(&self) -> Box { - unimplemented!() - // 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_linker.rs similarity index 55% rename from crates/gpui3/src/platform/mac/display_link.rs rename to crates/gpui3/src/platform/mac/display_linker.rs index 72961bd28a7a1400f2cb3348e099c0accb257a93..05381845b2ce249ad5c592a27e4af726f38cc900 100644 --- a/crates/gpui3/src/platform/mac/display_link.rs +++ b/crates/gpui3/src/platform/mac/display_linker.rs @@ -1,41 +1,77 @@ -use crate::PlatformDisplayLink; use std::ffi::c_void; +use crate::{DisplayId, PlatformDisplayLinker}; +use collections::HashMap; +use parking_lot::Mutex; pub use sys::CVTimeStamp as VideoTimestamp; -pub struct MacDisplayLink { - sys_link: sys::DisplayLink, - output_callback: Option>, +pub struct MacDisplayLinker { + links: Mutex>, } -impl MacDisplayLink { - pub unsafe fn new(display_id: u32) -> Self { - Self { - sys_link: sys::DisplayLink::on_display(display_id).unwrap(), - output_callback: None, +struct MacDisplayLink { + output_callback: Box, + system_link: sys::DisplayLink, +} + +unsafe impl Send for MacDisplayLink {} + +impl MacDisplayLinker { + pub fn new() -> Self { + MacDisplayLinker { + links: Default::default(), } } } -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, +impl PlatformDisplayLinker for MacDisplayLinker { + fn set_output_callback( + &self, + display_id: DisplayId, + mut output_callback: Box, + ) { + if let Some(mut system_link) = unsafe { sys::DisplayLink::on_display(display_id.0) } { + unsafe { + system_link.set_output_callback( + trampoline, + output_callback.as_mut() as *mut dyn FnMut(_, _) as *mut c_void, + ) + } + + let previous = self.links.lock().insert( + display_id, + MacDisplayLink { + output_callback, + system_link, + }, ); + assert!( + previous.is_none(), + "You can currently only set an output callback once per display." + ) + } else { + return log::warn!("DisplayLink could not be obtained for {:?}", display_id); } - self.output_callback = Some(callback); } - fn start(&mut self) { - unsafe { self.sys_link.start() } + fn start(&self, display_id: DisplayId) { + if let Some(link) = self.links.lock().get_mut(&display_id) { + unsafe { + link.system_link.start(); + } + } else { + log::warn!("No DisplayLink callback registered for {:?}", display_id) + } } - fn stop(&mut self) { - unsafe { self.sys_link.stop() } + fn stop(&self, display_id: DisplayId) { + if let Some(link) = self.links.lock().get_mut(&display_id) { + unsafe { + link.system_link.stop(); + } + } else { + log::warn!("No DisplayLink callback registered for {:?}", display_id) + } } } @@ -48,11 +84,8 @@ unsafe extern "C" fn trampoline( 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); - } + if let Some((current_time, output_time)) = current_time.as_ref().zip(output_time.as_ref()) { + output_callback(¤t_time, &output_time); } 0 } @@ -61,9 +94,8 @@ 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)] + #![allow(dead_code, non_upper_case_globals)] - pub use cocoa::quartzcore::CVTimeStamp; use foreign_types::{foreign_type, ForeignType}; use std::{ ffi::c_void, @@ -90,6 +122,64 @@ mod sys { } } + #[repr(C)] + #[derive(Clone, Copy)] + pub struct CVTimeStamp { + pub version: u32, + pub video_time_scale: i32, + pub video_time: i64, + pub host_time: u64, + pub rate_scalar: f64, + pub video_refresh_period: i64, + pub smpte_time: CVSMPTETime, + pub flags: u64, + pub reserved: u64, + } + + pub type CVTimeStampFlags = u64; + + pub const kCVTimeStampVideoTimeValid: CVTimeStampFlags = 1 << 0; + pub const kCVTimeStampHostTimeValid: CVTimeStampFlags = 1 << 1; + pub const kCVTimeStampSMPTETimeValid: CVTimeStampFlags = 1 << 2; + pub const kCVTimeStampVideoRefreshPeriodValid: CVTimeStampFlags = 1 << 3; + pub const kCVTimeStampRateScalarValid: CVTimeStampFlags = 1 << 4; + pub const kCVTimeStampTopField: CVTimeStampFlags = 1 << 16; + pub const kCVTimeStampBottomField: CVTimeStampFlags = 1 << 17; + pub const kCVTimeStampVideoHostTimeValid: CVTimeStampFlags = + kCVTimeStampVideoTimeValid | kCVTimeStampHostTimeValid; + pub const kCVTimeStampIsInterlaced: CVTimeStampFlags = + kCVTimeStampTopField | kCVTimeStampBottomField; + + #[repr(C)] + #[derive(Clone, Copy)] + pub struct CVSMPTETime { + pub subframes: i16, + pub subframe_divisor: i16, + pub counter: u32, + pub time_type: u32, + pub flags: u32, + pub hours: i16, + pub minutes: i16, + pub seconds: i16, + pub frames: i16, + } + + pub type CVSMPTETimeType = u32; + + pub const kCVSMPTETimeType24: CVSMPTETimeType = 0; + pub const kCVSMPTETimeType25: CVSMPTETimeType = 1; + pub const kCVSMPTETimeType30Drop: CVSMPTETimeType = 2; + pub const kCVSMPTETimeType30: CVSMPTETimeType = 3; + pub const kCVSMPTETimeType2997: CVSMPTETimeType = 4; + pub const kCVSMPTETimeType2997Drop: CVSMPTETimeType = 5; + pub const kCVSMPTETimeType60: CVSMPTETimeType = 6; + pub const kCVSMPTETimeType5994: CVSMPTETimeType = 7; + + pub type CVSMPTETimeFlags = u32; + + pub const kCVSMPTETimeValid: CVSMPTETimeFlags = 1 << 0; + pub const kCVSMPTETimeRunning: CVSMPTETimeFlags = 1 << 1; + 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. diff --git a/crates/gpui3/src/platform/mac/platform.rs b/crates/gpui3/src/platform/mac/platform.rs index a499f9087730739b69db6b12b9e76ba31947daac..a3f6fbfbe693d2902320bb892e2868dde146990f 100644 --- a/crates/gpui3/src/platform/mac/platform.rs +++ b/crates/gpui3/src/platform/mac/platform.rs @@ -1,8 +1,9 @@ use super::BoolExt; use crate::{ AnyWindowHandle, ClipboardItem, CursorStyle, DisplayId, Event, Executor, MacDispatcher, - MacDisplay, MacTextSystem, MacWindow, PathPromptOptions, Platform, PlatformDisplay, - PlatformTextSystem, PlatformWindow, Result, SemanticVersion, WindowOptions, + MacDisplay, MacDisplayLinker, MacTextSystem, MacWindow, PathPromptOptions, Platform, + PlatformDisplay, PlatformDisplayLinker, PlatformTextSystem, PlatformWindow, Result, + SemanticVersion, WindowOptions, }; use anyhow::anyhow; use block::ConcreteBlock; @@ -347,6 +348,10 @@ impl Platform for MacPlatform { self.0.lock().executor.clone() } + fn display_linker(&self) -> Arc { + Arc::new(MacDisplayLinker::new()) + } + fn text_system(&self) -> Arc { self.0.lock().text_system.clone() } diff --git a/crates/gpui3/src/platform/test.rs b/crates/gpui3/src/platform/test.rs index 562e91b2b2decb12072b472520649885f66cc5b6..88ec46e83317136034508afb6ed44bcc4acd601a 100644 --- a/crates/gpui3/src/platform/test.rs +++ b/crates/gpui3/src/platform/test.rs @@ -15,6 +15,10 @@ impl Platform for TestPlatform { unimplemented!() } + fn display_linker(&self) -> std::sync::Arc { + unimplemented!() + } + fn text_system(&self) -> std::sync::Arc { unimplemented!() } diff --git a/crates/gpui3/src/window.rs b/crates/gpui3/src/window.rs index f255172c776276f5e8297e3037e033f982d0bfd3..a4ff0cfa8307e69e93ec0a36de6fdb2b721a189d 100644 --- a/crates/gpui3/src/window.rs +++ b/crates/gpui3/src/window.rs @@ -1,10 +1,11 @@ use crate::{ image_cache::RenderImageParams, px, AnyView, AppContext, AsyncWindowContext, AvailableSpace, - BorrowAppContext, Bounds, Context, Corners, DevicePixels, Effect, Element, EntityId, FontId, - GlyphId, Handle, Hsla, ImageData, IsZero, LayerId, LayoutId, MainThread, MainThreadOnly, - MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Reference, - RenderGlyphParams, RenderSvgParams, ScaledPixels, Scene, SharedString, Size, Style, - TaffyLayoutEngine, Task, WeakHandle, WindowOptions, SUBPIXEL_VARIANTS, + BorrowAppContext, Bounds, Context, Corners, DevicePixels, DisplayId, Effect, Element, EntityId, + FontId, GlyphId, Handle, Hsla, ImageData, IsZero, LayerId, LayoutId, MainThread, + MainThreadOnly, MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point, + PolychromeSprite, Reference, RenderGlyphParams, RenderSvgParams, ScaledPixels, Scene, + SharedString, Size, Style, TaffyLayoutEngine, Task, WeakHandle, WindowOptions, + SUBPIXEL_VARIANTS, }; use anyhow::Result; use smallvec::SmallVec; @@ -16,6 +17,7 @@ pub struct AnyWindow {} pub struct Window { handle: AnyWindowHandle, platform_window: MainThreadOnly>, + pub(crate) display_id: DisplayId, // todo!("make private again?") sprite_atlas: Arc, rem_size: Pixels, content_size: Size, @@ -35,6 +37,7 @@ impl Window { cx: &mut MainThread, ) -> Self { let platform_window = cx.platform().open_window(handle, options); + let display_id = platform_window.display().id(); let sprite_atlas = platform_window.sprite_atlas(); let mouse_position = platform_window.mouse_position(); let content_size = platform_window.content_size(); @@ -46,6 +49,12 @@ impl Window { cx.update_window(handle, |cx| { cx.window.scene = Scene::new(scale_factor); cx.window.content_size = content_size; + cx.window.display_id = cx + .window + .platform_window + .borrow_on_main_thread() + .display() + .id(); cx.window.dirty = true; }) .log_err(); @@ -57,6 +66,7 @@ impl Window { Window { handle, platform_window, + display_id, sprite_atlas, rem_size: px(16.), content_size, From 77b9a7aa5a19eaf3a8856c2ac8f118381f8d607a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 4 Oct 2023 23:59:21 -0600 Subject: [PATCH 04/17] Checkpoint --- crates/gpui3/src/app.rs | 6 --- crates/gpui3/src/display_linker.rs | 3 +- .../gpui3/src/platform/mac/display_linker.rs | 42 ++++++++++--------- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/crates/gpui3/src/app.rs b/crates/gpui3/src/app.rs index ec212c7a685a6cc64be4bc5c198fcd8b1193864c..805f7b24f0394d8afa1ed9e2580275cd39bef1bd 100644 --- a/crates/gpui3/src/app.rs +++ b/crates/gpui3/src/app.rs @@ -146,7 +146,6 @@ impl AppContext { } fn flush_effects(&mut self) { - dbg!("flush effects"); while let Some(effect) = self.pending_effects.pop_front() { match effect { Effect::Notify(entity_id) => self.apply_notify_effect(entity_id), @@ -364,11 +363,6 @@ impl MainThread { let root_view = build_root_view(&mut WindowContext::mutable(cx, &mut window)); window.root_view.replace(root_view.into_any()); cx.windows.get_mut(id).unwrap().replace(window); - - cx.display_linker.on_next_frame(display_id, |_, _| { - dbg!("next frame"); - }); - handle }) } diff --git a/crates/gpui3/src/display_linker.rs b/crates/gpui3/src/display_linker.rs index 1be32a23e2c0b401592d5ed29896a1d96366075b..54954b15b69afbe11132c2c7d369b84345dda5e9 100644 --- a/crates/gpui3/src/display_linker.rs +++ b/crates/gpui3/src/display_linker.rs @@ -28,7 +28,7 @@ impl DisplayLinker { match self.next_frame_callbacks.lock().entry(display_id) { collections::hash_map::Entry::Occupied(mut entry) => { if entry.get().is_empty() { - self.platform_linker.start(display_id) + self.platform_linker.start(display_id); } entry.get_mut().push(callback) } @@ -48,6 +48,7 @@ impl DisplayLinker { // platform_linker.stop(display_id); }), ); + self.platform_linker.start(display_id); entry.insert(vec![callback]); } } diff --git a/crates/gpui3/src/platform/mac/display_linker.rs b/crates/gpui3/src/platform/mac/display_linker.rs index 05381845b2ce249ad5c592a27e4af726f38cc900..47c26f007c24bcc5610b04a07fdc28489d92925e 100644 --- a/crates/gpui3/src/platform/mac/display_linker.rs +++ b/crates/gpui3/src/platform/mac/display_linker.rs @@ -1,4 +1,8 @@ -use std::ffi::c_void; +use std::{ + ffi::c_void, + mem, + sync::{Arc, Weak}, +}; use crate::{DisplayId, PlatformDisplayLinker}; use collections::HashMap; @@ -10,8 +14,8 @@ pub struct MacDisplayLinker { } struct MacDisplayLink { - output_callback: Box, system_link: sys::DisplayLink, + _output_callback: Arc, } unsafe impl Send for MacDisplayLink {} @@ -24,33 +28,29 @@ impl MacDisplayLinker { } } +type OutputCallback = Mutex>; + impl PlatformDisplayLinker for MacDisplayLinker { fn set_output_callback( &self, display_id: DisplayId, - mut output_callback: Box, + output_callback: Box, ) { if let Some(mut system_link) = unsafe { sys::DisplayLink::on_display(display_id.0) } { - unsafe { - system_link.set_output_callback( - trampoline, - output_callback.as_mut() as *mut dyn FnMut(_, _) as *mut c_void, - ) - } + let callback = Arc::new(Mutex::new(output_callback)); + let weak_callback_ptr: *const OutputCallback = Arc::downgrade(&callback).into_raw(); + unsafe { system_link.set_output_callback(trampoline, weak_callback_ptr as *mut c_void) } - let previous = self.links.lock().insert( + self.links.lock().insert( display_id, MacDisplayLink { - output_callback, + _output_callback: callback, system_link, }, ); - assert!( - previous.is_none(), - "You can currently only set an output callback once per display." - ) } else { - return log::warn!("DisplayLink could not be obtained for {:?}", display_id); + log::warn!("DisplayLink could not be obtained for {:?}", display_id); + return; } } @@ -81,11 +81,15 @@ unsafe extern "C" fn trampoline( output_time: *const sys::CVTimeStamp, _flags_in: i64, _flags_out: *mut i64, - context: *mut c_void, + user_data: *mut c_void, ) -> i32 { - let output_callback = &mut (*(context as *mut MacDisplayLink)).output_callback; if let Some((current_time, output_time)) = current_time.as_ref().zip(output_time.as_ref()) { - output_callback(¤t_time, &output_time); + let output_callback: Weak = + Weak::from_raw(user_data as *mut OutputCallback); + if let Some(output_callback) = output_callback.upgrade() { + (output_callback.lock())(current_time, output_time) + } + mem::forget(output_callback); } 0 } From 1c70ca2214da6b2784482558c8efb0815d19189c Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 5 Oct 2023 00:08:45 -0600 Subject: [PATCH 05/17] Checkpoint --- crates/gpui3/src/app.rs | 1 - crates/gpui3/src/app/async_context.rs | 6 +++++- crates/gpui3/src/window.rs | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/crates/gpui3/src/app.rs b/crates/gpui3/src/app.rs index 805f7b24f0394d8afa1ed9e2580275cd39bef1bd..9ffb23943b6c9b8de7ab88cb46b3b31a37f69c8b 100644 --- a/crates/gpui3/src/app.rs +++ b/crates/gpui3/src/app.rs @@ -359,7 +359,6 @@ impl MainThread { let id = cx.windows.insert(None); let handle = WindowHandle::new(id); let mut window = Window::new(handle.into(), options, cx); - let display_id = window.display_id; let root_view = build_root_view(&mut WindowContext::mutable(cx, &mut window)); window.root_view.replace(root_view.into_any()); cx.windows.get_mut(id).unwrap().replace(window); diff --git a/crates/gpui3/src/app/async_context.rs b/crates/gpui3/src/app/async_context.rs index 92a26456e442d093a12f2d990d5eae6ffc07b817..65f4e666b318fd3b44d270f352aee7f1dc3882af 100644 --- a/crates/gpui3/src/app/async_context.rs +++ b/crates/gpui3/src/app/async_context.rs @@ -60,9 +60,13 @@ pub struct AsyncWindowContext { } impl AsyncWindowContext { - pub fn new(app: AsyncAppContext, window: AnyWindowHandle) -> Self { + pub(crate) fn new(app: AsyncAppContext, window: AnyWindowHandle) -> Self { Self { app, window } } + + pub fn update(&self, update: impl FnOnce(&mut WindowContext) -> R) -> Result { + self.app.update_window(self.window, update) + } } impl Context for AsyncWindowContext { diff --git a/crates/gpui3/src/window.rs b/crates/gpui3/src/window.rs index a4ff0cfa8307e69e93ec0a36de6fdb2b721a189d..7e3c5d3ce58be10a56db5a05867ff887824d2bbf 100644 --- a/crates/gpui3/src/window.rs +++ b/crates/gpui3/src/window.rs @@ -143,6 +143,14 @@ impl<'a, 'w> WindowContext<'a, 'w> { AsyncWindowContext::new(self.app.to_async(), self.window.handle) } + pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + Send + 'static) { + let cx = self.to_async(); + let display_id = self.window.display_id; + self.display_linker.on_next_frame(display_id, move |_, _| { + cx.update(f).ok(); + }); + } + pub fn spawn( &mut self, f: impl FnOnce(AnyWindowHandle, AsyncWindowContext) -> Fut + Send + 'static, @@ -581,6 +589,15 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> { self.entities.weak_handle(self.entity_id) } + pub fn on_next_frame(&mut self, f: impl FnOnce(&mut S, &mut ViewContext) + Send + 'static) { + let mut cx = self.to_async(); + let entity = self.handle(); + let display_id = self.window.display_id; + self.display_linker.on_next_frame(display_id, move |_, _| { + entity.update(&mut cx, f).ok(); + }); + } + pub fn observe( &mut self, handle: &Handle, From ed20397a2bdf7ee209dbe4143eac33cf54f52688 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 5 Oct 2023 00:13:17 -0600 Subject: [PATCH 06/17] Checkpoint --- crates/gpui3/src/app.rs | 1 + crates/gpui3/src/app/async_context.rs | 6 ++++++ crates/gpui3/src/elements/img.rs | 7 +++---- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/crates/gpui3/src/app.rs b/crates/gpui3/src/app.rs index 9ffb23943b6c9b8de7ab88cb46b3b31a37f69c8b..9a58112e0094c0c8ba2abfd7cee5db8bbe984108 100644 --- a/crates/gpui3/src/app.rs +++ b/crates/gpui3/src/app.rs @@ -146,6 +146,7 @@ impl AppContext { } fn flush_effects(&mut self) { + dbg!("flush effects"); while let Some(effect) = self.pending_effects.pop_front() { match effect { Effect::Notify(entity_id) => self.apply_notify_effect(entity_id), diff --git a/crates/gpui3/src/app/async_context.rs b/crates/gpui3/src/app/async_context.rs index 65f4e666b318fd3b44d270f352aee7f1dc3882af..026a1b0a076edc0928bd1f9396187cab56e32c84 100644 --- a/crates/gpui3/src/app/async_context.rs +++ b/crates/gpui3/src/app/async_context.rs @@ -67,6 +67,12 @@ impl AsyncWindowContext { pub fn update(&self, update: impl FnOnce(&mut WindowContext) -> R) -> Result { self.app.update_window(self.window, update) } + + pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + Send + 'static) { + self.app + .update_window(self.window, |cx| cx.on_next_frame(f)) + .ok(); + } } impl Context for AsyncWindowContext { diff --git a/crates/gpui3/src/elements/img.rs b/crates/gpui3/src/elements/img.rs index 28bc51a04d5c12f702c5de0b4e2e0e524cf58f70..29ddcc3702d15c9d27d4c9a4352693d34501e869 100644 --- a/crates/gpui3/src/elements/img.rs +++ b/crates/gpui3/src/elements/img.rs @@ -75,12 +75,11 @@ impl Element for Img { let corner_radii = style.corner_radii.to_pixels(bounds, cx.rem_size()); cx.paint_image(bounds, corner_radii, order, data, self.grayscale)?; } else { - cx.spawn(|view, mut cx| async move { + cx.spawn(|_, mut cx| async move { if image_future.await.log_err().is_some() { - view.update(&mut cx, |_, cx| { + cx.on_next_frame(|cx| { cx.notify(); - }) - .ok(); + }); } }) .detach() From bf73b405290d8d93fadee7f482822c819e0741b2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 5 Oct 2023 10:57:16 +0200 Subject: [PATCH 07/17] Draw only once on next frame callbacks --- crates/gpui3/src/app.rs | 16 +++--- crates/gpui3/src/display_linker.rs | 56 ------------------- crates/gpui3/src/elements/img.rs | 4 +- crates/gpui3/src/gpui3.rs | 2 - .../gpui3/src/platform/mac/metal_renderer.rs | 2 - crates/gpui3/src/window.rs | 45 ++++++++++++--- 6 files changed, 47 insertions(+), 78 deletions(-) delete mode 100644 crates/gpui3/src/display_linker.rs diff --git a/crates/gpui3/src/app.rs b/crates/gpui3/src/app.rs index 9a58112e0094c0c8ba2abfd7cee5db8bbe984108..7a79f53a5a53a10c13cfd347dbb21feb090a3e8f 100644 --- a/crates/gpui3/src/app.rs +++ b/crates/gpui3/src/app.rs @@ -8,9 +8,9 @@ pub use model_context::*; use refineable::Refineable; use crate::{ - current_platform, image_cache::ImageCache, AssetSource, Context, DisplayLinker, Executor, - LayoutId, MainThread, MainThreadOnly, Platform, RootView, SvgRenderer, Task, TextStyle, - TextStyleRefinement, TextSystem, Window, WindowContext, WindowHandle, WindowId, + current_platform, image_cache::ImageCache, AssetSource, Context, DisplayId, Executor, LayoutId, + MainThread, MainThreadOnly, Platform, PlatformDisplayLinker, RootView, SvgRenderer, Task, + TextStyle, TextStyleRefinement, TextSystem, Window, WindowContext, WindowHandle, WindowId, }; use anyhow::{anyhow, Result}; use collections::{HashMap, VecDeque}; @@ -56,13 +56,14 @@ impl App { Self(Arc::new_cyclic(|this| { Mutex::new(AppContext { this: this.clone(), - display_linker: Arc::new(DisplayLinker::new(platform.display_linker())), text_system: Arc::new(TextSystem::new(platform.text_system())), + pending_updates: 0, + display_linker: platform.display_linker(), + next_frame_callbacks: Default::default(), platform: MainThreadOnly::new(platform, executor.clone()), executor, svg_renderer: SvgRenderer::new(asset_source), image_cache: ImageCache::new(http_client), - pending_updates: 0, text_style_stack: Vec::new(), state_stacks_by_type: HashMap::default(), unit_entity, @@ -90,14 +91,16 @@ impl App { } type Handlers = SmallVec<[Arc bool + Send + Sync + 'static>; 2]>; +type FrameCallback = Box; pub struct AppContext { this: Weak>, platform: MainThreadOnly, text_system: Arc, pending_updates: usize, + pub(crate) display_linker: Arc, + pub(crate) next_frame_callbacks: HashMap>, pub(crate) executor: Executor, - pub(crate) display_linker: Arc, pub(crate) svg_renderer: SvgRenderer, pub(crate) image_cache: ImageCache, pub(crate) text_style_stack: Vec, @@ -146,7 +149,6 @@ impl AppContext { } fn flush_effects(&mut self) { - dbg!("flush effects"); while let Some(effect) = self.pending_effects.pop_front() { match effect { Effect::Notify(entity_id) => self.apply_notify_effect(entity_id), diff --git a/crates/gpui3/src/display_linker.rs b/crates/gpui3/src/display_linker.rs deleted file mode 100644 index 54954b15b69afbe11132c2c7d369b84345dda5e9..0000000000000000000000000000000000000000 --- a/crates/gpui3/src/display_linker.rs +++ /dev/null @@ -1,56 +0,0 @@ -use crate::{DisplayId, PlatformDisplayLinker, VideoTimestamp}; -use collections::HashMap; -use parking_lot::Mutex; -use std::sync::Arc; - -type FrameCallback = Box; - -pub struct DisplayLinker { - platform_linker: Arc, - next_frame_callbacks: Arc>>>, -} - -impl DisplayLinker { - pub(crate) fn new(platform_linker: Arc) -> Self { - Self { - platform_linker, - next_frame_callbacks: Default::default(), - } - } - - pub(crate) fn on_next_frame( - &self, - display_id: DisplayId, - callback: impl FnOnce(&VideoTimestamp, &VideoTimestamp) + Send + 'static, - ) { - let next_frame_callbacks = self.next_frame_callbacks.clone(); - let callback = Box::new(callback); - match self.next_frame_callbacks.lock().entry(display_id) { - collections::hash_map::Entry::Occupied(mut entry) => { - if entry.get().is_empty() { - self.platform_linker.start(display_id); - } - entry.get_mut().push(callback) - } - collections::hash_map::Entry::Vacant(entry) => { - // let platform_linker = self.platform_linker.clone(); - self.platform_linker.set_output_callback( - display_id, - Box::new(move |current_time, output_time| { - for callback in next_frame_callbacks - .lock() - .get_mut(&display_id) - .unwrap() - .drain(..) - { - callback(current_time, output_time); - } - // platform_linker.stop(display_id); - }), - ); - self.platform_linker.start(display_id); - entry.insert(vec![callback]); - } - } - } -} diff --git a/crates/gpui3/src/elements/img.rs b/crates/gpui3/src/elements/img.rs index 29ddcc3702d15c9d27d4c9a4352693d34501e869..49ed91fbde6f7620e7902ae9bf66b565c446d570 100644 --- a/crates/gpui3/src/elements/img.rs +++ b/crates/gpui3/src/elements/img.rs @@ -77,9 +77,7 @@ impl Element for Img { } else { cx.spawn(|_, mut cx| async move { if image_future.await.log_err().is_some() { - cx.on_next_frame(|cx| { - cx.notify(); - }); + cx.on_next_frame(|cx| cx.notify()); } }) .detach() diff --git a/crates/gpui3/src/gpui3.rs b/crates/gpui3/src/gpui3.rs index e8276488e40d06d63357c638982b26be414fd3c9..92a8043b7c9e8e49d98964f0a6b110fb8096b768 100644 --- a/crates/gpui3/src/gpui3.rs +++ b/crates/gpui3/src/gpui3.rs @@ -1,7 +1,6 @@ mod app; mod assets; mod color; -mod display_linker; mod element; mod elements; mod executor; @@ -23,7 +22,6 @@ pub use anyhow::Result; pub use app::*; pub use assets::*; pub use color::*; -pub use display_linker::*; pub use element::*; pub use elements::*; pub use executor::*; diff --git a/crates/gpui3/src/platform/mac/metal_renderer.rs b/crates/gpui3/src/platform/mac/metal_renderer.rs index 84a5fd51261aaa959b2b8f91ce3dc8d1e42a6f51..a018aad7623cdec236d1c97f5176a6f4f5fe65aa 100644 --- a/crates/gpui3/src/platform/mac/metal_renderer.rs +++ b/crates/gpui3/src/platform/mac/metal_renderer.rs @@ -131,8 +131,6 @@ impl MetalRenderer { } pub fn draw(&mut self, scene: &mut Scene) { - dbg!("draw scene"); - let layer = self.layer.clone(); let viewport_size = layer.drawable_size(); let viewport_size: Size = size( diff --git a/crates/gpui3/src/window.rs b/crates/gpui3/src/window.rs index 7e3c5d3ce58be10a56db5a05867ff887824d2bbf..1b15cae691975b059d899ce7bdd6cb1ae62529ba 100644 --- a/crates/gpui3/src/window.rs +++ b/crates/gpui3/src/window.rs @@ -144,11 +144,42 @@ impl<'a, 'w> WindowContext<'a, 'w> { } pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + Send + 'static) { - let cx = self.to_async(); + let f = Box::new(f); let display_id = self.window.display_id; - self.display_linker.on_next_frame(display_id, move |_, _| { - cx.update(f).ok(); - }); + let async_cx = self.to_async(); + let app_cx = self.app_mut(); + match app_cx.next_frame_callbacks.entry(display_id) { + collections::hash_map::Entry::Occupied(mut entry) => { + if entry.get().is_empty() { + app_cx.display_linker.start(display_id); + } + entry.get_mut().push(f); + } + collections::hash_map::Entry::Vacant(entry) => { + app_cx.display_linker.set_output_callback( + display_id, + Box::new(move |_current_time, _output_time| { + let _ = async_cx.update(|cx| { + let callbacks = cx + .next_frame_callbacks + .get_mut(&display_id) + .unwrap() + .drain(..) + .collect::>(); + for callback in callbacks { + callback(cx); + } + + if cx.next_frame_callbacks.get(&display_id).unwrap().is_empty() { + cx.display_linker.stop(display_id); + } + }); + }), + ); + app_cx.display_linker.start(display_id); + entry.insert(vec![f]); + } + } } pub fn spawn( @@ -590,11 +621,9 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> { } pub fn on_next_frame(&mut self, f: impl FnOnce(&mut S, &mut ViewContext) + Send + 'static) { - let mut cx = self.to_async(); let entity = self.handle(); - let display_id = self.window.display_id; - self.display_linker.on_next_frame(display_id, move |_, _| { - entity.update(&mut cx, f).ok(); + self.window_cx.on_next_frame(move |cx| { + entity.update(cx, f).ok(); }); } From 7643bd61fda00f9fad5cafaf1a59a1ba9eb84360 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 5 Oct 2023 10:59:50 +0200 Subject: [PATCH 08/17] Checkpoint --- crates/gpui3/src/scene.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/crates/gpui3/src/scene.rs b/crates/gpui3/src/scene.rs index 3426eefc9ea306ab530e7082b6ad16880d080c30..742317da42ccd5167648af46b7875f42fbb40551 100644 --- a/crates/gpui3/src/scene.rs +++ b/crates/gpui3/src/scene.rs @@ -204,22 +204,6 @@ pub struct Quad { pub border_widths: Edges, } -impl Quad { - pub fn vertices(&self) -> impl Iterator> { - let x1 = self.bounds.origin.x; - let y1 = self.bounds.origin.y; - let x2 = x1 + self.bounds.size.width; - let y2 = y1 + self.bounds.size.height; - [ - Point::new(x1, y1), - Point::new(x2, y1), - Point::new(x2, y2), - Point::new(x1, y2), - ] - .into_iter() - } -} - impl Ord for Quad { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.order.cmp(&other.order) From 92bda1231ef3b42df052acfa8bfd43c27b5a48a3 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 5 Oct 2023 12:11:28 +0200 Subject: [PATCH 09/17] Use content mask for quad as well --- crates/gpui3/src/platform/mac/shaders.metal | 3 ++- crates/gpui3/src/scene.rs | 5 ++--- crates/gpui3/src/style.rs | 6 ++---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/crates/gpui3/src/platform/mac/shaders.metal b/crates/gpui3/src/platform/mac/shaders.metal index b4614bef4d58fc08f8f8a24008e0ae79b86e8f02..f71f849438f428e69994e39473088ad13a545185 100644 --- a/crates/gpui3/src/platform/mac/shaders.metal +++ b/crates/gpui3/src/platform/mac/shaders.metal @@ -30,7 +30,8 @@ vertex QuadVertexOutput quad_vertex(uint unit_vertex_id [[vertex_id]], float2 unit_vertex = unit_vertices[unit_vertex_id]; Quad quad = quads[quad_id]; float4 device_position = to_device_position(unit_vertex, quad.bounds, - quad.clip_bounds, viewport_size); + quad.content_mask.bounds, + viewport_size); float4 background_color = hsla_to_rgba(quad.background); float4 border_color = hsla_to_rgba(quad.border_color); return QuadVertexOutput{device_position, background_color, border_color, diff --git a/crates/gpui3/src/scene.rs b/crates/gpui3/src/scene.rs index 742317da42ccd5167648af46b7875f42fbb40551..8d34d0d3cfdecdff2cad01244cbdaf31a886cba7 100644 --- a/crates/gpui3/src/scene.rs +++ b/crates/gpui3/src/scene.rs @@ -191,13 +191,12 @@ pub(crate) enum PrimitiveBatch<'a> { }, } -#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] #[repr(C)] pub struct Quad { pub order: u32, pub bounds: Bounds, - pub clip_bounds: Bounds, - pub clip_corner_radii: Corners, + pub content_mask: ScaledContentMask, pub background: Hsla, pub border_color: Hsla, pub corner_radii: Corners, diff --git a/crates/gpui3/src/style.rs b/crates/gpui3/src/style.rs index 1f874807488b01374f4517273518127f9460d787..19c9c31b48baf11f2acd0a9a7ec8956ed39bca2b 100644 --- a/crates/gpui3/src/style.rs +++ b/crates/gpui3/src/style.rs @@ -236,15 +236,13 @@ impl Style { let background_color = self.fill.as_ref().and_then(Fill::color); if background_color.is_some() || self.is_border_visible() { let layer_id = cx.current_layer_id(); + let content_mask = cx.content_mask(); cx.scene().insert( layer_id, Quad { order, bounds: bounds.scale(scale), - clip_bounds: bounds.scale(scale), // todo! - clip_corner_radii: self - .corner_radii - .map(|length| length.to_pixels(rem_size).scale(scale)), + content_mask: content_mask.scale(scale), background: background_color.unwrap_or_default(), border_color: self.border_color.unwrap_or_default(), corner_radii: self From 2e056e9b0b8da8b13ed8ae4496cd3051e71fd979 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 5 Oct 2023 15:30:47 +0200 Subject: [PATCH 10/17] WIP --- crates/gpui3/build.rs | 2 + crates/gpui3/src/geometry.rs | 15 ++- .../gpui3/src/platform/mac/metal_renderer.rs | 87 +++++++++++++- crates/gpui3/src/platform/mac/shaders.metal | 109 ++++++++++++++++++ crates/gpui3/src/scene.rs | 61 ++++++++++ crates/gpui3/src/style.rs | 43 ++++++- crates/gpui3/src/style_helpers.rs | 57 ++++++++- crates/storybook2/src/collab_panel.rs | 3 +- 8 files changed, 366 insertions(+), 11 deletions(-) diff --git a/crates/gpui3/build.rs b/crates/gpui3/build.rs index 861534aa6eb75874fa25c1213e0c118bf3ee33aa..c1ad7491d815fa60c696f13ba84df8147a29fa36 100644 --- a/crates/gpui3/build.rs +++ b/crates/gpui3/build.rs @@ -52,6 +52,8 @@ fn generate_shader_bindings() -> PathBuf { "AtlasTile".into(), "QuadInputIndex".into(), "Quad".into(), + "ShadowInputIndex".into(), + "Shadow".into(), "SpriteInputIndex".into(), "MonochromeSprite".into(), "PolychromeSprite".into(), diff --git a/crates/gpui3/src/geometry.rs b/crates/gpui3/src/geometry.rs index 478598daba2150ba3cea8c8aee7b0231fa75633a..2616690b089dc48cdfd917ae260a014533c8fa2e 100644 --- a/crates/gpui3/src/geometry.rs +++ b/crates/gpui3/src/geometry.rs @@ -469,6 +469,17 @@ impl Edges { } } +impl Edges { + pub fn scale(&self, factor: f32) -> Edges { + Edges { + top: self.top.scale(factor), + right: self.right.scale(factor), + bottom: self.bottom.scale(factor), + left: self.left.scale(factor), + } + } +} + #[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)] #[refineable(debug)] #[repr(C)] @@ -480,8 +491,8 @@ pub struct Corners { } impl Corners { - pub fn to_pixels(&self, bounds: Bounds, rem_size: Pixels) -> Corners { - let max = bounds.size.width.max(bounds.size.height) / 2.; + pub fn to_pixels(&self, size: Size, rem_size: Pixels) -> Corners { + let max = size.width.max(size.height) / 2.; Corners { top_left: self.top_left.to_pixels(rem_size).min(max), top_right: self.top_right.to_pixels(rem_size).min(max), diff --git a/crates/gpui3/src/platform/mac/metal_renderer.rs b/crates/gpui3/src/platform/mac/metal_renderer.rs index a018aad7623cdec236d1c97f5176a6f4f5fe65aa..f2e14a04319dfcf2348e3447d2c89172acba7ad6 100644 --- a/crates/gpui3/src/platform/mac/metal_renderer.rs +++ b/crates/gpui3/src/platform/mac/metal_renderer.rs @@ -1,6 +1,6 @@ use crate::{ point, size, AtlasTextureId, DevicePixels, MetalAtlas, MonochromeSprite, PolychromeSprite, - Quad, Scene, Size, + Quad, Scene, Shadow, Size, }; use cocoa::{ base::{NO, YES}, @@ -18,6 +18,7 @@ pub struct MetalRenderer { layer: metal::MetalLayer, command_queue: CommandQueue, quads_pipeline_state: metal::RenderPipelineState, + shadows_pipeline_state: metal::RenderPipelineState, monochrome_sprites_pipeline_state: metal::RenderPipelineState, polychrome_sprites_pipeline_state: metal::RenderPipelineState, unit_vertices: metal::Buffer, @@ -90,6 +91,14 @@ impl MetalRenderer { "quad_fragment", PIXEL_FORMAT, ); + let shadows_pipeline_state = build_pipeline_state( + &device, + &library, + "shadows", + "shadow_vertex", + "shadow_fragment", + PIXEL_FORMAT, + ); let monochrome_sprites_pipeline_state = build_pipeline_state( &device, &library, @@ -114,6 +123,7 @@ impl MetalRenderer { layer, command_queue, quads_pipeline_state, + shadows_pipeline_state, monochrome_sprites_pipeline_state, polychrome_sprites_pipeline_state, unit_vertices, @@ -183,6 +193,14 @@ impl MetalRenderer { command_encoder, ); } + crate::PrimitiveBatch::Shadows(shadows) => { + self.draw_shadows( + shadows, + &mut instance_offset, + viewport_size, + command_encoder, + ); + } crate::PrimitiveBatch::MonochromeSprites { texture_id, sprites, @@ -279,6 +297,66 @@ impl MetalRenderer { *offset = next_offset; } + fn draw_shadows( + &mut self, + shadows: &[Shadow], + offset: &mut usize, + viewport_size: Size, + command_encoder: &metal::RenderCommandEncoderRef, + ) { + if shadows.is_empty() { + return; + } + align_offset(offset); + + command_encoder.set_render_pipeline_state(&self.shadows_pipeline_state); + command_encoder.set_vertex_buffer( + ShadowInputIndex::Vertices as u64, + Some(&self.unit_vertices), + 0, + ); + command_encoder.set_vertex_buffer( + ShadowInputIndex::Shadows as u64, + Some(&self.instances), + *offset as u64, + ); + command_encoder.set_fragment_buffer( + ShadowInputIndex::Shadows as u64, + Some(&self.instances), + *offset as u64, + ); + + command_encoder.set_vertex_bytes( + ShadowInputIndex::ViewportSize as u64, + mem::size_of_val(&viewport_size) as u64, + &viewport_size as *const Size as *const _, + ); + + let shadow_bytes_len = mem::size_of::() * shadows.len(); + let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; + unsafe { + ptr::copy_nonoverlapping( + shadows.as_ptr() as *const u8, + buffer_contents, + shadow_bytes_len, + ); + } + + let next_offset = *offset + shadow_bytes_len; + assert!( + next_offset <= INSTANCE_BUFFER_SIZE, + "instance buffer exhausted" + ); + + command_encoder.draw_primitives_instanced( + metal::MTLPrimitiveType::Triangle, + 0, + 6, + shadows.len() as u64, + ); + *offset = next_offset; + } + fn draw_monochrome_sprites( &mut self, texture_id: AtlasTextureId, @@ -469,6 +547,13 @@ enum QuadInputIndex { ViewportSize = 2, } +#[repr(C)] +enum ShadowInputIndex { + Vertices = 0, + Shadows = 1, + ViewportSize = 2, +} + #[repr(C)] enum SpriteInputIndex { Vertices = 0, diff --git a/crates/gpui3/src/platform/mac/shaders.metal b/crates/gpui3/src/platform/mac/shaders.metal index f71f849438f428e69994e39473088ad13a545185..5e37b48027c3f82d1edd53f0ff038f14a229b60b 100644 --- a/crates/gpui3/src/platform/mac/shaders.metal +++ b/crates/gpui3/src/platform/mac/shaders.metal @@ -11,6 +11,9 @@ float2 to_tile_position(float2 unit_vertex, AtlasTile tile, constant Size_DevicePixels *atlas_size); float quad_sdf(float2 point, Bounds_ScaledPixels bounds, Corners_ScaledPixels corner_radii); +float gaussian(float x, float sigma); +float2 erf(float2 x); +float blur_along_x(float x, float y, float sigma, float corner, float2 half_size); struct QuadVertexOutput { float4 position [[position]]; @@ -110,6 +113,91 @@ fragment float4 quad_fragment(QuadVertexOutput input [[stage_in]], return color * float4(1., 1., 1., saturate(0.5 - distance)); } +struct ShadowVertexOutput { + float4 position [[position]]; + float4 color [[flat]]; + uint shadow_id [[flat]]; +}; + +vertex ShadowVertexOutput shadow_vertex( + uint unit_vertex_id [[vertex_id]], + uint shadow_id [[instance_id]], + constant float2 *unit_vertices [[buffer(ShadowInputIndex_Vertices)]], + constant Shadow *shadows [[buffer(ShadowInputIndex_Shadows)]], + constant Size_DevicePixels *viewport_size [[buffer(ShadowInputIndex_ViewportSize)]] +) { + float2 unit_vertex = unit_vertices[unit_vertex_id]; + Shadow shadow = shadows[shadow_id]; + + float margin = (3. * shadow.blur_radius) + shadow.spread_radius; + // Set the bounds of the shadow and adjust its size based on the shadow's spread radius + // to achieve the spreading effect + Bounds_ScaledPixels bounds = shadow.bounds; + bounds.origin.x -= margin; + bounds.origin.y -= margin; + bounds.size.width += 2. * margin; + bounds.size.height += 2. * margin; + + float4 device_position = to_device_position(unit_vertex, bounds, bounds, viewport_size); + float4 color = hsla_to_rgba(shadow.color); + + return ShadowVertexOutput { + device_position, + color, + shadow_id, + }; +} + +fragment float4 shadow_fragment( + ShadowVertexOutput input [[stage_in]], + constant Shadow *shadows [[buffer(ShadowInputIndex_Shadows)]] +) { + Shadow shadow = shadows[input.shadow_id]; + + float2 origin = float2( + shadow.bounds.origin.x - shadow.spread_radius, + shadow.bounds.origin.y - shadow.spread_radius + ); + float2 size = float2( + shadow.bounds.size.width + shadow.spread_radius * 2., + shadow.bounds.size.height + shadow.spread_radius * 2. + ); + float2 half_size = size / 2.; + float2 center = origin + half_size; + float2 point = input.position.xy - center; + float corner_radius; + if (point.x < 0.) { + if (point.y < 0.) { + corner_radius = shadow.corner_radii.top_left; + } else { + corner_radius = shadow.corner_radii.bottom_left; + } + } else { + if (point.y < 0.) { + corner_radius = shadow.corner_radii.top_right; + } else { + corner_radius = shadow.corner_radii.bottom_right; + } + } + + // The signal is only non-zero in a limited range, so don't waste samples + float low = point.y - half_size.y; + float high = point.y + half_size.y; + float start = clamp(-3. * shadow.blur_radius, low, high); + float end = clamp(3. * shadow.blur_radius, low, high); + + // Accumulate samples (we can get away with surprisingly few samples) + float step = (end - start) / 4.; + float y = start + step * 0.5; + float alpha = 0.; + for (int i = 0; i < 4; i++) { + alpha += blur_along_x(point.x, point.y - y, shadow.blur_radius, corner_radius, half_size) * gaussian(y, shadow.blur_radius) * step; + y += step; + } + + return input.color * float4(1., 1., 1., alpha); +} + struct MonochromeSpriteVertexOutput { float4 position [[position]]; float2 tile_position; @@ -308,3 +396,24 @@ float quad_sdf(float2 point, Bounds_ScaledPixels bounds, return distance; } + +// A standard gaussian function, used for weighting samples +float gaussian(float x, float sigma) { + return exp(-(x * x) / (2. * sigma * sigma)) / (sqrt(2. * M_PI_F) * sigma); +} + +// This approximates the error function, needed for the gaussian integral +float2 erf(float2 x) { + float2 s = sign(x); + float2 a = abs(x); + x = 1. + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a; + x *= x; + return s - s / (x * x); +} + +float blur_along_x(float x, float y, float sigma, float corner, float2 half_size) { + float delta = min(half_size.y - corner - abs(y), 0.); + float curved = half_size.x - corner + sqrt(max(0., corner * corner - delta * delta)); + float2 integral = 0.5 + 0.5 * erf((x + float2(-curved, curved)) * (sqrt(0.5) / sigma)); + return integral.y - integral.x; +} diff --git a/crates/gpui3/src/scene.rs b/crates/gpui3/src/scene.rs index 8d34d0d3cfdecdff2cad01244cbdaf31a886cba7..6a897a78d29235cde93a2c3bc29d279c45ed0fd8 100644 --- a/crates/gpui3/src/scene.rs +++ b/crates/gpui3/src/scene.rs @@ -38,6 +38,9 @@ impl Scene { Primitive::Quad(quad) => { layer.quads.push(quad); } + Primitive::Shadow(shadow) => { + layer.shadows.push(shadow); + } Primitive::MonochromeSprite(sprite) => { layer.monochrome_sprites.push(sprite); } @@ -55,6 +58,7 @@ impl Scene { #[derive(Debug, Default)] pub(crate) struct SceneLayer { pub quads: Vec, + pub shadows: Vec, pub monochrome_sprites: Vec, pub polychrome_sprites: Vec, } @@ -68,6 +72,9 @@ impl SceneLayer { quads: &self.quads, quads_start: 0, quads_iter: self.quads.iter().peekable(), + shadows: &self.shadows, + shadows_start: 0, + shadows_iter: self.shadows.iter().peekable(), monochrome_sprites: &self.monochrome_sprites, monochrome_sprites_start: 0, monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(), @@ -82,6 +89,9 @@ struct BatchIterator<'a> { quads: &'a [Quad], quads_start: usize, quads_iter: Peekable>, + shadows: &'a [Shadow], + shadows_start: usize, + shadows_iter: Peekable>, monochrome_sprites: &'a [MonochromeSprite], monochrome_sprites_start: usize, monochrome_sprites_iter: Peekable>, @@ -96,6 +106,10 @@ impl<'a> Iterator for BatchIterator<'a> { fn next(&mut self) -> Option { let mut kinds_and_orders = [ (PrimitiveKind::Quad, self.quads_iter.peek().map(|q| q.order)), + ( + PrimitiveKind::Shadow, + self.shadows_iter.peek().map(|s| s.order), + ), ( PrimitiveKind::MonochromeSprite, self.monochrome_sprites_iter.peek().map(|s| s.order), @@ -127,6 +141,19 @@ impl<'a> Iterator for BatchIterator<'a> { self.quads_start = quads_end; Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end])) } + PrimitiveKind::Shadow => { + let shadows_start = self.shadows_start; + let shadows_end = shadows_start + + self + .shadows_iter + .by_ref() + .take_while(|shadow| shadow.order <= max_order) + .count(); + self.shadows_start = shadows_end; + Some(PrimitiveBatch::Shadows( + &self.shadows[shadows_start..shadows_end], + )) + } PrimitiveKind::MonochromeSprite => { let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id; let sprites_start = self.monochrome_sprites_start; @@ -168,6 +195,7 @@ impl<'a> Iterator for BatchIterator<'a> { #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum PrimitiveKind { Quad, + Shadow, MonochromeSprite, PolychromeSprite, } @@ -175,12 +203,15 @@ pub enum PrimitiveKind { #[derive(Clone, Debug)] pub enum Primitive { Quad(Quad), + Shadow(Shadow), MonochromeSprite(MonochromeSprite), PolychromeSprite(PolychromeSprite), } +#[derive(Debug)] pub(crate) enum PrimitiveBatch<'a> { Quads(&'a [Quad]), + Shadows(&'a [Shadow]), MonochromeSprites { texture_id: AtlasTextureId, sprites: &'a [MonochromeSprite], @@ -221,6 +252,36 @@ impl From for Primitive { } } +#[derive(Debug, Clone, Eq, PartialEq)] +#[repr(C)] +pub struct Shadow { + pub order: u32, + pub bounds: Bounds, + pub corner_radii: Corners, + pub content_mask: ScaledContentMask, + pub color: Hsla, + pub blur_radius: ScaledPixels, + pub spread_radius: ScaledPixels, +} + +impl Ord for Shadow { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.order.cmp(&other.order) + } +} + +impl PartialOrd for Shadow { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl From for Primitive { + fn from(shadow: Shadow) -> Self { + Primitive::Shadow(shadow) + } +} + #[derive(Clone, Debug, Eq, PartialEq)] #[repr(C)] pub struct MonochromeSprite { diff --git a/crates/gpui3/src/style.rs b/crates/gpui3/src/style.rs index 19c9c31b48baf11f2acd0a9a7ec8956ed39bca2b..299743e695fe2a8c34c288b7f1f367933ac97a21 100644 --- a/crates/gpui3/src/style.rs +++ b/crates/gpui3/src/style.rs @@ -1,7 +1,7 @@ use crate::{ phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners, CornersRefinement, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures, FontStyle, - FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Quad, Rems, Result, RunStyle, + FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Quad, Rems, Result, RunStyle, Shadow, SharedString, Size, SizeRefinement, ViewContext, WindowContext, }; use refineable::Refineable; @@ -89,10 +89,21 @@ pub struct Style { #[refineable] pub corner_radii: Corners, + /// Box Shadow of the element + pub box_shadow: Option, + /// TEXT pub text: TextStyleRefinement, } +#[derive(Clone, Debug)] +pub struct BoxShadow { + pub color: Hsla, + pub offset: Point, + pub blur_radius: Pixels, + pub spread_radius: Pixels, +} + #[derive(Refineable, Clone, Debug)] #[refineable(debug)] pub struct TextStyle { @@ -233,6 +244,28 @@ impl Style { let rem_size = cx.rem_size(); let scale = cx.scale_factor(); + if let Some(shadow) = self.box_shadow.as_ref() { + let layer_id = cx.current_layer_id(); + let content_mask = cx.content_mask(); + let mut shadow_bounds = bounds; + shadow_bounds.origin += shadow.offset; + cx.scene().insert( + layer_id, + Shadow { + order, + bounds: shadow_bounds.scale(scale), + content_mask: content_mask.scale(scale), + corner_radii: self + .corner_radii + .to_pixels(bounds.size, rem_size) + .scale(scale), + color: shadow.color, + blur_radius: shadow.blur_radius.scale(scale), + spread_radius: shadow.spread_radius.scale(scale), + }, + ); + } + let background_color = self.fill.as_ref().and_then(Fill::color); if background_color.is_some() || self.is_border_visible() { let layer_id = cx.current_layer_id(); @@ -247,10 +280,9 @@ impl Style { border_color: self.border_color.unwrap_or_default(), corner_radii: self .corner_radii - .map(|length| length.to_pixels(rem_size).scale(scale)), - border_widths: self - .border_widths - .map(|length| length.to_pixels(rem_size).scale(scale)), + .to_pixels(bounds.size, rem_size) + .scale(scale), + border_widths: self.border_widths.to_pixels(rem_size).scale(scale), }, ); } @@ -296,6 +328,7 @@ impl Default for Style { fill: None, border_color: None, corner_radii: Corners::default(), + box_shadow: None, text: TextStyleRefinement::default(), } } diff --git a/crates/gpui3/src/style_helpers.rs b/crates/gpui3/src/style_helpers.rs index 12d9eade058d7bd29f292ec29224f06ae25c7975..109edba90c4a0a17617004b227bf4a53906dc601 100644 --- a/crates/gpui3/src/style_helpers.rs +++ b/crates/gpui3/src/style_helpers.rs @@ -1,6 +1,7 @@ use crate::{ - self as gpui3, relative, rems, AlignItems, Display, Fill, FlexDirection, Hsla, JustifyContent, - Length, Position, SharedString, Style, StyleRefinement, Styled, TextStyleRefinement, + self as gpui3, hsla, point, px, relative, rems, AlignItems, BoxShadow, Display, Fill, + FlexDirection, Hsla, JustifyContent, Length, Position, SharedString, Style, StyleRefinement, + Styled, TextStyleRefinement, }; pub trait StyleHelpers: Styled