Add gpui2 crate

Nathan Sobo created

Change summary

Cargo.lock                                     |  16 
crates/gpui/playground/src/components.rs       |   2 
crates/gpui2/Cargo.toml                        |  21 +
crates/gpui2/src/adapter.rs                    |  78 ++++
crates/gpui2/src/color.rs                      |   0 
crates/gpui2/src/components.rs                 | 101 +++++
crates/gpui2/src/div.rs                        | 116 ++++++
crates/gpui2/src/element.rs                    | 170 ++++++++
crates/gpui2/src/gpui2.rs                      |  36 +
crates/gpui2/src/hoverable.rs                  | 104 +++++
crates/gpui2/src/interactive.rs                | 147 +++++++
crates/gpui2/src/layout_context.rs             |  64 +++
crates/gpui2/src/lib.rs                        |  14 
crates/gpui2/src/paint_context.rs              |  54 ++
crates/gpui2/src/playground.rs                 |  99 +++++
crates/gpui2/src/pressable.rs                  | 107 +++++
crates/gpui2/src/style.rs                      | 379 ++++++++++++++++++++
crates/gpui2/src/text.rs                       | 148 +++++++
crates/gpui2/src/themes.rs                     | 175 +++++++++
crates/gpui2/src/themes/rose_pine.rs           | 133 +++++++
crates/gpui2/src/view.rs                       |  26 +
crates/gpui2_macros/src/derive_element.rs      |  12 
crates/gpui2_macros/src/derive_into_element.rs |   2 
23 files changed, 1,981 insertions(+), 23 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -3166,6 +3166,22 @@ dependencies = [
 [[package]]
 name = "gpui2"
 version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "derive_more",
+ "gpui",
+ "gpui2_macros",
+ "log",
+ "parking_lot 0.11.2",
+ "refineable",
+ "rust-embed",
+ "serde",
+ "settings",
+ "simplelog",
+ "smallvec",
+ "theme",
+ "util",
+]
 
 [[package]]
 name = "gpui2_macros"

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

@@ -20,7 +20,7 @@ impl<V, D> Default for ButtonHandlers<V, D> {
     }
 }
 
-use crate as playground;
+use crate as gpui2;
 #[derive(Element)]
 pub struct Button<V: 'static, D: 'static> {
     handlers: ButtonHandlers<V, D>,

crates/gpui2/Cargo.toml 🔗

@@ -4,6 +4,25 @@ version = "0.1.0"
 edition = "2021"
 publish = false
 
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[lib]
+name = "gpui2"
+path = "src/gpui2.rs"
 
 [dependencies]
+anyhow.workspace = true
+derive_more.workspace = true
+gpui = { path = "../gpui" }
+log.workspace = true
+gpui2_macros = { path = "../gpui2_macros" }
+parking_lot.workspace = true
+refineable.workspace = true
+rust-embed.workspace = true
+serde.workspace = true
+settings = { path = "../settings" }
+simplelog = "0.9"
+smallvec.workspace = true
+theme = { path = "../theme" }
+util = { path = "../util" }
+
+[dev-dependencies]
+gpui = { path = "../gpui", features = ["test-support"] }

crates/gpui2/src/adapter.rs 🔗

@@ -0,0 +1,78 @@
+use crate::{layout_context::LayoutContext, paint_context::PaintContext};
+use gpui::{geometry::rect::RectF, LayoutEngine, LayoutId};
+use util::ResultExt;
+
+/// Makes a new, playground-style element into a legacy element.
+pub struct AdapterElement<V>(pub(crate) crate::element::AnyElement<V>);
+
+impl<V: 'static> gpui::Element<V> for AdapterElement<V> {
+    type LayoutState = Option<(LayoutEngine, LayoutId)>;
+    type PaintState = ();
+
+    fn layout(
+        &mut self,
+        constraint: gpui::SizeConstraint,
+        view: &mut V,
+        cx: &mut gpui::LayoutContext<V>,
+    ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
+        cx.push_layout_engine(LayoutEngine::new());
+
+        let size = constraint.max;
+        let mut cx = LayoutContext::new(cx);
+        let layout_id = self.0.layout(view, &mut cx).log_err();
+        if let Some(layout_id) = layout_id {
+            cx.layout_engine()
+                .unwrap()
+                .compute_layout(layout_id, constraint.max)
+                .log_err();
+        }
+
+        let layout_engine = cx.pop_layout_engine();
+        debug_assert!(layout_engine.is_some(),
+            "unexpected layout stack state. is there an unmatched pop_layout_engine in the called code?"
+        );
+
+        (constraint.max, layout_engine.zip(layout_id))
+    }
+
+    fn paint(
+        &mut self,
+        scene: &mut gpui::SceneBuilder,
+        bounds: RectF,
+        visible_bounds: RectF,
+        layout_data: &mut Option<(LayoutEngine, LayoutId)>,
+        view: &mut V,
+        legacy_cx: &mut gpui::PaintContext<V>,
+    ) -> Self::PaintState {
+        let (layout_engine, layout_id) = layout_data.take().unwrap();
+        legacy_cx.push_layout_engine(layout_engine);
+        let mut cx = PaintContext::new(legacy_cx, scene);
+        self.0.paint(view, bounds.origin(), &mut cx);
+        *layout_data = legacy_cx.pop_layout_engine().zip(Some(layout_id));
+        debug_assert!(layout_data.is_some());
+    }
+
+    fn rect_for_text_range(
+        &self,
+        range_utf16: std::ops::Range<usize>,
+        bounds: RectF,
+        visible_bounds: RectF,
+        layout: &Self::LayoutState,
+        paint: &Self::PaintState,
+        view: &V,
+        cx: &gpui::ViewContext<V>,
+    ) -> Option<RectF> {
+        todo!("implement before merging to main")
+    }
+
+    fn debug(
+        &self,
+        bounds: RectF,
+        layout: &Self::LayoutState,
+        paint: &Self::PaintState,
+        view: &V,
+        cx: &gpui::ViewContext<V>,
+    ) -> gpui::serde_json::Value {
+        todo!("implement before merging to main")
+    }
+}

crates/gpui2/src/components.rs 🔗

@@ -0,0 +1,101 @@
+use crate::{
+    div::div,
+    element::{IntoElement, ParentElement},
+    interactive::Interactive,
+    style::StyleHelpers,
+    text::ArcCow,
+    // themes::Theme,
+};
+use gpui::{platform::MouseButton, ViewContext};
+use gpui2_macros::Element;
+use std::{marker::PhantomData, rc::Rc};
+
+struct ButtonHandlers<V, D> {
+    click: Option<Rc<dyn Fn(&mut V, &D, &mut ViewContext<V>)>>,
+}
+
+impl<V, D> Default for ButtonHandlers<V, D> {
+    fn default() -> Self {
+        Self { click: None }
+    }
+}
+
+use crate as gpui2;
+#[derive(Element)]
+pub struct Button<V: 'static, D: 'static> {
+    handlers: ButtonHandlers<V, D>,
+    label: Option<ArcCow<'static, str>>,
+    icon: Option<ArcCow<'static, str>>,
+    data: Rc<D>,
+    view_type: PhantomData<V>,
+}
+
+// Impl block for buttons without data.
+// See below for an impl block for any button.
+impl<V: 'static> Button<V, ()> {
+    fn new() -> Self {
+        Self {
+            handlers: ButtonHandlers::default(),
+            label: None,
+            icon: None,
+            data: Rc::new(()),
+            view_type: PhantomData,
+        }
+    }
+
+    pub fn data<D: 'static>(self, data: D) -> Button<V, D> {
+        Button {
+            handlers: ButtonHandlers::default(),
+            label: self.label,
+            icon: self.icon,
+            data: Rc::new(data),
+            view_type: PhantomData,
+        }
+    }
+}
+
+// Impl block for button regardless of its data type.
+impl<V: 'static, D: 'static> Button<V, D> {
+    pub fn label(mut self, label: impl Into<ArcCow<'static, str>>) -> Self {
+        self.label = Some(label.into());
+        self
+    }
+
+    pub fn icon(mut self, icon: impl Into<ArcCow<'static, str>>) -> Self {
+        self.icon = Some(icon.into());
+        self
+    }
+
+    pub fn on_click(mut self, handler: impl Fn(&mut V, &D, &mut ViewContext<V>) + 'static) -> Self {
+        self.handlers.click = Some(Rc::new(handler));
+        self
+    }
+}
+
+pub fn button<V>() -> Button<V, ()> {
+    Button::new()
+}
+
+impl<V: 'static, D: 'static> Button<V, D> {
+    fn render(
+        &mut self,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
+    ) -> impl IntoElement<V> + Interactive<V> {
+        // let colors = &cx.theme::<Theme>().colors;
+
+        let button = div()
+            // .fill(colors.error(0.5))
+            .h_4()
+            .children(self.label.clone());
+
+        if let Some(handler) = self.handlers.click.clone() {
+            let data = self.data.clone();
+            button.on_mouse_down(MouseButton::Left, move |view, event, cx| {
+                handler(view, data.as_ref(), cx)
+            })
+        } else {
+            button
+        }
+    }
+}

crates/gpui2/src/div.rs 🔗

@@ -0,0 +1,116 @@
+use crate::{
+    element::{AnyElement, Element, IntoElement, Layout, ParentElement},
+    interactive::{InteractionHandlers, Interactive},
+    layout_context::LayoutContext,
+    paint_context::PaintContext,
+    style::{Style, StyleHelpers, Styleable},
+};
+use anyhow::Result;
+use gpui::{LayoutId, RenderContext};
+use refineable::{Refineable, RefinementCascade};
+use smallvec::SmallVec;
+
+pub struct Div<V: 'static> {
+    styles: RefinementCascade<Style>,
+    handlers: InteractionHandlers<V>,
+    children: SmallVec<[AnyElement<V>; 2]>,
+}
+
+pub fn div<V>() -> Div<V> {
+    Div {
+        styles: Default::default(),
+        handlers: Default::default(),
+        children: Default::default(),
+    }
+}
+
+impl<V: 'static> Element<V> for Div<V> {
+    type PaintState = ();
+
+    fn layout(
+        &mut self,
+        view: &mut V,
+        cx: &mut LayoutContext<V>,
+    ) -> Result<(LayoutId, Self::PaintState)>
+    where
+        Self: Sized,
+    {
+        let style = self.computed_style();
+        let pop_text_style = style.text_style().map_or(false, |style| {
+            cx.push_text_style(cx.text_style().clone().refined(&style));
+            true
+        });
+
+        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.add_layout_node(style, children)?, ()))
+    }
+
+    fn paint(
+        &mut self,
+        view: &mut V,
+        layout: &Layout,
+        paint_state: &mut Self::PaintState,
+        cx: &mut PaintContext<V>,
+    ) where
+        Self: Sized,
+    {
+        let style = &self.computed_style();
+        let pop_text_style = style.text_style().map_or(false, |style| {
+            let style = cx.text_style().clone().refined(&style);
+            cx.push_text_style(style);
+            true
+        });
+        style.paint_background(layout.bounds, cx);
+        self.interaction_handlers()
+            .paint(layout.order, layout.bounds, cx);
+        for child in &mut self.children {
+            child.paint(view, layout.bounds.origin(), cx);
+        }
+        if pop_text_style {
+            cx.pop_text_style();
+        }
+    }
+}
+
+impl<V> Styleable 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> StyleHelpers for Div<V> {}
+
+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
+    }
+}
+
+impl<V: 'static> IntoElement<V> for Div<V> {
+    type Element = Self;
+
+    fn into_element(self) -> Self::Element {
+        self
+    }
+}

crates/gpui2/src/element.rs 🔗

@@ -0,0 +1,170 @@
+use std::marker::PhantomData;
+
+pub use crate::layout_context::LayoutContext;
+pub use crate::paint_context::PaintContext;
+use crate::themes::{Theme, Themed};
+use anyhow::Result;
+use gpui::geometry::vector::Vector2F;
+pub use gpui::{Layout, LayoutId};
+use smallvec::SmallVec;
+
+pub trait Element<V: 'static>: 'static {
+    type PaintState;
+
+    fn layout(
+        &mut self,
+        view: &mut V,
+        cx: &mut LayoutContext<V>,
+    ) -> Result<(LayoutId, Self::PaintState)>
+    where
+        Self: Sized;
+
+    fn paint(
+        &mut self,
+        view: &mut V,
+        layout: &Layout,
+        state: &mut Self::PaintState,
+        cx: &mut PaintContext<V>,
+    ) where
+        Self: Sized;
+
+    fn into_any(self) -> AnyElement<V>
+    where
+        Self: 'static + Sized,
+    {
+        AnyElement(Box::new(StatefulElement {
+            element: self,
+            phase: ElementPhase::Init,
+        }))
+    }
+
+    fn themed(self, theme: Theme) -> Themed<V, Self>
+    where
+        Self: Sized,
+    {
+        crate::themes::Themed {
+            child: self,
+            theme,
+            view_type: PhantomData,
+        }
+    }
+}
+
+/// Used to make ElementState<V, E> into a trait object, so we can wrap it in AnyElement<V>.
+trait AnyStatefulElement<V> {
+    fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<LayoutId>;
+    fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut PaintContext<V>);
+}
+
+/// A wrapper around an element that stores its layout state.
+struct StatefulElement<V: 'static, E: Element<V>> {
+    element: E,
+    phase: ElementPhase<V, E>,
+}
+
+enum ElementPhase<V: 'static, E: Element<V>> {
+    Init,
+    PostLayout {
+        layout_id: LayoutId,
+        paint_state: E::PaintState,
+    },
+    PostPaint {
+        layout: Layout,
+        paint_state: E::PaintState,
+    },
+    Error(String),
+}
+
+impl<V: 'static, E: Element<V>> Default for ElementPhase<V, E> {
+    fn default() -> Self {
+        Self::Init
+    }
+}
+
+/// We blanket-implement the object-safe ElementStateObject interface to make ElementStates into trait objects
+impl<V, E: Element<V>> AnyStatefulElement<V> for StatefulElement<V, E> {
+    fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<LayoutId> {
+        let result;
+        self.phase = match self.element.layout(view, cx) {
+            Ok((layout_id, paint_state)) => {
+                result = Ok(layout_id);
+                ElementPhase::PostLayout {
+                    layout_id,
+                    paint_state,
+                }
+            }
+            Err(error) => {
+                let message = error.to_string();
+                result = Err(error);
+                ElementPhase::Error(message)
+            }
+        };
+        result
+    }
+
+    fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut PaintContext<V>) {
+        self.phase = match std::mem::take(&mut self.phase) {
+            ElementPhase::PostLayout {
+                layout_id,
+                mut paint_state,
+            } => match cx.computed_layout(layout_id) {
+                Ok(mut layout) => {
+                    layout.bounds = layout.bounds + parent_origin;
+                    self.element.paint(view, &layout, &mut paint_state, cx);
+                    ElementPhase::PostPaint {
+                        layout,
+                        paint_state,
+                    }
+                }
+                Err(error) => ElementPhase::Error(error.to_string()),
+            },
+            phase @ ElementPhase::Error(_) => phase,
+            _ => panic!("invalid element phase to call paint"),
+        };
+    }
+}
+
+/// A dynamic element.
+pub struct AnyElement<V>(Box<dyn AnyStatefulElement<V>>);
+
+impl<V> AnyElement<V> {
+    pub fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<LayoutId> {
+        self.0.layout(view, cx)
+    }
+
+    pub fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut PaintContext<V>) {
+        self.0.paint(view, parent_origin, cx)
+    }
+}
+
+pub trait ParentElement<V: 'static> {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]>;
+
+    fn child(mut self, child: impl IntoElement<V>) -> Self
+    where
+        Self: Sized,
+    {
+        self.children_mut().push(child.into_element().into_any());
+        self
+    }
+
+    fn children<I, E>(mut self, children: I) -> Self
+    where
+        I: IntoIterator<Item = E>,
+        E: IntoElement<V>,
+        Self: Sized,
+    {
+        self.children_mut().extend(
+            children
+                .into_iter()
+                .map(|child| child.into_element().into_any()),
+        );
+        self
+    }
+}
+
+pub trait IntoElement<V: 'static> {
+    type Element: Element<V>;
+
+    fn into_element(self) -> Self::Element;
+}

crates/gpui2/src/gpui2.rs 🔗

@@ -0,0 +1,36 @@
+#![allow(dead_code, unused_variables)]
+use gpui::{serde_json, ViewContext};
+use theme::ThemeSettings;
+use themes::Theme;
+
+mod adapter;
+mod color;
+mod components;
+mod div;
+mod element;
+mod hoverable;
+mod interactive;
+mod layout_context;
+mod paint_context;
+mod pressable;
+mod style;
+mod text;
+mod themes;
+mod view;
+
+// Nathan: During the transition, we will include the base theme on the legacy Theme struct.
+fn current_theme<V: 'static>(cx: &mut ViewContext<V>) -> Theme {
+    settings::get::<ThemeSettings>(cx)
+        .theme
+        .deserialized_base_theme
+        .lock()
+        .get_or_insert_with(|| {
+            let theme: Theme =
+                serde_json::from_value(settings::get::<ThemeSettings>(cx).theme.base_theme.clone())
+                    .unwrap();
+            Box::new(theme)
+        })
+        .downcast_ref::<Theme>()
+        .unwrap()
+        .clone()
+}

crates/gpui2/src/hoverable.rs 🔗

@@ -0,0 +1,104 @@
+use crate::{
+    element::{AnyElement, Element, IntoElement, Layout, ParentElement},
+    interactive::{InteractionHandlers, Interactive},
+    layout_context::LayoutContext,
+    paint_context::PaintContext,
+    style::{Style, StyleHelpers, Styleable},
+};
+use anyhow::Result;
+use gpui::{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 LayoutContext<V>,
+    ) -> Result<(LayoutId, Self::PaintState)>
+    where
+        Self: Sized,
+    {
+        Ok(self.child.layout(view, cx)?)
+    }
+
+    fn paint(
+        &mut self,
+        view: &mut V,
+        layout: &Layout,
+        paint_state: &mut Self::PaintState,
+        cx: &mut PaintContext<V>,
+    ) where
+        Self: Sized,
+    {
+        self.hovered
+            .set(layout.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();
+        let bounds = layout.bounds;
+        cx.on_event(layout.order, move |view, event: &MouseMovedEvent, cx| {
+            if bounds.contains_point(cx.mouse_position()) != hovered.get() {
+                cx.repaint();
+            }
+        });
+
+        self.child.paint(view, 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/gpui2/src/interactive.rs 🔗

@@ -0,0 +1,147 @@
+use gpui::{
+    geometry::rect::RectF,
+    platform::{MouseButton, MouseButtonEvent},
+    EventContext,
+};
+use smallvec::SmallVec;
+use std::{cell::Cell, rc::Rc};
+
+use crate::element::PaintContext;
+
+pub trait Interactive<V: 'static> {
+    fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V>;
+
+    fn on_mouse_down(
+        mut self,
+        button: MouseButton,
+        handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
+    ) -> Self
+    where
+        Self: Sized,
+    {
+        self.interaction_handlers()
+            .mouse_down
+            .push(Rc::new(handler));
+        self
+    }
+
+    fn on_mouse_up(
+        mut self,
+        button: MouseButton,
+        handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
+    ) -> Self
+    where
+        Self: Sized,
+    {
+        self.interaction_handlers().mouse_up.push(Rc::new(handler));
+        self
+    }
+
+    fn on_mouse_down_out(
+        mut self,
+        button: MouseButton,
+        handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
+    ) -> Self
+    where
+        Self: Sized,
+    {
+        self.interaction_handlers()
+            .mouse_down_out
+            .push(Rc::new(handler));
+        self
+    }
+
+    fn on_mouse_up_out(
+        mut self,
+        button: MouseButton,
+        handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
+    ) -> Self
+    where
+        Self: Sized,
+    {
+        self.interaction_handlers()
+            .mouse_up_out
+            .push(Rc::new(handler));
+        self
+    }
+
+    fn on_click(
+        self,
+        button: MouseButton,
+        handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
+    ) -> Self
+    where
+        Self: Sized,
+    {
+        let pressed = Rc::new(Cell::new(false));
+        self.on_mouse_down(button, {
+            let pressed = pressed.clone();
+            move |_, _, _| {
+                pressed.set(true);
+            }
+        })
+        .on_mouse_up_out(button, {
+            let pressed = pressed.clone();
+            move |_, _, _| {
+                pressed.set(false);
+            }
+        })
+        .on_mouse_up(button, move |view, event, cx| {
+            if pressed.get() {
+                pressed.set(false);
+                handler(view, event, cx);
+            }
+        })
+    }
+}
+
+pub struct InteractionHandlers<V: 'static> {
+    mouse_down: SmallVec<[Rc<dyn Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>)>; 2]>,
+    mouse_down_out: SmallVec<[Rc<dyn Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>)>; 2]>,
+    mouse_up: SmallVec<[Rc<dyn Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>)>; 2]>,
+    mouse_up_out: SmallVec<[Rc<dyn Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>)>; 2]>,
+}
+
+impl<V: 'static> InteractionHandlers<V> {
+    pub fn paint(&self, order: u32, bounds: RectF, cx: &mut PaintContext<V>) {
+        for handler in self.mouse_down.iter().cloned() {
+            cx.on_event(order, move |view, event: &MouseButtonEvent, cx| {
+                if event.is_down && bounds.contains_point(event.position) {
+                    handler(view, event, cx);
+                }
+            })
+        }
+        for handler in self.mouse_up.iter().cloned() {
+            cx.on_event(order, move |view, event: &MouseButtonEvent, cx| {
+                if !event.is_down && bounds.contains_point(event.position) {
+                    handler(view, event, cx);
+                }
+            })
+        }
+        for handler in self.mouse_down_out.iter().cloned() {
+            cx.on_event(order, move |view, event: &MouseButtonEvent, cx| {
+                if event.is_down && !bounds.contains_point(event.position) {
+                    handler(view, event, cx);
+                }
+            })
+        }
+        for handler in self.mouse_up_out.iter().cloned() {
+            cx.on_event(order, move |view, event: &MouseButtonEvent, cx| {
+                if !event.is_down && !bounds.contains_point(event.position) {
+                    handler(view, event, cx);
+                }
+            })
+        }
+    }
+}
+
+impl<V> Default for InteractionHandlers<V> {
+    fn default() -> Self {
+        Self {
+            mouse_down: Default::default(),
+            mouse_up: Default::default(),
+            mouse_down_out: Default::default(),
+            mouse_up_out: Default::default(),
+        }
+    }
+}

crates/gpui2/src/layout_context.rs 🔗

@@ -0,0 +1,64 @@
+use crate::{element::LayoutId, style::Style};
+use anyhow::{anyhow, Result};
+use derive_more::{Deref, DerefMut};
+use gpui::{geometry::Size, MeasureParams, RenderContext, ViewContext};
+pub use gpui::{taffy::tree::NodeId, LayoutContext as LegacyLayoutContext};
+
+#[derive(Deref, DerefMut)]
+pub struct LayoutContext<'a, 'b, 'c, 'd, V> {
+    #[deref]
+    #[deref_mut]
+    pub(crate) legacy_cx: &'d mut LegacyLayoutContext<'a, 'b, 'c, V>,
+}
+
+impl<'a, 'b, V> RenderContext<'a, 'b, V> for LayoutContext<'a, 'b, '_, '_, 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()
+    }
+
+    fn as_view_context(&mut self) -> &mut ViewContext<'a, 'b, V> {
+        &mut self.view_context
+    }
+}
+
+impl<'a, 'b, 'c, 'd, V: 'static> LayoutContext<'a, 'b, 'c, 'd, V> {
+    pub fn new(legacy_cx: &'d mut LegacyLayoutContext<'a, 'b, 'c, V>) -> Self {
+        Self { legacy_cx }
+    }
+
+    pub fn add_layout_node(
+        &mut self,
+        style: Style,
+        children: impl IntoIterator<Item = NodeId>,
+    ) -> Result<LayoutId> {
+        let rem_size = self.rem_pixels();
+        let id = self
+            .legacy_cx
+            .layout_engine()
+            .ok_or_else(|| anyhow!("no layout engine"))?
+            .add_node(style.to_taffy(rem_size), children)?;
+
+        Ok(id)
+    }
+
+    pub fn add_measured_layout_node<F>(&mut self, style: Style, measure: F) -> Result<LayoutId>
+    where
+        F: Fn(MeasureParams) -> Size<f32> + Sync + Send + 'static,
+    {
+        let rem_size = self.rem_pixels();
+        let layout_id = self
+            .layout_engine()
+            .ok_or_else(|| anyhow!("no layout engine"))?
+            .add_measured_node(style.to_taffy(rem_size), measure)?;
+
+        Ok(layout_id)
+    }
+}

crates/gpui2/src/lib.rs 🔗

@@ -1,14 +0,0 @@
-pub fn add(left: usize, right: usize) -> usize {
-    left + right
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn it_works() {
-        let result = add(2, 2);
-        assert_eq!(result, 4);
-    }
-}

crates/gpui2/src/paint_context.rs 🔗

@@ -0,0 +1,54 @@
+use anyhow::{anyhow, Result};
+use derive_more::{Deref, DerefMut};
+pub use gpui::taffy::tree::NodeId;
+use gpui::{
+    scene::EventHandler, EventContext, Layout, LayoutId, PaintContext as LegacyPaintContext,
+};
+use std::{any::TypeId, rc::Rc};
+
+#[derive(Deref, DerefMut)]
+pub struct PaintContext<'a, 'b, 'c, 'd, V> {
+    #[deref]
+    #[deref_mut]
+    pub(crate) legacy_cx: &'d mut LegacyPaintContext<'a, 'b, 'c, V>,
+    pub(crate) scene: &'d mut gpui::SceneBuilder,
+}
+
+impl<'a, 'b, 'c, 'd, V: 'static> PaintContext<'a, 'b, 'c, 'd, V> {
+    pub fn new(
+        legacy_cx: &'d mut LegacyPaintContext<'a, 'b, 'c, V>,
+        scene: &'d mut gpui::SceneBuilder,
+    ) -> Self {
+        Self { legacy_cx, scene }
+    }
+
+    pub fn on_event<E: 'static>(
+        &mut self,
+        order: u32,
+        handler: impl Fn(&mut V, &E, &mut EventContext<V>) + 'static,
+    ) {
+        let view = self.weak_handle();
+
+        self.scene.event_handlers.push(EventHandler {
+            order,
+            handler: Rc::new(move |event, window_cx| {
+                if let Some(view) = view.upgrade(window_cx) {
+                    view.update(window_cx, |view, view_cx| {
+                        let mut event_cx = EventContext::new(view_cx);
+                        handler(view, event.downcast_ref().unwrap(), &mut event_cx);
+                        event_cx.bubble
+                    })
+                } else {
+                    true
+                }
+            }),
+            event_type: TypeId::of::<E>(),
+        })
+    }
+
+    pub(crate) fn computed_layout(&mut self, layout_id: LayoutId) -> Result<Layout> {
+        self.layout_engine()
+            .ok_or_else(|| anyhow!("no layout engine present"))?
+            .computed_layout(layout_id)
+    }
+}

crates/gpui2/src/playground.rs 🔗

@@ -0,0 +1,99 @@
+#![allow(dead_code, unused_variables)]
+use crate::element::Element;
+use gpui::{
+    geometry::{rect::RectF, vector::vec2f},
+    platform::WindowOptions,
+    serde_json, ViewContext,
+};
+use log::LevelFilter;
+use settings::{default_settings, SettingsStore};
+use simplelog::SimpleLogger;
+use theme::ThemeSettings;
+use themes::Theme;
+use view::view;
+use workspace::workspace;
+
+mod adapter;
+mod color;
+mod components;
+mod div;
+mod element;
+mod hoverable;
+mod interactive;
+mod layout_context;
+mod paint_context;
+mod pressable;
+mod style;
+mod text;
+mod themes;
+mod view;
+mod workspace;
+
+fn main() {
+    SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
+
+    gpui::App::new(()).unwrap().run(|cx| {
+        let mut store = SettingsStore::default();
+        store
+            .set_default_settings(default_settings().as_ref(), cx)
+            .unwrap();
+        cx.set_global(store);
+        theme::init(Assets, cx);
+
+        cx.add_window(
+            WindowOptions {
+                bounds: gpui::platform::WindowBounds::Fixed(RectF::new(
+                    vec2f(0., 0.),
+                    vec2f(400., 300.),
+                )),
+                center: true,
+                ..Default::default()
+            },
+            |_| view(|cx| playground(cx)),
+        );
+        cx.platform().activate(true);
+    });
+}
+
+fn playground<V: 'static>(cx: &mut ViewContext<V>) -> impl Element<V> {
+    workspace().themed(current_theme(cx))
+}
+
+// Nathan: During the transition, we will include the base theme on the legacy Theme struct.
+fn current_theme<V: 'static>(cx: &mut ViewContext<V>) -> Theme {
+    settings::get::<ThemeSettings>(cx)
+        .theme
+        .deserialized_base_theme
+        .lock()
+        .get_or_insert_with(|| {
+            let theme: Theme =
+                serde_json::from_value(settings::get::<ThemeSettings>(cx).theme.base_theme.clone())
+                    .unwrap();
+            Box::new(theme)
+        })
+        .downcast_ref::<Theme>()
+        .unwrap()
+        .clone()
+}
+
+use anyhow::{anyhow, Result};
+use gpui::AssetSource;
+use rust_embed::RustEmbed;
+
+#[derive(RustEmbed)]
+#[folder = "../../../assets"]
+#[include = "themes/**/*"]
+#[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()
+    }
+}

crates/gpui2/src/pressable.rs 🔗

@@ -0,0 +1,107 @@
+use crate::{
+    element::{AnyElement, Element, IntoElement, Layout, ParentElement},
+    interactive::{InteractionHandlers, Interactive},
+    layout_context::LayoutContext,
+    paint_context::PaintContext,
+    style::{Style, StyleHelpers, Styleable},
+};
+use anyhow::Result;
+use gpui::{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 LayoutContext<V>,
+    ) -> Result<(LayoutId, Self::PaintState)>
+    where
+        Self: Sized,
+    {
+        self.child.layout(view, cx)
+    }
+
+    fn paint(
+        &mut self,
+        view: &mut V,
+        layout: &Layout,
+        paint_state: &mut Self::PaintState,
+        cx: &mut PaintContext<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;
+        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, 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/gpui2/src/style.rs 🔗

@@ -0,0 +1,379 @@
+use crate::{
+    color::Hsla,
+    hoverable::{hoverable, Hoverable},
+    paint_context::PaintContext,
+    pressable::{pressable, Pressable},
+};
+pub use gpui::taffy::style::{
+    AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
+    Overflow, Position,
+};
+use gpui::{
+    fonts::TextStyleRefinement,
+    geometry::{
+        rect::RectF, relative, AbsoluteLength, DefiniteLength, Edges, EdgesRefinement, Length,
+        Point, PointRefinement, Size, SizeRefinement,
+    },
+    taffy,
+};
+use gpui2_macros::styleable_helpers;
+use refineable::{Refineable, RefinementCascade};
+
+#[derive(Clone, Refineable)]
+pub struct Style {
+    /// What layout strategy should be used?
+    pub display: Display,
+
+    // Overflow properties
+    /// How children overflowing their container should affect layout
+    #[refineable]
+    pub overflow: Point<Overflow>,
+    /// How much space (in points) should be reserved for the scrollbars of `Overflow::Scroll` and `Overflow::Auto` nodes.
+    pub scrollbar_width: f32,
+
+    // Position properties
+    /// What should the `position` value of this struct use as a base offset?
+    pub position: Position,
+    /// How should the position of this element be tweaked relative to the layout defined?
+    #[refineable]
+    pub inset: Edges<Length>,
+
+    // Size properies
+    /// Sets the initial size of the item
+    #[refineable]
+    pub size: Size<Length>,
+    /// Controls the minimum size of the item
+    #[refineable]
+    pub min_size: Size<Length>,
+    /// Controls the maximum size of the item
+    #[refineable]
+    pub max_size: Size<Length>,
+    /// Sets the preferred aspect ratio for the item. The ratio is calculated as width divided by height.
+    pub aspect_ratio: Option<f32>,
+
+    // Spacing Properties
+    /// How large should the margin be on each side?
+    #[refineable]
+    pub margin: Edges<Length>,
+    /// How large should the padding be on each side?
+    #[refineable]
+    pub padding: Edges<DefiniteLength>,
+    /// How large should the border be on each side?
+    #[refineable]
+    pub border: Edges<DefiniteLength>,
+
+    // Alignment properties
+    /// How this node's children aligned in the cross/block axis?
+    pub align_items: Option<AlignItems>,
+    /// How this node should be aligned in the cross/block axis. Falls back to the parents [`AlignItems`] if not set
+    pub align_self: Option<AlignSelf>,
+    /// How should content contained within this item be aligned in the cross/block axis
+    pub align_content: Option<AlignContent>,
+    /// How should contained within this item be aligned in the main/inline axis
+    pub justify_content: Option<JustifyContent>,
+    /// How large should the gaps between items in a flex container be?
+    #[refineable]
+    pub gap: Size<DefiniteLength>,
+
+    // Flexbox properies
+    /// Which direction does the main axis flow in?
+    pub flex_direction: FlexDirection,
+    /// Should elements wrap, or stay in a single line?
+    pub flex_wrap: FlexWrap,
+    /// Sets the initial main axis size of the item
+    pub flex_basis: Length,
+    /// The relative rate at which this item grows when it is expanding to fill space, 0.0 is the default value, and this value must be positive.
+    pub flex_grow: f32,
+    /// The relative rate at which this item shrinks when it is contracting to fit into space, 1.0 is the default value, and this value must be positive.
+    pub flex_shrink: f32,
+
+    /// The fill color of this element
+    pub fill: Option<Fill>,
+    /// The radius of the corners of this element
+    #[refineable]
+    pub corner_radii: CornerRadii,
+    /// The color of text within this element. Cascades to children unless overridden.
+    pub text_color: Option<Hsla>,
+}
+
+impl Style {
+    pub fn to_taffy(&self, rem_size: f32) -> taffy::style::Style {
+        taffy::style::Style {
+            display: self.display,
+            overflow: self.overflow.clone().into(),
+            scrollbar_width: self.scrollbar_width,
+            position: self.position,
+            inset: self.inset.to_taffy(rem_size),
+            size: self.size.to_taffy(rem_size),
+            min_size: self.min_size.to_taffy(rem_size),
+            max_size: self.max_size.to_taffy(rem_size),
+            aspect_ratio: self.aspect_ratio,
+            margin: self.margin.to_taffy(rem_size),
+            padding: self.padding.to_taffy(rem_size),
+            border: self.border.to_taffy(rem_size),
+            align_items: self.align_items,
+            align_self: self.align_self,
+            align_content: self.align_content,
+            justify_content: self.justify_content,
+            gap: self.gap.to_taffy(rem_size),
+            flex_direction: self.flex_direction,
+            flex_wrap: self.flex_wrap,
+            flex_basis: self.flex_basis.to_taffy(rem_size).into(),
+            flex_grow: self.flex_grow,
+            flex_shrink: self.flex_shrink,
+            ..Default::default() // Ignore grid properties for now
+        }
+    }
+
+    /// Paints the background of an element styled with this style.
+    /// Return the bounds in which to paint the content.
+    pub fn paint_background<V: 'static>(&self, bounds: RectF, cx: &mut PaintContext<V>) {
+        let rem_size = cx.rem_pixels();
+        if let Some(color) = self.fill.as_ref().and_then(Fill::color) {
+            cx.scene.push_quad(gpui::Quad {
+                bounds,
+                background: Some(color.into()),
+                corner_radii: self.corner_radii.to_gpui(rem_size),
+                border: Default::default(),
+            });
+        }
+    }
+
+    pub fn text_style(&self) -> Option<TextStyleRefinement> {
+        if let Some(color) = self.text_color {
+            Some(TextStyleRefinement {
+                color: Some(color.into()),
+                ..Default::default()
+            })
+        } else {
+            None
+        }
+    }
+}
+
+impl Default for Style {
+    fn default() -> Self {
+        Style {
+            display: Display::Block,
+            overflow: Point {
+                x: Overflow::Visible,
+                y: Overflow::Visible,
+            },
+            scrollbar_width: 0.0,
+            position: Position::Relative,
+            inset: Edges::auto(),
+            margin: Edges::<Length>::zero(),
+            padding: Edges::<DefiniteLength>::zero(),
+            border: Edges::<DefiniteLength>::zero(),
+            size: Size::auto(),
+            min_size: Size::auto(),
+            max_size: Size::auto(),
+            aspect_ratio: None,
+            gap: Size::zero(),
+            // Aligment
+            align_items: None,
+            align_self: None,
+            align_content: None,
+            justify_content: None,
+            // Flexbox
+            flex_direction: FlexDirection::Row,
+            flex_wrap: FlexWrap::NoWrap,
+            flex_grow: 0.0,
+            flex_shrink: 1.0,
+            flex_basis: Length::Auto,
+            fill: None,
+            text_color: None,
+            corner_radii: CornerRadii::default(),
+        }
+    }
+}
+
+impl StyleRefinement {
+    pub fn text_style(&self) -> Option<TextStyleRefinement> {
+        self.text_color.map(|color| TextStyleRefinement {
+            color: Some(color.into()),
+            ..Default::default()
+        })
+    }
+}
+
+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, Debug)]
+pub enum Fill {
+    Color(Hsla),
+}
+
+impl Fill {
+    pub fn color(&self) -> Option<Hsla> {
+        match self {
+            Fill::Color(color) => Some(*color),
+        }
+    }
+}
+
+impl Default for Fill {
+    fn default() -> Self {
+        Self::Color(Hsla::default())
+    }
+}
+
+impl From<Hsla> for Fill {
+    fn from(color: Hsla) -> Self {
+        Self::Color(color)
+    }
+}
+
+#[derive(Clone, Refineable, Default)]
+pub struct CornerRadii {
+    top_left: AbsoluteLength,
+    top_right: AbsoluteLength,
+    bottom_left: AbsoluteLength,
+    bottom_right: AbsoluteLength,
+}
+
+impl CornerRadii {
+    pub fn to_gpui(&self, rem_size: f32) -> gpui::scene::CornerRadii {
+        gpui::scene::CornerRadii {
+            top_left: self.top_left.to_pixels(rem_size),
+            top_right: self.top_right.to_pixels(rem_size),
+            bottom_left: self.bottom_left.to_pixels(rem_size),
+            bottom_right: self.bottom_right.to_pixels(rem_size),
+        }
+    }
+}
+
+pub trait Styleable {
+    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 hovered(self) -> Hoverable<Self>
+    where
+        Self: Sized,
+    {
+        hoverable(self)
+    }
+
+    fn pressed(self) -> Pressable<Self>
+    where
+        Self: Sized,
+    {
+        pressable(self)
+    }
+}
+
+// Helpers methods that take and return mut self. This includes tailwind style methods for standard sizes etc.
+//
+// Example:
+// // Sets the padding to 0.5rem, just like class="p-2" in Tailwind.
+// fn p_2(mut self) -> Self where Self: Sized;
+pub trait StyleHelpers: Styleable<Style = Style> {
+    styleable_helpers!();
+
+    fn h(mut self, height: Length) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().size.height = Some(height);
+        self
+    }
+
+    fn full(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().size.width = Some(relative(1.));
+        self.declared_style().size.height = Some(relative(1.));
+        self
+    }
+
+    fn relative(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().position = Some(Position::Relative);
+        self
+    }
+
+    fn absolute(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().position = Some(Position::Absolute);
+        self
+    }
+
+    fn block(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().display = Some(Display::Block);
+        self
+    }
+
+    fn flex(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().display = Some(Display::Flex);
+        self
+    }
+
+    fn flex_col(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().flex_direction = Some(FlexDirection::Column);
+        self
+    }
+
+    fn flex_row(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().flex_direction = Some(FlexDirection::Row);
+        self
+    }
+
+    fn flex_grow(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().flex_grow = Some(1.);
+        self
+    }
+
+    fn fill<F>(mut self, fill: F) -> Self
+    where
+        F: Into<Fill>,
+        Self: Sized,
+    {
+        self.declared_style().fill = Some(fill.into());
+        self
+    }
+
+    fn text_color<C>(mut self, color: C) -> Self
+    where
+        C: Into<Hsla>,
+        Self: Sized,
+    {
+        self.declared_style().text_color = Some(color.into());
+        self
+    }
+}

crates/gpui2/src/text.rs 🔗

@@ -0,0 +1,148 @@
+use crate::{
+    element::{Element, IntoElement, Layout},
+    layout_context::LayoutContext,
+    paint_context::PaintContext,
+};
+use anyhow::Result;
+use gpui::{geometry::Size, text_layout::LineLayout, LayoutId, RenderContext};
+use parking_lot::Mutex;
+use std::sync::Arc;
+
+impl<V: 'static, S: Into<ArcCow<'static, str>>> IntoElement<V> for S {
+    type Element = Text;
+
+    fn into_element(self) -> Self::Element {
+        Text { text: self.into() }
+    }
+}
+
+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 LayoutContext<V>,
+    ) -> Result<(LayoutId, Self::PaintState)> {
+        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 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,
+        layout: &Layout,
+        paint_state: &mut Self::PaintState,
+        cx: &mut PaintContext<V>,
+    ) {
+        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())]);
+
+        let origin = layout.bounds.origin();
+        // TODO: We haven't added visible bounds to the new element system yet, so this is a placeholder.
+        let visible_bounds = layout.bounds;
+        line.paint(cx.scene, origin, visible_bounds, line_height, cx.legacy_cx);
+    }
+}
+
+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/gpui2/src/themes.rs 🔗

@@ -0,0 +1,175 @@
+use crate::{
+    color::Hsla,
+    element::{Element, PaintContext},
+    layout_context::LayoutContext,
+};
+use gpui::WindowContext;
+use serde::{de::Visitor, Deserialize, Deserializer};
+use std::{collections::HashMap, fmt, marker::PhantomData};
+
+#[derive(Deserialize, Clone, Default, Debug)]
+pub struct Theme {
+    pub name: String,
+    pub is_light: bool,
+    pub lowest: Layer,
+    pub middle: Layer,
+    pub highest: Layer,
+    pub popover_shadow: Shadow,
+    pub modal_shadow: Shadow,
+    #[serde(deserialize_with = "deserialize_player_colors")]
+    pub players: Vec<PlayerColors>,
+    #[serde(deserialize_with = "deserialize_syntax_colors")]
+    pub syntax: HashMap<String, Hsla>,
+}
+
+#[derive(Deserialize, Clone, Default, Debug)]
+pub struct Layer {
+    pub base: StyleSet,
+    pub variant: StyleSet,
+    pub on: StyleSet,
+    pub accent: StyleSet,
+    pub positive: StyleSet,
+    pub warning: StyleSet,
+    pub negative: StyleSet,
+}
+
+#[derive(Deserialize, Clone, Default, Debug)]
+pub struct StyleSet {
+    #[serde(rename = "default")]
+    pub default: ContainerColors,
+    pub hovered: ContainerColors,
+    pub pressed: ContainerColors,
+    pub active: ContainerColors,
+    pub disabled: ContainerColors,
+    pub inverted: ContainerColors,
+}
+
+#[derive(Deserialize, Clone, Default, Debug)]
+pub struct ContainerColors {
+    pub background: Hsla,
+    pub foreground: Hsla,
+    pub border: Hsla,
+}
+
+#[derive(Deserialize, Clone, Default, Debug)]
+pub struct PlayerColors {
+    pub selection: Hsla,
+    pub cursor: Hsla,
+}
+
+#[derive(Deserialize, Clone, Default, Debug)]
+pub struct Shadow {
+    pub blur: u8,
+    pub color: Hsla,
+    pub offset: Vec<u8>,
+}
+
+pub fn theme<'a>(cx: &'a WindowContext) -> &'a Theme {
+    cx.theme::<Theme>()
+}
+
+fn deserialize_player_colors<'de, D>(deserializer: D) -> Result<Vec<PlayerColors>, D::Error>
+where
+    D: Deserializer<'de>,
+{
+    struct PlayerArrayVisitor;
+
+    impl<'de> Visitor<'de> for PlayerArrayVisitor {
+        type Value = Vec<PlayerColors>;
+
+        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+            formatter.write_str("an object with integer keys")
+        }
+
+        fn visit_map<A: serde::de::MapAccess<'de>>(
+            self,
+            mut map: A,
+        ) -> Result<Self::Value, A::Error> {
+            let mut players = Vec::with_capacity(8);
+            while let Some((key, value)) = map.next_entry::<usize, PlayerColors>()? {
+                if key < 8 {
+                    players.push(value);
+                } else {
+                    return Err(serde::de::Error::invalid_value(
+                        serde::de::Unexpected::Unsigned(key as u64),
+                        &"a key in range 0..7",
+                    ));
+                }
+            }
+            Ok(players)
+        }
+    }
+
+    deserializer.deserialize_map(PlayerArrayVisitor)
+}
+
+fn deserialize_syntax_colors<'de, D>(deserializer: D) -> Result<HashMap<String, Hsla>, D::Error>
+where
+    D: serde::Deserializer<'de>,
+{
+    #[derive(Deserialize)]
+    struct ColorWrapper {
+        color: Hsla,
+    }
+
+    struct SyntaxVisitor;
+
+    impl<'de> Visitor<'de> for SyntaxVisitor {
+        type Value = HashMap<String, Hsla>;
+
+        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+            formatter.write_str("a map with keys and objects with a single color field as values")
+        }
+
+        fn visit_map<M>(self, mut map: M) -> Result<HashMap<String, Hsla>, M::Error>
+        where
+            M: serde::de::MapAccess<'de>,
+        {
+            let mut result = HashMap::new();
+            while let Some(key) = map.next_key()? {
+                let wrapper: ColorWrapper = map.next_value()?; // Deserialize values as Hsla
+                result.insert(key, wrapper.color);
+            }
+            Ok(result)
+        }
+    }
+    deserializer.deserialize_map(SyntaxVisitor)
+}
+
+pub struct Themed<V: 'static, E> {
+    pub(crate) theme: Theme,
+    pub(crate) child: E,
+    pub(crate) view_type: PhantomData<V>,
+}
+
+impl<V: 'static, E: Element<V>> Element<V> for Themed<V, E> {
+    type PaintState = E::PaintState;
+
+    fn layout(
+        &mut self,
+        view: &mut V,
+        cx: &mut LayoutContext<V>,
+    ) -> anyhow::Result<(gpui::LayoutId, Self::PaintState)>
+    where
+        Self: Sized,
+    {
+        cx.push_theme(self.theme.clone());
+        let result = self.child.layout(view, cx);
+        cx.pop_theme();
+        result
+    }
+
+    fn paint(
+        &mut self,
+        view: &mut V,
+        layout: &gpui::Layout,
+        state: &mut Self::PaintState,
+        cx: &mut PaintContext<V>,
+    ) where
+        Self: Sized,
+    {
+        cx.push_theme(self.theme.clone());
+        self.child.paint(view, layout, state, cx);
+        cx.pop_theme();
+    }
+}

crates/gpui2/src/themes/rose_pine.rs 🔗

@@ -0,0 +1,133 @@
+use std::ops::Range;
+
+use crate::{
+    color::{hsla, rgb, Hsla},
+    ThemeColors,
+};
+
+pub struct RosePineThemes {
+    pub default: RosePinePalette,
+    pub dawn: RosePinePalette,
+    pub moon: RosePinePalette,
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct RosePinePalette {
+    pub base: Hsla,
+    pub surface: Hsla,
+    pub overlay: Hsla,
+    pub muted: Hsla,
+    pub subtle: Hsla,
+    pub text: Hsla,
+    pub love: Hsla,
+    pub gold: Hsla,
+    pub rose: Hsla,
+    pub pine: Hsla,
+    pub foam: Hsla,
+    pub iris: Hsla,
+    pub highlight_low: Hsla,
+    pub highlight_med: Hsla,
+    pub highlight_high: Hsla,
+}
+
+impl RosePinePalette {
+    pub fn default() -> RosePinePalette {
+        RosePinePalette {
+            base: rgb(0x191724),
+            surface: rgb(0x1f1d2e),
+            overlay: rgb(0x26233a),
+            muted: rgb(0x6e6a86),
+            subtle: rgb(0x908caa),
+            text: rgb(0xe0def4),
+            love: rgb(0xeb6f92),
+            gold: rgb(0xf6c177),
+            rose: rgb(0xebbcba),
+            pine: rgb(0x31748f),
+            foam: rgb(0x9ccfd8),
+            iris: rgb(0xc4a7e7),
+            highlight_low: rgb(0x21202e),
+            highlight_med: rgb(0x403d52),
+            highlight_high: rgb(0x524f67),
+        }
+    }
+
+    pub fn moon() -> RosePinePalette {
+        RosePinePalette {
+            base: rgb(0x232136),
+            surface: rgb(0x2a273f),
+            overlay: rgb(0x393552),
+            muted: rgb(0x6e6a86),
+            subtle: rgb(0x908caa),
+            text: rgb(0xe0def4),
+            love: rgb(0xeb6f92),
+            gold: rgb(0xf6c177),
+            rose: rgb(0xea9a97),
+            pine: rgb(0x3e8fb0),
+            foam: rgb(0x9ccfd8),
+            iris: rgb(0xc4a7e7),
+            highlight_low: rgb(0x2a283e),
+            highlight_med: rgb(0x44415a),
+            highlight_high: rgb(0x56526e),
+        }
+    }
+
+    pub fn dawn() -> RosePinePalette {
+        RosePinePalette {
+            base: rgb(0xfaf4ed),
+            surface: rgb(0xfffaf3),
+            overlay: rgb(0xf2e9e1),
+            muted: rgb(0x9893a5),
+            subtle: rgb(0x797593),
+            text: rgb(0x575279),
+            love: rgb(0xb4637a),
+            gold: rgb(0xea9d34),
+            rose: rgb(0xd7827e),
+            pine: rgb(0x286983),
+            foam: rgb(0x56949f),
+            iris: rgb(0x907aa9),
+            highlight_low: rgb(0xf4ede8),
+            highlight_med: rgb(0xdfdad9),
+            highlight_high: rgb(0xcecacd),
+        }
+    }
+}
+
+pub fn default() -> ThemeColors {
+    theme_colors(&RosePinePalette::default())
+}
+
+pub fn moon() -> ThemeColors {
+    theme_colors(&RosePinePalette::moon())
+}
+
+pub fn dawn() -> ThemeColors {
+    theme_colors(&RosePinePalette::dawn())
+}
+
+fn theme_colors(p: &RosePinePalette) -> ThemeColors {
+    ThemeColors {
+        base: scale_sl(p.base, (0.8, 0.8), (1.2, 1.2)),
+        surface: scale_sl(p.surface, (0.8, 0.8), (1.2, 1.2)),
+        overlay: scale_sl(p.overlay, (0.8, 0.8), (1.2, 1.2)),
+        muted: scale_sl(p.muted, (0.8, 0.8), (1.2, 1.2)),
+        subtle: scale_sl(p.subtle, (0.8, 0.8), (1.2, 1.2)),
+        text: scale_sl(p.text, (0.8, 0.8), (1.2, 1.2)),
+        highlight_low: scale_sl(p.highlight_low, (0.8, 0.8), (1.2, 1.2)),
+        highlight_med: scale_sl(p.highlight_med, (0.8, 0.8), (1.2, 1.2)),
+        highlight_high: scale_sl(p.highlight_high, (0.8, 0.8), (1.2, 1.2)),
+        success: scale_sl(p.foam, (0.8, 0.8), (1.2, 1.2)),
+        warning: scale_sl(p.gold, (0.8, 0.8), (1.2, 1.2)),
+        error: scale_sl(p.love, (0.8, 0.8), (1.2, 1.2)),
+        inserted: scale_sl(p.foam, (0.8, 0.8), (1.2, 1.2)),
+        deleted: scale_sl(p.love, (0.8, 0.8), (1.2, 1.2)),
+        modified: scale_sl(p.rose, (0.8, 0.8), (1.2, 1.2)),
+    }
+}
+
+/// Produces a range by multiplying the saturation and lightness of the base color by the given
+/// start and end factors.
+fn scale_sl(base: Hsla, (start_s, start_l): (f32, f32), (end_s, end_l): (f32, f32)) -> Range<Hsla> {
+    let start = hsla(base.h, base.s * start_s, base.l * start_l, base.a);
+    let end = hsla(base.h, base.s * end_s, base.l * end_l, base.a);
+    Range { start, end }
+}

crates/gpui2/src/view.rs 🔗

@@ -0,0 +1,26 @@
+use crate::{
+    adapter::AdapterElement,
+    element::{AnyElement, Element},
+};
+use gpui::ViewContext;
+
+pub fn view<F, E>(mut render: F) -> ViewFn
+where
+    F: 'static + FnMut(&mut ViewContext<ViewFn>) -> E,
+    E: Element<ViewFn>,
+{
+    ViewFn(Box::new(move |cx| (render)(cx).into_any()))
+}
+
+pub struct ViewFn(Box<dyn FnMut(&mut ViewContext<ViewFn>) -> AnyElement<ViewFn>>);
+
+impl gpui::Entity for ViewFn {
+    type Event = ();
+}
+
+impl gpui::View for ViewFn {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> gpui::AnyElement<Self> {
+        use gpui::Element as _;
+        AdapterElement((self.0)(cx)).into_any()
+    }
+}

crates/gpui2_macros/src/derive_element.rs 🔗

@@ -59,16 +59,16 @@ pub fn derive_element(input: TokenStream) -> TokenStream {
     );
 
     let gen = quote! {
-        impl #impl_generics playground::element::Element<#view_type_name> for #type_name #type_generics
+        impl #impl_generics gpui2::element::Element<#view_type_name> for #type_name #type_generics
         #where_clause
         {
-            type PaintState = playground::element::AnyElement<#view_type_name #lifetimes>;
+            type PaintState = gpui2::element::AnyElement<#view_type_name #lifetimes>;
 
             fn layout(
                 &mut self,
                 view: &mut V,
-                cx: &mut playground::element::LayoutContext<V>,
-            ) -> anyhow::Result<(playground::element::LayoutId, Self::PaintState)> {
+                cx: &mut gpui2::element::LayoutContext<V>,
+            ) -> anyhow::Result<(gpui2::element::LayoutId, Self::PaintState)> {
                 let mut rendered_element = self.render(view, cx).into_element().into_any();
                 let layout_id = rendered_element.layout(view, cx)?;
                 Ok((layout_id, rendered_element))
@@ -77,9 +77,9 @@ pub fn derive_element(input: TokenStream) -> TokenStream {
             fn paint(
                 &mut self,
                 view: &mut V,
-                layout: &playground::element::Layout,
+                layout: &gpui2::element::Layout,
                 rendered_element: &mut Self::PaintState,
-                cx: &mut playground::element::PaintContext<V>,
+                cx: &mut gpui2::element::PaintContext<V>,
             ) {
                 rendered_element.paint(view, layout.bounds.origin(), cx);
             }

crates/gpui2_macros/src/derive_into_element.rs 🔗

@@ -56,7 +56,7 @@ pub fn impl_into_element(
     where_clause: &Option<&WhereClause>,
 ) -> proc_macro2::TokenStream {
     quote! {
-        impl #impl_generics playground::element::IntoElement<#view_type_name> for #type_name #type_generics
+        impl #impl_generics gpui2::element::IntoElement<#view_type_name> for #type_name #type_generics
         #where_clause
         {
             type Element = Self;