Compiling checkpoint

Nathan Sobo created

Change summary

crates/gpui/playground/src/adapter.rs                  |  64 +++++
crates/gpui/playground/src/editor_layout_demo.rs       | 151 ------------
crates/gpui/playground/src/element.rs                  |  47 ++-
crates/gpui/playground/src/frame.rs                    |  42 ++
crates/gpui/playground/src/playground.rs               |  86 +++++-
crates/gpui/playground/src/style.rs                    | 100 ++++---
crates/gpui/playground/src/themes.rs                   |  24 -
crates/gpui/playground/src/tokens.rs                   |  10 
crates/gpui/playground_macros/src/playground_macros.rs | 124 ++++----
crates/gpui/src/app/window.rs                          |  68 +++++
crates/gpui/src/gpui.rs                                |   4 
11 files changed, 375 insertions(+), 345 deletions(-)

Detailed changes

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

@@ -0,0 +1,64 @@
+use util::ResultExt;
+
+use crate::element::AnyElement;
+
+struct Adapter<V>(AnyElement<V>);
+
+impl<V: 'static> gpui::Element<V> for Adapter<V> {
+    type LayoutState = ();
+    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();
+        if let Some(node) = self.0.layout(view, cx).log_err() {
+            cx.layout_engine()
+                .unwrap()
+                .compute_layout(node, constraint.max)
+                .log_err();
+        }
+        cx.pop_layout_engine();
+
+        (constraint.max, ())
+    }
+
+    fn paint(
+        &mut self,
+        scene: &mut gpui::SceneBuilder,
+        bounds: gpui::geometry::rect::RectF,
+        visible_bounds: gpui::geometry::rect::RectF,
+        layout: &mut Self::LayoutState,
+        view: &mut V,
+        cx: &mut gpui::PaintContext<V>,
+    ) -> Self::PaintState {
+        todo!()
+    }
+
+    fn rect_for_text_range(
+        &self,
+        range_utf16: std::ops::Range<usize>,
+        bounds: gpui::geometry::rect::RectF,
+        visible_bounds: gpui::geometry::rect::RectF,
+        layout: &Self::LayoutState,
+        paint: &Self::PaintState,
+        view: &V,
+        cx: &gpui::ViewContext<V>,
+    ) -> Option<gpui::geometry::rect::RectF> {
+        todo!()
+    }
+
+    fn debug(
+        &self,
+        bounds: gpui::geometry::rect::RectF,
+        layout: &Self::LayoutState,
+        paint: &Self::PaintState,
+        view: &V,
+        cx: &gpui::ViewContext<V>,
+    ) -> gpui::serde_json::Value {
+        todo!()
+    }
+}

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

@@ -1,151 +0,0 @@
-use gpui::{AnyElement, Element, LayoutContext, View, ViewContext};
-
-#[derive(Element, Clone, Default)]
-pub struct Playground<V>(PhantomData<V>);
-
-// example layout design here: https://www.figma.com/file/5QLTmxjO0xQpDD3CD4hR6T/Untitled?type=design&node-id=0%3A1&mode=design&t=SoJieVVIvDDDKagv-1
-
-impl<V> Playground<V> {
-    pub fn render(&mut self, _: &mut V, _: &mut gpui::ViewContext<V>) -> impl Element<V> {
-        col() // fullscreen container with header and main in it
-            .width(flex(1.))
-            .height(flex(1.))
-            .fill(colors(gray.900))
-            .children([
-                row() // header container
-                    .fill(colors(gray.900))
-                    .width(flex(1.))
-                    .children([
-                        row() // tab bar
-                            .width(flex(1.))
-                            .gap(spacing(2))
-                            .padding(spacing(3))
-                            .overflow_x(scroll())
-                            .chidren([
-                                row() // tab
-                                    .padding_x(spacing(3))
-                                    .padding_y(spacing(2))
-                                    .corner_radius(6.)
-                                    .gap(spacing(3))
-                                    .align(center())
-                                    .fill(colors(gray.800))
-                                    .children([text("Tab title 1"), svg("icon_name")]),
-                                row() // tab
-                                    .padding_x(spacing(3))
-                                    .padding_y(spacing(2))
-                                    .corner_radius(6.)
-                                    .gap(spacing(3))
-                                    .align(center())
-                                    .fill(colors(gray.800))
-                                    .children([text("Tab title 2"), svg("icon_name")]),
-                                row() // tab
-                                    .padding_x(spacing(3))
-                                    .padding_y(spacing(2))
-                                    .corner_radius(6.)
-                                    .gap(spacing(3))
-                                    .align(center())
-                                    .fill(colors(gray.800))
-                                    .children([text("Tab title 3"), svg("icon_name")]),
-                            ]),
-                        row() // tab bar actions
-                            .border_left(colors(gray.700))
-                            .gap(spacing(2))
-                            .padding(spacing(3))
-                            .chidren([
-                                row()
-                                    .width(spacing(8))
-                                    .height(spacing(8))
-                                    .corner_radius(6.)
-                                    .justify(center())
-                                    .align(center())
-                                    .fill(colors(gray.800))
-                                    .child(svg(icon_name)),
-                                row()
-                                    .width(spacing(8))
-                                    .height(spacing(8))
-                                    .corner_radius(6.)
-                                    .justify(center())
-                                    .align(center())
-                                    .fill(colors(gray.800))
-                                    .child(svg(icon_name)),
-                                row()
-                                    .width(spacing(8))
-                                    .height(spacing(8))
-                                    .corner_radius(6.)
-                                    .justify(center())
-                                    .align(center())
-                                    .fill(colors(gray.800))
-                                    .child(svg(icon_name)),
-                            ]),
-                    ]),
-                row() // main container
-                    .width(flex(1.))
-                    .height(flex(1.))
-                    .children([
-                        col() // left sidebar
-                            .fill(colors(gray.800))
-                            .border_right(colors(gray.700))
-                            .height(flex(1.))
-                            .width(260.)
-                            .children([
-                                col() // containter to hold list items and notification alert box
-                                    .justify(between())
-                                    .padding_x(spacing(6))
-                                    .padding_bottom(3)
-                                    .padding_top(spacing(6))
-                                    .children([
-                                        col().gap(spacing(3)).children([ // sidebar list
-                                            text("Item"),
-                                            text("Item"),
-                                            text("Item"),
-                                            text("Item"),
-                                            text("Item"),
-                                            text("Item"),
-                                            text("Item"),
-                                            text("Item"),
-                                        ]),
-                                        col().align(center()).gap(spacing(1)).children([ // notification alert box
-                                            text("Title text").size("lg"),
-                                            text("Description text goes here")
-                                                .text_color(colors(rose.200)),
-                                        ]),
-                                    ]),
-                                row()
-                                    .padding_x(spacing(3))
-                                    .padding_y(spacing(2))
-                                    .border_top(1., colors(gray.700))
-                                    .align(center())
-                                    .gap(spacing(2))
-                                    .fill(colors(gray.900))
-                                    .children([
-                                        row() // avatar container
-                                            .width(spacing(8))
-                                            .height(spacing(8))
-                                            .corner_radius(spacing(8))
-                                            .justify(center())
-                                            .align(center())
-                                            .child(image(image_url)),
-                                        text("FirstName Lastname"), // user name
-                                    ]),
-                            ]),
-                        col() // primary content container
-                            .align(center())
-                            .justify(center())
-                            .child(
-                                col().justify(center()).gap(spacing(8)).children([ // detail container wrapper for center positioning
-                                    col() // blue rectangle
-                                        .width(rem(30.))
-                                        .height(rem(20.))
-                                        .corner_radius(16.)
-                                        .fill(colors(blue.200)),
-                                    col().gap(spacing(1)).children([ // center content text items
-                                        text("This is a title").size("lg"),
-                                        text("This is a description").text_color(colors(gray.500)),
-                                    ]),
-                                ]),
-                            ),
-                        col(), // right sidebar
-                    ]),
-            ])
-    }
-}

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

@@ -1,12 +1,25 @@
-use crate::style::{Display, Length, Overflow, Position, Style};
-use gpui::{LayoutContext, PaintContext};
+use crate::style::{DefinedLength, Display, Overflow, Position, Style};
+use anyhow::Result;
+use gpui::{Layout, LayoutContext, PaintContext};
 use playground_macros::tailwind_lengths;
-pub use taffy::tree::{Layout, NodeId};
+pub use taffy::tree::NodeId;
 
 pub trait Element<V> {
     fn style_mut(&mut self) -> &mut Style;
-    fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> NodeId;
-    fn paint(&mut self, layout: &Layout, view: &mut V, cx: &mut gpui::PaintContext<V>);
+    fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<NodeId>;
+    fn paint(&mut self, layout: Layout, view: &mut V, cx: &mut gpui::PaintContext<V>)
+        -> Result<()>;
+
+    /// Convert to a dynamically-typed element suitable for layout and paint.
+    fn into_any(self) -> AnyElement<V>
+    where
+        Self: 'static + Sized,
+    {
+        AnyElement {
+            element: Box::new(self) as Box<dyn Element<V>>,
+            layout_node_id: None,
+        }
+    }
 
     // Display ////////////////////
 
@@ -131,7 +144,7 @@ pub trait Element<V> {
     }
 
     #[tailwind_lengths]
-    fn inset(mut self, length: Length) -> Self
+    fn inset(mut self, length: DefinedLength) -> Self
     where
         Self: Sized,
     {
@@ -143,7 +156,7 @@ pub trait Element<V> {
     }
 
     #[tailwind_lengths]
-    fn w(mut self, length: Length) -> Self
+    fn w(mut self, length: DefinedLength) -> Self
     where
         Self: Sized,
     {
@@ -152,7 +165,7 @@ pub trait Element<V> {
     }
 
     #[tailwind_lengths]
-    fn min_w(mut self, length: Length) -> Self
+    fn min_w(mut self, length: DefinedLength) -> Self
     where
         Self: Sized,
     {
@@ -161,7 +174,7 @@ pub trait Element<V> {
     }
 
     #[tailwind_lengths]
-    fn h(mut self, length: Length) -> Self
+    fn h(mut self, length: DefinedLength) -> Self
     where
         Self: Sized,
     {
@@ -176,15 +189,19 @@ pub struct AnyElement<V> {
 }
 
 impl<V> AnyElement<V> {
-    fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> NodeId {
-        let layout_node_id = self.element.layout(view, cx);
+    pub fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<NodeId> {
+        let layout_node_id = self.element.layout(view, cx)?;
         self.layout_node_id = Some(layout_node_id);
-        layout_node_id
+        Ok(layout_node_id)
     }
 
-    fn paint(&mut self, view: &mut V, cx: &mut PaintContext<V>) {
+    pub fn paint(&mut self, view: &mut V, cx: &mut PaintContext<V>) -> Result<()> {
         let layout_node_id = self.layout_node_id.expect("paint called before layout");
-        let layout = cx.layout_engine().layout(layout_node_id).unwrap().clone();
-        self.element.paint(&layout, view, cx);
+        let layout = cx
+            .layout_engine()
+            .unwrap()
+            .computed_layout(layout_node_id)
+            .expect("you can currently only use playground elements within an adapter");
+        self.element.paint(layout, view, cx)
     }
 }

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

@@ -1,34 +1,54 @@
-use crate::{element::Element, style::Style};
+use anyhow::{anyhow, Result};
+use gpui::{Layout, LayoutNodeId};
 
-pub struct Frame {
+use crate::{
+    element::{AnyElement, Element},
+    style::Style,
+};
+
+pub struct Frame<V> {
     style: Style,
-    children: Vec<Frame>,
+    children: Vec<AnyElement<V>>,
+}
+
+pub fn frame<V>() -> Frame<V> {
+    Frame {
+        style: Style::default(),
+        children: Vec::new(),
+    }
 }
 
-impl<V: 'static> Element<V> for Frame {
+impl<V: 'static> Element<V> for Frame<V> {
     fn style_mut(&mut self) -> &mut Style {
         &mut self.style
     }
 
-    fn layout(&mut self, view: &mut V, cx: &mut gpui::LayoutContext<V>) -> taffy::tree::NodeId {
+    fn layout(
+        &mut self,
+        view: &mut V,
+        cx: &mut gpui::LayoutContext<V>,
+    ) -> Result<taffy::tree::NodeId> {
         let child_layout_node_ids = self
             .children
             .iter_mut()
             .map(|child| child.layout(view, cx))
-            .collect::<Vec<_>>();
+            .collect::<Result<Vec<LayoutNodeId>>>()?;
 
         let rem_size = cx.rem_pixels();
         cx.layout_engine()
-            .new_with_children(self.style.to_taffy(rem_size), &child_layout_node_ids)
-            .unwrap()
+            .ok_or_else(|| anyhow!("no layout engine"))?
+            .add_node(self.style.to_taffy(rem_size), child_layout_node_ids)
     }
 
     fn paint(
         &mut self,
-        layout: &taffy::tree::Layout,
+        layout: Layout,
         view: &mut V,
         cx: &mut gpui::PaintContext<V>,
-    ) {
-        todo!()
+    ) -> Result<()> {
+        for child in &mut self.children {
+            child.paint(view, cx)?;
+        }
+        Ok(())
     }
 }

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

@@ -1,52 +1,94 @@
 #![allow(dead_code, unused_variables)]
-
-use gpui::{elements::Empty, Element};
+use element::{AnyElement, Element};
+use frame::frame;
 use log::LevelFilter;
 use simplelog::SimpleLogger;
+use taffy::tree::NodeId;
 
 fn main() {
     SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
 
     gpui::App::new(()).unwrap().run(|cx| {
         cx.platform().activate(true);
+
         // cx.add_window(
-        //     WindowOptions {
-        //         titlebar: Some(TitlebarOptions {
-        //             appears_transparent: true,
-        //             ..Default::default()
-        //         }),
-        //         ..Default::default()
-        //     },
-        //     |_| view(|_| Playground::new()),
+        //     Default::default(),
+        //     // |_| view(|_| Playground::new()),
         // );
     });
 }
 
-use std::marker::PhantomData;
-use themes::ThemeColors;
+use themes::{rose_pine, ThemeColors};
 
+mod adapter;
 mod color;
 mod element;
 mod frame;
 mod style;
 mod themes;
-mod tokens;
 
-#[derive(Element, Clone)]
-pub struct Playground<V: 'static>(PhantomData<V>);
+pub struct Playground<V: 'static>(AnyElement<V>);
 
-impl<V> Playground<V> {
-    pub fn new() -> Self {
-        Self(PhantomData)
+impl<V: 'static> gpui::Element<V> for Playground<V> {
+    type LayoutState = NodeId;
+
+    type PaintState = ();
+
+    fn layout(
+        &mut self,
+        constraint: gpui::SizeConstraint,
+        view: &mut V,
+        cx: &mut gpui::LayoutContext<V>,
+    ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
+        todo!()
+    }
+
+    fn paint(
+        &mut self,
+        scene: &mut gpui::SceneBuilder,
+        bounds: gpui::geometry::rect::RectF,
+        visible_bounds: gpui::geometry::rect::RectF,
+        layout: &mut Self::LayoutState,
+        view: &mut V,
+        cx: &mut gpui::PaintContext<V>,
+    ) -> Self::PaintState {
+        todo!()
     }
 
-    pub fn render(&mut self, _: &mut V, _: &mut gpui::ViewContext<V>) -> impl Element<V> {
-        Empty::new()
-        // workspace(&rose_pine::dawn())
+    fn rect_for_text_range(
+        &self,
+        range_utf16: std::ops::Range<usize>,
+        bounds: gpui::geometry::rect::RectF,
+        visible_bounds: gpui::geometry::rect::RectF,
+        layout: &Self::LayoutState,
+        paint: &Self::PaintState,
+        view: &V,
+        cx: &gpui::ViewContext<V>,
+    ) -> Option<gpui::geometry::rect::RectF> {
+        todo!()
+    }
+
+    fn debug(
+        &self,
+        bounds: gpui::geometry::rect::RectF,
+        layout: &Self::LayoutState,
+        paint: &Self::PaintState,
+        view: &V,
+        cx: &gpui::ViewContext<V>,
+    ) -> gpui::serde_json::Value {
+        todo!()
+    }
+}
+
+impl<V> Playground<V> {
+    pub fn new() -> Self {
+        Self(workspace(&rose_pine::moon()).into_any())
     }
 }
 
-// fn workspace<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
+fn workspace<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
+    frame()
+}
 //     todo!()
 //     // column()
 //     // .size(auto())

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

@@ -19,25 +19,25 @@ pub struct Style {
     /// 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?
-    pub inset: Edges<LengthOrAuto>,
+    pub inset: Edges<Length>,
 
     // Size properies
     /// Sets the initial size of the item
-    pub size: Size<LengthOrAuto>,
+    pub size: Size<Length>,
     /// Controls the minimum size of the item
-    pub min_size: Size<LengthOrAuto>,
+    pub min_size: Size<Length>,
     /// Controls the maximum size of the item
-    pub max_size: Size<LengthOrAuto>,
+    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?
-    pub margin: Edges<LengthOrAuto>,
+    pub margin: Edges<Length>,
     /// How large should the padding be on each side?
-    pub padding: Edges<Length>,
+    pub padding: Edges<DefinedLength>,
     /// How large should the border be on each side?
-    pub border: Edges<Length>,
+    pub border: Edges<DefinedLength>,
 
     // Alignment properties
     /// How this node's children aligned in the cross/block axis?
@@ -49,7 +49,7 @@ pub struct Style {
     /// 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?
-    pub gap: Size<Length>,
+    pub gap: Size<DefinedLength>,
 
     // Flexbox properies
     /// Which direction does the main axis flow in?
@@ -57,7 +57,7 @@ pub struct Style {
     /// 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: LengthOrAuto,
+    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.
@@ -77,9 +77,9 @@ impl Style {
         scrollbar_width: 0.0,
         position: Position::Relative,
         inset: Edges::auto(),
-        margin: Edges::<LengthOrAuto>::zero(),
-        padding: Edges::<Length>::zero(),
-        border: Edges::<Length>::zero(),
+        margin: Edges::<Length>::zero(),
+        padding: Edges::<DefinedLength>::zero(),
+        border: Edges::<DefinedLength>::zero(),
         size: Size::auto(),
         min_size: Size::auto(),
         max_size: Size::auto(),
@@ -95,7 +95,7 @@ impl Style {
         flex_wrap: FlexWrap::NoWrap,
         flex_grow: 0.0,
         flex_shrink: 1.0,
-        flex_basis: LengthOrAuto::Auto,
+        flex_basis: Length::Auto,
         fill: Fill::Color(Hsla {
             h: 0.,
             s: 0.,
@@ -137,6 +137,12 @@ impl Style {
     }
 }
 
+impl Default for Style {
+    fn default() -> Self {
+        Self::DEFAULT.clone()
+    }
+}
+
 #[derive(Clone)]
 pub struct Point<T> {
     pub x: T,
@@ -158,11 +164,11 @@ pub struct Size<T> {
     pub height: T,
 }
 
-impl Size<Length> {
+impl Size<DefinedLength> {
     pub const fn zero() -> Self {
         Self {
-            width: Length::Pixels(0.),
-            height: Length::Pixels(0.),
+            width: DefinedLength::Pixels(0.),
+            height: DefinedLength::Pixels(0.),
         }
     }
 
@@ -174,11 +180,11 @@ impl Size<Length> {
     }
 }
 
-impl Size<LengthOrAuto> {
+impl Size<Length> {
     pub const fn auto() -> Self {
         Self {
-            width: LengthOrAuto::Auto,
-            height: LengthOrAuto::Auto,
+            width: Length::Auto,
+            height: Length::Auto,
         }
     }
 
@@ -201,13 +207,13 @@ pub struct Edges<T> {
     pub left: T,
 }
 
-impl Edges<Length> {
+impl Edges<DefinedLength> {
     pub const fn zero() -> Self {
         Self {
-            top: Length::Pixels(0.0),
-            right: Length::Pixels(0.0),
-            bottom: Length::Pixels(0.0),
-            left: Length::Pixels(0.0),
+            top: DefinedLength::Pixels(0.0),
+            right: DefinedLength::Pixels(0.0),
+            bottom: DefinedLength::Pixels(0.0),
+            left: DefinedLength::Pixels(0.0),
         }
     }
 
@@ -221,22 +227,22 @@ impl Edges<Length> {
     }
 }
 
-impl Edges<LengthOrAuto> {
+impl Edges<Length> {
     pub const fn auto() -> Self {
         Self {
-            top: LengthOrAuto::Auto,
-            right: LengthOrAuto::Auto,
-            bottom: LengthOrAuto::Auto,
-            left: LengthOrAuto::Auto,
+            top: Length::Auto,
+            right: Length::Auto,
+            bottom: Length::Auto,
+            left: Length::Auto,
         }
     }
 
     pub const fn zero() -> Self {
         Self {
-            top: LengthOrAuto::Length(Length::Pixels(0.0)),
-            right: LengthOrAuto::Length(Length::Pixels(0.0)),
-            bottom: LengthOrAuto::Length(Length::Pixels(0.0)),
-            left: LengthOrAuto::Length(Length::Pixels(0.0)),
+            top: Length::Length(DefinedLength::Pixels(0.0)),
+            right: Length::Length(DefinedLength::Pixels(0.0)),
+            bottom: Length::Length(DefinedLength::Pixels(0.0)),
+            left: Length::Length(DefinedLength::Pixels(0.0)),
         }
     }
 
@@ -253,41 +259,43 @@ impl Edges<LengthOrAuto> {
     }
 }
 
+/// A non-auto length that can be defined in pixels, rems, or percent of parent.
 #[derive(Clone, Copy)]
-pub enum Length {
+pub enum DefinedLength {
     Pixels(f32),
     Rems(f32),
     Percent(f32), // 0. - 100.
 }
 
-impl Length {
+impl DefinedLength {
     fn to_taffy(&self, rem_size: f32) -> taffy::style::LengthPercentage {
         match self {
-            Length::Pixels(pixels) => taffy::style::LengthPercentage::Length(*pixels),
-            Length::Rems(rems) => taffy::style::LengthPercentage::Length(rems * rem_size),
-            Length::Percent(percent) => taffy::style::LengthPercentage::Percent(*percent),
+            DefinedLength::Pixels(pixels) => taffy::style::LengthPercentage::Length(*pixels),
+            DefinedLength::Rems(rems) => taffy::style::LengthPercentage::Length(rems * rem_size),
+            DefinedLength::Percent(percent) => taffy::style::LengthPercentage::Percent(*percent),
         }
     }
 }
 
+/// A length that can be defined in pixels, rems, percent of parent, or auto.
 #[derive(Clone, Copy)]
-pub enum LengthOrAuto {
-    Length(Length),
+pub enum Length {
+    Length(DefinedLength),
     Auto,
 }
 
-impl LengthOrAuto {
+impl Length {
     fn to_taffy(&self, rem_size: f32) -> taffy::prelude::LengthPercentageAuto {
         match self {
-            LengthOrAuto::Length(length) => length.to_taffy(rem_size).into(),
-            LengthOrAuto::Auto => taffy::prelude::LengthPercentageAuto::Auto,
+            Length::Length(length) => length.to_taffy(rem_size).into(),
+            Length::Auto => taffy::prelude::LengthPercentageAuto::Auto,
         }
     }
 }
 
-impl From<Length> for LengthOrAuto {
-    fn from(value: Length) -> Self {
-        LengthOrAuto::Length(value)
+impl From<DefinedLength> for Length {
+    fn from(value: DefinedLength) -> Self {
+        Length::Length(value)
     }
 }
 

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

@@ -1,6 +1,5 @@
 use crate::color::{Hsla, Lerp};
-use serde::{Deserialize, Serialize};
-use std::{ops::Range, sync::Arc};
+use std::ops::Range;
 
 pub mod rose_pine;
 
@@ -83,24 +82,3 @@ impl ThemeColors {
         self.modified.lerp(level)
     }
 }
-
-#[derive(Serialize, Deserialize)]
-struct Entity {
-    class: String,
-    #[serde(rename = "type")]
-    kind: String,
-    id: Arc<str>,
-    name: String,
-    value: String,
-    description: String,
-    category_id: String,
-    last_updated_by: String,
-    last_updated: String,
-    tags: Vec<String>,
-}
-
-#[derive(Serialize, Deserialize)]
-struct Category {
-    id: String,
-    label: String,
-}

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

@@ -1,10 +0,0 @@
-pub mod color {
-    use crate::color::{scale, ColorScale, Hsla};
-
-    pub fn ramp(color: impl Into<Hsla>) -> ColorScale {
-        let color = color.into();
-        let end_color = color.desaturate(0.1).brighten(0.5);
-        let start_color = color.desaturate(0.1).darken(0.4);
-        scale([start_color, color, end_color])
-    }
-}

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

@@ -34,67 +34,67 @@ pub fn tailwind_lengths(_attr: TokenStream, item: TokenStream) -> TokenStream {
 
 fn fixed_lengths() -> Vec<(&'static str, TokenStream2)> {
     vec![
-        ("0", quote! { Length::Pixels(0.) }),
-        ("px", quote! { Length::Pixels(1.) }),
-        ("0_5", quote! { Length::Rems(0.125) }),
-        ("1", quote! { Length::Rems(0.25) }),
-        ("1_5", quote! { Length::Rems(0.375) }),
-        ("2", quote! { Length::Rems(0.5) }),
-        ("2_5", quote! { Length::Rems(0.625) }),
-        ("3", quote! { Length::Rems(0.75) }),
-        ("3_5", quote! { Length::Rems(0.875) }),
-        ("4", quote! { Length::Rems(1.) }),
-        ("5", quote! { Length::Rems(1.25) }),
-        ("6", quote! { Length::Rems(1.5) }),
-        ("7", quote! { Length::Rems(1.75) }),
-        ("8", quote! { Length::Rems(2.) }),
-        ("9", quote! { Length::Rems(2.25) }),
-        ("10", quote! { Length::Rems(2.5) }),
-        ("11", quote! { Length::Rems(2.75) }),
-        ("12", quote! { Length::Rems(3.) }),
-        ("14", quote! { Length::Rems(3.5) }),
-        ("16", quote! { Length::Rems(4.) }),
-        ("20", quote! { Length::Rems(5.) }),
-        ("24", quote! { Length::Rems(6.) }),
-        ("28", quote! { Length::Rems(7.) }),
-        ("32", quote! { Length::Rems(8.) }),
-        ("36", quote! { Length::Rems(9.) }),
-        ("40", quote! { Length::Rems(10.) }),
-        ("44", quote! { Length::Rems(11.) }),
-        ("48", quote! { Length::Rems(12.) }),
-        ("52", quote! { Length::Rems(13.) }),
-        ("56", quote! { Length::Rems(14.) }),
-        ("60", quote! { Length::Rems(15.) }),
-        ("64", quote! { Length::Rems(16.) }),
-        ("72", quote! { Length::Rems(18.) }),
-        ("80", quote! { Length::Rems(20.) }),
-        ("96", quote! { Length::Rems(24.) }),
-        ("half", quote! { Length::Percent(50.) }),
-        ("1_3rd", quote! { Length::Percent(33.333333) }),
-        ("2_3rd", quote! { Length::Percent(66.666667) }),
-        ("1_4th", quote! { Length::Percent(25.) }),
-        ("2_4th", quote! { Length::Percent(50.) }),
-        ("3_4th", quote! { Length::Percent(75.) }),
-        ("1_5th", quote! { Length::Percent(20.) }),
-        ("2_5th", quote! { Length::Percent(40.) }),
-        ("3_5th", quote! { Length::Percent(60.) }),
-        ("4_5th", quote! { Length::Percent(80.) }),
-        ("1_6th", quote! { Length::Percent(16.666667) }),
-        ("2_6th", quote! { Length::Percent(33.333333) }),
-        ("3_6th", quote! { Length::Percent(50.) }),
-        ("4_6th", quote! { Length::Percent(66.666667) }),
-        ("5_6th", quote! { Length::Percent(83.333333) }),
-        ("1_12th", quote! { Length::Percent(8.333333) }),
-        ("2_12th", quote! { Length::Percent(16.666667) }),
-        ("3_12th", quote! { Length::Percent(25.) }),
-        ("4_12th", quote! { Length::Percent(33.333333) }),
-        ("5_12th", quote! { Length::Percent(41.666667) }),
-        ("6_12th", quote! { Length::Percent(50.) }),
-        ("7_12th", quote! { Length::Percent(58.333333) }),
-        ("8_12th", quote! { Length::Percent(66.666667) }),
-        ("9_12th", quote! { Length::Percent(75.) }),
-        ("10_12th", quote! { Length::Percent(83.333333) }),
-        ("11_12th", quote! { Length::Percent(91.666667) }),
-        ("full", quote! { Length::Percent(100.) }),
+        ("0", quote! { DefinedLength::Pixels(0.) }),
+        ("px", quote! { DefinedLength::Pixels(1.) }),
+        ("0_5", quote! { DefinedLength::Rems(0.125) }),
+        ("1", quote! { DefinedLength::Rems(0.25) }),
+        ("1_5", quote! { DefinedLength::Rems(0.375) }),
+        ("2", quote! { DefinedLength::Rems(0.5) }),
+        ("2_5", quote! { DefinedLength::Rems(0.625) }),
+        ("3", quote! { DefinedLength::Rems(0.75) }),
+        ("3_5", quote! { DefinedLength::Rems(0.875) }),
+        ("4", quote! { DefinedLength::Rems(1.) }),
+        ("5", quote! { DefinedLength::Rems(1.25) }),
+        ("6", quote! { DefinedLength::Rems(1.5) }),
+        ("7", quote! { DefinedLength::Rems(1.75) }),
+        ("8", quote! { DefinedLength::Rems(2.) }),
+        ("9", quote! { DefinedLength::Rems(2.25) }),
+        ("10", quote! { DefinedLength::Rems(2.5) }),
+        ("11", quote! { DefinedLength::Rems(2.75) }),
+        ("12", quote! { DefinedLength::Rems(3.) }),
+        ("14", quote! { DefinedLength::Rems(3.5) }),
+        ("16", quote! { DefinedLength::Rems(4.) }),
+        ("20", quote! { DefinedLength::Rems(5.) }),
+        ("24", quote! { DefinedLength::Rems(6.) }),
+        ("28", quote! { DefinedLength::Rems(7.) }),
+        ("32", quote! { DefinedLength::Rems(8.) }),
+        ("36", quote! { DefinedLength::Rems(9.) }),
+        ("40", quote! { DefinedLength::Rems(10.) }),
+        ("44", quote! { DefinedLength::Rems(11.) }),
+        ("48", quote! { DefinedLength::Rems(12.) }),
+        ("52", quote! { DefinedLength::Rems(13.) }),
+        ("56", quote! { DefinedLength::Rems(14.) }),
+        ("60", quote! { DefinedLength::Rems(15.) }),
+        ("64", quote! { DefinedLength::Rems(16.) }),
+        ("72", quote! { DefinedLength::Rems(18.) }),
+        ("80", quote! { DefinedLength::Rems(20.) }),
+        ("96", quote! { DefinedLength::Rems(24.) }),
+        ("half", quote! { DefinedLength::Percent(50.) }),
+        ("1_3rd", quote! { DefinedLength::Percent(33.333333) }),
+        ("2_3rd", quote! { DefinedLength::Percent(66.666667) }),
+        ("1_4th", quote! { DefinedLength::Percent(25.) }),
+        ("2_4th", quote! { DefinedLength::Percent(50.) }),
+        ("3_4th", quote! { DefinedLength::Percent(75.) }),
+        ("1_5th", quote! { DefinedLength::Percent(20.) }),
+        ("2_5th", quote! { DefinedLength::Percent(40.) }),
+        ("3_5th", quote! { DefinedLength::Percent(60.) }),
+        ("4_5th", quote! { DefinedLength::Percent(80.) }),
+        ("1_6th", quote! { DefinedLength::Percent(16.666667) }),
+        ("2_6th", quote! { DefinedLength::Percent(33.333333) }),
+        ("3_6th", quote! { DefinedLength::Percent(50.) }),
+        ("4_6th", quote! { DefinedLength::Percent(66.666667) }),
+        ("5_6th", quote! { DefinedLength::Percent(83.333333) }),
+        ("1_12th", quote! { DefinedLength::Percent(8.333333) }),
+        ("2_12th", quote! { DefinedLength::Percent(16.666667) }),
+        ("3_12th", quote! { DefinedLength::Percent(25.) }),
+        ("4_12th", quote! { DefinedLength::Percent(33.333333) }),
+        ("5_12th", quote! { DefinedLength::Percent(41.666667) }),
+        ("6_12th", quote! { DefinedLength::Percent(50.) }),
+        ("7_12th", quote! { DefinedLength::Percent(58.333333) }),
+        ("8_12th", quote! { DefinedLength::Percent(66.666667) }),
+        ("9_12th", quote! { DefinedLength::Percent(75.) }),
+        ("10_12th", quote! { DefinedLength::Percent(83.333333) }),
+        ("11_12th", quote! { DefinedLength::Percent(91.666667) }),
+        ("full", quote! { DefinedLength::Percent(100.) }),
     ]
 }

crates/gpui/src/app/window.rs 🔗

@@ -40,7 +40,7 @@ use uuid::Uuid;
 use super::{Reference, ViewMetadata};
 
 pub struct Window {
-    layout_engine: Taffy,
+    layout_engines: Vec<LayoutEngine>,
     pub(crate) root_view: Option<AnyViewHandle>,
     pub(crate) focused_view_id: Option<usize>,
     pub(crate) parents: HashMap<usize, usize>,
@@ -75,7 +75,7 @@ impl Window {
         let titlebar_height = platform_window.titlebar_height();
         let appearance = platform_window.appearance();
         let mut window = Self {
-            layout_engine: Taffy::new(),
+            layout_engines: Vec::new(),
             root_view: None,
             focused_view_id: None,
             parents: Default::default(),
@@ -210,8 +210,16 @@ impl<'a> WindowContext<'a> {
         }
     }
 
-    pub fn layout_engine(&mut self) -> &mut Taffy {
-        &mut self.window.layout_engine
+    pub fn layout_engine(&mut self) -> Option<&mut LayoutEngine> {
+        self.window.layout_engines.last_mut()
+    }
+
+    pub fn push_layout_engine(&mut self) {
+        self.window.layout_engines.push(LayoutEngine::new());
+    }
+
+    pub fn pop_layout_engine(&mut self) {
+        self.window.layout_engines.pop();
     }
 
     pub fn remove_window(&mut self) {
@@ -1222,6 +1230,58 @@ impl<'a> WindowContext<'a> {
     }
 }
 
+pub struct LayoutEngine(Taffy);
+pub use taffy::style::Style as LayoutStyle;
+
+impl LayoutEngine {
+    fn new() -> Self {
+        Self(Taffy::new())
+    }
+
+    pub fn add_node<C>(&mut self, style: LayoutStyle, children: C) -> Result<LayoutNodeId>
+    where
+        C: IntoIterator<Item = LayoutNodeId>,
+    {
+        Ok(self
+            .0
+            .new_with_children(style, &children.into_iter().collect::<Vec<_>>())?)
+    }
+
+    pub fn compute_layout(&mut self, root: LayoutNodeId, available_space: Vector2F) -> Result<()> {
+        self.0.compute_layout(
+            root,
+            taffy::geometry::Size {
+                width: available_space.x().into(),
+                height: available_space.y().into(),
+            },
+        )?;
+        Ok(())
+    }
+
+    pub fn computed_layout(&mut self, node: LayoutNodeId) -> Result<Layout> {
+        Ok(self.0.layout(node)?.into())
+    }
+}
+
+pub struct Layout {
+    pub bounds: RectF,
+    pub order: u32,
+}
+
+impl From<&taffy::tree::Layout> for Layout {
+    fn from(value: &taffy::tree::Layout) -> Self {
+        Self {
+            bounds: RectF::new(
+                vec2f(value.location.x, value.location.y),
+                vec2f(value.size.width, value.size.height),
+            ),
+            order: value.order,
+        }
+    }
+}
+
+pub type LayoutNodeId = taffy::prelude::NodeId;
+
 pub struct RenderParams {
     pub view_id: usize,
     pub titlebar_height: f32,

crates/gpui/src/gpui.rs 🔗

@@ -27,7 +27,9 @@ pub mod json;
 pub mod keymap_matcher;
 pub mod platform;
 pub use gpui_macros::{test, Element};
-pub use window::{Axis, RectFExt, SizeConstraint, Vector2FExt, WindowContext};
+pub use window::{
+    Axis, Layout, LayoutNodeId, RectFExt, SizeConstraint, Vector2FExt, WindowContext,
+};
 
 pub use anyhow;
 pub use serde_json;