Get text rendering

Nathan Sobo and Antonio Scandurra created

Co-Authored-By: Antonio Scandurra <antonio@zed.dev>

Change summary

Cargo.lock                                               |   1 
crates/gpui/playground/Cargo.toml                        |   1 
crates/gpui/playground/src/color.rs                      |   9 
crates/gpui/playground/src/components.rs                 |  20 
crates/gpui/playground/src/element.rs                    |  69 ++++
crates/gpui/playground/src/frame.rs                      |  24 +
crates/gpui/playground/src/playground.rs                 |  13 
crates/gpui/playground/src/style.rs                      |  25 +
crates/gpui/playground/src/text.rs                       | 147 +++++++++
crates/gpui/playground_macros/src/derive_element.rs      |  13 
crates/gpui/playground_macros/src/derive_into_element.rs |  95 ++++++
crates/gpui/playground_macros/src/playground_macros.rs   |  16 
crates/gpui/src/app.rs                                   |  66 ++-
crates/gpui/src/app/window.rs                            |   1 
crates/gpui/src/text_layout.rs                           |   6 
15 files changed, 436 insertions(+), 70 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -5304,6 +5304,7 @@ dependencies = [
  "gpui",
  "log",
  "optional_struct",
+ "parking_lot 0.11.2",
  "playground_macros",
  "serde",
  "simplelog",

crates/gpui/playground/Cargo.toml 🔗

@@ -14,6 +14,7 @@ gpui = { path = ".." }
 log.workspace = true
 optional_struct = "0.3.1"
 playground_macros = { path = "../playground_macros" }
+parking_lot.workspace = true
 serde.workspace = true
 simplelog = "0.9"
 smallvec.workspace = true

crates/gpui/playground/src/color.rs 🔗

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

crates/gpui/playground/src/components.rs 🔗

@@ -1,11 +1,12 @@
 use crate::{
     element::{Element, ElementMetadata},
     frame,
+    text::ArcCow,
     themes::rose_pine,
 };
 use gpui::ViewContext;
 use playground_macros::Element;
-use std::{borrow::Cow, marker::PhantomData, rc::Rc};
+use std::{marker::PhantomData, rc::Rc};
 
 struct ButtonHandlers<V, D> {
     click: Option<Rc<dyn Fn(&mut V, &D)>>,
@@ -22,8 +23,8 @@ impl<V, D> Default for ButtonHandlers<V, D> {
 pub struct Button<V: 'static, D: 'static> {
     metadata: ElementMetadata<V>,
     handlers: ButtonHandlers<V, D>,
-    label: Option<Cow<'static, str>>,
-    icon: Option<Cow<'static, str>>,
+    label: Option<ArcCow<'static, str>>,
+    icon: Option<ArcCow<'static, str>>,
     data: Rc<D>,
     view_type: PhantomData<V>,
 }
@@ -56,17 +57,17 @@ impl<V: 'static> Button<V, ()> {
 
 // Impl block for *any* button.
 impl<V: 'static, D: 'static> Button<V, D> {
-    fn label(mut self, label: impl Into<Cow<'static, str>>) -> Self {
+    pub fn label(mut self, label: impl Into<ArcCow<'static, str>>) -> Self {
         self.label = Some(label.into());
         self
     }
 
-    fn icon(mut self, icon: impl Into<Cow<'static, str>>) -> Self {
+    pub fn icon(mut self, icon: impl Into<ArcCow<'static, str>>) -> Self {
         self.icon = Some(icon.into());
         self
     }
 
-    fn click(self, handler: impl Fn(&mut V, &D) + 'static) -> Self {
+    pub fn click(self, handler: impl Fn(&mut V, &D) + 'static) -> Self {
         let data = self.data.clone();
         Element::click(self, move |view, _| {
             handler(view, data.as_ref());
@@ -80,8 +81,11 @@ pub fn button<V>() -> Button<V, ()> {
 
 impl<V: 'static, D: 'static> Button<V, D> {
     fn render(&mut self, view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
-        // TODO: Drive from the context
-        let button = frame().fill(rose_pine::dawn().error(0.5)).h_5().w_9();
+        // TODO: Drive theme from the context
+        let button = frame()
+            .fill(rose_pine::dawn().error(0.5))
+            .h_4()
+            .children(self.label.clone());
 
         if let Some(handler) = self.handlers.click.clone() {
             let data = self.data.clone();

crates/gpui/playground/src/element.rs 🔗

@@ -1,5 +1,6 @@
 use crate::{
     adapter::Adapter,
+    color::Hsla,
     style::{Display, ElementStyle, Fill, Overflow, Position},
 };
 use anyhow::Result;
@@ -8,7 +9,7 @@ pub use gpui::LayoutContext;
 use gpui::{
     geometry::{DefinedLength, Length},
     scene::MouseClick,
-    EngineLayout, PaintContext as LegacyPaintContext,
+    EngineLayout, PaintContext as LegacyPaintContext, RenderContext,
 };
 use playground_macros::tailwind_lengths;
 use std::{any::Any, rc::Rc};
@@ -22,6 +23,20 @@ pub struct PaintContext<'a, 'b, 'c, 'd, V> {
     pub(crate) scene: &'d mut gpui::SceneBuilder,
 }
 
+impl<V> RenderContext for PaintContext<'_, '_, '_, '_, V> {
+    fn text_style(&self) -> gpui::fonts::TextStyle {
+        self.legacy_cx.text_style()
+    }
+
+    fn push_text_style(&mut self, style: gpui::fonts::TextStyle) {
+        self.legacy_cx.push_text_style(style)
+    }
+
+    fn pop_text_style(&mut self) {
+        self.legacy_cx.pop_text_style()
+    }
+}
+
 pub struct Layout<'a, E: ?Sized> {
     pub from_engine: EngineLayout,
     pub from_element: &'a mut E,
@@ -303,9 +318,18 @@ pub trait Element<V: 'static>: 'static {
         self.style_mut().fill = fill.into();
         self
     }
+
+    fn text_color(mut self, color: impl Into<Hsla>) -> Self
+    where
+        Self: Sized,
+    {
+        self.style_mut().text_color = Some(color.into());
+        self
+    }
 }
 
-pub trait ElementObject<V> {
+// Object-safe counterpart of Element used by AnyElement to store elements as trait objects.
+trait ElementObject<V> {
     fn style_mut(&mut self) -> &mut ElementStyle;
     fn handlers_mut(&mut self) -> &mut ElementHandlers<V>;
     fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>)
@@ -360,12 +384,33 @@ pub struct AnyElement<V> {
 
 impl<V> AnyElement<V> {
     pub fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<NodeId> {
+        let pushed_text_style = self.push_text_style(cx);
+
         let (node_id, layout) = self.element.layout(view, cx)?;
         self.layout = Some((node_id, layout));
+
+        if pushed_text_style {
+            cx.pop_text_style();
+        }
+
         Ok(node_id)
     }
 
+    pub fn push_text_style(&mut self, cx: &mut impl RenderContext) -> bool {
+        let text_style = self.element.style_mut().text_style();
+        if let Some(text_style) = text_style {
+            let mut current_text_style = cx.text_style();
+            text_style.apply(&mut current_text_style);
+            cx.push_text_style(current_text_style);
+            true
+        } else {
+            false
+        }
+    }
+
     pub fn paint(&mut self, view: &mut V, cx: &mut PaintContext<V>) -> Result<()> {
+        let pushed_text_style = self.push_text_style(cx);
+
         let (layout_node_id, element_layout) =
             self.layout.as_mut().expect("paint called before layout");
 
@@ -378,7 +423,12 @@ impl<V> AnyElement<V> {
             from_element: element_layout.as_mut(),
         };
 
-        self.element.paint(layout, view, cx)
+        self.element.paint(layout, view, cx)?;
+        if pushed_text_style {
+            cx.pop_text_style();
+        }
+
+        Ok(())
     }
 }
 
@@ -405,3 +455,16 @@ impl<V: 'static> Element<V> for AnyElement<V> {
         self.paint(view, cx)
     }
 }
+
+pub trait IntoElement<V: 'static> {
+    type Element: Element<V>;
+
+    fn into_element(self) -> Self::Element;
+
+    fn into_any_element(self) -> AnyElement<V>
+    where
+        Self: Sized,
+    {
+        self.into_element().into_any()
+    }
+}

crates/gpui/playground/src/frame.rs 🔗

@@ -1,11 +1,17 @@
 use crate::{
-    element::{AnyElement, Element, ElementHandlers, Layout, LayoutContext, NodeId, PaintContext},
+    element::{
+        AnyElement, Element, ElementHandlers, IntoElement, Layout, LayoutContext, NodeId,
+        PaintContext,
+    },
     style::ElementStyle,
 };
 use anyhow::{anyhow, Result};
 use gpui::LayoutNodeId;
+use playground_macros::IntoElement;
 
-pub struct Frame<V> {
+#[derive(IntoElement)]
+#[element_crate = "crate"]
+pub struct Frame<V: 'static> {
     style: ElementStyle,
     handlers: ElementHandlers<V>,
     children: Vec<AnyElement<V>>,
@@ -66,8 +72,18 @@ impl<V: 'static> Element<V> for Frame<V> {
 }
 
 impl<V: 'static> Frame<V> {
-    pub fn child(mut self, child: impl Element<V>) -> Self {
-        self.children.push(child.into_any());
+    pub fn child(mut self, child: impl IntoElement<V>) -> Self {
+        self.children.push(child.into_any_element());
+        self
+    }
+
+    pub fn children<I, E>(mut self, children: I) -> Self
+    where
+        I: IntoIterator<Item = E>,
+        E: IntoElement<V>,
+    {
+        self.children
+            .extend(children.into_iter().map(|e| e.into_any_element()));
         self
     }
 }

crates/gpui/playground/src/playground.rs 🔗

@@ -1,9 +1,10 @@
 #![allow(dead_code, unused_variables)]
+use color::black;
 use components::button;
 use element::Element;
 use frame::frame;
 use gpui::{
-    geometry::{percent, rect::RectF, vector::vec2f},
+    geometry::{rect::RectF, vector::vec2f},
     platform::WindowOptions,
 };
 use log::LevelFilter;
@@ -35,19 +36,21 @@ fn main() {
                 center: true,
                 ..Default::default()
             },
-            |_| view(|_| workspace(&rose_pine::moon())),
+            |_| view(|_| playground(&rose_pine::moon())),
         );
         cx.platform().activate(true);
     });
 }
 
-fn workspace<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
+fn playground<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
     frame()
+        .text_color(black())
         .h_full()
-        .w(percent(50.))
+        .w_half()
         .fill(theme.success(0.5))
-        .child(button())
+        .child(button().label("Hello").click(|_, _| (println!("hey!"))))
 }
+
 //     todo!()
 //     // column()
 //     // .size(auto())

crates/gpui/playground/src/style.rs 🔗

@@ -66,6 +66,8 @@ pub struct ElementStyle {
 
     /// The fill color of this element
     pub fill: Fill,
+    /// The color of text within this element. Cascades to children unless overridden.
+    pub text_color: Option<Hsla>,
 }
 
 impl ElementStyle {
@@ -103,6 +105,7 @@ impl ElementStyle {
             l: 0.,
             a: 0.,
         }),
+        text_color: None,
     };
 
     pub fn new() -> Self {
@@ -136,6 +139,16 @@ impl ElementStyle {
             ..Default::default() // Ignore grid properties for now
         }
     }
+
+    pub fn text_style(&self) -> Option<OptionalTextStyle> {
+        if self.text_color.is_some() {
+            Some(OptionalTextStyle {
+                color: self.text_color,
+            })
+        } else {
+            None
+        }
+    }
 }
 
 impl Default for ElementStyle {
@@ -144,6 +157,18 @@ impl Default for ElementStyle {
     }
 }
 
+pub struct OptionalTextStyle {
+    color: Option<Hsla>,
+}
+
+impl OptionalTextStyle {
+    pub fn apply(&self, style: &mut gpui::fonts::TextStyle) {
+        if let Some(color) = self.color {
+            style.color = color.into();
+        }
+    }
+}
+
 #[derive(Clone)]
 pub enum Fill {
     Color(Hsla),

crates/gpui/playground/src/text.rs 🔗

@@ -1,36 +1,155 @@
-use std::borrow::Cow;
+use crate::element::{Element, ElementMetadata, IntoElement};
+use gpui::{geometry::Size, text_layout::LineLayout, RenderContext};
+use parking_lot::Mutex;
+use std::sync::Arc;
 
-use crate::element::Element;
+impl<V: 'static, S: Into<ArcCow<'static, str>>> IntoElement<V> for S {
+    type Element = Text<V>;
 
-impl<V, S> Element<V> for S
-where
-    V: 'static,
-    S: 'static + Into<Cow<'static, str>>,
-{
-    type Layout = Cow<'static, str>;
+    fn into_element(self) -> Self::Element {
+        Text {
+            text: self.into(),
+            metadata: Default::default(),
+        }
+    }
+}
+
+pub struct Text<V> {
+    text: ArcCow<'static, str>,
+    metadata: ElementMetadata<V>,
+}
+
+impl<V: 'static> Element<V> for Text<V> {
+    type Layout = Arc<Mutex<Option<TextLayout>>>;
 
     fn style_mut(&mut self) -> &mut crate::style::ElementStyle {
-        todo!()
+        &mut self.metadata.style
     }
 
     fn handlers_mut(&mut self) -> &mut crate::element::ElementHandlers<V> {
-        todo!()
+        &mut self.metadata.handlers
     }
 
     fn layout(
         &mut self,
         view: &mut V,
-        cx: &mut crate::element::LayoutContext<V>,
+        cx: &mut gpui::LayoutContext<V>,
     ) -> anyhow::Result<(taffy::tree::NodeId, Self::Layout)> {
-        todo!()
+        let rem_size = cx.rem_pixels();
+        let fonts = cx.platform().fonts();
+        let text_style = cx.text_style();
+        let line_height = cx.font_cache().line_height(text_style.font_size);
+        let layout_engine = cx.layout_engine().expect("no layout engine present");
+        let text = self.text.clone();
+        let layout = Arc::new(Mutex::new(None));
+
+        let node_id = layout_engine.add_measured_node(self.metadata.style.to_taffy(rem_size), {
+            let layout = layout.clone();
+            move |params| {
+                let line_layout = fonts.layout_line(
+                    text.as_ref(),
+                    text_style.font_size,
+                    &[(text.len(), text_style.to_run())],
+                );
+
+                let size = Size {
+                    width: line_layout.width,
+                    height: line_height,
+                };
+
+                layout.lock().replace(TextLayout {
+                    line_layout: Arc::new(line_layout),
+                    line_height,
+                });
+
+                size
+            }
+        })?;
+
+        Ok((node_id, layout))
     }
 
     fn paint<'a>(
         &mut self,
-        layout: crate::element::Layout<Self::Layout>,
+        layout: crate::element::Layout<Arc<Mutex<Option<TextLayout>>>>,
         view: &mut V,
         cx: &mut crate::element::PaintContext<V>,
     ) -> anyhow::Result<()> {
-        todo!()
+        let element_layout_lock = layout.from_element.lock();
+        let element_layout = element_layout_lock
+            .as_ref()
+            .expect("layout has not been performed");
+        let line_layout = element_layout.line_layout.clone();
+        let line_height = element_layout.line_height;
+        drop(element_layout_lock);
+
+        let text_style = cx.text_style();
+        let line =
+            gpui::text_layout::Line::new(line_layout, &[(self.text.len(), text_style.to_run())]);
+        line.paint(
+            cx.scene,
+            layout.from_engine.bounds.origin(),
+            layout.from_engine.bounds,
+            line_height,
+            cx.legacy_cx,
+        );
+        Ok(())
+    }
+}
+
+pub struct TextLayout {
+    line_layout: Arc<LineLayout>,
+    line_height: f32,
+}
+
+pub enum ArcCow<'a, T: ?Sized> {
+    Borrowed(&'a T),
+    Owned(Arc<T>),
+}
+
+impl<'a, T: ?Sized> Clone for ArcCow<'a, T> {
+    fn clone(&self) -> Self {
+        match self {
+            Self::Borrowed(borrowed) => Self::Borrowed(borrowed),
+            Self::Owned(owned) => Self::Owned(owned.clone()),
+        }
+    }
+}
+
+impl<'a, T: ?Sized> From<&'a T> for ArcCow<'a, T> {
+    fn from(s: &'a T) -> Self {
+        Self::Borrowed(s)
+    }
+}
+
+impl<T> From<Arc<T>> for ArcCow<'_, T> {
+    fn from(s: Arc<T>) -> Self {
+        Self::Owned(s)
+    }
+}
+
+impl From<String> for ArcCow<'_, str> {
+    fn from(value: String) -> Self {
+        Self::Owned(value.into())
+    }
+}
+
+impl<T: ?Sized> std::ops::Deref for ArcCow<'_, T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        match self {
+            ArcCow::Borrowed(s) => s,
+            ArcCow::Owned(s) => s.as_ref(),
+        }
+    }
+}
+
+impl<T: ?Sized> AsRef<T> for ArcCow<'_, T> {
+    fn as_ref(&self) -> &T {
+        match self {
+            ArcCow::Borrowed(borrowed) => borrowed,
+            ArcCow::Owned(owned) => owned.as_ref(),
+        }
     }
 }

crates/gpui/playground_macros/src/derive_element.rs 🔗

@@ -5,6 +5,8 @@ use syn::{
     WhereClause,
 };
 
+use crate::derive_into_element::impl_into_element;
+
 pub fn derive_element(input: TokenStream) -> TokenStream {
     let ast = parse_macro_input!(input as DeriveInput);
     let type_name = ast.ident;
@@ -62,6 +64,15 @@ pub fn derive_element(input: TokenStream) -> TokenStream {
         }
     }
 
+    let impl_into_element = impl_into_element(
+        &impl_generics,
+        &crate_name,
+        &view_type_name,
+        &type_name,
+        &type_generics,
+        &where_clause,
+    );
+
     let gen = quote! {
         impl #impl_generics #crate_name::element::Element<#view_type_name> for #type_name #type_generics
         #where_clause
@@ -96,6 +107,8 @@ pub fn derive_element(input: TokenStream) -> TokenStream {
                 Ok(())
             }
         }
+
+        #impl_into_element
     };
 
     gen.into()

crates/gpui/playground_macros/src/derive_into_element.rs 🔗

@@ -0,0 +1,95 @@
+use proc_macro::TokenStream;
+use quote::{format_ident, quote};
+use syn::{
+    parse_macro_input, parse_quote, DeriveInput, GenericParam, Generics, Ident, Lit, Meta,
+    WhereClause,
+};
+
+pub fn derive_into_element(input: TokenStream) -> TokenStream {
+    let ast = parse_macro_input!(input as DeriveInput);
+    let type_name = ast.ident;
+
+    let crate_name: String = ast
+        .attrs
+        .iter()
+        .find_map(|attr| {
+            if attr.path.is_ident("element_crate") {
+                match attr.parse_meta() {
+                    Ok(Meta::NameValue(nv)) => {
+                        if let Lit::Str(s) = nv.lit {
+                            Some(s.value())
+                        } else {
+                            None
+                        }
+                    }
+                    _ => None,
+                }
+            } else {
+                None
+            }
+        })
+        .unwrap_or_else(|| String::from("playground"));
+
+    let crate_name = format_ident!("{}", crate_name);
+
+    let placeholder_view_generics: Generics = parse_quote! { <V: 'static> };
+    let placeholder_view_type_name: Ident = parse_quote! { V };
+    let view_type_name: Ident;
+    let impl_generics: syn::ImplGenerics<'_>;
+    let type_generics: Option<syn::TypeGenerics<'_>>;
+    let where_clause: Option<&'_ WhereClause>;
+
+    match ast.generics.params.iter().find_map(|param| {
+        if let GenericParam::Type(type_param) = param {
+            Some(type_param.ident.clone())
+        } else {
+            None
+        }
+    }) {
+        Some(type_name) => {
+            view_type_name = type_name;
+            let generics = ast.generics.split_for_impl();
+            impl_generics = generics.0;
+            type_generics = Some(generics.1);
+            where_clause = generics.2;
+        }
+        _ => {
+            view_type_name = placeholder_view_type_name;
+            let generics = placeholder_view_generics.split_for_impl();
+            impl_generics = generics.0;
+            type_generics = None;
+            where_clause = generics.2;
+        }
+    }
+
+    impl_into_element(
+        &impl_generics,
+        &crate_name,
+        &view_type_name,
+        &type_name,
+        &type_generics,
+        &where_clause,
+    )
+    .into()
+}
+
+pub fn impl_into_element(
+    impl_generics: &syn::ImplGenerics<'_>,
+    crate_name: &Ident,
+    view_type_name: &Ident,
+    type_name: &Ident,
+    type_generics: &Option<syn::TypeGenerics<'_>>,
+    where_clause: &Option<&WhereClause>,
+) -> proc_macro2::TokenStream {
+    quote! {
+        impl #impl_generics #crate_name::element::IntoElement<#view_type_name> for #type_name #type_generics
+        #where_clause
+        {
+            type Element = Self;
+
+            fn into_element(self) -> Self {
+                self
+            }
+        }
+    }
+}

crates/gpui/playground_macros/src/playground_macros.rs 🔗

@@ -1,14 +1,20 @@
 use proc_macro::TokenStream;
 
 mod derive_element;
+mod derive_into_element;
 mod tailwind_lengths;
 
-#[proc_macro_attribute]
-pub fn tailwind_lengths(attr: TokenStream, item: TokenStream) -> TokenStream {
-    tailwind_lengths::tailwind_lengths(attr, item)
-}
-
 #[proc_macro_derive(Element, attributes(element_crate))]
 pub fn derive_element(input: TokenStream) -> TokenStream {
     derive_element::derive_element(input)
 }
+
+#[proc_macro_derive(IntoElement, attributes(element_crate))]
+pub fn derive_into_element(input: TokenStream) -> TokenStream {
+    derive_into_element::derive_into_element(input)
+}
+
+#[proc_macro_attribute]
+pub fn tailwind_lengths(attr: TokenStream, item: TokenStream) -> TokenStream {
+    tailwind_lengths::tailwind_lengths(attr, item)
+}

crates/gpui/src/app.rs 🔗

@@ -3361,11 +3361,21 @@ impl<V> BorrowWindowContext for ViewContext<'_, '_, V> {
     }
 }
 
+/// Methods shared by both LayoutContext and PaintContext
+///
+/// It's that PaintContext should be implemented in terms of layout context and
+/// deref to it, in which case we wouldn't need this.
+pub trait RenderContext {
+    fn text_style(&self) -> TextStyle;
+    fn push_text_style(&mut self, style: TextStyle);
+    fn pop_text_style(&mut self);
+}
+
 pub struct LayoutContext<'a, 'b, 'c, V> {
     view_context: &'c mut ViewContext<'a, 'b, V>,
     new_parents: &'c mut HashMap<usize, usize>,
     views_to_notify_if_ancestors_change: &'c mut HashMap<usize, SmallVec<[usize; 2]>>,
-    text_style_stack: Vec<Arc<TextStyle>>,
+    text_style_stack: Vec<TextStyle>,
     pub refreshing: bool,
 }
 
@@ -3433,31 +3443,32 @@ impl<'a, 'b, 'c, V> LayoutContext<'a, 'b, 'c, V> {
             .push(self_view_id);
     }
 
-    pub fn text_style(&self) -> Arc<TextStyle> {
+    pub fn with_text_style<F, T>(&mut self, style: TextStyle, f: F) -> T
+    where
+        F: FnOnce(&mut Self) -> T,
+    {
+        self.push_text_style(style);
+        let result = f(self);
+        self.pop_text_style();
+        result
+    }
+}
+
+impl<'a, 'b, 'c, V> RenderContext for LayoutContext<'a, 'b, 'c, V> {
+    fn text_style(&self) -> TextStyle {
         self.text_style_stack
             .last()
             .cloned()
-            .unwrap_or(Arc::new(TextStyle::default(&self.font_cache)))
+            .unwrap_or(TextStyle::default(&self.font_cache))
     }
 
-    pub fn push_text_style<S: Into<Arc<TextStyle>>>(&mut self, style: S) {
-        self.text_style_stack.push(style.into());
+    fn push_text_style(&mut self, style: TextStyle) {
+        self.text_style_stack.push(style);
     }
 
-    pub fn pop_text_style(&mut self) {
+    fn pop_text_style(&mut self) {
         self.text_style_stack.pop();
     }
-
-    pub fn with_text_style<S, F, T>(&mut self, style: S, f: F) -> T
-    where
-        S: Into<Arc<TextStyle>>,
-        F: FnOnce(&mut Self) -> T,
-    {
-        self.push_text_style(style);
-        let result = f(self);
-        self.pop_text_style();
-        result
-    }
 }
 
 impl<'a, 'b, 'c, V> Deref for LayoutContext<'a, 'b, 'c, V> {
@@ -3516,7 +3527,7 @@ impl<V> BorrowWindowContext for LayoutContext<'_, '_, '_, V> {
 
 pub struct PaintContext<'a, 'b, 'c, V> {
     view_context: &'c mut ViewContext<'a, 'b, V>,
-    text_style_stack: Vec<Arc<TextStyle>>,
+    text_style_stack: Vec<TextStyle>,
 }
 
 impl<'a, 'b, 'c, V> PaintContext<'a, 'b, 'c, V> {
@@ -3526,23 +3537,22 @@ impl<'a, 'b, 'c, V> PaintContext<'a, 'b, 'c, V> {
             text_style_stack: Vec::new(),
         }
     }
+}
 
-    pub fn text_style(&self) -> Arc<TextStyle> {
+impl<'a, 'b, 'c, V> RenderContext for PaintContext<'a, 'b, 'c, V> {
+    fn text_style(&self) -> TextStyle {
         self.text_style_stack
             .last()
             .cloned()
-            .unwrap_or(Arc::new(TextStyle::default(&self.font_cache)))
+            .unwrap_or(TextStyle::default(&self.font_cache))
     }
 
-    pub fn with_text_style<S, F, T>(&mut self, style: S, f: F) -> T
-    where
-        S: Into<Arc<TextStyle>>,
-        F: FnOnce(&mut Self) -> T,
-    {
-        self.text_style_stack.push(style.into());
-        let result = f(self);
+    fn push_text_style(&mut self, style: TextStyle) {
+        self.text_style_stack.push(style);
+    }
+
+    fn pop_text_style(&mut self) {
         self.text_style_stack.pop();
-        result
     }
 }
 

crates/gpui/src/text_layout.rs 🔗

@@ -212,7 +212,7 @@ pub struct Glyph {
 }
 
 impl Line {
-    fn new(layout: Arc<LineLayout>, runs: &[(usize, RunStyle)]) -> Self {
+    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 {
@@ -364,13 +364,13 @@ impl Line {
                         origin: glyph_origin,
                     });
                 } else {
-                    scene.push_glyph(scene::Glyph {
+                    scene.push_glyph(dbg!(scene::Glyph {
                         font_id: run.font_id,
                         font_size: self.layout.font_size,
                         id: glyph.id,
                         origin: glyph_origin,
                         color,
-                    });
+                    }));
                 }
             }
         }