Merge branch 'gpui2' into marshall/gpui2-playground

Marshall Bowers created

Change summary

Cargo.lock                                        |  15 
crates/gpui/src/image_cache.rs                    |   1 
crates/gpui/src/platform/mac/atlas.rs             |   1 
crates/gpui/src/platform/mac/renderer.rs          |   1 
crates/gpui2/src/color.rs                         |   9 
crates/gpui3/Cargo.toml                           |   1 
crates/gpui3/build.rs                             |  12 
crates/gpui3/src/app.rs                           |  31 
crates/gpui3/src/app/entity_map.rs                |  11 
crates/gpui3/src/assets.rs                        |  64 ++
crates/gpui3/src/color.rs                         |  12 
crates/gpui3/src/elements/div.rs                  |  33 
crates/gpui3/src/elements/img.rs                  |  70 +-
crates/gpui3/src/elements/svg.rs                  |  30 
crates/gpui3/src/elements/text.rs                 |  37 
crates/gpui3/src/geometry.rs                      | 293 ++++++++++-
crates/gpui3/src/gpui3.rs                         |  24 
crates/gpui3/src/image_cache.rs                   |  99 +++
crates/gpui3/src/platform.rs                      | 104 +++
crates/gpui3/src/platform/mac.rs                  |   2 
crates/gpui3/src/platform/mac/metal_atlas.rs      | 185 +++++++
crates/gpui3/src/platform/mac/metal_renderer.rs   | 287 +++++++++--
crates/gpui3/src/platform/mac/shaders.metal       | 424 +++++++++++-----
crates/gpui3/src/platform/mac/text_system.rs      | 179 +++---
crates/gpui3/src/platform/mac/window.rs           |  25 
crates/gpui3/src/scene.rs                         | 283 +++++++++--
crates/gpui3/src/style.rs                         |  90 +++
crates/gpui3/src/svg_renderer.rs                  |  47 +
crates/gpui3/src/text_system.rs                   | 159 +++--
crates/gpui3/src/text_system/line.rs              | 322 ++++++++++++
crates/gpui3/src/text_system/text_layout_cache.rs | 337 -------------
crates/gpui3/src/util.rs                          |   6 
crates/gpui3/src/window.rs                        | 331 ++++++++++++
crates/storybook2/src/assets.rs                   |  30 +
crates/storybook2/src/collab_panel.rs             |  10 
crates/storybook2/src/storybook2.rs               |  39 -
crates/storybook2/src/theme.rs                    |   4 
crates/storybook2/src/workspace.rs                |  20 
crates/util/src/arc_cow.rs                        |  10 
39 files changed, 2,601 insertions(+), 1,037 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1070,20 +1070,6 @@ name = "bytemuck"
 version = "1.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
-dependencies = [
- "bytemuck_derive",
-]
-
-[[package]]
-name = "bytemuck_derive"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.29",
-]
 
 [[package]]
 name = "byteorder"
@@ -3282,7 +3268,6 @@ dependencies = [
  "bindgen 0.65.1",
  "bitflags 2.4.0",
  "block",
- "bytemuck",
  "cbindgen",
  "cocoa",
  "collections",

crates/gpui/src/image_cache.rs 🔗

@@ -84,7 +84,6 @@ impl ImageCache {
                         let format = image::guess_format(&body)?;
                         let image =
                             image::load_from_memory_with_format(&body, format)?.into_bgra8();
-
                         Ok(ImageData::new(image))
                     }
                 }

crates/gpui/src/platform/mac/atlas.rs 🔗

@@ -109,6 +109,7 @@ impl AtlasAllocator {
             };
             descriptor.set_width(size.x() as u64);
             descriptor.set_height(size.y() as u64);
+
             self.device.new_texture(&descriptor)
         } else {
             self.device.new_texture(&self.texture_descriptor)

crates/gpui/src/platform/mac/renderer.rs 🔗

@@ -632,6 +632,7 @@ impl Renderer {
             ) {
                 // Snap sprite to pixel grid.
                 let origin = (glyph.origin * scale_factor).floor() + sprite.offset.to_f32();
+
                 sprites_by_atlas
                     .entry(sprite.atlas_id)
                     .or_insert_with(Vec::new)

crates/gpui2/src/color.rs 🔗

@@ -160,6 +160,15 @@ pub fn black() -> Hsla {
     }
 }
 
+pub fn white() -> Hsla {
+    Hsla {
+        h: 0.,
+        s: 0.,
+        l: 1.,
+        a: 1.,
+    }
+}
+
 impl From<Rgba> for Hsla {
     fn from(color: Rgba) -> Self {
         let r = color.r;

crates/gpui3/Cargo.toml 🔗

@@ -55,7 +55,6 @@ usvg = { version = "0.14", features = [] }
 uuid = { version = "1.1.2", features = ["v4"] }
 waker-fn = "1.1.0"
 slotmap = "1.0.6"
-bytemuck = { version = "1.14.0", features = ["derive"] }
 schemars.workspace = true
 plane-split = "0.18.0"
 bitflags = "2.4.0"

crates/gpui3/build.rs 🔗

@@ -45,9 +45,14 @@ fn generate_shader_bindings() -> PathBuf {
         "Pixels".into(),
         "PointF".into(),
         "Hsla".into(),
-        "Quad".into(),
+        "ScaledContentMask".into(),
+        "Uniforms".into(),
+        "AtlasTile".into(),
         "QuadInputIndex".into(),
-        "QuadUniforms".into(),
+        "Quad".into(),
+        "SpriteInputIndex".into(),
+        "MonochromeSprite".into(),
+        "PolychromeSprite".into(),
     ]);
     config.no_includes = true;
     config.enumeration.prefix_with_name = true;
@@ -55,11 +60,14 @@ fn generate_shader_bindings() -> PathBuf {
         .with_src(crate_dir.join("src/scene.rs"))
         .with_src(crate_dir.join("src/geometry.rs"))
         .with_src(crate_dir.join("src/color.rs"))
+        .with_src(crate_dir.join("src/window.rs"))
+        .with_src(crate_dir.join("src/platform.rs"))
         .with_src(crate_dir.join("src/platform/mac/metal_renderer.rs"))
         .with_config(config)
         .generate()
         .expect("Unable to generate bindings")
         .write_to_file(&output_path);
+
     output_path
 }
 

crates/gpui3/src/app.rs 🔗

@@ -8,9 +8,9 @@ pub use model_context::*;
 use refineable::Refineable;
 
 use crate::{
-    current_platform, run_on_main, spawn_on_main, Context, LayoutId, MainThread, MainThreadOnly,
-    Platform, PlatformDispatcher, RootView, TextStyle, TextStyleRefinement, TextSystem, Window,
-    WindowContext, WindowHandle, WindowId,
+    current_platform, image_cache::ImageCache, run_on_main, spawn_on_main, AssetSource, Context,
+    LayoutId, MainThread, MainThreadOnly, Platform, PlatformDispatcher, RootView, SvgRenderer,
+    TextStyle, TextStyleRefinement, TextSystem, Window, WindowContext, WindowHandle, WindowId,
 };
 use anyhow::{anyhow, Result};
 use collections::{HashMap, VecDeque};
@@ -23,22 +23,33 @@ use std::{
     mem,
     sync::{Arc, Weak},
 };
-use util::ResultExt;
+use util::{
+    http::{self, HttpClient},
+    ResultExt,
+};
 
 #[derive(Clone)]
 pub struct App(Arc<Mutex<MainThread<AppContext>>>);
 
 impl App {
-    pub fn production() -> Self {
-        Self::new(current_platform())
+    pub fn production(asset_source: Arc<dyn AssetSource>) -> Self {
+        let http_client = http::client();
+        Self::new(current_platform(), asset_source, http_client)
     }
 
     #[cfg(any(test, feature = "test"))]
     pub fn test() -> Self {
-        Self::new(Arc::new(super::TestPlatform::new()))
+        let platform = Arc::new(super::TestPlatform::new());
+        let asset_source = Arc::new(());
+        let http_client = util::http::FakeHttpClient::with_404_response();
+        Self::new(platform, asset_source, http_client)
     }
 
-    fn new(platform: Arc<dyn Platform>) -> Self {
+    fn new(
+        platform: Arc<dyn Platform>,
+        asset_source: Arc<dyn AssetSource>,
+        http_client: Arc<dyn HttpClient>,
+    ) -> Self {
         let dispatcher = platform.dispatcher();
         let text_system = Arc::new(TextSystem::new(platform.text_system()));
         let entities = EntityMap::new();
@@ -49,6 +60,8 @@ impl App {
                 platform: MainThreadOnly::new(platform, dispatcher.clone()),
                 dispatcher,
                 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(),
@@ -83,6 +96,8 @@ pub struct AppContext {
     dispatcher: Arc<dyn PlatformDispatcher>,
     text_system: Arc<TextSystem>,
     pending_updates: usize,
+    pub(crate) svg_renderer: SvgRenderer,
+    pub(crate) image_cache: ImageCache,
     pub(crate) text_style_stack: Vec<TextStyleRefinement>,
     pub(crate) state_stacks_by_type: HashMap<TypeId, Vec<Box<dyn Any + Send + Sync>>>,
     pub(crate) unit_entity: Handle<()>,

crates/gpui3/src/app/entity_map.rs 🔗

@@ -125,11 +125,12 @@ impl<T: Send + Sync> Clone for Handle<T> {
 
 impl<T: Send + Sync> Drop for Handle<T> {
     fn drop(&mut self) {
-        if let Some(ref_counts) = self.ref_counts.upgrade() {
-            if let Some(count) = ref_counts.read().get(self.id) {
-                let prev_count = count.fetch_sub(1, SeqCst);
-                assert_ne!(prev_count, 0, "Detected over-release of a handle.");
-            }
+        if let Some(_ref_counts) = self.ref_counts.upgrade() {
+            // todo!()
+            // if let Some(count) = ref_counts.read().get(self.id) {
+            //     let prev_count = count.fetch_sub(1, SeqCst);
+            //     assert_ne!(prev_count, 0, "Detected over-release of a handle.");
+            // }
         }
     }
 }

crates/gpui3/src/assets.rs 🔗

@@ -0,0 +1,64 @@
+use crate::{size, DevicePixels, Result, SharedString, Size};
+use anyhow::anyhow;
+use image::{Bgra, ImageBuffer};
+use std::{
+    borrow::Cow,
+    fmt,
+    hash::Hash,
+    sync::atomic::{AtomicUsize, Ordering::SeqCst},
+};
+
+pub trait AssetSource: 'static + Send + Sync {
+    fn load(&self, path: &SharedString) -> Result<Cow<[u8]>>;
+    fn list(&self, path: &SharedString) -> Result<Vec<SharedString>>;
+}
+
+impl AssetSource for () {
+    fn load(&self, path: &SharedString) -> Result<Cow<[u8]>> {
+        Err(anyhow!(
+            "get called on empty asset provider with \"{}\"",
+            path
+        ))
+    }
+
+    fn list(&self, _path: &SharedString) -> Result<Vec<SharedString>> {
+        Ok(vec![])
+    }
+}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub struct ImageId(usize);
+
+pub struct ImageData {
+    pub id: ImageId,
+    data: ImageBuffer<Bgra<u8>, Vec<u8>>,
+}
+
+impl ImageData {
+    pub fn new(data: ImageBuffer<Bgra<u8>, Vec<u8>>) -> Self {
+        static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
+
+        Self {
+            id: ImageId(NEXT_ID.fetch_add(1, SeqCst)),
+            data,
+        }
+    }
+
+    pub fn as_bytes(&self) -> &[u8] {
+        &self.data
+    }
+
+    pub fn size(&self) -> Size<DevicePixels> {
+        let (width, height) = self.data.dimensions();
+        size(width.into(), height.into())
+    }
+}
+
+impl fmt::Debug for ImageData {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("ImageData")
+            .field("id", &self.id)
+            .field("size", &self.data.dimensions())
+            .finish()
+    }
+}

crates/gpui3/src/color.rs 🔗

@@ -1,6 +1,5 @@
 #![allow(dead_code)]
 
-use bytemuck::{Pod, Zeroable};
 use serde::de::{self, Deserialize, Deserializer, Visitor};
 use std::fmt;
 use std::num::ParseIntError;
@@ -118,7 +117,7 @@ impl TryFrom<&'_ str> for Rgba {
     }
 }
 
-#[derive(Default, Copy, Clone, Debug, PartialEq, Zeroable, Pod)]
+#[derive(Default, Copy, Clone, Debug, PartialEq)]
 #[repr(C)]
 pub struct Hsla {
     pub h: f32,
@@ -147,6 +146,15 @@ pub fn black() -> Hsla {
     }
 }
 
+pub fn white() -> Hsla {
+    Hsla {
+        h: 0.,
+        s: 0.,
+        l: 1.,
+        a: 1.,
+    }
+}
+
 impl Hsla {
     /// Returns true if the HSLA color is fully transparent, false otherwise.
     pub fn is_transparent(&self) -> bool {

crates/gpui3/src/elements/div.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
     AnyElement, Bounds, Element, Layout, LayoutId, Overflow, ParentElement, Pixels, Point,
-    Refineable, RefinementCascade, Result, StackContext, Style, StyleHelpers, Styled, ViewContext,
+    Refineable, RefinementCascade, Result, Style, StyleHelpers, Styled, ViewContext,
 };
 use parking_lot::Mutex;
 use smallvec::SmallVec;
@@ -33,16 +33,9 @@ impl<S: 'static + Send + Sync> Element for Div<S> {
         cx: &mut ViewContext<S>,
     ) -> Result<(LayoutId, Self::FrameState)> {
         let style = self.computed_style();
-        let child_layout_ids = if let Some(text_style) = style.text_style(cx) {
-            cx.with_text_style(text_style.clone(), |cx| self.layout_children(view, cx))?
-        } else {
-            self.layout_children(view, cx)?
-        };
-
-        Ok((
-            cx.request_layout(style.into(), child_layout_ids.clone())?,
-            child_layout_ids,
-        ))
+        let child_layout_ids = style.apply_text_style(cx, |cx| self.layout_children(view, cx))?;
+        let layout_id = cx.request_layout(style.into(), child_layout_ids.clone())?;
+        Ok((layout_id, child_layout_ids))
     }
 
     fn paint(
@@ -56,20 +49,18 @@ impl<S: 'static + Send + Sync> Element for Div<S> {
 
         let style = self.computed_style();
         style.paint(order, bounds, cx);
-        let overflow = &style.overflow;
+
         // // todo!("support only one dimension being hidden")
+        let overflow = &style.overflow;
         // if style.overflow.y != Overflow::Visible || style.overflow.x != Overflow::Visible {
-        //     cx.scene().push_layer(Some(bounds));
-        //     pop_layer = true;
+        //     cx.clip(layout.bounds, style.corner_radii, || )
         // }
-        if let Some(text_style) = style.text_style(cx) {
-            cx.with_text_style(text_style.clone(), |cx| {
-                self.paint_children(overflow, state, cx)
-            })?;
-        } else {
-            self.paint_children(overflow, state, cx)?;
-        }
 
+        style.apply_text_style(cx, |cx| {
+            style.apply_overflow(layout.bounds, cx, |cx| {
+                self.paint_children(overflow, state, cx)
+            })
+        })?;
         self.handle_scroll(order, bounds, style.overflow.clone(), child_layouts, cx);
 
         // todo!("enable inspector")

crates/gpui3/src/elements/img.rs 🔗

@@ -1,11 +1,15 @@
-use crate::{Element, Layout, LayoutId, Result, Style, StyleHelpers, Styled};
+use crate::{
+    Element, Layout, LayoutId, Result, SharedString, Style, StyleHelpers, Styled, ViewContext,
+};
+use futures::FutureExt;
 use refineable::RefinementCascade;
 use std::marker::PhantomData;
-use util::arc_cow::ArcCow;
+use util::ResultExt;
 
 pub struct Img<S> {
     style: RefinementCascade<Style>,
-    uri: Option<ArcCow<'static, str>>,
+    uri: Option<SharedString>,
+    grayscale: bool,
     state_type: PhantomData<S>,
 }
 
@@ -13,15 +17,21 @@ pub fn img<S>() -> Img<S> {
     Img {
         style: RefinementCascade::default(),
         uri: None,
+        grayscale: false,
         state_type: PhantomData,
     }
 }
 
 impl<S> Img<S> {
-    pub fn uri(mut self, uri: impl Into<ArcCow<'static, str>>) -> Self {
+    pub fn uri(mut self, uri: impl Into<SharedString>) -> Self {
         self.uri = Some(uri.into());
         self
     }
+
+    pub fn grayscale(mut self, grayscale: bool) -> Self {
+        self.grayscale = grayscale;
+        self
+    }
 }
 
 impl<S: 'static> Element for Img<S> {
@@ -31,7 +41,7 @@ impl<S: 'static> Element for Img<S> {
     fn layout(
         &mut self,
         _: &mut Self::State,
-        cx: &mut crate::ViewContext<Self::State>,
+        cx: &mut ViewContext<Self::State>,
     ) -> anyhow::Result<(LayoutId, Self::FrameState)>
     where
         Self: Sized,
@@ -46,7 +56,7 @@ impl<S: 'static> Element for Img<S> {
         layout: Layout,
         _: &mut Self::State,
         _: &mut Self::FrameState,
-        cx: &mut crate::ViewContext<Self::State>,
+        cx: &mut ViewContext<Self::State>,
     ) -> Result<()> {
         let style = self.computed_style();
         let order = layout.order;
@@ -54,36 +64,24 @@ impl<S: 'static> Element for Img<S> {
 
         style.paint(order, bounds, cx);
 
-        // if let Some(uri) = &self.uri {
-        //     let image_future = cx.image_cache.get(uri.clone());
-        //     if let Some(data) = image_future
-        //         .clone()
-        //         .now_or_never()
-        //         .and_then(ResultExt::log_err)
-        //     {
-        //         let rem_size = cx.rem_size();
-        //         cx.scene().push_image(scene::Image {
-        //             bounds,
-        //             border: gpui::Border {
-        //                 color: style.border_color.unwrap_or_default().into(),
-        //                 top: style.border_widths.top.to_pixels(rem_size),
-        //                 right: style.border_widths.right.to_pixels(rem_size),
-        //                 bottom: style.border_widths.bottom.to_pixels(rem_size),
-        //                 left: style.border_widths.left.to_pixels(rem_size),
-        //             },
-        //             corner_radii: style.corner_radii.to_gpui(bounds.size(), rem_size),
-        //             grayscale: false,
-        //             data,
-        //         })
-        //     } else {
-        //         cx.spawn(|this, mut cx| async move {
-        //             if image_future.await.log_err().is_some() {
-        //                 this.update(&mut cx, |_, cx| cx.notify()).ok();
-        //             }
-        //         })
-        //         .detach();
-        //     }
-        // }
+        if let Some(uri) = &self.uri {
+            let image_future = cx.image_cache.get(uri.clone());
+            if let Some(data) = image_future
+                .clone()
+                .now_or_never()
+                .and_then(ResultExt::log_err)
+            {
+                cx.paint_image(bounds, order, data, self.grayscale)?;
+            } else {
+                log::warn!("image not loaded yet");
+                // cx.spawn(|this, mut cx| async move {
+                //     if image_future.await.log_err().is_some() {
+                //         this.update(&mut cx, |_, cx| cx.notify()).ok();
+                //     }
+                // })
+                // .detach();
+            }
+        }
         Ok(())
     }
 }

crates/gpui3/src/elements/svg.rs 🔗

@@ -1,9 +1,9 @@
-use crate::{Element, Layout, LayoutId, Result, Style, StyleHelpers, Styled};
+use crate::{Element, Layout, LayoutId, Result, SharedString, Style, StyleHelpers, Styled};
 use refineable::RefinementCascade;
-use std::{borrow::Cow, marker::PhantomData};
+use std::marker::PhantomData;
 
 pub struct Svg<S> {
-    path: Option<Cow<'static, str>>,
+    path: Option<SharedString>,
     style: RefinementCascade<Style>,
     state_type: PhantomData<S>,
 }
@@ -17,7 +17,7 @@ pub fn svg<S>() -> Svg<S> {
 }
 
 impl<S> Svg<S> {
-    pub fn path(mut self, path: impl Into<Cow<'static, str>>) -> Self {
+    pub fn path(mut self, path: impl Into<SharedString>) -> Self {
         self.path = Some(path.into());
         self
     }
@@ -41,28 +41,18 @@ impl<S: 'static> Element for Svg<S> {
 
     fn paint(
         &mut self,
-        _layout: Layout,
+        layout: Layout,
         _: &mut Self::State,
         _: &mut Self::FrameState,
-        _cx: &mut crate::ViewContext<S>,
+        cx: &mut crate::ViewContext<S>,
     ) -> Result<()>
     where
         Self: Sized,
     {
-        // todo!
-        // 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) {
-        //     if let Some(svg_tree) = cx.asset_cache.svg(path).log_err() {
-        //         let icon = scene::Icon {
-        //             bounds: layout.bounds + parent_origin,
-        //             svg: svg_tree,
-        //             path: path.clone(),
-        //             color: Rgba::from(fill_color).into(),
-        //         };
-
-        //         cx.scene().push_icon(icon);
-        //     }
-        // }
+        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)?;
+        }
         Ok(())
     }
 }

crates/gpui3/src/elements/text.rs 🔗

@@ -32,15 +32,13 @@ pub struct Text<S> {
 
 impl<S: 'static> Element for Text<S> {
     type State = S;
-    type FrameState = Arc<Mutex<Option<TextLayout>>>;
+    type FrameState = Arc<Mutex<Option<TextFrameState>>>;
 
     fn layout(
         &mut self,
         _view: &mut S,
         cx: &mut ViewContext<S>,
     ) -> Result<(LayoutId, Self::FrameState)> {
-        dbg!("layout text");
-
         let text_system = cx.text_system().clone();
         let text_style = cx.text_style();
         let font_size = text_style.font_size * cx.rem_size();
@@ -48,13 +46,12 @@ impl<S: 'static> Element for Text<S> {
             .line_height
             .to_pixels(font_size.into(), cx.rem_size());
         let text = self.text.clone();
-        let paint_state = Arc::new(Mutex::new(None));
+        let frame_state = Arc::new(Mutex::new(None));
 
         let rem_size = cx.rem_size();
         let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
-            let frame_state = paint_state.clone();
+            let frame_state = frame_state.clone();
             move |_, _| {
-                dbg!("starting measurement");
                 let Some(line_layout) = text_system
                     .layout_line(
                         text.as_ref(),
@@ -65,57 +62,51 @@ impl<S: 'static> Element for Text<S> {
                 else {
                     return Size::default();
                 };
-                dbg!("bbbb");
 
                 let size = Size {
                     width: line_layout.width(),
                     height: line_height,
                 };
 
-                frame_state.lock().replace(TextLayout {
+                frame_state.lock().replace(TextFrameState {
                     line: Arc::new(line_layout),
                     line_height,
                 });
 
-                dbg!(size)
+                size
             }
         });
 
-        dbg!("got to end of text layout");
-        Ok((layout_id?, paint_state))
+        Ok((layout_id?, frame_state))
     }
 
     fn paint<'a>(
         &mut self,
         layout: Layout,
         _: &mut Self::State,
-        paint_state: &mut Self::FrameState,
+        frame_state: &mut Self::FrameState,
         cx: &mut ViewContext<S>,
     ) -> Result<()> {
-        let bounds = layout.bounds;
-
         let line;
         let line_height;
         {
-            let paint_state = paint_state.lock();
-            let paint_state = paint_state
+            let frame_state = frame_state.lock();
+            let frame_state = frame_state
                 .as_ref()
                 .expect("measurement has not been performed");
-            line = paint_state.line.clone();
-            line_height = paint_state.line_height;
+            line = frame_state.line.clone();
+            line_height = frame_state.line_height;
         }
 
-        let _text_style = cx.text_style();
-
         // todo!("We haven't added visible bounds to the new element system yet, so this is a placeholder.");
-        let visible_bounds = bounds;
-        line.paint(bounds.origin, visible_bounds, line_height, cx)?;
+        let visible_bounds = layout.bounds;
+        line.paint(&layout, visible_bounds, line_height, cx)?;
 
         Ok(())
     }
 }
 
-pub struct TextLayout {
+pub struct TextFrameState {
     line: Arc<Line>,
     line_height: Pixels,
 }

crates/gpui3/src/geometry.rs 🔗

@@ -1,4 +1,3 @@
-use bytemuck::{Pod, Zeroable};
 use core::fmt::Debug;
 use derive_more::{Add, AddAssign, Div, Mul, Sub, SubAssign};
 use refineable::Refineable;
@@ -29,12 +28,21 @@ impl<T: Clone + Debug> Point<T> {
     }
 }
 
+impl Point<Pixels> {
+    pub fn scale(&self, factor: f32) -> Point<ScaledPixels> {
+        Point {
+            x: self.x.scale(factor),
+            y: self.y.scale(factor),
+        }
+    }
+}
+
 impl<T, Rhs> Mul<Rhs> for Point<T>
 where
-    T: Mul<Rhs, Output = Rhs> + Clone + Debug,
+    T: Mul<Rhs, Output = T> + Clone + Debug,
     Rhs: Clone + Debug,
 {
-    type Output = Point<Rhs>;
+    type Output = Point<T>;
 
     fn mul(self, rhs: Rhs) -> Self::Output {
         Point {
@@ -102,10 +110,7 @@ impl<T: Clone + Debug> Clone for Point<T> {
     }
 }
 
-unsafe impl<T: Clone + Debug + Zeroable + Pod> Zeroable for Point<T> {}
-unsafe impl<T: Clone + Debug + Zeroable + Pod> Pod for Point<T> {}
-
-#[derive(Refineable, Default, Clone, Copy, Debug, PartialEq, Div)]
+#[derive(Refineable, Default, Clone, Copy, Debug, PartialEq, Div, Hash)]
 #[refineable(debug)]
 #[repr(C)]
 pub struct Size<T: Clone + Debug> {
@@ -113,9 +118,6 @@ pub struct Size<T: Clone + Debug> {
     pub height: T,
 }
 
-unsafe impl<T: Clone + Debug + Zeroable + Pod> Zeroable for Size<T> {}
-unsafe impl<T: Clone + Debug + Zeroable + Pod> Pod for Size<T> {}
-
 pub fn size<T: Clone + Debug>(width: T, height: T) -> Size<T> {
     Size { width, height }
 }
@@ -129,6 +131,32 @@ impl<T: Clone + Debug> Size<T> {
     }
 }
 
+impl Size<Pixels> {
+    pub fn scale(&self, factor: f32) -> Size<ScaledPixels> {
+        Size {
+            width: self.width.scale(factor),
+            height: self.height.scale(factor),
+        }
+    }
+}
+
+impl<T: Clone + Debug + Ord> Size<T> {
+    pub fn max(&self, other: &Self) -> Self {
+        Size {
+            width: if self.width >= other.width {
+                self.width.clone()
+            } else {
+                other.width.clone()
+            },
+            height: if self.height >= other.height {
+                self.height.clone()
+            } else {
+                other.height.clone()
+            },
+        }
+    }
+}
+
 impl<T, Rhs> Mul<Rhs> for Size<T>
 where
     T: Mul<Rhs, Output = Rhs> + Debug + Clone,
@@ -151,11 +179,13 @@ 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(val: Size<Option<Pixels>>) -> Self {
+    fn from(size: Size<Option<Pixels>>) -> Self {
         Size {
-            width: val.width.map(|p| p.0 as f32),
-            height: val.height.map(|p| p.0 as f32),
+            width: size.width.map(|p| p.0 as f32),
+            height: size.height.map(|p| p.0 as f32),
         }
     }
 }
@@ -187,7 +217,7 @@ impl Size<Length> {
     }
 }
 
-#[derive(Refineable, Clone, Default, Debug, PartialEq)]
+#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
 #[refineable(debug)]
 #[repr(C)]
 pub struct Bounds<T: Clone + Debug> {
@@ -195,13 +225,24 @@ pub struct Bounds<T: Clone + Debug> {
     pub size: Size<T>,
 }
 
-unsafe impl<T: Clone + Debug + Zeroable + Pod> Zeroable for Bounds<T> {}
-unsafe impl<T: Clone + Debug + Zeroable + Pod> Pod for Bounds<T> {}
+impl<T: Clone + Debug + Sub<Output = T>> Bounds<T> {
+    pub fn from_corners(upper_left: Point<T>, lower_right: Point<T>) -> Self {
+        let origin = Point {
+            x: upper_left.x.clone(),
+            y: upper_left.y.clone(),
+        };
+        let size = Size {
+            width: lower_right.x - upper_left.x,
+            height: lower_right.y - upper_left.y,
+        };
+        Bounds { origin, size }
+    }
+}
 
-// Bounds<f32> * Pixels = Bounds<Pixels>
 impl<T, Rhs> Mul<Rhs> for Bounds<T>
 where
     T: Mul<Rhs, Output = Rhs> + Clone + Debug,
+    Point<T>: Mul<Rhs, Output = Point<Rhs>>,
     Rhs: Clone + Debug,
 {
     type Output = Bounds<Rhs>;
@@ -267,9 +308,18 @@ impl<T: Clone + Debug + PartialOrd + Add<T, Output = T>> Bounds<T> {
     }
 }
 
+impl Bounds<Pixels> {
+    pub fn scale(&self, factor: f32) -> Bounds<ScaledPixels> {
+        Bounds {
+            origin: self.origin.scale(factor),
+            size: self.size.scale(factor),
+        }
+    }
+}
+
 impl<T: Clone + Debug + Copy> Copy for Bounds<T> {}
 
-#[derive(Refineable, Clone, Default, Debug)]
+#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
 #[refineable(debug)]
 #[repr(C)]
 pub struct Edges<T: Clone + Debug> {
@@ -303,10 +353,6 @@ impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Edges<T>
 
 impl<T: Clone + Debug + Copy> Copy for Edges<T> {}
 
-unsafe impl<T: Clone + Debug + Zeroable + Pod> Zeroable for Edges<T> {}
-
-unsafe impl<T: Clone + Debug + Zeroable + Pod> Pod for Edges<T> {}
-
 impl<T: Clone + Debug> Edges<T> {
     pub fn map<U: Clone + Debug, F: Fn(&T) -> U>(&self, f: F) -> Edges<U> {
         Edges {
@@ -376,7 +422,7 @@ impl Edges<AbsoluteLength> {
     }
 }
 
-#[derive(Refineable, Clone, Default, Debug)]
+#[derive(Refineable, Clone, Default, Debug, Eq, PartialEq)]
 #[refineable(debug)]
 #[repr(C)]
 pub struct Corners<T: Clone + Debug> {
@@ -386,6 +432,28 @@ pub struct Corners<T: Clone + Debug> {
     pub bottom_left: T,
 }
 
+impl Corners<AbsoluteLength> {
+    pub fn to_pixels(&self, rem_size: Pixels) -> Corners<Pixels> {
+        Corners {
+            top_left: self.top_left.to_pixels(rem_size),
+            top_right: self.top_right.to_pixels(rem_size),
+            bottom_right: self.bottom_right.to_pixels(rem_size),
+            bottom_left: self.bottom_left.to_pixels(rem_size),
+        }
+    }
+}
+
+impl Corners<Pixels> {
+    pub fn scale(&self, factor: f32) -> Corners<ScaledPixels> {
+        Corners {
+            top_left: self.top_left.scale(factor),
+            top_right: self.top_right.scale(factor),
+            bottom_right: self.bottom_right.scale(factor),
+            bottom_left: self.bottom_left.scale(factor),
+        }
+    }
+}
+
 impl<T: Clone + Debug> Corners<T> {
     pub fn map<U: Clone + Debug, F: Fn(&T) -> U>(&self, f: F) -> Corners<U> {
         Corners {
@@ -421,10 +489,6 @@ impl<T: Clone + Debug + Mul<S, Output = T>, S: Clone> MulAssign<S> for Corners<T
 
 impl<T: Clone + Debug + Copy> Copy for Corners<T> {}
 
-unsafe impl<T: Clone + Debug + Zeroable + Pod> Zeroable for Corners<T> {}
-
-unsafe impl<T: Clone + Debug + Zeroable + Pod> Pod for Corners<T> {}
-
 #[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd)]
 #[repr(transparent)]
 pub struct Pixels(pub(crate) f32);
@@ -445,13 +509,19 @@ impl Mul<Pixels> for f32 {
     }
 }
 
+impl MulAssign<f32> for Pixels {
+    fn mul_assign(&mut self, other: f32) {
+        self.0 *= other;
+    }
+}
+
 impl Pixels {
     pub fn round(&self) -> Self {
         Self(self.0.round())
     }
 
-    pub fn to_device_pixels(&self, scale: f32) -> DevicePixels {
-        DevicePixels((self.0 * scale).ceil() as u32)
+    pub fn scale(&self, factor: f32) -> ScaledPixels {
+        ScaledPixels(self.0 * factor)
     }
 }
 
@@ -478,20 +548,17 @@ impl std::hash::Hash for Pixels {
 }
 
 impl From<f64> for Pixels {
-    fn from(val: f64) -> Self {
-        Pixels(val as f32)
+    fn from(pixels: f64) -> Self {
+        Pixels(pixels as f32)
     }
 }
 
 impl From<f32> for Pixels {
-    fn from(val: f32) -> Self {
-        Pixels(val)
+    fn from(pixels: f32) -> Self {
+        Pixels(pixels)
     }
 }
 
-unsafe impl bytemuck::Pod for Pixels {}
-unsafe impl bytemuck::Zeroable for Pixels {}
-
 impl Debug for Pixels {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         write!(f, "{} px", self.0)
@@ -517,23 +584,90 @@ impl From<Pixels> for f64 {
 }
 
 #[derive(
-    Clone, Copy, Debug, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd,
+    Add, AddAssign, Clone, Copy, Default, Div, Eq, Hash, Ord, PartialEq, PartialOrd, Sub, SubAssign,
 )]
 #[repr(transparent)]
-pub struct DevicePixels(pub(crate) u32);
+pub struct DevicePixels(pub(crate) i32);
 
-unsafe impl bytemuck::Pod for DevicePixels {}
-unsafe impl bytemuck::Zeroable for DevicePixels {}
+impl DevicePixels {
+    pub fn to_bytes(&self, bytes_per_pixel: u8) -> u32 {
+        self.0 as u32 * bytes_per_pixel as u32
+    }
+}
 
-impl From<DevicePixels> for u32 {
+impl std::fmt::Debug for DevicePixels {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{} px (device)", self.0)
+    }
+}
+
+impl From<DevicePixels> for i32 {
     fn from(device_pixels: DevicePixels) -> Self {
         device_pixels.0
     }
 }
 
+impl From<i32> for DevicePixels {
+    fn from(device_pixels: i32) -> Self {
+        DevicePixels(device_pixels)
+    }
+}
+
 impl From<u32> for DevicePixels {
-    fn from(val: u32) -> Self {
-        DevicePixels(val)
+    fn from(device_pixels: u32) -> Self {
+        DevicePixels(device_pixels as i32)
+    }
+}
+
+impl From<DevicePixels> for u32 {
+    fn from(device_pixels: DevicePixels) -> Self {
+        device_pixels.0 as u32
+    }
+}
+
+impl From<DevicePixels> for u64 {
+    fn from(device_pixels: DevicePixels) -> Self {
+        device_pixels.0 as u64
+    }
+}
+
+impl From<u64> for DevicePixels {
+    fn from(device_pixels: u64) -> Self {
+        DevicePixels(device_pixels as i32)
+    }
+}
+
+#[derive(Clone, Copy, Default, Add, AddAssign, Sub, SubAssign, Div, PartialEq, PartialOrd)]
+#[repr(transparent)]
+pub struct ScaledPixels(pub(crate) f32);
+
+impl ScaledPixels {
+    pub fn floor(&self) -> Self {
+        Self(self.0.floor())
+    }
+
+    pub fn ceil(&self) -> Self {
+        Self(self.0.ceil())
+    }
+}
+
+impl Eq for ScaledPixels {}
+
+impl Debug for ScaledPixels {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{} px (scaled)", self.0)
+    }
+}
+
+impl From<ScaledPixels> for DevicePixels {
+    fn from(scaled: ScaledPixels) -> Self {
+        DevicePixels(scaled.0.ceil() as i32)
+    }
+}
+
+impl From<DevicePixels> for ScaledPixels {
+    fn from(device: DevicePixels) -> Self {
+        ScaledPixels(device.0 as f32)
     }
 }
 
@@ -721,3 +855,76 @@ impl From<()> for Length {
         Self::Definite(DefiniteLength::default())
     }
 }
+
+pub trait IsZero {
+    fn is_zero(&self) -> bool;
+}
+
+impl IsZero for DevicePixels {
+    fn is_zero(&self) -> bool {
+        self.0 == 0
+    }
+}
+
+impl IsZero for ScaledPixels {
+    fn is_zero(&self) -> bool {
+        self.0 == 0.
+    }
+}
+
+impl IsZero for Pixels {
+    fn is_zero(&self) -> bool {
+        self.0 == 0.
+    }
+}
+
+impl IsZero for Rems {
+    fn is_zero(&self) -> bool {
+        self.0 == 0.
+    }
+}
+
+impl IsZero for AbsoluteLength {
+    fn is_zero(&self) -> bool {
+        match self {
+            AbsoluteLength::Pixels(pixels) => pixels.is_zero(),
+            AbsoluteLength::Rems(rems) => rems.is_zero(),
+        }
+    }
+}
+
+impl IsZero for DefiniteLength {
+    fn is_zero(&self) -> bool {
+        match self {
+            DefiniteLength::Absolute(length) => length.is_zero(),
+            DefiniteLength::Fraction(fraction) => *fraction == 0.,
+        }
+    }
+}
+
+impl IsZero for Length {
+    fn is_zero(&self) -> bool {
+        match self {
+            Length::Definite(length) => length.is_zero(),
+            Length::Auto => false,
+        }
+    }
+}
+
+impl<T: IsZero + Debug + Clone> IsZero for Point<T> {
+    fn is_zero(&self) -> bool {
+        self.x.is_zero() && self.y.is_zero()
+    }
+}
+
+impl<T: IsZero + Debug + Clone> IsZero for Size<T> {
+    fn is_zero(&self) -> bool {
+        self.width.is_zero() || self.height.is_zero()
+    }
+}
+
+impl<T: IsZero + Debug + Clone> IsZero for Bounds<T> {
+    fn is_zero(&self) -> bool {
+        self.origin.is_zero() && self.size.is_zero()
+    }
+}

crates/gpui3/src/gpui3.rs 🔗

@@ -1,14 +1,17 @@
 mod app;
+mod assets;
 mod color;
 mod element;
 mod elements;
 mod executor;
 mod geometry;
+mod image_cache;
 mod platform;
 mod scene;
 mod style;
 mod style_helpers;
 mod styled;
+mod svg_renderer;
 mod taffy;
 mod text_system;
 mod util;
@@ -17,12 +20,15 @@ mod window;
 
 pub use anyhow::Result;
 pub use app::*;
+pub use assets::*;
 pub use color::*;
 pub use element::*;
 pub use elements::*;
 pub use executor::*;
 pub use geometry::*;
 pub use gpui3_macros::*;
+pub use svg_renderer::*;
+
 pub use platform::*;
 pub use refineable::*;
 pub use scene::*;
@@ -83,16 +89,16 @@ impl<T> DerefMut for MainThread<T> {
     }
 }
 
-pub trait StackContext {
-    fn app(&mut self) -> &mut AppContext;
+pub trait BorrowAppContext {
+    fn app_mut(&mut self) -> &mut AppContext;
 
     fn with_text_style<F, R>(&mut self, style: TextStyleRefinement, f: F) -> R
     where
         F: FnOnce(&mut Self) -> R,
     {
-        self.app().push_text_style(style);
+        self.app_mut().push_text_style(style);
         let result = f(self);
-        self.app().pop_text_style();
+        self.app_mut().pop_text_style();
         result
     }
 
@@ -100,9 +106,9 @@ pub trait StackContext {
     where
         F: FnOnce(&mut Self) -> R,
     {
-        self.app().push_state(state);
+        self.app_mut().push_state(state);
         let result = f(self);
-        self.app().pop_state::<T>();
+        self.app_mut().pop_state::<T>();
         result
     }
 }
@@ -144,6 +150,12 @@ impl std::fmt::Debug for SharedString {
     }
 }
 
+impl std::fmt::Display for SharedString {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.0.as_ref())
+    }
+}
+
 impl<T: Into<ArcCow<'static, str>>> From<T> for SharedString {
     fn from(value: T) -> Self {
         Self(value.into())

crates/gpui3/src/image_cache.rs 🔗

@@ -0,0 +1,99 @@
+use crate::{ImageData, ImageId, SharedString};
+use collections::HashMap;
+use futures::{
+    future::{BoxFuture, Shared},
+    AsyncReadExt, FutureExt,
+};
+use image::ImageError;
+use parking_lot::Mutex;
+use std::sync::Arc;
+use thiserror::Error;
+use util::http::{self, HttpClient};
+
+#[derive(PartialEq, Eq, Hash, Clone)]
+pub struct RenderImageParams {
+    pub(crate) image_id: ImageId,
+}
+
+#[derive(Debug, Error, Clone)]
+pub enum Error {
+    #[error("http error: {0}")]
+    Client(#[from] http::Error),
+    #[error("IO error: {0}")]
+    Io(Arc<std::io::Error>),
+    #[error("unexpected http status: {status}, body: {body}")]
+    BadStatus {
+        status: http::StatusCode,
+        body: String,
+    },
+    #[error("image error: {0}")]
+    Image(Arc<ImageError>),
+}
+
+impl From<std::io::Error> for Error {
+    fn from(error: std::io::Error) -> Self {
+        Error::Io(Arc::new(error))
+    }
+}
+
+impl From<ImageError> for Error {
+    fn from(error: ImageError) -> Self {
+        Error::Image(Arc::new(error))
+    }
+}
+
+pub struct ImageCache {
+    client: Arc<dyn HttpClient>,
+    images: Arc<Mutex<HashMap<SharedString, FetchImageFuture>>>,
+}
+
+type FetchImageFuture = Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>>;
+
+impl ImageCache {
+    pub fn new(client: Arc<dyn HttpClient>) -> Self {
+        ImageCache {
+            client,
+            images: Default::default(),
+        }
+    }
+
+    pub fn get(
+        &self,
+        uri: impl Into<SharedString>,
+    ) -> Shared<BoxFuture<'static, Result<Arc<ImageData>, Error>>> {
+        let uri = uri.into();
+        let mut images = self.images.lock();
+
+        match images.get(&uri) {
+            Some(future) => future.clone(),
+            None => {
+                let client = self.client.clone();
+                let future = {
+                    let uri = uri.clone();
+                    async move {
+                        let mut response = client.get(uri.as_ref(), ().into(), true).await?;
+                        let mut body = Vec::new();
+                        response.body_mut().read_to_end(&mut body).await?;
+
+                        if !response.status().is_success() {
+                            return Err(Error::BadStatus {
+                                status: response.status(),
+                                body: String::from_utf8_lossy(&body).into_owned(),
+                            });
+                        }
+
+                        let format = image::guess_format(&body)?;
+                        let image =
+                            image::load_from_memory_with_format(&body, format)?.into_bgra8();
+                        Ok(Arc::new(ImageData::new(image)))
+                    }
+                }
+                .boxed()
+                .shared();
+
+                images.insert(uri, future.clone());
+                future
+            }
+        }
+    }
+}

crates/gpui3/src/platform.rs 🔗

@@ -5,15 +5,17 @@ mod mac;
 #[cfg(any(test, feature = "test"))]
 mod test;
 
+use crate::image_cache::RenderImageParams;
 use crate::{
-    AnyWindowHandle, Bounds, Font, FontId, FontMetrics, GlyphId, LineLayout, Pixels, Point, Result,
-    Scene, SharedString, Size,
+    AnyWindowHandle, Bounds, DevicePixels, Font, FontId, FontMetrics, GlyphId, Pixels, Point,
+    RenderGlyphParams, RenderSvgParams, Result, Scene, ShapedLine, SharedString, Size,
 };
 use anyhow::anyhow;
 use async_task::Runnable;
 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::{
@@ -122,7 +124,7 @@ pub trait PlatformWindow {
     fn screen(&self) -> Rc<dyn PlatformScreen>;
     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 InputHandler>);
+    fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>);
     fn prompt(
         &self,
         level: WindowPromptLevel,
@@ -146,6 +148,8 @@ pub trait PlatformWindow {
     fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
     fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
     fn draw(&self, scene: Scene);
+
+    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
 }
 
 pub trait PlatformDispatcher: Send + Sync {
@@ -161,16 +165,9 @@ pub trait PlatformTextSystem: Send + Sync {
     fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>>;
     fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>>;
     fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId>;
-    fn rasterize_glyph(
-        &self,
-        font_id: FontId,
-        font_size: f32,
-        glyph_id: GlyphId,
-        subpixel_shift: Point<Pixels>,
-        scale_factor: f32,
-        options: RasterizationOptions,
-    ) -> Option<(Bounds<u32>, Vec<u8>)>;
-    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, FontId)]) -> LineLayout;
+    fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>>;
+    fn rasterize_glyph(&self, params: &RenderGlyphParams) -> Result<(Size<DevicePixels>, Vec<u8>)>;
+    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[(usize, FontId)]) -> ShapedLine;
     fn wrap_line(
         &self,
         text: &str,
@@ -180,7 +177,80 @@ pub trait PlatformTextSystem: Send + Sync {
     ) -> Vec<usize>;
 }
 
-pub trait InputHandler {
+#[derive(PartialEq, Eq, Hash, Clone)]
+pub enum AtlasKey {
+    Glyph(RenderGlyphParams),
+    Svg(RenderSvgParams),
+    Image(RenderImageParams),
+}
+
+impl AtlasKey {
+    pub fn is_monochrome(&self) -> bool {
+        match self {
+            AtlasKey::Glyph(params) => !params.is_emoji,
+            AtlasKey::Svg(_) => true,
+            AtlasKey::Image(_) => false,
+        }
+    }
+}
+
+impl From<RenderGlyphParams> for AtlasKey {
+    fn from(params: RenderGlyphParams) -> Self {
+        Self::Glyph(params)
+    }
+}
+
+impl From<RenderSvgParams> for AtlasKey {
+    fn from(params: RenderSvgParams) -> Self {
+        Self::Svg(params)
+    }
+}
+
+impl From<RenderImageParams> for AtlasKey {
+    fn from(params: RenderImageParams) -> Self {
+        Self::Image(params)
+    }
+}
+
+pub trait PlatformAtlas: Send + Sync {
+    fn get_or_insert_with<'a>(
+        &self,
+        key: &AtlasKey,
+        build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
+    ) -> Result<AtlasTile>;
+
+    fn clear(&self);
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+#[repr(C)]
+pub struct AtlasTile {
+    pub(crate) texture_id: AtlasTextureId,
+    pub(crate) tile_id: TileId,
+    pub(crate) bounds: Bounds<DevicePixels>,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+#[repr(C)]
+pub(crate) struct AtlasTextureId(pub(crate) u32); // We use u32 instead of usize for Metal Shader Language compatibility
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
+#[repr(C)]
+pub(crate) struct TileId(pub(crate) u32);
+
+impl From<etagere::AllocId> for TileId {
+    fn from(id: etagere::AllocId) -> Self {
+        Self(id.serialize())
+    }
+}
+
+impl From<TileId> for etagere::AllocId {
+    fn from(id: TileId) -> Self {
+        Self::deserialize(id.0)
+    }
+}
+
+pub trait PlatformInputHandler {
     fn selected_text_range(&self) -> Option<Range<usize>>;
     fn marked_text_range(&self) -> Option<Range<usize>>;
     fn text_for_range(&self, range_utf16: Range<usize>) -> Option<String>;
@@ -198,12 +268,6 @@ pub trait InputHandler {
 #[derive(Copy, Clone, Debug, PartialEq)]
 pub struct ScreenId(pub(crate) Uuid);
 
-#[derive(Copy, Clone, Debug)]
-pub enum RasterizationOptions {
-    Alpha,
-    Bgra,
-}
-
 #[derive(Debug)]
 pub struct WindowOptions {
     pub bounds: WindowBounds,

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

@@ -2,6 +2,7 @@
 ///! an origin at the bottom left of the main display.
 mod dispatcher;
 mod events;
+mod metal_atlas;
 mod metal_renderer;
 mod open_type;
 mod platform;
@@ -30,6 +31,7 @@ use std::{
 };
 
 pub use dispatcher::*;
+pub use metal_atlas::*;
 pub use platform::*;
 pub use screen::*;
 pub use text_system::*;

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

@@ -0,0 +1,185 @@
+use std::borrow::Cow;
+
+use crate::{
+    AtlasKey, AtlasTextureId, AtlasTile, Bounds, DevicePixels, PlatformAtlas, Point, Size,
+};
+use anyhow::{anyhow, Result};
+use collections::HashMap;
+use derive_more::{Deref, DerefMut};
+use etagere::BucketedAtlasAllocator;
+use metal::Device;
+use parking_lot::Mutex;
+
+pub struct MetalAtlas(Mutex<MetalAtlasState>);
+
+impl MetalAtlas {
+    pub fn new(device: Device) -> Self {
+        MetalAtlas(Mutex::new(MetalAtlasState {
+            device: AssertSend(device),
+            textures: Default::default(),
+            tiles_by_key: Default::default(),
+        }))
+    }
+
+    pub(crate) fn texture(&self, id: AtlasTextureId) -> metal::Texture {
+        self.0.lock().textures[id.0 as usize].metal_texture.clone()
+    }
+}
+
+struct MetalAtlasState {
+    device: AssertSend<Device>,
+    textures: Vec<MetalAtlasTexture>,
+    tiles_by_key: HashMap<AtlasKey, AtlasTile>,
+}
+
+impl PlatformAtlas for MetalAtlas {
+    fn get_or_insert_with<'a>(
+        &self,
+        key: &AtlasKey,
+        build: &mut dyn FnMut() -> Result<(Size<DevicePixels>, Cow<'a, [u8]>)>,
+    ) -> Result<AtlasTile> {
+        let mut lock = self.0.lock();
+        if let Some(tile) = lock.tiles_by_key.get(key) {
+            return Ok(tile.clone());
+        } else {
+            let (size, bytes) = build()?;
+            let tile = lock
+                .textures
+                .iter_mut()
+                .rev()
+                .find_map(|texture| {
+                    if texture.monochrome == key.is_monochrome() {
+                        texture.upload(size, &bytes)
+                    } else {
+                        None
+                    }
+                })
+                .or_else(|| {
+                    let texture = lock.push_texture(size, key.is_monochrome());
+                    texture.upload(size, &bytes)
+                })
+                .ok_or_else(|| anyhow!("could not allocate in new texture"))?;
+            lock.tiles_by_key.insert(key.clone(), tile.clone());
+            Ok(tile)
+        }
+    }
+
+    fn clear(&self) {
+        self.0.lock().tiles_by_key.clear();
+    }
+}
+
+impl MetalAtlasState {
+    fn push_texture(
+        &mut self,
+        min_size: Size<DevicePixels>,
+        monochrome: bool,
+    ) -> &mut MetalAtlasTexture {
+        const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = Size {
+            width: DevicePixels(1024),
+            height: DevicePixels(1024),
+        };
+
+        let size = min_size.max(&DEFAULT_ATLAS_SIZE);
+        let texture_descriptor = metal::TextureDescriptor::new();
+        texture_descriptor.set_width(size.width.into());
+        texture_descriptor.set_height(size.height.into());
+        if monochrome {
+            texture_descriptor.set_pixel_format(metal::MTLPixelFormat::A8Unorm);
+        } else {
+            texture_descriptor.set_pixel_format(metal::MTLPixelFormat::BGRA8Unorm);
+        }
+        let metal_texture = self.device.new_texture(&texture_descriptor);
+
+        let atlas_texture = MetalAtlasTexture {
+            id: AtlasTextureId(self.textures.len() as u32),
+            allocator: etagere::BucketedAtlasAllocator::new(size.into()),
+            metal_texture: AssertSend(metal_texture),
+            monochrome,
+        };
+        self.textures.push(atlas_texture);
+        self.textures.last_mut().unwrap()
+    }
+}
+
+struct MetalAtlasTexture {
+    id: AtlasTextureId,
+    allocator: BucketedAtlasAllocator,
+    metal_texture: AssertSend<metal::Texture>,
+    monochrome: bool,
+}
+
+impl MetalAtlasTexture {
+    fn upload(&mut self, size: Size<DevicePixels>, bytes: &[u8]) -> Option<AtlasTile> {
+        let allocation = self.allocator.allocate(size.into())?;
+        let tile = AtlasTile {
+            texture_id: self.id,
+            tile_id: allocation.id.into(),
+            bounds: Bounds {
+                origin: allocation.rectangle.min.into(),
+                size,
+            },
+        };
+
+        let region = metal::MTLRegion::new_2d(
+            tile.bounds.origin.x.into(),
+            tile.bounds.origin.y.into(),
+            tile.bounds.size.width.into(),
+            tile.bounds.size.height.into(),
+        );
+        self.metal_texture.replace_region(
+            region,
+            0,
+            bytes.as_ptr() as *const _,
+            u32::from(tile.bounds.size.width.to_bytes(self.bytes_per_pixel())) as u64,
+        );
+        Some(tile)
+    }
+
+    fn bytes_per_pixel(&self) -> u8 {
+        use metal::MTLPixelFormat::*;
+        match self.metal_texture.pixel_format() {
+            A8Unorm | R8Unorm => 1,
+            RGBA8Unorm | BGRA8Unorm => 4,
+            _ => unimplemented!(),
+        }
+    }
+}
+
+impl From<Size<DevicePixels>> for etagere::Size {
+    fn from(size: Size<DevicePixels>) -> Self {
+        etagere::Size::new(size.width.into(), size.height.into())
+    }
+}
+
+impl From<etagere::Point> for Point<DevicePixels> {
+    fn from(value: etagere::Point) -> Self {
+        Point {
+            x: DevicePixels::from(value.x),
+            y: DevicePixels::from(value.y),
+        }
+    }
+}
+
+impl From<etagere::Size> for Size<DevicePixels> {
+    fn from(size: etagere::Size) -> Self {
+        Size {
+            width: DevicePixels::from(size.width),
+            height: DevicePixels::from(size.height),
+        }
+    }
+}
+
+impl From<etagere::Rectangle> for Bounds<DevicePixels> {
+    fn from(rectangle: etagere::Rectangle) -> Self {
+        Bounds {
+            origin: rectangle.min.into(),
+            size: rectangle.size().into(),
+        }
+    }
+}
+
+#[derive(Deref, DerefMut)]
+struct AssertSend<T>(T);
+
+unsafe impl<T> Send for AssertSend<T> {}

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

@@ -1,5 +1,7 @@
-use crate::{point, size, DevicePixels, Quad, Scene, Size};
-use bytemuck::{Pod, Zeroable};
+use crate::{
+    point, size, AtlasTextureId, DevicePixels, MetalAtlas, MonochromeSprite, PolychromeSprite,
+    Quad, Scene, Size,
+};
 use cocoa::{
     base::{NO, YES},
     foundation::NSUInteger,
@@ -7,18 +9,20 @@ use cocoa::{
 };
 use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
 use objc::{self, msg_send, sel, sel_impl};
-use std::{ffi::c_void, mem, ptr};
+use std::{ffi::c_void, mem, ptr, sync::Arc};
 
 const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
 const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value.
 
 pub struct MetalRenderer {
-    device: metal::Device,
     layer: metal::MetalLayer,
     command_queue: CommandQueue,
-    quad_pipeline_state: metal::RenderPipelineState,
+    quads_pipeline_state: metal::RenderPipelineState,
+    monochrome_sprites_pipeline_state: metal::RenderPipelineState,
+    polychrome_sprites_pipeline_state: metal::RenderPipelineState,
     unit_vertices: metal::Buffer,
     instances: metal::Buffer,
+    sprite_atlas: Arc<MetalAtlas>,
 }
 
 impl MetalRenderer {
@@ -78,23 +82,43 @@ impl MetalRenderer {
             MTLResourceOptions::StorageModeManaged,
         );
 
-        let quad_pipeline_state = build_pipeline_state(
+        let quads_pipeline_state = build_pipeline_state(
             &device,
             &library,
-            "quad",
+            "quads",
             "quad_vertex",
             "quad_fragment",
             PIXEL_FORMAT,
         );
+        let monochrome_sprites_pipeline_state = build_pipeline_state(
+            &device,
+            &library,
+            "monochrome_sprites",
+            "monochrome_sprite_vertex",
+            "monochrome_sprite_fragment",
+            PIXEL_FORMAT,
+        );
+        let polychrome_sprites_pipeline_state = build_pipeline_state(
+            &device,
+            &library,
+            "polychrome_sprites",
+            "polychrome_sprite_vertex",
+            "polychrome_sprite_fragment",
+            PIXEL_FORMAT,
+        );
 
         let command_queue = device.new_command_queue();
+        let sprite_atlas = Arc::new(MetalAtlas::new(device.clone()));
+
         Self {
-            device,
             layer,
             command_queue,
-            quad_pipeline_state,
+            quads_pipeline_state,
+            monochrome_sprites_pipeline_state,
+            polychrome_sprites_pipeline_state,
             unit_vertices,
             instances,
+            sprite_atlas,
         }
     }
 
@@ -102,14 +126,16 @@ impl MetalRenderer {
         &*self.layer
     }
 
-    pub fn draw(&mut self, scene: &Scene) {
-        dbg!(scene);
+    pub fn sprite_atlas(&self) -> &Arc<MetalAtlas> {
+        &self.sprite_atlas
+    }
 
+    pub fn draw(&mut self, scene: &mut Scene) {
         let layer = self.layer.clone();
         let viewport_size = layer.drawable_size();
         let viewport_size: Size<DevicePixels> = size(
-            (viewport_size.width.ceil() as u32).into(),
-            (viewport_size.height.ceil() as u32).into(),
+            (viewport_size.width.ceil() as i32).into(),
+            (viewport_size.height.ceil() as i32).into(),
         );
         let drawable = if let Some(drawable) = layer.next_drawable() {
             drawable
@@ -124,20 +150,6 @@ impl MetalRenderer {
         let command_buffer = command_queue.new_command_buffer();
 
         let render_pass_descriptor = metal::RenderPassDescriptor::new();
-
-        let depth_texture_desc = metal::TextureDescriptor::new();
-        depth_texture_desc.set_pixel_format(metal::MTLPixelFormat::Depth32Float);
-        depth_texture_desc.set_storage_mode(metal::MTLStorageMode::Private);
-        depth_texture_desc.set_usage(metal::MTLTextureUsage::RenderTarget);
-        depth_texture_desc.set_width(u32::from(viewport_size.width) as u64);
-        depth_texture_desc.set_height(u32::from(viewport_size.height) as u64);
-        let depth_texture = self.device.new_texture(&depth_texture_desc);
-        let depth_attachment = render_pass_descriptor.depth_attachment().unwrap();
-
-        depth_attachment.set_texture(Some(&depth_texture));
-        depth_attachment.set_clear_depth(1.);
-        depth_attachment.set_store_action(metal::MTLStoreAction::Store);
-
         let color_attachment = render_pass_descriptor
             .color_attachments()
             .object_at(0)
@@ -153,27 +165,57 @@ impl MetalRenderer {
         command_encoder.set_viewport(metal::MTLViewport {
             originX: 0.0,
             originY: 0.0,
-            width: u32::from(viewport_size.width) as f64,
-            height: u32::from(viewport_size.height) as f64,
+            width: i32::from(viewport_size.width) as f64,
+            height: i32::from(viewport_size.height) as f64,
             znear: 0.0,
             zfar: 1.0,
         });
 
-        let mut buffer_offset = 0;
+        let mut instance_offset = 0;
         for layer in scene.layers() {
-            self.draw_quads(
-                &layer.quads,
-                &mut buffer_offset,
-                viewport_size,
-                command_encoder,
-            );
+            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 {
+                        texture_id,
+                        sprites,
+                    } => {
+                        self.draw_monochrome_sprites(
+                            texture_id,
+                            sprites,
+                            &mut instance_offset,
+                            viewport_size,
+                            command_encoder,
+                        );
+                    }
+                    crate::PrimitiveBatch::PolychromeSprites {
+                        texture_id,
+                        sprites,
+                    } => {
+                        self.draw_polychrome_sprites(
+                            texture_id,
+                            sprites,
+                            &mut instance_offset,
+                            viewport_size,
+                            command_encoder,
+                        );
+                    }
+                }
+            }
         }
 
         command_encoder.end_encoding();
 
         self.instances.did_modify_range(NSRange {
             location: 0,
-            length: buffer_offset as NSUInteger,
+            length: instance_offset as NSUInteger,
         });
 
         command_buffer.commit();
@@ -193,7 +235,7 @@ impl MetalRenderer {
         }
         align_offset(offset);
 
-        command_encoder.set_render_pipeline_state(&self.quad_pipeline_state);
+        command_encoder.set_render_pipeline_state(&self.quads_pipeline_state);
         command_encoder.set_vertex_buffer(
             QuadInputIndex::Vertices as u64,
             Some(&self.unit_vertices),
@@ -209,22 +251,20 @@ impl MetalRenderer {
             Some(&self.instances),
             *offset as u64,
         );
-        let quad_uniforms = QuadUniforms { viewport_size };
 
-        let quad_uniform_bytes = bytemuck::bytes_of(&quad_uniforms);
         command_encoder.set_vertex_bytes(
-            QuadInputIndex::Uniforms as u64,
-            quad_uniform_bytes.len() as u64,
-            quad_uniform_bytes.as_ptr() as *const c_void,
+            QuadInputIndex::ViewportSize as u64,
+            mem::size_of_val(&viewport_size) as u64,
+            &viewport_size as *const Size<DevicePixels> as *const _,
         );
 
-        let quad_bytes = bytemuck::cast_slice(quads);
+        let quad_bytes_len = mem::size_of::<Quad>() * quads.len();
         let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
         unsafe {
-            ptr::copy_nonoverlapping(quad_bytes.as_ptr(), buffer_contents, quad_bytes.len());
+            ptr::copy_nonoverlapping(quads.as_ptr() as *const u8, buffer_contents, quad_bytes_len);
         }
 
-        let next_offset = *offset + quad_bytes.len();
+        let next_offset = *offset + quad_bytes_len;
         assert!(
             next_offset <= INSTANCE_BUFFER_SIZE,
             "instance buffer exhausted"
@@ -238,6 +278,148 @@ impl MetalRenderer {
         );
         *offset = next_offset;
     }
+
+    fn draw_monochrome_sprites(
+        &mut self,
+        texture_id: AtlasTextureId,
+        sprites: &[MonochromeSprite],
+        offset: &mut usize,
+        viewport_size: Size<DevicePixels>,
+        command_encoder: &metal::RenderCommandEncoderRef,
+    ) {
+        if sprites.is_empty() {
+            return;
+        }
+        align_offset(offset);
+
+        let texture = self.sprite_atlas.texture(texture_id);
+        let texture_size = size(
+            DevicePixels(texture.width() as i32),
+            DevicePixels(texture.height() as i32),
+        );
+        command_encoder.set_render_pipeline_state(&self.monochrome_sprites_pipeline_state);
+        command_encoder.set_vertex_buffer(
+            SpriteInputIndex::Vertices as u64,
+            Some(&self.unit_vertices),
+            0,
+        );
+        command_encoder.set_vertex_buffer(
+            SpriteInputIndex::Sprites as u64,
+            Some(&self.instances),
+            *offset as u64,
+        );
+        command_encoder.set_vertex_bytes(
+            SpriteInputIndex::ViewportSize as u64,
+            mem::size_of_val(&viewport_size) as u64,
+            &viewport_size as *const Size<DevicePixels> as *const _,
+        );
+        command_encoder.set_vertex_bytes(
+            SpriteInputIndex::AtlasTextureSize as u64,
+            mem::size_of_val(&texture_size) as u64,
+            &texture_size as *const Size<DevicePixels> as *const _,
+        );
+        command_encoder.set_fragment_buffer(
+            SpriteInputIndex::Sprites as u64,
+            Some(&self.instances),
+            *offset as u64,
+        );
+        command_encoder.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
+
+        let sprite_bytes_len = mem::size_of::<MonochromeSprite>() * sprites.len();
+        let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
+        unsafe {
+            ptr::copy_nonoverlapping(
+                sprites.as_ptr() as *const u8,
+                buffer_contents,
+                sprite_bytes_len,
+            );
+        }
+
+        let next_offset = *offset + sprite_bytes_len;
+        assert!(
+            next_offset <= INSTANCE_BUFFER_SIZE,
+            "instance buffer exhausted"
+        );
+
+        command_encoder.draw_primitives_instanced(
+            metal::MTLPrimitiveType::Triangle,
+            0,
+            6,
+            sprites.len() as u64,
+        );
+        *offset = next_offset;
+    }
+
+    fn draw_polychrome_sprites(
+        &mut self,
+        texture_id: AtlasTextureId,
+        sprites: &[PolychromeSprite],
+        offset: &mut usize,
+        viewport_size: Size<DevicePixels>,
+        command_encoder: &metal::RenderCommandEncoderRef,
+    ) {
+        if sprites.is_empty() {
+            return;
+        }
+        align_offset(offset);
+
+        let texture = self.sprite_atlas.texture(texture_id);
+        let texture_size = size(
+            DevicePixels(texture.width() as i32),
+            DevicePixels(texture.height() as i32),
+        );
+        command_encoder.set_render_pipeline_state(&self.polychrome_sprites_pipeline_state);
+        command_encoder.set_vertex_buffer(
+            SpriteInputIndex::Vertices as u64,
+            Some(&self.unit_vertices),
+            0,
+        );
+        command_encoder.set_vertex_buffer(
+            SpriteInputIndex::Sprites as u64,
+            Some(&self.instances),
+            *offset as u64,
+        );
+        command_encoder.set_vertex_bytes(
+            SpriteInputIndex::ViewportSize as u64,
+            mem::size_of_val(&viewport_size) as u64,
+            &viewport_size as *const Size<DevicePixels> as *const _,
+        );
+        command_encoder.set_vertex_bytes(
+            SpriteInputIndex::AtlasTextureSize as u64,
+            mem::size_of_val(&texture_size) as u64,
+            &texture_size as *const Size<DevicePixels> as *const _,
+        );
+        command_encoder.set_fragment_buffer(
+            SpriteInputIndex::Sprites as u64,
+            Some(&self.instances),
+            *offset as u64,
+        );
+        command_encoder.set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
+
+        let sprite_bytes_len = mem::size_of::<PolychromeSprite>() * sprites.len();
+        let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
+        unsafe {
+            ptr::copy_nonoverlapping(
+                sprites.as_ptr() as *const u8,
+                buffer_contents,
+                sprite_bytes_len,
+            );
+        }
+
+        let next_offset = *offset + sprite_bytes_len;
+        assert!(
+            next_offset <= INSTANCE_BUFFER_SIZE,
+            "instance buffer exhausted"
+        );
+
+        command_encoder.draw_primitives_instanced(
+            metal::MTLPrimitiveType::Triangle,
+            0,
+            6,
+            sprites.len() as u64,
+        );
+        *offset = next_offset;
+    }
 }
 
 fn build_pipeline_state(
@@ -268,7 +450,7 @@ fn build_pipeline_state(
     color_attachment.set_source_alpha_blend_factor(metal::MTLBlendFactor::One);
     color_attachment.set_destination_rgb_blend_factor(metal::MTLBlendFactor::OneMinusSourceAlpha);
     color_attachment.set_destination_alpha_blend_factor(metal::MTLBlendFactor::One);
-    // descriptor.set_depth_attachment_pixel_format(MTLPixelFormat::Depth32Float);
+    descriptor.set_depth_attachment_pixel_format(MTLPixelFormat::Invalid);
 
     device
         .new_render_pipeline_state(&descriptor)
@@ -284,11 +466,14 @@ fn align_offset(offset: &mut usize) {
 enum QuadInputIndex {
     Vertices = 0,
     Quads = 1,
-    Uniforms = 2,
+    ViewportSize = 2,
 }
 
-#[derive(Debug, Clone, Copy, Zeroable, Pod)]
 #[repr(C)]
-pub(crate) struct QuadUniforms {
-    viewport_size: Size<DevicePixels>,
+enum SpriteInputIndex {
+    Vertices = 0,
+    Sprites = 1,
+    ViewportSize = 2,
+    AtlasTextureSize = 3,
+    AtlasTexture = 4,
 }

crates/gpui3/src/platform/mac/shaders.metal 🔗

@@ -4,177 +4,303 @@
 using namespace metal;
 
 float4 hsla_to_rgba(Hsla hsla);
-float4 to_device_position(float2 pixel_position, float2 viewport_size);
+float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds,
+                          Bounds_ScaledPixels clip_bounds,
+                          constant Size_DevicePixels *viewport_size);
+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);
 
 struct QuadVertexOutput {
-    float4 position [[position]];
-    float4 background_color;
-    float4 border_color;
-    uint quad_id;
+  float4 position [[position]];
+  float4 background_color [[flat]];
+  float4 border_color [[flat]];
+  uint quad_id [[flat]];
 };
 
-vertex QuadVertexOutput quad_vertex(
-    uint unit_vertex_id [[vertex_id]],
-    uint quad_id [[instance_id]],
-    constant float2 *unit_vertices [[buffer(QuadInputIndex_Vertices)]],
-    constant Quad *quads [[buffer(QuadInputIndex_Quads)]],
-    constant QuadUniforms *uniforms [[buffer(QuadInputIndex_Uniforms)]]
-) {
-    float2 unit_vertex = unit_vertices[unit_vertex_id];
-    Quad quad = quads[quad_id];
-    float2 position_2d = unit_vertex * float2(quad.bounds.size.width, quad.bounds.size.height) + float2(quad.bounds.origin.x, quad.bounds.origin.y);
-    position_2d.x = max(quad.clip_bounds.origin.x, position_2d.x);
-    position_2d.x = min(quad.clip_bounds.origin.x + quad.clip_bounds.size.width, position_2d.x);
-    position_2d.y = max(quad.clip_bounds.origin.y, position_2d.y);
-    position_2d.y = min(quad.clip_bounds.origin.y + quad.clip_bounds.size.height, position_2d.y);
-
-    float2 viewport_size = float2((float)uniforms->viewport_size.width, (float)uniforms->viewport_size.height);
-    float4 device_position = to_device_position(position_2d, 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,
-        quad_id
-    };
+vertex QuadVertexOutput quad_vertex(uint unit_vertex_id [[vertex_id]],
+                                    uint quad_id [[instance_id]],
+                                    constant float2 *unit_vertices
+                                    [[buffer(QuadInputIndex_Vertices)]],
+                                    constant Quad *quads
+                                    [[buffer(QuadInputIndex_Quads)]],
+                                    constant Size_DevicePixels *viewport_size
+                                    [[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 background_color = hsla_to_rgba(quad.background);
+  float4 border_color = hsla_to_rgba(quad.border_color);
+  return QuadVertexOutput{device_position, background_color, border_color,
+                          quad_id};
 }
 
-float quad_sdf(float2 point, Bounds_Pixels bounds, Corners_Pixels corner_radii) {
-    float2 half_size = float2(bounds.size.width, bounds.size.height) / 2.;
-    float2 center = float2(bounds.origin.x, bounds.origin.y) + half_size;
-    float2 center_to_point = point - center;
-    float corner_radius;
-    if (center_to_point.x < 0.) {
-        if (center_to_point.y < 0.) {
-            corner_radius = corner_radii.top_left;
-        } else {
-            corner_radius = corner_radii.bottom_left;
-        }
+fragment float4 quad_fragment(QuadVertexOutput input [[stage_in]],
+                              constant Quad *quads
+                              [[buffer(QuadInputIndex_Quads)]]) {
+  Quad quad = quads[input.quad_id];
+  float2 half_size =
+      float2(quad.bounds.size.width, quad.bounds.size.height) / 2.;
+  float2 center =
+      float2(quad.bounds.origin.x, quad.bounds.origin.y) + half_size;
+  float2 center_to_point = input.position.xy - center;
+  float corner_radius;
+  if (center_to_point.x < 0.) {
+    if (center_to_point.y < 0.) {
+      corner_radius = quad.corner_radii.top_left;
     } else {
-        if (center_to_point.y < 0.) {
-            corner_radius = corner_radii.top_right;
-        } else {
-            corner_radius = corner_radii.bottom_right;
-        }
+      corner_radius = quad.corner_radii.bottom_left;
     }
+  } else {
+    if (center_to_point.y < 0.) {
+      corner_radius = quad.corner_radii.top_right;
+    } else {
+      corner_radius = quad.corner_radii.bottom_right;
+    }
+  }
+
+  float2 rounded_edge_to_point =
+      fabs(center_to_point) - half_size + corner_radius;
+  float distance =
+      length(max(0., rounded_edge_to_point)) +
+      min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) -
+      corner_radius;
+
+  float vertical_border = center_to_point.x <= 0. ? quad.border_widths.left
+                                                  : quad.border_widths.right;
+  float horizontal_border = center_to_point.y <= 0. ? quad.border_widths.top
+                                                    : quad.border_widths.bottom;
+  float2 inset_size =
+      half_size - corner_radius - float2(vertical_border, horizontal_border);
+  float2 point_to_inset_corner = fabs(center_to_point) - inset_size;
+  float border_width;
+  if (point_to_inset_corner.x < 0. && point_to_inset_corner.y < 0.) {
+    border_width = 0.;
+  } else if (point_to_inset_corner.y > point_to_inset_corner.x) {
+    border_width = horizontal_border;
+  } else {
+    border_width = vertical_border;
+  }
+
+  float4 color;
+  if (border_width == 0.) {
+    color = input.background_color;
+  } else {
+    float inset_distance = distance + border_width;
 
-    float2 rounded_edge_to_point = abs(center_to_point) - half_size + corner_radius;
-    float distance = length(max(0., rounded_edge_to_point))
-                     + min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y))
-                     - corner_radius;
+    // Decrease border's opacity as we move inside the background.
+    input.border_color.a *= 1. - saturate(0.5 - inset_distance);
 
-    return distance;
+    // Alpha-blend the border and the background.
+    float output_alpha =
+        quad.border_color.a + quad.background.a * (1. - quad.border_color.a);
+    float3 premultiplied_border_rgb =
+        input.border_color.rgb * quad.border_color.a;
+    float3 premultiplied_background_rgb =
+        input.background_color.rgb * input.background_color.a;
+    float3 premultiplied_output_rgb =
+        premultiplied_border_rgb +
+        premultiplied_background_rgb * (1. - input.border_color.a);
+    color = float4(premultiplied_output_rgb, output_alpha);
+  }
+
+  float clip_distance =
+      quad_sdf(input.position.xy, quad.clip_bounds, quad.clip_corner_radii);
+  return color *
+         float4(1., 1., 1.,
+                saturate(0.5 - distance) * saturate(0.5 - clip_distance));
 }
 
-fragment float4 quad_fragment(
-    QuadVertexOutput input [[stage_in]],
-    constant Quad *quads [[buffer(QuadInputIndex_Quads)]]
-) {
-    Quad quad = quads[input.quad_id];
-    float2 half_size = float2(quad.bounds.size.width, quad.bounds.size.height) / 2.;
-    float2 center = float2(quad.bounds.origin.x, quad.bounds.origin.y) + half_size;
-    float2 center_to_point = input.position.xy - center;
-    float corner_radius;
-    if (center_to_point.x < 0.) {
-        if (center_to_point.y < 0.) {
-            corner_radius = quad.corner_radii.top_left;
-        } else {
-            corner_radius = quad.corner_radii.bottom_left;
-        }
-    } else {
-        if (center_to_point.y < 0.) {
-            corner_radius = quad.corner_radii.top_right;
-        } else {
-            corner_radius = quad.corner_radii.bottom_right;
-        }
-    }
+struct MonochromeSpriteVertexOutput {
+  float4 position [[position]];
+  float2 tile_position;
+  float4 color [[flat]];
+  uint sprite_id [[flat]];
+};
 
-    float2 rounded_edge_to_point = fabs(center_to_point) - half_size + corner_radius;
-    float distance = length(max(0., rounded_edge_to_point)) + min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) - corner_radius;
-
-    float vertical_border = center_to_point.x <= 0. ? quad.border_widths.left : quad.border_widths.right;
-    float horizontal_border = center_to_point.y <= 0. ? quad.border_widths.top : quad.border_widths.bottom;
-    float2 inset_size = half_size - corner_radius - float2(vertical_border, horizontal_border);
-    float2 point_to_inset_corner = fabs(center_to_point) - inset_size;
-    float border_width;
-    if (point_to_inset_corner.x < 0. && point_to_inset_corner.y < 0.) {
-        border_width = 0.;
-    } else if (point_to_inset_corner.y > point_to_inset_corner.x) {
-        border_width = horizontal_border;
-    } else {
-        border_width = vertical_border;
-    }
+vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
+    uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
+    constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
+    constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
+    constant Size_DevicePixels *viewport_size
+    [[buffer(SpriteInputIndex_ViewportSize)]],
+    constant Size_DevicePixels *atlas_size
+    [[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
 
-    float4 color;
-    if (border_width == 0.) {
-        color = input.background_color;
-    } else {
-        float inset_distance = distance + border_width;
+  float2 unit_vertex = unit_vertices[unit_vertex_id];
+  MonochromeSprite sprite = sprites[sprite_id];
+  float4 device_position = to_device_position(
+      unit_vertex, sprite.bounds, sprite.content_mask.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,
+                                      sprite_id};
+}
 
-        // Decrease border's opacity as we move inside the background.
-        input.border_color.a *= 1. - saturate(0.5 - inset_distance);
+fragment float4 monochrome_sprite_fragment(
+    MonochromeSpriteVertexOutput input [[stage_in]],
+    constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
+    texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
+  MonochromeSprite sprite = sprites[input.sprite_id];
+  constexpr sampler atlas_texture_sampler(mag_filter::linear,
+                                          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,
+                                 sprite.content_mask.corner_radii);
+  float4 color = input.color;
+  color.a *= sample.a * saturate(0.5 - clip_distance);
+  return color;
+}
 
-        // Alpha-blend the border and the background.
-        float output_alpha = quad.border_color.a + quad.background.a * (1. - quad.border_color.a);
-        float3 premultiplied_border_rgb = input.border_color.rgb * quad.border_color.a;
-        float3 premultiplied_background_rgb = input.background_color.rgb * input.background_color.a;
-        float3 premultiplied_output_rgb = premultiplied_border_rgb + premultiplied_background_rgb * (1. - input.border_color.a);
-        color = float4(premultiplied_output_rgb, output_alpha);
-    }
+struct PolychromeSpriteVertexOutput {
+  float4 position [[position]];
+  float2 tile_position;
+  uint sprite_id [[flat]];
+};
+
+vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex(
+    uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
+    constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
+    constant PolychromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
+    constant Size_DevicePixels *viewport_size
+    [[buffer(SpriteInputIndex_ViewportSize)]],
+    constant Size_DevicePixels *atlas_size
+    [[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
 
-    float clip_distance = quad_sdf(input.position.xy, quad.clip_bounds, quad.clip_corner_radii);
-    return color * float4(1., 1., 1., saturate(0.5 - distance) * saturate(0.5 - clip_distance));
+  float2 unit_vertex = unit_vertices[unit_vertex_id];
+  PolychromeSprite sprite = sprites[sprite_id];
+  float4 device_position = to_device_position(
+      unit_vertex, sprite.bounds, sprite.content_mask.bounds, viewport_size);
+  float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
+  return PolychromeSpriteVertexOutput{device_position, tile_position,
+                                      sprite_id};
+}
+
+fragment float4 polychrome_sprite_fragment(
+    PolychromeSpriteVertexOutput input [[stage_in]],
+    constant PolychromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
+    texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
+  PolychromeSprite sprite = sprites[input.sprite_id];
+  constexpr sampler atlas_texture_sampler(mag_filter::linear,
+                                          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,
+                                 sprite.content_mask.corner_radii);
+  float4 color = sample;
+  if (sprite.grayscale) {
+    float grayscale = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
+    color.r = grayscale;
+    color.g = grayscale;
+    color.b = grayscale;
+  }
+  color.a *= saturate(0.5 - clip_distance);
+  return color;
 }
 
 float4 hsla_to_rgba(Hsla hsla) {
-    float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
-    float s = hsla.s;
-    float l = hsla.l;
-    float a = hsla.a;
-
-    float c = (1.0 - fabs(2.0*l - 1.0)) * s;
-    float x = c * (1.0 - fabs(fmod(h, 2.0) - 1.0));
-    float m = l - c/2.0;
-
-    float r = 0.0;
-    float g = 0.0;
-    float b = 0.0;
-
-    if (h >= 0.0 && h < 1.0) {
-        r = c;
-        g = x;
-        b = 0.0;
-    } else if (h >= 1.0 && h < 2.0) {
-        r = x;
-        g = c;
-        b = 0.0;
-    } else if (h >= 2.0 && h < 3.0) {
-        r = 0.0;
-        g = c;
-        b = x;
-    } else if (h >= 3.0 && h < 4.0) {
-        r = 0.0;
-        g = x;
-        b = c;
-    } else if (h >= 4.0 && h < 5.0) {
-        r = x;
-        g = 0.0;
-        b = c;
+  float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
+  float s = hsla.s;
+  float l = hsla.l;
+  float a = hsla.a;
+
+  float c = (1.0 - fabs(2.0 * l - 1.0)) * s;
+  float x = c * (1.0 - fabs(fmod(h, 2.0) - 1.0));
+  float m = l - c / 2.0;
+
+  float r = 0.0;
+  float g = 0.0;
+  float b = 0.0;
+
+  if (h >= 0.0 && h < 1.0) {
+    r = c;
+    g = x;
+    b = 0.0;
+  } else if (h >= 1.0 && h < 2.0) {
+    r = x;
+    g = c;
+    b = 0.0;
+  } else if (h >= 2.0 && h < 3.0) {
+    r = 0.0;
+    g = c;
+    b = x;
+  } else if (h >= 3.0 && h < 4.0) {
+    r = 0.0;
+    g = x;
+    b = c;
+  } else if (h >= 4.0 && h < 5.0) {
+    r = x;
+    g = 0.0;
+    b = c;
+  } else {
+    r = c;
+    g = 0.0;
+    b = x;
+  }
+
+  float4 rgba;
+  rgba.x = (r + m);
+  rgba.y = (g + m);
+  rgba.z = (b + m);
+  rgba.w = a;
+  return rgba;
+}
+
+float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds,
+                          Bounds_ScaledPixels clip_bounds,
+                          constant Size_DevicePixels *input_viewport_size) {
+  float2 position =
+      unit_vertex * float2(bounds.size.width, bounds.size.height) +
+      float2(bounds.origin.x, bounds.origin.y);
+  position.x = max(clip_bounds.origin.x, position.x);
+  position.x = min(clip_bounds.origin.x + clip_bounds.size.width, position.x);
+  position.y = max(clip_bounds.origin.y, position.y);
+  position.y = min(clip_bounds.origin.y + clip_bounds.size.height, position.y);
+
+  float2 viewport_size = float2((float)input_viewport_size->width,
+                                (float)input_viewport_size->height);
+  float2 device_position =
+      position / viewport_size * float2(2., -2.) + float2(-1., 1.);
+  return float4(device_position, 0., 1.);
+}
+
+float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
+                        constant Size_DevicePixels *atlas_size) {
+  float2 tile_origin = float2(tile.bounds.origin.x, tile.bounds.origin.y);
+  float2 tile_size = float2(tile.bounds.size.width, tile.bounds.size.height);
+  return (tile_origin + unit_vertex * tile_size) /
+         float2((float)atlas_size->width, (float)atlas_size->height);
+}
+
+float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
+               Corners_ScaledPixels corner_radii) {
+  float2 half_size = float2(bounds.size.width, bounds.size.height) / 2.;
+  float2 center = float2(bounds.origin.x, bounds.origin.y) + half_size;
+  float2 center_to_point = point - center;
+  float corner_radius;
+  if (center_to_point.x < 0.) {
+    if (center_to_point.y < 0.) {
+      corner_radius = corner_radii.top_left;
+    } else {
+      corner_radius = corner_radii.bottom_left;
+    }
+  } else {
+    if (center_to_point.y < 0.) {
+      corner_radius = corner_radii.top_right;
     } else {
-        r = c;
-        g = 0.0;
-        b = x;
+      corner_radius = corner_radii.bottom_right;
     }
+  }
 
-    float4 rgba;
-    rgba.x = (r + m);
-    rgba.y = (g + m);
-    rgba.z = (b + m);
-    rgba.w = a;
-    return rgba;
-}
+  float2 rounded_edge_to_point =
+      abs(center_to_point) - half_size + corner_radius;
+  float distance =
+      length(max(0., rounded_edge_to_point)) +
+      min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) -
+      corner_radius;
 
-float4 to_device_position(float2 pixel_position, float2 viewport_size) {
-    return float4(pixel_position / viewport_size * float2(2., -2.) + float2(-1., 1.), 0., 1.);
+  return distance;
 }

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

@@ -1,8 +1,9 @@
 use crate::{
-    point, px, size, Bounds, Font, FontFeatures, FontId, FontMetrics, FontStyle, FontWeight, Glyph,
-    GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, RasterizationOptions, Result, Run,
-    SharedString, Size,
+    point, px, size, Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontStyle,
+    FontWeight, GlyphId, Pixels, PlatformTextSystem, Point, RenderGlyphParams, Result, ShapedGlyph,
+    ShapedLine, ShapedRun, SharedString, Size, SUBPIXEL_VARIANTS,
 };
+use anyhow::anyhow;
 use cocoa::appkit::{CGFloat, CGPoint};
 use collections::HashMap;
 use core_foundation::{
@@ -137,23 +138,15 @@ impl PlatformTextSystem for MacTextSystem {
         self.0.read().glyph_for_char(font_id, ch)
     }
 
+    fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
+        self.0.read().raster_bounds(params)
+    }
+
     fn rasterize_glyph(
         &self,
-        font_id: FontId,
-        font_size: f32,
-        glyph_id: GlyphId,
-        subpixel_shift: Point<Pixels>,
-        scale_factor: f32,
-        options: RasterizationOptions,
-    ) -> Option<(Bounds<u32>, Vec<u8>)> {
-        self.0.read().rasterize_glyph(
-            font_id,
-            font_size,
-            glyph_id,
-            subpixel_shift,
-            scale_factor,
-            options,
-        )
+        glyph_id: &RenderGlyphParams,
+    ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
+        self.0.read().rasterize_glyph(glyph_id)
     }
 
     fn layout_line(
@@ -161,7 +154,7 @@ impl PlatformTextSystem for MacTextSystem {
         text: &str,
         font_size: Pixels,
         font_runs: &[(usize, FontId)],
-    ) -> LineLayout {
+    ) -> ShapedLine {
         self.0.write().layout_line(text, font_size, font_runs)
     }
 
@@ -245,90 +238,91 @@ impl MacTextSystemState {
             })
     }
 
-    fn rasterize_glyph(
-        &self,
-        font_id: FontId,
-        font_size: f32,
-        glyph_id: GlyphId,
-        subpixel_shift: Point<Pixels>,
-        scale_factor: f32,
-        options: RasterizationOptions,
-    ) -> Option<(Bounds<u32>, Vec<u8>)> {
-        let font = &self.fonts[font_id.0];
-        let scale = Transform2F::from_scale(scale_factor);
-        let glyph_bounds = font
+    fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
+        let font = &self.fonts[params.font_id.0];
+        let scale = Transform2F::from_scale(params.scale_factor);
+        Ok(font
             .raster_bounds(
-                glyph_id.into(),
-                font_size,
+                params.glyph_id.into(),
+                params.font_size.into(),
                 scale,
                 HintingOptions::None,
                 font_kit::canvas::RasterizationOptions::GrayscaleAa,
-            )
-            .ok()?;
+            )?
+            .into())
+    }
 
-        if glyph_bounds.width() == 0 || glyph_bounds.height() == 0 {
-            None
+    fn rasterize_glyph(&self, params: &RenderGlyphParams) -> Result<(Size<DevicePixels>, Vec<u8>)> {
+        let glyph_bounds = self.raster_bounds(params)?;
+        if glyph_bounds.size.width.0 == 0 || glyph_bounds.size.height.0 == 0 {
+            Err(anyhow!("glyph bounds are empty"))
         } else {
-            // Make room for subpixel variants.
-            let subpixel_padding = subpixel_shift.map(|v| f32::from(v).ceil() as u32);
-            let cx_bounds = RectI::new(
-                glyph_bounds.origin(),
-                glyph_bounds.size() + Vector2I::from(subpixel_padding),
-            );
+            // Add an extra pixel when the subpixel variant isn't zero to make room for anti-aliasing.
+            let mut bitmap_size = glyph_bounds.size;
+            if params.subpixel_variant.x > 0 {
+                bitmap_size.width += DevicePixels(1);
+            }
+            if params.subpixel_variant.y > 0 {
+                bitmap_size.height += DevicePixels(1);
+            }
 
             let mut bytes;
             let cx;
-            match options {
-                RasterizationOptions::Alpha => {
-                    bytes = vec![0; cx_bounds.width() as usize * cx_bounds.height() as usize];
-                    cx = CGContext::create_bitmap_context(
-                        Some(bytes.as_mut_ptr() as *mut _),
-                        cx_bounds.width() as usize,
-                        cx_bounds.height() as usize,
-                        8,
-                        cx_bounds.width() as usize,
-                        &CGColorSpace::create_device_gray(),
-                        kCGImageAlphaOnly,
-                    );
-                }
-                RasterizationOptions::Bgra => {
-                    bytes = vec![0; cx_bounds.width() as usize * 4 * cx_bounds.height() as usize];
-                    cx = CGContext::create_bitmap_context(
-                        Some(bytes.as_mut_ptr() as *mut _),
-                        cx_bounds.width() as usize,
-                        cx_bounds.height() as usize,
-                        8,
-                        cx_bounds.width() as usize * 4,
-                        &CGColorSpace::create_device_rgb(),
-                        kCGImageAlphaPremultipliedLast,
-                    );
-                }
+            if params.is_emoji {
+                bytes = vec![0; bitmap_size.width.0 as usize * 4 * bitmap_size.height.0 as usize];
+                cx = CGContext::create_bitmap_context(
+                    Some(bytes.as_mut_ptr() as *mut _),
+                    bitmap_size.width.0 as usize,
+                    bitmap_size.height.0 as usize,
+                    8,
+                    bitmap_size.width.0 as usize * 4,
+                    &CGColorSpace::create_device_rgb(),
+                    kCGImageAlphaPremultipliedLast,
+                );
+            } else {
+                bytes = vec![0; bitmap_size.width.0 as usize * bitmap_size.height.0 as usize];
+                cx = CGContext::create_bitmap_context(
+                    Some(bytes.as_mut_ptr() as *mut _),
+                    bitmap_size.width.0 as usize,
+                    bitmap_size.height.0 as usize,
+                    8,
+                    bitmap_size.width.0 as usize,
+                    &CGColorSpace::create_device_gray(),
+                    kCGImageAlphaOnly,
+                );
             }
 
             // Move the origin to bottom left and account for scaling, this
             // makes drawing text consistent with the font-kit's raster_bounds.
             cx.translate(
-                -glyph_bounds.origin_x() as CGFloat,
-                (glyph_bounds.origin_y() + glyph_bounds.height()) as CGFloat,
+                -glyph_bounds.origin.x.0 as CGFloat,
+                (glyph_bounds.origin.y.0 + glyph_bounds.size.height.0) as CGFloat,
+            );
+            cx.scale(
+                params.scale_factor as CGFloat,
+                params.scale_factor as CGFloat,
             );
-            cx.scale(scale_factor as CGFloat, scale_factor as CGFloat);
 
+            let subpixel_shift = params
+                .subpixel_variant
+                .map(|v| v as f32 / SUBPIXEL_VARIANTS as f32);
             cx.set_allows_font_subpixel_positioning(true);
             cx.set_should_subpixel_position_fonts(true);
             cx.set_allows_font_subpixel_quantization(false);
             cx.set_should_subpixel_quantize_fonts(false);
-            font.native_font()
-                .clone_with_font_size(font_size as CGFloat)
+            self.fonts[params.font_id.0]
+                .native_font()
+                .clone_with_font_size(f32::from(params.font_size) as CGFloat)
                 .draw_glyphs(
-                    &[u32::from(glyph_id) as CGGlyph],
+                    &[u32::from(params.glyph_id) as CGGlyph],
                     &[CGPoint::new(
-                        (f32::from(subpixel_shift.x) / scale_factor) as CGFloat,
-                        (f32::from(subpixel_shift.y) / scale_factor) as CGFloat,
+                        (subpixel_shift.x / params.scale_factor) as CGFloat,
+                        (subpixel_shift.y / params.scale_factor) as CGFloat,
                     )],
                     cx,
                 );
 
-            if let RasterizationOptions::Bgra = options {
+            if params.is_emoji {
                 // Convert from RGBA with premultiplied alpha to BGRA with straight alpha.
                 for pixel in bytes.chunks_exact_mut(4) {
                     pixel.swap(0, 2);
@@ -339,7 +333,7 @@ impl MacTextSystemState {
                 }
             }
 
-            Some((cx_bounds.into(), bytes))
+            Ok((bitmap_size.into(), bytes))
         }
     }
 
@@ -348,7 +342,7 @@ impl MacTextSystemState {
         text: &str,
         font_size: Pixels,
         font_runs: &[(usize, FontId)],
-    ) -> LineLayout {
+    ) -> ShapedLine {
         // Construct the attributed string, converting UTF8 ranges to UTF16 ranges.
         let mut string = CFMutableAttributedString::new();
         {
@@ -409,7 +403,7 @@ impl MacTextSystemState {
             {
                 let glyph_utf16_ix = usize::try_from(*glyph_utf16_ix).unwrap();
                 ix_converter.advance_to_utf16_ix(glyph_utf16_ix);
-                glyphs.push(Glyph {
+                glyphs.push(ShapedGlyph {
                     id: (*glyph_id).into(),
                     position: point(position.x as f32, position.y as f32).map(px),
                     index: ix_converter.utf8_ix,
@@ -417,11 +411,11 @@ impl MacTextSystemState {
                 });
             }
 
-            runs.push(Run { font_id, glyphs })
+            runs.push(ShapedRun { font_id, glyphs })
         }
 
         let typographic_bounds = line.get_typographic_bounds();
-        LineLayout {
+        ShapedLine {
             width: typographic_bounds.width.into(),
             ascent: typographic_bounds.ascent.into(),
             descent: typographic_bounds.descent.into(),
@@ -549,11 +543,26 @@ impl From<RectF> for Bounds<f32> {
     }
 }
 
-impl From<RectI> for Bounds<u32> {
+impl From<RectI> for Bounds<DevicePixels> {
+    fn from(rect: RectI) -> Self {
+        Bounds {
+            origin: point(DevicePixels(rect.origin_x()), DevicePixels(rect.origin_y())),
+            size: size(DevicePixels(rect.width()), DevicePixels(rect.height())),
+        }
+    }
+}
+
+impl From<Vector2I> for Size<DevicePixels> {
+    fn from(value: Vector2I) -> Self {
+        size(value.x().into(), value.y().into())
+    }
+}
+
+impl From<RectI> for Bounds<i32> {
     fn from(rect: RectI) -> Self {
         Bounds {
-            origin: point(rect.origin_x() as u32, rect.origin_y() as u32),
-            size: size(rect.width() as u32, rect.height() as u32),
+            origin: point(rect.origin_x(), rect.origin_y()),
+            size: size(rect.width(), rect.height()),
         }
     }
 }

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

@@ -1,10 +1,10 @@
 use super::{ns_string, MetalRenderer, NSRange};
 use crate::{
-    point, px, size, AnyWindowHandle, Bounds, Event, InputHandler, KeyDownEvent, Keystroke,
-    MacScreen, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMovedEvent,
-    MouseUpEvent, NSRectExt, Pixels, Platform, PlatformDispatcher, PlatformScreen, PlatformWindow,
-    Point, Scene, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions,
-    WindowPromptLevel,
+    point, px, size, AnyWindowHandle, Bounds, Event, KeyDownEvent, Keystroke, MacScreen, Modifiers,
+    ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMovedEvent, MouseUpEvent, NSRectExt,
+    Pixels, Platform, PlatformAtlas, PlatformDispatcher, PlatformInputHandler, PlatformScreen,
+    PlatformWindow, Point, Scene, Size, Timer, WindowAppearance, WindowBounds, WindowKind,
+    WindowOptions, WindowPromptLevel,
 };
 use block::ConcreteBlock;
 use cocoa::{
@@ -292,7 +292,7 @@ struct MacWindowState {
     should_close_callback: Option<Box<dyn FnMut() -> bool>>,
     close_callback: Option<Box<dyn FnOnce()>>,
     appearance_changed_callback: Option<Box<dyn FnMut()>>,
-    input_handler: Option<Box<dyn InputHandler>>,
+    input_handler: Option<Box<dyn PlatformInputHandler>>,
     pending_key_down: Option<(KeyDownEvent, Option<InsertText>)>,
     last_key_equivalent: Option<KeyDownEvent>,
     synthetic_drag_counter: usize,
@@ -671,7 +671,7 @@ impl PlatformWindow for MacWindow {
         self
     }
 
-    fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>) {
+    fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>) {
         self.0.as_ref().lock().input_handler = Some(input_handler);
     }
 
@@ -885,6 +885,10 @@ impl PlatformWindow for MacWindow {
             let _: () = msg_send![this.native_window.contentView(), setNeedsDisplay: YES];
         }
     }
+
+    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
+        self.0.lock().renderer.sprite_atlas().clone()
+    }
 }
 
 fn get_scale_factor(native_window: id) -> f32 {
@@ -1357,9 +1361,8 @@ extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
     unsafe {
         let window_state = get_window_state(this);
         let mut window_state = window_state.as_ref().lock();
-        if let Some(scene) = window_state.scene_to_render.take() {
-            dbg!("render", &scene);
-            window_state.renderer.draw(&scene);
+        if let Some(mut scene) = window_state.scene_to_render.take() {
+            window_state.renderer.draw(&mut scene);
         }
     }
 }
@@ -1580,7 +1583,7 @@ async fn synthetic_drag(
 
 fn with_input_handler<F, R>(window: &Object, f: F) -> Option<R>
 where
-    F: FnOnce(&mut dyn InputHandler) -> R,
+    F: FnOnce(&mut dyn PlatformInputHandler) -> R,
 {
     let window_state = unsafe { get_window_state(window) };
     let mut lock = window_state.as_ref().lock();

crates/gpui3/src/scene.rs 🔗

@@ -1,97 +1,211 @@
-use std::mem;
+use std::{iter::Peekable, mem, slice};
 
-use super::{Bounds, Hsla, Pixels, Point};
-use crate::{Corners, Edges};
-use bytemuck::{Pod, Zeroable};
+use super::{Bounds, Hsla, Point};
+use crate::{AtlasTextureId, AtlasTile, Corners, Edges, ScaledContentMask, ScaledPixels};
 use collections::BTreeMap;
+use smallvec::SmallVec;
 
 // Exported to metal
 pub type PointF = Point<f32>;
+pub type LayerId = SmallVec<[u32; 16]>;
 
 #[derive(Debug)]
 pub struct Scene {
-    layers: BTreeMap<u32, SceneLayer>,
     pub(crate) scale_factor: f32,
-}
-
-#[derive(Default, Debug)]
-pub struct SceneLayer {
-    pub quads: Vec<Quad>,
+    pub(crate) layers: BTreeMap<LayerId, SceneLayer>,
 }
 
 impl Scene {
     pub fn new(scale_factor: f32) -> Scene {
         Scene {
-            layers: Default::default(),
             scale_factor,
+            layers: BTreeMap::new(),
         }
     }
 
     pub fn take(&mut self) -> Scene {
         Scene {
-            layers: mem::take(&mut self.layers),
             scale_factor: self.scale_factor,
+            layers: mem::take(&mut self.layers),
         }
     }
 
-    pub fn insert(&mut self, primitive: impl Into<Primitive>) {
-        let mut primitive = primitive.into();
-        primitive.scale(self.scale_factor);
-        let layer = self.layers.entry(primitive.order()).or_default();
+    pub fn insert(&mut self, stacking_order: LayerId, primitive: impl Into<Primitive>) {
+        let layer = self.layers.entry(stacking_order).or_default();
+
+        let primitive = primitive.into();
         match primitive {
-            Primitive::Quad(quad) => layer.quads.push(quad),
+            Primitive::Quad(quad) => {
+                layer.quads.push(quad);
+            }
+            Primitive::MonochromeSprite(sprite) => {
+                layer.monochrome_sprites.push(sprite);
+            }
+            Primitive::PolychromeSprite(sprite) => {
+                layer.polychrome_sprites.push(sprite);
+            }
         }
     }
 
-    pub fn layers(&self) -> impl Iterator<Item = &SceneLayer> {
-        self.layers.values()
+    pub(crate) fn layers(&mut self) -> impl Iterator<Item = &mut SceneLayer> {
+        self.layers.values_mut()
     }
 }
 
-#[derive(Clone, Debug)]
-pub enum Primitive {
-    Quad(Quad),
+#[derive(Debug, Default)]
+pub(crate) struct SceneLayer {
+    pub quads: Vec<Quad>,
+    pub monochrome_sprites: Vec<MonochromeSprite>,
+    pub polychrome_sprites: Vec<PolychromeSprite>,
 }
 
-impl Primitive {
-    pub fn order(&self) -> u32 {
-        match self {
-            Primitive::Quad(quad) => quad.order,
+impl SceneLayer {
+    pub fn batches(&mut self) -> impl Iterator<Item = PrimitiveBatch> {
+        self.quads.sort_unstable();
+        self.monochrome_sprites.sort_unstable();
+        self.polychrome_sprites.sort_unstable();
+        BatchIterator {
+            quads: &self.quads,
+            quads_start: 0,
+            quads_iter: self.quads.iter().peekable(),
+            monochrome_sprites: &self.monochrome_sprites,
+            monochrome_sprites_start: 0,
+            monochrome_sprites_iter: self.monochrome_sprites.iter().peekable(),
+            polychrome_sprites: &self.polychrome_sprites,
+            polychrome_sprites_start: 0,
+            polychrome_sprites_iter: self.polychrome_sprites.iter().peekable(),
         }
     }
+}
 
-    pub fn is_transparent(&self) -> bool {
-        match self {
-            Primitive::Quad(quad) => {
-                quad.background.is_transparent() && quad.border_color.is_transparent()
-            }
-        }
-    }
+struct BatchIterator<'a> {
+    quads: &'a [Quad],
+    quads_start: usize,
+    quads_iter: Peekable<slice::Iter<'a, Quad>>,
+    monochrome_sprites: &'a [MonochromeSprite],
+    monochrome_sprites_start: usize,
+    monochrome_sprites_iter: Peekable<slice::Iter<'a, MonochromeSprite>>,
+    polychrome_sprites: &'a [PolychromeSprite],
+    polychrome_sprites_start: usize,
+    polychrome_sprites_iter: Peekable<slice::Iter<'a, PolychromeSprite>>,
+}
 
-    pub fn scale(&mut self, factor: f32) {
-        match self {
-            Primitive::Quad(quad) => {
-                quad.scale(factor);
+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)),
+            (
+                PrimitiveKind::MonochromeSprite,
+                self.monochrome_sprites_iter.peek().map(|s| s.order),
+            ),
+            (
+                PrimitiveKind::PolychromeSprite,
+                self.polychrome_sprites_iter.peek().map(|s| s.order),
+            ),
+        ];
+        kinds_and_orders.sort_by_key(|(_, order)| order.unwrap_or(u32::MAX));
+
+        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))
+        } else {
+            return None;
+        };
+
+        match batch_kind {
+            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();
+                self.quads_start = quads_end;
+                Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_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();
+                self.monochrome_sprites_start = sprites_end;
+                Some(PrimitiveBatch::MonochromeSprites {
+                    texture_id,
+                    sprites: &self.monochrome_sprites[sprites_start..sprites_end],
+                })
+            }
+            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();
+                self.polychrome_sprites_start = sprites_end;
+                Some(PrimitiveBatch::PolychromeSprites {
+                    texture_id,
+                    sprites: &self.polychrome_sprites[sprites_start..sprites_end],
+                })
             }
         }
     }
 }
 
-#[derive(Debug, Clone, Copy, Zeroable, Pod)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum PrimitiveKind {
+    Quad,
+    MonochromeSprite,
+    PolychromeSprite,
+}
+
+#[derive(Clone, Debug)]
+pub enum Primitive {
+    Quad(Quad),
+    MonochromeSprite(MonochromeSprite),
+    PolychromeSprite(PolychromeSprite),
+}
+
+pub(crate) enum PrimitiveBatch<'a> {
+    Quads(&'a [Quad]),
+    MonochromeSprites {
+        texture_id: AtlasTextureId,
+        sprites: &'a [MonochromeSprite],
+    },
+    PolychromeSprites {
+        texture_id: AtlasTextureId,
+        sprites: &'a [PolychromeSprite],
+    },
+}
+
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
 #[repr(C)]
 pub struct Quad {
     pub order: u32,
-    pub bounds: Bounds<Pixels>,
-    pub clip_bounds: Bounds<Pixels>,
-    pub clip_corner_radii: Corners<Pixels>,
+    pub bounds: Bounds<ScaledPixels>,
+    pub clip_bounds: Bounds<ScaledPixels>,
+    pub clip_corner_radii: Corners<ScaledPixels>,
     pub background: Hsla,
     pub border_color: Hsla,
-    pub corner_radii: Corners<Pixels>,
-    pub border_widths: Edges<Pixels>,
+    pub corner_radii: Corners<ScaledPixels>,
+    pub border_widths: Edges<ScaledPixels>,
 }
 
 impl Quad {
-    pub fn vertices(&self) -> impl Iterator<Item = Point<Pixels>> {
+    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;
@@ -104,13 +218,17 @@ impl Quad {
         ]
         .into_iter()
     }
+}
+
+impl Ord for Quad {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.order.cmp(&other.order)
+    }
+}
 
-    pub fn scale(&mut self, factor: f32) {
-        self.bounds *= factor;
-        self.clip_bounds *= factor;
-        self.clip_corner_radii *= factor;
-        self.corner_radii *= factor;
-        self.border_widths *= factor;
+impl PartialOrd for Quad {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
     }
 }
 
@@ -119,3 +237,68 @@ impl From<Quad> for Primitive {
         Primitive::Quad(quad)
     }
 }
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+#[repr(C)]
+pub struct MonochromeSprite {
+    pub order: u32,
+    pub bounds: Bounds<ScaledPixels>,
+    pub content_mask: ScaledContentMask,
+    pub color: Hsla,
+    pub tile: AtlasTile,
+}
+
+impl Ord for MonochromeSprite {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        match self.order.cmp(&other.order) {
+            std::cmp::Ordering::Equal => self.tile.tile_id.cmp(&other.tile.tile_id),
+            order => order,
+        }
+    }
+}
+
+impl PartialOrd for MonochromeSprite {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl From<MonochromeSprite> for Primitive {
+    fn from(sprite: MonochromeSprite) -> Self {
+        Primitive::MonochromeSprite(sprite)
+    }
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+#[repr(C)]
+pub struct PolychromeSprite {
+    pub order: u32,
+    pub bounds: Bounds<ScaledPixels>,
+    pub content_mask: ScaledContentMask,
+    pub tile: AtlasTile,
+    pub grayscale: bool,
+}
+
+impl Ord for PolychromeSprite {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        match self.order.cmp(&other.order) {
+            std::cmp::Ordering::Equal => self.tile.tile_id.cmp(&other.tile.tile_id),
+            order => order,
+        }
+    }
+}
+
+impl PartialOrd for PolychromeSprite {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl From<PolychromeSprite> for Primitive {
+    fn from(sprite: PolychromeSprite) -> Self {
+        Primitive::PolychromeSprite(sprite)
+    }
+}
+
+#[derive(Copy, Clone, Debug)]
+pub struct AtlasId(pub(crate) usize);

crates/gpui3/src/style.rs 🔗

@@ -1,8 +1,8 @@
 use crate::{
-    phi, rems, AbsoluteLength, Bounds, Corners, CornersRefinement, DefiniteLength, Edges,
-    EdgesRefinement, Font, FontFeatures, FontStyle, FontWeight, Hsla, Length, Pixels, Point,
-    PointRefinement, Quad, Rems, Result, RunStyle, SharedString, Size, SizeRefinement, ViewContext,
-    WindowContext,
+    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,
+    SharedString, Size, SizeRefinement, ViewContext, WindowContext,
 };
 use refineable::Refineable;
 pub use taffy::style::{
@@ -179,22 +179,84 @@ impl Style {
         }
     }
 
+    pub fn apply_text_style<C, F, R>(&self, cx: &mut C, f: F) -> R
+    where
+        C: BorrowAppContext,
+        F: FnOnce(&mut C) -> R,
+    {
+        if self.text.is_some() {
+            cx.with_text_style(self.text.clone(), f)
+        } else {
+            f(cx)
+        }
+    }
+
+    /// Apply overflow to content mask
+    pub fn apply_overflow<C, F, R>(&self, bounds: Bounds<Pixels>, cx: &mut C, f: F) -> R
+    where
+        C: BorrowWindow,
+        F: FnOnce(&mut C) -> R,
+    {
+        let current_mask = cx.content_mask();
+
+        let min = current_mask.bounds.origin;
+        let max = current_mask.bounds.lower_right();
+
+        let mask_corner_radii = Corners::default();
+        let mask_bounds = match (
+            self.overflow.x == Overflow::Visible,
+            self.overflow.y == Overflow::Visible,
+        ) {
+            // x and y both visible
+            (true, true) => return f(cx),
+            // x visible, y hidden
+            (true, false) => Bounds::from_corners(
+                point(min.x, bounds.origin.y),
+                point(max.x, bounds.lower_right().y),
+            ),
+            // x hidden, y visible
+            (false, true) => Bounds::from_corners(
+                point(bounds.origin.x, min.y),
+                point(bounds.lower_right().x, max.y),
+            ),
+            // both hidden
+            (false, false) => bounds,
+        };
+        let mask = ContentMask {
+            bounds: mask_bounds,
+            corner_radii: mask_corner_radii,
+        };
+
+        cx.with_content_mask(mask, f)
+    }
+
     /// 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>) {
         let rem_size = cx.rem_size();
+        let scale = cx.scale_factor();
 
         let background_color = self.fill.as_ref().and_then(Fill::color);
         if background_color.is_some() || self.is_border_visible() {
-            cx.scene().insert(Quad {
-                order,
-                bounds,
-                clip_bounds: bounds, // todo!
-                clip_corner_radii: self.corner_radii.map(|length| length.to_pixels(rem_size)),
-                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)),
-                border_widths: self.border_widths.map(|length| length.to_pixels(rem_size)),
-            });
+            let layer_id = cx.current_layer_id();
+            cx.scene().insert(
+                layer_id,
+                Quad {
+                    order,
+                    bounds: bounds.scale(scale),
+                    clip_bounds: bounds.scale(scale), // todo!
+                    clip_corner_radii: self
+                        .corner_radii
+                        .map(|length| length.to_pixels(rem_size).scale(scale)),
+                    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)),
+                },
+            );
         }
     }
 

crates/gpui3/src/svg_renderer.rs 🔗

@@ -0,0 +1,47 @@
+use crate::{AssetSource, DevicePixels, IsZero, Result, SharedString, Size};
+use anyhow::anyhow;
+use std::hash::Hash;
+use std::sync::Arc;
+
+#[derive(Clone, PartialEq, Hash, Eq)]
+pub struct RenderSvgParams {
+    pub(crate) path: SharedString,
+    pub(crate) size: Size<DevicePixels>,
+}
+
+pub struct SvgRenderer {
+    asset_source: Arc<dyn AssetSource>,
+}
+
+impl SvgRenderer {
+    pub fn new(asset_source: Arc<dyn AssetSource>) -> Self {
+        Self { asset_source }
+    }
+
+    pub fn render(&self, params: &RenderSvgParams) -> Result<Vec<u8>> {
+        if params.size.is_zero() {
+            return Err(anyhow!("can't render at a zero size"));
+        }
+
+        // Load the tree.
+        let bytes = self.asset_source.load(&params.path)?;
+        let tree = usvg::Tree::from_data(&bytes, &usvg::Options::default())?;
+
+        // Render the SVG to a pixmap with the specified width and height.
+        let mut pixmap =
+            tiny_skia::Pixmap::new(params.size.width.into(), params.size.height.into()).unwrap();
+        resvg::render(
+            &tree,
+            usvg::FitTo::Width(params.size.width.into()),
+            pixmap.as_mut(),
+        );
+
+        // Convert the pixmap's pixels into an alpha mask.
+        let alpha_mask = pixmap
+            .pixels()
+            .iter()
+            .map(|p| p.alpha())
+            .collect::<Vec<_>>();
+        Ok(alpha_mask)
+    }
+}

crates/gpui3/src/text_system.rs 🔗

@@ -1,14 +1,17 @@
 mod font_features;
+mod line;
 mod line_wrapper;
 mod text_layout_cache;
 
 use anyhow::anyhow;
 pub use font_features::*;
+pub use line::*;
 use line_wrapper::*;
 pub use text_layout_cache::*;
 
 use crate::{
-    px, Bounds, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size, UnderlineStyle,
+    px, Bounds, DevicePixels, Hsla, Pixels, PlatformTextSystem, Point, Result, SharedString, Size,
+    UnderlineStyle,
 };
 use collections::HashMap;
 use core::fmt;
@@ -21,17 +24,19 @@ use std::{
 };
 
 #[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
+#[repr(C)]
 pub struct FontId(pub usize);
 
 #[derive(Hash, PartialEq, Eq, Clone, Copy, Debug)]
 pub struct FontFamilyId(pub usize);
 
+pub const SUBPIXEL_VARIANTS: u8 = 4;
+
 pub struct TextSystem {
     text_layout_cache: Arc<TextLayoutCache>,
     platform_text_system: Arc<dyn PlatformTextSystem>,
     font_ids_by_font: RwLock<HashMap<Font, FontId>>,
-    fonts_by_font_id: RwLock<HashMap<FontId, Font>>,
-    font_metrics: RwLock<HashMap<Font, FontMetrics>>,
+    font_metrics: RwLock<HashMap<FontId, FontMetrics>>,
     wrapper_pool: Mutex<HashMap<FontIdWithSize, Vec<LineWrapper>>>,
     font_runs_pool: Mutex<Vec<Vec<(usize, FontId)>>>,
 }
@@ -43,7 +48,6 @@ impl TextSystem {
             platform_text_system,
             font_metrics: RwLock::new(HashMap::default()),
             font_ids_by_font: RwLock::new(HashMap::default()),
-            fonts_by_font_id: RwLock::new(HashMap::default()),
             wrapper_pool: Mutex::new(HashMap::default()),
             font_runs_pool: Default::default(),
         }
@@ -51,36 +55,25 @@ impl TextSystem {
 
     pub fn font_id(&self, font: &Font) -> Result<FontId> {
         let font_id = self.font_ids_by_font.read().get(font).copied();
-
         if let Some(font_id) = font_id {
             Ok(font_id)
         } else {
             let font_id = self.platform_text_system.font_id(font)?;
             self.font_ids_by_font.write().insert(font.clone(), font_id);
-            self.fonts_by_font_id.write().insert(font_id, font.clone());
             Ok(font_id)
         }
     }
 
-    pub fn with_font<T>(&self, font_id: FontId, f: impl FnOnce(&Self, &Font) -> T) -> Result<T> {
-        self.fonts_by_font_id
-            .read()
-            .get(&font_id)
-            .ok_or_else(|| anyhow!("font not found"))
-            .map(|font| f(self, font))
-    }
-
-    pub fn bounding_box(&self, font: &Font, font_size: Pixels) -> Result<Bounds<Pixels>> {
-        self.read_metrics(&font, |metrics| metrics.bounding_box(font_size))
+    pub fn bounding_box(&self, font_id: FontId, font_size: Pixels) -> Result<Bounds<Pixels>> {
+        self.read_metrics(font_id, |metrics| metrics.bounding_box(font_size))
     }
 
     pub fn typographic_bounds(
         &self,
-        font: &Font,
+        font_id: FontId,
         font_size: Pixels,
         character: char,
     ) -> Result<Bounds<Pixels>> {
-        let font_id = self.font_id(font)?;
         let glyph_id = self
             .platform_text_system
             .glyph_for_char(font_id, character)
@@ -88,65 +81,63 @@ impl TextSystem {
         let bounds = self
             .platform_text_system
             .typographic_bounds(font_id, glyph_id)?;
-        self.read_metrics(font, |metrics| {
+        self.read_metrics(font_id, |metrics| {
             (bounds / metrics.units_per_em as f32 * font_size.0).map(px)
         })
     }
 
-    pub fn advance(&self, font: &Font, font_size: Pixels, ch: char) -> Result<Size<Pixels>> {
-        let font_id = self.font_id(font)?;
+    pub fn advance(&self, font_id: FontId, font_size: Pixels, ch: char) -> Result<Size<Pixels>> {
         let glyph_id = self
             .platform_text_system
             .glyph_for_char(font_id, ch)
             .ok_or_else(|| anyhow!("glyph not found for character '{}'", ch))?;
-        let result =
-            self.platform_text_system.advance(font_id, glyph_id)? / self.units_per_em(font)? as f32;
+        let result = self.platform_text_system.advance(font_id, glyph_id)?
+            / self.units_per_em(font_id)? as f32;
 
         Ok(result * font_size)
     }
 
-    pub fn units_per_em(&self, font: &Font) -> Result<u32> {
-        self.read_metrics(font, |metrics| metrics.units_per_em as u32)
+    pub fn units_per_em(&self, font_id: FontId) -> Result<u32> {
+        self.read_metrics(font_id, |metrics| metrics.units_per_em as u32)
     }
 
-    pub fn cap_height(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
-        self.read_metrics(font, |metrics| metrics.cap_height(font_size))
+    pub fn cap_height(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
+        self.read_metrics(font_id, |metrics| metrics.cap_height(font_size))
     }
 
-    pub fn x_height(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
-        self.read_metrics(font, |metrics| metrics.x_height(font_size))
+    pub fn x_height(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
+        self.read_metrics(font_id, |metrics| metrics.x_height(font_size))
     }
 
-    pub fn ascent(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
-        self.read_metrics(font, |metrics| metrics.ascent(font_size))
+    pub fn ascent(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
+        self.read_metrics(font_id, |metrics| metrics.ascent(font_size))
     }
 
-    pub fn descent(&self, font: &Font, font_size: Pixels) -> Result<Pixels> {
-        self.read_metrics(font, |metrics| metrics.descent(font_size))
+    pub fn descent(&self, font_id: FontId, font_size: Pixels) -> Result<Pixels> {
+        self.read_metrics(font_id, |metrics| metrics.descent(font_size))
     }
 
     pub fn baseline_offset(
         &self,
-        font: &Font,
+        font_id: FontId,
         font_size: Pixels,
         line_height: Pixels,
     ) -> Result<Pixels> {
-        let ascent = self.ascent(font, font_size)?;
-        let descent = self.descent(font, font_size)?;
+        let ascent = self.ascent(font_id, font_size)?;
+        let descent = self.descent(font_id, font_size)?;
         let padding_top = (line_height - ascent - descent) / 2.;
         Ok(padding_top + ascent)
     }
 
-    fn read_metrics<T>(&self, font: &Font, read: impl FnOnce(&FontMetrics) -> T) -> Result<T> {
+    fn read_metrics<T>(&self, font_id: FontId, read: impl FnOnce(&FontMetrics) -> T) -> Result<T> {
         let lock = self.font_metrics.upgradable_read();
 
-        if let Some(metrics) = lock.get(font) {
+        if let Some(metrics) = lock.get(&font_id) {
             Ok(read(metrics))
         } else {
-            let font_id = self.platform_text_system.font_id(&font)?;
             let mut lock = RwLockUpgradableReadGuard::upgrade(lock);
             let metrics = lock
-                .entry(font.clone())
+                .entry(font_id)
                 .or_insert_with(|| self.platform_text_system.font_metrics(font_id));
             Ok(read(metrics))
         }
@@ -160,29 +151,18 @@ impl TextSystem {
     ) -> Result<Line> {
         let mut font_runs = self.font_runs_pool.lock().pop().unwrap_or_default();
 
-        dbg!("got font runs from pool");
         let mut last_font: Option<&Font> = None;
         for (len, style) in runs {
-            dbg!(len);
             if let Some(last_font) = last_font.as_ref() {
-                dbg!("a");
                 if **last_font == style.font {
-                    dbg!("b");
                     font_runs.last_mut().unwrap().0 += len;
-                    dbg!("c");
                     continue;
                 }
-                dbg!("d");
             }
-            dbg!("e");
             last_font = Some(&style.font);
-            dbg!("f");
             font_runs.push((*len, self.font_id(&style.font)?));
-            dbg!("g");
         }
 
-        dbg!("built font runs");
-
         let layout = self
             .text_layout_cache
             .layout_line(text, font_size, &font_runs);
@@ -220,6 +200,17 @@ impl TextSystem {
             text_system: self.clone(),
         })
     }
+
+    pub fn raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
+        self.platform_text_system.glyph_raster_bounds(params)
+    }
+
+    pub fn rasterize_glyph(
+        &self,
+        glyph_id: &RenderGlyphParams,
+    ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
+        self.platform_text_system.rasterize_glyph(glyph_id)
+    }
 }
 
 #[derive(Hash, Eq, PartialEq)]
@@ -333,6 +324,7 @@ pub struct RunStyle {
 }
 
 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
+#[repr(C)]
 pub struct GlyphId(u32);
 
 impl From<GlyphId> for u32 {
@@ -353,28 +345,69 @@ impl From<u32> for GlyphId {
     }
 }
 
-#[derive(Clone, Debug)]
-pub struct Glyph {
-    pub id: GlyphId,
-    pub position: Point<Pixels>,
-    pub index: usize,
-    pub is_emoji: bool,
-}
-
 #[derive(Default, Debug)]
-pub struct LineLayout {
+pub struct ShapedLine {
     pub font_size: Pixels,
     pub width: Pixels,
     pub ascent: Pixels,
     pub descent: Pixels,
-    pub runs: Vec<Run>,
+    pub runs: Vec<ShapedRun>,
     pub len: usize,
 }
 
 #[derive(Debug)]
-pub struct Run {
+pub struct ShapedRun {
     pub font_id: FontId,
-    pub glyphs: Vec<Glyph>,
+    pub glyphs: Vec<ShapedGlyph>,
+}
+
+#[derive(Clone, Debug)]
+pub struct ShapedGlyph {
+    pub id: GlyphId,
+    pub position: Point<Pixels>,
+    pub index: usize,
+    pub is_emoji: bool,
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct RenderGlyphParams {
+    pub(crate) font_id: FontId,
+    pub(crate) glyph_id: GlyphId,
+    pub(crate) font_size: Pixels,
+    pub(crate) subpixel_variant: Point<u8>,
+    pub(crate) scale_factor: f32,
+    pub(crate) is_emoji: bool,
+}
+
+impl Eq for RenderGlyphParams {}
+
+impl Hash for RenderGlyphParams {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.font_id.0.hash(state);
+        self.glyph_id.0.hash(state);
+        self.font_size.0.to_bits().hash(state);
+        self.subpixel_variant.hash(state);
+        self.scale_factor.to_bits().hash(state);
+    }
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct RenderEmojiParams {
+    pub(crate) font_id: FontId,
+    pub(crate) glyph_id: GlyphId,
+    pub(crate) font_size: Pixels,
+    pub(crate) scale_factor: f32,
+}
+
+impl Eq for RenderEmojiParams {}
+
+impl Hash for RenderEmojiParams {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.font_id.0.hash(state);
+        self.glyph_id.0.hash(state);
+        self.font_size.0.to_bits().hash(state);
+        self.scale_factor.to_bits().hash(state);
+    }
 }
 
 #[derive(Clone, Debug, Eq, PartialEq, Hash)]

crates/gpui3/src/text_system/line.rs 🔗

@@ -0,0 +1,322 @@
+use crate::{
+    black, point, px, Bounds, FontId, Hsla, Layout, Pixels, Point, RunStyle, ShapedBoundary,
+    ShapedLine, ShapedRun, UnderlineStyle, WindowContext,
+};
+use anyhow::Result;
+use smallvec::SmallVec;
+use std::sync::Arc;
+
+#[derive(Default, Debug, Clone)]
+pub struct Line {
+    layout: Arc<ShapedLine>,
+    style_runs: SmallVec<[StyleRun; 32]>,
+}
+
+#[derive(Debug, Clone)]
+struct StyleRun {
+    len: u32,
+    color: Hsla,
+    underline: UnderlineStyle,
+}
+
+impl Line {
+    pub fn new(layout: Arc<ShapedLine>, runs: &[(usize, RunStyle)]) -> Self {
+        let mut style_runs = SmallVec::new();
+        for (len, style) in runs {
+            style_runs.push(StyleRun {
+                len: *len as u32,
+                color: style.color,
+                underline: style.underline.clone().unwrap_or_default(),
+            });
+        }
+        Self { layout, style_runs }
+    }
+
+    pub fn runs(&self) -> &[ShapedRun] {
+        &self.layout.runs
+    }
+
+    pub fn width(&self) -> Pixels {
+        self.layout.width
+    }
+
+    pub fn font_size(&self) -> Pixels {
+        self.layout.font_size
+    }
+
+    pub fn x_for_index(&self, index: usize) -> Pixels {
+        for run in &self.layout.runs {
+            for glyph in &run.glyphs {
+                if glyph.index >= index {
+                    return glyph.position.x;
+                }
+            }
+        }
+        self.layout.width
+    }
+
+    pub fn font_for_index(&self, index: usize) -> Option<FontId> {
+        for run in &self.layout.runs {
+            for glyph in &run.glyphs {
+                if glyph.index >= index {
+                    return Some(run.font_id);
+                }
+            }
+        }
+
+        None
+    }
+
+    pub fn len(&self) -> usize {
+        self.layout.len
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.layout.len == 0
+    }
+
+    pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
+        if x >= self.layout.width {
+            None
+        } else {
+            for run in self.layout.runs.iter().rev() {
+                for glyph in run.glyphs.iter().rev() {
+                    if glyph.position.x <= x {
+                        return Some(glyph.index);
+                    }
+                }
+            }
+            Some(0)
+        }
+    }
+
+    // todo!
+    pub fn paint(
+        &self,
+        layout: &Layout,
+        visible_bounds: Bounds<Pixels>,
+        line_height: Pixels,
+        cx: &mut WindowContext,
+    ) -> Result<()> {
+        let origin = layout.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);
+
+        let mut style_runs = self.style_runs.iter();
+        let mut run_end = 0;
+        let mut color = black();
+        let mut underline = None;
+        let text_system = cx.text_system().clone();
+
+        for run in &self.layout.runs {
+            let max_glyph_width = text_system
+                .bounding_box(run.font_id, self.layout.font_size)?
+                .size
+                .width;
+
+            for glyph in &run.glyphs {
+                let glyph_origin = origin + baseline_offset + glyph.position;
+                if glyph_origin.x > visible_bounds.upper_right().x {
+                    break;
+                }
+
+                let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
+                if glyph.index >= run_end {
+                    if let Some(style_run) = style_runs.next() {
+                        if let Some((_, underline_style)) = &mut underline {
+                            if style_run.underline != *underline_style {
+                                finished_underline = underline.take();
+                            }
+                        }
+                        if style_run.underline.thickness > px(0.) {
+                            underline.get_or_insert((
+                                point(
+                                    glyph_origin.x,
+                                    origin.y + baseline_offset.y + (self.layout.descent * 0.618),
+                                ),
+                                UnderlineStyle {
+                                    color: style_run.underline.color,
+                                    thickness: style_run.underline.thickness,
+                                    squiggly: style_run.underline.squiggly,
+                                },
+                            ));
+                        }
+
+                        run_end += style_run.len as usize;
+                        color = style_run.color;
+                    } else {
+                        run_end = self.layout.len;
+                        finished_underline = underline.take();
+                    }
+                }
+
+                if glyph_origin.x + max_glyph_width < visible_bounds.origin.x {
+                    continue;
+                }
+
+                if let Some((_underline_origin, _underline_style)) = finished_underline {
+                    todo!()
+                }
+
+                if glyph.is_emoji {
+                    cx.paint_emoji(
+                        glyph_origin,
+                        layout.order,
+                        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,
+                        color,
+                    )?;
+                }
+            }
+        }
+
+        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,
+            // });
+        }
+
+        Ok(())
+    }
+
+    pub fn paint_wrapped(
+        &self,
+        origin: Point<Pixels>,
+        _visible_bounds: Bounds<Pixels>,
+        line_height: Pixels,
+        boundaries: &[ShapedBoundary],
+        cx: &mut WindowContext,
+    ) -> Result<()> {
+        let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
+        let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
+
+        let mut boundaries = boundaries.into_iter().peekable();
+        let mut color_runs = self.style_runs.iter();
+        let mut style_run_end = 0;
+        let mut _color = black(); // todo!
+        let mut underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
+
+        let mut glyph_origin = origin;
+        let mut prev_position = px(0.);
+        for (run_ix, run) in self.layout.runs.iter().enumerate() {
+            for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
+                glyph_origin.x += glyph.position.x - prev_position;
+
+                if boundaries
+                    .peek()
+                    .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,
+                        // });
+                    }
+
+                    glyph_origin = point(origin.x, glyph_origin.y + line_height);
+                }
+                prev_position = glyph.position.x;
+
+                let mut finished_underline = None;
+                if glyph.index >= style_run_end {
+                    if let Some(style_run) = color_runs.next() {
+                        style_run_end += style_run.len as usize;
+                        _color = style_run.color;
+                        if let Some((_, underline_style)) = &mut underline {
+                            if style_run.underline != *underline_style {
+                                finished_underline = underline.take();
+                            }
+                        }
+                        if style_run.underline.thickness > px(0.) {
+                            underline.get_or_insert((
+                                glyph_origin
+                                    + point(
+                                        px(0.),
+                                        baseline_offset.y + (self.layout.descent * 0.618),
+                                    ),
+                                UnderlineStyle {
+                                    color: Some(
+                                        style_run.underline.color.unwrap_or(style_run.color),
+                                    ),
+                                    thickness: style_run.underline.thickness,
+                                    squiggly: style_run.underline.squiggly,
+                                },
+                            ));
+                        }
+                    } else {
+                        style_run_end = self.layout.len;
+                        _color = black();
+                        finished_underline = underline.take();
+                    }
+                }
+
+                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,
+                    // });
+                }
+
+                let text_system = cx.text_system();
+                let _glyph_bounds = Bounds {
+                    origin: glyph_origin,
+                    size: text_system
+                        .bounding_box(run.font_id, self.layout.font_size)?
+                        .size,
+                };
+                // if glyph_bounds.intersects(visible_bounds) {
+                //     if glyph.is_emoji {
+                //         cx.scene().push_image_glyph(scene::ImageGlyph {
+                //             font_id: run.font_id,
+                //             font_size: self.layout.font_size,
+                //             id: glyph.id,
+                //             origin: glyph_bounds.origin() + baseline_offset,
+                //         });
+                //     } else {
+                //         cx.scene().push_glyph(scene::Glyph {
+                //             font_id: run.font_id,
+                //             font_size: self.layout.font_size,
+                //             id: glyph.id,
+                //             origin: glyph_bounds.origin() + baseline_offset,
+                //             color,
+                //         });
+                //     }
+                // }
+            }
+        }
+
+        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,
+            // });
+        }
+
+        Ok(())
+    }
+}

crates/gpui3/src/text_system/text_layout_cache.rs 🔗

@@ -1,8 +1,4 @@
-use crate::{
-    black, point, px, Bounds, FontId, Glyph, Hsla, LineLayout, Pixels, PlatformTextSystem, Point,
-    Run, RunStyle, UnderlineStyle, WindowContext,
-};
-use anyhow::Result;
+use crate::{FontId, Pixels, PlatformTextSystem, ShapedGlyph, ShapedLine, ShapedRun};
 use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
 use smallvec::SmallVec;
 use std::{
@@ -13,8 +9,8 @@ use std::{
 };
 
 pub(crate) struct TextLayoutCache {
-    prev_frame: Mutex<HashMap<CacheKeyValue, Arc<LineLayout>>>,
-    curr_frame: RwLock<HashMap<CacheKeyValue, Arc<LineLayout>>>,
+    prev_frame: Mutex<HashMap<CacheKeyValue, Arc<ShapedLine>>>,
+    curr_frame: RwLock<HashMap<CacheKeyValue, Arc<ShapedLine>>>,
     platform_text_system: Arc<dyn PlatformTextSystem>,
 }
 
@@ -39,8 +35,7 @@ impl TextLayoutCache {
         text: &'a str,
         font_size: Pixels,
         runs: &[(usize, FontId)],
-    ) -> Arc<LineLayout> {
-        dbg!("layout line");
+    ) -> Arc<ShapedLine> {
         let key = &CacheKeyRef {
             text,
             font_size,
@@ -145,334 +140,14 @@ impl<'a> Hash for CacheKeyRef<'a> {
     }
 }
 
-#[derive(Default, Debug, Clone)]
-pub struct Line {
-    layout: Arc<LineLayout>,
-    style_runs: SmallVec<[StyleRun; 32]>,
-}
-
-#[derive(Debug, Clone)]
-struct StyleRun {
-    len: u32,
-    color: Hsla,
-    underline: UnderlineStyle,
-}
-
 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
 pub struct ShapedBoundary {
     pub run_ix: usize,
     pub glyph_ix: usize,
 }
 
-impl Line {
-    pub fn new(layout: Arc<LineLayout>, runs: &[(usize, RunStyle)]) -> Self {
-        let mut style_runs = SmallVec::new();
-        for (len, style) in runs {
-            style_runs.push(StyleRun {
-                len: *len as u32,
-                color: style.color,
-                underline: style.underline.clone().unwrap_or_default(),
-            });
-        }
-        Self { layout, style_runs }
-    }
-
-    pub fn runs(&self) -> &[Run] {
-        &self.layout.runs
-    }
-
-    pub fn width(&self) -> Pixels {
-        self.layout.width
-    }
-
-    pub fn font_size(&self) -> Pixels {
-        self.layout.font_size
-    }
-
-    pub fn x_for_index(&self, index: usize) -> Pixels {
-        for run in &self.layout.runs {
-            for glyph in &run.glyphs {
-                if glyph.index >= index {
-                    return glyph.position.x;
-                }
-            }
-        }
-        self.layout.width
-    }
-
-    pub fn font_for_index(&self, index: usize) -> Option<FontId> {
-        for run in &self.layout.runs {
-            for glyph in &run.glyphs {
-                if glyph.index >= index {
-                    return Some(run.font_id);
-                }
-            }
-        }
-
-        None
-    }
-
-    pub fn len(&self) -> usize {
-        self.layout.len
-    }
-
-    pub fn is_empty(&self) -> bool {
-        self.layout.len == 0
-    }
-
-    pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
-        if x >= self.layout.width {
-            None
-        } else {
-            for run in self.layout.runs.iter().rev() {
-                for glyph in run.glyphs.iter().rev() {
-                    if glyph.position.x <= x {
-                        return Some(glyph.index);
-                    }
-                }
-            }
-            Some(0)
-        }
-    }
-
-    // todo!
-    pub fn paint(
-        &self,
-        origin: Point<Pixels>,
-        visible_bounds: Bounds<Pixels>,
-        line_height: Pixels,
-        cx: &mut WindowContext,
-    ) -> Result<()> {
-        let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
-        let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
-
-        let mut style_runs = self.style_runs.iter();
-        let mut run_end = 0;
-        let mut color = black();
-        let mut underline = None;
-
-        for run in &self.layout.runs {
-            cx.text_system().with_font(run.font_id, |system, font| {
-                let max_glyph_width = system.bounding_box(font, self.layout.font_size)?.size.width;
-
-                for glyph in &run.glyphs {
-                    let glyph_origin = origin + baseline_offset + glyph.position;
-                    if glyph_origin.x > visible_bounds.upper_right().x {
-                        break;
-                    }
-
-                    let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
-                    if glyph.index >= run_end {
-                        if let Some(style_run) = style_runs.next() {
-                            if let Some((_, underline_style)) = &mut underline {
-                                if style_run.underline != *underline_style {
-                                    finished_underline = underline.take();
-                                }
-                            }
-                            if style_run.underline.thickness > px(0.) {
-                                underline.get_or_insert((
-                                    point(
-                                        glyph_origin.x,
-                                        origin.y
-                                            + baseline_offset.y
-                                            + (self.layout.descent * 0.618),
-                                    ),
-                                    UnderlineStyle {
-                                        color: style_run.underline.color,
-                                        thickness: style_run.underline.thickness,
-                                        squiggly: style_run.underline.squiggly,
-                                    },
-                                ));
-                            }
-
-                            run_end += style_run.len as usize;
-                            color = style_run.color;
-                        } else {
-                            run_end = self.layout.len;
-                            finished_underline = underline.take();
-                        }
-                    }
-
-                    if glyph_origin.x + max_glyph_width < visible_bounds.origin.x {
-                        continue;
-                    }
-
-                    if let Some((_underline_origin, _underline_style)) = finished_underline {
-                        // cx.scene().insert(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 glyph.is_emoji {
-                    //     cx.scene().push_image_glyph(scene::ImageGlyph {
-                    //         font_id: run.font_id,
-                    //         font_size: self.layout.font_size,
-                    //         id: glyph.id,
-                    //         origin: glyph_origin,
-                    //     });
-                    // } else {
-                    //     cx.scene().push_glyph(scene::Glyph {
-                    //         font_id: run.font_id,
-                    //         font_size: self.layout.font_size,
-                    //         id: glyph.id,
-                    //         origin: glyph_origin,
-                    //         color,
-                    //     });
-                    // }
-                }
-
-                anyhow::Ok(())
-            })??;
-        }
-
-        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,
-            // });
-        }
-
-        Ok(())
-    }
-
-    pub fn paint_wrapped(
-        &self,
-        origin: Point<Pixels>,
-        _visible_bounds: Bounds<Pixels>,
-        line_height: Pixels,
-        boundaries: &[ShapedBoundary],
-        cx: &mut WindowContext,
-    ) -> Result<()> {
-        let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
-        let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
-
-        let mut boundaries = boundaries.into_iter().peekable();
-        let mut color_runs = self.style_runs.iter();
-        let mut style_run_end = 0;
-        let mut _color = black(); // todo!
-        let mut underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
-
-        let mut glyph_origin = origin;
-        let mut prev_position = px(0.);
-        for (run_ix, run) in self.layout.runs.iter().enumerate() {
-            for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
-                glyph_origin.x += glyph.position.x - prev_position;
-
-                if boundaries
-                    .peek()
-                    .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,
-                        // });
-                    }
-
-                    glyph_origin = point(origin.x, glyph_origin.y + line_height);
-                }
-                prev_position = glyph.position.x;
-
-                let mut finished_underline = None;
-                if glyph.index >= style_run_end {
-                    if let Some(style_run) = color_runs.next() {
-                        style_run_end += style_run.len as usize;
-                        _color = style_run.color;
-                        if let Some((_, underline_style)) = &mut underline {
-                            if style_run.underline != *underline_style {
-                                finished_underline = underline.take();
-                            }
-                        }
-                        if style_run.underline.thickness > px(0.) {
-                            underline.get_or_insert((
-                                glyph_origin
-                                    + point(
-                                        px(0.),
-                                        baseline_offset.y + (self.layout.descent * 0.618),
-                                    ),
-                                UnderlineStyle {
-                                    color: Some(
-                                        style_run.underline.color.unwrap_or(style_run.color),
-                                    ),
-                                    thickness: style_run.underline.thickness,
-                                    squiggly: style_run.underline.squiggly,
-                                },
-                            ));
-                        }
-                    } else {
-                        style_run_end = self.layout.len;
-                        _color = black();
-                        finished_underline = underline.take();
-                    }
-                }
-
-                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,
-                    // });
-                }
-
-                cx.text_system().with_font(run.font_id, |system, font| {
-                    let _glyph_bounds = Bounds {
-                        origin: glyph_origin,
-                        size: system.bounding_box(font, self.layout.font_size)?.size,
-                    };
-                    // if glyph_bounds.intersects(visible_bounds) {
-                    //     if glyph.is_emoji {
-                    //         cx.scene().push_image_glyph(scene::ImageGlyph {
-                    //             font_id: run.font_id,
-                    //             font_size: self.layout.font_size,
-                    //             id: glyph.id,
-                    //             origin: glyph_bounds.origin() + baseline_offset,
-                    //         });
-                    //     } else {
-                    //         cx.scene().push_glyph(scene::Glyph {
-                    //             font_id: run.font_id,
-                    //             font_size: self.layout.font_size,
-                    //             id: glyph.id,
-                    //             origin: glyph_bounds.origin() + baseline_offset,
-                    //             color,
-                    //         });
-                    //     }
-                    // }
-                    anyhow::Ok(())
-                })??;
-            }
-        }
-
-        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,
-            // });
-        }
-
-        Ok(())
-    }
-}
-
-impl Run {
-    pub fn glyphs(&self) -> &[Glyph] {
+impl ShapedRun {
+    pub fn glyphs(&self) -> &[ShapedGlyph] {
         &self.glyphs
     }
 }

crates/gpui3/src/util.rs 🔗

@@ -2,12 +2,6 @@ use smol::future::FutureExt;
 use std::{future::Future, time::Duration};
 pub use util::*;
 
-pub fn post_inc(value: &mut usize) -> usize {
-    let prev = *value;
-    *value += 1;
-    prev
-}
-
 pub async fn timeout<F, T>(timeout: Duration, f: F) -> Result<T, ()>
 where
     F: Future<Output = T>,

crates/gpui3/src/window.rs 🔗

@@ -1,11 +1,15 @@
 use crate::{
-    px, AnyView, AppContext, AvailableSpace, Bounds, Context, Effect, Element, EntityId, Handle,
-    LayoutId, MainThread, MainThreadOnly, Pixels, PlatformWindow, Point, Reference, Scene, Size,
-    StackContext, Style, TaffyLayoutEngine, WeakHandle, WindowOptions,
+    image_cache::RenderImageParams, px, AnyView, AppContext, 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, WeakHandle,
+    WindowOptions, SUBPIXEL_VARIANTS,
 };
 use anyhow::Result;
 use futures::Future;
-use std::{any::TypeId, marker::PhantomData, mem, sync::Arc};
+use smallvec::SmallVec;
+use std::{any::TypeId, borrow::Cow, marker::PhantomData, mem, sync::Arc};
 use util::ResultExt;
 
 pub struct AnyWindow {}
@@ -13,11 +17,14 @@ pub struct AnyWindow {}
 pub struct Window {
     handle: AnyWindowHandle,
     platform_window: MainThreadOnly<Box<dyn PlatformWindow>>,
+    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,
+    content_mask_stack: Vec<ContentMask>,
     pub(crate) scene: Scene,
     pub(crate) dirty: bool,
 }
@@ -29,6 +36,7 @@ impl Window {
         cx: &mut MainThread<AppContext>,
     ) -> Self {
         let platform_window = cx.platform().open_window(handle, options);
+        let sprite_atlas = platform_window.sprite_atlas();
         let mouse_position = platform_window.mouse_position();
         let content_size = platform_window.content_size();
         let scale_factor = platform_window.scale_factor();
@@ -51,17 +59,42 @@ impl Window {
         Window {
             handle,
             platform_window,
+            sprite_atlas,
             rem_size: px(16.),
             content_size,
             layout_engine: TaffyLayoutEngine::new(),
             root_view: None,
             mouse_position,
+            current_layer_id: SmallVec::new(),
+            content_mask_stack: Vec::new(),
             scene: Scene::new(scale_factor),
             dirty: true,
         }
     }
 }
 
+#[derive(Clone, Debug)]
+pub struct ContentMask {
+    pub bounds: Bounds<Pixels>,
+    pub corner_radii: Corners<Pixels>,
+}
+
+impl ContentMask {
+    pub fn scale(&self, factor: f32) -> ScaledContentMask {
+        ScaledContentMask {
+            bounds: self.bounds.scale(factor),
+            corner_radii: self.corner_radii.scale(factor),
+        }
+    }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+#[repr(C)]
+pub struct ScaledContentMask {
+    bounds: Bounds<ScaledPixels>,
+    corner_radii: Corners<ScaledPixels>,
+}
+
 pub struct WindowContext<'a, 'w> {
     app: Reference<'a, AppContext>,
     window: Reference<'w, Window>,
@@ -114,6 +147,10 @@ impl<'a, 'w> WindowContext<'a, 'w> {
             .map(Into::into)?)
     }
 
+    pub fn scale_factor(&self) -> f32 {
+        self.window.scene.scale_factor
+    }
+
     pub fn rem_size(&self) -> Pixels {
         self.window.rem_size
     }
@@ -126,6 +163,34 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         &mut self.window.scene
     }
 
+    pub fn stack<R>(&mut self, order: u32, f: impl FnOnce(&mut Self) -> R) -> R {
+        self.window.current_layer_id.push(order);
+        let result = f(self);
+        self.window.current_layer_id.pop();
+        result
+    }
+
+    pub fn clip<F, R>(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        corner_radii: Corners<Pixels>,
+        f: impl FnOnce(&mut Self) -> R,
+    ) -> R {
+        let clip_mask = ContentMask {
+            bounds,
+            corner_radii,
+        };
+
+        self.window.content_mask_stack.push(clip_mask);
+        let result = f(self);
+        self.window.content_mask_stack.pop();
+        result
+    }
+
+    pub fn current_layer_id(&self) -> LayerId {
+        self.window.current_layer_id.clone()
+    }
+
     pub fn run_on_main<R>(
         &self,
         f: impl FnOnce(&mut MainThread<WindowContext>) -> R + Send + 'static,
@@ -143,6 +208,185 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         })
     }
 
+    pub fn paint_glyph(
+        &mut self,
+        origin: Point<Pixels>,
+        order: u32,
+        font_id: FontId,
+        glyph_id: GlyphId,
+        font_size: Pixels,
+        color: Hsla,
+    ) -> Result<()> {
+        let scale_factor = self.scale_factor();
+        let glyph_origin = origin.scale(scale_factor);
+        let subpixel_variant = Point {
+            x: (glyph_origin.x.0.fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
+            y: (glyph_origin.y.0.fract() * SUBPIXEL_VARIANTS as f32).floor() as u8,
+        };
+        let params = RenderGlyphParams {
+            font_id,
+            glyph_id,
+            font_size,
+            subpixel_variant,
+            scale_factor,
+            is_emoji: false,
+        };
+
+        let raster_bounds = self.text_system().raster_bounds(&params)?;
+        if !raster_bounds.is_zero() {
+            let layer_id = self.current_layer_id();
+            let tile =
+                self.window
+                    .sprite_atlas
+                    .get_or_insert_with(&params.clone().into(), &mut || {
+                        let (size, bytes) = self.text_system().rasterize_glyph(&params)?;
+                        Ok((size, Cow::Owned(bytes)))
+                    })?;
+            let bounds = Bounds {
+                origin: glyph_origin.map(|px| px.floor()) + raster_bounds.origin.map(Into::into),
+                size: tile.bounds.size.map(Into::into),
+            };
+            let content_mask = self.content_mask().scale(scale_factor);
+
+            self.window.scene.insert(
+                layer_id,
+                MonochromeSprite {
+                    order,
+                    bounds,
+                    content_mask,
+                    color,
+                    tile,
+                },
+            );
+        }
+        Ok(())
+    }
+
+    pub fn paint_emoji(
+        &mut self,
+        origin: Point<Pixels>,
+        order: u32,
+        font_id: FontId,
+        glyph_id: GlyphId,
+        font_size: Pixels,
+    ) -> Result<()> {
+        let scale_factor = self.scale_factor();
+        let glyph_origin = origin.scale(scale_factor);
+        let params = RenderGlyphParams {
+            font_id,
+            glyph_id,
+            font_size,
+            // We don't render emojis with subpixel variants.
+            subpixel_variant: Default::default(),
+            scale_factor,
+            is_emoji: true,
+        };
+
+        let raster_bounds = self.text_system().raster_bounds(&params)?;
+        if !raster_bounds.is_zero() {
+            let layer_id = self.current_layer_id();
+            let tile =
+                self.window
+                    .sprite_atlas
+                    .get_or_insert_with(&params.clone().into(), &mut || {
+                        let (size, bytes) = self.text_system().rasterize_glyph(&params)?;
+                        Ok((size, Cow::Owned(bytes)))
+                    })?;
+            let bounds = Bounds {
+                origin: glyph_origin.map(|px| px.floor()) + raster_bounds.origin.map(Into::into),
+                size: tile.bounds.size.map(Into::into),
+            };
+            let content_mask = self.content_mask().scale(scale_factor);
+
+            self.window.scene.insert(
+                layer_id,
+                PolychromeSprite {
+                    order,
+                    bounds,
+                    content_mask,
+                    tile,
+                    grayscale: false,
+                },
+            );
+        }
+        Ok(())
+    }
+
+    pub fn paint_svg(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        order: u32,
+        path: SharedString,
+        color: Hsla,
+    ) -> Result<()> {
+        let scale_factor = self.scale_factor();
+        let bounds = bounds.scale(scale_factor);
+        // Render the SVG at twice the size to get a higher quality result.
+        let params = RenderSvgParams {
+            path,
+            size: bounds
+                .size
+                .map(|pixels| DevicePixels::from((pixels.0 * 2.).ceil() as i32)),
+        };
+
+        let layer_id = self.current_layer_id();
+        let tile =
+            self.window
+                .sprite_atlas
+                .get_or_insert_with(&params.clone().into(), &mut || {
+                    let bytes = self.svg_renderer.render(&params)?;
+                    Ok((params.size, Cow::Owned(bytes)))
+                })?;
+        let content_mask = self.content_mask().scale(scale_factor);
+
+        self.window.scene.insert(
+            layer_id,
+            MonochromeSprite {
+                order,
+                bounds,
+                content_mask,
+                color,
+                tile,
+            },
+        );
+
+        Ok(())
+    }
+
+    pub fn paint_image(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        order: u32,
+        data: Arc<ImageData>,
+        grayscale: bool,
+    ) -> Result<()> {
+        let scale_factor = self.scale_factor();
+        let bounds = bounds.scale(scale_factor);
+        let params = RenderImageParams { image_id: data.id };
+
+        let layer_id = self.current_layer_id();
+        let tile = self
+            .window
+            .sprite_atlas
+            .get_or_insert_with(&params.clone().into(), &mut || {
+                Ok((data.size(), Cow::Borrowed(data.as_bytes())))
+            })?;
+        let content_mask = self.content_mask().scale(scale_factor);
+
+        self.window.scene.insert(
+            layer_id,
+            PolychromeSprite {
+                order,
+                bounds,
+                content_mask,
+                tile,
+                grayscale,
+            },
+        );
+
+        Ok(())
+    }
+
     pub(crate) fn draw(&mut self) -> Result<()> {
         let unit_entity = self.unit_entity.clone();
         self.update_entity(&unit_entity, |_, cx| {
@@ -150,15 +394,11 @@ 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);
 
-            dbg!("computing layout");
             cx.window
                 .layout_engine
                 .compute_layout(root_layout_id, available_space)?;
-            dbg!("asking for layout");
             let layout = cx.window.layout_engine.layout(root_layout_id)?;
 
-            dbg!("painting root view");
-
             root_view.paint(layout, &mut (), &mut frame_state, cx)?;
             cx.window.root_view = Some(root_view);
             let scene = cx.window.scene.take();
@@ -176,13 +416,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
     }
 }
 
-impl MainThread<WindowContext<'_, '_>> {
-    // todo!("implement other methods that use platform window")
-    fn platform_window(&self) -> &dyn PlatformWindow {
-        self.window.platform_window.borrow_on_main_thread().as_ref()
-    }
-}
-
 impl Context for WindowContext<'_, '_> {
     type EntityContext<'a, 'w, T: 'static + Send + Sync> = ViewContext<'a, 'w, T>;
     type Result<T> = T;
@@ -229,8 +462,60 @@ impl<'a, 'w> std::ops::DerefMut for WindowContext<'a, 'w> {
     }
 }
 
-impl<S> StackContext for ViewContext<'_, '_, S> {
-    fn app(&mut self) -> &mut AppContext {
+impl BorrowAppContext for WindowContext<'_, '_> {
+    fn app_mut(&mut self) -> &mut AppContext {
+        &mut *self.app
+    }
+}
+
+pub trait BorrowWindow: BorrowAppContext {
+    fn window(&self) -> &Window;
+    fn window_mut(&mut self) -> &mut Window;
+
+    fn with_content_mask<R>(&mut self, mask: ContentMask, f: impl FnOnce(&mut Self) -> R) -> R {
+        self.window_mut().content_mask_stack.push(mask);
+        let result = f(self);
+        self.window_mut().content_mask_stack.pop();
+        result
+    }
+
+    fn content_mask(&self) -> ContentMask {
+        self.window()
+            .content_mask_stack
+            .last()
+            .cloned()
+            .unwrap_or_else(|| ContentMask {
+                bounds: Bounds {
+                    origin: Point::default(),
+                    size: self.window().content_size,
+                },
+                corner_radii: Default::default(),
+            })
+    }
+
+    fn rem_size(&self) -> Pixels {
+        self.window().rem_size
+    }
+}
+
+impl BorrowWindow for WindowContext<'_, '_> {
+    fn window(&self) -> &Window {
+        &*self.window
+    }
+
+    fn window_mut(&mut self) -> &mut Window {
+        &mut *self.window
+    }
+}
+
+pub struct ViewContext<'a, 'w, S> {
+    window_cx: WindowContext<'a, 'w>,
+    entity_type: PhantomData<S>,
+    entity_id: EntityId,
+}
+
+impl<S> BorrowAppContext for ViewContext<'_, '_, S> {
+    fn app_mut(&mut self) -> &mut AppContext {
         &mut *self.window_cx.app
     }
 
@@ -255,10 +540,14 @@ impl<S> StackContext for ViewContext<'_, '_, S> {
     }
 }
 
-pub struct ViewContext<'a, 'w, S> {
-    window_cx: WindowContext<'a, 'w>,
-    entity_type: PhantomData<S>,
-    entity_id: EntityId,
+impl<S> BorrowWindow for ViewContext<'_, '_, S> {
+    fn window(&self) -> &Window {
+        &self.window_cx.window
+    }
+
+    fn window_mut(&mut self) -> &mut Window {
+        &mut *self.window_cx.window
+    }
 }
 
 impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> {

crates/storybook2/src/assets.rs 🔗

@@ -0,0 +1,30 @@
+use std::borrow::Cow;
+
+use anyhow::{anyhow, Result};
+use gpui3::{AssetSource, SharedString};
+use rust_embed::RustEmbed;
+
+#[derive(RustEmbed)]
+#[folder = "../../assets"]
+#[include = "fonts/**/*"]
+#[include = "icons/**/*"]
+#[include = "themes/**/*"]
+#[include = "sounds/**/*"]
+#[include = "*.md"]
+#[exclude = "*.DS_Store"]
+pub struct Assets;
+
+impl AssetSource for Assets {
+    fn load(&self, path: &SharedString) -> Result<Cow<[u8]>> {
+        Self::get(path.as_ref())
+            .map(|f| f.data)
+            .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))
+    }
+
+    fn list(&self, path: &SharedString) -> Result<Vec<SharedString>> {
+        Ok(Self::iter()
+            .filter(|p| p.starts_with(path.as_ref()))
+            .map(SharedString::from)
+            .collect())
+    }
+}

crates/storybook2/src/collab_panel.rs 🔗

@@ -1,7 +1,7 @@
 use crate::theme::{theme, Theme};
 use gpui3::{
-    div, img, svg, view, AppContext, ArcCow, Context, Element, IntoAnyElement, ParentElement,
-    ScrollState, StyleHelpers, View, ViewContext, WindowContext,
+    div, img, svg, view, AppContext, Context, Element, IntoAnyElement, ParentElement, ScrollState,
+    SharedString, StyleHelpers, View, ViewContext, WindowContext,
 };
 
 pub struct CollabPanel {
@@ -30,7 +30,7 @@ impl CollabPanel {
             .h_full()
             .flex()
             .flex_col()
-            .font("Zed Sans Extended")
+            .font("Courier")
             .text_color(theme.middle.base.default.foreground)
             .border_color(theme.middle.base.default.border)
             .border()
@@ -51,7 +51,7 @@ impl CollabPanel {
                             //:: https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-parent-state
                             // .group()
                             // List Section Header
-                            .child(self.list_section_header("#CRDB", true, theme))
+                            .child(self.list_section_header("#CRDB 🗃️", true, theme))
                             // List Item Large
                             .child(self.list_item(
                                 "http://github.com/maxbrunsfeld.png?s=50",
@@ -144,7 +144,7 @@ impl CollabPanel {
 
     fn list_item(
         &self,
-        avatar_uri: impl Into<ArcCow<'static, str>>,
+        avatar_uri: impl Into<SharedString>,
         label: impl IntoAnyElement<Self>,
         theme: &Theme,
     ) -> impl Element<State = Self> {

crates/storybook2/src/storybook2.rs 🔗

@@ -1,9 +1,13 @@
 #![allow(dead_code, unused_variables)]
 
-use gpui3::{Bounds, WindowBounds, WindowOptions};
+use assets::Assets;
+use gpui3::{px, size, Bounds, WindowBounds, WindowOptions};
 use log::LevelFilter;
 use simplelog::SimpleLogger;
+use std::sync::Arc;
+use workspace::workspace;
 
+mod assets;
 mod collab_panel;
 mod theme;
 mod themes;
@@ -20,15 +24,13 @@ fn main() {
 
     SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
 
-    gpui3::App::production().run(|cx| {
+    let asset_source = Arc::new(Assets);
+    gpui3::App::production(asset_source).run(|cx| {
         let window = cx.open_window(
             WindowOptions {
                 bounds: WindowBounds::Fixed(Bounds {
-                    size: gpui3::Size {
-                        width: 800_f32.into(),
-                        height: 600_f32.into(),
-                    },
-                    ..Default::default()
+                    origin: Default::default(),
+                    size: size(px(800.), px(600.)),
                 }),
                 ..Default::default()
             },
@@ -39,29 +41,6 @@ fn main() {
     });
 }
 
-use rust_embed::RustEmbed;
-use workspace::workspace;
-
-#[derive(RustEmbed)]
-#[folder = "../../assets"]
-#[include = "themes/**/*"]
-#[include = "fonts/**/*"]
-#[include = "icons/**/*"]
-#[exclude = "*.DS_Store"]
-pub struct Assets;
-
-// impl AssetSource for Assets {
-//     fn load(&self, path: &str) -> Result<std::borrow::Cow<[u8]>> {
-//         Self::get(path)
-//             .map(|f| f.data)
-//             .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))
-//     }
-
-//     fn list(&self, path: &str) -> Vec<std::borrow::Cow<'static, str>> {
-//         Self::iter().filter(|p| p.starts_with(path)).collect()
-//     }
-// }
-
 // fn load_embedded_fonts(platform: &dyn gpui2::Platform) {
 //     let font_paths = Assets.list("fonts");
 //     let mut embedded_fonts = Vec::new();

crates/storybook2/src/theme.rs 🔗

@@ -1,4 +1,6 @@
-use gpui3::{Element, Hsla, Layout, LayoutId, Result, StackContext, ViewContext, WindowContext};
+use gpui3::{
+    BorrowAppContext, Element, Hsla, Layout, LayoutId, Result, ViewContext, WindowContext,
+};
 use serde::{de::Visitor, Deserialize, Deserializer};
 use std::{collections::HashMap, fmt};
 

crates/storybook2/src/workspace.rs 🔗

@@ -2,7 +2,7 @@ use crate::ui::prelude::*;
 use crate::ui::{Panel, Stack};
 use crate::{
     collab_panel::{collab_panel, CollabPanel},
-    theme::theme,
+    theme::{theme, themed},
     themes::rose_pine_dawn,
 };
 use gpui3::{
@@ -56,31 +56,17 @@ impl Workspace {
                     .fill(theme.middle.negative.default.background),
             )
 
-        // div()
-        //     .font("Helvetica")
-        //     .text_base()
-        //     .size_full()
-        //     .fill(theme.middle.positive.default.background)
-        //     .child("Hello world")
-
-        // TODO: Implement style.
-        //.size_full().fill(gpui3::hsla(0.83, 1., 0.5, 1.))
-
-        // TODO: Debug font not font.
-        //.child("Is this thing on?")
-
         // themed(rose_pine_dawn(), cx, |cx| {
         //     div()
         //         .size_full()
         //         .flex()
         //         .flex_col()
-        //         .font("Zed Sans Extended")
+        //         .font("Courier")
         //         .gap_0()
         //         .justify_start()
         //         .items_start()
         //         .text_color(theme.lowest.base.default.foreground)
-        //         // .fill(theme.middle.base.default.background)
-        //         .fill(gpui3::hsla(0.83, 1., 0.5, 1.))
+        //         .fill(theme.middle.base.default.background)
         //         .child(titlebar(cx))
         //         .child(
         //             div()

crates/util/src/arc_cow.rs 🔗

@@ -1,4 +1,5 @@
 use std::{
+    borrow::Cow,
     fmt::{self, Debug},
     sync::Arc,
 };
@@ -47,6 +48,15 @@ impl From<String> for ArcCow<'_, str> {
     }
 }
 
+impl<'a> From<Cow<'a, str>> for ArcCow<'a, str> {
+    fn from(value: Cow<'a, str>) -> Self {
+        match value {
+            Cow::Borrowed(borrowed) => Self::Borrowed(borrowed),
+            Cow::Owned(owned) => Self::Owned(owned.into()),
+        }
+    }
+}
+
 impl<'a, T: ?Sized + ToOwned> std::borrow::Borrow<T> for ArcCow<'a, T> {
     fn borrow(&self) -> &T {
         match self {