diff --git a/crates/gpui3/Cargo.toml b/crates/gpui3/Cargo.toml index 855ce49d9d..e8ba5def50 100644 --- a/crates/gpui3/Cargo.toml +++ b/crates/gpui3/Cargo.toml @@ -7,7 +7,7 @@ description = "The next version of Zed's GPU-accelerated UI framework" publish = false [features] -test = ["backtrace", "dhat", "env_logger", "collections/test-support"] +test = ["backtrace", "dhat", "env_logger", "collections/test-support", "util/test-support"] [lib] path = "src/gpui3.rs" @@ -66,6 +66,7 @@ dhat = "0.3" env_logger.workspace = true png = "0.16" simplelog = "0.9" +util = { path = "../util", features = ["test-support"] } [build-dependencies] bindgen = "0.65.1" diff --git a/crates/gpui3/build.rs b/crates/gpui3/build.rs index 861534aa6e..d0d4a5c74b 100644 --- a/crates/gpui3/build.rs +++ b/crates/gpui3/build.rs @@ -50,7 +50,11 @@ fn generate_shader_bindings() -> PathBuf { "ScaledContentMask".into(), "Uniforms".into(), "AtlasTile".into(), + "ShadowInputIndex".into(), + "Shadow".into(), "QuadInputIndex".into(), + "Underline".into(), + "UnderlineInputIndex".into(), "Quad".into(), "SpriteInputIndex".into(), "MonochromeSprite".into(), diff --git a/crates/gpui3/src/app.rs b/crates/gpui3/src/app.rs index 36de3b932b..7a79f53a5a 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, 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}; @@ -51,18 +51,19 @@ 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(), + 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, - text_system, 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,12 +91,15 @@ 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) svg_renderer: SvgRenderer, pub(crate) image_cache: ImageCache, @@ -145,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/app/async_context.rs b/crates/gpui3/src/app/async_context.rs index 92a26456e4..026a1b0a07 100644 --- a/crates/gpui3/src/app/async_context.rs +++ b/crates/gpui3/src/app/async_context.rs @@ -60,9 +60,19 @@ 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) + } + + 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/element.rs b/crates/gpui3/src/element.rs index 2b2ddfc3bf..abe6ae0218 100644 --- a/crates/gpui3/src/element.rs +++ b/crates/gpui3/src/element.rs @@ -1,4 +1,6 @@ -use super::{Layout, LayoutId, Pixels, Point, Result, ViewContext}; +use crate::Bounds; + +use super::{LayoutId, Pixels, Point, Result, ViewContext}; pub(crate) use smallvec::SmallVec; pub trait Element: 'static { @@ -13,7 +15,7 @@ pub trait Element: 'static { fn paint( &mut self, - layout: Layout, + bounds: Bounds, state: &mut Self::State, frame_state: &mut Self::FrameState, cx: &mut ViewContext, @@ -90,7 +92,7 @@ enum ElementRenderPhase { frame_state: S, }, Painted { - layout: Layout, + bounds: Bounds, frame_state: S, }, } @@ -130,24 +132,23 @@ impl ElementObject for RenderedElement { layout_id, mut frame_state, } => { - let mut layout = cx.layout(layout_id)?.clone(); - offset.map(|offset| layout.bounds.origin += offset); - self.element - .paint(layout.clone(), state, &mut frame_state, cx)?; + let mut bounds = cx.layout_bounds(layout_id)?.clone(); + offset.map(|offset| bounds.origin += offset); + self.element.paint(bounds, state, &mut frame_state, cx)?; ElementRenderPhase::Painted { - layout, + bounds, frame_state, } } ElementRenderPhase::Painted { - layout, + bounds, mut frame_state, } => { self.element - .paint(layout.clone(), state, &mut frame_state, cx)?; + .paint(bounds.clone(), state, &mut frame_state, cx)?; ElementRenderPhase::Painted { - layout, + bounds, frame_state, } } diff --git a/crates/gpui3/src/elements/div.rs b/crates/gpui3/src/elements/div.rs index 535ccb9953..f577d9b5c6 100644 --- a/crates/gpui3/src/elements/div.rs +++ b/crates/gpui3/src/elements/div.rs @@ -1,6 +1,6 @@ use crate::{ - AnyElement, Bounds, Element, Layout, LayoutId, Overflow, ParentElement, Pixels, Point, - Refineable, RefinementCascade, Result, Style, StyleHelpers, Styled, ViewContext, + AnyElement, Bounds, Element, LayoutId, Overflow, ParentElement, Pixels, Point, Refineable, + RefinementCascade, Result, Style, StyleHelpers, Styled, ViewContext, }; use parking_lot::Mutex; use smallvec::SmallVec; @@ -40,34 +40,28 @@ impl Element for Div { fn paint( &mut self, - layout: Layout, + bounds: Bounds, state: &mut S, child_layouts: &mut Self::FrameState, cx: &mut ViewContext, ) -> Result<()> { - let Layout { order, bounds } = layout; - let style = self.computed_style(); - style.paint(order, bounds, cx); + cx.stack(0, |cx| style.paint(bounds, cx)); - // // todo!("support only one dimension being hidden") let overflow = &style.overflow; - // if style.overflow.y != Overflow::Visible || style.overflow.x != Overflow::Visible { - // cx.clip(layout.bounds, style.corner_radii, || ) - // } - style.apply_text_style(cx, |cx| { - style.apply_overflow(layout.bounds, cx, |cx| { - self.paint_children(overflow, state, cx) + cx.stack(1, |cx| { + style.apply_overflow(bounds, cx, |cx| self.paint_children(overflow, state, cx)) }) })?; - self.handle_scroll(order, bounds, style.overflow.clone(), child_layouts, cx); + self.handle_scroll(bounds, style.overflow.clone(), child_layouts, cx); // todo!("enable inspector") // if cx.is_inspector_enabled() { // self.paint_inspector(parent_origin, layout, cx); // } // + Ok(()) } } @@ -142,7 +136,6 @@ impl Div { fn handle_scroll( &mut self, - _order: u32, bounds: Bounds, overflow: Point, child_layout_ids: &[LayoutId], @@ -151,8 +144,8 @@ impl Div { if overflow.y == Overflow::Scroll || overflow.x == Overflow::Scroll { let mut scroll_max = Point::default(); for child_layout_id in child_layout_ids { - if let Some(child_layout) = cx.layout(*child_layout_id).log_err() { - scroll_max = scroll_max.max(&child_layout.bounds.lower_right()); + if let Some(child_bounds) = cx.layout_bounds(*child_layout_id).log_err() { + scroll_max = scroll_max.max(&child_bounds.lower_right()); } } scroll_max -= bounds.size; diff --git a/crates/gpui3/src/elements/img.rs b/crates/gpui3/src/elements/img.rs index 28bc51a04d..68aac46446 100644 --- a/crates/gpui3/src/elements/img.rs +++ b/crates/gpui3/src/elements/img.rs @@ -1,6 +1,6 @@ use crate::{ - BorrowWindow, Element, Layout, LayoutId, Result, SharedString, Style, StyleHelpers, Styled, - ViewContext, + BorrowWindow, Bounds, Element, LayoutId, Pixels, Result, SharedString, Style, StyleHelpers, + Styled, ViewContext, }; use futures::FutureExt; use refineable::RefinementCascade; @@ -54,16 +54,14 @@ impl Element for Img { fn paint( &mut self, - layout: Layout, + bounds: Bounds, _: &mut Self::State, _: &mut Self::FrameState, cx: &mut ViewContext, ) -> Result<()> { let style = self.computed_style(); - let order = layout.order; - let bounds = layout.bounds; - style.paint(order, bounds, cx); + style.paint(bounds, cx); if let Some(uri) = self.uri.clone() { let image_future = cx.image_cache.get(uri); @@ -72,15 +70,14 @@ impl Element for Img { .now_or_never() .and_then(ResultExt::log_err) { - let corner_radii = style.corner_radii.to_pixels(bounds, cx.rem_size()); - cx.paint_image(bounds, corner_radii, order, data, self.grayscale)?; + let corner_radii = style.corner_radii.to_pixels(bounds.size, cx.rem_size()); + cx.stack(1, |cx| { + cx.paint_image(bounds, corner_radii, 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.notify(); - }) - .ok(); + cx.on_next_frame(|cx| cx.notify()); } }) .detach() diff --git a/crates/gpui3/src/elements/stateless.rs b/crates/gpui3/src/elements/stateless.rs index 9726685365..b1cd31a146 100644 --- a/crates/gpui3/src/elements/stateless.rs +++ b/crates/gpui3/src/elements/stateless.rs @@ -1,4 +1,4 @@ -use crate::Element; +use crate::{Bounds, Element, Pixels}; use std::marker::PhantomData; pub struct Stateless, S> { @@ -20,11 +20,11 @@ impl, S: Send + Sync + 'static> Element for Stateless, _: &mut Self::State, frame_state: &mut Self::FrameState, cx: &mut crate::ViewContext, ) -> anyhow::Result<()> { - cx.erase_state(|cx| self.element.paint(layout, &mut (), frame_state, cx)) + cx.erase_state(|cx| self.element.paint(bounds, &mut (), frame_state, cx)) } } diff --git a/crates/gpui3/src/elements/svg.rs b/crates/gpui3/src/elements/svg.rs index dbb4ffb155..4af620ec06 100644 --- a/crates/gpui3/src/elements/svg.rs +++ b/crates/gpui3/src/elements/svg.rs @@ -1,4 +1,4 @@ -use crate::{Element, Layout, LayoutId, Result, SharedString, Style, StyleHelpers, Styled}; +use crate::{Bounds, Element, LayoutId, Pixels, Result, SharedString, Style, StyleHelpers, Styled}; use refineable::RefinementCascade; use std::marker::PhantomData; @@ -41,7 +41,7 @@ impl Element for Svg { fn paint( &mut self, - layout: Layout, + bounds: Bounds, _: &mut Self::State, _: &mut Self::FrameState, cx: &mut crate::ViewContext, @@ -51,7 +51,7 @@ impl Element for Svg { { let fill_color = self.computed_style().fill.and_then(|fill| fill.color()); if let Some((path, fill_color)) = self.path.as_ref().zip(fill_color) { - cx.paint_svg(layout.bounds, layout.order, path.clone(), fill_color)?; + cx.paint_svg(bounds, path.clone(), fill_color)?; } Ok(()) } diff --git a/crates/gpui3/src/elements/text.rs b/crates/gpui3/src/elements/text.rs index 1ed31716fd..b564a3ccd4 100644 --- a/crates/gpui3/src/elements/text.rs +++ b/crates/gpui3/src/elements/text.rs @@ -1,5 +1,5 @@ use crate::{ - AnyElement, Element, IntoAnyElement, Layout, LayoutId, Line, Pixels, Result, Size, ViewContext, + AnyElement, Bounds, Element, IntoAnyElement, LayoutId, Line, Pixels, Result, Size, ViewContext, }; use parking_lot::Mutex; use std::{marker::PhantomData, sync::Arc}; @@ -94,7 +94,7 @@ impl Element for Text { fn paint<'a>( &mut self, - layout: Layout, + bounds: Bounds, _: &mut Self::State, frame_state: &mut Self::FrameState, cx: &mut ViewContext, @@ -111,8 +111,7 @@ impl Element for Text { } // todo!("We haven't added visible bounds to the new element system yet, so this is a placeholder."); - let visible_bounds = layout.bounds; - line.paint(&layout, visible_bounds, line_height, cx)?; + line.paint(bounds, bounds, line_height, cx)?; Ok(()) } diff --git a/crates/gpui3/src/geometry.rs b/crates/gpui3/src/geometry.rs index d60866816d..93d7dd2a08 100644 --- a/crates/gpui3/src/geometry.rs +++ b/crates/gpui3/src/geometry.rs @@ -2,7 +2,7 @@ use core::fmt::Debug; use derive_more::{Add, AddAssign, Div, Mul, Sub, SubAssign}; use refineable::Refineable; use std::{ - cmp, + cmp, fmt, ops::{Add, AddAssign, Div, Mul, MulAssign, Sub, SubAssign}, }; @@ -128,7 +128,7 @@ impl Clone for Point { } } -#[derive(Refineable, Default, Clone, Copy, Debug, PartialEq, Div, Hash)] +#[derive(Refineable, Default, Clone, Copy, PartialEq, Div, Hash)] #[refineable(debug)] #[repr(C)] pub struct Size { @@ -199,11 +199,17 @@ impl, S: Clone> MulAssign for Size { impl Eq for Size {} -impl From>> for Size> { - fn from(size: Size>) -> Self { +impl Debug for Size { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Size {{ {:?} × {:?} }}", self.width, self.height) + } +} + +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 +263,26 @@ 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 + } + + pub fn dilate(&mut self, amount: T) { + self.origin.x = self.origin.x.clone() - amount.clone(); + self.origin.y = self.origin.y.clone() - amount.clone(); + let double_amount = amount.clone() + amount; + self.size.width = self.size.width.clone() + double_amount.clone(); + self.size.height = self.size.height.clone() + double_amount; + } +} + impl + Sub> Bounds { pub fn intersect(&self, other: &Self) -> Self { let upper_left = self.origin.max(&other.origin); @@ -316,6 +342,13 @@ impl> Bounds { y: self.origin.y.clone() + self.size.height.clone(), } } + + pub fn lower_left(&self) -> Point { + Point { + x: self.origin.x.clone(), + y: self.origin.y.clone() + self.size.height.clone(), + } + } } impl> Bounds { @@ -448,6 +481,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)] @@ -459,8 +503,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), @@ -587,7 +631,7 @@ impl From for Pixels { } impl Debug for Pixels { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{} px", self.0) } } @@ -622,8 +666,8 @@ impl DevicePixels { } } -impl std::fmt::Debug for DevicePixels { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Debug for DevicePixels { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{} px (device)", self.0) } } @@ -681,7 +725,7 @@ impl ScaledPixels { impl Eq for ScaledPixels {} impl Debug for ScaledPixels { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{} px (scaled)", self.0) } } @@ -698,6 +742,34 @@ impl From for ScaledPixels { } } +impl From for f64 { + fn from(scaled_pixels: ScaledPixels) -> Self { + scaled_pixels.0 as f64 + } +} + +#[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 fmt::Formatter<'_>) -> 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); @@ -710,7 +782,7 @@ impl Mul for Rems { } impl Debug for Rems { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{} rem", self.0) } } @@ -778,7 +850,7 @@ impl DefiniteLength { } impl Debug for DefiniteLength { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { DefiniteLength::Absolute(length) => Debug::fmt(length, f), DefiniteLength::Fraction(fract) => write!(f, "{}%", (fract * 100.0) as i32), @@ -818,7 +890,7 @@ pub enum Length { } impl Debug for Length { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Length::Definite(definite_length) => write!(f, "{:?}", definite_length), Length::Auto => write!(f, "auto"), @@ -964,3 +1036,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 53d2fd89f8..92a8043b7c 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 9feec5a9c5..53a35fadcc 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::*; @@ -44,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); @@ -54,8 +53,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 +96,22 @@ 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; } -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,16 @@ pub trait PlatformDispatcher: Send + Sync { fn dispatch_on_main_thread(&self, task: Runnable); } +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 { fn add_fonts(&self, fonts: &[Arc>]) -> Result<()>; fn all_font_families(&self) -> Vec; @@ -266,9 +274,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 +283,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 +300,7 @@ impl Default for WindowOptions { show: true, kind: WindowKind::Normal, is_movable: true, - screen: None, + display_id: None, } } } @@ -332,7 +337,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 dcbf8f887f..462aa209cc 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_linker; 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_linker::*; 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)) - } - - 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 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 0000000000..dc064293f3 --- /dev/null +++ b/crates/gpui3/src/platform/mac/display.rs @@ -0,0 +1,101 @@ +use crate::{point, size, Bounds, DisplayId, GlobalPixels, PlatformDisplay}; +use core_graphics::{ + display::{CGDirectDisplayID, CGDisplayBounds, 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_size = unsafe { CGDisplayBounds(MacDisplay::primary().id().0) }.size; + + Bounds { + origin: point( + GlobalPixels(rect.origin.x 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), + 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 { + let native_bounds = CGDisplayBounds(self.0); + display_bounds_from_native(native_bounds) + } + } +} diff --git a/crates/gpui3/src/platform/mac/display_linker.rs b/crates/gpui3/src/platform/mac/display_linker.rs new file mode 100644 index 0000000000..6cafb6a84b --- /dev/null +++ b/crates/gpui3/src/platform/mac/display_linker.rs @@ -0,0 +1,276 @@ +use std::{ + ffi::c_void, + mem, + sync::{Arc, Weak}, +}; + +use crate::{DisplayId, PlatformDisplayLinker}; +use collections::HashMap; +use parking_lot::Mutex; +pub use sys::CVTimeStamp as VideoTimestamp; + +pub struct MacDisplayLinker { + links: Mutex>, +} + +struct MacDisplayLink { + system_link: Mutex, + _output_callback: Arc, +} + +unsafe impl Send for MacDisplayLink {} + +impl MacDisplayLinker { + pub fn new() -> Self { + MacDisplayLinker { + links: Default::default(), + } + } +} + +type OutputCallback = Mutex>; + +impl PlatformDisplayLinker for MacDisplayLinker { + fn set_output_callback( + &self, + display_id: DisplayId, + output_callback: Box, + ) { + if let Some(mut system_link) = unsafe { sys::DisplayLink::on_display(display_id.0) } { + 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) } + + self.links.lock().insert( + display_id, + MacDisplayLink { + _output_callback: callback, + system_link: Mutex::new(system_link), + }, + ); + } else { + log::warn!("DisplayLink could not be obtained for {:?}", display_id); + return; + } + } + + fn start(&self, display_id: DisplayId) { + if let Some(link) = self.links.lock().get_mut(&display_id) { + unsafe { + link.system_link.lock().start(); + } + } else { + log::warn!("No DisplayLink callback registered for {:?}", display_id) + } + } + + fn stop(&self, display_id: DisplayId) { + if let Some(link) = self.links.lock().get_mut(&display_id) { + unsafe { + link.system_link.lock().stop(); + } + } else { + log::warn!("No DisplayLink callback registered for {:?}", display_id) + } + } +} + +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, + user_data: *mut c_void, +) -> i32 { + if let Some((current_time, output_time)) = current_time.as_ref().zip(output_time.as_ref()) { + 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 +} + +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, non_upper_case_globals)] + + 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() + } + } + + #[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. + 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/metal_renderer.rs b/crates/gpui3/src/platform/mac/metal_renderer.rs index 84a5fd5126..9b93a8a561 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, + PrimitiveBatch, Quad, Scene, Shadow, Size, Underline, }; use cocoa::{ base::{NO, YES}, @@ -17,7 +17,9 @@ const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decisio pub struct MetalRenderer { layer: metal::MetalLayer, command_queue: CommandQueue, + shadows_pipeline_state: metal::RenderPipelineState, quads_pipeline_state: metal::RenderPipelineState, + underlines_pipeline_state: metal::RenderPipelineState, monochrome_sprites_pipeline_state: metal::RenderPipelineState, polychrome_sprites_pipeline_state: metal::RenderPipelineState, unit_vertices: metal::Buffer, @@ -82,6 +84,14 @@ impl MetalRenderer { MTLResourceOptions::StorageModeManaged, ); + let shadows_pipeline_state = build_pipeline_state( + &device, + &library, + "shadows", + "shadow_vertex", + "shadow_fragment", + PIXEL_FORMAT, + ); let quads_pipeline_state = build_pipeline_state( &device, &library, @@ -90,6 +100,14 @@ impl MetalRenderer { "quad_fragment", PIXEL_FORMAT, ); + let underlines_pipeline_state = build_pipeline_state( + &device, + &library, + "underlines", + "underline_vertex", + "underline_fragment", + PIXEL_FORMAT, + ); let monochrome_sprites_pipeline_state = build_pipeline_state( &device, &library, @@ -113,7 +131,9 @@ impl MetalRenderer { Self { layer, command_queue, + shadows_pipeline_state, quads_pipeline_state, + underlines_pipeline_state, monochrome_sprites_pipeline_state, polychrome_sprites_pipeline_state, unit_vertices, @@ -131,8 +151,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( @@ -174,41 +192,50 @@ impl MetalRenderer { }); let mut instance_offset = 0; - for layer in scene.layers() { - for batch in layer.batches() { - match batch { - crate::PrimitiveBatch::Quads(quads) => { - self.draw_quads( - quads, - &mut instance_offset, - viewport_size, - command_encoder, - ); - } - crate::PrimitiveBatch::MonochromeSprites { + for batch in scene.batches() { + match batch { + PrimitiveBatch::Shadows(shadows) => { + self.draw_shadows( + shadows, + &mut instance_offset, + viewport_size, + command_encoder, + ); + } + PrimitiveBatch::Quads(quads) => { + self.draw_quads(quads, &mut instance_offset, viewport_size, command_encoder); + } + PrimitiveBatch::Underlines(underlines) => { + self.draw_underlines( + underlines, + &mut instance_offset, + viewport_size, + command_encoder, + ); + } + PrimitiveBatch::MonochromeSprites { + texture_id, + sprites, + } => { + self.draw_monochrome_sprites( texture_id, sprites, - } => { - self.draw_monochrome_sprites( - texture_id, - sprites, - &mut instance_offset, - viewport_size, - command_encoder, - ); - } - crate::PrimitiveBatch::PolychromeSprites { + &mut instance_offset, + viewport_size, + command_encoder, + ); + } + PrimitiveBatch::PolychromeSprites { + texture_id, + sprites, + } => { + self.draw_polychrome_sprites( texture_id, sprites, - } => { - self.draw_polychrome_sprites( - texture_id, - sprites, - &mut instance_offset, - viewport_size, - command_encoder, - ); - } + &mut instance_offset, + viewport_size, + command_encoder, + ); } } } @@ -225,6 +252,66 @@ impl MetalRenderer { drawable.present(); } + 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_quads( &mut self, quads: &[Quad], @@ -281,6 +368,66 @@ impl MetalRenderer { *offset = next_offset; } + fn draw_underlines( + &mut self, + underlines: &[Underline], + offset: &mut usize, + viewport_size: Size, + command_encoder: &metal::RenderCommandEncoderRef, + ) { + if underlines.is_empty() { + return; + } + align_offset(offset); + + command_encoder.set_render_pipeline_state(&self.underlines_pipeline_state); + command_encoder.set_vertex_buffer( + UnderlineInputIndex::Vertices as u64, + Some(&self.unit_vertices), + 0, + ); + command_encoder.set_vertex_buffer( + UnderlineInputIndex::Underlines as u64, + Some(&self.instances), + *offset as u64, + ); + command_encoder.set_fragment_buffer( + UnderlineInputIndex::Underlines as u64, + Some(&self.instances), + *offset as u64, + ); + + command_encoder.set_vertex_bytes( + UnderlineInputIndex::ViewportSize as u64, + mem::size_of_val(&viewport_size) as u64, + &viewport_size as *const Size as *const _, + ); + + let quad_bytes_len = mem::size_of::() * underlines.len(); + let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) }; + unsafe { + ptr::copy_nonoverlapping( + underlines.as_ptr() as *const u8, + buffer_contents, + quad_bytes_len, + ); + } + + let next_offset = *offset + quad_bytes_len; + assert!( + next_offset <= INSTANCE_BUFFER_SIZE, + "instance buffer exhausted" + ); + + command_encoder.draw_primitives_instanced( + metal::MTLPrimitiveType::Triangle, + 0, + 6, + underlines.len() as u64, + ); + *offset = next_offset; + } + fn draw_monochrome_sprites( &mut self, texture_id: AtlasTextureId, @@ -464,6 +611,13 @@ fn align_offset(offset: &mut usize) { *offset = ((*offset + 255) / 256) * 256; } +#[repr(C)] +enum ShadowInputIndex { + Vertices = 0, + Shadows = 1, + ViewportSize = 2, +} + #[repr(C)] enum QuadInputIndex { Vertices = 0, @@ -471,6 +625,13 @@ enum QuadInputIndex { ViewportSize = 2, } +#[repr(C)] +enum UnderlineInputIndex { + Vertices = 0, + Underlines = 1, + ViewportSize = 2, +} + #[repr(C)] enum SpriteInputIndex { Vertices = 0, diff --git a/crates/gpui3/src/platform/mac/platform.rs b/crates/gpui3/src/platform/mac/platform.rs index d4110a114e..a3f6fbfbe6 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, Event, Executor, MacDispatcher, MacScreen, - MacTextSystem, MacWindow, PathPromptOptions, Platform, PlatformScreen, PlatformTextSystem, - PlatformWindow, Result, ScreenId, SemanticVersion, WindowOptions, + AnyWindowHandle, ClipboardItem, CursorStyle, DisplayId, Event, Executor, MacDispatcher, + 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() } @@ -455,21 +460,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 +741,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 +804,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 048b9dd6fd..0000000000 --- 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/shaders.metal b/crates/gpui3/src/platform/mac/shaders.metal index b4614bef4d..33670c02bb 100644 --- a/crates/gpui3/src/platform/mac/shaders.metal +++ b/crates/gpui3/src/platform/mac/shaders.metal @@ -11,6 +11,10 @@ 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]]; @@ -29,8 +33,8 @@ vertex QuadVertexOutput quad_vertex(uint unit_vertex_id [[vertex_id]], [[buffer(QuadInputIndex_ViewportSize)]]) { 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); + float4 device_position = to_device_position( + unit_vertex, quad.bounds, 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, @@ -109,6 +113,133 @@ 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; + // 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, shadow.content_mask.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.bounds.origin.y); + float2 size = float2(shadow.bounds.size.width, shadow.bounds.size.height); + 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 UnderlineVertexOutput { + float4 position [[position]]; + float4 color [[flat]]; + uint underline_id [[flat]]; +}; + +vertex UnderlineVertexOutput underline_vertex( + uint unit_vertex_id [[vertex_id]], uint underline_id [[instance_id]], + constant float2 *unit_vertices [[buffer(UnderlineInputIndex_Vertices)]], + constant Underline *underlines [[buffer(UnderlineInputIndex_Underlines)]], + constant Size_DevicePixels *viewport_size + [[buffer(ShadowInputIndex_ViewportSize)]]) { + float2 unit_vertex = unit_vertices[unit_vertex_id]; + Underline underline = underlines[underline_id]; + float4 device_position = + to_device_position(unit_vertex, underline.bounds, + underline.content_mask.bounds, viewport_size); + float4 color = hsla_to_rgba(underline.color); + return UnderlineVertexOutput{device_position, color, underline_id}; +} + +fragment float4 underline_fragment(UnderlineVertexOutput input [[stage_in]], + constant Underline *underlines + [[buffer(UnderlineInputIndex_Underlines)]]) { + Underline underline = underlines[input.underline_id]; + if (underline.wavy) { + float half_thickness = underline.thickness * 0.5; + float2 origin = + float2(underline.bounds.origin.x, underline.bounds.origin.y); + float2 st = ((input.position.xy - origin) / underline.bounds.size.height) - + float2(0., 0.5); + float frequency = (M_PI_F * (3. * underline.thickness)) / 8.; + float amplitude = 1. / (2. * underline.thickness); + float sine = sin(st.x * frequency) * amplitude; + float dSine = cos(st.x * frequency) * amplitude * frequency; + float distance = (st.y - sine) / sqrt(1. + dSine * dSine); + float distance_in_pixels = distance * underline.bounds.size.height; + float distance_from_top_border = distance_in_pixels - half_thickness; + float distance_from_bottom_border = distance_in_pixels + half_thickness; + float alpha = saturate( + 0.5 - max(-distance_from_bottom_border, distance_from_top_border)); + return input.color * float4(1., 1., 1., alpha); + } else { + return input.color; + } +} + struct MonochromeSpriteVertexOutput { float4 position [[position]]; float2 tile_position; @@ -127,9 +258,10 @@ vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex( float2 unit_vertex = unit_vertices[unit_vertex_id]; MonochromeSprite sprite = sprites[sprite_id]; - // Don't apply content mask at the vertex level because we don't have time to make sampling from the texture match the mask. - float4 device_position = to_device_position( - unit_vertex, sprite.bounds, sprite.bounds, viewport_size); + // Don't apply content mask at the vertex level because we don't have time + // to make sampling from the texture match the mask. + float4 device_position = to_device_position(unit_vertex, sprite.bounds, + sprite.bounds, viewport_size); float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size); float4 color = hsla_to_rgba(sprite.color); return MonochromeSpriteVertexOutput{device_position, tile_position, color, @@ -145,11 +277,8 @@ fragment float4 monochrome_sprite_fragment( min_filter::linear); float4 sample = atlas_texture.sample(atlas_texture_sampler, input.tile_position); - float clip_distance = quad_sdf( - input.position.xy, - sprite.content_mask.bounds, - Corners_ScaledPixels { 0., 0., 0., 0. } - ); + float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds, + Corners_ScaledPixels{0., 0., 0., 0.}); float4 color = input.color; color.a *= sample.a * saturate(0.5 - clip_distance); return color; @@ -172,9 +301,10 @@ vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex( float2 unit_vertex = unit_vertices[unit_vertex_id]; PolychromeSprite sprite = sprites[sprite_id]; - // Don't apply content mask at the vertex level because we don't have time to make sampling from the texture match the mask. - float4 device_position = to_device_position( - unit_vertex, sprite.bounds, sprite.bounds, viewport_size); + // Don't apply content mask at the vertex level because we don't have time + // to make sampling from the texture match the mask. + float4 device_position = to_device_position(unit_vertex, sprite.bounds, + sprite.bounds, viewport_size); float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size); return PolychromeSpriteVertexOutput{device_position, tile_position, sprite_id}; @@ -189,8 +319,10 @@ fragment float4 polychrome_sprite_fragment( min_filter::linear); float4 sample = atlas_texture.sample(atlas_texture_sampler, input.tile_position); - float quad_distance = quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii); - float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds, Corners_ScaledPixels { 0., 0., 0., 0. }); + float quad_distance = + quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii); + float clip_distance = quad_sdf(input.position.xy, sprite.content_mask.bounds, + Corners_ScaledPixels{0., 0., 0., 0.}); float distance = max(quad_distance, clip_distance); float4 color = sample; @@ -307,3 +439,27 @@ 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/platform/mac/window.rs b/crates/gpui3/src/platform/mac/window.rs index 080aa74350..b16e85f08d 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 = 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; + } + } + 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 e170e21e95..88ec46e833 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; @@ -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!() } @@ -47,11 +51,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/gpui3/src/scene.rs b/crates/gpui3/src/scene.rs index 3426eefc9e..8cabda9f9f 100644 --- a/crates/gpui3/src/scene.rs +++ b/crates/gpui3/src/scene.rs @@ -1,18 +1,27 @@ -use std::{iter::Peekable, mem, slice}; - -use super::{Bounds, Hsla, Point}; -use crate::{AtlasTextureId, AtlasTile, Corners, Edges, ScaledContentMask, ScaledPixels}; +use crate::{ + AtlasTextureId, AtlasTile, Bounds, Corners, Edges, Hsla, Point, ScaledContentMask, ScaledPixels, +}; use collections::BTreeMap; +use etagere::euclid::{Point3D, Vector3D}; +use plane_split::{BspSplitter, Polygon as BspPolygon}; use smallvec::SmallVec; +use std::{iter::Peekable, mem, slice}; // Exported to metal pub type PointF = Point; -pub type LayerId = SmallVec<[u32; 16]>; +pub type StackingOrder = SmallVec<[u32; 16]>; +pub type LayerId = u32; +pub type DrawOrder = u32; #[derive(Debug)] pub struct Scene { pub(crate) scale_factor: f32, - pub(crate) layers: BTreeMap, + pub(crate) layers: BTreeMap, + pub shadows: Vec, + pub quads: Vec, + pub underlines: Vec, + pub monochrome_sprites: Vec, + pub polychrome_sprites: Vec, } impl Scene { @@ -20,6 +29,11 @@ impl Scene { Scene { scale_factor, layers: BTreeMap::new(), + shadows: Vec::new(), + quads: Vec::new(), + underlines: Vec::new(), + monochrome_sprites: Vec::new(), + polychrome_sprites: Vec::new(), } } @@ -27,47 +41,125 @@ impl Scene { Scene { scale_factor: self.scale_factor, layers: mem::take(&mut self.layers), + shadows: mem::take(&mut self.shadows), + quads: mem::take(&mut self.quads), + underlines: mem::take(&mut self.underlines), + monochrome_sprites: mem::take(&mut self.monochrome_sprites), + polychrome_sprites: mem::take(&mut self.polychrome_sprites), } } - pub fn insert(&mut self, stacking_order: LayerId, primitive: impl Into) { - let layer = self.layers.entry(stacking_order).or_default(); - + pub fn insert(&mut self, layer_id: StackingOrder, primitive: impl Into) { + let next_id = self.layers.len() as LayerId; + let layer_id = *self.layers.entry(layer_id).or_insert(next_id); let primitive = primitive.into(); match primitive { - Primitive::Quad(quad) => { - layer.quads.push(quad); + Primitive::Shadow(mut shadow) => { + shadow.order = layer_id; + self.shadows.push(shadow); } - Primitive::MonochromeSprite(sprite) => { - layer.monochrome_sprites.push(sprite); + Primitive::Quad(mut quad) => { + quad.order = layer_id; + self.quads.push(quad); } - Primitive::PolychromeSprite(sprite) => { - layer.polychrome_sprites.push(sprite); + Primitive::Underline(mut underline) => { + underline.order = layer_id; + self.underlines.push(underline); + } + Primitive::MonochromeSprite(mut sprite) => { + sprite.order = layer_id; + self.monochrome_sprites.push(sprite); + } + Primitive::PolychromeSprite(mut sprite) => { + sprite.order = layer_id; + self.polychrome_sprites.push(sprite); } } } - pub(crate) fn layers(&mut self) -> impl Iterator { - self.layers.values_mut() - } -} + pub(crate) fn batches(&mut self) -> impl Iterator { + // Map each layer id to a float between 0. and 1., with 1. closer to the viewer. + let mut layer_z_values = vec![0.; self.layers.len()]; + for (ix, layer_id) in self.layers.values().enumerate() { + layer_z_values[*layer_id as usize] = ix as f32 / self.layers.len() as f32; + } -#[derive(Debug, Default)] -pub(crate) struct SceneLayer { - pub quads: Vec, - pub monochrome_sprites: Vec, - pub polychrome_sprites: Vec, -} + // Add all primitives to the BSP splitter to determine draw order + // todo!("reuse the same splitter") + let mut splitter = BspSplitter::new(); -impl SceneLayer { - pub fn batches(&mut self) -> impl Iterator { + for (ix, shadow) in self.shadows.iter().enumerate() { + let z = layer_z_values[shadow.order as LayerId as usize]; + splitter.add(shadow.bounds.to_bsp_polygon(z, (PrimitiveKind::Shadow, ix))); + } + + for (ix, quad) in self.quads.iter().enumerate() { + let z = layer_z_values[quad.order as LayerId as usize]; + splitter.add(quad.bounds.to_bsp_polygon(z, (PrimitiveKind::Quad, ix))); + } + + for (ix, underline) in self.underlines.iter().enumerate() { + let z = layer_z_values[underline.order as LayerId as usize]; + splitter.add( + underline + .bounds + .to_bsp_polygon(z, (PrimitiveKind::Underline, ix)), + ); + } + + for (ix, monochrome_sprite) in self.monochrome_sprites.iter().enumerate() { + let z = layer_z_values[monochrome_sprite.order as LayerId as usize]; + splitter.add( + monochrome_sprite + .bounds + .to_bsp_polygon(z, (PrimitiveKind::MonochromeSprite, ix)), + ); + } + + for (ix, polychrome_sprite) in self.polychrome_sprites.iter().enumerate() { + let z = layer_z_values[polychrome_sprite.order as LayerId as usize]; + splitter.add( + polychrome_sprite + .bounds + .to_bsp_polygon(z, (PrimitiveKind::PolychromeSprite, ix)), + ); + } + + // Sort all polygons, then reassign the order field of each primitive to `draw_order` + // We need primitives to be repr(C), hence the weird reuse of the order field for two different types. + for (draw_order, polygon) in splitter.sort(Vector3D::new(0., 0., 1.)).iter().enumerate() { + match polygon.anchor { + (PrimitiveKind::Shadow, ix) => self.shadows[ix].order = draw_order as DrawOrder, + (PrimitiveKind::Quad, ix) => self.quads[ix].order = draw_order as DrawOrder, + (PrimitiveKind::Underline, ix) => { + self.underlines[ix].order = draw_order as DrawOrder + } + (PrimitiveKind::MonochromeSprite, ix) => { + self.monochrome_sprites[ix].order = draw_order as DrawOrder + } + (PrimitiveKind::PolychromeSprite, ix) => { + self.polychrome_sprites[ix].order = draw_order as DrawOrder + } + } + } + + // Sort the primitives + self.shadows.sort_unstable(); self.quads.sort_unstable(); + self.underlines.sort_unstable(); self.monochrome_sprites.sort_unstable(); self.polychrome_sprites.sort_unstable(); + BatchIterator { + shadows: &self.shadows, + shadows_start: 0, + shadows_iter: self.shadows.iter().peekable(), quads: &self.quads, quads_start: 0, quads_iter: self.quads.iter().peekable(), + underlines: &self.underlines, + underlines_start: 0, + underlines_iter: self.underlines.iter().peekable(), monochrome_sprites: &self.monochrome_sprites, monochrome_sprites_start: 0, monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(), @@ -82,6 +174,12 @@ struct BatchIterator<'a> { quads: &'a [Quad], quads_start: usize, quads_iter: Peekable>, + shadows: &'a [Shadow], + shadows_start: usize, + shadows_iter: Peekable>, + underlines: &'a [Underline], + underlines_start: usize, + underlines_iter: Peekable>, monochrome_sprites: &'a [MonochromeSprite], monochrome_sprites_start: usize, monochrome_sprites_iter: Peekable>, @@ -94,50 +192,92 @@ impl<'a> Iterator for BatchIterator<'a> { type Item = PrimitiveBatch<'a>; fn next(&mut self) -> Option { - let mut kinds_and_orders = [ - (PrimitiveKind::Quad, self.quads_iter.peek().map(|q| q.order)), + let mut orders_and_kinds = [ ( - PrimitiveKind::MonochromeSprite, - self.monochrome_sprites_iter.peek().map(|s| s.order), + self.shadows_iter.peek().map(|s| s.order), + PrimitiveKind::Shadow, + ), + (self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad), + ( + self.underlines_iter.peek().map(|u| u.order), + PrimitiveKind::Underline, + ), + ( + self.monochrome_sprites_iter.peek().map(|s| s.order), + PrimitiveKind::MonochromeSprite, ), ( - PrimitiveKind::PolychromeSprite, self.polychrome_sprites_iter.peek().map(|s| s.order), + PrimitiveKind::PolychromeSprite, ), ]; - kinds_and_orders.sort_by_key(|(_, order)| order.unwrap_or(u32::MAX)); + orders_and_kinds.sort_by_key(|(order, kind)| (order.unwrap_or(u32::MAX), *kind)); - let first = kinds_and_orders[0]; - let second = kinds_and_orders[1]; - let (batch_kind, max_order) = if first.1.is_some() { - (first.0, second.1.unwrap_or(u32::MAX)) + let first = orders_and_kinds[0]; + let second = orders_and_kinds[1]; + let (batch_kind, max_order) = if first.0.is_some() { + (first.1, second.0.unwrap_or(u32::MAX)) } else { return None; }; match batch_kind { + PrimitiveKind::Shadow => { + let shadows_start = self.shadows_start; + let mut shadows_end = shadows_start; + while self + .shadows_iter + .next_if(|shadow| shadow.order <= max_order) + .is_some() + { + shadows_end += 1; + } + self.shadows_start = shadows_end; + Some(PrimitiveBatch::Shadows( + &self.shadows[shadows_start..shadows_end], + )) + } PrimitiveKind::Quad => { let quads_start = self.quads_start; - let quads_end = quads_start - + self - .quads_iter - .by_ref() - .take_while(|quad| quad.order <= max_order) - .count(); + let mut quads_end = quads_start; + while self + .quads_iter + .next_if(|quad| quad.order <= max_order) + .is_some() + { + quads_end += 1; + } self.quads_start = quads_end; Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end])) } + PrimitiveKind::Underline => { + let underlines_start = self.underlines_start; + let mut underlines_end = underlines_start; + while self + .underlines_iter + .next_if(|underline| underline.order <= max_order) + .is_some() + { + underlines_end += 1; + } + self.underlines_start = underlines_end; + Some(PrimitiveBatch::Underlines( + &self.underlines[underlines_start..underlines_end], + )) + } PrimitiveKind::MonochromeSprite => { let texture_id = self.monochrome_sprites_iter.peek().unwrap().tile.texture_id; let sprites_start = self.monochrome_sprites_start; - let sprites_end = sprites_start - + self - .monochrome_sprites_iter - .by_ref() - .take_while(|sprite| { - sprite.order <= max_order && sprite.tile.texture_id == texture_id - }) - .count(); + let mut sprites_end = sprites_start; + while self + .monochrome_sprites_iter + .next_if(|sprite| { + sprite.order <= max_order && sprite.tile.texture_id == texture_id + }) + .is_some() + { + sprites_end += 1; + } self.monochrome_sprites_start = sprites_end; Some(PrimitiveBatch::MonochromeSprites { texture_id, @@ -147,14 +287,16 @@ impl<'a> Iterator for BatchIterator<'a> { PrimitiveKind::PolychromeSprite => { let texture_id = self.polychrome_sprites_iter.peek().unwrap().tile.texture_id; let sprites_start = self.polychrome_sprites_start; - let sprites_end = sprites_start - + self - .polychrome_sprites_iter - .by_ref() - .take_while(|sprite| { - sprite.order <= max_order && sprite.tile.texture_id == texture_id - }) - .count(); + let mut sprites_end = self.polychrome_sprites_start; + while self + .polychrome_sprites_iter + .next_if(|sprite| { + sprite.order <= max_order && sprite.tile.texture_id == texture_id + }) + .is_some() + { + sprites_end += 1; + } self.polychrome_sprites_start = sprites_end; Some(PrimitiveBatch::PolychromeSprites { texture_id, @@ -165,22 +307,30 @@ impl<'a> Iterator for BatchIterator<'a> { } } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)] pub enum PrimitiveKind { + Shadow, + #[default] Quad, + Underline, MonochromeSprite, PolychromeSprite, } #[derive(Clone, Debug)] pub enum Primitive { + Shadow(Shadow), Quad(Quad), + Underline(Underline), MonochromeSprite(MonochromeSprite), PolychromeSprite(PolychromeSprite), } +#[derive(Debug)] pub(crate) enum PrimitiveBatch<'a> { + Shadows(&'a [Shadow]), Quads(&'a [Quad]), + Underlines(&'a [Underline]), MonochromeSprites { texture_id: AtlasTextureId, sprites: &'a [MonochromeSprite], @@ -191,35 +341,18 @@ pub(crate) enum PrimitiveBatch<'a> { }, } -#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Default, Debug, Clone, Eq, PartialEq)] #[repr(C)] pub struct Quad { - pub order: u32, + pub order: u32, // Initially a LayerId, then a DrawOrder. 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, 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) @@ -238,6 +371,64 @@ impl From for Primitive { } } +#[derive(Debug, Clone, Eq, PartialEq)] +#[repr(C)] +pub struct Underline { + pub order: u32, + pub bounds: Bounds, + pub content_mask: ScaledContentMask, + pub thickness: ScaledPixels, + pub color: Hsla, + pub wavy: bool, +} + +impl Ord for Underline { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.order.cmp(&other.order) + } +} + +impl PartialOrd for Underline { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl From for Primitive { + fn from(underline: Underline) -> Self { + Primitive::Underline(underline) + } +} + +#[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, +} + +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 { @@ -303,3 +494,76 @@ impl From for Primitive { #[derive(Copy, Clone, Debug)] pub struct AtlasId(pub(crate) usize); + +impl Bounds { + fn to_bsp_polygon(&self, z: f32, anchor: A) -> BspPolygon { + let upper_left = self.origin; + let upper_right = self.upper_right(); + let lower_right = self.lower_right(); + let lower_left = self.lower_left(); + + BspPolygon::from_points( + [ + Point3D::new(upper_left.x.into(), upper_left.y.into(), z as f64), + Point3D::new(upper_right.x.into(), upper_right.y.into(), z as f64), + Point3D::new(lower_right.x.into(), lower_right.y.into(), z as f64), + Point3D::new(lower_left.x.into(), lower_left.y.into(), z as f64), + ], + anchor, + ) + .expect("Polygon should not be empty") + } +} + +#[cfg(test)] +mod tests { + use crate::{point, size}; + + use super::*; + use smallvec::smallvec; + + #[test] + fn test_scene() { + let mut scene = Scene::new(1.0); + assert_eq!(scene.layers.len(), 0); + + scene.insert(smallvec![1], quad()); + scene.insert(smallvec![2], shadow()); + scene.insert(smallvec![3], quad()); + + let mut batches_count = 0; + for _ in scene.batches() { + batches_count += 1; + } + assert_eq!(batches_count, 3); + } + + fn quad() -> Quad { + Quad { + order: 0, + bounds: Bounds { + origin: point(ScaledPixels(0.), ScaledPixels(0.)), + size: size(ScaledPixels(100.), ScaledPixels(100.)), + }, + content_mask: Default::default(), + background: Default::default(), + border_color: Default::default(), + corner_radii: Default::default(), + border_widths: Default::default(), + } + } + + fn shadow() -> Shadow { + Shadow { + order: Default::default(), + bounds: Bounds { + origin: point(ScaledPixels(0.), ScaledPixels(0.)), + size: size(ScaledPixels(100.), ScaledPixels(100.)), + }, + corner_radii: Default::default(), + content_mask: Default::default(), + color: Default::default(), + blur_radius: Default::default(), + } + } +} diff --git a/crates/gpui3/src/style.rs b/crates/gpui3/src/style.rs index 1f87480748..af2b87d0bb 100644 --- a/crates/gpui3/src/style.rs +++ b/crates/gpui3/src/style.rs @@ -1,10 +1,11 @@ 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; +use smallvec::SmallVec; pub use taffy::style::{ AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent, Overflow, Position, @@ -89,10 +90,21 @@ pub struct Style { #[refineable] pub corner_radii: Corners, + /// Box Shadow of the element + pub box_shadow: SmallVec<[BoxShadow; 2]>, + /// 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 { @@ -229,32 +241,55 @@ impl Style { } /// Paints the background of an element styled with this style. - pub fn paint(&self, order: u32, bounds: Bounds, cx: &mut ViewContext) { + pub fn paint(&self, bounds: Bounds, cx: &mut ViewContext) { let rem_size = cx.rem_size(); let scale = cx.scale_factor(); + for shadow in &self.box_shadow { + let content_mask = cx.content_mask(); + let mut shadow_bounds = bounds; + shadow_bounds.origin += shadow.offset; + shadow_bounds.dilate(shadow.spread_radius); + cx.stack(0, |cx| { + let layer_id = cx.current_stacking_order(); + cx.scene().insert( + layer_id, + Shadow { + order: 0, + bounds: shadow_bounds.scale(scale), + content_mask: content_mask.scale(scale), + corner_radii: self + .corner_radii + .to_pixels(shadow_bounds.size, rem_size) + .scale(scale), + color: shadow.color, + blur_radius: shadow.blur_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(); - cx.scene().insert( - layer_id, - Quad { + let content_mask = cx.content_mask(); + cx.stack(1, |cx| { + let order = cx.current_stacking_order(); + cx.scene().insert( 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)), - background: background_color.unwrap_or_default(), - 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)), - }, - ); + Quad { + order: 0, + bounds: bounds.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 + .corner_radii + .to_pixels(bounds.size, rem_size) + .scale(scale), + border_widths: self.border_widths.to_pixels(rem_size).scale(scale), + }, + ); + }); } } @@ -298,6 +333,7 @@ impl Default for Style { fill: None, border_color: None, corner_radii: Corners::default(), + box_shadow: Default::default(), text: TextStyleRefinement::default(), } } @@ -308,7 +344,7 @@ impl Default for Style { pub struct UnderlineStyle { pub thickness: Pixels, pub color: Option, - pub squiggly: bool, + pub wavy: bool, } #[derive(Clone, Debug)] diff --git a/crates/gpui3/src/style_helpers.rs b/crates/gpui3/src/style_helpers.rs index 0557c81ef4..b58da2fbfc 100644 --- a/crates/gpui3/src/style_helpers.rs +++ b/crates/gpui3/src/style_helpers.rs @@ -1,9 +1,11 @@ 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, }; +use smallvec::smallvec; -pub trait StyleHelpers: Sized + Styled