Merge branch 'gpui2' into marshall/gpui2-playground

Marshall Bowers created

Change summary

crates/gpui3/Cargo.toml                         |   3 
crates/gpui3/build.rs                           |   4 
crates/gpui3/src/app.rs                         |  17 
crates/gpui3/src/app/async_context.rs           |  12 
crates/gpui3/src/element.rs                     |  23 
crates/gpui3/src/elements/div.rs                |  27 
crates/gpui3/src/elements/img.rs                |  23 
crates/gpui3/src/elements/stateless.rs          |   6 
crates/gpui3/src/elements/svg.rs                |   6 
crates/gpui3/src/elements/text.rs               |   7 
crates/gpui3/src/geometry.rs                    | 141 ++
crates/gpui3/src/gpui3.rs                       |   1 
crates/gpui3/src/platform.rs                    |  51 
crates/gpui3/src/platform/mac.rs                |  42 
crates/gpui3/src/platform/mac/display.rs        | 101 ++
crates/gpui3/src/platform/mac/display_linker.rs | 276 ++++++
crates/gpui3/src/platform/mac/metal_renderer.rs | 229 ++++
crates/gpui3/src/platform/mac/platform.rs       |  75 
crates/gpui3/src/platform/mac/screen.rs         | 156 ---
crates/gpui3/src/platform/mac/shaders.metal     | 186 +++
crates/gpui3/src/platform/mac/window.rs         |  75 +
crates/gpui3/src/platform/test.rs               |  10 
crates/gpui3/src/scene.rs                       | 422 +++++++-
crates/gpui3/src/style.rs                       |  80 +
crates/gpui3/src/style_helpers.rs               | 168 +++
crates/gpui3/src/taffy.rs                       |  38 
crates/gpui3/src/text_system/line.rs            |  98 -
crates/gpui3/src/view.rs                        |  17 
crates/gpui3/src/window.rs                      | 157 ++
crates/storybook2/src/collab_panel.rs           |   5 
crates/storybook2/src/theme.rs                  |   6 
crates/storybook2/src/themes.rs                 |   4 
crates/storybook2/src/themes/rose_pine.rs       | 843 ++++++++++++++++++
crates/storybook2/src/workspace.rs              |  11 
34 files changed, 2,705 insertions(+), 615 deletions(-)

Detailed changes

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"

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(),

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<dyn HttpClient>,
     ) -> 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<dyn Fn(&mut AppContext) -> bool + Send + Sync + 'static>; 2]>;
+type FrameCallback = Box<dyn FnOnce(&mut WindowContext) + Send>;
 
 pub struct AppContext {
     this: Weak<Mutex<AppContext>>,
     platform: MainThreadOnly<dyn Platform>,
     text_system: Arc<TextSystem>,
     pending_updates: usize,
+    pub(crate) display_linker: Arc<dyn PlatformDisplayLinker>,
+    pub(crate) next_frame_callbacks: HashMap<DisplayId, Vec<FrameCallback>>,
     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),

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<R>(&self, update: impl FnOnce(&mut WindowContext) -> R) -> Result<R> {
+        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 {

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<Pixels>,
         state: &mut Self::State,
         frame_state: &mut Self::FrameState,
         cx: &mut ViewContext<Self::State>,
@@ -90,7 +92,7 @@ enum ElementRenderPhase<S> {
         frame_state: S,
     },
     Painted {
-        layout: Layout,
+        bounds: Bounds<Pixels>,
         frame_state: S,
     },
 }
@@ -130,24 +132,23 @@ impl<E: Element> ElementObject<E::State> for RenderedElement<E> {
                 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,
                 }
             }

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<S: 'static + Send + Sync> Element for Div<S> {
 
     fn paint(
         &mut self,
-        layout: Layout,
+        bounds: Bounds<Pixels>,
         state: &mut S,
         child_layouts: &mut Self::FrameState,
         cx: &mut ViewContext<S>,
     ) -> 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<S: 'static> Div<S> {
 
     fn handle_scroll(
         &mut self,
-        _order: u32,
         bounds: Bounds<Pixels>,
         overflow: Point<Overflow>,
         child_layout_ids: &[LayoutId],
@@ -151,8 +144,8 @@ impl<S: 'static> Div<S> {
         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;

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<S: Send + Sync + 'static> Element for Img<S> {
 
     fn paint(
         &mut self,
-        layout: Layout,
+        bounds: Bounds<Pixels>,
         _: &mut Self::State,
         _: &mut Self::FrameState,
         cx: &mut ViewContext<Self::State>,
     ) -> 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<S: Send + Sync + 'static> Element for Img<S> {
                 .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()

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<E: Element<State = ()>, S> {
@@ -20,11 +20,11 @@ impl<E: Element<State = ()>, S: Send + Sync + 'static> Element for Stateless<E,
 
     fn paint(
         &mut self,
-        layout: crate::Layout,
+        bounds: Bounds<Pixels>,
         _: &mut Self::State,
         frame_state: &mut Self::FrameState,
         cx: &mut crate::ViewContext<Self::State>,
     ) -> 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))
     }
 }

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<S: 'static> Element for Svg<S> {
 
     fn paint(
         &mut self,
-        layout: Layout,
+        bounds: Bounds<Pixels>,
         _: &mut Self::State,
         _: &mut Self::FrameState,
         cx: &mut crate::ViewContext<S>,
@@ -51,7 +51,7 @@ impl<S: 'static> Element for Svg<S> {
     {
         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(())
     }

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<S: 'static> Element for Text<S> {
 
     fn paint<'a>(
         &mut self,
-        layout: Layout,
+        bounds: Bounds<Pixels>,
         _: &mut Self::State,
         frame_state: &mut Self::FrameState,
         cx: &mut ViewContext<S>,
@@ -111,8 +111,7 @@ impl<S: 'static> Element for Text<S> {
         }
 
         // 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(())
     }

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<T: Clone + Debug> Clone for Point<T> {
     }
 }
 
-#[derive(Refineable, Default, Clone, Copy, Debug, PartialEq, Div, Hash)]
+#[derive(Refineable, Default, Clone, Copy, PartialEq, Div, Hash)]
 #[refineable(debug)]
 #[repr(C)]
 pub struct Size<T: Clone + Debug> {
@@ -199,11 +199,17 @@ impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Size<T> {
 
 impl<T: Eq + Debug + Clone> Eq for Size<T> {}
 
-impl From<Size<Option<Pixels>>> for Size<Option<f32>> {
-    fn from(size: Size<Option<Pixels>>) -> Self {
+impl<T: Clone + Debug> Debug for Size<T> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "Size {{ {:?} Γ— {:?} }}", self.width, self.height)
+    }
+}
+
+impl From<Size<Pixels>> for Size<GlobalPixels> {
+    fn from(size: Size<Pixels>) -> Self {
         Size {
-            width: size.width.map(|p| p.0 as f32),
-            height: size.height.map(|p| p.0 as f32),
+            width: GlobalPixels(size.width.0),
+            height: GlobalPixels(size.height.0),
         }
     }
 }
@@ -257,6 +263,26 @@ impl<T: Clone + Debug + Sub<Output = T>> Bounds<T> {
     }
 }
 
+impl<T: Clone + Debug + PartialOrd + Add<T, Output = T> + Sub<Output = T>> Bounds<T> {
+    pub fn intersects(&self, other: &Bounds<T>) -> bool {
+        let my_lower_right = self.lower_right();
+        let their_lower_right = other.lower_right();
+
+        self.origin.x < their_lower_right.x
+            && my_lower_right.x > other.origin.x
+            && self.origin.y < their_lower_right.y
+            && my_lower_right.y > other.origin.y
+    }
+
+    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<T: Clone + Debug + PartialOrd + Add<T, Output = T> + Sub<Output = T>> Bounds<T> {
     pub fn intersect(&self, other: &Self) -> Self {
         let upper_left = self.origin.max(&other.origin);
@@ -316,6 +342,13 @@ impl<T: Clone + Debug + Add<T, Output = T>> Bounds<T> {
             y: self.origin.y.clone() + self.size.height.clone(),
         }
     }
+
+    pub fn lower_left(&self) -> Point<T> {
+        Point {
+            x: self.origin.x.clone(),
+            y: self.origin.y.clone() + self.size.height.clone(),
+        }
+    }
 }
 
 impl<T: Clone + Debug + PartialOrd + Add<T, Output = T>> Bounds<T> {
@@ -448,6 +481,17 @@ impl Edges<AbsoluteLength> {
     }
 }
 
+impl Edges<Pixels> {
+    pub fn scale(&self, factor: f32) -> Edges<ScaledPixels> {
+        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<T: Clone + Debug> {
 }
 
 impl Corners<AbsoluteLength> {
-    pub fn to_pixels(&self, bounds: Bounds<Pixels>, rem_size: Pixels) -> Corners<Pixels> {
-        let max = bounds.size.width.max(bounds.size.height) / 2.;
+    pub fn to_pixels(&self, size: Size<Pixels>, rem_size: Pixels) -> Corners<Pixels> {
+        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<f32> 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<DevicePixels> for ScaledPixels {
     }
 }
 
+impl From<ScaledPixels> 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<GlobalPixels> for f64 {
+    fn from(global_pixels: GlobalPixels) -> Self {
+        global_pixels.0 as f64
+    }
+}
+
+impl From<f64> for GlobalPixels {
+    fn from(global_pixels: f64) -> Self {
+        GlobalPixels(global_pixels as f32)
+    }
+}
+
 #[derive(Clone, Copy, Default, Add, Sub, Mul, Div)]
 pub struct Rems(f32);
 
@@ -710,7 +782,7 @@ impl Mul<Pixels> 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<T: IsZero + Debug + Clone> IsZero for Corners<T> {
             && self.bottom_left.is_zero()
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_bounds_intersects() {
+        let bounds1 = Bounds {
+            origin: Point { x: 0.0, y: 0.0 },
+            size: Size {
+                width: 5.0,
+                height: 5.0,
+            },
+        };
+        let bounds2 = Bounds {
+            origin: Point { x: 4.0, y: 4.0 },
+            size: Size {
+                width: 5.0,
+                height: 5.0,
+            },
+        };
+        let bounds3 = Bounds {
+            origin: Point { x: 10.0, y: 10.0 },
+            size: Size {
+                width: 5.0,
+                height: 5.0,
+            },
+        };
+
+        // Test Case 1: Intersecting bounds
+        assert_eq!(bounds1.intersects(&bounds2), true);
+
+        // Test Case 2: Non-Intersecting bounds
+        assert_eq!(bounds1.intersects(&bounds3), false);
+
+        // Test Case 3: Bounds intersecting with themselves
+        assert_eq!(bounds1.intersects(&bounds1), true);
+    }
+}

crates/gpui3/src/gpui3.rs πŸ”—

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

crates/gpui3/src/platform.rs πŸ”—

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

crates/gpui3/src/platform/mac.rs πŸ”—

@@ -1,17 +1,18 @@
 ///! Macos screen have a y axis that goings up from the bottom of the screen and
 ///! an origin at the bottom left of the main display.
 mod dispatcher;
+mod display;
+mod display_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<Pixels> {
-        size(px(self.size.width as f32), px(self.size.height as f32))
+impl From<NSRect> for Size<Pixels> {
+    fn from(rect: NSRect) -> Self {
+        let NSSize { width, height } = rect.size;
+        size(width.into(), height.into())
     }
+}
 
-    fn intersects(&self, other: Self) -> bool {
-        self.size.width > 0.
-            && self.size.height > 0.
-            && other.size.width > 0.
-            && other.size.height > 0.
-            && self.origin.x <= other.origin.x + other.size.width
-            && self.origin.x + self.size.width >= other.origin.x
-            && self.origin.y <= other.origin.y + other.size.height
-            && self.origin.y + self.size.height >= other.origin.y
+impl From<NSRect> for Size<GlobalPixels> {
+    fn from(rect: NSRect) -> Self {
+        let NSSize { width, height } = rect.size;
+        size(width.into(), height.into())
     }
 }
 
+// impl NSRectExt for NSRect {
+//     fn intersects(&self, other: Self) -> bool {
+//         self.size.width > 0.
+//             && self.size.height > 0.
+//             && other.size.width > 0.
+//             && other.size.height > 0.
+//             && self.origin.x <= other.origin.x + other.size.width
+//             && self.origin.x + self.size.width >= other.origin.x
+//             && self.origin.y <= other.origin.y + other.size.height
+//             && self.origin.y + self.size.height >= other.origin.y
+//     }
+// }
+
 // todo!
 #[allow(unused)]
 unsafe fn ns_url_to_path(url: id) -> crate::Result<PathBuf> {

crates/gpui3/src/platform/mac/display.rs πŸ”—

@@ -0,0 +1,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> {
+        Self::all().find(|screen| screen.id() == id)
+    }
+
+    /// Get the primary screen - the one with the menu bar, and whose bottom left
+    /// corner is at the origin of the AppKit coordinate system.
+    pub fn primary() -> Self {
+        Self::all().next().unwrap()
+    }
+
+    pub fn all() -> impl Iterator<Item = Self> {
+        unsafe {
+            let mut display_count: u32 = 0;
+            let result = CGGetActiveDisplayList(0, std::ptr::null_mut(), &mut display_count);
+
+            if result == 0 {
+                let mut displays = Vec::with_capacity(display_count as usize);
+                CGGetActiveDisplayList(display_count, displays.as_mut_ptr(), &mut display_count);
+                displays.set_len(display_count as usize);
+
+                displays.into_iter().map(|display| MacDisplay(display))
+            } else {
+                panic!("Failed to get active display list");
+            }
+        }
+    }
+}
+
+/// Convert the given rectangle from CoreGraphics' native coordinate space to GPUI's coordinate space.
+///
+/// CoreGraphics' coordinate space has its origin at the bottom left of the primary screen,
+/// with the Y axis pointing upwards.
+///
+/// Conversely, in GPUI's coordinate system, the origin is placed at the top left of the primary
+/// screen, with the Y axis pointing downwards.
+pub(crate) fn display_bounds_from_native(rect: CGRect) -> Bounds<GlobalPixels> {
+    let primary_screen_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<GlobalPixels>) -> CGRect {
+    let primary_screen_height = MacDisplay::primary().bounds().size.height;
+
+    CGRect::new(
+        &CGPoint::new(
+            bounds.origin.x.into(),
+            (primary_screen_height - bounds.origin.y - bounds.size.height).into(),
+        ),
+        &CGSize::new(bounds.size.width.into(), bounds.size.height.into()),
+    )
+}
+
+impl PlatformDisplay for MacDisplay {
+    fn id(&self) -> DisplayId {
+        DisplayId(self.0)
+    }
+
+    fn as_any(&self) -> &dyn Any {
+        self
+    }
+
+    fn bounds(&self) -> Bounds<GlobalPixels> {
+        unsafe {
+            let native_bounds = CGDisplayBounds(self.0);
+            display_bounds_from_native(native_bounds)
+        }
+    }
+}

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<HashMap<DisplayId, MacDisplayLink>>,
+}
+
+struct MacDisplayLink {
+    system_link: Mutex<sys::DisplayLink>,
+    _output_callback: Arc<OutputCallback>,
+}
+
+unsafe impl Send for MacDisplayLink {}
+
+impl MacDisplayLinker {
+    pub fn new() -> Self {
+        MacDisplayLinker {
+            links: Default::default(),
+        }
+    }
+}
+
+type OutputCallback = Mutex<Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>>;
+
+impl PlatformDisplayLinker for MacDisplayLinker {
+    fn set_output_callback(
+        &self,
+        display_id: DisplayId,
+        output_callback: Box<dyn FnMut(&VideoTimestamp, &VideoTimestamp)>,
+    ) {
+        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<OutputCallback> =
+            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<Self> {
+            let mut display_link: *mut CVDisplayLink = 0 as _;
+            let code = CVDisplayLinkCreateWithActiveCGDisplays(&mut display_link);
+            if code == 0 {
+                Some(DisplayLink::from_ptr(display_link))
+            } else {
+                None
+            }
+        }
+
+        /// Apple docs: [CVDisplayLinkCreateWithCGDisplay](https://developer.apple.com/documentation/corevideo/1456981-cvdisplaylinkcreatewithcgdisplay?language=objc)
+        pub unsafe fn on_display(display_id: u32) -> Option<Self> {
+            let mut display_link: *mut CVDisplayLink = 0 as _;
+            let code = CVDisplayLinkCreateWithCGDisplay(display_id, &mut display_link);
+            if code == 0 {
+                Some(DisplayLink::from_ptr(display_link))
+            } else {
+                None
+            }
+        }
+    }
+
+    impl DisplayLinkRef {
+        /// Apple docs: [CVDisplayLinkSetOutputCallback](https://developer.apple.com/documentation/corevideo/1457096-cvdisplaylinksetoutputcallback?language=objc)
+        pub unsafe fn set_output_callback(
+            &mut self,
+            callback: CVDisplayLinkOutputCallback,
+            user_info: *mut c_void,
+        ) {
+            assert_eq!(CVDisplayLinkSetOutputCallback(self, callback, user_info), 0);
+        }
+
+        /// Apple docs: [CVDisplayLinkSetCurrentCGDisplay](https://developer.apple.com/documentation/corevideo/1456768-cvdisplaylinksetcurrentcgdisplay?language=objc)
+        pub unsafe fn set_current_display(&mut self, display_id: u32) {
+            assert_eq!(CVDisplayLinkSetCurrentCGDisplay(self, display_id), 0);
+        }
+
+        /// Apple docs: [CVDisplayLinkStart](https://developer.apple.com/documentation/corevideo/1457193-cvdisplaylinkstart?language=objc)
+        pub unsafe fn start(&mut self) {
+            assert_eq!(CVDisplayLinkStart(self), 0);
+        }
+
+        /// Apple docs: [CVDisplayLinkStop](https://developer.apple.com/documentation/corevideo/1457281-cvdisplaylinkstop?language=objc)
+        pub unsafe fn stop(&mut self) {
+            assert_eq!(CVDisplayLinkStop(self), 0);
+        }
+    }
+}

crates/gpui3/src/platform/mac/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<DevicePixels> = 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<DevicePixels>,
+        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<DevicePixels> as *const _,
+        );
+
+        let shadow_bytes_len = mem::size_of::<Shadow>() * 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<DevicePixels>,
+        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<DevicePixels> as *const _,
+        );
+
+        let quad_bytes_len = mem::size_of::<Underline>() * 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,

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<dyn PlatformDisplayLinker> {
+        Arc::new(MacDisplayLinker::new())
+    }
+
     fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
         self.0.lock().text_system.clone()
     }
@@ -455,21 +460,21 @@ impl Platform for MacPlatform {
         }
     }
 
-    fn screens(&self) -> Vec<Rc<dyn PlatformScreen>> {
-        MacScreen::all()
+    fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
+        MacDisplay::all()
             .into_iter()
             .map(|screen| Rc::new(screen) as Rc<_>)
             .collect()
     }
 
-    fn screen_by_id(&self, id: ScreenId) -> Option<Rc<dyn PlatformScreen>> {
-        MacScreen::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
-    }
-
     // fn add_status_item(&self, _handle: AnyWindowHandle) -> Box<dyn platform::Window> {
     //     Box::new(StatusItem::add(self.fonts()))
     // }
 
+    fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>> {
+        MacDisplay::find_by_id(id).map(|screen| Rc::new(screen) as Rc<_>)
+    }
+
     fn main_window(&self) -> Option<AnyWindowHandle> {
         MacWindow::main_window()
     }
@@ -736,6 +741,32 @@ impl Platform for MacPlatform {
         }
     }
 
+    // fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>) {
+    //     self.0.lock().menu_command = Some(callback);
+    // }
+
+    // fn on_will_open_menu(&self, callback: Box<dyn FnMut()>) {
+    //     self.0.lock().will_open_menu = Some(callback);
+    // }
+
+    // fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
+    //     self.0.lock().validate_menu_command = Some(callback);
+    // }
+
+    // fn set_menus(&self, menus: Vec<Menu>, keystroke_matcher: &KeymapMatcher) {
+    //     unsafe {
+    //         let app: id = msg_send![APP_CLASS, sharedApplication];
+    //         let mut state = self.0.lock();
+    //         let actions = &mut state.menu_actions;
+    //         app.setMainMenu_(self.create_menu_bar(
+    //             menus,
+    //             app.delegate(),
+    //             actions,
+    //             keystroke_matcher,
+    //         ));
+    //     }
+    // }
+
     fn read_from_clipboard(&self) -> Option<ClipboardItem> {
         let state = self.0.lock();
         unsafe {
@@ -773,32 +804,6 @@ impl Platform for MacPlatform {
         }
     }
 
-    // fn on_menu_command(&self, callback: Box<dyn FnMut(&dyn Action)>) {
-    //     self.0.lock().menu_command = Some(callback);
-    // }
-
-    // fn on_will_open_menu(&self, callback: Box<dyn FnMut()>) {
-    //     self.0.lock().will_open_menu = Some(callback);
-    // }
-
-    // fn on_validate_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
-    //     self.0.lock().validate_menu_command = Some(callback);
-    // }
-
-    // fn set_menus(&self, menus: Vec<Menu>, keystroke_matcher: &KeymapMatcher) {
-    //     unsafe {
-    //         let app: id = msg_send![APP_CLASS, sharedApplication];
-    //         let mut state = self.0.lock();
-    //         let actions = &mut state.menu_actions;
-    //         app.setMainMenu_(self.create_menu_bar(
-    //             menus,
-    //             app.delegate(),
-    //             actions,
-    //             keystroke_matcher,
-    //         ));
-    //     }
-    // }
-
     fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Result<()> {
         let url = CFString::from(url);
         let username = CFString::from(username);

crates/gpui3/src/platform/mac/screen.rs πŸ”—

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

crates/gpui3/src/platform/mac/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;
+}

crates/gpui3/src/platform/mac/window.rs πŸ”—

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

crates/gpui3/src/platform/test.rs πŸ”—

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

crates/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<f32>;
-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<LayerId, SceneLayer>,
+    pub(crate) layers: BTreeMap<StackingOrder, LayerId>,
+    pub shadows: Vec<Shadow>,
+    pub quads: Vec<Quad>,
+    pub underlines: Vec<Underline>,
+    pub monochrome_sprites: Vec<MonochromeSprite>,
+    pub polychrome_sprites: Vec<PolychromeSprite>,
 }
 
 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<Primitive>) {
-        let layer = self.layers.entry(stacking_order).or_default();
-
+    pub fn insert(&mut self, layer_id: StackingOrder, primitive: impl Into<Primitive>) {
+        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<Item = &mut SceneLayer> {
-        self.layers.values_mut()
-    }
-}
+    pub(crate) fn batches(&mut self) -> impl Iterator<Item = PrimitiveBatch> {
+        // 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<Quad>,
-    pub monochrome_sprites: Vec<MonochromeSprite>,
-    pub polychrome_sprites: Vec<PolychromeSprite>,
-}
+        // Add all primitives to the BSP splitter to determine draw order
+        // todo!("reuse the same splitter")
+        let mut splitter = BspSplitter::new();
+
+        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)),
+            );
+        }
 
-impl SceneLayer {
-    pub fn batches(&mut self) -> impl Iterator<Item = PrimitiveBatch> {
+        // 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<slice::Iter<'a, Quad>>,
+    shadows: &'a [Shadow],
+    shadows_start: usize,
+    shadows_iter: Peekable<slice::Iter<'a, Shadow>>,
+    underlines: &'a [Underline],
+    underlines_start: usize,
+    underlines_iter: Peekable<slice::Iter<'a, Underline>>,
     monochrome_sprites: &'a [MonochromeSprite],
     monochrome_sprites_start: usize,
     monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>,
@@ -94,50 +192,92 @@ impl<'a> Iterator for BatchIterator<'a> {
     type Item = PrimitiveBatch<'a>;
 
     fn next(&mut self) -> Option<Self::Item> {
-        let mut kinds_and_orders = [
-            (PrimitiveKind::Quad, self.quads_iter.peek().map(|q| q.order)),
+        let mut orders_and_kinds = [
+            (
+                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,
+            ),
             (
-                PrimitiveKind::MonochromeSprite,
                 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<ScaledPixels>,
-    pub clip_bounds: Bounds<ScaledPixels>,
-    pub clip_corner_radii: Corners<ScaledPixels>,
+    pub content_mask: ScaledContentMask,
     pub background: Hsla,
     pub border_color: Hsla,
     pub corner_radii: Corners<ScaledPixels>,
     pub border_widths: Edges<ScaledPixels>,
 }
 
-impl Quad {
-    pub fn vertices(&self) -> impl Iterator<Item = Point<ScaledPixels>> {
-        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<Quad> for Primitive {
     }
 }
 
+#[derive(Debug, Clone, Eq, PartialEq)]
+#[repr(C)]
+pub struct Underline {
+    pub order: u32,
+    pub bounds: Bounds<ScaledPixels>,
+    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<std::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl From<Underline> 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<ScaledPixels>,
+    pub corner_radii: Corners<ScaledPixels>,
+    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<std::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl From<Shadow> 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<PolychromeSprite> for Primitive {
 
 #[derive(Copy, Clone, Debug)]
 pub struct AtlasId(pub(crate) usize);
+
+impl Bounds<ScaledPixels> {
+    fn to_bsp_polygon<A: Copy>(&self, z: f32, anchor: A) -> BspPolygon<A> {
+        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(),
+        }
+    }
+}

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<AbsoluteLength>,
 
+    /// 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<Pixels>,
+    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<V: 'static>(&self, order: u32, bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) {
+    pub fn paint<V: 'static>(&self, bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) {
         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<Hsla>,
-    pub squiggly: bool,
+    pub wavy: bool,
 }
 
 #[derive(Clone, Debug)]

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<Style = Style> {
+pub trait StyleHelpers: Styled<Style = Style> {
     gpui3_macros::style_helpers!();
 
     fn h(mut self, height: Length) -> Self {
@@ -147,6 +149,103 @@ pub trait StyleHelpers: Sized + Styled<Style = Style> {
         self
     }
 
+    fn shadow(mut self) -> Self {
+        self.declared_style().box_shadow = Some(smallvec![
+            BoxShadow {
+                color: hsla(0., 0., 0., 0.1),
+                offset: point(px(0.), px(1.)),
+                blur_radius: px(3.),
+                spread_radius: px(0.),
+            },
+            BoxShadow {
+                color: hsla(0., 0., 0., 0.1),
+                offset: point(px(0.), px(1.)),
+                blur_radius: px(2.),
+                spread_radius: px(-1.),
+            }
+        ]);
+        self
+    }
+
+    fn shadow_none(mut self) -> Self {
+        self.declared_style().box_shadow = Some(Default::default());
+        self
+    }
+
+    fn shadow_sm(mut self) -> Self {
+        self.declared_style().box_shadow = Some(smallvec![BoxShadow {
+            color: hsla(0., 0., 0., 0.05),
+            offset: point(px(0.), px(1.)),
+            blur_radius: px(2.),
+            spread_radius: px(0.),
+        }]);
+        self
+    }
+
+    fn shadow_md(mut self) -> Self {
+        self.declared_style().box_shadow = Some(smallvec![
+            BoxShadow {
+                color: hsla(0.5, 0., 0., 1.0),
+                offset: point(px(0.), px(4.)),
+                blur_radius: px(6.),
+                spread_radius: px(-1.),
+            },
+            BoxShadow {
+                color: hsla(0., 0., 0., 0.1),
+                offset: point(px(0.), px(2.)),
+                blur_radius: px(4.),
+                spread_radius: px(-2.),
+            }
+        ]);
+        self
+    }
+
+    fn shadow_lg(mut self) -> Self {
+        self.declared_style().box_shadow = Some(smallvec![
+            BoxShadow {
+                color: hsla(0., 0., 0., 0.1),
+                offset: point(px(0.), px(10.)),
+                blur_radius: px(15.),
+                spread_radius: px(-3.),
+            },
+            BoxShadow {
+                color: hsla(0., 0., 0., 0.1),
+                offset: point(px(0.), px(4.)),
+                blur_radius: px(6.),
+                spread_radius: px(-4.),
+            }
+        ]);
+        self
+    }
+
+    fn shadow_xl(mut self) -> Self {
+        self.declared_style().box_shadow = Some(smallvec![
+            BoxShadow {
+                color: hsla(0., 0., 0., 0.1),
+                offset: point(px(0.), px(20.)),
+                blur_radius: px(25.),
+                spread_radius: px(-5.),
+            },
+            BoxShadow {
+                color: hsla(0., 0., 0., 0.1),
+                offset: point(px(0.), px(8.)),
+                blur_radius: px(10.),
+                spread_radius: px(-6.),
+            }
+        ]);
+        self
+    }
+
+    fn shadow_2xl(mut self) -> Self {
+        self.declared_style().box_shadow = Some(smallvec![BoxShadow {
+            color: hsla(0., 0., 0., 0.25),
+            offset: point(px(0.), px(25.)),
+            blur_radius: px(50.),
+            spread_radius: px(-12.),
+        }]);
+        self
+    }
+
     fn text_style(&mut self) -> &mut Option<TextStyleRefinement> {
         let style: &mut StyleRefinement = self.declared_style();
         &mut style.text
@@ -206,6 +305,69 @@ pub trait StyleHelpers: Sized + Styled<Style = Style> {
         self
     }
 
+    fn text_decoration_none(mut self) -> Self {
+        self.text_style()
+            .get_or_insert_with(Default::default)
+            .underline = None;
+        self
+    }
+
+    fn text_decoration_color(mut self, color: impl Into<Hsla>) -> Self {
+        let style = self.text_style().get_or_insert_with(Default::default);
+        let underline = style.underline.get_or_insert_with(Default::default);
+        underline.color = Some(color.into());
+        self
+    }
+
+    fn text_decoration_solid(mut self) -> Self {
+        let style = self.text_style().get_or_insert_with(Default::default);
+        let underline = style.underline.get_or_insert_with(Default::default);
+        underline.wavy = false;
+        self
+    }
+
+    fn text_decoration_wavy(mut self) -> Self {
+        let style = self.text_style().get_or_insert_with(Default::default);
+        let underline = style.underline.get_or_insert_with(Default::default);
+        underline.wavy = true;
+        self
+    }
+
+    fn text_decoration_0(mut self) -> Self {
+        let style = self.text_style().get_or_insert_with(Default::default);
+        let underline = style.underline.get_or_insert_with(Default::default);
+        underline.thickness = px(0.);
+        self
+    }
+
+    fn text_decoration_1(mut self) -> Self {
+        let style = self.text_style().get_or_insert_with(Default::default);
+        let underline = style.underline.get_or_insert_with(Default::default);
+        underline.thickness = px(1.);
+        self
+    }
+
+    fn text_decoration_2(mut self) -> Self {
+        let style = self.text_style().get_or_insert_with(Default::default);
+        let underline = style.underline.get_or_insert_with(Default::default);
+        underline.thickness = px(2.);
+        self
+    }
+
+    fn text_decoration_4(mut self) -> Self {
+        let style = self.text_style().get_or_insert_with(Default::default);
+        let underline = style.underline.get_or_insert_with(Default::default);
+        underline.thickness = px(4.);
+        self
+    }
+
+    fn text_decoration_8(mut self) -> Self {
+        let style = self.text_style().get_or_insert_with(Default::default);
+        let underline = style.underline.get_or_insert_with(Default::default);
+        underline.thickness = px(8.);
+        self
+    }
+
     fn font(mut self, family_name: impl Into<SharedString>) -> Self {
         self.text_style()
             .get_or_insert_with(Default::default)

crates/gpui3/src/taffy.rs πŸ”—

@@ -1,6 +1,5 @@
 use super::{
-    AbsoluteLength, Bounds, DefiniteLength, Edges, Layout, Length, Pixels, Point, Result, Size,
-    Style,
+    AbsoluteLength, Bounds, DefiniteLength, Edges, Length, Pixels, Point, Result, Size, Style,
 };
 use collections::HashMap;
 use std::fmt::Debug;
@@ -14,7 +13,7 @@ use taffy::{
 pub struct TaffyLayoutEngine {
     taffy: Taffy,
     children_to_parents: HashMap<LayoutId, LayoutId>,
-    absolute_layouts: HashMap<LayoutId, Layout>,
+    absolute_layout_bounds: HashMap<LayoutId, Bounds<Pixels>>,
 }
 
 impl TaffyLayoutEngine {
@@ -22,7 +21,7 @@ impl TaffyLayoutEngine {
         TaffyLayoutEngine {
             taffy: Taffy::new(),
             children_to_parents: HashMap::default(),
-            absolute_layouts: HashMap::default(),
+            absolute_layout_bounds: HashMap::default(),
         }
     }
 
@@ -127,19 +126,24 @@ impl TaffyLayoutEngine {
         Ok(())
     }
 
-    pub fn layout(&mut self, id: LayoutId) -> Result<Layout> {
-        if let Some(layout) = self.absolute_layouts.get(&id).cloned() {
+    pub fn layout_bounds(&mut self, id: LayoutId) -> Result<Bounds<Pixels>> {
+        if let Some(layout) = self.absolute_layout_bounds.get(&id).cloned() {
             return Ok(layout);
         }
 
-        let mut relative_layout: Layout = self.taffy.layout(id.into()).map(Into::into)?;
+        let layout = self.taffy.layout(id.into())?;
+        let mut bounds = Bounds {
+            origin: layout.location.into(),
+            size: layout.size.into(),
+        };
+
         if let Some(parent_id) = self.children_to_parents.get(&id).copied() {
-            let parent_layout = self.layout(parent_id)?;
-            relative_layout.bounds.origin += parent_layout.bounds.origin;
+            let parent_bounds = self.layout_bounds(parent_id)?;
+            bounds.origin += parent_bounds.origin;
         }
-        self.absolute_layouts.insert(id, relative_layout.clone());
+        self.absolute_layout_bounds.insert(id, bounds);
 
-        Ok(relative_layout)
+        Ok(bounds)
     }
 }
 
@@ -420,15 +424,3 @@ impl From<Pixels> for AvailableSpace {
         AvailableSpace::Definite(pixels)
     }
 }
-
-impl From<&taffy::tree::Layout> for Layout {
-    fn from(layout: &taffy::tree::Layout) -> Self {
-        Layout {
-            order: layout.order,
-            bounds: Bounds {
-                origin: layout.location.into(),
-                size: layout.size.into(),
-            },
-        }
-    }
-}

crates/gpui3/src/text_system/line.rs πŸ”—

@@ -1,6 +1,6 @@
 use crate::{
-    black, point, px, Bounds, FontId, Hsla, Layout, Pixels, Point, RunStyle, ShapedBoundary,
-    ShapedLine, ShapedRun, UnderlineStyle, WindowContext,
+    black, point, px, Bounds, FontId, Hsla, Pixels, Point, RunStyle, ShapedBoundary, ShapedLine,
+    ShapedRun, UnderlineStyle, WindowContext,
 };
 use anyhow::Result;
 use smallvec::SmallVec;
@@ -90,15 +90,14 @@ impl Line {
         }
     }
 
-    // todo!
     pub fn paint(
         &self,
-        layout: &Layout,
-        visible_bounds: Bounds<Pixels>,
+        bounds: Bounds<Pixels>,
+        visible_bounds: Bounds<Pixels>, // todo!("use clipping")
         line_height: Pixels,
         cx: &mut WindowContext,
     ) -> Result<()> {
-        let origin = layout.bounds.origin;
+        let origin = bounds.origin;
         let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
         let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
 
@@ -135,9 +134,11 @@ impl Line {
                                     origin.y + baseline_offset.y + (self.layout.descent * 0.618),
                                 ),
                                 UnderlineStyle {
-                                    color: style_run.underline.color,
+                                    color: Some(
+                                        style_run.underline.color.unwrap_or(style_run.color),
+                                    ),
                                     thickness: style_run.underline.thickness,
-                                    squiggly: style_run.underline.squiggly,
+                                    wavy: style_run.underline.wavy,
                                 },
                             ));
                         }
@@ -154,22 +155,19 @@ impl Line {
                     continue;
                 }
 
-                if let Some((_underline_origin, _underline_style)) = finished_underline {
-                    todo!()
+                if let Some((underline_origin, underline_style)) = finished_underline {
+                    cx.paint_underline(
+                        underline_origin,
+                        glyph_origin.x - underline_origin.x,
+                        &underline_style,
+                    )?;
                 }
 
                 if glyph.is_emoji {
-                    cx.paint_emoji(
-                        glyph_origin,
-                        layout.order,
-                        run.font_id,
-                        glyph.id,
-                        self.layout.font_size,
-                    )?;
+                    cx.paint_emoji(glyph_origin, run.font_id, glyph.id, self.layout.font_size)?;
                 } else {
                     cx.paint_glyph(
                         glyph_origin,
-                        layout.order,
                         run.font_id,
                         glyph.id,
                         self.layout.font_size,
@@ -179,15 +177,13 @@ impl Line {
             }
         }
 
-        if let Some((_underline_start, _underline_style)) = underline.take() {
-            let _line_end_x = origin.x + self.layout.width;
-            // cx.scene().push_underline(Underline {
-            //     origin: underline_start,
-            //     width: line_end_x - underline_start.x,
-            //     color: underline_style.color,
-            //     thickness: underline_style.thickness.into(),
-            //     squiggly: underline_style.squiggly,
-            // });
+        if let Some((underline_start, underline_style)) = underline.take() {
+            let line_end_x = origin.x + self.layout.width;
+            cx.paint_underline(
+                underline_start,
+                line_end_x - underline_start.x,
+                &underline_style,
+            )?;
         }
 
         Ok(())
@@ -196,7 +192,7 @@ impl Line {
     pub fn paint_wrapped(
         &self,
         origin: Point<Pixels>,
-        _visible_bounds: Bounds<Pixels>,
+        _visible_bounds: Bounds<Pixels>, // todo!("use clipping")
         line_height: Pixels,
         boundaries: &[ShapedBoundary],
         cx: &mut WindowContext,
@@ -221,14 +217,12 @@ impl Line {
                     .map_or(false, |b| b.run_ix == run_ix && b.glyph_ix == glyph_ix)
                 {
                     boundaries.next();
-                    if let Some((_underline_origin, _underline_style)) = underline.take() {
-                        // cx.scene().push_underline(Underline {
-                        //     origin: underline_origin,
-                        //     width: glyph_origin.x - underline_origin.x,
-                        //     thickness: underline_style.thickness.into(),
-                        //     color: underline_style.color.unwrap(),
-                        //     squiggly: underline_style.squiggly,
-                        // });
+                    if let Some((underline_origin, underline_style)) = underline.take() {
+                        cx.paint_underline(
+                            underline_origin,
+                            glyph_origin.x - underline_origin.x,
+                            &underline_style,
+                        )?;
                     }
 
                     glyph_origin = point(origin.x, glyph_origin.y + line_height);
@@ -257,7 +251,7 @@ impl Line {
                                         style_run.underline.color.unwrap_or(style_run.color),
                                     ),
                                     thickness: style_run.underline.thickness,
-                                    squiggly: style_run.underline.squiggly,
+                                    wavy: style_run.underline.wavy,
                                 },
                             ));
                         }
@@ -268,14 +262,12 @@ impl Line {
                     }
                 }
 
-                if let Some((_underline_origin, _underline_style)) = finished_underline {
-                    // cx.scene().push_underline(Underline {
-                    //     origin: underline_origin,
-                    //     width: glyph_origin.x - underline_origin.x,
-                    //     thickness: underline_style.thickness.into(),
-                    //     color: underline_style.color.unwrap(),
-                    //     squiggly: underline_style.squiggly,
-                    // });
+                if let Some((underline_origin, underline_style)) = finished_underline {
+                    cx.paint_underline(
+                        underline_origin,
+                        glyph_origin.x - underline_origin.x,
+                        &underline_style,
+                    )?;
                 }
 
                 let text_system = cx.text_system();
@@ -306,15 +298,13 @@ impl Line {
             }
         }
 
-        if let Some((_underline_origin, _underline_style)) = underline.take() {
-            // let line_end_x = glyph_origin.x + self.layout.width - prev_position;
-            // cx.scene().push_underline(Underline {
-            //     origin: underline_origin,
-            //     width: line_end_x - underline_origin.x,
-            //     thickness: underline_style.thickness.into(),
-            //     color: underline_style.color,
-            //     squiggly: underline_style.squiggly,
-            // });
+        if let Some((underline_origin, underline_style)) = underline.take() {
+            let line_end_x = glyph_origin.x + self.layout.width - prev_position;
+            cx.paint_underline(
+                underline_origin,
+                line_end_x - underline_origin.x,
+                &underline_style,
+            )?;
         }
 
         Ok(())

crates/gpui3/src/view.rs πŸ”—

@@ -1,7 +1,7 @@
 use parking_lot::Mutex;
 
 use crate::{
-    AnyElement, Element, Handle, IntoAnyElement, Layout, LayoutId, Result, ViewContext,
+    AnyElement, Bounds, Element, Handle, IntoAnyElement, LayoutId, Pixels, Result, ViewContext,
     WindowContext,
 };
 use std::{any::Any, marker::PhantomData, sync::Arc};
@@ -67,7 +67,7 @@ impl<S: Send + Sync + 'static, P: Send + 'static> Element for View<S, P> {
 
     fn paint(
         &mut self,
-        _: Layout,
+        _: Bounds<Pixels>,
         _: &mut Self::State,
         element: &mut Self::FrameState,
         cx: &mut ViewContext<Self::State>,
@@ -81,7 +81,7 @@ trait ViewObject: Send + 'static {
     fn layout(&mut self, cx: &mut WindowContext) -> Result<(LayoutId, Box<dyn Any>)>;
     fn paint(
         &mut self,
-        layout: Layout,
+        bounds: Bounds<Pixels>,
         element: &mut dyn Any,
         cx: &mut WindowContext,
     ) -> Result<()>;
@@ -97,7 +97,12 @@ impl<S: Send + Sync + 'static, P: Send + 'static> ViewObject for View<S, P> {
         })
     }
 
-    fn paint(&mut self, _: Layout, element: &mut dyn Any, cx: &mut WindowContext) -> Result<()> {
+    fn paint(
+        &mut self,
+        _: Bounds<Pixels>,
+        element: &mut dyn Any,
+        cx: &mut WindowContext,
+    ) -> Result<()> {
         self.state.update(cx, |state, cx| {
             let element = element.downcast_mut::<AnyElement<S>>().unwrap();
             element.paint(state, None, cx)
@@ -124,12 +129,12 @@ impl<S: 'static> Element for AnyView<S> {
 
     fn paint(
         &mut self,
-        layout: Layout,
+        bounds: Bounds<Pixels>,
         _: &mut (),
         element: &mut Box<dyn Any>,
         cx: &mut ViewContext<Self::State>,
     ) -> Result<()> {
-        self.view.lock().paint(layout, element.as_mut(), cx)
+        self.view.lock().paint(bounds, element.as_mut(), cx)
     }
 }
 

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,
+    image_cache::RenderImageParams, px, size, AnyView, AppContext, AsyncWindowContext,
+    AvailableSpace, BorrowAppContext, Bounds, Context, Corners, DevicePixels, DisplayId, Effect,
+    Element, EntityId, FontId, GlyphId, Handle, Hsla, ImageData, IsZero, LayoutId, MainThread,
+    MainThreadOnly, MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point,
+    PolychromeSprite, Reference, RenderGlyphParams, RenderSvgParams, ScaledPixels, Scene,
+    SharedString, Size, StackingOrder, Style, TaffyLayoutEngine, Task, Underline, UnderlineStyle,
+    WeakHandle, WindowOptions, SUBPIXEL_VARIANTS,
 };
 use anyhow::Result;
 use smallvec::SmallVec;
@@ -16,13 +17,14 @@ pub struct AnyWindow {}
 pub struct Window {
     handle: AnyWindowHandle,
     platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
+    pub(crate) display_id: DisplayId, // todo!("make private again?")
     sprite_atlas: Arc<dyn PlatformAtlas>,
     rem_size: Pixels,
     content_size: Size<Pixels>,
     layout_engine: TaffyLayoutEngine,
     pub(crate) root_view: Option<AnyView<()>>,
     mouse_position: Point<Pixels>,
-    current_layer_id: LayerId,
+    current_stacking_order: StackingOrder,
     content_mask_stack: Vec<ContentMask>,
     pub(crate) scene: Scene,
     pub(crate) dirty: bool,
@@ -35,6 +37,7 @@ impl Window {
         cx: &mut MainThread<AppContext>,
     ) -> 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,13 +66,14 @@ impl Window {
         Window {
             handle,
             platform_window,
+            display_id,
             sprite_atlas,
             rem_size: px(16.),
             content_size,
             layout_engine: TaffyLayoutEngine::new(),
             root_view: None,
             mouse_position,
-            current_layer_id: SmallVec::new(),
+            current_stacking_order: SmallVec::new(),
             content_mask_stack: Vec::new(),
             scene: Scene::new(scale_factor),
             dirty: true,
@@ -89,7 +99,7 @@ impl ContentMask {
     }
 }
 
-#[derive(Clone, Debug, PartialEq, Eq)]
+#[derive(Default, Clone, Debug, PartialEq, Eq)]
 #[repr(C)]
 pub struct ScaledContentMask {
     bounds: Bounds<ScaledPixels>,
@@ -133,6 +143,45 @@ 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 f = Box::new(f);
+        let display_id = self.window.display_id;
+        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::<Vec<_>>();
+                            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<Fut, R>(
         &mut self,
         f: impl FnOnce(AnyWindowHandle, AsyncWindowContext) -> Fut + Send + 'static,
@@ -176,11 +225,11 @@ impl<'a, 'w> WindowContext<'a, 'w> {
             .request_measured_layout(style, rem_size, measure)
     }
 
-    pub fn layout(&mut self, layout_id: LayoutId) -> Result<Layout> {
+    pub fn layout_bounds(&mut self, layout_id: LayoutId) -> Result<Bounds<Pixels>> {
         Ok(self
             .window
             .layout_engine
-            .layout(layout_id)
+            .layout_bounds(layout_id)
             .map(Into::into)?)
     }
 
@@ -201,20 +250,51 @@ impl<'a, 'w> WindowContext<'a, 'w> {
     }
 
     pub fn stack<R>(&mut self, order: u32, f: impl FnOnce(&mut Self) -> R) -> R {
-        self.window.current_layer_id.push(order);
+        self.window.current_stacking_order.push(order);
         let result = f(self);
-        self.window.current_layer_id.pop();
+        self.window.current_stacking_order.pop();
         result
     }
 
-    pub fn current_layer_id(&self) -> LayerId {
-        self.window.current_layer_id.clone()
+    pub fn current_stacking_order(&self) -> StackingOrder {
+        self.window.current_stacking_order.clone()
+    }
+
+    pub fn paint_underline(
+        &mut self,
+        origin: Point<Pixels>,
+        width: Pixels,
+        style: &UnderlineStyle,
+    ) -> Result<()> {
+        let scale_factor = self.scale_factor();
+        let height = if style.wavy {
+            style.thickness * 3.
+        } else {
+            style.thickness
+        };
+        let bounds = Bounds {
+            origin,
+            size: size(width, height),
+        };
+        let content_mask = self.content_mask();
+        let layer_id = self.current_stacking_order();
+        self.window.scene.insert(
+            layer_id,
+            Underline {
+                order: 0,
+                bounds: bounds.scale(scale_factor),
+                content_mask: content_mask.scale(scale_factor),
+                thickness: style.thickness.scale(scale_factor),
+                color: style.color.unwrap_or_default(),
+                wavy: style.wavy,
+            },
+        );
+        Ok(())
     }
 
     pub fn paint_glyph(
         &mut self,
         origin: Point<Pixels>,
-        order: u32,
         font_id: FontId,
         glyph_id: GlyphId,
         font_size: Pixels,
@@ -237,7 +317,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
 
         let raster_bounds = self.text_system().raster_bounds(&params)?;
         if !raster_bounds.is_zero() {
-            let layer_id = self.current_layer_id();
+            let layer_id = self.current_stacking_order();
             let tile =
                 self.window
                     .sprite_atlas
@@ -254,7 +334,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
             self.window.scene.insert(
                 layer_id,
                 MonochromeSprite {
-                    order,
+                    order: 0,
                     bounds,
                     content_mask,
                     color,
@@ -268,7 +348,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
     pub fn paint_emoji(
         &mut self,
         origin: Point<Pixels>,
-        order: u32,
         font_id: FontId,
         glyph_id: GlyphId,
         font_size: Pixels,
@@ -287,7 +366,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
 
         let raster_bounds = self.text_system().raster_bounds(&params)?;
         if !raster_bounds.is_zero() {
-            let layer_id = self.current_layer_id();
+            let layer_id = self.current_stacking_order();
             let tile =
                 self.window
                     .sprite_atlas
@@ -304,7 +383,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
             self.window.scene.insert(
                 layer_id,
                 PolychromeSprite {
-                    order,
+                    order: 0,
                     bounds,
                     corner_radii: Default::default(),
                     content_mask,
@@ -319,7 +398,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
     pub fn paint_svg(
         &mut self,
         bounds: Bounds<Pixels>,
-        order: u32,
         path: SharedString,
         color: Hsla,
     ) -> Result<()> {
@@ -333,7 +411,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
                 .map(|pixels| DevicePixels::from((pixels.0 * 2.).ceil() as i32)),
         };
 
-        let layer_id = self.current_layer_id();
+        let layer_id = self.current_stacking_order();
         let tile =
             self.window
                 .sprite_atlas
@@ -346,7 +424,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         self.window.scene.insert(
             layer_id,
             MonochromeSprite {
-                order,
+                order: 0,
                 bounds,
                 content_mask,
                 color,
@@ -361,7 +439,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         &mut self,
         bounds: Bounds<Pixels>,
         corner_radii: Corners<Pixels>,
-        order: u32,
         data: Arc<ImageData>,
         grayscale: bool,
     ) -> Result<()> {
@@ -369,7 +446,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         let bounds = bounds.scale(scale_factor);
         let params = RenderImageParams { image_id: data.id };
 
-        let layer_id = self.current_layer_id();
+        let order = self.current_stacking_order();
         let tile = self
             .window
             .sprite_atlas
@@ -380,9 +457,9 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         let corner_radii = corner_radii.scale(scale_factor);
 
         self.window.scene.insert(
-            layer_id,
+            order,
             PolychromeSprite {
-                order,
+                order: 0,
                 bounds,
                 content_mask,
                 corner_radii,
@@ -401,12 +478,10 @@ impl<'a, 'w> WindowContext<'a, 'w> {
             let (root_layout_id, mut frame_state) = root_view.layout(&mut (), cx)?;
             let available_space = cx.window.content_size.map(Into::into);
 
-            let started_at = std::time::Instant::now();
             cx.window
                 .layout_engine
                 .compute_layout(root_layout_id, available_space)?;
-            println!("compute_layout took {:?}", started_at.elapsed());
-            let layout = cx.window.layout_engine.layout(root_layout_id)?;
+            let layout = cx.window.layout_engine.layout_bounds(root_layout_id)?;
 
             root_view.paint(layout, &mut (), &mut frame_state, cx)?;
             cx.window.root_view = Some(root_view);
@@ -573,6 +648,20 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {
         self.entities.weak_handle(self.entity_id)
     }
 
+    pub fn stack<R>(&mut self, order: u32, f: impl FnOnce(&mut Self) -> R) -> R {
+        self.window.current_stacking_order.push(order);
+        let result = f(self);
+        self.window.current_stacking_order.pop();
+        result
+    }
+
+    pub fn on_next_frame(&mut self, f: impl FnOnce(&mut S, &mut ViewContext<S>) + Send + 'static) {
+        let entity = self.handle();
+        self.window_cx.on_next_frame(move |cx| {
+            entity.update(cx, f).ok();
+        });
+    }
+
     pub fn observe<E: Send + Sync + 'static>(
         &mut self,
         handle: &Handle<E>,
@@ -726,9 +815,3 @@ pub struct AnyWindowHandle {
     pub(crate) id: WindowId,
     state_type: TypeId,
 }
-
-#[derive(Clone)]
-pub struct Layout {
-    pub order: u32,
-    pub bounds: Bounds<Pixels>,
-}

crates/storybook2/src/collab_panel.rs πŸ”—

@@ -92,7 +92,7 @@ impl CollabPanel {
                                         ),
                                     ]
                                 })
-                                .take(10)
+                                .take(5)
                                 .flatten(),
                             ),
                     ),
@@ -168,7 +168,8 @@ impl CollabPanel {
                             .uri(avatar_uri)
                             .size_3p5()
                             .rounded_full()
-                            .fill(theme.middle.positive.default.foreground),
+                            .fill(theme.middle.positive.default.foreground)
+                            .shadow_md(),
                     )
                     .child(label),
             )

crates/storybook2/src/theme.rs πŸ”—

@@ -2,7 +2,7 @@ use std::sync::Arc;
 use std::{collections::HashMap, fmt};
 
 use gpui3::{
-    BorrowAppContext, Element, Hsla, Layout, LayoutId, Result, ViewContext, WindowContext,
+    BorrowAppContext, Bounds, Element, Hsla, LayoutId, Pixels, Result, ViewContext, WindowContext,
 };
 use serde::{de::Visitor, Deserialize, Deserializer};
 
@@ -162,7 +162,7 @@ impl<E: Element> Element for Themed<E> {
 
     fn paint(
         &mut self,
-        layout: Layout,
+        bounds: Bounds<Pixels>,
         state: &mut Self::State,
         frame_state: &mut Self::FrameState,
         cx: &mut ViewContext<Self::State>,
@@ -171,7 +171,7 @@ impl<E: Element> Element for Themed<E> {
         Self: Sized,
     {
         cx.with_state(self.theme.clone(), |cx| {
-            self.child.paint(layout, state, frame_state, cx)
+            self.child.paint(bounds, state, frame_state, cx)
         })
     }
 }

crates/storybook2/src/themes/rose_pine_dawn.rs β†’ crates/storybook2/src/themes/rose_pine.rs πŸ”—

@@ -1,7 +1,7 @@
 use crate::theme::Theme;
 use gpui3::serde_json::{self, json};
 
-pub fn rose_pine_dawn() -> Theme {
+pub fn rose_pine() -> Theme {
     serde_json::from_value(json! {
         {
           "name": "RosΓ© Pine",
@@ -843,3 +843,844 @@ pub fn rose_pine_dawn() -> Theme {
     })
     .unwrap()
 }
+
+pub fn rose_pine_dawn() -> Theme {
+    serde_json::from_value(json!({
+      "name": "RosΓ© Pine Dawn",
+      "is_light": true,
+      "ramps": {},
+      "lowest": {
+        "base": {
+          "default": {
+            "background": "#dcd8d8",
+            "border": "#dcd6d5",
+            "foreground": "#575279"
+          },
+          "hovered": {
+            "background": "#dcd6d5",
+            "border": "#dcd6d5",
+            "foreground": "#575279"
+          },
+          "pressed": {
+            "background": "#efe6df",
+            "border": "#dcd6d5",
+            "foreground": "#575279"
+          },
+          "active": {
+            "background": "#c1bac1",
+            "border": "#a9a3b0",
+            "foreground": "#575279"
+          },
+          "disabled": {
+            "background": "#dcd8d8",
+            "border": "#d0cccf",
+            "foreground": "#938fa3"
+          },
+          "inverted": {
+            "background": "#575279",
+            "border": "#faf4ed",
+            "foreground": "#c7c0c5"
+          }
+        },
+        "variant": {
+          "default": {
+            "background": "#dcd8d8",
+            "border": "#dcd6d5",
+            "foreground": "#706c8c"
+          },
+          "hovered": {
+            "background": "#dcd6d5",
+            "border": "#dcd6d5",
+            "foreground": "#706c8c"
+          },
+          "pressed": {
+            "background": "#efe6df",
+            "border": "#dcd6d5",
+            "foreground": "#706c8c"
+          },
+          "active": {
+            "background": "#c1bac1",
+            "border": "#a9a3b0",
+            "foreground": "#575279"
+          },
+          "disabled": {
+            "background": "#dcd8d8",
+            "border": "#d0cccf",
+            "foreground": "#938fa3"
+          },
+          "inverted": {
+            "background": "#575279",
+            "border": "#faf4ed",
+            "foreground": "#c7c0c5"
+          }
+        },
+        "on": {
+          "default": {
+            "background": "#fef9f2",
+            "border": "#e5e0df",
+            "foreground": "#575279"
+          },
+          "hovered": {
+            "background": "#e5e0df",
+            "border": "#e5e0df",
+            "foreground": "#575279"
+          },
+          "pressed": {
+            "background": "#d4d0d2",
+            "border": "#e5e0df",
+            "foreground": "#575279"
+          },
+          "active": {
+            "background": "#dbd5d4",
+            "border": "#dbd3d1",
+            "foreground": "#575279"
+          },
+          "disabled": {
+            "background": "#fef9f2",
+            "border": "#f6f1eb",
+            "foreground": "#b1abb5"
+          },
+          "inverted": {
+            "background": "#575279",
+            "border": "#faf4ed",
+            "foreground": "#d6d1d1"
+          }
+        },
+        "accent": {
+          "default": {
+            "background": "#dde9eb",
+            "border": "#c3d7db",
+            "foreground": "#57949f"
+          },
+          "hovered": {
+            "background": "#c3d7db",
+            "border": "#c3d7db",
+            "foreground": "#57949f"
+          },
+          "pressed": {
+            "background": "#b6cfd3",
+            "border": "#c3d7db",
+            "foreground": "#57949f"
+          },
+          "active": {
+            "background": "#a3c3c9",
+            "border": "#8db6bd",
+            "foreground": "#06090a"
+          },
+          "disabled": {
+            "background": "#dde9eb",
+            "border": "#d0e0e3",
+            "foreground": "#72a5ae"
+          },
+          "inverted": {
+            "background": "#06090a",
+            "border": "#ffffff",
+            "foreground": "#a8c7cd"
+          }
+        },
+        "positive": {
+          "default": {
+            "background": "#dbeee7",
+            "border": "#bee0d5",
+            "foreground": "#3eaa8e"
+          },
+          "hovered": {
+            "background": "#bee0d5",
+            "border": "#bee0d5",
+            "foreground": "#3eaa8e"
+          },
+          "pressed": {
+            "background": "#b0dacb",
+            "border": "#bee0d5",
+            "foreground": "#3eaa8e"
+          },
+          "active": {
+            "background": "#9bd0bf",
+            "border": "#82c6b1",
+            "foreground": "#060a09"
+          },
+          "disabled": {
+            "background": "#dbeee7",
+            "border": "#cde7de",
+            "foreground": "#63b89f"
+          },
+          "inverted": {
+            "background": "#060a09",
+            "border": "#ffffff",
+            "foreground": "#a1d4c3"
+          }
+        },
+        "warning": {
+          "default": {
+            "background": "#ffebd6",
+            "border": "#ffdab7",
+            "foreground": "#e99d35"
+          },
+          "hovered": {
+            "background": "#ffdab7",
+            "border": "#ffdab7",
+            "foreground": "#e99d35"
+          },
+          "pressed": {
+            "background": "#fed2a6",
+            "border": "#ffdab7",
+            "foreground": "#e99d35"
+          },
+          "active": {
+            "background": "#fbc891",
+            "border": "#f7bc77",
+            "foreground": "#330704"
+          },
+          "disabled": {
+            "background": "#ffebd6",
+            "border": "#ffe2c7",
+            "foreground": "#f1ac57"
+          },
+          "inverted": {
+            "background": "#330704",
+            "border": "#ffffff",
+            "foreground": "#fccb97"
+          }
+        },
+        "negative": {
+          "default": {
+            "background": "#f1dfe3",
+            "border": "#e6c6cd",
+            "foreground": "#b4647a"
+          },
+          "hovered": {
+            "background": "#e6c6cd",
+            "border": "#e6c6cd",
+            "foreground": "#b4647a"
+          },
+          "pressed": {
+            "background": "#e0bac2",
+            "border": "#e6c6cd",
+            "foreground": "#b4647a"
+          },
+          "active": {
+            "background": "#d8a8b3",
+            "border": "#ce94a3",
+            "foreground": "#0b0708"
+          },
+          "disabled": {
+            "background": "#f1dfe3",
+            "border": "#ecd2d8",
+            "foreground": "#c17b8e"
+          },
+          "inverted": {
+            "background": "#0b0708",
+            "border": "#ffffff",
+            "foreground": "#dbadb8"
+          }
+        }
+      },
+      "middle": {
+        "base": {
+          "default": {
+            "background": "#fef9f2",
+            "border": "#e5e0df",
+            "foreground": "#575279"
+          },
+          "hovered": {
+            "background": "#e5e0df",
+            "border": "#e5e0df",
+            "foreground": "#575279"
+          },
+          "pressed": {
+            "background": "#d4d0d2",
+            "border": "#e5e0df",
+            "foreground": "#575279"
+          },
+          "active": {
+            "background": "#dbd5d4",
+            "border": "#dbd3d1",
+            "foreground": "#575279"
+          },
+          "disabled": {
+            "background": "#fef9f2",
+            "border": "#f6f1eb",
+            "foreground": "#b1abb5"
+          },
+          "inverted": {
+            "background": "#575279",
+            "border": "#faf4ed",
+            "foreground": "#d6d1d1"
+          }
+        },
+        "variant": {
+          "default": {
+            "background": "#fef9f2",
+            "border": "#e5e0df",
+            "foreground": "#706c8c"
+          },
+          "hovered": {
+            "background": "#e5e0df",
+            "border": "#e5e0df",
+            "foreground": "#706c8c"
+          },
+          "pressed": {
+            "background": "#d4d0d2",
+            "border": "#e5e0df",
+            "foreground": "#706c8c"
+          },
+          "active": {
+            "background": "#dbd5d4",
+            "border": "#dbd3d1",
+            "foreground": "#575279"
+          },
+          "disabled": {
+            "background": "#fef9f2",
+            "border": "#f6f1eb",
+            "foreground": "#b1abb5"
+          },
+          "inverted": {
+            "background": "#575279",
+            "border": "#faf4ed",
+            "foreground": "#d6d1d1"
+          }
+        },
+        "on": {
+          "default": {
+            "background": "#faf4ed",
+            "border": "#fdf8f1",
+            "foreground": "#575279"
+          },
+          "hovered": {
+            "background": "#fdf8f1",
+            "border": "#fdf8f1",
+            "foreground": "#575279"
+          },
+          "pressed": {
+            "background": "#fdf8f2",
+            "border": "#fdf8f1",
+            "foreground": "#575279"
+          },
+          "active": {
+            "background": "#e6e1e0",
+            "border": "#d0cccf",
+            "foreground": "#575279"
+          },
+          "disabled": {
+            "background": "#faf4ed",
+            "border": "#fcf6ef",
+            "foreground": "#efe6df"
+          },
+          "inverted": {
+            "background": "#575279",
+            "border": "#faf4ed",
+            "foreground": "#ede9e5"
+          }
+        },
+        "accent": {
+          "default": {
+            "background": "#dde9eb",
+            "border": "#c3d7db",
+            "foreground": "#57949f"
+          },
+          "hovered": {
+            "background": "#c3d7db",
+            "border": "#c3d7db",
+            "foreground": "#57949f"
+          },
+          "pressed": {
+            "background": "#b6cfd3",
+            "border": "#c3d7db",
+            "foreground": "#57949f"
+          },
+          "active": {
+            "background": "#a3c3c9",
+            "border": "#8db6bd",
+            "foreground": "#06090a"
+          },
+          "disabled": {
+            "background": "#dde9eb",
+            "border": "#d0e0e3",
+            "foreground": "#72a5ae"
+          },
+          "inverted": {
+            "background": "#06090a",
+            "border": "#ffffff",
+            "foreground": "#a8c7cd"
+          }
+        },
+        "positive": {
+          "default": {
+            "background": "#dbeee7",
+            "border": "#bee0d5",
+            "foreground": "#3eaa8e"
+          },
+          "hovered": {
+            "background": "#bee0d5",
+            "border": "#bee0d5",
+            "foreground": "#3eaa8e"
+          },
+          "pressed": {
+            "background": "#b0dacb",
+            "border": "#bee0d5",
+            "foreground": "#3eaa8e"
+          },
+          "active": {
+            "background": "#9bd0bf",
+            "border": "#82c6b1",
+            "foreground": "#060a09"
+          },
+          "disabled": {
+            "background": "#dbeee7",
+            "border": "#cde7de",
+            "foreground": "#63b89f"
+          },
+          "inverted": {
+            "background": "#060a09",
+            "border": "#ffffff",
+            "foreground": "#a1d4c3"
+          }
+        },
+        "warning": {
+          "default": {
+            "background": "#ffebd6",
+            "border": "#ffdab7",
+            "foreground": "#e99d35"
+          },
+          "hovered": {
+            "background": "#ffdab7",
+            "border": "#ffdab7",
+            "foreground": "#e99d35"
+          },
+          "pressed": {
+            "background": "#fed2a6",
+            "border": "#ffdab7",
+            "foreground": "#e99d35"
+          },
+          "active": {
+            "background": "#fbc891",
+            "border": "#f7bc77",
+            "foreground": "#330704"
+          },
+          "disabled": {
+            "background": "#ffebd6",
+            "border": "#ffe2c7",
+            "foreground": "#f1ac57"
+          },
+          "inverted": {
+            "background": "#330704",
+            "border": "#ffffff",
+            "foreground": "#fccb97"
+          }
+        },
+        "negative": {
+          "default": {
+            "background": "#f1dfe3",
+            "border": "#e6c6cd",
+            "foreground": "#b4647a"
+          },
+          "hovered": {
+            "background": "#e6c6cd",
+            "border": "#e6c6cd",
+            "foreground": "#b4647a"
+          },
+          "pressed": {
+            "background": "#e0bac2",
+            "border": "#e6c6cd",
+            "foreground": "#b4647a"
+          },
+          "active": {
+            "background": "#d8a8b3",
+            "border": "#ce94a3",
+            "foreground": "#0b0708"
+          },
+          "disabled": {
+            "background": "#f1dfe3",
+            "border": "#ecd2d8",
+            "foreground": "#c17b8e"
+          },
+          "inverted": {
+            "background": "#0b0708",
+            "border": "#ffffff",
+            "foreground": "#dbadb8"
+          }
+        }
+      },
+      "highest": {
+        "base": {
+          "default": {
+            "background": "#faf4ed",
+            "border": "#fdf8f1",
+            "foreground": "#575279"
+          },
+          "hovered": {
+            "background": "#fdf8f1",
+            "border": "#fdf8f1",
+            "foreground": "#575279"
+          },
+          "pressed": {
+            "background": "#fdf8f2",
+            "border": "#fdf8f1",
+            "foreground": "#575279"
+          },
+          "active": {
+            "background": "#e6e1e0",
+            "border": "#d0cccf",
+            "foreground": "#575279"
+          },
+          "disabled": {
+            "background": "#faf4ed",
+            "border": "#fcf6ef",
+            "foreground": "#efe6df"
+          },
+          "inverted": {
+            "background": "#575279",
+            "border": "#faf4ed",
+            "foreground": "#ede9e5"
+          }
+        },
+        "variant": {
+          "default": {
+            "background": "#faf4ed",
+            "border": "#fdf8f1",
+            "foreground": "#706c8c"
+          },
+          "hovered": {
+            "background": "#fdf8f1",
+            "border": "#fdf8f1",
+            "foreground": "#706c8c"
+          },
+          "pressed": {
+            "background": "#fdf8f2",
+            "border": "#fdf8f1",
+            "foreground": "#706c8c"
+          },
+          "active": {
+            "background": "#e6e1e0",
+            "border": "#d0cccf",
+            "foreground": "#575279"
+          },
+          "disabled": {
+            "background": "#faf4ed",
+            "border": "#fcf6ef",
+            "foreground": "#efe6df"
+          },
+          "inverted": {
+            "background": "#575279",
+            "border": "#faf4ed",
+            "foreground": "#ede9e5"
+          }
+        },
+        "on": {
+          "default": {
+            "background": "#fef9f2",
+            "border": "#e5e0df",
+            "foreground": "#575279"
+          },
+          "hovered": {
+            "background": "#e5e0df",
+            "border": "#e5e0df",
+            "foreground": "#575279"
+          },
+          "pressed": {
+            "background": "#d4d0d2",
+            "border": "#e5e0df",
+            "foreground": "#575279"
+          },
+          "active": {
+            "background": "#dbd5d4",
+            "border": "#dbd3d1",
+            "foreground": "#575279"
+          },
+          "disabled": {
+            "background": "#fef9f2",
+            "border": "#f6f1eb",
+            "foreground": "#b1abb5"
+          },
+          "inverted": {
+            "background": "#575279",
+            "border": "#faf4ed",
+            "foreground": "#d6d1d1"
+          }
+        },
+        "accent": {
+          "default": {
+            "background": "#dde9eb",
+            "border": "#c3d7db",
+            "foreground": "#57949f"
+          },
+          "hovered": {
+            "background": "#c3d7db",
+            "border": "#c3d7db",
+            "foreground": "#57949f"
+          },
+          "pressed": {
+            "background": "#b6cfd3",
+            "border": "#c3d7db",
+            "foreground": "#57949f"
+          },
+          "active": {
+            "background": "#a3c3c9",
+            "border": "#8db6bd",
+            "foreground": "#06090a"
+          },
+          "disabled": {
+            "background": "#dde9eb",
+            "border": "#d0e0e3",
+            "foreground": "#72a5ae"
+          },
+          "inverted": {
+            "background": "#06090a",
+            "border": "#ffffff",
+            "foreground": "#a8c7cd"
+          }
+        },
+        "positive": {
+          "default": {
+            "background": "#dbeee7",
+            "border": "#bee0d5",
+            "foreground": "#3eaa8e"
+          },
+          "hovered": {
+            "background": "#bee0d5",
+            "border": "#bee0d5",
+            "foreground": "#3eaa8e"
+          },
+          "pressed": {
+            "background": "#b0dacb",
+            "border": "#bee0d5",
+            "foreground": "#3eaa8e"
+          },
+          "active": {
+            "background": "#9bd0bf",
+            "border": "#82c6b1",
+            "foreground": "#060a09"
+          },
+          "disabled": {
+            "background": "#dbeee7",
+            "border": "#cde7de",
+            "foreground": "#63b89f"
+          },
+          "inverted": {
+            "background": "#060a09",
+            "border": "#ffffff",
+            "foreground": "#a1d4c3"
+          }
+        },
+        "warning": {
+          "default": {
+            "background": "#ffebd6",
+            "border": "#ffdab7",
+            "foreground": "#e99d35"
+          },
+          "hovered": {
+            "background": "#ffdab7",
+            "border": "#ffdab7",
+            "foreground": "#e99d35"
+          },
+          "pressed": {
+            "background": "#fed2a6",
+            "border": "#ffdab7",
+            "foreground": "#e99d35"
+          },
+          "active": {
+            "background": "#fbc891",
+            "border": "#f7bc77",
+            "foreground": "#330704"
+          },
+          "disabled": {
+            "background": "#ffebd6",
+            "border": "#ffe2c7",
+            "foreground": "#f1ac57"
+          },
+          "inverted": {
+            "background": "#330704",
+            "border": "#ffffff",
+            "foreground": "#fccb97"
+          }
+        },
+        "negative": {
+          "default": {
+            "background": "#f1dfe3",
+            "border": "#e6c6cd",
+            "foreground": "#b4647a"
+          },
+          "hovered": {
+            "background": "#e6c6cd",
+            "border": "#e6c6cd",
+            "foreground": "#b4647a"
+          },
+          "pressed": {
+            "background": "#e0bac2",
+            "border": "#e6c6cd",
+            "foreground": "#b4647a"
+          },
+          "active": {
+            "background": "#d8a8b3",
+            "border": "#ce94a3",
+            "foreground": "#0b0708"
+          },
+          "disabled": {
+            "background": "#f1dfe3",
+            "border": "#ecd2d8",
+            "foreground": "#c17b8e"
+          },
+          "inverted": {
+            "background": "#0b0708",
+            "border": "#ffffff",
+            "foreground": "#dbadb8"
+          }
+        }
+      },
+      "popover_shadow": {
+        "blur": 4,
+        "color": "#2c2a4d33",
+        "offset": [
+          1,
+          2
+        ]
+      },
+      "modal_shadow": {
+        "blur": 16,
+        "color": "#2c2a4d33",
+        "offset": [
+          0,
+          2
+        ]
+      },
+      "players": {
+        "0": {
+          "selection": "#57949f3d",
+          "cursor": "#57949f"
+        },
+        "1": {
+          "selection": "#3eaa8e3d",
+          "cursor": "#3eaa8e"
+        },
+        "2": {
+          "selection": "#7c697f3d",
+          "cursor": "#7c697f"
+        },
+        "3": {
+          "selection": "#907aa93d",
+          "cursor": "#907aa9"
+        },
+        "4": {
+          "selection": "#907aa93d",
+          "cursor": "#907aa9"
+        },
+        "5": {
+          "selection": "#2a69833d",
+          "cursor": "#2a6983"
+        },
+        "6": {
+          "selection": "#b4647a3d",
+          "cursor": "#b4647a"
+        },
+        "7": {
+          "selection": "#e99d353d",
+          "cursor": "#e99d35"
+        }
+      },
+      "syntax": {
+        "comment": {
+          "color": "#9893a5"
+        },
+        "operator": {
+          "color": "#286983"
+        },
+        "punctuation": {
+          "color": "#797593"
+        },
+        "variable": {
+          "color": "#575279"
+        },
+        "string": {
+          "color": "#ea9d34"
+        },
+        "type": {
+          "color": "#56949f"
+        },
+        "type.builtin": {
+          "color": "#56949f"
+        },
+        "boolean": {
+          "color": "#d7827e"
+        },
+        "function": {
+          "color": "#d7827e"
+        },
+        "keyword": {
+          "color": "#286983"
+        },
+        "tag": {
+          "color": "#56949f"
+        },
+        "function.method": {
+          "color": "#d7827e"
+        },
+        "title": {
+          "color": "#ea9d34"
+        },
+        "link_text": {
+          "color": "#56949f",
+          "italic": false
+        },
+        "link_uri": {
+          "color": "#d7827e"
+        }
+      },
+      "color_family": {
+        "neutral": {
+          "low": 39.80392156862745,
+          "high": 95.49019607843137,
+          "range": 55.686274509803916,
+          "scaling_value": 1.7957746478873242
+        },
+        "red": {
+          "low": 0,
+          "high": 100,
+          "range": 100,
+          "scaling_value": 1
+        },
+        "orange": {
+          "low": 0,
+          "high": 100,
+          "range": 100,
+          "scaling_value": 1
+        },
+        "yellow": {
+          "low": 8.823529411764707,
+          "high": 100,
+          "range": 91.17647058823529,
+          "scaling_value": 1.0967741935483872
+        },
+        "green": {
+          "low": 0,
+          "high": 100,
+          "range": 100,
+          "scaling_value": 1
+        },
+        "cyan": {
+          "low": 0,
+          "high": 100,
+          "range": 100,
+          "scaling_value": 1
+        },
+        "blue": {
+          "low": 0,
+          "high": 100,
+          "range": 100,
+          "scaling_value": 1
+        },
+        "violet": {
+          "low": 0,
+          "high": 100,
+          "range": 100,
+          "scaling_value": 1
+        },
+        "magenta": {
+          "low": 0,
+          "high": 100,
+          "range": 100,
+          "scaling_value": 1
+        }
+      }
+    }))
+    .unwrap()
+}

crates/storybook2/src/workspace.rs πŸ”—

@@ -28,9 +28,8 @@ impl Workspace {
     }
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element<State = Self> {
-        let theme = rose_pine_dawn();
-
         themed(rose_pine_dawn(), cx, |cx| {
+            let theme = theme(cx);
             div()
                 .size_full()
                 .v_stack()
@@ -201,7 +200,13 @@ impl Titlebar {
                             // .fill(theme.lowest.base.hovered.background)
                             // .active()
                             // .fill(theme.lowest.base.pressed.background)
-                            .child(div().text_sm().child("branch")),
+                            .child(
+                                div()
+                                    .text_sm()
+                                    .text_decoration_1()
+                                    .text_decoration_wavy()
+                                    .child("branch"),
+                            ),
                     ),
             )
     }