Checkpoint

Nathan Sobo created

Change summary

crates/gpui3/src/element.rs            |  12 +
crates/gpui3/src/elements.rs           |   1 
crates/gpui3/src/elements/div.rs       | 291 ++++++++++++++++++++++++++-
crates/gpui3/src/elements/hoverable.rs | 105 ++++++++++
crates/gpui3/src/elements/img.rs       | 110 ++++++++++
crates/gpui3/src/elements/pressable.rs | 108 ++++++++++
crates/gpui3/src/elements/svg.rs       |  84 ++++++++
crates/gpui3/src/elements/text.rs      | 119 +++++++++++
crates/gpui3/src/geometry.rs           |  64 +++++-
crates/gpui3/src/gpui3.rs              |   4 
crates/gpui3/src/platform/mac.rs       |   1 
crates/gpui3/src/styled.rs             |  26 ++
crates/gpui3/src/window.rs             |  16 +
13 files changed, 909 insertions(+), 32 deletions(-)

Detailed changes

crates/gpui3/src/element.rs 🔗

@@ -1,3 +1,5 @@
+use smallvec::SmallVec;
+
 use super::{Handle, Layout, LayoutId, Pixels, Point, Result, ViewContext, WindowContext};
 use std::{any::Any, cell::RefCell, marker::PhantomData, rc::Rc};
 
@@ -21,7 +23,15 @@ pub trait Element: 'static {
 }
 
 pub trait ParentElement<S> {
-    fn child(self, child: impl IntoAnyElement<S>) -> Self;
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<S>; 2]>;
+
+    fn child(mut self, child: impl IntoAnyElement<S>) -> Self
+    where
+        Self: Sized,
+    {
+        self.children_mut().push(child.into_any());
+        self
+    }
 }
 
 trait ElementObject<S> {

crates/gpui3/src/elements.rs 🔗

@@ -2,7 +2,6 @@ pub mod div;
 pub mod editor;
 
 use super::*;
-use std::marker::PhantomData;
 
 pub use div::div;
 pub use editor::field;

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

@@ -1,38 +1,295 @@
-use super::{
-    Element, IntoAnyElement, Layout, LayoutId, ParentElement, PhantomData, Result, ViewContext,
+use crate::{
+    AnyElement, Bounds, Element, Layout, LayoutId, Overflow, ParentElement, Pixels, Point,
+    Refineable, RefinementCascade, Result, Style, Styled, ViewContext,
 };
+use smallvec::SmallVec;
+use std::{cell::Cell, rc::Rc};
+use util::ResultExt;
 
-pub struct Div<S>(PhantomData<S>);
+pub struct Div<S: 'static> {
+    styles: RefinementCascade<Style>,
+    // handlers: InteractionHandlers<V>,
+    children: SmallVec<[AnyElement<S>; 2]>,
+    scroll_state: Option<ScrollState>,
+}
+
+pub fn div<S>() -> Div<S> {
+    Div {
+        styles: Default::default(),
+        // handlers: Default::default(),
+        children: Default::default(),
+        scroll_state: None,
+    }
+}
 
 impl<S: 'static> Element for Div<S> {
     type State = S;
-    type FrameState = ();
+    type FrameState = Vec<LayoutId>;
 
     fn layout(
         &mut self,
-        _state: &mut Self::State,
-        _cx: &mut ViewContext<Self::State>,
+        view: &mut S,
+        cx: &mut ViewContext<S>,
     ) -> Result<(LayoutId, Self::FrameState)> {
-        todo!()
+        let style = self.computed_style();
+        let pop_text_style = style
+            .text_style(cx)
+            .map(|style| cx.push_text_style(style))
+            .is_some();
+
+        let children = self
+            .children
+            .iter_mut()
+            .map(|child| child.layout(view, cx))
+            .collect::<Result<Vec<LayoutId>>>()?;
+
+        if pop_text_style {
+            cx.pop_text_style();
+        }
+
+        Ok((cx.request_layout(style, children.clone())?, children))
     }
 
     fn paint(
         &mut self,
-        _layout: Layout,
-        _state: &mut Self::State,
-        _frame_state: &mut Self::FrameState,
-        _cx: &mut ViewContext<Self::State>,
+        layout: Layout,
+        state: &mut S,
+        child_layouts: &mut Self::FrameState,
+        cx: &mut ViewContext<S>,
     ) -> Result<()> {
-        todo!()
+        let Layout { order, bounds } = layout;
+
+        let style = self.computed_style();
+        let pop_text_style = style
+            .text_style(cx)
+            .map(|style| cx.push_text_style(style))
+            .is_some();
+        style.paint_background(bounds, cx);
+        // self.interaction_handlers().paint(order, bounds, cx);
+
+        let scrolled_origin = bounds.origin - self.scroll_offset(&style.overflow);
+
+        // // TODO: Support only one dimension being hidden
+        // let mut pop_layer = false;
+        // if style.overflow.y != Overflow::Visible || style.overflow.x != Overflow::Visible {
+        //     cx.scene().push_layer(Some(bounds));
+        //     pop_layer = true;
+        // }
+
+        for child in &mut self.children {
+            child.paint(scrolled_origin, state, cx);
+        }
+
+        // if pop_layer {
+        //     cx.scene().pop_layer();
+        // }
+
+        style.paint_foreground(bounds, cx);
+        if pop_text_style {
+            cx.pop_text_style();
+        }
+
+        self.handle_scroll(order, bounds, style.overflow.clone(), child_layouts, cx);
+
+        // if cx.is_inspector_enabled() {
+        //     self.paint_inspector(parent_origin, layout, cx);
+        // }
+        //
+        Ok(())
     }
 }
 
-impl<S> ParentElement<S> for Div<S> {
-    fn child(self, _child: impl IntoAnyElement<S>) -> Self {
-        todo!()
+impl<V: 'static> Div<V> {
+    pub fn overflow_hidden(mut self) -> Self {
+        self.declared_style().overflow.x = Some(Overflow::Hidden);
+        self.declared_style().overflow.y = Some(Overflow::Hidden);
+        self
+    }
+
+    pub fn overflow_hidden_x(mut self) -> Self {
+        self.declared_style().overflow.x = Some(Overflow::Hidden);
+        self
+    }
+
+    pub fn overflow_hidden_y(mut self) -> Self {
+        self.declared_style().overflow.y = Some(Overflow::Hidden);
+        self
+    }
+
+    pub fn overflow_scroll(mut self, scroll_state: ScrollState) -> Self {
+        self.scroll_state = Some(scroll_state);
+        self.declared_style().overflow.x = Some(Overflow::Scroll);
+        self.declared_style().overflow.y = Some(Overflow::Scroll);
+        self
+    }
+
+    pub fn overflow_x_scroll(mut self, scroll_state: ScrollState) -> Self {
+        self.scroll_state = Some(scroll_state);
+        self.declared_style().overflow.x = Some(Overflow::Scroll);
+        self
+    }
+
+    pub fn overflow_y_scroll(mut self, scroll_state: ScrollState) -> Self {
+        self.scroll_state = Some(scroll_state);
+        self.declared_style().overflow.y = Some(Overflow::Scroll);
+        self
+    }
+
+    fn scroll_offset(&self, overflow: &Point<Overflow>) -> Point<Pixels> {
+        let mut offset = Point::default();
+        if overflow.y == Overflow::Scroll {
+            offset.y = self.scroll_state.as_ref().unwrap().y();
+        }
+        if overflow.x == Overflow::Scroll {
+            offset.x = self.scroll_state.as_ref().unwrap().x();
+        }
+
+        offset
+    }
+
+    fn handle_scroll(
+        &mut self,
+        order: u32,
+        bounds: Bounds<Pixels>,
+        overflow: Point<Overflow>,
+        child_layout_ids: &[LayoutId],
+        cx: &mut ViewContext<V>,
+    ) {
+        if overflow.y == Overflow::Scroll || overflow.x == Overflow::Scroll {
+            let mut scroll_max = Point::default();
+            for child_layout_id in child_layout_ids {
+                if let Some(child_layout) = cx.layout(*child_layout_id).log_err() {
+                    scroll_max = scroll_max.max(&child_layout.bounds.lower_right());
+                }
+            }
+            scroll_max -= bounds.size;
+
+            // let scroll_state = self.scroll_state.as_ref().unwrap().clone();
+            // cx.on_event(order, move |_, event: &ScrollWheelEvent, cx| {
+            //     if bounds.contains_point(event.position) {
+            //         let scroll_delta = match event.delta {
+            //             ScrollDelta::Pixels(delta) => delta,
+            //             ScrollDelta::Lines(delta) => cx.text_style().font_size * delta,
+            //         };
+            //         if overflow.x == Overflow::Scroll {
+            //             scroll_state.set_x(
+            //                 (scroll_state.x() - scroll_delta.x())
+            //                     .max(px(0.))
+            //                     .min(scroll_max.x),
+            //             );
+            //         }
+            //         if overflow.y == Overflow::Scroll {
+            //             scroll_state.set_y(
+            //                 (scroll_state.y() - scroll_delta.y())
+            //                     .max(px(0.))
+            //                     .min(scroll_max.y),
+            //             );
+            //         }
+            //         cx.repaint();
+            //     } else {
+            //         cx.bubble_event();
+            //     }
+            // })
+        }
+    }
+
+    fn paint_inspector(
+        &self,
+        parent_origin: Point<Pixels>,
+        layout: &Layout,
+        cx: &mut ViewContext<V>,
+    ) {
+        let style = self.styles.merged();
+        let bounds = layout.bounds;
+
+        let hovered = bounds.contains_point(cx.mouse_position());
+        if hovered {
+            let rem_size = cx.rem_size();
+            // cx.scene().push_quad(scene::Quad {
+            //     bounds,
+            //     background: Some(hsla(0., 0., 1., 0.05).into()),
+            //     border: gpui::Border {
+            //         color: hsla(0., 0., 1., 0.2).into(),
+            //         top: 1.,
+            //         right: 1.,
+            //         bottom: 1.,
+            //         left: 1.,
+            //     },
+            //     corner_radii: CornerRadii::default()
+            //         .refined(&style.corner_radii)
+            //         .to_gpui(bounds.size(), rem_size),
+            // })
+        }
+
+        // let pressed = Cell::new(hovered && cx.is_mouse_down(MouseButton::Left));
+        // cx.on_event(layout.order, move |_, event: &MouseButtonEvent, _| {
+        //     if bounds.contains_point(event.position) {
+        //         if event.is_down {
+        //             pressed.set(true);
+        //         } else if pressed.get() {
+        //             pressed.set(false);
+        //             eprintln!("clicked div {:?} {:#?}", bounds, style);
+        //         }
+        //     }
+        // });
+
+        // let hovered = Cell::new(hovered);
+        // cx.on_event(layout.order, move |_, event: &MouseMovedEvent, cx| {
+        //     cx.bubble_event();
+        //     let hovered_now = bounds.contains_point(event.position);
+        //     if hovered.get() != hovered_now {
+        //         hovered.set(hovered_now);
+        //         cx.repaint();
+        //     }
+        // });
     }
 }
 
-pub fn div<S>() -> Div<S> {
-    todo!()
+impl<V> Styled for Div<V> {
+    type Style = Style;
+
+    fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
+        &mut self.styles
+    }
+
+    fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
+        self.styles.base()
+    }
+}
+
+// impl<V> Interactive<V> for Div<V> {
+//     fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
+//         &mut self.handlers
+//     }
+// }
+
+impl<V: 'static> ParentElement<V> for Div<V> {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
+        &mut self.children
+    }
+}
+
+#[derive(Default, Clone)]
+pub struct ScrollState(Rc<Cell<Point<Pixels>>>);
+
+impl ScrollState {
+    pub fn x(&self) -> Pixels {
+        self.0.get().x
+    }
+
+    pub fn set_x(&self, value: Pixels) {
+        let mut current_value = self.0.get();
+        current_value.x = value;
+        self.0.set(current_value);
+    }
+
+    pub fn y(&self) -> Pixels {
+        self.0.get().y
+    }
+
+    pub fn set_y(&self, value: Pixels) {
+        let mut current_value = self.0.get();
+        current_value.y = value;
+        self.0.set(current_value);
+    }
 }

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

@@ -0,0 +1,105 @@
+use crate::{
+    element::{AnyElement, Element, IntoElement, Layout, ParentElement},
+    interactive::{InteractionHandlers, Interactive},
+    style::{Style, StyleHelpers, Styleable},
+    ViewContext,
+};
+use anyhow::Result;
+use gpui::{geometry::vector::Vector2F, platform::MouseMovedEvent, LayoutId};
+use refineable::{CascadeSlot, Refineable, RefinementCascade};
+use smallvec::SmallVec;
+use std::{cell::Cell, rc::Rc};
+
+pub struct Hoverable<E: Styleable> {
+    hovered: Rc<Cell<bool>>,
+    cascade_slot: CascadeSlot,
+    hovered_style: <E::Style as Refineable>::Refinement,
+    child: E,
+}
+
+pub fn hoverable<E: Styleable>(mut child: E) -> Hoverable<E> {
+    Hoverable {
+        hovered: Rc::new(Cell::new(false)),
+        cascade_slot: child.style_cascade().reserve(),
+        hovered_style: Default::default(),
+        child,
+    }
+}
+
+impl<E: Styleable> Styleable for Hoverable<E> {
+    type Style = E::Style;
+
+    fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
+        self.child.style_cascade()
+    }
+
+    fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
+        &mut self.hovered_style
+    }
+}
+
+impl<V: 'static, E: Element<V> + Styleable> Element<V> for Hoverable<E> {
+    type PaintState = E::PaintState;
+
+    fn layout(
+        &mut self,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
+    ) -> Result<(LayoutId, Self::PaintState)>
+    where
+        Self: Sized,
+    {
+        Ok(self.child.layout(view, cx)?)
+    }
+
+    fn paint(
+        &mut self,
+        view: &mut V,
+        parent_origin: Vector2F,
+        layout: &Layout,
+        paint_state: &mut Self::PaintState,
+        cx: &mut ViewContext<V>,
+    ) where
+        Self: Sized,
+    {
+        let bounds = layout.bounds + parent_origin;
+        self.hovered.set(bounds.contains_point(cx.mouse_position()));
+
+        let slot = self.cascade_slot;
+        let style = self.hovered.get().then_some(self.hovered_style.clone());
+        self.style_cascade().set(slot, style);
+
+        let hovered = self.hovered.clone();
+        cx.on_event(layout.order, move |_view, _: &MouseMovedEvent, cx| {
+            cx.bubble_event();
+            if bounds.contains_point(cx.mouse_position()) != hovered.get() {
+                cx.repaint();
+            }
+        });
+
+        self.child
+            .paint(view, parent_origin, layout, paint_state, cx);
+    }
+}
+
+impl<E: Styleable<Style = Style>> StyleHelpers for Hoverable<E> {}
+
+impl<V: 'static, E: Interactive<V> + Styleable> Interactive<V> for Hoverable<E> {
+    fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
+        self.child.interaction_handlers()
+    }
+}
+
+impl<V: 'static, E: ParentElement<V> + Styleable> ParentElement<V> for Hoverable<E> {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
+        self.child.children_mut()
+    }
+}
+
+impl<V: 'static, E: Element<V> + Styleable> IntoElement<V> for Hoverable<E> {
+    type Element = Self;
+
+    fn into_element(self) -> Self::Element {
+        self
+    }
+}

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

@@ -0,0 +1,110 @@
+use crate as gpui2;
+use crate::{
+    style::{Style, StyleHelpers, Styleable},
+    Element,
+};
+use futures::FutureExt;
+use gpui::geometry::vector::Vector2F;
+use gpui::scene;
+use gpui2_macros::IntoElement;
+use refineable::RefinementCascade;
+use util::arc_cow::ArcCow;
+use util::ResultExt;
+
+#[derive(IntoElement)]
+pub struct Img {
+    style: RefinementCascade<Style>,
+    uri: Option<ArcCow<'static, str>>,
+}
+
+pub fn img() -> Img {
+    Img {
+        style: RefinementCascade::default(),
+        uri: None,
+    }
+}
+
+impl Img {
+    pub fn uri(mut self, uri: impl Into<ArcCow<'static, str>>) -> Self {
+        self.uri = Some(uri.into());
+        self
+    }
+}
+
+impl<V: 'static> Element<V> for Img {
+    type PaintState = ();
+
+    fn layout(
+        &mut self,
+        _: &mut V,
+        cx: &mut crate::ViewContext<V>,
+    ) -> anyhow::Result<(gpui::LayoutId, Self::PaintState)>
+    where
+        Self: Sized,
+    {
+        let style = self.computed_style();
+        let layout_id = cx.add_layout_node(style, [])?;
+        Ok((layout_id, ()))
+    }
+
+    fn paint(
+        &mut self,
+        _: &mut V,
+        parent_origin: Vector2F,
+        layout: &gpui::Layout,
+        _: &mut Self::PaintState,
+        cx: &mut crate::ViewContext<V>,
+    ) where
+        Self: Sized,
+    {
+        let style = self.computed_style();
+        let bounds = layout.bounds + parent_origin;
+
+        style.paint_background(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();
+            }
+        }
+    }
+}
+
+impl Styleable for Img {
+    type Style = Style;
+
+    fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
+        &mut self.style
+    }
+
+    fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
+        self.style.base()
+    }
+}
+
+impl StyleHelpers for Img {}

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

@@ -0,0 +1,108 @@
+use crate::{
+    element::{AnyElement, Element, IntoElement, Layout, ParentElement},
+    interactive::{InteractionHandlers, Interactive},
+    style::{Style, StyleHelpers, Styleable},
+    ViewContext,
+};
+use anyhow::Result;
+use gpui::{geometry::vector::Vector2F, platform::MouseButtonEvent, LayoutId};
+use refineable::{CascadeSlot, Refineable, RefinementCascade};
+use smallvec::SmallVec;
+use std::{cell::Cell, rc::Rc};
+
+pub struct Pressable<E: Styleable> {
+    pressed: Rc<Cell<bool>>,
+    pressed_style: <E::Style as Refineable>::Refinement,
+    cascade_slot: CascadeSlot,
+    child: E,
+}
+
+pub fn pressable<E: Styleable>(mut child: E) -> Pressable<E> {
+    Pressable {
+        pressed: Rc::new(Cell::new(false)),
+        pressed_style: Default::default(),
+        cascade_slot: child.style_cascade().reserve(),
+        child,
+    }
+}
+
+impl<E: Styleable> Styleable for Pressable<E> {
+    type Style = E::Style;
+
+    fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
+        &mut self.pressed_style
+    }
+
+    fn style_cascade(&mut self) -> &mut RefinementCascade<E::Style> {
+        self.child.style_cascade()
+    }
+}
+
+impl<V: 'static, E: Element<V> + Styleable> Element<V> for Pressable<E> {
+    type PaintState = E::PaintState;
+
+    fn layout(
+        &mut self,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
+    ) -> Result<(LayoutId, Self::PaintState)>
+    where
+        Self: Sized,
+    {
+        self.child.layout(view, cx)
+    }
+
+    fn paint(
+        &mut self,
+        view: &mut V,
+        parent_origin: Vector2F,
+        layout: &Layout,
+        paint_state: &mut Self::PaintState,
+        cx: &mut ViewContext<V>,
+    ) where
+        Self: Sized,
+    {
+        let slot = self.cascade_slot;
+        let style = self.pressed.get().then_some(self.pressed_style.clone());
+        self.style_cascade().set(slot, style);
+
+        let pressed = self.pressed.clone();
+        let bounds = layout.bounds + parent_origin;
+        cx.on_event(layout.order, move |_view, event: &MouseButtonEvent, cx| {
+            if event.is_down {
+                if bounds.contains_point(event.position) {
+                    pressed.set(true);
+                    cx.repaint();
+                }
+            } else if pressed.get() {
+                pressed.set(false);
+                cx.repaint();
+            }
+        });
+
+        self.child
+            .paint(view, parent_origin, layout, paint_state, cx);
+    }
+}
+
+impl<E: Styleable<Style = Style>> StyleHelpers for Pressable<E> {}
+
+impl<V: 'static, E: Interactive<V> + Styleable> Interactive<V> for Pressable<E> {
+    fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
+        self.child.interaction_handlers()
+    }
+}
+
+impl<V: 'static, E: ParentElement<V> + Styleable> ParentElement<V> for Pressable<E> {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
+        self.child.children_mut()
+    }
+}
+
+impl<V: 'static, E: Element<V> + Styleable> IntoElement<V> for Pressable<E> {
+    type Element = Self;
+
+    fn into_element(self) -> Self::Element {
+        self
+    }
+}

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

@@ -0,0 +1,84 @@
+use crate::{
+    self as gpui2, scene,
+    style::{Style, StyleHelpers, Styleable},
+    Element, IntoElement, Layout, LayoutId, Rgba,
+};
+use gpui::geometry::vector::Vector2F;
+use refineable::RefinementCascade;
+use std::borrow::Cow;
+use util::ResultExt;
+
+#[derive(IntoElement)]
+pub struct Svg {
+    path: Option<Cow<'static, str>>,
+    style: RefinementCascade<Style>,
+}
+
+pub fn svg() -> Svg {
+    Svg {
+        path: None,
+        style: RefinementCascade::<Style>::default(),
+    }
+}
+
+impl Svg {
+    pub fn path(mut self, path: impl Into<Cow<'static, str>>) -> Self {
+        self.path = Some(path.into());
+        self
+    }
+}
+
+impl<V: 'static> Element<V> for Svg {
+    type PaintState = ();
+
+    fn layout(
+        &mut self,
+        _: &mut V,
+        cx: &mut crate::ViewContext<V>,
+    ) -> anyhow::Result<(LayoutId, Self::PaintState)>
+    where
+        Self: Sized,
+    {
+        let style = self.computed_style();
+        Ok((cx.add_layout_node(style, [])?, ()))
+    }
+
+    fn paint(
+        &mut self,
+        _: &mut V,
+        parent_origin: Vector2F,
+        layout: &Layout,
+        _: &mut Self::PaintState,
+        cx: &mut crate::ViewContext<V>,
+    ) where
+        Self: Sized,
+    {
+        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);
+            }
+        }
+    }
+}
+
+impl Styleable for Svg {
+    type Style = Style;
+
+    fn style_cascade(&mut self) -> &mut refineable::RefinementCascade<Self::Style> {
+        &mut self.style
+    }
+
+    fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
+        self.style.base()
+    }
+}
+
+impl StyleHelpers for Svg {}

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

@@ -0,0 +1,119 @@
+use crate::{
+    element::{Element, IntoElement, Layout},
+    ViewContext,
+};
+use anyhow::Result;
+use gpui::{
+    geometry::{vector::Vector2F, Size},
+    text_layout::LineLayout,
+    LayoutId,
+};
+use parking_lot::Mutex;
+use std::sync::Arc;
+use util::arc_cow::ArcCow;
+
+impl<V: 'static> IntoElement<V> for ArcCow<'static, str> {
+    type Element = Text;
+
+    fn into_element(self) -> Self::Element {
+        Text { text: self }
+    }
+}
+
+impl<V: 'static> IntoElement<V> for &'static str {
+    type Element = Text;
+
+    fn into_element(self) -> Self::Element {
+        Text {
+            text: ArcCow::from(self),
+        }
+    }
+}
+
+pub struct Text {
+    text: ArcCow<'static, str>,
+}
+
+impl<V: 'static> Element<V> for Text {
+    type PaintState = Arc<Mutex<Option<TextLayout>>>;
+
+    fn layout(
+        &mut self,
+        _view: &mut V,
+        cx: &mut ViewContext<V>,
+    ) -> Result<(LayoutId, Self::PaintState)> {
+        let fonts = cx.platform().fonts();
+        let text_style = cx.text_style();
+        let line_height = cx.font_cache().line_height(text_style.font_size);
+        let text = self.text.clone();
+        let paint_state = Arc::new(Mutex::new(None));
+
+        let layout_id = cx.add_measured_layout_node(Default::default(), {
+            let paint_state = paint_state.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,
+                };
+
+                paint_state.lock().replace(TextLayout {
+                    line_layout: Arc::new(line_layout),
+                    line_height,
+                });
+
+                size
+            }
+        });
+
+        Ok((layout_id?, paint_state))
+    }
+
+    fn paint<'a>(
+        &mut self,
+        _view: &mut V,
+        parent_origin: Vector2F,
+        layout: &Layout,
+        paint_state: &mut Self::PaintState,
+        cx: &mut ViewContext<V>,
+    ) {
+        let bounds = layout.bounds + parent_origin;
+
+        let line_layout;
+        let line_height;
+        {
+            let paint_state = paint_state.lock();
+            let paint_state = paint_state
+                .as_ref()
+                .expect("measurement has not been performed");
+            line_layout = paint_state.line_layout.clone();
+            line_height = paint_state.line_height;
+        }
+
+        let text_style = cx.text_style();
+        let line =
+            gpui::text_layout::Line::new(line_layout, &[(self.text.len(), text_style.to_run())]);
+
+        // 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.legacy_cx);
+    }
+}
+
+impl<V: 'static> IntoElement<V> for Text {
+    type Element = Self;
+
+    fn into_element(self) -> Self::Element {
+        self
+    }
+}
+
+pub struct TextLayout {
+    line_layout: Arc<LineLayout>,
+    line_height: f32,
+}

crates/gpui3/src/geometry.rs 🔗

@@ -2,9 +2,11 @@ use bytemuck::{Pod, Zeroable};
 use core::fmt::Debug;
 use derive_more::{Add, AddAssign, Div, Mul, Sub, SubAssign};
 use refineable::Refineable;
-use std::ops::{Add, Mul};
+use std::ops::{Add, Mul, Sub, SubAssign};
 
-#[derive(Refineable, Default, Add, AddAssign, Sub, Mul, Div, Copy, Debug, PartialEq, Eq, Hash)]
+#[derive(
+    Refineable, Default, Add, AddAssign, Sub, SubAssign, Mul, Div, Copy, Debug, PartialEq, Eq, Hash,
+)]
 #[refineable(debug)]
 #[repr(C)]
 pub struct Point<T: Clone + Debug> {
@@ -29,6 +31,30 @@ impl<T: Clone + Debug> Point<T> {
     }
 }
 
+impl<T: Clone + Debug + Sub<Output = T>> SubAssign<Size<T>> for Point<T> {
+    fn sub_assign(&mut self, rhs: Size<T>) {
+        self.x = self.x.clone() - rhs.width;
+        self.y = self.y.clone() - rhs.height;
+    }
+}
+
+impl<T: Clone + Debug + std::cmp::PartialOrd> Point<T> {
+    pub fn max(&self, other: &Self) -> Self {
+        Point {
+            x: if self.x >= other.x {
+                self.x.clone()
+            } else {
+                other.x.clone()
+            },
+            y: if self.y >= other.y {
+                self.y.clone()
+            } else {
+                other.y.clone()
+            },
+        }
+    }
+}
+
 impl<T: Clone + Debug> Clone for Point<T> {
     fn clone(&self) -> Self {
         Self {
@@ -94,6 +120,22 @@ impl<T: Clone + Debug + Copy + Add<T, Output = T>> Bounds<T> {
             y: self.origin.y,
         }
     }
+
+    pub fn lower_right(&self) -> Point<T> {
+        Point {
+            x: self.origin.x + self.size.width,
+            y: self.origin.y + self.size.height,
+        }
+    }
+}
+
+impl<T: Clone + Debug + Copy + PartialOrd + Add<T, Output = T>> Bounds<T> {
+    pub fn contains_point(&self, point: Point<T>) -> bool {
+        point.x >= self.origin.x
+            && point.x <= self.origin.x + self.size.width
+            && point.y >= self.origin.y
+            && point.y <= self.origin.y + self.size.height
+    }
 }
 
 impl<T: Clone + Debug + Copy> Copy for Bounds<T> {}
@@ -168,9 +210,9 @@ impl Edges<Pixels> {
 #[repr(transparent)]
 pub struct Pixels(pub(crate) f32);
 
-impl From<Pixels> for f64 {
-    fn from(pixels: Pixels) -> Self {
-        pixels.0.into()
+impl Pixels {
+    pub fn round(&self) -> Self {
+        Self(self.0.round())
     }
 }
 
@@ -190,12 +232,6 @@ impl Mul<Pixels> for Pixels {
     }
 }
 
-impl Pixels {
-    pub fn round(&self) -> Self {
-        Self(self.0.round())
-    }
-}
-
 impl Eq for Pixels {}
 
 impl Ord for Pixels {
@@ -231,6 +267,12 @@ impl From<&Pixels> for f32 {
     }
 }
 
+impl From<Pixels> for f64 {
+    fn from(pixels: Pixels) -> Self {
+        pixels.0 as f64
+    }
+}
+
 #[derive(Clone, Copy, Default, Add, Sub, Mul, Div)]
 pub struct Rems(f32);
 

crates/gpui3/src/gpui3.rs 🔗

@@ -9,6 +9,7 @@ mod platform;
 mod renderer;
 mod scene;
 mod style;
+mod styled;
 mod taffy;
 mod text;
 mod util;
@@ -23,10 +24,13 @@ pub use executor::*;
 pub use fonts::*;
 pub use geometry::*;
 pub use platform::*;
+pub use refineable::*;
 pub use scene::*;
+pub use smallvec;
 pub use smol::Timer;
 use std::ops::{Deref, DerefMut};
 pub use style::*;
+pub use styled::*;
 pub use taffy::LayoutId;
 use taffy::TaffyLayoutEngine;
 use text::*;

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

@@ -28,7 +28,6 @@ use objc::{
 pub use platform::*;
 pub use screen::*;
 pub use window::*;
-use window_appearence::*;
 
 trait BoolExt {
     fn to_objc(self) -> BOOL;

crates/gpui3/src/styled.rs 🔗

@@ -0,0 +1,26 @@
+use crate::{Refineable, RefinementCascade};
+
+pub trait Styled {
+    type Style: Refineable + Default;
+
+    fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style>;
+    fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement;
+
+    fn computed_style(&mut self) -> Self::Style {
+        Self::Style::from_refinement(&self.style_cascade().merged())
+    }
+
+    // fn hover(self) -> Hoverable<Self>
+    // where
+    //     Self: Sized,
+    // {
+    //     hoverable(self)
+    // }
+
+    // fn active(self) -> Pressable<Self>
+    // where
+    //     Self: Sized,
+    // {
+    //     pressable(self)
+    // }
+}

crates/gpui3/src/window.rs 🔗

@@ -1,4 +1,4 @@
-use crate::PlatformWindow;
+use crate::{PlatformWindow, Point, TextStyleRefinement};
 
 use super::{
     px, taffy::LayoutId, AppContext, Bounds, Context, EntityId, Handle, Pixels, Reference, Style,
@@ -18,6 +18,7 @@ pub struct Window {
     platform_window: Box<dyn PlatformWindow>,
     rem_size: Pixels,
     layout_engine: TaffyLayoutEngine,
+    text_style_stack: Vec<TextStyleRefinement>,
     pub(crate) root_view: Option<Box<dyn Any>>,
 }
 
@@ -28,6 +29,7 @@ impl Window {
             platform_window,
             rem_size: px(16.),
             layout_engine: TaffyLayoutEngine::new(),
+            text_style_stack: Vec::new(),
             root_view: None,
         }
     }
@@ -82,6 +84,18 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         self.window.rem_size
     }
 
+    pub fn push_text_style(&mut self, text_style: TextStyleRefinement) {
+        self.window.text_style_stack.push(text_style);
+    }
+
+    pub fn pop_text_style(&mut self) {
+        self.window.text_style_stack.pop();
+    }
+
+    pub fn mouse_position(&self) -> Point<Pixels> {
+        self.window.platform_window.mouse_position()
+    }
+
     fn update_window<R>(
         &mut self,
         window_id: WindowId,