Introduce gpui2 and storybook crates (#2919)

Nathan Sobo created

This PR splits `crates/gpui/playground` with two new crates: `gpui2` and
`storybook`.

GPUI 2 re-exports most of `gpui`, but makes some adjustments. I want to
keep the scope focused. This isn't literally version 2.0 of GPUI or
anything. It's just a convenient way to make a create with fewer
dependencies where we can iterate quickly on aspects of GPUI's design.
Most of the focus is on improving our approach to element layout and
styling.

The `storybook` crate is pretty empty for now, but it's where I intend
to start rebuilding interfaces. I welcome anyone else to join me in
this, though until I get one interface fully built, buyer beware. You
may need to pair on it with me.

Change summary

.gitignore                                                   |   1 
Cargo.lock                                                   |  75 
Cargo.toml                                                   |   5 
assets/icons/Icons/exit.svg                                  |   3 
assets/icons/stop_sharing.svg                                |   2 
crates/collab_ui/src/collab_panel.rs                         |   8 
crates/collab_ui/src/collab_titlebar_item.rs                 |  11 
crates/collab_ui/src/face_pile.rs                            |  14 
crates/editor/src/element.rs                                 | 453 +--
crates/editor/src/hover_popover.rs                           |   8 
crates/gpui/Cargo.toml                                       |   3 
crates/gpui/examples/corner_radii.rs                         |  27 
crates/gpui/examples/text.rs                                 |   6 
crates/gpui/playground/Cargo.toml                            |  26 
crates/gpui/playground/src/div.rs                            | 108 -
crates/gpui/playground/src/element.rs                        | 158 -
crates/gpui/playground/src/hoverable.rs                      |  76 
crates/gpui/playground/src/interactive.rs                    |  34 
crates/gpui/playground/src/layout_context.rs                 |  54 
crates/gpui/playground/src/paint_context.rs                  |  71 
crates/gpui/playground/src/playground.rs                     |  83 
crates/gpui/playground/src/style.rs                          | 286 --
crates/gpui/playground/src/text.rs                           | 151 -
crates/gpui/playground/src/themes.rs                         |  84 
crates/gpui/playground/src/themes/rose_pine.rs               | 133 -
crates/gpui/playground_macros/src/styleable_helpers.rs       | 147 -
crates/gpui/playground_macros/src/tailwind_lengths.rs        |  99 
crates/gpui/src/app.rs                                       | 356 --
crates/gpui/src/app/test_app_context.rs                      |   1 
crates/gpui/src/app/window.rs                                | 200 +
crates/gpui/src/elements.rs                                  |  87 
crates/gpui/src/elements/align.rs                            |   9 
crates/gpui/src/elements/canvas.rs                           |  13 
crates/gpui/src/elements/clipped.rs                          |  18 
crates/gpui/src/elements/component.rs                        |  13 
crates/gpui/src/elements/constrained_box.rs                  |  22 
crates/gpui/src/elements/container.rs                        | 196 +
crates/gpui/src/elements/empty.rs                            |   7 
crates/gpui/src/elements/expanded.rs                         |  11 
crates/gpui/src/elements/flex.rs                             |  34 
crates/gpui/src/elements/hook.rs                             |  10 
crates/gpui/src/elements/image.rs                            |  14 
crates/gpui/src/elements/keystroke_label.rs                  |   7 
crates/gpui/src/elements/label.rs                            |  15 
crates/gpui/src/elements/list.rs                             |  78 
crates/gpui/src/elements/mouse_event_handler.rs              |  37 
crates/gpui/src/elements/overlay.rs                          |  42 
crates/gpui/src/elements/resizable.rs                        | 120 
crates/gpui/src/elements/stack.rs                            |  13 
crates/gpui/src/elements/svg.rs                              |  10 
crates/gpui/src/elements/text.rs                             |  33 
crates/gpui/src/elements/tooltip.rs                          |  13 
crates/gpui/src/elements/uniform_list.rs                     |  15 
crates/gpui/src/fonts.rs                                     |  85 
crates/gpui/src/geometry.rs                                  | 126 +
crates/gpui/src/gpui.rs                                      |   6 
crates/gpui/src/image_cache.rs                               |  99 
crates/gpui/src/platform.rs                                  |   1 
crates/gpui/src/platform/mac/platform.rs                     |   1 
crates/gpui/src/platform/mac/renderer.rs                     |  18 
crates/gpui/src/platform/mac/status_item.rs                  |   4 
crates/gpui/src/platform/mac/window.rs                       |  18 
crates/gpui/src/platform/test.rs                             |   4 
crates/gpui/src/scene.rs                                     | 215 -
crates/gpui/src/text_layout.rs                               |  21 
crates/gpui2/Cargo.toml                                      |  32 
crates/gpui2/src/adapter.rs                                  |  46 
crates/gpui2/src/color.rs                                    |  52 
crates/gpui2/src/element.rs                                  | 186 +
crates/gpui2/src/elements.rs                                 |  10 
crates/gpui2/src/elements/div.rs                             | 320 +++
crates/gpui2/src/elements/hoverable.rs                       | 105 +
crates/gpui2/src/elements/img.rs                             | 110 +
crates/gpui2/src/elements/pressable.rs                       | 108 +
crates/gpui2/src/elements/svg.rs                             |  84 
crates/gpui2/src/elements/text.rs                            | 109 +
crates/gpui2/src/gpui2.rs                                    |  22 
crates/gpui2/src/interactive.rs                              | 165 +
crates/gpui2/src/style.rs                                    | 604 ++++++
crates/gpui2/src/view.rs                                     |   0 
crates/gpui2/src/view_context.rs                             |  79 
crates/gpui2_macros/Cargo.toml                               |   4 
crates/gpui2_macros/src/derive_element.rs                    |  22 
crates/gpui2_macros/src/derive_into_element.rs               |   2 
crates/gpui2_macros/src/gpui2_macros.rs                      |   6 
crates/gpui2_macros/src/styleable_helpers.rs                 | 327 +++
crates/gpui_macros/src/gpui_macros.rs                        |   7 
crates/refineable/derive_refineable/src/derive_refineable.rs |  56 
crates/refineable/src/refineable.rs                          |  46 
crates/storybook/Cargo.lock                                  |   2 
crates/storybook/Cargo.toml                                  |  23 
crates/storybook/docs/thoughts.md                            |   0 
crates/storybook/src/collab_panel.rs                         | 177 +
crates/storybook/src/components.rs                           |  59 
crates/storybook/src/element_ext.rs                          |  22 
crates/storybook/src/storybook.rs                            | 109 +
crates/storybook/src/theme.rs                                | 192 +
crates/storybook/src/workspace.rs                            | 435 ++++
crates/terminal_view/src/terminal_element.rs                 |  49 
crates/theme/src/theme.rs                                    |  15 
crates/util/src/arc_cow.rs                                   |  74 
crates/util/src/http.rs                                      |   2 
crates/util/src/util.rs                                      |  17 
crates/workspace/src/pane.rs                                 |  24 
crates/workspace/src/pane/dragged_item_receiver.rs           |  17 
crates/workspace/src/pane_group.rs                           |  27 
crates/workspace/src/shared_screen.rs                        |   4 
crates/workspace/src/status_bar.rs                           |  15 
crates/workspace/src/workspace.rs                            |   4 
styles/src/build_themes.ts                                   |   3 
test.rs                                                      | 416 +--
111 files changed, 5,045 insertions(+), 3,241 deletions(-)

Detailed changes

.gitignore 🔗

@@ -1,4 +1,5 @@
 **/target
+**/cargo-target
 /zed.xcworkspace
 .DS_Store
 /plugins/bin

Cargo.lock 🔗

@@ -3163,6 +3163,7 @@ dependencies = [
  "sqlez",
  "sum_tree",
  "taffy",
+ "thiserror",
  "time 0.3.27",
  "tiny-skia",
  "usvg",
@@ -3171,6 +3172,36 @@ dependencies = [
  "waker-fn",
 ]
 
+[[package]]
+name = "gpui2"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "derive_more",
+ "futures 0.3.28",
+ "gpui",
+ "gpui2_macros",
+ "log",
+ "parking_lot 0.11.2",
+ "refineable",
+ "rust-embed",
+ "serde",
+ "settings",
+ "simplelog",
+ "smallvec",
+ "theme",
+ "util",
+]
+
+[[package]]
+name = "gpui2_macros"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
 [[package]]
 name = "gpui_macros"
 version = "0.1.0"
@@ -5231,33 +5262,6 @@ version = "0.3.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
 
-[[package]]
-name = "playground"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "derive_more",
- "gpui",
- "log",
- "parking_lot 0.11.2",
- "playground_macros",
- "refineable",
- "serde",
- "simplelog",
- "smallvec",
- "taffy",
- "util",
-]
-
-[[package]]
-name = "playground_macros"
-version = "0.1.0"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
 [[package]]
 name = "plist"
 version = "1.5.0"
@@ -7367,6 +7371,21 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
 
+[[package]]
+name = "storybook"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "gpui2",
+ "log",
+ "rust-embed",
+ "serde",
+ "settings",
+ "simplelog",
+ "theme",
+ "util",
+]
+
 [[package]]
 name = "stringprep"
 version = "0.1.3"
@@ -7566,7 +7585,7 @@ dependencies = [
 [[package]]
 name = "taffy"
 version = "0.3.11"
-source = "git+https://github.com/DioxusLabs/taffy?rev=dab541d6104d58e2e10ce90c4a1dad0b703160cd#dab541d6104d58e2e10ce90c4a1dad0b703160cd"
+source = "git+https://github.com/DioxusLabs/taffy?rev=4fb530bdd71609bb1d3f76c6a8bde1ba82805d5e#4fb530bdd71609bb1d3f76c6a8bde1ba82805d5e"
 dependencies = [
  "arrayvec 0.7.4",
  "grid",

Cargo.toml 🔗

@@ -32,9 +32,9 @@ members = [
     "crates/git",
     "crates/go_to_line",
     "crates/gpui",
-    "crates/gpui/playground",
-    "crates/gpui/playground_macros",
     "crates/gpui_macros",
+    "crates/gpui2",
+    "crates/gpui2_macros",
     "crates/install_cli",
     "crates/journal",
     "crates/language",
@@ -63,6 +63,7 @@ members = [
     "crates/sqlez",
     "crates/sqlez_macros",
     "crates/feature_flags",
+    "crates/storybook",
     "crates/sum_tree",
     "crates/terminal",
     "crates/text",

assets/icons/Icons/exit.svg 🔗

@@ -0,0 +1,3 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M3 1C2.44771 1 2 1.44772 2 2V13C2 13.5523 2.44772 14 3 14H10.5C10.7761 14 11 13.7761 11 13.5C11 13.2239 10.7761 13 10.5 13H3V2L10.5 2C10.7761 2 11 1.77614 11 1.5C11 1.22386 10.7761 1 10.5 1H3ZM12.6036 4.89645C12.4083 4.70118 12.0917 4.70118 11.8964 4.89645C11.7012 5.09171 11.7012 5.40829 11.8964 5.60355L13.2929 7H6.5C6.22386 7 6 7.22386 6 7.5C6 7.77614 6.22386 8 6.5 8H13.2929L11.8964 9.39645C11.7012 9.59171 11.7012 9.90829 11.8964 10.1036C12.0917 10.2988 12.4083 10.2988 12.6036 10.1036L14.8536 7.85355C15.0488 7.65829 15.0488 7.34171 14.8536 7.14645L12.6036 4.89645Z" fill="black"/>
+</svg>

assets/icons/stop_sharing.svg 🔗

@@ -0,0 +1,5 @@
+<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M7.70312 4L7.26046 2.97338C7.10239 2.60678 6.74141 2.36933 6.34219 2.36933H2.5C2.22386 2.36933 2 2.59319 2 2.86933V4.375V8" stroke="#11181C" stroke-width="1.25" stroke-linecap="round"/>

crates/collab_ui/src/collab_panel.rs 🔗

@@ -2434,14 +2434,14 @@ fn render_tree_branch(
     let cap_height = row_style.cap_height(font_cache);
     let baseline_offset = row_style.baseline_offset(font_cache) + (size.y() - line_height) / 2.;
 
-    Canvas::new(move |scene, bounds, _, _, _| {
-        scene.paint_layer(None, |scene| {
+    Canvas::new(move |bounds, _, _, cx| {
+        cx.paint_layer(None, |cx| {
             let start_x = bounds.min_x() + (bounds.width() / 2.) - (branch_style.width / 2.);
             let end_x = bounds.max_x();
             let start_y = bounds.min_y();
             let end_y = bounds.min_y() + baseline_offset - (cap_height / 2.);
 
-            scene.push_quad(gpui::Quad {
+            cx.scene().push_quad(gpui::Quad {
                 bounds: RectF::from_points(
                     vec2f(start_x, start_y),
                     vec2f(
@@ -2453,7 +2453,7 @@ fn render_tree_branch(
                 border: gpui::Border::default(),
                 corner_radii: (0.).into(),
             });
-            scene.push_quad(gpui::Quad {
+            cx.scene().push_quad(gpui::Quad {
                 bounds: RectF::from_points(
                     vec2f(start_x, end_y),
                     vec2f(end_x, end_y + branch_style.width),

crates/collab_ui/src/collab_titlebar_item.rs 🔗

@@ -13,8 +13,8 @@ use gpui::{
     geometry::{rect::RectF, vector::vec2f, PathBuilder},
     json::{self, ToJson},
     platform::{CursorStyle, MouseButton},
-    AppContext, Entity, ImageData, LayoutContext, ModelHandle, PaintContext, SceneBuilder,
-    Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
+    AppContext, Entity, ImageData, ModelHandle, Subscription, View, ViewContext, ViewHandle,
+    WeakViewHandle,
 };
 use picker::PickerEvent;
 use project::{Project, RepositoryEntry};
@@ -1165,19 +1165,18 @@ impl Element<CollabTitlebarItem> for AvatarRibbon {
         &mut self,
         constraint: gpui::SizeConstraint,
         _: &mut CollabTitlebarItem,
-        _: &mut LayoutContext<CollabTitlebarItem>,
+        _: &mut ViewContext<CollabTitlebarItem>,
     ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
         (constraint.max, ())
     }
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         _: RectF,
         _: &mut Self::LayoutState,
         _: &mut CollabTitlebarItem,
-        _: &mut PaintContext<CollabTitlebarItem>,
+        cx: &mut ViewContext<CollabTitlebarItem>,
     ) -> Self::PaintState {
         let mut path = PathBuilder::new();
         path.reset(bounds.lower_left());
@@ -1188,7 +1187,7 @@ impl Element<CollabTitlebarItem> for AvatarRibbon {
         path.line_to(bounds.upper_right() - vec2f(bounds.height(), 0.));
         path.curve_to(bounds.lower_right(), bounds.upper_right());
         path.line_to(bounds.lower_left());
-        scene.push_path(path.build(self.color, None));
+        cx.scene().push_path(path.build(self.color, None));
     }
 
     fn rect_for_text_range(

crates/collab_ui/src/face_pile.rs 🔗

@@ -7,7 +7,7 @@ use gpui::{
     },
     json::ToJson,
     serde_json::{self, json},
-    AnyElement, Axis, Element, LayoutContext, PaintContext, SceneBuilder, View, ViewContext,
+    AnyElement, Axis, Element, View, ViewContext,
 };
 
 pub(crate) struct FacePile<V: View> {
@@ -32,7 +32,7 @@ impl<V: View> Element<V> for FacePile<V> {
         &mut self,
         constraint: gpui::SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         debug_assert!(constraint.max_along(Axis::Horizontal) == f32::INFINITY);
 
@@ -53,12 +53,11 @@ impl<V: View> Element<V> for FacePile<V> {
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _layout: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
 
@@ -69,9 +68,10 @@ impl<V: View> Element<V> for FacePile<V> {
             let size = face.size();
             origin_x -= size.x();
             let origin_y = origin_y + (bounds.height() - size.y()) / 2.0;
-            scene.paint_layer(None, |scene| {
-                face.paint(scene, vec2f(origin_x, origin_y), visible_bounds, view, cx);
-            });
+
+            cx.scene().push_layer(None);
+            face.paint(vec2f(origin_x, origin_y), visible_bounds, view, cx);
+            cx.scene().pop_layer();
             origin_x += self.overlap;
         }
 

crates/editor/src/element.rs 🔗

@@ -32,8 +32,8 @@ use gpui::{
     json::{self, ToJson},
     platform::{CursorStyle, Modifiers, MouseButton, MouseButtonEvent, MouseMovedEvent},
     text_layout::{self, Line, RunStyle, TextLayoutCache},
-    AnyElement, Axis, Border, CursorRegion, Element, EventContext, FontCache, LayoutContext,
-    MouseRegion, PaintContext, Quad, SceneBuilder, SizeConstraint, ViewContext, WindowContext,
+    AnyElement, Axis, CursorRegion, Element, EventContext, FontCache, MouseRegion, Quad,
+    SizeConstraint, ViewContext, WindowContext,
 };
 use itertools::Itertools;
 use json::json;
@@ -131,7 +131,6 @@ impl EditorElement {
     }
 
     fn attach_mouse_handlers(
-        scene: &mut SceneBuilder,
         position_map: &Arc<PositionMap>,
         has_popovers: bool,
         visible_bounds: RectF,
@@ -141,124 +140,124 @@ impl EditorElement {
         cx: &mut ViewContext<Editor>,
     ) {
         enum EditorElementMouseHandlers {}
-        scene.push_mouse_region(
-            MouseRegion::new::<EditorElementMouseHandlers>(
-                cx.view_id(),
-                cx.view_id(),
-                visible_bounds,
-            )
-            .on_down(MouseButton::Left, {
-                let position_map = position_map.clone();
-                move |event, editor, cx| {
-                    if !Self::mouse_down(
-                        editor,
-                        event.platform_event,
-                        position_map.as_ref(),
-                        text_bounds,
-                        gutter_bounds,
-                        cx,
-                    ) {
-                        cx.propagate_event();
-                    }
-                }
-            })
-            .on_down(MouseButton::Right, {
-                let position_map = position_map.clone();
-                move |event, editor, cx| {
-                    if !Self::mouse_right_down(
-                        editor,
-                        event.position,
-                        position_map.as_ref(),
-                        text_bounds,
-                        cx,
-                    ) {
-                        cx.propagate_event();
+        let view_id = cx.view_id();
+        cx.scene().push_mouse_region(
+            MouseRegion::new::<EditorElementMouseHandlers>(view_id, view_id, visible_bounds)
+                .on_down(MouseButton::Left, {
+                    let position_map = position_map.clone();
+                    move |event, editor, cx| {
+                        if !Self::mouse_down(
+                            editor,
+                            event.platform_event,
+                            position_map.as_ref(),
+                            text_bounds,
+                            gutter_bounds,
+                            cx,
+                        ) {
+                            cx.propagate_event();
+                        }
                     }
-                }
-            })
-            .on_up(MouseButton::Left, {
-                let position_map = position_map.clone();
-                move |event, editor, cx| {
-                    if !Self::mouse_up(
-                        editor,
-                        event.position,
-                        event.cmd,
-                        event.shift,
-                        event.alt,
-                        position_map.as_ref(),
-                        text_bounds,
-                        cx,
-                    ) {
-                        cx.propagate_event()
+                })
+                .on_down(MouseButton::Right, {
+                    let position_map = position_map.clone();
+                    move |event, editor, cx| {
+                        if !Self::mouse_right_down(
+                            editor,
+                            event.position,
+                            position_map.as_ref(),
+                            text_bounds,
+                            cx,
+                        ) {
+                            cx.propagate_event();
+                        }
                     }
-                }
-            })
-            .on_drag(MouseButton::Left, {
-                let position_map = position_map.clone();
-                move |event, editor, cx| {
-                    if event.end {
-                        return;
+                })
+                .on_up(MouseButton::Left, {
+                    let position_map = position_map.clone();
+                    move |event, editor, cx| {
+                        if !Self::mouse_up(
+                            editor,
+                            event.position,
+                            event.cmd,
+                            event.shift,
+                            event.alt,
+                            position_map.as_ref(),
+                            text_bounds,
+                            cx,
+                        ) {
+                            cx.propagate_event()
+                        }
                     }
+                })
+                .on_drag(MouseButton::Left, {
+                    let position_map = position_map.clone();
+                    move |event, editor, cx| {
+                        if event.end {
+                            return;
+                        }
 
-                    if !Self::mouse_dragged(
-                        editor,
-                        event.platform_event,
-                        position_map.as_ref(),
-                        text_bounds,
-                        cx,
-                    ) {
-                        cx.propagate_event()
+                        if !Self::mouse_dragged(
+                            editor,
+                            event.platform_event,
+                            position_map.as_ref(),
+                            text_bounds,
+                            cx,
+                        ) {
+                            cx.propagate_event()
+                        }
                     }
-                }
-            })
-            .on_move({
-                let position_map = position_map.clone();
-                move |event, editor, cx| {
-                    if !Self::mouse_moved(
-                        editor,
-                        event.platform_event,
-                        &position_map,
-                        text_bounds,
-                        cx,
-                    ) {
-                        cx.propagate_event()
+                })
+                .on_move({
+                    let position_map = position_map.clone();
+                    move |event, editor, cx| {
+                        if !Self::mouse_moved(
+                            editor,
+                            event.platform_event,
+                            &position_map,
+                            text_bounds,
+                            cx,
+                        ) {
+                            cx.propagate_event()
+                        }
                     }
-                }
-            })
-            .on_move_out(move |_, editor: &mut Editor, cx| {
-                if has_popovers {
-                    hide_hover(editor, cx);
-                }
-            })
-            .on_scroll({
-                let position_map = position_map.clone();
-                move |event, editor, cx| {
-                    if !Self::scroll(
-                        editor,
-                        event.position,
-                        *event.delta.raw(),
-                        event.delta.precise(),
-                        &position_map,
-                        bounds,
-                        cx,
-                    ) {
-                        cx.propagate_event()
+                })
+                .on_move_out(move |_, editor: &mut Editor, cx| {
+                    if has_popovers {
+                        hide_hover(editor, cx);
                     }
-                }
-            }),
+                })
+                .on_scroll({
+                    let position_map = position_map.clone();
+                    move |event, editor, cx| {
+                        if !Self::scroll(
+                            editor,
+                            event.position,
+                            *event.delta.raw(),
+                            event.delta.precise(),
+                            &position_map,
+                            bounds,
+                            cx,
+                        ) {
+                            cx.propagate_event()
+                        }
+                    }
+                }),
         );
 
         enum GutterHandlers {}
-        scene.push_mouse_region(
-            MouseRegion::new::<GutterHandlers>(cx.view_id(), cx.view_id() + 1, gutter_bounds)
-                .on_hover(|hover, editor: &mut Editor, cx| {
+        let view_id = cx.view_id();
+        let region_id = cx.view_id() + 1;
+        cx.scene().push_mouse_region(
+            MouseRegion::new::<GutterHandlers>(view_id, region_id, gutter_bounds).on_hover(
+                |hover, editor: &mut Editor, cx| {
                     editor.gutter_hover(
                         &GutterHover {
                             hovered: hover.started,
                         },
                         cx,
                     );
-                }),
+                },
+            ),
         )
     }
 
@@ -528,24 +527,24 @@ impl EditorElement {
 
     fn paint_background(
         &self,
-        scene: &mut SceneBuilder,
         gutter_bounds: RectF,
         text_bounds: RectF,
         layout: &LayoutState,
+        cx: &mut ViewContext<Editor>,
     ) {
         let bounds = gutter_bounds.union_rect(text_bounds);
         let scroll_top =
             layout.position_map.snapshot.scroll_position().y() * layout.position_map.line_height;
-        scene.push_quad(Quad {
+        cx.scene().push_quad(Quad {
             bounds: gutter_bounds,
             background: Some(self.style.gutter_background),
-            border: Border::new(0., Color::transparent_black()),
+            border: Border::new(0., Color::transparent_black()).into(),
             corner_radii: Default::default(),
         });
-        scene.push_quad(Quad {
+        cx.scene().push_quad(Quad {
             bounds: text_bounds,
             background: Some(self.style.background),
-            border: Border::new(0., Color::transparent_black()),
+            border: Border::new(0., Color::transparent_black()).into(),
             corner_radii: Default::default(),
         });
 
@@ -570,10 +569,10 @@ impl EditorElement {
                         bounds.width(),
                         layout.position_map.line_height * (end_row - start_row + 1) as f32,
                     );
-                    scene.push_quad(Quad {
+                    cx.scene().push_quad(Quad {
                         bounds: RectF::new(origin, size),
                         background: Some(self.style.active_line_background),
-                        border: Border::default(),
+                        border: Border::default().into(),
                         corner_radii: Default::default(),
                     });
                 }
@@ -590,10 +589,10 @@ impl EditorElement {
                     bounds.width(),
                     layout.position_map.line_height * highlighted_rows.len() as f32,
                 );
-                scene.push_quad(Quad {
+                cx.scene().push_quad(Quad {
                     bounds: RectF::new(origin, size),
                     background: Some(self.style.highlighted_line_background),
-                    border: Border::default(),
+                    border: Border::default().into(),
                     corner_radii: Default::default(),
                 });
             }
@@ -617,13 +616,13 @@ impl EditorElement {
                 } else {
                     self.style.wrap_guide
                 };
-                scene.push_quad(Quad {
+                cx.scene().push_quad(Quad {
                     bounds: RectF::new(
                         vec2f(x, text_bounds.origin_y()),
                         vec2f(1., text_bounds.height()),
                     ),
                     background: Some(color),
-                    border: Border::new(0., Color::transparent_black()),
+                    border: Border::new(0., Color::transparent_black()).into(),
                     corner_radii: Default::default(),
                 });
             }
@@ -632,12 +631,11 @@ impl EditorElement {
 
     fn paint_gutter(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         layout: &mut LayoutState,
         editor: &mut Editor,
-        cx: &mut PaintContext<Editor>,
+        cx: &mut ViewContext<Editor>,
     ) {
         let line_height = layout.position_map.line_height;
 
@@ -650,7 +648,7 @@ impl EditorElement {
         );
 
         if show_gutter {
-            Self::paint_diff_hunks(scene, bounds, layout, cx);
+            Self::paint_diff_hunks(bounds, layout, cx);
         }
 
         for (ix, line) in layout.line_number_layouts.iter().enumerate() {
@@ -661,7 +659,7 @@ impl EditorElement {
                         ix as f32 * line_height - (scroll_top % line_height),
                     );
 
-                line.paint(scene, line_origin, visible_bounds, line_height, cx);
+                line.paint(line_origin, visible_bounds, line_height, cx);
             }
         }
 
@@ -678,7 +676,7 @@ impl EditorElement {
 
                 let indicator_origin = bounds.origin() + position + centering_offset;
 
-                indicator.paint(scene, indicator_origin, visible_bounds, editor, cx);
+                indicator.paint(indicator_origin, visible_bounds, editor, cx);
             }
         }
 
@@ -687,22 +685,11 @@ impl EditorElement {
             let mut y = *row as f32 * line_height - scroll_top;
             x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x()) / 2.;
             y += (line_height - indicator.size().y()) / 2.;
-            indicator.paint(
-                scene,
-                bounds.origin() + vec2f(x, y),
-                visible_bounds,
-                editor,
-                cx,
-            );
+            indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, editor, cx);
         }
     }
 
-    fn paint_diff_hunks(
-        scene: &mut SceneBuilder,
-        bounds: RectF,
-        layout: &mut LayoutState,
-        cx: &mut ViewContext<Editor>,
-    ) {
+    fn paint_diff_hunks(bounds: RectF, layout: &mut LayoutState, cx: &mut ViewContext<Editor>) {
         let diff_style = &theme::current(cx).editor.diff.clone();
         let line_height = layout.position_map.line_height;
 
@@ -721,10 +708,10 @@ impl EditorElement {
                     let highlight_size = vec2f(width * 2., end_y - start_y);
                     let highlight_bounds = RectF::new(highlight_origin, highlight_size);
 
-                    scene.push_quad(Quad {
+                    cx.scene().push_quad(Quad {
                         bounds: highlight_bounds,
                         background: Some(diff_style.modified),
-                        border: Border::new(0., Color::transparent_black()),
+                        border: Border::new(0., Color::transparent_black()).into(),
                         corner_radii: (1. * line_height).into(),
                     });
 
@@ -754,10 +741,10 @@ impl EditorElement {
                     let highlight_size = vec2f(width * 2., end_y - start_y);
                     let highlight_bounds = RectF::new(highlight_origin, highlight_size);
 
-                    scene.push_quad(Quad {
+                    cx.scene().push_quad(Quad {
                         bounds: highlight_bounds,
                         background: Some(diff_style.deleted),
-                        border: Border::new(0., Color::transparent_black()),
+                        border: Border::new(0., Color::transparent_black()).into(),
                         corner_radii: (1. * line_height).into(),
                     });
 
@@ -776,10 +763,10 @@ impl EditorElement {
             let highlight_size = vec2f(width * 2., end_y - start_y);
             let highlight_bounds = RectF::new(highlight_origin, highlight_size);
 
-            scene.push_quad(Quad {
+            cx.scene().push_quad(Quad {
                 bounds: highlight_bounds,
                 background: Some(color),
-                border: Border::new(0., Color::transparent_black()),
+                border: Border::new(0., Color::transparent_black()).into(),
                 corner_radii: (diff_style.corner_radius * line_height).into(),
             });
         }
@@ -787,12 +774,11 @@ impl EditorElement {
 
     fn paint_text(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         layout: &mut LayoutState,
         editor: &mut Editor,
-        cx: &mut PaintContext<Editor>,
+        cx: &mut ViewContext<Editor>,
     ) {
         let style = &self.style;
         let scroll_position = layout.position_map.snapshot.scroll_position();
@@ -804,9 +790,9 @@ impl EditorElement {
         let line_end_overshoot = 0.15 * layout.position_map.line_height;
         let whitespace_setting = editor.buffer.read(cx).settings_at(0, cx).show_whitespaces;
 
-        scene.push_layer(Some(bounds));
+        cx.scene().push_layer(Some(bounds));
 
-        scene.push_cursor_region(CursorRegion {
+        cx.scene().push_cursor_region(CursorRegion {
             bounds,
             style: if !editor.link_go_to_definition_state.definitions.is_empty() {
                 CursorStyle::PointingHand
@@ -819,7 +805,6 @@ impl EditorElement {
             self.style.folds.ellipses.corner_radius_factor * layout.position_map.line_height;
         for (id, range, color) in layout.fold_ranges.iter() {
             self.paint_highlighted_range(
-                scene,
                 range.clone(),
                 *color,
                 fold_corner_radius,
@@ -829,6 +814,7 @@ impl EditorElement {
                 scroll_top,
                 scroll_left,
                 bounds,
+                cx,
             );
 
             for bound in range_to_bounds(
@@ -840,7 +826,7 @@ impl EditorElement {
                 line_end_overshoot,
                 &layout.position_map,
             ) {
-                scene.push_cursor_region(CursorRegion {
+                cx.scene().push_cursor_region(CursorRegion {
                     bounds: bound,
                     style: CursorStyle::PointingHand,
                 });
@@ -851,8 +837,9 @@ impl EditorElement {
                     .to_point(&layout.position_map.snapshot.display_snapshot)
                     .row;
 
-                scene.push_mouse_region(
-                    MouseRegion::new::<FoldMarkers>(cx.view_id(), *id as usize, bound)
+                let view_id = cx.view_id();
+                cx.scene().push_mouse_region(
+                    MouseRegion::new::<FoldMarkers>(view_id, *id as usize, bound)
                         .on_click(MouseButton::Left, move |_, editor: &mut Editor, cx| {
                             editor.unfold_at(&UnfoldAt { buffer_row }, cx)
                         })
@@ -864,7 +851,6 @@ impl EditorElement {
 
         for (range, color) in &layout.highlighted_ranges {
             self.paint_highlighted_range(
-                scene,
                 range.clone(),
                 *color,
                 0.,
@@ -874,6 +860,7 @@ impl EditorElement {
                 scroll_top,
                 scroll_left,
                 bounds,
+                cx,
             );
         }
 
@@ -891,7 +878,6 @@ impl EditorElement {
 
             for selection in selections {
                 self.paint_highlighted_range(
-                    scene,
                     selection.range.clone(),
                     selection_style.selection,
                     corner_radius,
@@ -901,6 +887,7 @@ impl EditorElement {
                     scroll_top,
                     scroll_left,
                     bounds,
+                    cx,
                 );
 
                 if selection.is_local && !selection.range.is_empty() {
@@ -980,7 +967,6 @@ impl EditorElement {
                     layout,
                     row,
                     scroll_top,
-                    scene,
                     content_origin,
                     scroll_left,
                     visible_text_bounds,
@@ -992,14 +978,14 @@ impl EditorElement {
             }
         }
 
-        scene.paint_layer(Some(bounds), |scene| {
-            for cursor in cursors {
-                cursor.paint(scene, content_origin, cx);
-            }
-        });
+        cx.scene().push_layer(Some(bounds));
+        for cursor in cursors {
+            cursor.paint(content_origin, cx);
+        }
+        cx.scene().pop_layer();
 
         if let Some((position, context_menu)) = layout.context_menu.as_mut() {
-            scene.push_stacking_context(None, None);
+            cx.scene().push_stacking_context(None, None);
             let cursor_row_layout =
                 &layout.position_map.line_layouts[(position.row() - start_row) as usize].line;
             let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_left;
@@ -1019,18 +1005,17 @@ impl EditorElement {
             }
 
             context_menu.paint(
-                scene,
                 list_origin,
                 RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
                 editor,
                 cx,
             );
 
-            scene.pop_stacking_context();
+            cx.scene().pop_stacking_context();
         }
 
         if let Some((position, hover_popovers)) = layout.hover_popovers.as_mut() {
-            scene.push_stacking_context(None, None);
+            cx.scene().push_stacking_context(None, None);
 
             // This is safe because we check on layout whether the required row is available
             let hovered_row_layout =
@@ -1061,7 +1046,6 @@ impl EditorElement {
                     }
 
                     hover_popover.paint(
-                        scene,
                         popover_origin,
                         RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
                         editor,
@@ -1083,7 +1067,6 @@ impl EditorElement {
                     }
 
                     hover_popover.paint(
-                        scene,
                         popover_origin,
                         RectF::from_points(Vector2F::zero(), vec2f(f32::MAX, f32::MAX)), // Let content bleed outside of editor
                         editor,
@@ -1094,10 +1077,10 @@ impl EditorElement {
                 }
             }
 
-            scene.pop_stacking_context();
+            cx.scene().pop_stacking_context();
         }
 
-        scene.pop_layer();
+        cx.scene().pop_layer();
     }
 
     fn scrollbar_left(&self, bounds: &RectF) -> f32 {
@@ -1106,11 +1089,10 @@ impl EditorElement {
 
     fn paint_scrollbar(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         layout: &mut LayoutState,
-        cx: &mut ViewContext<Editor>,
         editor: &Editor,
+        cx: &mut ViewContext<Editor>,
     ) {
         enum ScrollbarMouseHandlers {}
         if layout.mode != EditorMode::Full {
@@ -1147,9 +1129,9 @@ impl EditorElement {
         let thumb_bounds = RectF::from_points(vec2f(left, thumb_top), vec2f(right, thumb_bottom));
 
         if layout.show_scrollbars {
-            scene.push_quad(Quad {
+            cx.scene().push_quad(Quad {
                 bounds: track_bounds,
-                border: style.track.border,
+                border: style.track.border.into(),
                 background: style.track.background_color,
                 ..Default::default()
             });
@@ -1177,10 +1159,10 @@ impl EditorElement {
                     }
                     let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y));
 
-                    scene.push_quad(Quad {
+                    cx.scene().push_quad(Quad {
                         bounds,
                         background: Some(color),
-                        border,
+                        border: border.into(),
                         corner_radii: style.thumb.corner_radii.into(),
                     })
                 };
@@ -1237,29 +1219,30 @@ impl EditorElement {
                         left: true,
                     };
 
-                    scene.push_quad(Quad {
+                    cx.scene().push_quad(Quad {
                         bounds,
                         background: Some(color),
-                        border,
+                        border: border.into(),
                         corner_radii: style.thumb.corner_radii.into(),
                     })
                 }
             }
 
-            scene.push_quad(Quad {
+            cx.scene().push_quad(Quad {
                 bounds: thumb_bounds,
-                border: style.thumb.border,
+                border: style.thumb.border.into(),
                 background: style.thumb.background_color,
                 corner_radii: style.thumb.corner_radii.into(),
             });
         }
 
-        scene.push_cursor_region(CursorRegion {
+        cx.scene().push_cursor_region(CursorRegion {
             bounds: track_bounds,
             style: CursorStyle::Arrow,
         });
-        scene.push_mouse_region(
-            MouseRegion::new::<ScrollbarMouseHandlers>(cx.view_id(), cx.view_id(), track_bounds)
+        let region_id = cx.view_id();
+        cx.scene().push_mouse_region(
+            MouseRegion::new::<ScrollbarMouseHandlers>(region_id, region_id, track_bounds)
                 .on_move(move |event, editor: &mut Editor, cx| {
                     if event.pressed_button.is_none() {
                         editor.scroll_manager.show_scrollbar(cx);
@@ -1305,7 +1288,6 @@ impl EditorElement {
     #[allow(clippy::too_many_arguments)]
     fn paint_highlighted_range(
         &self,
-        scene: &mut SceneBuilder,
         range: Range<DisplayPoint>,
         color: Color,
         corner_radius: f32,
@@ -1315,6 +1297,7 @@ impl EditorElement {
         scroll_top: f32,
         scroll_left: f32,
         bounds: RectF,
+        cx: &mut ViewContext<Editor>,
     ) {
         let start_row = layout.visible_display_row_range.start;
         let end_row = layout.visible_display_row_range.end;
@@ -1358,18 +1341,17 @@ impl EditorElement {
                     .collect(),
             };
 
-            highlighted_range.paint(bounds, scene);
+            highlighted_range.paint(bounds, cx);
         }
     }
 
     fn paint_blocks(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         layout: &mut LayoutState,
         editor: &mut Editor,
-        cx: &mut PaintContext<Editor>,
+        cx: &mut ViewContext<Editor>,
     ) {
         let scroll_position = layout.position_map.snapshot.scroll_position();
         let scroll_left = scroll_position.x() * layout.position_map.em_width;
@@ -1384,9 +1366,7 @@ impl EditorElement {
             if !matches!(block.style, BlockStyle::Sticky) {
                 origin += vec2f(-scroll_left, 0.);
             }
-            block
-                .element
-                .paint(scene, origin, visible_bounds, editor, cx);
+            block.element.paint(origin, visible_bounds, editor, cx);
         }
     }
 
@@ -1690,7 +1670,7 @@ impl EditorElement {
         style: &EditorStyle,
         line_layouts: &[LineWithInvisibles],
         editor: &mut Editor,
-        cx: &mut LayoutContext<Editor>,
+        cx: &mut ViewContext<Editor>,
     ) -> (f32, Vec<BlockLayout>) {
         let mut block_id = 0;
         let scroll_x = snapshot.scroll_anchor.offset.x();
@@ -2022,7 +2002,6 @@ impl LineWithInvisibles {
         layout: &LayoutState,
         row: u32,
         scroll_top: f32,
-        scene: &mut SceneBuilder,
         content_origin: Vector2F,
         scroll_left: f32,
         visible_text_bounds: RectF,
@@ -2035,7 +2014,6 @@ impl LineWithInvisibles {
         let line_y = row as f32 * line_height - scroll_top;
 
         self.line.paint(
-            scene,
             content_origin + vec2f(-scroll_left, line_y),
             visible_text_bounds,
             line_height,
@@ -2049,7 +2027,6 @@ impl LineWithInvisibles {
             scroll_left,
             line_y,
             row,
-            scene,
             visible_bounds,
             line_height,
             whitespace_setting,
@@ -2065,7 +2042,6 @@ impl LineWithInvisibles {
         scroll_left: f32,
         line_y: f32,
         row: u32,
-        scene: &mut SceneBuilder,
         visible_bounds: RectF,
         line_height: f32,
         whitespace_setting: ShowWhitespaceSetting,
@@ -2097,7 +2073,7 @@ impl LineWithInvisibles {
                     continue;
                 }
             }
-            invisible_symbol.paint(scene, origin, visible_bounds, line_height, cx);
+            invisible_symbol.paint(origin, visible_bounds, line_height, cx);
         }
     }
 }
@@ -2116,7 +2092,7 @@ impl Element<Editor> for EditorElement {
         &mut self,
         constraint: SizeConstraint,
         editor: &mut Editor,
-        cx: &mut LayoutContext<Editor>,
+        cx: &mut ViewContext<Editor>,
     ) -> (Vector2F, Self::LayoutState) {
         let mut size = constraint.max;
         if size.x().is_infinite() {
@@ -2590,15 +2566,14 @@ impl Element<Editor> for EditorElement {
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         layout: &mut Self::LayoutState,
         editor: &mut Editor,
-        cx: &mut PaintContext<Editor>,
+        cx: &mut ViewContext<Editor>,
     ) -> Self::PaintState {
         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
-        scene.push_layer(Some(visible_bounds));
+        cx.scene().push_layer(Some(visible_bounds));
 
         let gutter_bounds = RectF::new(bounds.origin(), layout.gutter_size);
         let text_bounds = RectF::new(
@@ -2607,7 +2582,6 @@ impl Element<Editor> for EditorElement {
         );
 
         Self::attach_mouse_handlers(
-            scene,
             &layout.position_map,
             layout.hover_popovers.is_some(),
             visible_bounds,
@@ -2617,20 +2591,19 @@ impl Element<Editor> for EditorElement {
             cx,
         );
 
-        self.paint_background(scene, gutter_bounds, text_bounds, layout);
+        self.paint_background(gutter_bounds, text_bounds, layout, cx);
         if layout.gutter_size.x() > 0. {
-            self.paint_gutter(scene, gutter_bounds, visible_bounds, layout, editor, cx);
+            self.paint_gutter(gutter_bounds, visible_bounds, layout, editor, cx);
         }
-        self.paint_text(scene, text_bounds, visible_bounds, layout, editor, cx);
+        self.paint_text(text_bounds, visible_bounds, layout, editor, cx);
 
-        scene.push_layer(Some(bounds));
+        cx.scene().push_layer(Some(bounds));
         if !layout.blocks.is_empty() {
-            self.paint_blocks(scene, bounds, visible_bounds, layout, editor, cx);
+            self.paint_blocks(bounds, visible_bounds, layout, editor, cx);
         }
-        self.paint_scrollbar(scene, bounds, layout, cx, &editor);
-        scene.pop_layer();
-
-        scene.pop_layer();
+        self.paint_scrollbar(bounds, layout, &editor, cx);
+        cx.scene().pop_layer();
+        cx.scene().pop_layer();
     }
 
     fn rect_for_text_range(
@@ -2873,7 +2846,7 @@ impl Cursor {
         )
     }
 
-    pub fn paint(&self, scene: &mut SceneBuilder, origin: Vector2F, cx: &mut WindowContext) {
+    pub fn paint(&self, origin: Vector2F, cx: &mut WindowContext) {
         let bounds = match self.shape {
             CursorShape::Bar => RectF::new(self.origin + origin, vec2f(2.0, self.line_height)),
             CursorShape::Block | CursorShape::Hollow => RectF::new(
@@ -2888,14 +2861,14 @@ impl Cursor {
 
         //Draw background or border quad
         if matches!(self.shape, CursorShape::Hollow) {
-            scene.push_quad(Quad {
+            cx.scene().push_quad(Quad {
                 bounds,
                 background: None,
-                border: Border::all(1., self.color),
+                border: Border::all(1., self.color).into(),
                 corner_radii: Default::default(),
             });
         } else {
-            scene.push_quad(Quad {
+            cx.scene().push_quad(Quad {
                 bounds,
                 background: Some(self.color),
                 border: Default::default(),
@@ -2904,7 +2877,7 @@ impl Cursor {
         }
 
         if let Some(block_text) = &self.block_text {
-            block_text.paint(scene, self.origin + origin, bounds, self.line_height, cx);
+            block_text.paint(self.origin + origin, bounds, self.line_height, cx);
         }
     }
 
@@ -2929,17 +2902,17 @@ pub struct HighlightedRangeLine {
 }
 
 impl HighlightedRange {
-    pub fn paint(&self, bounds: RectF, scene: &mut SceneBuilder) {
+    pub fn paint(&self, bounds: RectF, cx: &mut WindowContext) {
         if self.lines.len() >= 2 && self.lines[0].start_x > self.lines[1].end_x {
-            self.paint_lines(self.start_y, &self.lines[0..1], bounds, scene);
+            self.paint_lines(self.start_y, &self.lines[0..1], bounds, cx);
             self.paint_lines(
                 self.start_y + self.line_height,
                 &self.lines[1..],
                 bounds,
-                scene,
+                cx,
             );
         } else {
-            self.paint_lines(self.start_y, &self.lines, bounds, scene);
+            self.paint_lines(self.start_y, &self.lines, bounds, cx);
         }
     }
 
@@ -2948,7 +2921,7 @@ impl HighlightedRange {
         start_y: f32,
         lines: &[HighlightedRangeLine],
         bounds: RectF,
-        scene: &mut SceneBuilder,
+        cx: &mut WindowContext,
     ) {
         if lines.is_empty() {
             return;
@@ -3046,7 +3019,7 @@ impl HighlightedRange {
         }
         path.line_to(first_top_right - top_curve_width);
 
-        scene.push_path(path.build(self.color, Some(bounds)));
+        cx.scene().push_path(path.build(self.color, Some(bounds)));
     }
 }
 
@@ -3204,18 +3177,10 @@ mod tests {
                     Point::new(5, 6)..Point::new(6, 0),
                 ]);
             });
-            let mut new_parents = Default::default();
-            let mut notify_views_if_parents_change = Default::default();
-            let mut layout_cx = LayoutContext::new(
-                cx,
-                &mut new_parents,
-                &mut notify_views_if_parents_change,
-                false,
-            );
             element.layout(
                 SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)),
                 editor,
-                &mut layout_cx,
+                cx,
             )
         });
         assert_eq!(state.selections.len(), 1);
@@ -3296,18 +3261,10 @@ mod tests {
                     DisplayPoint::new(10, 0)..DisplayPoint::new(13, 0),
                 ]);
             });
-            let mut new_parents = Default::default();
-            let mut notify_views_if_parents_change = Default::default();
-            let mut layout_cx = LayoutContext::new(
-                cx,
-                &mut new_parents,
-                &mut notify_views_if_parents_change,
-                false,
-            );
             element.layout(
                 SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)),
                 editor,
-                &mut layout_cx,
+                cx,
             )
         });
 
@@ -3363,18 +3320,10 @@ mod tests {
 
         let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
         let (size, mut state) = editor.update(cx, |editor, cx| {
-            let mut new_parents = Default::default();
-            let mut notify_views_if_parents_change = Default::default();
-            let mut layout_cx = LayoutContext::new(
-                cx,
-                &mut new_parents,
-                &mut notify_views_if_parents_change,
-                false,
-            );
             element.layout(
                 SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)),
                 editor,
-                &mut layout_cx,
+                cx,
             )
         });
 
@@ -3389,17 +3338,9 @@ mod tests {
         );
 
         // Don't panic.
-        let mut scene = SceneBuilder::new(1.0);
         let bounds = RectF::new(Default::default(), size);
         editor.update(cx, |editor, cx| {
-            element.paint(
-                &mut scene,
-                bounds,
-                bounds,
-                &mut state,
-                editor,
-                &mut PaintContext::new(cx),
-            );
+            element.paint(bounds, bounds, &mut state, editor, cx);
         });
     }
 
@@ -3567,18 +3508,10 @@ mod tests {
             editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
             editor.set_wrap_width(Some(editor_width), cx);
 
-            let mut new_parents = Default::default();
-            let mut notify_views_if_parents_change = Default::default();
-            let mut layout_cx = LayoutContext::new(
-                cx,
-                &mut new_parents,
-                &mut notify_views_if_parents_change,
-                false,
-            );
             element.layout(
                 SizeConstraint::new(vec2f(editor_width, 500.), vec2f(editor_width, 500.)),
                 editor,
-                &mut layout_cx,
+                cx,
             )
         });
 

crates/editor/src/hover_popover.rs 🔗

@@ -691,15 +691,15 @@ impl InfoPopover {
                         .with_highlights(rendered_content.highlights.clone())
                         .with_custom_runs(
                             rendered_content.region_ranges.clone(),
-                            move |ix, bounds, scene, _| {
+                            move |ix, bounds, cx| {
                                 region_id += 1;
                                 let region = regions[ix].clone();
                                 if let Some(url) = region.link_url {
-                                    scene.push_cursor_region(CursorRegion {
+                                    cx.scene().push_cursor_region(CursorRegion {
                                         bounds,
                                         style: CursorStyle::PointingHand,
                                     });
-                                    scene.push_mouse_region(
+                                    cx.scene().push_mouse_region(
                                         MouseRegion::new::<Self>(view_id, region_id, bounds)
                                             .on_click::<Editor, _>(
                                                 MouseButton::Left,
@@ -708,7 +708,7 @@ impl InfoPopover {
                                     );
                                 }
                                 if region.code {
-                                    scene.push_quad(gpui::Quad {
+                                    cx.scene().push_quad(gpui::Quad {
                                         bounds,
                                         background: Some(code_span_background_color),
                                         border: Default::default(),

crates/gpui/Cargo.toml 🔗

@@ -48,7 +48,8 @@ serde_derive.workspace = true
 serde_json.workspace = true
 smallvec.workspace = true
 smol.workspace = true
-taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "dab541d6104d58e2e10ce90c4a1dad0b703160cd", features = ["flexbox"] }
+taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "4fb530bdd71609bb1d3f76c6a8bde1ba82805d5e" }
+thiserror.workspace = true
 time.workspace = true
 tiny-skia = "0.5"
 usvg = { version = "0.14", features = [] }

crates/gpui/examples/corner_radii.rs 🔗

@@ -42,29 +42,28 @@ impl<V: View> gpui::Element<V> for CornersElement {
         &mut self,
         constraint: gpui::SizeConstraint,
         _: &mut V,
-        _: &mut gpui::LayoutContext<V>,
+        _: &mut gpui::ViewContext<V>,
     ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
         (constraint.max, ())
     }
 
     fn paint(
         &mut self,
-        scene: &mut gpui::SceneBuilder,
         bounds: pathfinder_geometry::rect::RectF,
         _: pathfinder_geometry::rect::RectF,
         _: &mut Self::LayoutState,
         _: &mut V,
-        _: &mut gpui::PaintContext<V>,
+        cx: &mut gpui::ViewContext<V>,
     ) -> Self::PaintState {
-        scene.push_quad(Quad {
+        cx.scene().push_quad(Quad {
             bounds,
             background: Some(Color::white()),
             ..Default::default()
         });
 
-        scene.push_layer(None);
+        cx.scene().push_layer(None);
 
-        scene.push_quad(Quad {
+        cx.scene().push_quad(Quad {
             bounds: RectF::new(vec2f(100., 100.), vec2f(100., 100.)),
             background: Some(Color::red()),
             border: Default::default(),
@@ -74,7 +73,7 @@ impl<V: View> gpui::Element<V> for CornersElement {
             },
         });
 
-        scene.push_quad(Quad {
+        cx.scene().push_quad(Quad {
             bounds: RectF::new(vec2f(200., 100.), vec2f(100., 100.)),
             background: Some(Color::green()),
             border: Default::default(),
@@ -84,7 +83,7 @@ impl<V: View> gpui::Element<V> for CornersElement {
             },
         });
 
-        scene.push_quad(Quad {
+        cx.scene().push_quad(Quad {
             bounds: RectF::new(vec2f(100., 200.), vec2f(100., 100.)),
             background: Some(Color::blue()),
             border: Default::default(),
@@ -94,7 +93,7 @@ impl<V: View> gpui::Element<V> for CornersElement {
             },
         });
 
-        scene.push_quad(Quad {
+        cx.scene().push_quad(Quad {
             bounds: RectF::new(vec2f(200., 200.), vec2f(100., 100.)),
             background: Some(Color::yellow()),
             border: Default::default(),
@@ -104,7 +103,7 @@ impl<V: View> gpui::Element<V> for CornersElement {
             },
         });
 
-        scene.push_shadow(Shadow {
+        cx.scene().push_shadow(Shadow {
             bounds: RectF::new(vec2f(400., 100.), vec2f(100., 100.)),
             corner_radii: gpui::scene::CornerRadii {
                 bottom_right: 20.,
@@ -114,8 +113,8 @@ impl<V: View> gpui::Element<V> for CornersElement {
             color: Color::black(),
         });
 
-        scene.push_layer(None);
-        scene.push_quad(Quad {
+        cx.scene().push_layer(None);
+        cx.scene().push_quad(Quad {
             bounds: RectF::new(vec2f(400., 100.), vec2f(100., 100.)),
             background: Some(Color::red()),
             border: Default::default(),
@@ -125,8 +124,8 @@ impl<V: View> gpui::Element<V> for CornersElement {
             },
         });
 
-        scene.pop_layer();
-        scene.pop_layer();
+        cx.scene().pop_layer();
+        cx.scene().pop_layer();
     }
 
     fn rect_for_text_range(

crates/gpui/examples/text.rs 🔗

@@ -62,12 +62,12 @@ impl gpui::View for TextView {
             },
         )
         .with_highlights(vec![(17..26, underline), (34..40, underline)])
-        .with_custom_runs(vec![(17..26), (34..40)], move |ix, bounds, scene, _| {
-            scene.push_cursor_region(CursorRegion {
+        .with_custom_runs(vec![(17..26), (34..40)], move |ix, bounds, cx| {
+            cx.scene().push_cursor_region(CursorRegion {
                 bounds,
                 style: CursorStyle::PointingHand,
             });
-            scene.push_mouse_region(
+            cx.scene().push_mouse_region(
                 MouseRegion::new::<Self>(view_id, ix, bounds).on_click::<Self, _>(
                     MouseButton::Left,
                     move |_, _, _| {

crates/gpui/playground/Cargo.toml 🔗

@@ -1,26 +0,0 @@
-[package]
-name = "playground"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[[bin]]
-name = "playground"
-path = "src/playground.rs"
-
-[dependencies]
-anyhow.workspace = true
-derive_more.workspace = true
-gpui = { path = ".." }
-log.workspace = true
-playground_macros = { path = "../playground_macros" }
-parking_lot.workspace = true
-refineable.workspace = true
-serde.workspace = true
-simplelog = "0.9"
-smallvec.workspace = true
-taffy = { git = "https://github.com/DioxusLabs/taffy", rev = "dab541d6104d58e2e10ce90c4a1dad0b703160cd", features = ["flexbox"] }
-util = { path = "../../util" }
-
-[dev-dependencies]
-gpui = { path = "..", features = ["test-support"] }

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

@@ -1,108 +0,0 @@
-use crate::{
-    element::{AnyElement, Element, Layout, ParentElement},
-    interactive::{InteractionHandlers, Interactive},
-    layout_context::LayoutContext,
-    paint_context::PaintContext,
-    style::{Style, StyleHelpers, StyleRefinement, Styleable},
-};
-use anyhow::Result;
-use gpui::LayoutId;
-use smallvec::SmallVec;
-
-pub struct Div<V: 'static> {
-    style: StyleRefinement,
-    handlers: InteractionHandlers<V>,
-    children: SmallVec<[AnyElement<V>; 2]>,
-}
-
-pub fn div<V>() -> Div<V> {
-    Div {
-        style: Default::default(),
-        handlers: Default::default(),
-        children: Default::default(),
-    }
-}
-
-impl<V: 'static> Element<V> for Div<V> {
-    type Layout = ();
-
-    fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<Layout<V, ()>>
-    where
-        Self: Sized,
-    {
-        let children = self
-            .children
-            .iter_mut()
-            .map(|child| child.layout(view, cx))
-            .collect::<Result<Vec<LayoutId>>>()?;
-
-        cx.add_layout_node(self.style(), (), children)
-    }
-
-    fn paint(&mut self, view: &mut V, layout: &mut Layout<V, ()>, cx: &mut PaintContext<V>)
-    where
-        Self: Sized,
-    {
-        let style = self.style();
-
-        style.paint_background::<V, Self>(layout, cx);
-        for child in &mut self.children {
-            child.paint(view, cx);
-        }
-    }
-}
-
-impl<V> Styleable for Div<V> {
-    type Style = Style;
-
-    fn declared_style(&mut self) -> &mut StyleRefinement {
-        &mut self.style
-    }
-}
-
-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
-    }
-}
-
-#[test]
-fn test() {
-    // let elt = div().w_auto();
-}
-
-// trait Element<V: 'static> {
-//     type Style;
-
-//     fn layout()
-// }
-
-// trait Stylable<V: 'static>: Element<V> {
-//     type Style;
-
-//     fn with_style(self, style: Self::Style) -> Self;
-// }
-
-// pub struct HoverStyle<S> {
-//     default: S,
-//     hovered: S,
-// }
-
-// struct Hover<V: 'static, C: Stylable<V>> {
-//     child: C,
-//     style: HoverStyle<C::Style>,
-// }
-
-// impl<V: 'static, C: Stylable<V>> Hover<V, C> {
-//     fn new(child: C, style: HoverStyle<C::Style>) -> Self {
-//         Self { child, style }
-//     }
-// }

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

@@ -1,158 +0,0 @@
-use anyhow::Result;
-use derive_more::{Deref, DerefMut};
-use gpui::{geometry::rect::RectF, EngineLayout};
-use smallvec::SmallVec;
-use std::marker::PhantomData;
-use util::ResultExt;
-
-pub use crate::layout_context::LayoutContext;
-pub use crate::paint_context::PaintContext;
-
-type LayoutId = gpui::LayoutId;
-
-pub trait Element<V: 'static>: 'static {
-    type Layout;
-
-    fn layout(
-        &mut self,
-        view: &mut V,
-        cx: &mut LayoutContext<V>,
-    ) -> Result<Layout<V, Self::Layout>>
-    where
-        Self: Sized;
-
-    fn paint(
-        &mut self,
-        view: &mut V,
-        layout: &mut Layout<V, Self::Layout>,
-        cx: &mut PaintContext<V>,
-    ) where
-        Self: Sized;
-
-    fn into_any(self) -> AnyElement<V>
-    where
-        Self: 'static + Sized,
-    {
-        AnyElement(Box::new(ElementState {
-            element: self,
-            layout: None,
-        }))
-    }
-}
-
-/// Used to make ElementState<V, E> into a trait object, so we can wrap it in AnyElement<V>.
-trait ElementStateObject<V> {
-    fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<LayoutId>;
-    fn paint(&mut self, view: &mut V, cx: &mut PaintContext<V>);
-}
-
-/// A wrapper around an element that stores its layout state.
-struct ElementState<V: 'static, E: Element<V>> {
-    element: E,
-    layout: Option<Layout<V, E::Layout>>,
-}
-
-/// We blanket-implement the object-safe ElementStateObject interface to make ElementStates into trait objects
-impl<V, E: Element<V>> ElementStateObject<V> for ElementState<V, E> {
-    fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<LayoutId> {
-        let layout = self.element.layout(view, cx)?;
-        let layout_id = layout.id;
-        self.layout = Some(layout);
-        Ok(layout_id)
-    }
-
-    fn paint(&mut self, view: &mut V, cx: &mut PaintContext<V>) {
-        let layout = self.layout.as_mut().expect("paint called before layout");
-        if layout.engine_layout.is_none() {
-            layout.engine_layout = cx.computed_layout(layout.id).log_err()
-        }
-        self.element.paint(view, layout, cx)
-    }
-}
-
-/// A dynamic element.
-pub struct AnyElement<V>(Box<dyn ElementStateObject<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, cx: &mut PaintContext<V>) {
-        self.0.paint(view, cx)
-    }
-}
-
-#[derive(Deref, DerefMut)]
-pub struct Layout<V, D> {
-    id: LayoutId,
-    engine_layout: Option<EngineLayout>,
-    #[deref]
-    #[deref_mut]
-    element_data: D,
-    view_type: PhantomData<V>,
-}
-
-impl<V: 'static, D> Layout<V, D> {
-    pub fn new(id: LayoutId, element_data: D) -> Self {
-        Self {
-            id,
-            engine_layout: None,
-            element_data: element_data,
-            view_type: PhantomData,
-        }
-    }
-
-    pub fn bounds(&mut self, cx: &mut PaintContext<V>) -> RectF {
-        self.engine_layout(cx).bounds
-    }
-
-    pub fn order(&mut self, cx: &mut PaintContext<V>) -> u32 {
-        self.engine_layout(cx).order
-    }
-
-    fn engine_layout(&mut self, cx: &mut PaintContext<'_, '_, '_, '_, V>) -> &mut EngineLayout {
-        self.engine_layout
-            .get_or_insert_with(|| cx.computed_layout(self.id).log_err().unwrap_or_default())
-    }
-}
-
-impl<V: 'static> Layout<V, Option<AnyElement<V>>> {
-    pub fn paint(&mut self, view: &mut V, cx: &mut PaintContext<V>) {
-        let mut element = self.element_data.take().unwrap();
-        element.paint(view, cx);
-        self.element_data = Some(element);
-    }
-}
-
-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/gpui/playground/src/hoverable.rs 🔗

@@ -1,76 +0,0 @@
-use crate::{
-    element::{Element, Layout},
-    layout_context::LayoutContext,
-    paint_context::PaintContext,
-    style::{StyleRefinement, Styleable},
-};
-use anyhow::Result;
-use gpui::platform::MouseMovedEvent;
-use refineable::Refineable;
-use std::{cell::Cell, marker::PhantomData};
-
-pub struct Hoverable<V: 'static, E: Element<V> + Styleable> {
-    hovered: Cell<bool>,
-    child_style: StyleRefinement,
-    hovered_style: StyleRefinement,
-    child: E,
-    view_type: PhantomData<V>,
-}
-
-pub fn hoverable<V, E: Element<V> + Styleable>(mut child: E) -> Hoverable<V, E> {
-    Hoverable {
-        hovered: Cell::new(false),
-        child_style: child.declared_style().clone(),
-        hovered_style: Default::default(),
-        child,
-        view_type: PhantomData,
-    }
-}
-
-impl<V, E: Element<V> + Styleable> Styleable for Hoverable<V, E> {
-    type Style = E::Style;
-
-    fn declared_style(&mut self) -> &mut crate::style::StyleRefinement {
-        self.child.declared_style()
-    }
-}
-
-impl<V: 'static, E: Element<V> + Styleable> Element<V> for Hoverable<V, E> {
-    type Layout = E::Layout;
-
-    fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<Layout<V, Self::Layout>>
-    where
-        Self: Sized,
-    {
-        self.child.layout(view, cx)
-    }
-
-    fn paint(
-        &mut self,
-        view: &mut V,
-        layout: &mut Layout<V, Self::Layout>,
-        cx: &mut PaintContext<V>,
-    ) where
-        Self: Sized,
-    {
-        if self.hovered.get() {
-            // If hovered, refine the child's style with this element's style.
-            self.child.declared_style().refine(&self.hovered_style);
-        } else {
-            // Otherwise, set the child's style back to its original style.
-            *self.child.declared_style() = self.child_style.clone();
-        }
-
-        let bounds = layout.bounds(cx);
-        let order = layout.order(cx);
-        self.hovered.set(bounds.contains_point(cx.mouse_position()));
-        let was_hovered = self.hovered.clone();
-        cx.on_event(order, move |view, event: &MouseMovedEvent, cx| {
-            let is_hovered = bounds.contains_point(event.position);
-            if is_hovered != was_hovered.get() {
-                was_hovered.set(is_hovered);
-                cx.repaint();
-            }
-        });
-    }
-}

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

@@ -1,34 +0,0 @@
-use gpui::{platform::MouseMovedEvent, EventContext};
-use smallvec::SmallVec;
-use std::rc::Rc;
-
-pub trait Interactive<V: 'static> {
-    fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V>;
-
-    fn on_mouse_move<H>(mut self, handler: H) -> Self
-    where
-        H: 'static + Fn(&mut V, &MouseMovedEvent, bool, &mut EventContext<V>),
-        Self: Sized,
-    {
-        self.interaction_handlers()
-            .mouse_moved
-            .push(Rc::new(move |view, event, hit_test, cx| {
-                handler(view, event, hit_test, cx);
-                cx.bubble
-            }));
-        self
-    }
-}
-
-pub struct InteractionHandlers<V: 'static> {
-    mouse_moved:
-        SmallVec<[Rc<dyn Fn(&mut V, &MouseMovedEvent, bool, &mut EventContext<V>) -> bool>; 2]>,
-}
-
-impl<V> Default for InteractionHandlers<V> {
-    fn default() -> Self {
-        Self {
-            mouse_moved: Default::default(),
-        }
-    }
-}

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

@@ -1,54 +0,0 @@
-use anyhow::{anyhow, Result};
-use derive_more::{Deref, DerefMut};
-pub use gpui::LayoutContext as LegacyLayoutContext;
-use gpui::{RenderContext, ViewContext};
-pub use taffy::tree::NodeId;
-
-use crate::{element::Layout, style::Style};
-
-#[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<D>(
-        &mut self,
-        style: Style,
-        element_data: D,
-        children: impl IntoIterator<Item = NodeId>,
-    ) -> Result<Layout<V, D>> {
-        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(Layout::new(id, element_data))
-    }
-}

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

@@ -1,71 +0,0 @@
-use anyhow::{anyhow, Result};
-use derive_more::{Deref, DerefMut};
-use gpui::{scene::EventHandler, EngineLayout, EventContext, LayoutId, RenderContext, ViewContext};
-pub use gpui::{LayoutContext, PaintContext as LegacyPaintContext};
-use std::{any::TypeId, rc::Rc};
-pub use taffy::tree::NodeId;
-
-#[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, V> RenderContext<'a, 'b, V> for PaintContext<'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> 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 ViewContext<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<EngineLayout> {
-        self.layout_engine()
-            .ok_or_else(|| anyhow!("no layout engine present"))?
-            .computed_layout(layout_id)
-    }
-}

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

@@ -1,83 +0,0 @@
-#![allow(dead_code, unused_variables)]
-use crate::{color::black, style::StyleHelpers};
-use element::Element;
-use gpui::{
-    geometry::{rect::RectF, vector::vec2f},
-    platform::WindowOptions,
-};
-use log::LevelFilter;
-use simplelog::SimpleLogger;
-use themes::{rose_pine, ThemeColors};
-use view::view;
-
-mod adapter;
-mod color;
-mod components;
-mod div;
-mod element;
-mod hoverable;
-mod interactive;
-mod layout_context;
-mod paint_context;
-mod style;
-mod text;
-mod themes;
-mod view;
-
-fn main() {
-    SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
-
-    gpui::App::new(()).unwrap().run(|cx| {
-        cx.add_window(
-            WindowOptions {
-                bounds: gpui::platform::WindowBounds::Fixed(RectF::new(
-                    vec2f(0., 0.),
-                    vec2f(400., 300.),
-                )),
-                center: true,
-                ..Default::default()
-            },
-            |_| view(|_| playground(&rose_pine::moon())),
-        );
-        cx.platform().activate(true);
-    });
-}
-
-fn playground<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
-    use div::div;
-
-    div()
-        .text_color(black())
-        .h_full()
-        .w_1_2()
-        .fill(theme.success(0.5))
-    // .hover()
-    // .fill(theme.error(0.5))
-    // .child(button().label("Hello").click(|_, _, _| println!("click!")))
-}
-
-//     todo!()
-//     // column()
-//     // .size(auto())
-//     // .fill(theme.base(0.5))
-//     // .text_color(theme.text(0.5))
-//     // .child(title_bar(theme))
-//     // .child(stage(theme))
-//     // .child(status_bar(theme))
-// }
-
-// fn title_bar<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
-//     row()
-//         .fill(theme.base(0.2))
-//         .justify(0.)
-//         .width(auto())
-//         .child(text("Zed Playground"))
-// }
-
-// fn stage<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
-//     row().fill(theme.surface(0.9))
-// }
-
-// fn status_bar<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
-//     row().fill(theme.surface(0.1))
-// }

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

@@ -1,286 +0,0 @@
-use crate::{
-    color::Hsla,
-    element::{Element, Layout},
-    paint_context::PaintContext,
-};
-use gpui::{
-    fonts::TextStyleRefinement,
-    geometry::{
-        AbsoluteLength, DefiniteLength, Edges, EdgesRefinement, Length, Point, PointRefinement,
-        Size, SizeRefinement,
-    },
-};
-use playground_macros::styleable_helpers;
-use refineable::Refineable;
-pub use taffy::style::{
-    AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
-    Overflow, Position,
-};
-
-#[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, E: Element<V>>(
-        &self,
-        layout: &mut Layout<V, E::Layout>,
-        cx: &mut PaintContext<V>,
-    ) {
-        let bounds = layout.bounds(cx);
-        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(),
-            });
-        }
-    }
-}
-
-impl Default for Style {
-    fn default() -> Self {
-        Style {
-            display: Display::DEFAULT,
-            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)]
-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::Refineable;
-
-    fn declared_style(&mut self) -> &mut playground::style::StyleRefinement;
-
-    fn style(&mut self) -> playground::style::Style {
-        let mut style = playground::style::Style::default();
-        style.refine(self.declared_style());
-        style
-    }
-}
-
-// 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;
-use crate as playground; // Macro invocation references this crate as playground.
-pub trait StyleHelpers: Styleable<Style = Style> {
-    styleable_helpers!();
-
-    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/gpui/playground/src/text.rs 🔗

@@ -1,151 +0,0 @@
-use crate::{
-    element::{Element, IntoElement, Layout},
-    layout_context::LayoutContext,
-    paint_context::PaintContext,
-};
-use anyhow::Result;
-use gpui::text_layout::LineLayout;
-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 Layout = Arc<Mutex<Option<TextLayout>>>;
-
-    fn layout(
-        &mut self,
-        view: &mut V,
-        cx: &mut LayoutContext<V>,
-    ) -> Result<Layout<V, Self::Layout>> {
-        // let rem_size = cx.rem_pixels();
-        // let fonts = cx.platform().fonts();
-        // let text_style = cx.text_style();
-        // let line_height = cx.font_cache().line_height(text_style.font_size);
-        // let layout_engine = cx.layout_engine().expect("no layout engine present");
-        // let text = self.text.clone();
-        // let layout = Arc::new(Mutex::new(None));
-
-        // let style: Style = Style::default().refined(&self.metadata.style);
-        // let node_id = layout_engine.add_measured_node(style.to_taffy(rem_size), {
-        //     let layout = layout.clone();
-        //     move |params| {
-        //         let line_layout = fonts.layout_line(
-        //             text.as_ref(),
-        //             text_style.font_size,
-        //             &[(text.len(), text_style.to_run())],
-        //         );
-
-        //         let size = Size {
-        //             width: line_layout.width,
-        //             height: line_height,
-        //         };
-
-        //         layout.lock().replace(TextLayout {
-        //             line_layout: Arc::new(line_layout),
-        //             line_height,
-        //         });
-
-        //         size
-        //     }
-        // })?;
-
-        // Ok((node_id, layout))
-        todo!()
-    }
-
-    fn paint<'a>(
-        &mut self,
-        view: &mut V,
-        layout: &mut Layout<V, Self::Layout>,
-        cx: &mut PaintContext<V>,
-    ) {
-        // ) {
-        //     let element_layout_lock = layout.from_element.lock();
-        //     let element_layout = element_layout_lock
-        //         .as_ref()
-        //         .expect("layout has not been performed");
-        //     let line_layout = element_layout.line_layout.clone();
-        //     let line_height = element_layout.line_height;
-        //     drop(element_layout_lock);
-
-        //     let text_style = cx.text_style();
-        //     let line =
-        //         gpui::text_layout::Line::new(line_layout, &[(self.text.len(), text_style.to_run())]);
-        //     line.paint(
-        //         cx.scene,
-        //         layout.from_engine.bounds.origin(),
-        //         layout.from_engine.bounds,
-        //         line_height,
-        //         cx.legacy_cx,
-        //     );
-        todo!()
-    }
-}
-
-pub struct TextLayout {
-    line_layout: Arc<LineLayout>,
-    line_height: f32,
-}
-
-pub enum ArcCow<'a, T: ?Sized> {
-    Borrowed(&'a T),
-    Owned(Arc<T>),
-}
-
-impl<'a, T: ?Sized> Clone for ArcCow<'a, T> {
-    fn clone(&self) -> Self {
-        match self {
-            Self::Borrowed(borrowed) => Self::Borrowed(borrowed),
-            Self::Owned(owned) => Self::Owned(owned.clone()),
-        }
-    }
-}
-
-impl<'a, T: ?Sized> From<&'a T> for ArcCow<'a, T> {
-    fn from(s: &'a T) -> Self {
-        Self::Borrowed(s)
-    }
-}
-
-impl<T> From<Arc<T>> for ArcCow<'_, T> {
-    fn from(s: Arc<T>) -> Self {
-        Self::Owned(s)
-    }
-}
-
-impl From<String> for ArcCow<'_, str> {
-    fn from(value: String) -> Self {
-        Self::Owned(value.into())
-    }
-}
-
-impl<T: ?Sized> std::ops::Deref for ArcCow<'_, T> {
-    type Target = T;
-
-    fn deref(&self) -> &Self::Target {
-        match self {
-            ArcCow::Borrowed(s) => s,
-            ArcCow::Owned(s) => s.as_ref(),
-        }
-    }
-}
-
-impl<T: ?Sized> AsRef<T> for ArcCow<'_, T> {
-    fn as_ref(&self) -> &T {
-        match self {
-            ArcCow::Borrowed(borrowed) => borrowed,
-            ArcCow::Owned(owned) => owned.as_ref(),
-        }
-    }
-}

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

@@ -1,84 +0,0 @@
-use crate::color::{Hsla, Lerp};
-use std::ops::Range;
-
-pub mod rose_pine;
-
-pub struct ThemeColors {
-    pub base: Range<Hsla>,
-    pub surface: Range<Hsla>,
-    pub overlay: Range<Hsla>,
-    pub muted: Range<Hsla>,
-    pub subtle: Range<Hsla>,
-    pub text: Range<Hsla>,
-    pub highlight_low: Range<Hsla>,
-    pub highlight_med: Range<Hsla>,
-    pub highlight_high: Range<Hsla>,
-    pub success: Range<Hsla>,
-    pub warning: Range<Hsla>,
-    pub error: Range<Hsla>,
-    pub inserted: Range<Hsla>,
-    pub deleted: Range<Hsla>,
-    pub modified: Range<Hsla>,
-}
-
-impl ThemeColors {
-    pub fn base(&self, level: f32) -> Hsla {
-        self.base.lerp(level)
-    }
-
-    pub fn surface(&self, level: f32) -> Hsla {
-        self.surface.lerp(level)
-    }
-
-    pub fn overlay(&self, level: f32) -> Hsla {
-        self.overlay.lerp(level)
-    }
-
-    pub fn muted(&self, level: f32) -> Hsla {
-        self.muted.lerp(level)
-    }
-
-    pub fn subtle(&self, level: f32) -> Hsla {
-        self.subtle.lerp(level)
-    }
-
-    pub fn text(&self, level: f32) -> Hsla {
-        self.text.lerp(level)
-    }
-
-    pub fn highlight_low(&self, level: f32) -> Hsla {
-        self.highlight_low.lerp(level)
-    }
-
-    pub fn highlight_med(&self, level: f32) -> Hsla {
-        self.highlight_med.lerp(level)
-    }
-
-    pub fn highlight_high(&self, level: f32) -> Hsla {
-        self.highlight_high.lerp(level)
-    }
-
-    pub fn success(&self, level: f32) -> Hsla {
-        self.success.lerp(level)
-    }
-
-    pub fn warning(&self, level: f32) -> Hsla {
-        self.warning.lerp(level)
-    }
-
-    pub fn error(&self, level: f32) -> Hsla {
-        self.error.lerp(level)
-    }
-
-    pub fn inserted(&self, level: f32) -> Hsla {
-        self.inserted.lerp(level)
-    }
-
-    pub fn deleted(&self, level: f32) -> Hsla {
-        self.deleted.lerp(level)
-    }
-
-    pub fn modified(&self, level: f32) -> Hsla {
-        self.modified.lerp(level)
-    }
-}

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

@@ -1,133 +0,0 @@
-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/gpui/playground_macros/src/styleable_helpers.rs 🔗

@@ -1,147 +0,0 @@
-use proc_macro::TokenStream;
-use proc_macro2::TokenStream as TokenStream2;
-use quote::{format_ident, quote};
-use syn::{
-    parse::{Parse, ParseStream, Result},
-    parse_macro_input,
-};
-
-struct StyleableMacroInput;
-
-impl Parse for StyleableMacroInput {
-    fn parse(_input: ParseStream) -> Result<Self> {
-        Ok(StyleableMacroInput)
-    }
-}
-
-pub fn styleable_helpers(input: TokenStream) -> TokenStream {
-    let _ = parse_macro_input!(input as StyleableMacroInput);
-    let methods = generate_methods();
-    let output = quote! {
-        #(#methods)*
-    };
-    output.into()
-}
-
-fn generate_methods() -> Vec<TokenStream2> {
-    let mut methods = Vec::new();
-
-    for (prefix, auto_allowed, fields) in tailwind_prefixes() {
-        for (suffix, length_tokens) in tailwind_lengths() {
-            if !auto_allowed && suffix == "auto" {
-                // Conditional to skip "auto"
-                continue;
-            }
-
-            let method_name = format_ident!("{}_{}", prefix, suffix);
-            let field_assignments = fields
-                .iter()
-                .map(|field_tokens| {
-                    quote! {
-                        style.#field_tokens = Some(gpui::geometry::#length_tokens);
-                    }
-                })
-                .collect::<Vec<_>>();
-
-            let method = quote! {
-                fn #method_name(mut self) -> Self where Self: std::marker::Sized {
-                    let mut style = self.declared_style();
-                    #(#field_assignments)*
-                    self
-                }
-            };
-
-            methods.push(method);
-        }
-    }
-
-    methods
-}
-
-fn tailwind_lengths() -> Vec<(&'static str, TokenStream2)> {
-    vec![
-        ("0", quote! { pixels(0.) }),
-        ("1", quote! { rems(0.25) }),
-        ("2", quote! { rems(0.5) }),
-        ("3", quote! { rems(0.75) }),
-        ("4", quote! { rems(1.) }),
-        ("5", quote! { rems(1.25) }),
-        ("6", quote! { rems(1.5) }),
-        ("8", quote! { rems(2.0) }),
-        ("10", quote! { rems(2.5) }),
-        ("12", quote! { rems(3.) }),
-        ("16", quote! { rems(4.) }),
-        ("20", quote! { rems(5.) }),
-        ("24", quote! { rems(6.) }),
-        ("32", quote! { rems(8.) }),
-        ("40", quote! { rems(10.) }),
-        ("48", quote! { rems(12.) }),
-        ("56", quote! { rems(14.) }),
-        ("64", quote! { rems(16.) }),
-        ("72", quote! { rems(18.) }),
-        ("80", quote! { rems(20.) }),
-        ("96", quote! { rems(24.) }),
-        ("auto", quote! { auto() }),
-        ("px", quote! { pixels(1.) }),
-        ("full", quote! { relative(1.) }),
-        ("1_2", quote! { relative(0.5) }),
-        ("1_3", quote! { relative(1./3.) }),
-        ("2_3", quote! { relative(2./3.) }),
-        ("1_4", quote! { relative(0.25) }),
-        ("2_4", quote! { relative(0.5) }),
-        ("3_4", quote! { relative(0.75) }),
-        ("1_5", quote! { relative(0.2) }),
-        ("2_5", quote! { relative(0.4) }),
-        ("3_5", quote! { relative(0.6) }),
-        ("4_5", quote! { relative(0.8) }),
-        ("1_6", quote! { relative(1./6.) }),
-        ("5_6", quote! { relative(5./6.) }),
-        ("1_12", quote! { relative(1./12.) }),
-        // ("screen_50", quote! { DefiniteLength::Vh(50.0) }),
-        // ("screen_75", quote! { DefiniteLength::Vh(75.0) }),
-        // ("screen", quote! { DefiniteLength::Vh(100.0) }),
-    ]
-}
-
-fn tailwind_prefixes() -> Vec<(&'static str, bool, Vec<TokenStream2>)> {
-    vec![
-        ("w", true, vec![quote! { size.width }]),
-        ("h", true, vec![quote! { size.height }]),
-        ("min_w", false, vec![quote! { min_size.width }]),
-        ("min_h", false, vec![quote! { min_size.height }]),
-        ("max_w", false, vec![quote! { max_size.width }]),
-        ("max_h", false, vec![quote! { max_size.height }]),
-        (
-            "m",
-            true,
-            vec![quote! { margin.top }, quote! { margin.bottom }],
-        ),
-        ("mt", true, vec![quote! { margin.top }]),
-        ("mb", true, vec![quote! { margin.bottom }]),
-        (
-            "mx",
-            true,
-            vec![quote! { margin.left }, quote! { margin.right }],
-        ),
-        ("ml", true, vec![quote! { margin.left }]),
-        ("mr", true, vec![quote! { margin.right }]),
-        (
-            "p",
-            false,
-            vec![quote! { padding.top }, quote! { padding.bottom }],
-        ),
-        ("pt", false, vec![quote! { padding.top }]),
-        ("pb", false, vec![quote! { padding.bottom }]),
-        (
-            "px",
-            false,
-            vec![quote! { padding.left }, quote! { padding.right }],
-        ),
-        ("pl", false, vec![quote! { padding.left }]),
-        ("pr", false, vec![quote! { padding.right }]),
-        ("top", true, vec![quote! { inset.top }]),
-        ("bottom", true, vec![quote! { inset.bottom }]),
-        ("left", true, vec![quote! { inset.left }]),
-        ("right", true, vec![quote! { inset.right }]),
-    ]
-}

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

@@ -1,99 +0,0 @@
-use proc_macro::TokenStream;
-use proc_macro2::TokenStream as TokenStream2;
-use quote::{format_ident, quote};
-use syn::{parse_macro_input, FnArg, ItemFn, PatType};
-
-pub fn tailwind_lengths(_attr: TokenStream, item: TokenStream) -> TokenStream {
-    let input_function = parse_macro_input!(item as ItemFn);
-
-    let visibility = &input_function.vis;
-    let function_signature = input_function.sig.clone();
-    let function_body = input_function.block;
-    let where_clause = &function_signature.generics.where_clause;
-
-    let argument_name = match function_signature.inputs.iter().nth(1) {
-        Some(FnArg::Typed(PatType { pat, .. })) => pat,
-        _ => panic!("Couldn't find the second argument in the function signature"),
-    };
-
-    let mut output_functions = TokenStream2::new();
-
-    for (length, value) in fixed_lengths() {
-        let function_name = format_ident!("{}{}", function_signature.ident, length);
-        output_functions.extend(quote! {
-            #visibility fn #function_name(mut self) -> Self #where_clause {
-                let #argument_name = #value.into();
-                #function_body
-            }
-        });
-    }
-
-    output_functions.into()
-}
-
-fn fixed_lengths() -> Vec<(&'static str, TokenStream2)> {
-    vec![
-        ("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.rs 🔗

@@ -10,7 +10,7 @@ mod window_input_handler;
 use crate::{
     elements::{AnyElement, AnyRootElement, RootElement},
     executor::{self, Task},
-    fonts::TextStyle,
+    image_cache::ImageCache,
     json,
     keymap_matcher::{self, Binding, KeymapContext, KeymapMatcher, Keystroke, MatchResult},
     platform::{
@@ -28,6 +28,7 @@ use collections::{hash_map::Entry, BTreeMap, HashMap, HashSet, VecDeque};
 use derive_more::Deref;
 pub use menu::*;
 use parking_lot::Mutex;
+use pathfinder_geometry::rect::RectF;
 use platform::Event;
 use postage::oneshot;
 #[cfg(any(test, feature = "test-support"))]
@@ -51,8 +52,12 @@ use std::{
 };
 #[cfg(any(test, feature = "test-support"))]
 pub use test_app_context::{ContextHandle, TestAppContext};
-use util::ResultExt;
+use util::{
+    http::{self, HttpClient},
+    ResultExt,
+};
 use uuid::Uuid;
+pub use window::MeasureParams;
 use window_input_handler::WindowInputHandler;
 
 pub trait Entity: 'static {
@@ -154,12 +159,14 @@ impl App {
         let platform = platform::current::platform();
         let foreground = Rc::new(executor::Foreground::platform(platform.dispatcher())?);
         let foreground_platform = platform::current::foreground_platform(foreground.clone());
+        let http_client = http::client();
         let app = Self(Rc::new(RefCell::new(AppContext::new(
             foreground,
             Arc::new(executor::Background::new()),
             platform.clone(),
             foreground_platform.clone(),
             Arc::new(FontCache::new(platform.fonts())),
+            http_client,
             Default::default(),
             asset_source,
         ))));
@@ -456,6 +463,7 @@ pub struct AppContext {
     pub asset_cache: Arc<AssetCache>,
     font_system: Arc<dyn FontSystem>,
     pub font_cache: Arc<FontCache>,
+    pub image_cache: Arc<ImageCache>,
     action_deserializers: HashMap<&'static str, (TypeId, DeserializeActionCallback)>,
     capture_actions: HashMap<TypeId, HashMap<TypeId, Vec<Box<ActionCallback>>>>,
     // Entity Types -> { Action Types -> Action Handlers }
@@ -499,6 +507,7 @@ impl AppContext {
         platform: Arc<dyn platform::Platform>,
         foreground_platform: Rc<dyn platform::ForegroundPlatform>,
         font_cache: Arc<FontCache>,
+        http_client: Arc<dyn HttpClient>,
         ref_counts: RefCounts,
         asset_source: impl AssetSource,
     ) -> Self {
@@ -517,6 +526,7 @@ impl AppContext {
             platform,
             foreground_platform,
             font_cache,
+            image_cache: Arc::new(ImageCache::new(http_client)),
             asset_cache: Arc::new(AssetCache::new(asset_source)),
             action_deserializers: Default::default(),
             capture_actions: Default::default(),
@@ -1898,7 +1908,6 @@ impl AppContext {
 
     fn handle_repaint_window_effect(&mut self, window: AnyWindowHandle) {
         self.update_window(window, |cx| {
-            cx.layout(false).log_err();
             if let Some(scene) = cx.paint().log_err() {
                 cx.window.platform_window.present_scene(scene);
             }
@@ -3345,10 +3354,6 @@ impl<'a, 'b, V: 'static> ViewContext<'a, 'b, V> {
         self.element_state::<Tag, T>(element_id, T::default())
     }
 
-    pub fn rem_pixels(&self) -> f32 {
-        16.
-    }
-
     pub fn default_element_state_dynamic<T: 'static + Default>(
         &mut self,
         tag: TypeTag,
@@ -3356,136 +3361,6 @@ impl<'a, 'b, V: 'static> ViewContext<'a, 'b, V> {
     ) -> ElementStateHandle<T> {
         self.element_state_dynamic::<T>(tag, element_id, T::default())
     }
-}
-
-impl<V: View> ViewContext<'_, '_, V> {
-    pub fn emit(&mut self, event: V::Event) {
-        self.window_context
-            .pending_effects
-            .push_back(Effect::Event {
-                entity_id: self.view_id,
-                payload: Box::new(event),
-            });
-    }
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
-pub struct TypeTag {
-    tag: TypeId,
-    composed: Option<TypeId>,
-    #[cfg(debug_assertions)]
-    tag_type_name: &'static str,
-}
-
-impl TypeTag {
-    pub fn new<Tag: 'static>() -> Self {
-        Self {
-            tag: TypeId::of::<Tag>(),
-            composed: None,
-            #[cfg(debug_assertions)]
-            tag_type_name: std::any::type_name::<Tag>(),
-        }
-    }
-
-    pub fn dynamic(tag: TypeId, #[cfg(debug_assertions)] type_name: &'static str) -> Self {
-        Self {
-            tag,
-            composed: None,
-            #[cfg(debug_assertions)]
-            tag_type_name: type_name,
-        }
-    }
-
-    pub fn compose(mut self, other: TypeTag) -> Self {
-        self.composed = Some(other.tag);
-        self
-    }
-
-    #[cfg(debug_assertions)]
-    pub(crate) fn type_name(&self) -> &'static str {
-        self.tag_type_name
-    }
-}
-
-impl<V> BorrowAppContext for ViewContext<'_, '_, V> {
-    fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
-        BorrowAppContext::read_with(&*self.window_context, f)
-    }
-
-    fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
-        BorrowAppContext::update(&mut *self.window_context, f)
-    }
-}
-
-impl<V> BorrowWindowContext for ViewContext<'_, '_, V> {
-    type Result<T> = T;
-
-    fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, window: AnyWindowHandle, f: F) -> T {
-        BorrowWindowContext::read_window(&*self.window_context, window, f)
-    }
-
-    fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
-    where
-        F: FnOnce(&WindowContext) -> Option<T>,
-    {
-        BorrowWindowContext::read_window_optional(&*self.window_context, window, f)
-    }
-
-    fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
-        &mut self,
-        window: AnyWindowHandle,
-        f: F,
-    ) -> T {
-        BorrowWindowContext::update_window(&mut *self.window_context, window, f)
-    }
-
-    fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
-    where
-        F: FnOnce(&mut WindowContext) -> Option<T>,
-    {
-        BorrowWindowContext::update_window_optional(&mut *self.window_context, window, f)
-    }
-}
-
-/// Methods shared by both LayoutContext and PaintContext
-///
-/// It's that PaintContext should be implemented in terms of layout context and
-/// deref to it, in which case we wouldn't need this.
-pub trait RenderContext<'a, 'b, V> {
-    fn text_style(&self) -> TextStyle;
-    fn push_text_style(&mut self, style: TextStyle);
-    fn pop_text_style(&mut self);
-    fn as_view_context(&mut self) -> &mut ViewContext<'a, 'b, V>;
-}
-
-pub struct LayoutContext<'a, 'b, 'c, V> {
-    // Nathan: Making this is public while I work on playground.
-    pub view_context: &'c mut ViewContext<'a, 'b, V>,
-    new_parents: &'c mut HashMap<usize, usize>,
-    views_to_notify_if_ancestors_change: &'c mut HashMap<usize, SmallVec<[usize; 2]>>,
-    text_style_stack: Vec<TextStyle>,
-    pub refreshing: bool,
-}
-
-impl<'a, 'b, 'c, V> LayoutContext<'a, 'b, 'c, V> {
-    pub fn new(
-        view_context: &'c mut ViewContext<'a, 'b, V>,
-        new_parents: &'c mut HashMap<usize, usize>,
-        views_to_notify_if_ancestors_change: &'c mut HashMap<usize, SmallVec<[usize; 2]>>,
-        refreshing: bool,
-    ) -> Self {
-        Self {
-            view_context,
-            new_parents,
-            views_to_notify_if_ancestors_change,
-            text_style_stack: Vec::new(),
-            refreshing,
-        }
-    }
-
-    pub fn view_context(&mut self) -> &mut ViewContext<'a, 'b, V> {
-        self.view_context
-    }
 
     /// Return keystrokes that would dispatch the given action on the given view.
     pub(crate) fn keystrokes_for_action(
@@ -3523,80 +3398,95 @@ impl<'a, 'b, 'c, V> LayoutContext<'a, 'b, 'c, V> {
 
     fn notify_if_view_ancestors_change(&mut self, view_id: usize) {
         let self_view_id = self.view_id;
-        self.views_to_notify_if_ancestors_change
+        self.window
+            .views_to_notify_if_ancestors_change
             .entry(view_id)
             .or_default()
             .push(self_view_id);
     }
 
-    pub fn with_text_style<F, T>(&mut self, style: TextStyle, f: F) -> T
+    pub fn paint_layer<F, R>(&mut self, clip_bounds: Option<RectF>, f: F) -> R
     where
-        F: FnOnce(&mut Self) -> T,
+        F: FnOnce(&mut Self) -> R,
     {
-        self.push_text_style(style);
+        self.scene().push_layer(clip_bounds);
         let result = f(self);
-        self.pop_text_style();
+        self.scene().pop_layer();
         result
     }
 }
 
-impl<'a, 'b, 'c, V> RenderContext<'a, 'b, V> for LayoutContext<'a, 'b, 'c, V> {
-    fn text_style(&self) -> TextStyle {
-        self.text_style_stack
-            .last()
-            .cloned()
-            .unwrap_or(TextStyle::default(&self.font_cache))
+impl<V: View> ViewContext<'_, '_, V> {
+    pub fn emit(&mut self, event: V::Event) {
+        self.window_context
+            .pending_effects
+            .push_back(Effect::Event {
+                entity_id: self.view_id,
+                payload: Box::new(event),
+            });
     }
+}
 
-    fn push_text_style(&mut self, style: TextStyle) {
-        self.text_style_stack.push(style);
-    }
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct TypeTag {
+    tag: TypeId,
+    composed: Option<TypeId>,
+    #[cfg(debug_assertions)]
+    tag_type_name: &'static str,
+}
 
-    fn pop_text_style(&mut self) {
-        self.text_style_stack.pop();
+impl TypeTag {
+    pub fn new<Tag: 'static>() -> Self {
+        Self {
+            tag: TypeId::of::<Tag>(),
+            composed: None,
+            #[cfg(debug_assertions)]
+            tag_type_name: std::any::type_name::<Tag>(),
+        }
     }
 
-    fn as_view_context(&mut self) -> &mut ViewContext<'a, 'b, V> {
-        &mut self.view_context
+    pub fn dynamic(tag: TypeId, #[cfg(debug_assertions)] type_name: &'static str) -> Self {
+        Self {
+            tag,
+            composed: None,
+            #[cfg(debug_assertions)]
+            tag_type_name: type_name,
+        }
     }
-}
-
-impl<'a, 'b, 'c, V> Deref for LayoutContext<'a, 'b, 'c, V> {
-    type Target = ViewContext<'a, 'b, V>;
 
-    fn deref(&self) -> &Self::Target {
-        &self.view_context
+    pub fn compose(mut self, other: TypeTag) -> Self {
+        self.composed = Some(other.tag);
+        self
     }
-}
 
-impl<V> DerefMut for LayoutContext<'_, '_, '_, V> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.view_context
+    #[cfg(debug_assertions)]
+    pub(crate) fn type_name(&self) -> &'static str {
+        self.tag_type_name
     }
 }
 
-impl<V> BorrowAppContext for LayoutContext<'_, '_, '_, V> {
+impl<V> BorrowAppContext for ViewContext<'_, '_, V> {
     fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
-        BorrowAppContext::read_with(&*self.view_context, f)
+        BorrowAppContext::read_with(&*self.window_context, f)
     }
 
     fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
-        BorrowAppContext::update(&mut *self.view_context, f)
+        BorrowAppContext::update(&mut *self.window_context, f)
     }
 }
 
-impl<V> BorrowWindowContext for LayoutContext<'_, '_, '_, V> {
+impl<V> BorrowWindowContext for ViewContext<'_, '_, V> {
     type Result<T> = T;
 
     fn read_window<T, F: FnOnce(&WindowContext) -> T>(&self, window: AnyWindowHandle, f: F) -> T {
-        BorrowWindowContext::read_window(&*self.view_context, window, f)
+        BorrowWindowContext::read_window(&*self.window_context, window, f)
     }
 
     fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
     where
         F: FnOnce(&WindowContext) -> Option<T>,
     {
-        BorrowWindowContext::read_window_optional(&*self.view_context, window, f)
+        BorrowWindowContext::read_window_optional(&*self.window_context, window, f)
     }
 
     fn update_window<T, F: FnOnce(&mut WindowContext) -> T>(
@@ -3604,105 +3494,14 @@ impl<V> BorrowWindowContext for LayoutContext<'_, '_, '_, V> {
         window: AnyWindowHandle,
         f: F,
     ) -> T {
-        BorrowWindowContext::update_window(&mut *self.view_context, window, f)
-    }
-
-    fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
-    where
-        F: FnOnce(&mut WindowContext) -> Option<T>,
-    {
-        BorrowWindowContext::update_window_optional(&mut *self.view_context, window, f)
-    }
-}
-
-pub struct PaintContext<'a, 'b, 'c, V> {
-    pub view_context: &'c mut ViewContext<'a, 'b, V>,
-    text_style_stack: Vec<TextStyle>,
-}
-
-impl<'a, 'b, 'c, V> PaintContext<'a, 'b, 'c, V> {
-    pub fn new(view_context: &'c mut ViewContext<'a, 'b, V>) -> Self {
-        Self {
-            view_context,
-            text_style_stack: Vec::new(),
-        }
-    }
-}
-
-impl<'a, 'b, 'c, V> RenderContext<'a, 'b, V> for PaintContext<'a, 'b, 'c, V> {
-    fn text_style(&self) -> TextStyle {
-        self.text_style_stack
-            .last()
-            .cloned()
-            .unwrap_or(TextStyle::default(&self.font_cache))
-    }
-
-    fn push_text_style(&mut self, style: TextStyle) {
-        self.text_style_stack.push(style);
-    }
-
-    fn pop_text_style(&mut self) {
-        self.text_style_stack.pop();
-    }
-
-    fn as_view_context(&mut self) -> &mut ViewContext<'a, 'b, V> {
-        &mut self.view_context
-    }
-}
-
-impl<'a, 'b, 'c, V> Deref for PaintContext<'a, 'b, 'c, V> {
-    type Target = ViewContext<'a, 'b, V>;
-
-    fn deref(&self) -> &Self::Target {
-        &self.view_context
-    }
-}
-
-impl<V> DerefMut for PaintContext<'_, '_, '_, V> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.view_context
-    }
-}
-
-impl<V> BorrowAppContext for PaintContext<'_, '_, '_, V> {
-    fn read_with<T, F: FnOnce(&AppContext) -> T>(&self, f: F) -> T {
-        BorrowAppContext::read_with(&*self.view_context, f)
-    }
-
-    fn update<T, F: FnOnce(&mut AppContext) -> T>(&mut self, f: F) -> T {
-        BorrowAppContext::update(&mut *self.view_context, f)
-    }
-}
-
-impl<V> BorrowWindowContext for PaintContext<'_, '_, '_, V> {
-    type Result<T> = T;
-
-    fn read_window<T, F>(&self, window: AnyWindowHandle, f: F) -> Self::Result<T>
-    where
-        F: FnOnce(&WindowContext) -> T,
-    {
-        BorrowWindowContext::read_window(self.view_context, window, f)
-    }
-
-    fn read_window_optional<T, F>(&self, window: AnyWindowHandle, f: F) -> Option<T>
-    where
-        F: FnOnce(&WindowContext) -> Option<T>,
-    {
-        BorrowWindowContext::read_window_optional(self.view_context, window, f)
-    }
-
-    fn update_window<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Self::Result<T>
-    where
-        F: FnOnce(&mut WindowContext) -> T,
-    {
-        BorrowWindowContext::update_window(self.view_context, window, f)
+        BorrowWindowContext::update_window(&mut *self.window_context, window, f)
     }
 
     fn update_window_optional<T, F>(&mut self, window: AnyWindowHandle, f: F) -> Option<T>
     where
         F: FnOnce(&mut WindowContext) -> Option<T>,
     {
-        BorrowWindowContext::update_window_optional(self.view_context, window, f)
+        BorrowWindowContext::update_window_optional(&mut *self.window_context, window, f)
     }
 }
 
@@ -6555,32 +6354,21 @@ mod tests {
         view_1.update(cx, |_, cx| {
             view_2.update(cx, |_, cx| {
                 // Sanity check
-                let mut new_parents = Default::default();
-                let mut notify_views_if_parents_change = Default::default();
-                let mut layout_cx = LayoutContext::new(
-                    cx,
-                    &mut new_parents,
-                    &mut notify_views_if_parents_change,
-                    false,
-                );
                 assert_eq!(
-                    layout_cx
-                        .keystrokes_for_action(view_1_id, &Action1)
+                    cx.keystrokes_for_action(view_1_id, &Action1)
                         .unwrap()
                         .as_slice(),
                     &[Keystroke::parse("a").unwrap()]
                 );
                 assert_eq!(
-                    layout_cx
-                        .keystrokes_for_action(view_2.id(), &Action2)
+                    cx.keystrokes_for_action(view_2.id(), &Action2)
                         .unwrap()
                         .as_slice(),
                     &[Keystroke::parse("b").unwrap()]
                 );
-                assert_eq!(layout_cx.keystrokes_for_action(view_1.id(), &Action3), None);
+                assert_eq!(cx.keystrokes_for_action(view_1.id(), &Action3), None);
                 assert_eq!(
-                    layout_cx
-                        .keystrokes_for_action(view_2.id(), &Action3)
+                    cx.keystrokes_for_action(view_2.id(), &Action3)
                         .unwrap()
                         .as_slice(),
                     &[Keystroke::parse("c").unwrap()]
@@ -6589,21 +6377,17 @@ mod tests {
                 // The 'a' keystroke propagates up the view tree from view_2
                 // to view_1. The action, Action1, is handled by view_1.
                 assert_eq!(
-                    layout_cx
-                        .keystrokes_for_action(view_2.id(), &Action1)
+                    cx.keystrokes_for_action(view_2.id(), &Action1)
                         .unwrap()
                         .as_slice(),
                     &[Keystroke::parse("a").unwrap()]
                 );
 
                 // Actions that are handled below the current view don't have bindings
-                assert_eq!(layout_cx.keystrokes_for_action(view_1_id, &Action2), None);
+                assert_eq!(cx.keystrokes_for_action(view_1_id, &Action2), None);
 
                 // Actions that are handled in other branches of the tree should not have a binding
-                assert_eq!(
-                    layout_cx.keystrokes_for_action(view_2.id(), &GlobalAction),
-                    None
-                );
+                assert_eq!(cx.keystrokes_for_action(view_2.id(), &GlobalAction), None);
             });
         });
 

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

@@ -57,6 +57,7 @@ impl TestAppContext {
             platform,
             foreground_platform.clone(),
             font_cache,
+            util::http::FakeHttpClient::with_404_response(),
             RefCounts::new(leak_detector),
             (),
         );

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

@@ -1,5 +1,6 @@
 use crate::{
     elements::AnyRootElement,
+    fonts::{TextStyle, TextStyleRefinement},
     geometry::{rect::RectF, Size},
     json::ToJson,
     keymap_matcher::{Binding, KeymapContext, Keystroke, MatchResult},
@@ -15,9 +16,8 @@ use crate::{
     text_layout::TextLayoutCache,
     util::post_inc,
     Action, AnyView, AnyViewHandle, AnyWindowHandle, AppContext, BorrowAppContext,
-    BorrowWindowContext, Effect, Element, Entity, Handle, LayoutContext, MouseRegion,
-    MouseRegionId, PaintContext, SceneBuilder, Subscription, View, ViewContext, ViewHandle,
-    WindowInvalidation,
+    BorrowWindowContext, Effect, Element, Entity, Handle, MouseRegion, MouseRegionId, SceneBuilder,
+    Subscription, View, ViewContext, ViewHandle, WindowInvalidation,
 };
 use anyhow::{anyhow, bail, Result};
 use collections::{HashMap, HashSet};
@@ -30,7 +30,7 @@ use sqlez::{
     statement::Statement,
 };
 use std::{
-    any::TypeId,
+    any::{type_name, Any, TypeId},
     mem,
     ops::{Deref, DerefMut, Range, Sub},
 };
@@ -50,20 +50,28 @@ pub struct Window {
     pub(crate) parents: HashMap<usize, usize>,
     pub(crate) is_active: bool,
     pub(crate) is_fullscreen: bool,
+    inspector_enabled: bool,
     pub(crate) invalidation: Option<WindowInvalidation>,
     pub(crate) platform_window: Box<dyn platform::Window>,
     pub(crate) rendered_views: HashMap<usize, Box<dyn AnyRootElement>>,
+    scene: SceneBuilder,
+    pub(crate) text_style_stack: Vec<TextStyle>,
+    pub(crate) theme_stack: Vec<Box<dyn Any>>,
+    pub(crate) new_parents: HashMap<usize, usize>,
+    pub(crate) views_to_notify_if_ancestors_change: HashMap<usize, SmallVec<[usize; 2]>>,
     titlebar_height: f32,
     appearance: Appearance,
     cursor_regions: Vec<CursorRegion>,
     mouse_regions: Vec<(MouseRegion, usize)>,
     event_handlers: Vec<EventHandler>,
     last_mouse_moved_event: Option<Event>,
+    last_mouse_position: Vector2F,
+    pressed_buttons: HashSet<MouseButton>,
     pub(crate) hovered_region_ids: Vec<MouseRegionId>,
     pub(crate) clicked_region_ids: Vec<MouseRegionId>,
     pub(crate) clicked_region: Option<(MouseRegionId, MouseButton)>,
-    mouse_position: Vector2F,
     text_layout_cache: TextLayoutCache,
+    refreshing: bool,
 }
 
 impl Window {
@@ -87,19 +95,27 @@ impl Window {
             is_active: false,
             invalidation: None,
             is_fullscreen: false,
+            inspector_enabled: false,
             platform_window,
             rendered_views: Default::default(),
+            scene: SceneBuilder::new(),
+            text_style_stack: Vec::new(),
+            theme_stack: Vec::new(),
+            new_parents: HashMap::default(),
+            views_to_notify_if_ancestors_change: HashMap::default(),
             cursor_regions: Default::default(),
             mouse_regions: Default::default(),
             event_handlers: Default::default(),
             text_layout_cache: TextLayoutCache::new(cx.font_system.clone()),
             last_mouse_moved_event: None,
+            last_mouse_position: Vector2F::zero(),
+            pressed_buttons: Default::default(),
             hovered_region_ids: Default::default(),
             clicked_region_ids: Default::default(),
             clicked_region: None,
-            mouse_position: vec2f(0., 0.),
             titlebar_height,
             appearance,
+            refreshing: false,
         };
 
         let mut window_context = WindowContext::mutable(cx, &mut window, handle);
@@ -226,6 +242,26 @@ impl<'a> WindowContext<'a> {
             .push_back(Effect::RepaintWindow { window });
     }
 
+    pub fn scene(&mut self) -> &mut SceneBuilder {
+        &mut self.window.scene
+    }
+
+    pub fn enable_inspector(&mut self) {
+        self.window.inspector_enabled = true;
+    }
+
+    pub fn is_inspector_enabled(&self) -> bool {
+        self.window.inspector_enabled
+    }
+
+    pub fn is_mouse_down(&self, button: MouseButton) -> bool {
+        self.window.pressed_buttons.contains(&button)
+    }
+
+    pub fn rem_size(&self) -> f32 {
+        16.
+    }
+
     pub fn layout_engine(&mut self) -> Option<&mut LayoutEngine> {
         self.window.layout_engines.last_mut()
     }
@@ -259,7 +295,11 @@ impl<'a> WindowContext<'a> {
     }
 
     pub fn mouse_position(&self) -> Vector2F {
-        self.window.mouse_position
+        self.window.platform_window.mouse_position()
+    }
+
+    pub fn refreshing(&self) -> bool {
+        self.window.refreshing
     }
 
     pub fn text_layout_cache(&self) -> &TextLayoutCache {
@@ -507,7 +547,9 @@ impl<'a> WindowContext<'a> {
     }
 
     pub(crate) fn dispatch_event(&mut self, event: Event, event_reused: bool) -> bool {
-        self.dispatch_to_new_event_handlers(&event);
+        if !event_reused {
+            self.dispatch_event_2(&event);
+        }
 
         let mut mouse_events = SmallVec::<[_; 2]>::new();
         let mut notified_views: HashSet<usize> = Default::default();
@@ -576,7 +618,7 @@ impl<'a> WindowContext<'a> {
                 // Synthesize one last drag event to end the drag
                 mouse_events.push(MouseEvent::Drag(MouseDrag {
                     region: Default::default(),
-                    prev_mouse_position: self.window.mouse_position,
+                    prev_mouse_position: self.window.last_mouse_position,
                     platform_event: MouseMovedEvent {
                         position: e.position,
                         pressed_button: Some(e.button),
@@ -630,14 +672,14 @@ impl<'a> WindowContext<'a> {
                     if pressed_button.is_some() {
                         mouse_events.push(MouseEvent::Drag(MouseDrag {
                             region: Default::default(),
-                            prev_mouse_position: self.window.mouse_position,
+                            prev_mouse_position: self.window.last_mouse_position,
                             platform_event: e.clone(),
                             end: false,
                         }));
                     } else if let Some((_, clicked_button)) = self.window.clicked_region {
                         mouse_events.push(MouseEvent::Drag(MouseDrag {
                             region: Default::default(),
-                            prev_mouse_position: self.window.mouse_position,
+                            prev_mouse_position: self.window.last_mouse_position,
                             platform_event: e.clone(),
                             end: true,
                         }));
@@ -697,7 +739,7 @@ impl<'a> WindowContext<'a> {
         }
 
         if let Some(position) = event.position() {
-            self.window.mouse_position = position;
+            self.window.last_mouse_position = position;
         }
 
         // 2. Dispatch mouse events on regions
@@ -711,7 +753,7 @@ impl<'a> WindowContext<'a> {
             match &mouse_event {
                 MouseEvent::Hover(_) => {
                     let mut highest_z_index = None;
-                    let mouse_position = self.window.mouse_position.clone();
+                    let mouse_position = self.mouse_position();
                     let window = &mut *self.window;
                     let prev_hovered_regions = mem::take(&mut window.hovered_region_ids);
                     for (region, z_index) in window.mouse_regions.iter().rev() {
@@ -756,7 +798,7 @@ impl<'a> WindowContext<'a> {
 
                 MouseEvent::Down(_) | MouseEvent::Up(_) => {
                     for (region, _) in self.window.mouse_regions.iter().rev() {
-                        if region.bounds.contains_point(self.window.mouse_position) {
+                        if region.bounds.contains_point(self.mouse_position()) {
                             valid_regions.push(region.clone());
                             if region.notify_on_click {
                                 notified_views.insert(region.id().view_id());
@@ -783,10 +825,7 @@ impl<'a> WindowContext<'a> {
                         // Find regions which still overlap with the mouse since the last MouseDown happened
                         for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
                             if clicked_region_ids.contains(&mouse_region.id()) {
-                                if mouse_region
-                                    .bounds
-                                    .contains_point(self.window.mouse_position)
-                                {
+                                if mouse_region.bounds.contains_point(self.mouse_position()) {
                                     valid_regions.push(mouse_region.clone());
                                 } else {
                                     // Let the view know that it hasn't been clicked anymore
@@ -813,10 +852,7 @@ impl<'a> WindowContext<'a> {
                 | MouseEvent::ClickOut(_) => {
                     for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
                         // NOT contains
-                        if !mouse_region
-                            .bounds
-                            .contains_point(self.window.mouse_position)
-                        {
+                        if !mouse_region.bounds.contains_point(self.mouse_position()) {
                             valid_regions.push(mouse_region.clone());
                         }
                     }
@@ -825,10 +861,7 @@ impl<'a> WindowContext<'a> {
                 _ => {
                     for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
                         // Contains
-                        if mouse_region
-                            .bounds
-                            .contains_point(self.window.mouse_position)
-                        {
+                        if mouse_region.bounds.contains_point(self.mouse_position()) {
                             valid_regions.push(mouse_region.clone());
                         }
                     }
@@ -892,12 +925,24 @@ impl<'a> WindowContext<'a> {
         any_event_handled
     }
 
-    fn dispatch_to_new_event_handlers(&mut self, event: &Event) {
+    fn dispatch_event_2(&mut self, event: &Event) {
+        match event {
+            Event::MouseDown(event) => {
+                self.window.pressed_buttons.insert(event.button);
+            }
+            Event::MouseUp(event) => {
+                self.window.pressed_buttons.remove(&event.button);
+            }
+            _ => {}
+        }
+
         if let Some(mouse_event) = event.mouse_event() {
             let event_handlers = self.window.take_event_handlers();
             for event_handler in event_handlers.iter().rev() {
                 if event_handler.event_type == mouse_event.type_id() {
-                    (event_handler.handler)(mouse_event, self);
+                    if !(event_handler.handler)(mouse_event, self) {
+                        break;
+                    }
                 }
             }
             self.window.event_handlers = event_handlers;
@@ -1000,21 +1045,17 @@ impl<'a> WindowContext<'a> {
 
         let mut rendered_root = self.window.rendered_views.remove(&root_view_id).unwrap();
 
-        let mut new_parents = HashMap::default();
-        let mut views_to_notify_if_ancestors_change = HashMap::default();
-        rendered_root.layout(
-            SizeConstraint::new(window_size, window_size),
-            &mut new_parents,
-            &mut views_to_notify_if_ancestors_change,
-            refreshing,
-            self,
-        )?;
+        self.window.refreshing = refreshing;
+        rendered_root.layout(SizeConstraint::strict(window_size), self)?;
+        self.window.refreshing = false;
 
+        let views_to_notify_if_ancestors_change =
+            mem::take(&mut self.window.views_to_notify_if_ancestors_change);
         for (view_id, view_ids_to_notify) in views_to_notify_if_ancestors_change {
             let mut current_view_id = view_id;
             loop {
                 let old_parent_id = self.window.parents.get(&current_view_id);
-                let new_parent_id = new_parents.get(&current_view_id);
+                let new_parent_id = self.window.new_parents.get(&current_view_id);
                 if old_parent_id.is_none() && new_parent_id.is_none() {
                     break;
                 } else if old_parent_id == new_parent_id {
@@ -1029,6 +1070,7 @@ impl<'a> WindowContext<'a> {
             }
         }
 
+        let new_parents = mem::take(&mut self.window.new_parents);
         let old_parents = mem::replace(&mut self.window.parents, new_parents);
         self.window
             .rendered_views
@@ -1043,9 +1085,7 @@ impl<'a> WindowContext<'a> {
         let root_view_id = self.window.root_view().id();
         let mut rendered_root = self.window.rendered_views.remove(&root_view_id).unwrap();
 
-        let mut scene_builder = SceneBuilder::new(scale_factor);
         rendered_root.paint(
-            &mut scene_builder,
             Vector2F::zero(),
             RectF::from_points(Vector2F::zero(), window_size),
             self,
@@ -1055,7 +1095,7 @@ impl<'a> WindowContext<'a> {
             .insert(root_view_id, rendered_root);
 
         self.window.text_layout_cache.finish_frame();
-        let mut scene = scene_builder.build();
+        let mut scene = self.window.scene.build(scale_factor);
         self.window.cursor_regions = scene.cursor_regions();
         self.window.mouse_regions = scene.mouse_regions();
         self.window.event_handlers = scene.take_event_handlers();
@@ -1203,6 +1243,10 @@ impl<'a> WindowContext<'a> {
         self.window.platform_window.bounds()
     }
 
+    pub fn titlebar_height(&self) -> f32 {
+        self.window.titlebar_height
+    }
+
     pub fn window_appearance(&self) -> Appearance {
         self.window.appearance
     }
@@ -1274,6 +1318,43 @@ impl<'a> WindowContext<'a> {
         };
         handle
     }
+
+    pub fn text_style(&self) -> TextStyle {
+        self.window
+            .text_style_stack
+            .last()
+            .cloned()
+            .unwrap_or(TextStyle::default(&self.font_cache))
+    }
+
+    pub fn push_text_style(&mut self, refinement: &TextStyleRefinement) -> Result<()> {
+        let mut style = self.text_style();
+        style.refine(refinement, self.font_cache())?;
+        self.window.text_style_stack.push(style);
+        Ok(())
+    }
+
+    pub fn pop_text_style(&mut self) {
+        self.window.text_style_stack.pop();
+    }
+
+    pub fn theme<T: 'static>(&self) -> &T {
+        self.window
+            .theme_stack
+            .iter()
+            .rev()
+            .find_map(|theme| theme.downcast_ref())
+            .ok_or_else(|| anyhow!("no theme provided of type {}", type_name::<T>()))
+            .unwrap()
+    }
+
+    pub fn push_theme<T: 'static>(&mut self, theme: T) {
+        self.window.theme_stack.push(Box::new(theme));
+    }
+
+    pub fn pop_theme(&mut self) {
+        self.window.theme_stack.pop();
+    }
 }
 
 #[derive(Default)]
@@ -1289,9 +1370,12 @@ impl LayoutEngine {
     where
         C: IntoIterator<Item = LayoutId>,
     {
-        Ok(self
-            .0
-            .new_with_children(style, &children.into_iter().collect::<Vec<_>>())?)
+        let children = children.into_iter().collect::<Vec<_>>();
+        if children.is_empty() {
+            Ok(self.0.new_leaf(style)?)
+        } else {
+            Ok(self.0.new_with_children(style, &children)?)
+        }
     }
 
     pub fn add_measured_node<F>(&mut self, style: LayoutStyle, measure: F) -> Result<LayoutId>
@@ -1314,8 +1398,8 @@ impl LayoutEngine {
         Ok(())
     }
 
-    pub fn computed_layout(&mut self, node: LayoutId) -> Result<EngineLayout> {
-        Ok(self.0.layout(node)?.into())
+    pub fn computed_layout(&mut self, node: LayoutId) -> Result<Layout> {
+        Ok(Layout::from(self.0.layout(node)?))
     }
 }
 
@@ -1339,7 +1423,7 @@ where
 }
 
 #[derive(Debug, Clone, Default)]
-pub struct EngineLayout {
+pub struct Layout {
     pub bounds: RectF,
     pub order: u32,
 }
@@ -1349,7 +1433,7 @@ pub struct MeasureParams {
     pub available_space: Size<AvailableSpace>,
 }
 
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 pub enum AvailableSpace {
     /// The amount of space available is the specified number of pixels
     Pixels(f32),
@@ -1375,7 +1459,7 @@ impl From<taffy::prelude::AvailableSpace> for AvailableSpace {
     }
 }
 
-impl From<&taffy::tree::Layout> for EngineLayout {
+impl From<&taffy::tree::Layout> for Layout {
     fn from(value: &taffy::tree::Layout) -> Self {
         Self {
             bounds: RectF::new(
@@ -1592,18 +1676,13 @@ impl<V: 'static> Element<V> for ChildView {
         &mut self,
         constraint: SizeConstraint,
         _: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         if let Some(mut rendered_view) = cx.window.rendered_views.remove(&self.view_id) {
-            cx.new_parents.insert(self.view_id, cx.view_id());
+            let parent_id = cx.view_id();
+            cx.window.new_parents.insert(self.view_id, parent_id);
             let size = rendered_view
-                .layout(
-                    constraint,
-                    cx.new_parents,
-                    cx.views_to_notify_if_ancestors_change,
-                    cx.refreshing,
-                    cx.view_context,
-                )
+                .layout(constraint, cx)
                 .log_err()
                 .unwrap_or(Vector2F::zero());
             cx.window.rendered_views.insert(self.view_id, rendered_view);
@@ -1620,16 +1699,15 @@ impl<V: 'static> Element<V> for ChildView {
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         _: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) {
         if let Some(mut rendered_view) = cx.window.rendered_views.remove(&self.view_id) {
             rendered_view
-                .paint(scene, bounds.origin(), visible_bounds, cx)
+                .paint(bounds.origin(), visible_bounds, cx)
                 .log_err();
             cx.window.rendered_views.insert(self.view_id, rendered_view);
         } else {

crates/gpui/src/elements.rs 🔗

@@ -34,14 +34,12 @@ use crate::{
         rect::RectF,
         vector::{vec2f, Vector2F},
     },
-    json, Action, Entity, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, TypeTag, View,
-    ViewContext, WeakViewHandle, WindowContext,
+    json, Action, Entity, SizeConstraint, TypeTag, View, ViewContext, WeakViewHandle,
+    WindowContext,
 };
 use anyhow::{anyhow, Result};
-use collections::HashMap;
 use core::panic;
 use json::ToJson;
-use smallvec::SmallVec;
 use std::{
     any::{type_name, Any},
     borrow::Cow,
@@ -61,17 +59,16 @@ pub trait Element<V: 'static>: 'static {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState);
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         layout: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState;
 
     fn rect_for_text_range(
@@ -262,16 +259,15 @@ trait AnyElementState<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Vector2F;
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         origin: Vector2F,
         visible_bounds: RectF,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     );
 
     fn rect_for_text_range(
@@ -314,7 +310,7 @@ impl<V, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Vector2F {
         let result;
         *self = match mem::take(self) {
@@ -348,11 +344,10 @@ impl<V, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         origin: Vector2F,
         visible_bounds: RectF,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) {
         *self = match mem::take(self) {
             ElementState::PostLayout {
@@ -362,14 +357,7 @@ impl<V, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
                 mut layout,
             } => {
                 let bounds = RectF::new(origin, size);
-                let paint = element.paint(
-                    scene,
-                    bounds,
-                    visible_bounds,
-                    &mut layout,
-                    view,
-                    &mut PaintContext::new(cx),
-                );
+                let paint = element.paint(bounds, visible_bounds, &mut layout, view, cx);
                 ElementState::PostPaint {
                     element,
                     constraint,
@@ -387,14 +375,7 @@ impl<V, E: Element<V>> AnyElementState<V> for ElementState<V, E> {
                 ..
             } => {
                 let bounds = RectF::new(origin, bounds.size());
-                let paint = element.paint(
-                    scene,
-                    bounds,
-                    visible_bounds,
-                    &mut layout,
-                    view,
-                    &mut PaintContext::new(cx),
-                );
+                let paint = element.paint(bounds, visible_bounds, &mut layout, view, cx);
                 ElementState::PostPaint {
                     element,
                     constraint,
@@ -517,20 +498,19 @@ impl<V> AnyElement<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Vector2F {
         self.state.layout(constraint, view, cx)
     }
 
     pub fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         origin: Vector2F,
         visible_bounds: RectF,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) {
-        self.state.paint(scene, origin, visible_bounds, view, cx);
+        self.state.paint(origin, visible_bounds, view, cx);
     }
 
     pub fn rect_for_text_range(
@@ -578,7 +558,7 @@ impl<V: 'static> Element<V> for AnyElement<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let size = self.layout(constraint, view, cx);
         (size, ())
@@ -586,14 +566,13 @@ impl<V: 'static> Element<V> for AnyElement<V> {
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
-        self.paint(scene, bounds.origin(), visible_bounds, view, cx);
+        self.paint(bounds.origin(), visible_bounds, view, cx);
     }
 
     fn rect_for_text_range(
@@ -646,17 +625,9 @@ impl<V> RootElement<V> {
 }
 
 pub trait AnyRootElement {
-    fn layout(
-        &mut self,
-        constraint: SizeConstraint,
-        new_parents: &mut HashMap<usize, usize>,
-        views_to_notify_if_ancestors_change: &mut HashMap<usize, SmallVec<[usize; 2]>>,
-        refreshing: bool,
-        cx: &mut WindowContext,
-    ) -> Result<Vector2F>;
+    fn layout(&mut self, constraint: SizeConstraint, cx: &mut WindowContext) -> Result<Vector2F>;
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         origin: Vector2F,
         visible_bounds: RectF,
         cx: &mut WindowContext,
@@ -671,32 +642,16 @@ pub trait AnyRootElement {
 }
 
 impl<V: View> AnyRootElement for RootElement<V> {
-    fn layout(
-        &mut self,
-        constraint: SizeConstraint,
-        new_parents: &mut HashMap<usize, usize>,
-        views_to_notify_if_ancestors_change: &mut HashMap<usize, SmallVec<[usize; 2]>>,
-        refreshing: bool,
-        cx: &mut WindowContext,
-    ) -> Result<Vector2F> {
+    fn layout(&mut self, constraint: SizeConstraint, cx: &mut WindowContext) -> Result<Vector2F> {
         let view = self
             .view
             .upgrade(cx)
             .ok_or_else(|| anyhow!("layout called on a root element for a dropped view"))?;
-        view.update(cx, |view, cx| {
-            let mut cx = LayoutContext::new(
-                cx,
-                new_parents,
-                views_to_notify_if_ancestors_change,
-                refreshing,
-            );
-            Ok(self.element.layout(constraint, view, &mut cx))
-        })
+        view.update(cx, |view, cx| Ok(self.element.layout(constraint, view, cx)))
     }
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         origin: Vector2F,
         visible_bounds: RectF,
         cx: &mut WindowContext,
@@ -707,9 +662,7 @@ impl<V: View> AnyRootElement for RootElement<V> {
             .ok_or_else(|| anyhow!("paint called on a root element for a dropped view"))?;
 
         view.update(cx, |view, cx| {
-            let mut cx = PaintContext::new(cx);
-            self.element
-                .paint(scene, origin, visible_bounds, view, &mut cx);
+            self.element.paint(origin, visible_bounds, view, cx);
             Ok(())
         })
     }

crates/gpui/src/elements/align.rs 🔗

@@ -1,7 +1,6 @@
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
-    json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
-    ViewContext,
+    json, AnyElement, Element, SizeConstraint, ViewContext,
 };
 use json::ToJson;
 
@@ -49,7 +48,7 @@ impl<V: 'static> Element<V> for Align<V> {
         &mut self,
         mut constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let mut size = constraint.max;
         constraint.min = Vector2F::zero();
@@ -65,12 +64,11 @@ impl<V: 'static> Element<V> for Align<V> {
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         let my_center = bounds.size() / 2.;
         let my_target = my_center + my_center * self.alignment;
@@ -79,7 +77,6 @@ impl<V: 'static> Element<V> for Align<V> {
         let child_target = child_center + child_center * self.alignment;
 
         self.child.paint(
-            scene,
             bounds.origin() - (child_target - my_target),
             visible_bounds,
             view,

crates/gpui/src/elements/canvas.rs 🔗

@@ -3,7 +3,7 @@ use std::marker::PhantomData;
 use super::Element;
 use crate::{
     json::{self, json},
-    PaintContext, SceneBuilder, ViewContext,
+    ViewContext,
 };
 use json::ToJson;
 use pathfinder_geometry::{
@@ -15,7 +15,7 @@ pub struct Canvas<V, F>(F, PhantomData<V>);
 
 impl<V, F> Canvas<V, F>
 where
-    F: FnMut(&mut SceneBuilder, RectF, RectF, &mut V, &mut ViewContext<V>),
+    F: FnMut(RectF, RectF, &mut V, &mut ViewContext<V>),
 {
     pub fn new(f: F) -> Self {
         Self(f, PhantomData)
@@ -24,7 +24,7 @@ where
 
 impl<V: 'static, F> Element<V> for Canvas<V, F>
 where
-    F: 'static + FnMut(&mut SceneBuilder, RectF, RectF, &mut V, &mut ViewContext<V>),
+    F: 'static + FnMut(RectF, RectF, &mut V, &mut ViewContext<V>),
 {
     type LayoutState = ();
     type PaintState = ();
@@ -33,7 +33,7 @@ where
         &mut self,
         constraint: crate::SizeConstraint,
         _: &mut V,
-        _: &mut crate::LayoutContext<V>,
+        _: &mut crate::ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let x = if constraint.max.x().is_finite() {
             constraint.max.x()
@@ -50,14 +50,13 @@ where
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
-        self.0(scene, bounds, visible_bounds, view, cx)
+        self.0(bounds, visible_bounds, view, cx)
     }
 
     fn rect_for_text_range(

crates/gpui/src/elements/clipped.rs 🔗

@@ -3,10 +3,7 @@ use std::ops::Range;
 use pathfinder_geometry::{rect::RectF, vector::Vector2F};
 use serde_json::json;
 
-use crate::{
-    json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
-    ViewContext,
-};
+use crate::{json, AnyElement, Element, SizeConstraint, ViewContext};
 
 pub struct Clipped<V> {
     child: AnyElement<V>,
@@ -26,24 +23,23 @@ impl<V: 'static> Element<V> for Clipped<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         (self.child.layout(constraint, view, cx), ())
     }
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
-        scene.paint_layer(Some(bounds), |scene| {
-            self.child
-                .paint(scene, bounds.origin(), visible_bounds, view, cx)
-        })
+        cx.scene().push_layer(Some(bounds));
+        let state = self.child.paint(bounds.origin(), visible_bounds, view, cx);
+        cx.scene().pop_layer();
+        state
     }
 
     fn rect_for_text_range(

crates/gpui/src/elements/component.rs 🔗

@@ -2,9 +2,7 @@ use std::{any::Any, marker::PhantomData};
 
 use pathfinder_geometry::{rect::RectF, vector::Vector2F};
 
-use crate::{
-    AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, ViewContext,
-};
+use crate::{AnyElement, Element, SizeConstraint, ViewContext};
 
 use super::Empty;
 
@@ -284,14 +282,14 @@ impl<V: 'static, C: StatefulComponent<V> + 'static> Element<V> for ComponentAdap
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         if self.element.is_none() {
             let element = self
                 .component
                 .take()
                 .expect("Component can only be rendered once")
-                .render(view, cx.view_context());
+                .render(view, cx);
             self.element = Some(element);
         }
         let constraint = self.element.as_mut().unwrap().layout(constraint, view, cx);
@@ -300,17 +298,16 @@ impl<V: 'static, C: StatefulComponent<V> + 'static> Element<V> for ComponentAdap
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         self.element
             .as_mut()
             .expect("Layout should always be called before paint")
-            .paint(scene, bounds.origin(), visible_bounds, view, cx)
+            .paint(bounds.origin(), visible_bounds, view, cx)
     }
 
     fn rect_for_text_range(

crates/gpui/src/elements/constrained_box.rs 🔗

@@ -5,8 +5,7 @@ use serde_json::json;
 
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
-    json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
-    ViewContext,
+    json, AnyElement, Element, SizeConstraint, ViewContext,
 };
 
 pub struct ConstrainedBox<V> {
@@ -16,7 +15,7 @@ pub struct ConstrainedBox<V> {
 
 pub enum Constraint<V> {
     Static(SizeConstraint),
-    Dynamic(Box<dyn FnMut(SizeConstraint, &mut V, &mut LayoutContext<V>) -> SizeConstraint>),
+    Dynamic(Box<dyn FnMut(SizeConstraint, &mut V, &mut ViewContext<V>) -> SizeConstraint>),
 }
 
 impl<V> ToJson for Constraint<V> {
@@ -38,8 +37,7 @@ impl<V: 'static> ConstrainedBox<V> {
 
     pub fn dynamically(
         mut self,
-        constraint: impl 'static
-            + FnMut(SizeConstraint, &mut V, &mut LayoutContext<V>) -> SizeConstraint,
+        constraint: impl 'static + FnMut(SizeConstraint, &mut V, &mut ViewContext<V>) -> SizeConstraint,
     ) -> Self {
         self.constraint = Constraint::Dynamic(Box::new(constraint));
         self
@@ -121,7 +119,7 @@ impl<V: 'static> ConstrainedBox<V> {
         &mut self,
         input_constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> SizeConstraint {
         match &mut self.constraint {
             Constraint::Static(constraint) => *constraint,
@@ -140,7 +138,7 @@ impl<V: 'static> Element<V> for ConstrainedBox<V> {
         &mut self,
         mut parent_constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let constraint = self.constraint(parent_constraint, view, cx);
         parent_constraint.min = parent_constraint.min.max(constraint.min);
@@ -152,17 +150,15 @@ impl<V: 'static> Element<V> for ConstrainedBox<V> {
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
-        scene.paint_layer(Some(visible_bounds), |scene| {
-            self.child
-                .paint(scene, bounds.origin(), visible_bounds, view, cx);
-        })
+        cx.scene().push_layer(Some(visible_bounds));
+        self.child.paint(bounds.origin(), visible_bounds, view, cx);
+        cx.scene().pop_layer();
     }
 
     fn rect_for_text_range(

crates/gpui/src/elements/container.rs 🔗

@@ -9,8 +9,8 @@ use crate::{
     },
     json::ToJson,
     platform::CursorStyle,
-    scene::{self, Border, CornerRadii, CursorRegion, Quad},
-    AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, ViewContext,
+    scene::{self, CornerRadii, CursorRegion, Quad},
+    AnyElement, Element, SizeConstraint, ViewContext,
 };
 use schemars::JsonSchema;
 use serde::Deserialize;
@@ -206,6 +206,163 @@ impl<V> Container<V> {
     }
 }
 
+#[derive(Copy, Clone, Debug, Default, JsonSchema)]
+pub struct Border {
+    pub color: Color,
+    pub width: f32,
+    pub overlay: bool,
+    pub top: bool,
+    pub bottom: bool,
+    pub left: bool,
+    pub right: bool,
+}
+
+impl Into<scene::Border> for Border {
+    fn into(self) -> scene::Border {
+        scene::Border {
+            color: self.color,
+            left: if self.left { self.width } else { 0.0 },
+            right: if self.right { self.width } else { 0.0 },
+            top: if self.top { self.width } else { 0.0 },
+            bottom: if self.bottom { self.width } else { 0.0 },
+        }
+    }
+}
+
+impl Border {
+    pub fn new(width: f32, color: Color) -> Self {
+        Self {
+            width,
+            color,
+            overlay: false,
+            top: false,
+            left: false,
+            bottom: false,
+            right: false,
+        }
+    }
+
+    pub fn all(width: f32, color: Color) -> Self {
+        Self {
+            width,
+            color,
+            overlay: false,
+            top: true,
+            left: true,
+            bottom: true,
+            right: true,
+        }
+    }
+
+    pub fn top(width: f32, color: Color) -> Self {
+        let mut border = Self::new(width, color);
+        border.top = true;
+        border
+    }
+
+    pub fn left(width: f32, color: Color) -> Self {
+        let mut border = Self::new(width, color);
+        border.left = true;
+        border
+    }
+
+    pub fn bottom(width: f32, color: Color) -> Self {
+        let mut border = Self::new(width, color);
+        border.bottom = true;
+        border
+    }
+
+    pub fn right(width: f32, color: Color) -> Self {
+        let mut border = Self::new(width, color);
+        border.right = true;
+        border
+    }
+
+    pub fn with_sides(mut self, top: bool, left: bool, bottom: bool, right: bool) -> Self {
+        self.top = top;
+        self.left = left;
+        self.bottom = bottom;
+        self.right = right;
+        self
+    }
+
+    pub fn top_width(&self) -> f32 {
+        if self.top {
+            self.width
+        } else {
+            0.0
+        }
+    }
+
+    pub fn left_width(&self) -> f32 {
+        if self.left {
+            self.width
+        } else {
+            0.0
+        }
+    }
+}
+
+impl<'de> Deserialize<'de> for Border {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        #[derive(Deserialize)]
+        struct BorderData {
+            pub width: f32,
+            pub color: Color,
+            #[serde(default)]
+            pub overlay: bool,
+            #[serde(default)]
+            pub top: bool,
+            #[serde(default)]
+            pub right: bool,
+            #[serde(default)]
+            pub bottom: bool,
+            #[serde(default)]
+            pub left: bool,
+        }
+
+        let data = BorderData::deserialize(deserializer)?;
+        let mut border = Border {
+            width: data.width,
+            color: data.color,
+            overlay: data.overlay,
+            top: data.top,
+            bottom: data.bottom,
+            left: data.left,
+            right: data.right,
+        };
+        if !border.top && !border.bottom && !border.left && !border.right {
+            border.top = true;
+            border.bottom = true;
+            border.left = true;
+            border.right = true;
+        }
+        Ok(border)
+    }
+}
+
+impl ToJson for Border {
+    fn to_json(&self) -> serde_json::Value {
+        let mut value = json!({});
+        if self.top {
+            value["top"] = json!(self.width);
+        }
+        if self.right {
+            value["right"] = json!(self.width);
+        }
+        if self.bottom {
+            value["bottom"] = json!(self.width);
+        }
+        if self.left {
+            value["left"] = json!(self.width);
+        }
+        value
+    }
+}
+
 impl<V: 'static> Element<V> for Container<V> {
     type LayoutState = ();
     type PaintState = ();
@@ -214,7 +371,7 @@ impl<V: 'static> Element<V> for Container<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let mut size_buffer = self.margin_size() + self.padding_size();
         if !self.style.border.overlay {
@@ -230,12 +387,11 @@ impl<V: 'static> Element<V> for Container<V> {
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         let quad_bounds = RectF::from_points(
             bounds.origin() + vec2f(self.style.margin.left, self.style.margin.top),
@@ -243,7 +399,7 @@ impl<V: 'static> Element<V> for Container<V> {
         );
 
         if let Some(shadow) = self.style.shadow.as_ref() {
-            scene.push_shadow(scene::Shadow {
+            cx.scene().push_shadow(scene::Shadow {
                 bounds: quad_bounds + shadow.offset,
                 corner_radii: self.style.corner_radii,
                 sigma: shadow.blur,
@@ -253,7 +409,7 @@ impl<V: 'static> Element<V> for Container<V> {
 
         if let Some(hit_bounds) = quad_bounds.intersection(visible_bounds) {
             if let Some(style) = self.style.cursor {
-                scene.push_cursor_region(CursorRegion {
+                cx.scene().push_cursor_region(CursorRegion {
                     bounds: hit_bounds,
                     style,
                 });
@@ -264,29 +420,28 @@ impl<V: 'static> Element<V> for Container<V> {
             quad_bounds.origin() + vec2f(self.style.padding.left, self.style.padding.top);
 
         if self.style.border.overlay {
-            scene.push_quad(Quad {
+            cx.scene().push_quad(Quad {
                 bounds: quad_bounds,
                 background: self.style.background_color,
                 border: Default::default(),
                 corner_radii: self.style.corner_radii.into(),
             });
 
-            self.child
-                .paint(scene, child_origin, visible_bounds, view, cx);
+            self.child.paint(child_origin, visible_bounds, view, cx);
 
-            scene.push_layer(None);
-            scene.push_quad(Quad {
+            cx.scene().push_layer(None);
+            cx.scene().push_quad(Quad {
                 bounds: quad_bounds,
                 background: self.style.overlay_color,
-                border: self.style.border,
+                border: self.style.border.into(),
                 corner_radii: self.style.corner_radii.into(),
             });
-            scene.pop_layer();
+            cx.scene().pop_layer();
         } else {
-            scene.push_quad(Quad {
+            cx.scene().push_quad(Quad {
                 bounds: quad_bounds,
                 background: self.style.background_color,
-                border: self.style.border,
+                border: self.style.border.into(),
                 corner_radii: self.style.corner_radii.into(),
             });
 
@@ -295,18 +450,17 @@ impl<V: 'static> Element<V> for Container<V> {
                     self.style.border.left_width(),
                     self.style.border.top_width(),
                 );
-            self.child
-                .paint(scene, child_origin, visible_bounds, view, cx);
+            self.child.paint(child_origin, visible_bounds, view, cx);
 
             if self.style.overlay_color.is_some() {
-                scene.push_layer(None);
-                scene.push_quad(Quad {
+                cx.scene().push_layer(None);
+                cx.scene().push_quad(Quad {
                     bounds: quad_bounds,
                     background: self.style.overlay_color,
                     border: Default::default(),
                     corner_radii: self.style.corner_radii.into(),
                 });
-                scene.pop_layer();
+                cx.scene().pop_layer();
             }
         }
     }

crates/gpui/src/elements/empty.rs 🔗

@@ -6,7 +6,7 @@ use crate::{
         vector::{vec2f, Vector2F},
     },
     json::{json, ToJson},
-    LayoutContext, PaintContext, SceneBuilder, ViewContext,
+    ViewContext,
 };
 use crate::{Element, SizeConstraint};
 
@@ -34,7 +34,7 @@ impl<V: 'static> Element<V> for Empty {
         &mut self,
         constraint: SizeConstraint,
         _: &mut V,
-        _: &mut LayoutContext<V>,
+        _: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let x = if constraint.max.x().is_finite() && !self.collapsed {
             constraint.max.x()
@@ -52,12 +52,11 @@ impl<V: 'static> Element<V> for Empty {
 
     fn paint(
         &mut self,
-        _: &mut SceneBuilder,
         _: RectF,
         _: RectF,
         _: &mut Self::LayoutState,
         _: &mut V,
-        _: &mut PaintContext<V>,
+        _: &mut ViewContext<V>,
     ) -> Self::PaintState {
     }
 

crates/gpui/src/elements/expanded.rs 🔗

@@ -2,8 +2,7 @@ use std::ops::Range;
 
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
-    json, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
-    ViewContext,
+    json, AnyElement, Element, SizeConstraint, ViewContext,
 };
 use serde_json::json;
 
@@ -43,7 +42,7 @@ impl<V: 'static> Element<V> for Expanded<V> {
         &mut self,
         mut constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         if self.full_width {
             constraint.min.set_x(constraint.max.x());
@@ -57,15 +56,13 @@ impl<V: 'static> Element<V> for Expanded<V> {
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
-        self.child
-            .paint(scene, bounds.origin(), visible_bounds, view, cx);
+        self.child.paint(bounds.origin(), visible_bounds, view, cx);
     }
 
     fn rect_for_text_range(

crates/gpui/src/elements/flex.rs 🔗

@@ -2,8 +2,7 @@ use std::{any::Any, cell::Cell, f32::INFINITY, ops::Range, rc::Rc};
 
 use crate::{
     json::{self, ToJson, Value},
-    AnyElement, Axis, Element, ElementStateHandle, LayoutContext, PaintContext, SceneBuilder,
-    SizeConstraint, Vector2FExt, ViewContext,
+    AnyElement, Axis, Element, ElementStateHandle, SizeConstraint, Vector2FExt, ViewContext,
 };
 use pathfinder_geometry::{
     rect::RectF,
@@ -85,7 +84,7 @@ impl<V: 'static> Flex<V> {
         remaining_flex: &mut f32,
         cross_axis_max: &mut f32,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) {
         let cross_axis = self.axis.invert();
         for child in self.children.iter_mut() {
@@ -136,7 +135,7 @@ impl<V: 'static> Element<V> for Flex<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let mut total_flex = None;
         let mut fixed_space = self.children.len().saturating_sub(1) as f32 * self.spacing;
@@ -225,7 +224,7 @@ impl<V: 'static> Element<V> for Flex<V> {
         }
 
         if let Some(scroll_state) = self.scroll_state.as_ref() {
-            scroll_state.0.update(cx.view_context(), |scroll_state, _| {
+            scroll_state.0.update(cx, |scroll_state, _| {
                 if let Some(scroll_to) = scroll_state.scroll_to.take() {
                     let visible_start = scroll_state.scroll_position.get();
                     let visible_end = visible_start + size.along(self.axis);
@@ -260,26 +259,25 @@ impl<V: 'static> Element<V> for Flex<V> {
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         remaining_space: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
 
         let mut remaining_space = *remaining_space;
         let overflowing = remaining_space < 0.;
         if overflowing {
-            scene.push_layer(Some(visible_bounds));
+            cx.scene().push_layer(Some(visible_bounds));
         }
 
-        if let Some(scroll_state) = &self.scroll_state {
-            scene.push_mouse_region(
-                crate::MouseRegion::new::<Self>(scroll_state.1, 0, bounds)
+        if let Some((scroll_state, id)) = &self.scroll_state {
+            let scroll_state = scroll_state.read(cx).clone();
+            cx.scene().push_mouse_region(
+                crate::MouseRegion::new::<Self>(*id, 0, bounds)
                     .on_scroll({
-                        let scroll_state = scroll_state.0.read(cx).clone();
                         let axis = self.axis;
                         move |e, _: &mut V, cx| {
                             if remaining_space < 0. {
@@ -358,7 +356,7 @@ impl<V: 'static> Element<V> for Flex<V> {
                 aligned_child_origin
             };
 
-            child.paint(scene, aligned_child_origin, visible_bounds, view, cx);
+            child.paint(aligned_child_origin, visible_bounds, view, cx);
 
             match self.axis {
                 Axis::Horizontal => child_origin += vec2f(child.size().x() + self.spacing, 0.0),
@@ -367,7 +365,7 @@ impl<V: 'static> Element<V> for Flex<V> {
         }
 
         if overflowing {
-            scene.pop_layer();
+            cx.scene().pop_layer();
         }
     }
 
@@ -443,7 +441,7 @@ impl<V: 'static> Element<V> for FlexItem<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let size = self.child.layout(constraint, view, cx);
         (size, ())
@@ -451,15 +449,13 @@ impl<V: 'static> Element<V> for FlexItem<V> {
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
-        self.child
-            .paint(scene, bounds.origin(), visible_bounds, view, cx)
+        self.child.paint(bounds.origin(), visible_bounds, view, cx)
     }
 
     fn rect_for_text_range(

crates/gpui/src/elements/hook.rs 🔗

@@ -3,7 +3,7 @@ use std::ops::Range;
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
     json::json,
-    AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, ViewContext,
+    AnyElement, Element, SizeConstraint, ViewContext,
 };
 
 pub struct Hook<V> {
@@ -36,7 +36,7 @@ impl<V: 'static> Element<V> for Hook<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let size = self.child.layout(constraint, view, cx);
         if let Some(handler) = self.after_layout.as_mut() {
@@ -47,15 +47,13 @@ impl<V: 'static> Element<V> for Hook<V> {
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) {
-        self.child
-            .paint(scene, bounds.origin(), visible_bounds, view, cx);
+        self.child.paint(bounds.origin(), visible_bounds, view, cx);
     }
 
     fn rect_for_text_range(

crates/gpui/src/elements/image.rs 🔗

@@ -1,12 +1,11 @@
-use super::constrain_size_preserving_aspect_ratio;
+use super::{constrain_size_preserving_aspect_ratio, Border};
 use crate::{
     geometry::{
         rect::RectF,
         vector::{vec2f, Vector2F},
     },
     json::{json, ToJson},
-    scene, Border, Element, ImageData, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
-    ViewContext,
+    scene, Element, ImageData, SizeConstraint, ViewContext,
 };
 use schemars::JsonSchema;
 use serde::Deserialize;
@@ -65,7 +64,7 @@ impl<V: 'static> Element<V> for Image {
         &mut self,
         constraint: SizeConstraint,
         _: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let data = match &self.source {
             ImageSource::Path(path) => match cx.asset_cache.png(path) {
@@ -92,17 +91,16 @@ impl<V: 'static> Element<V> for Image {
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         _: RectF,
         layout: &mut Self::LayoutState,
         _: &mut V,
-        _: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         if let Some(data) = layout {
-            scene.push_image(scene::Image {
+            cx.scene().push_image(scene::Image {
                 bounds,
-                border: self.style.border,
+                border: self.style.border.into(),
                 corner_radii: self.style.corner_radius.into(),
                 grayscale: self.style.grayscale,
                 data: data.clone(),

crates/gpui/src/elements/keystroke_label.rs 🔗

@@ -39,7 +39,7 @@ impl<V: 'static> Element<V> for KeystrokeLabel {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, AnyElement<V>) {
         let mut element = if let Some(keystrokes) =
             cx.keystrokes_for_action(self.view_id, self.action.as_ref())
@@ -61,14 +61,13 @@ impl<V: 'static> Element<V> for KeystrokeLabel {
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         element: &mut AnyElement<V>,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) {
-        element.paint(scene, bounds.origin(), visible_bounds, view, cx);
+        element.paint(bounds.origin(), visible_bounds, view, cx);
     }
 
     fn rect_for_text_range(

crates/gpui/src/elements/label.rs 🔗

@@ -8,7 +8,7 @@ use crate::{
     },
     json::{ToJson, Value},
     text_layout::{Line, RunStyle},
-    Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, ViewContext,
+    Element, SizeConstraint, ViewContext,
 };
 use schemars::JsonSchema;
 use serde::Deserialize;
@@ -136,7 +136,7 @@ impl<V: 'static> Element<V> for Label {
         &mut self,
         constraint: SizeConstraint,
         _: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let runs = self.compute_runs();
         let line = cx.text_layout_cache().layout_str(
@@ -158,21 +158,14 @@ impl<V: 'static> Element<V> for Label {
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         line: &mut Self::LayoutState,
         _: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
-        line.paint(
-            scene,
-            bounds.origin(),
-            visible_bounds,
-            bounds.size().y(),
-            cx,
-        )
+        line.paint(bounds.origin(), visible_bounds, bounds.size().y(), cx)
     }
 
     fn rect_for_text_range(

crates/gpui/src/elements/list.rs 🔗

@@ -4,8 +4,7 @@ use crate::{
         vector::{vec2f, Vector2F},
     },
     json::json,
-    AnyElement, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder, SizeConstraint,
-    ViewContext,
+    AnyElement, Element, MouseRegion, SizeConstraint, ViewContext,
 };
 use std::{cell::RefCell, collections::VecDeque, fmt::Debug, ops::Range, rc::Rc};
 use sum_tree::{Bias, SumTree};
@@ -100,7 +99,7 @@ impl<V: 'static> Element<V> for List<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let state = &mut *self.state.0.borrow_mut();
         let size = constraint.max;
@@ -108,7 +107,7 @@ impl<V: 'static> Element<V> for List<V> {
         item_constraint.min.set_y(0.);
         item_constraint.max.set_y(f32::INFINITY);
 
-        if cx.refreshing || state.last_layout_width != Some(size.x()) {
+        if cx.refreshing() || state.last_layout_width != Some(size.x()) {
             state.rendered_range = 0..0;
             state.items = SumTree::from_iter(
                 (0..state.items.summary().count).map(|_| ListItem::Unrendered),
@@ -250,17 +249,17 @@ impl<V: 'static> Element<V> for List<V> {
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         scroll_top: &mut ListOffset,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) {
         let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
-        scene.push_layer(Some(visible_bounds));
-        scene.push_mouse_region(
-            MouseRegion::new::<Self>(cx.view_id(), 0, bounds).on_scroll({
+        cx.scene().push_layer(Some(visible_bounds));
+        let view_id = cx.view_id();
+        cx.scene()
+            .push_mouse_region(MouseRegion::new::<Self>(view_id, 0, bounds).on_scroll({
                 let state = self.state.clone();
                 let height = bounds.height();
                 let scroll_top = scroll_top.clone();
@@ -274,17 +273,14 @@ impl<V: 'static> Element<V> for List<V> {
                         cx,
                     )
                 }
-            }),
-        );
+            }));
 
         let state = &mut *self.state.0.borrow_mut();
         for (element, origin) in state.visible_elements(bounds, scroll_top) {
-            element
-                .borrow_mut()
-                .paint(scene, origin, visible_bounds, view, cx);
+            element.borrow_mut().paint(origin, visible_bounds, view, cx);
         }
 
-        scene.pop_layer();
+        cx.scene().pop_layer();
     }
 
     fn rect_for_text_range(
@@ -453,7 +449,7 @@ impl<V: 'static> StateInner<V> {
         existing_element: Option<&ListItem<V>>,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Option<Rc<RefCell<AnyElement<V>>>> {
         if let Some(ListItem::Rendered(element)) = existing_element {
             Some(element.clone())
@@ -647,7 +643,7 @@ impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Height {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::{elements::Empty, geometry::vector::vec2f, Entity, PaintContext};
+    use crate::{elements::Empty, geometry::vector::vec2f, Entity};
     use rand::prelude::*;
     use std::env;
 
@@ -666,15 +662,7 @@ mod tests {
             });
 
             let mut list = List::new(state.clone());
-            let mut new_parents = Default::default();
-            let mut notify_views_if_parents_change = Default::default();
-            let mut layout_cx = LayoutContext::new(
-                cx,
-                &mut new_parents,
-                &mut notify_views_if_parents_change,
-                false,
-            );
-            let (size, _) = list.layout(constraint, &mut view, &mut layout_cx);
+            let (size, _) = list.layout(constraint, &mut view, cx);
             assert_eq!(size, vec2f(100., 40.));
             assert_eq!(
                 state.0.borrow().items.summary().clone(),
@@ -698,13 +686,7 @@ mod tests {
                 cx,
             );
 
-            let mut layout_cx = LayoutContext::new(
-                cx,
-                &mut new_parents,
-                &mut notify_views_if_parents_change,
-                false,
-            );
-            let (_, logical_scroll_top) = list.layout(constraint, &mut view, &mut layout_cx);
+            let (_, logical_scroll_top) = list.layout(constraint, &mut view, cx);
             assert_eq!(
                 logical_scroll_top,
                 ListOffset {
@@ -728,13 +710,7 @@ mod tests {
                 }
             );
 
-            let mut layout_cx = LayoutContext::new(
-                cx,
-                &mut new_parents,
-                &mut notify_views_if_parents_change,
-                false,
-            );
-            let (size, logical_scroll_top) = list.layout(constraint, &mut view, &mut layout_cx);
+            let (size, logical_scroll_top) = list.layout(constraint, &mut view, cx);
             assert_eq!(size, vec2f(100., 40.));
             assert_eq!(
                 state.0.borrow().items.summary().clone(),
@@ -852,18 +828,10 @@ mod tests {
 
                 let mut list = List::new(state.clone());
                 let window_size = vec2f(width, height);
-                let mut new_parents = Default::default();
-                let mut notify_views_if_parents_change = Default::default();
-                let mut layout_cx = LayoutContext::new(
-                    cx,
-                    &mut new_parents,
-                    &mut notify_views_if_parents_change,
-                    false,
-                );
                 let (size, logical_scroll_top) = list.layout(
                     SizeConstraint::new(vec2f(0., 0.), window_size),
                     &mut view,
-                    &mut layout_cx,
+                    cx,
                 );
                 assert_eq!(size, window_size);
                 last_logical_scroll_top = Some(logical_scroll_top);
@@ -976,20 +944,12 @@ mod tests {
             &mut self,
             _: SizeConstraint,
             _: &mut V,
-            _: &mut LayoutContext<V>,
+            _: &mut ViewContext<V>,
         ) -> (Vector2F, ()) {
             (self.size, ())
         }
 
-        fn paint(
-            &mut self,
-            _: &mut SceneBuilder,
-            _: RectF,
-            _: RectF,
-            _: &mut (),
-            _: &mut V,
-            _: &mut PaintContext<V>,
-        ) {
+        fn paint(&mut self, _: RectF, _: RectF, _: &mut (), _: &mut V, _: &mut ViewContext<V>) {
             unimplemented!()
         }
 

crates/gpui/src/elements/mouse_event_handler.rs 🔗

@@ -10,8 +10,8 @@ use crate::{
         CursorRegion, HandlerSet, MouseClick, MouseClickOut, MouseDown, MouseDownOut, MouseDrag,
         MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
     },
-    AnyElement, Element, EventContext, LayoutContext, MouseRegion, MouseState, PaintContext,
-    SceneBuilder, SizeConstraint, TypeTag, ViewContext,
+    AnyElement, Element, EventContext, MouseRegion, MouseState, SizeConstraint, TypeTag,
+    ViewContext,
 };
 use serde_json::json;
 use std::ops::Range;
@@ -236,26 +236,21 @@ impl<V: 'static> MouseEventHandler<V> {
         .round_out()
     }
 
-    fn paint_regions(
-        &self,
-        scene: &mut SceneBuilder,
-        bounds: RectF,
-        visible_bounds: RectF,
-        cx: &mut ViewContext<V>,
-    ) {
+    fn paint_regions(&self, bounds: RectF, visible_bounds: RectF, cx: &mut ViewContext<V>) {
         let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
         let hit_bounds = self.hit_bounds(visible_bounds);
 
         if let Some(style) = self.cursor_style {
-            scene.push_cursor_region(CursorRegion {
+            cx.scene().push_cursor_region(CursorRegion {
                 bounds: hit_bounds,
                 style,
             });
         }
-        scene.push_mouse_region(
+        let view_id = cx.view_id();
+        cx.scene().push_mouse_region(
             MouseRegion::from_handlers(
                 self.tag,
-                cx.view_id(),
+                view_id,
                 self.region_id,
                 hit_bounds,
                 self.handlers.clone(),
@@ -275,31 +270,27 @@ impl<V: 'static> Element<V> for MouseEventHandler<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         (self.child.layout(constraint, view, cx), ())
     }
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         if self.above {
-            self.child
-                .paint(scene, bounds.origin(), visible_bounds, view, cx);
-
-            scene.paint_layer(None, |scene| {
-                self.paint_regions(scene, bounds, visible_bounds, cx);
+            self.child.paint(bounds.origin(), visible_bounds, view, cx);
+            cx.paint_layer(None, |cx| {
+                self.paint_regions(bounds, visible_bounds, cx);
             });
         } else {
-            self.paint_regions(scene, bounds, visible_bounds, cx);
-            self.child
-                .paint(scene, bounds.origin(), visible_bounds, view, cx);
+            self.paint_regions(bounds, visible_bounds, cx);
+            self.child.paint(bounds.origin(), visible_bounds, view, cx);
         }
     }
 

crates/gpui/src/elements/overlay.rs 🔗

@@ -3,8 +3,7 @@ use std::ops::Range;
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
     json::ToJson,
-    AnyElement, Axis, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder,
-    SizeConstraint, ViewContext,
+    AnyElement, Axis, Element, MouseRegion, SizeConstraint, ViewContext,
 };
 use serde_json::json;
 
@@ -125,7 +124,7 @@ impl<V: 'static> Element<V> for Overlay<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let constraint = if self.anchor_position.is_some() {
             SizeConstraint::new(Vector2F::zero(), cx.window_size())
@@ -138,12 +137,11 @@ impl<V: 'static> Element<V> for Overlay<V> {
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         _: RectF,
         size: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) {
         let (anchor_position, mut bounds) = match self.position_mode {
             OverlayPositionMode::Window => {
@@ -213,25 +211,23 @@ impl<V: 'static> Element<V> for Overlay<V> {
             OverlayFitMode::None => {}
         }
 
-        scene.paint_stacking_context(None, self.z_index, |scene| {
-            if self.hoverable {
-                enum OverlayHoverCapture {}
-                // Block hovers in lower stacking contexts
-                scene.push_mouse_region(MouseRegion::new::<OverlayHoverCapture>(
-                    cx.view_id(),
-                    cx.view_id(),
-                    bounds,
+        cx.scene().push_stacking_context(None, self.z_index);
+        if self.hoverable {
+            enum OverlayHoverCapture {}
+            // Block hovers in lower stacking contexts
+            let view_id = cx.view_id();
+            cx.scene()
+                .push_mouse_region(MouseRegion::new::<OverlayHoverCapture>(
+                    view_id, view_id, bounds,
                 ));
-            }
-
-            self.child.paint(
-                scene,
-                bounds.origin(),
-                RectF::new(Vector2F::zero(), cx.window_size()),
-                view,
-                cx,
-            );
-        });
+        }
+        self.child.paint(
+            bounds.origin(),
+            RectF::new(Vector2F::zero(), cx.window_size()),
+            view,
+            cx,
+        );
+        cx.scene().pop_stacking_context();
     }
 
     fn rect_for_text_range(

crates/gpui/src/elements/resizable.rs 🔗

@@ -7,8 +7,7 @@ use serde_json::json;
 use crate::{
     geometry::rect::RectF,
     platform::{CursorStyle, MouseButton},
-    AnyElement, AppContext, Axis, Element, LayoutContext, MouseRegion, PaintContext, SceneBuilder,
-    SizeConstraint, TypeTag, View, ViewContext,
+    AnyElement, AppContext, Axis, Element, MouseRegion, SizeConstraint, TypeTag, View, ViewContext,
 };
 
 #[derive(Copy, Clone, Debug)]
@@ -105,77 +104,77 @@ impl<V: 'static> Element<V> for Resizable<V> {
         &mut self,
         constraint: crate::SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         (self.child.layout(constraint, view, cx), constraint)
     }
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: pathfinder_geometry::rect::RectF,
         visible_bounds: pathfinder_geometry::rect::RectF,
         constraint: &mut SizeConstraint,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
-        scene.push_stacking_context(None, None);
+        cx.scene().push_stacking_context(None, None);
 
         let handle_region = self.handle_side.of_rect(bounds, self.handle_size);
 
         enum ResizeHandle {}
-        scene.push_mouse_region(
-            MouseRegion::new::<ResizeHandle>(
-                cx.view_id(),
-                self.handle_side as usize,
-                handle_region,
-            )
-            .on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere
-            .on_click(MouseButton::Left, {
-                let on_resize = self.on_resize.clone();
-                move |click, v, cx| {
-                    if click.click_count == 2 {
-                        on_resize.borrow_mut()(v, None, cx);
+        let view_id = cx.view_id();
+        cx.scene().push_mouse_region(
+            MouseRegion::new::<ResizeHandle>(view_id, self.handle_side as usize, handle_region)
+                .on_down(MouseButton::Left, |_, _: &mut V, _| {}) // This prevents the mouse down event from being propagated elsewhere
+                .on_click(MouseButton::Left, {
+                    let on_resize = self.on_resize.clone();
+                    move |click, v, cx| {
+                        if click.click_count == 2 {
+                            on_resize.borrow_mut()(v, None, cx);
+                        }
                     }
-                }
-            })
-            .on_drag(MouseButton::Left, {
-                let bounds = bounds.clone();
-                let side = self.handle_side;
-                let prev_size = side.relevant_component(bounds.size());
-                let min_size = side.relevant_component(constraint.min);
-                let max_size = side.relevant_component(constraint.max);
-                let on_resize = self.on_resize.clone();
-                let tag = self.tag;
-                move |event, view: &mut V, cx| {
-                    if event.end {
-                        return;
+                })
+                .on_drag(MouseButton::Left, {
+                    let bounds = bounds.clone();
+                    let side = self.handle_side;
+                    let prev_size = side.relevant_component(bounds.size());
+                    let min_size = side.relevant_component(constraint.min);
+                    let max_size = side.relevant_component(constraint.max);
+                    let on_resize = self.on_resize.clone();
+                    let tag = self.tag;
+                    move |event, view: &mut V, cx| {
+                        if event.end {
+                            return;
+                        }
+
+                        let Some((bounds, _)) = get_bounds(tag, cx) else {
+                            return;
+                        };
+
+                        let new_size_raw = match side {
+                            // Handle on top side of element => Element is on bottom
+                            HandleSide::Top => {
+                                bounds.height() + bounds.origin_y() - event.position.y()
+                            }
+                            // Handle on right side of element => Element is on left
+                            HandleSide::Right => event.position.x() - bounds.lower_left().x(),
+                            // Handle on left side of element => Element is on the right
+                            HandleSide::Left => {
+                                bounds.width() + bounds.origin_x() - event.position.x()
+                            }
+                            // Handle on bottom side of element => Element is on the top
+                            HandleSide::Bottom => event.position.y() - bounds.lower_left().y(),
+                        };
+
+                        let new_size = min_size.max(new_size_raw).min(max_size).round();
+                        if new_size != prev_size {
+                            on_resize.borrow_mut()(view, Some(new_size), cx);
+                        }
                     }
-
-                    let Some((bounds, _)) = get_bounds(tag, cx) else {
-                        return;
-                    };
-
-                    let new_size_raw = match side {
-                        // Handle on top side of element => Element is on bottom
-                        HandleSide::Top => bounds.height() + bounds.origin_y() - event.position.y(),
-                        // Handle on right side of element => Element is on left
-                        HandleSide::Right => event.position.x() - bounds.lower_left().x(),
-                        // Handle on left side of element => Element is on the right
-                        HandleSide::Left => bounds.width() + bounds.origin_x() - event.position.x(),
-                        // Handle on bottom side of element => Element is on the top
-                        HandleSide::Bottom => event.position.y() - bounds.lower_left().y(),
-                    };
-
-                    let new_size = min_size.max(new_size_raw).min(max_size).round();
-                    if new_size != prev_size {
-                        on_resize.borrow_mut()(view, Some(new_size), cx);
-                    }
-                }
-            }),
+                }),
         );
 
-        scene.push_cursor_region(crate::CursorRegion {
+        cx.scene().push_cursor_region(crate::CursorRegion {
             bounds: handle_region,
             style: match self.handle_side.axis() {
                 Axis::Horizontal => CursorStyle::ResizeLeftRight,
@@ -183,10 +182,9 @@ impl<V: 'static> Element<V> for Resizable<V> {
             },
         });
 
-        scene.pop_stacking_context();
+        cx.scene().pop_stacking_context();
 
-        self.child
-            .paint(scene, bounds.origin(), visible_bounds, view, cx);
+        self.child.paint(bounds.origin(), visible_bounds, view, cx);
     }
 
     fn rect_for_text_range(
@@ -242,26 +240,24 @@ impl<V: View, P: 'static> Element<V> for BoundsProvider<V, P> {
         &mut self,
         constraint: crate::SizeConstraint,
         view: &mut V,
-        cx: &mut crate::LayoutContext<V>,
+        cx: &mut crate::ViewContext<V>,
     ) -> (pathfinder_geometry::vector::Vector2F, Self::LayoutState) {
         (self.child.layout(constraint, view, cx), ())
     }
 
     fn paint(
         &mut self,
-        scene: &mut crate::SceneBuilder,
         bounds: pathfinder_geometry::rect::RectF,
         visible_bounds: pathfinder_geometry::rect::RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut crate::PaintContext<V>,
+        cx: &mut crate::ViewContext<V>,
     ) -> Self::PaintState {
         cx.update_default_global::<ProviderMap, _, _>(|map, _| {
             map.0.insert(TypeTag::new::<P>(), (bounds, visible_bounds));
         });
 
-        self.child
-            .paint(scene, bounds.origin(), visible_bounds, view, cx)
+        self.child.paint(bounds.origin(), visible_bounds, view, cx)
     }
 
     fn rect_for_text_range(

crates/gpui/src/elements/stack.rs 🔗

@@ -3,7 +3,7 @@ use std::ops::Range;
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
     json::{self, json, ToJson},
-    AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, ViewContext,
+    AnyElement, Element, SizeConstraint, ViewContext,
 };
 
 /// Element which renders it's children in a stack on top of each other.
@@ -34,7 +34,7 @@ impl<V: 'static> Element<V> for Stack<V> {
         &mut self,
         mut constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let mut size = constraint.min;
         let mut children = self.children.iter_mut();
@@ -52,17 +52,16 @@ impl<V: 'static> Element<V> for Stack<V> {
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         for child in &mut self.children {
-            scene.paint_layer(None, |scene| {
-                child.paint(scene, bounds.origin(), visible_bounds, view, cx);
-            });
+            cx.scene().push_layer(None);
+            child.paint(bounds.origin(), visible_bounds, view, cx);
+            cx.scene().pop_layer();
         }
     }
 

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

@@ -1,13 +1,12 @@
 use super::constrain_size_preserving_aspect_ratio;
 use crate::json::ToJson;
-use crate::PaintContext;
 use crate::{
     color::Color,
     geometry::{
         rect::RectF,
         vector::{vec2f, Vector2F},
     },
-    scene, Element, LayoutContext, SceneBuilder, SizeConstraint, ViewContext,
+    scene, Element, SizeConstraint, ViewContext,
 };
 use schemars::JsonSchema;
 use serde_derive::Deserialize;
@@ -49,7 +48,7 @@ impl<V: 'static> Element<V> for Svg {
         &mut self,
         constraint: SizeConstraint,
         _: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         match cx.asset_cache.svg(&self.path) {
             Ok(tree) => {
@@ -69,15 +68,14 @@ impl<V: 'static> Element<V> for Svg {
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         _visible_bounds: RectF,
         svg: &mut Self::LayoutState,
         _: &mut V,
-        _: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) {
         if let Some(svg) = svg.clone() {
-            scene.push_icon(scene::Icon {
+            cx.scene().push_icon(scene::Icon {
                 bounds,
                 svg,
                 path: self.path.clone(),

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

@@ -7,8 +7,7 @@ use crate::{
     },
     json::{ToJson, Value},
     text_layout::{Line, RunStyle, ShapedBoundary},
-    AppContext, Element, FontCache, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
-    TextLayoutCache, ViewContext,
+    Element, FontCache, SizeConstraint, TextLayoutCache, ViewContext, WindowContext,
 };
 use log::warn;
 use serde_json::json;
@@ -21,7 +20,7 @@ pub struct Text {
     highlights: Option<Box<[(Range<usize>, HighlightStyle)]>>,
     custom_runs: Option<(
         Box<[Range<usize>]>,
-        Box<dyn FnMut(usize, RectF, &mut SceneBuilder, &mut AppContext)>,
+        Box<dyn FnMut(usize, RectF, &mut WindowContext)>,
     )>,
 }
 
@@ -58,7 +57,7 @@ impl Text {
     pub fn with_custom_runs(
         mut self,
         runs: impl Into<Box<[Range<usize>]>>,
-        callback: impl 'static + FnMut(usize, RectF, &mut SceneBuilder, &mut AppContext),
+        callback: impl 'static + FnMut(usize, RectF, &mut WindowContext),
     ) -> Self {
         self.custom_runs = Some((runs.into(), Box::new(callback)));
         self
@@ -78,7 +77,7 @@ impl<V: 'static> Element<V> for Text {
         &mut self,
         constraint: SizeConstraint,
         _: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         // Convert the string and highlight ranges into an iterator of highlighted chunks.
 
@@ -166,16 +165,15 @@ impl<V: 'static> Element<V> for Text {
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         layout: &mut Self::LayoutState,
         _: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         let mut origin = bounds.origin();
         let empty = Vec::new();
-        let mut callback = |_, _, _: &mut SceneBuilder, _: &mut AppContext| {};
+        let mut callback = |_, _, _: &mut WindowContext| {};
 
         let mouse_runs;
         let custom_run_callback;
@@ -202,7 +200,6 @@ impl<V: 'static> Element<V> for Text {
             if boundaries.intersects(visible_bounds) {
                 if self.soft_wrap {
                     line.paint_wrapped(
-                        scene,
                         origin,
                         visible_bounds,
                         layout.line_height,
@@ -210,7 +207,7 @@ impl<V: 'static> Element<V> for Text {
                         cx,
                     );
                 } else {
-                    line.paint(scene, origin, visible_bounds, layout.line_height, cx);
+                    line.paint(origin, visible_bounds, layout.line_height, cx);
                 }
             }
 
@@ -248,7 +245,7 @@ impl<V: 'static> Element<V> for Text {
                                     *run_origin,
                                     glyph_origin + vec2f(0., layout.line_height),
                                 );
-                                custom_run_callback(*run_ix, bounds, scene, cx);
+                                custom_run_callback(*run_ix, bounds, cx);
                                 *run_origin =
                                     vec2f(origin.x(), glyph_origin.y() + layout.line_height);
                             }
@@ -264,7 +261,7 @@ impl<V: 'static> Element<V> for Text {
                                     run_origin,
                                     glyph_origin + vec2f(0., layout.line_height),
                                 );
-                                custom_run_callback(run_ix, bounds, scene, cx);
+                                custom_run_callback(run_ix, bounds, cx);
                                 custom_runs.next();
                             }
 
@@ -294,7 +291,7 @@ impl<V: 'static> Element<V> for Text {
                             run_origin,
                             line_end + vec2f(0., layout.line_height),
                         );
-                        custom_run_callback(run_ix, bounds, scene, cx);
+                        custom_run_callback(run_ix, bounds, cx);
                         if end_offset == run_end_offset {
                             custom_runs.next();
                         }
@@ -411,18 +408,10 @@ mod tests {
             let mut view = TestView;
             fonts::with_font_cache(cx.font_cache().clone(), || {
                 let mut text = Text::new("Hello\r\n", Default::default()).with_soft_wrap(true);
-                let mut new_parents = Default::default();
-                let mut notify_views_if_parents_change = Default::default();
-                let mut layout_cx = LayoutContext::new(
-                    cx,
-                    &mut new_parents,
-                    &mut notify_views_if_parents_change,
-                    false,
-                );
                 let (_, state) = text.layout(
                     SizeConstraint::new(Default::default(), vec2f(f32::INFINITY, f32::INFINITY)),
                     &mut view,
-                    &mut layout_cx,
+                    cx,
                 );
                 assert_eq!(state.shaped_lines.len(), 2);
                 assert_eq!(state.wrap_boundaries.len(), 2);

crates/gpui/src/elements/tooltip.rs 🔗

@@ -6,8 +6,7 @@ use crate::{
     fonts::TextStyle,
     geometry::{rect::RectF, vector::Vector2F},
     json::json,
-    Action, Axis, ElementStateHandle, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
-    Task, TypeTag, ViewContext,
+    Action, Axis, ElementStateHandle, SizeConstraint, Task, TypeTag, ViewContext,
 };
 use schemars::JsonSchema;
 use serde::Deserialize;
@@ -189,7 +188,7 @@ impl<V: 'static> Element<V> for Tooltip<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let size = self.child.layout(constraint, view, cx);
         if let Some(tooltip) = self.tooltip.as_mut() {
@@ -204,17 +203,15 @@ impl<V: 'static> Element<V> for Tooltip<V> {
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) {
-        self.child
-            .paint(scene, bounds.origin(), visible_bounds, view, cx);
+        self.child.paint(bounds.origin(), visible_bounds, view, cx);
         if let Some(tooltip) = self.tooltip.as_mut() {
-            tooltip.paint(scene, bounds.origin(), visible_bounds, view, cx);
+            tooltip.paint(bounds.origin(), visible_bounds, view, cx);
         }
     }
 

crates/gpui/src/elements/uniform_list.rs 🔗

@@ -6,7 +6,7 @@ use crate::{
     },
     json::{self, json},
     platform::ScrollWheelEvent,
-    AnyElement, LayoutContext, MouseRegion, PaintContext, SceneBuilder, ViewContext,
+    AnyElement, MouseRegion, ViewContext,
 };
 use json::ToJson;
 use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
@@ -158,7 +158,7 @@ impl<V: 'static> Element<V> for UniformList<V> {
         &mut self,
         constraint: SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         if constraint.max.y().is_infinite() {
             unimplemented!(
@@ -272,18 +272,17 @@ impl<V: 'static> Element<V> for UniformList<V> {
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         layout: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         let visible_bounds = visible_bounds.intersection(bounds).unwrap_or_default();
 
-        scene.push_layer(Some(visible_bounds));
+        cx.scene().push_layer(Some(visible_bounds));
 
-        scene.push_mouse_region(
+        cx.scene().push_mouse_region(
             MouseRegion::new::<Self>(self.view_id, 0, visible_bounds).on_scroll({
                 let scroll_max = layout.scroll_max;
                 let state = self.state.clone();
@@ -312,11 +311,11 @@ impl<V: 'static> Element<V> for UniformList<V> {
             );
 
         for item in &mut layout.items {
-            item.paint(scene, item_origin, visible_bounds, view, cx);
+            item.paint(item_origin, visible_bounds, view, cx);
             item_origin += vec2f(0.0, layout.item_height);
         }
 
-        scene.pop_layer();
+        cx.scene().pop_layer();
     }
 
     fn rect_for_text_range(

crates/gpui/src/fonts.rs 🔗

@@ -60,7 +60,7 @@ pub struct Features {
     pub zero: Option<bool>,
 }
 
-#[derive(Clone, Debug, JsonSchema, Refineable)]
+#[derive(Clone, Debug, JsonSchema)]
 pub struct TextStyle {
     pub color: Color,
     pub font_family_name: Arc<str>,
@@ -80,19 +80,78 @@ impl TextStyle {
             ..Default::default()
         }
     }
+}
+
+impl TextStyle {
+    pub fn refine(
+        &mut self,
+        refinement: &TextStyleRefinement,
+        font_cache: &FontCache,
+    ) -> Result<()> {
+        if let Some(font_size) = refinement.font_size {
+            self.font_size = font_size;
+        }
+        if let Some(color) = refinement.color {
+            self.color = color;
+        }
+        if let Some(underline) = refinement.underline {
+            self.underline = underline;
+        }
+
+        let mut update_font_id = false;
+        if let Some(font_family) = refinement.font_family.clone() {
+            self.font_family_id = font_cache.load_family(&[&font_family], &Default::default())?;
+            self.font_family_name = font_family;
+            update_font_id = true;
+        }
+        if let Some(font_weight) = refinement.font_weight {
+            self.font_properties.weight = font_weight;
+            update_font_id = true;
+        }
+        if let Some(font_style) = refinement.font_style {
+            self.font_properties.style = font_style;
+            update_font_id = true;
+        }
+
+        if update_font_id {
+            self.font_id = font_cache.select_font(self.font_family_id, &self.font_properties)?;
+        }
 
-    pub fn refine(self, refinement: TextStyleRefinement) -> TextStyle {
-        TextStyle {
-            color: refinement.color.unwrap_or(self.color),
-            font_family_name: refinement
-                .font_family_name
-                .unwrap_or_else(|| self.font_family_name.clone()),
-            font_family_id: refinement.font_family_id.unwrap_or(self.font_family_id),
-            font_id: refinement.font_id.unwrap_or(self.font_id),
-            font_size: refinement.font_size.unwrap_or(self.font_size),
-            font_properties: refinement.font_properties.unwrap_or(self.font_properties),
-            underline: refinement.underline.unwrap_or(self.underline),
-            soft_wrap: refinement.soft_wrap.unwrap_or(self.soft_wrap),
+        Ok(())
+    }
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct TextStyleRefinement {
+    pub color: Option<Color>,
+    pub font_family: Option<Arc<str>>,
+    pub font_size: Option<f32>,
+    pub font_weight: Option<Weight>,
+    pub font_style: Option<Style>,
+    pub underline: Option<Underline>,
+}
+
+impl Refineable for TextStyleRefinement {
+    type Refinement = Self;
+
+    fn refine(&mut self, refinement: &Self::Refinement) {
+        if refinement.color.is_some() {
+            self.color = refinement.color;
+        }
+        if refinement.font_family.is_some() {
+            self.font_family = refinement.font_family.clone();
+        }
+        if refinement.font_size.is_some() {
+            self.font_size = refinement.font_size;
+        }
+        if refinement.font_weight.is_some() {
+            self.font_weight = refinement.font_weight;
+        }
+        if refinement.font_style.is_some() {
+            self.font_style = refinement.font_style;
+        }
+        if refinement.underline.is_some() {
+            self.underline = refinement.underline;
         }
     }
 }

crates/gpui/src/geometry.rs 🔗

@@ -1,3 +1,5 @@
+use std::fmt::Debug;
+
 use super::scene::{Path, PathVertex};
 use crate::{color::Color, json::ToJson};
 pub use pathfinder_geometry::*;
@@ -133,13 +135,14 @@ impl ToJson for RectF {
     }
 }
 
-#[derive(Refineable)]
-pub struct Point<T: Clone + Default> {
+#[derive(Refineable, Debug)]
+#[refineable(debug)]
+pub struct Point<T: Clone + Default + Debug> {
     pub x: T,
     pub y: T,
 }
 
-impl<T: Clone + Default> Clone for Point<T> {
+impl<T: Clone + Default + Debug> Clone for Point<T> {
     fn clone(&self) -> Self {
         Self {
             x: self.x.clone(),
@@ -148,7 +151,7 @@ impl<T: Clone + Default> Clone for Point<T> {
     }
 }
 
-impl<T: Clone + Default> Into<taffy::geometry::Point<T>> for Point<T> {
+impl<T: Clone + Default + Debug> Into<taffy::geometry::Point<T>> for Point<T> {
     fn into(self) -> taffy::geometry::Point<T> {
         taffy::geometry::Point {
             x: self.x,
@@ -157,13 +160,14 @@ impl<T: Clone + Default> Into<taffy::geometry::Point<T>> for Point<T> {
     }
 }
 
-#[derive(Clone, Refineable)]
-pub struct Size<T: Clone + Default> {
+#[derive(Refineable, Clone, Debug)]
+#[refineable(debug)]
+pub struct Size<T: Clone + Default + Debug> {
     pub width: T,
     pub height: T,
 }
 
-impl<S, T: Clone + Default> From<taffy::geometry::Size<S>> for Size<T>
+impl<S, T: Clone + Default + Debug> From<taffy::geometry::Size<S>> for Size<T>
 where
     S: Into<T>,
 {
@@ -175,7 +179,7 @@ where
     }
 }
 
-impl<S, T: Clone + Default> Into<taffy::geometry::Size<S>> for Size<T>
+impl<S, T: Clone + Default + Debug> Into<taffy::geometry::Size<S>> for Size<T>
 where
     T: Into<S>,
 {
@@ -222,15 +226,25 @@ impl Size<Length> {
     }
 }
 
-#[derive(Clone, Default, Refineable)]
-pub struct Edges<T: Clone + Default> {
+#[derive(Clone, Default, Refineable, Debug)]
+#[refineable(debug)]
+pub struct Edges<T: Clone + Default + Debug> {
     pub top: T,
     pub right: T,
     pub bottom: T,
     pub left: T,
 }
 
-impl Edges<DefiniteLength> {
+impl Edges<Length> {
+    pub fn auto() -> Self {
+        Self {
+            top: Length::Auto,
+            right: Length::Auto,
+            bottom: Length::Auto,
+            left: Length::Auto,
+        }
+    }
+
     pub fn zero() -> Self {
         Self {
             top: pixels(0.),
@@ -240,7 +254,10 @@ impl Edges<DefiniteLength> {
         }
     }
 
-    pub fn to_taffy(&self, rem_size: f32) -> taffy::geometry::Rect<taffy::style::LengthPercentage> {
+    pub fn to_taffy(
+        &self,
+        rem_size: f32,
+    ) -> taffy::geometry::Rect<taffy::style::LengthPercentageAuto> {
         taffy::geometry::Rect {
             top: self.top.to_taffy(rem_size),
             right: self.right.to_taffy(rem_size),
@@ -250,16 +267,27 @@ impl Edges<DefiniteLength> {
     }
 }
 
-impl Edges<Length> {
-    pub fn auto() -> Self {
+impl Edges<DefiniteLength> {
+    pub fn zero() -> Self {
         Self {
-            top: Length::Auto,
-            right: Length::Auto,
-            bottom: Length::Auto,
-            left: Length::Auto,
+            top: pixels(0.),
+            right: pixels(0.),
+            bottom: pixels(0.),
+            left: pixels(0.),
         }
     }
 
+    pub fn to_taffy(&self, rem_size: f32) -> taffy::geometry::Rect<taffy::style::LengthPercentage> {
+        taffy::geometry::Rect {
+            top: self.top.to_taffy(rem_size),
+            right: self.right.to_taffy(rem_size),
+            bottom: self.bottom.to_taffy(rem_size),
+            left: self.left.to_taffy(rem_size),
+        }
+    }
+}
+
+impl Edges<AbsoluteLength> {
     pub fn zero() -> Self {
         Self {
             top: pixels(0.),
@@ -269,10 +297,7 @@ impl Edges<Length> {
         }
     }
 
-    pub fn to_taffy(
-        &self,
-        rem_size: f32,
-    ) -> taffy::geometry::Rect<taffy::style::LengthPercentageAuto> {
+    pub fn to_taffy(&self, rem_size: f32) -> taffy::geometry::Rect<taffy::style::LengthPercentage> {
         taffy::geometry::Rect {
             top: self.top.to_taffy(rem_size),
             right: self.right.to_taffy(rem_size),
@@ -280,6 +305,21 @@ impl Edges<Length> {
             left: self.left.to_taffy(rem_size),
         }
     }
+
+    pub fn to_pixels(&self, rem_size: f32) -> Edges<f32> {
+        Edges {
+            top: self.top.to_pixels(rem_size),
+            right: self.right.to_pixels(rem_size),
+            bottom: self.bottom.to_pixels(rem_size),
+            left: self.left.to_pixels(rem_size),
+        }
+    }
+}
+
+impl Edges<f32> {
+    pub fn is_empty(&self) -> bool {
+        self.top == 0.0 && self.right == 0.0 && self.bottom == 0.0 && self.left == 0.0
+    }
 }
 
 #[derive(Clone, Copy)]
@@ -288,6 +328,15 @@ pub enum AbsoluteLength {
     Rems(f32),
 }
 
+impl std::fmt::Debug for AbsoluteLength {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            AbsoluteLength::Pixels(pixels) => write!(f, "{}px", pixels),
+            AbsoluteLength::Rems(rems) => write!(f, "{}rems", rems),
+        }
+    }
+}
+
 impl AbsoluteLength {
     pub fn to_pixels(&self, rem_size: f32) -> f32 {
         match self {
@@ -295,6 +344,13 @@ impl AbsoluteLength {
             AbsoluteLength::Rems(rems) => rems * rem_size,
         }
     }
+
+    pub fn to_taffy(&self, rem_size: f32) -> taffy::style::LengthPercentage {
+        match self {
+            AbsoluteLength::Pixels(pixels) => taffy::style::LengthPercentage::Length(*pixels),
+            AbsoluteLength::Rems(rems) => taffy::style::LengthPercentage::Length(rems * rem_size),
+        }
+    }
 }
 
 impl Default for AbsoluteLength {
@@ -307,7 +363,7 @@ impl Default for AbsoluteLength {
 #[derive(Clone, Copy)]
 pub enum DefiniteLength {
     Absolute(AbsoluteLength),
-    Relative(f32), // Percent, from 0 to 100.
+    Relative(f32), // 0. to 1.
 }
 
 impl DefiniteLength {
@@ -326,6 +382,15 @@ impl DefiniteLength {
     }
 }
 
+impl std::fmt::Debug for DefiniteLength {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            DefiniteLength::Absolute(length) => std::fmt::Debug::fmt(length, f),
+            DefiniteLength::Relative(fract) => write!(f, "{}%", (fract * 100.0) as i32),
+        }
+    }
+}
+
 impl From<AbsoluteLength> for DefiniteLength {
     fn from(length: AbsoluteLength) -> Self {
         Self::Absolute(length)
@@ -345,6 +410,15 @@ pub enum Length {
     Auto,
 }
 
+impl std::fmt::Debug for Length {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            Length::Definite(definite_length) => write!(f, "{:?}", definite_length),
+            Length::Auto => write!(f, "auto"),
+        }
+    }
+}
+
 pub fn relative<T: From<DefiniteLength>>(fraction: f32) -> T {
     DefiniteLength::Relative(fraction).into()
 }
@@ -387,3 +461,9 @@ impl Default for Length {
         Self::Definite(DefiniteLength::default())
     }
 }
+
+impl From<()> for Length {
+    fn from(_: ()) -> Self {
+        Self::Definite(DefiniteLength::default())
+    }
+}

crates/gpui/src/gpui.rs 🔗

@@ -1,4 +1,5 @@
 mod app;
+mod image_cache;
 pub use app::*;
 mod assets;
 #[cfg(any(test, feature = "test-support"))]
@@ -8,6 +9,7 @@ pub mod elements;
 pub mod font_cache;
 mod image_data;
 pub use crate::image_data::ImageData;
+pub use taffy;
 pub mod views;
 pub use font_cache::FontCache;
 mod clipboard;
@@ -27,9 +29,9 @@ pub mod json;
 pub mod keymap_matcher;
 pub mod platform;
 pub use gpui_macros::{test, Element};
+pub use usvg;
 pub use window::{
-    Axis, EngineLayout, LayoutEngine, LayoutId, RectFExt, SizeConstraint, Vector2FExt,
-    WindowContext,
+    Axis, Layout, LayoutEngine, LayoutId, RectFExt, SizeConstraint, Vector2FExt, WindowContext,
 };
 
 pub use anyhow;

crates/gpui/src/image_cache.rs 🔗

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

crates/gpui/src/platform.rs 🔗

@@ -146,6 +146,7 @@ pub trait Window {
     fn titlebar_height(&self) -> f32;
     fn appearance(&self) -> Appearance;
     fn screen(&self) -> Rc<dyn Screen>;
+    fn mouse_position(&self) -> Vector2F;
 
     fn as_any_mut(&mut self) -> &mut dyn Any;
     fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>);

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

@@ -577,7 +577,6 @@ impl Renderer {
         };
         for (ix, quad) in quads.iter().enumerate() {
             let bounds = quad.bounds * scale_factor;
-            let border_width = quad.border.width * scale_factor;
             let shader_quad = shaders::GPUIQuad {
                 origin: bounds.origin().round().to_float2(),
                 size: bounds.size().round().to_float2(),
@@ -585,10 +584,10 @@ impl Renderer {
                     .background
                     .unwrap_or_else(Color::transparent_black)
                     .to_uchar4(),
-                border_top: border_width * (quad.border.top as usize as f32),
-                border_right: border_width * (quad.border.right as usize as f32),
-                border_bottom: border_width * (quad.border.bottom as usize as f32),
-                border_left: border_width * (quad.border.left as usize as f32),
+                border_top: quad.border.top * scale_factor,
+                border_right: quad.border.right * scale_factor,
+                border_bottom: quad.border.bottom * scale_factor,
+                border_left: quad.border.left * scale_factor,
                 border_color: quad.border.color.to_uchar4(),
                 corner_radius_top_left: quad.corner_radii.top_left * scale_factor,
                 corner_radius_top_right: quad.corner_radii.top_right * scale_factor,
@@ -746,7 +745,6 @@ impl Renderer {
             let origin = image.bounds.origin() * scale_factor;
             let target_size = image.bounds.size() * scale_factor;
             let corner_radii = image.corner_radii * scale_factor;
-            let border_width = image.border.width * scale_factor;
             let (alloc_id, atlas_bounds) = self.image_cache.render(&image.data);
             images_by_atlas
                 .entry(alloc_id.atlas_id)
@@ -756,10 +754,10 @@ impl Renderer {
                     target_size: target_size.to_float2(),
                     source_size: atlas_bounds.size().to_float2(),
                     atlas_origin: atlas_bounds.origin().to_float2(),
-                    border_top: border_width * (image.border.top as usize as f32),
-                    border_right: border_width * (image.border.right as usize as f32),
-                    border_bottom: border_width * (image.border.bottom as usize as f32),
-                    border_left: border_width * (image.border.left as usize as f32),
+                    border_top: image.border.top * scale_factor,
+                    border_right: image.border.right * scale_factor,
+                    border_bottom: image.border.bottom * scale_factor,
+                    border_left: image.border.left * scale_factor,
                     border_color: image.border.color.to_uchar4(),
                     corner_radius_top_left: corner_radii.top_left,
                     corner_radius_top_right: corner_radii.top_right,

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

@@ -202,6 +202,10 @@ impl platform::Window for StatusItem {
         }
     }
 
+    fn mouse_position(&self) -> Vector2F {
+        unimplemented!()
+    }
+
     fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
         self
     }

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

@@ -221,6 +221,14 @@ unsafe fn build_classes() {
     };
 }
 
+pub fn convert_mouse_position(position: NSPoint, window_height: f32) -> Vector2F {
+    vec2f(
+        position.x as f32,
+        // MacOS screen coordinates are relative to bottom left
+        window_height - position.y as f32,
+    )
+}
+
 unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const Class {
     let mut decl = ClassDecl::new(name, superclass).unwrap();
     decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
@@ -661,6 +669,16 @@ impl platform::Window for MacWindow {
         }
     }
 
+    fn mouse_position(&self) -> Vector2F {
+        let position = unsafe {
+            self.0
+                .borrow()
+                .native_window
+                .mouseLocationOutsideOfEventStream()
+        };
+        convert_mouse_position(position, self.content_size().y())
+    }
+
     fn as_any_mut(&mut self) -> &mut dyn Any {
         self
     }

crates/gpui/src/platform/test.rs 🔗

@@ -332,6 +332,10 @@ impl super::Window for Window {
         Rc::new(Screen)
     }
 
+    fn mouse_position(&self) -> Vector2F {
+        Vector2F::zero()
+    }
+
     fn as_any_mut(&mut self) -> &mut dyn Any {
         self
     }

crates/gpui/src/scene.rs 🔗

@@ -8,7 +8,6 @@ use derive_more::Mul;
 use schemars::JsonSchema;
 use serde::Deserialize;
 use serde_derive::Serialize;
-use serde_json::json;
 use std::{
     any::{Any, TypeId},
     borrow::Cow,
@@ -20,7 +19,6 @@ use crate::{
     color::Color,
     fonts::{FontId, GlyphId},
     geometry::{rect::RectF, vector::Vector2F},
-    json::ToJson,
     platform::{current::Surface, CursorStyle},
     ImageData, WindowContext,
 };
@@ -28,10 +26,9 @@ pub use mouse_event::*;
 pub use mouse_region::*;
 
 pub struct SceneBuilder {
-    scale_factor: f32,
     stacking_contexts: Vec<StackingContext>,
     active_stacking_context_stack: Vec<usize>,
-    /// Used by the playground crate.
+    /// Used by the gpui2 crate.
     pub event_handlers: Vec<EventHandler>,
     #[cfg(debug_assertions)]
     mouse_region_ids: HashSet<MouseRegionId>,
@@ -171,15 +168,13 @@ pub struct Icon {
     pub color: Color,
 }
 
-#[derive(Clone, Copy, Default, Debug, JsonSchema)]
+#[derive(Clone, Copy, Default, Debug)]
 pub struct Border {
-    pub width: f32,
     pub color: Color,
-    pub overlay: bool,
-    pub top: bool,
-    pub right: bool,
-    pub bottom: bool,
-    pub left: bool,
+    pub top: f32,
+    pub right: f32,
+    pub bottom: f32,
+    pub left: f32,
 }
 
 #[derive(Clone, Copy, Default, Debug)]
@@ -191,47 +186,6 @@ pub struct Underline {
     pub squiggly: bool,
 }
 
-impl<'de> Deserialize<'de> for Border {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        #[derive(Deserialize)]
-        struct BorderData {
-            pub width: f32,
-            pub color: Color,
-            #[serde(default)]
-            pub overlay: bool,
-            #[serde(default)]
-            pub top: bool,
-            #[serde(default)]
-            pub right: bool,
-            #[serde(default)]
-            pub bottom: bool,
-            #[serde(default)]
-            pub left: bool,
-        }
-
-        let data = BorderData::deserialize(deserializer)?;
-        let mut border = Border {
-            width: data.width,
-            color: data.color,
-            overlay: data.overlay,
-            top: data.top,
-            bottom: data.bottom,
-            left: data.left,
-            right: data.right,
-        };
-        if !border.top && !border.bottom && !border.left && !border.right {
-            border.top = true;
-            border.bottom = true;
-            border.left = true;
-            border.right = true;
-        }
-        Ok(border)
-    }
-}
-
 #[derive(Debug)]
 pub struct Path {
     pub bounds: RectF,
@@ -290,43 +244,38 @@ impl Scene {
 }
 
 impl SceneBuilder {
-    pub fn new(scale_factor: f32) -> Self {
-        let stacking_context = StackingContext::new(None, 0);
-        SceneBuilder {
-            scale_factor,
-            stacking_contexts: vec![stacking_context],
-            active_stacking_context_stack: vec![0],
+    pub fn new() -> Self {
+        let mut this = SceneBuilder {
+            stacking_contexts: Vec::new(),
+            active_stacking_context_stack: Vec::new(),
             #[cfg(debug_assertions)]
-            mouse_region_ids: Default::default(),
+            mouse_region_ids: HashSet::default(),
             event_handlers: Vec::new(),
-        }
+        };
+        this.clear();
+        this
     }
 
-    pub fn build(mut self) -> Scene {
-        self.stacking_contexts
-            .sort_by_key(|context| context.z_index);
-        Scene {
-            scale_factor: self.scale_factor,
-            stacking_contexts: self.stacking_contexts,
-            event_handlers: self.event_handlers,
-        }
+    pub fn clear(&mut self) {
+        self.stacking_contexts.clear();
+        self.stacking_contexts.push(StackingContext::new(None, 0));
+        self.active_stacking_context_stack.clear();
+        self.active_stacking_context_stack.push(0);
+        #[cfg(debug_assertions)]
+        self.mouse_region_ids.clear();
     }
 
-    pub fn scale_factor(&self) -> f32 {
-        self.scale_factor
-    }
+    pub fn build(&mut self, scale_factor: f32) -> Scene {
+        let mut stacking_contexts = std::mem::take(&mut self.stacking_contexts);
+        stacking_contexts.sort_by_key(|context| context.z_index);
+        let event_handlers = std::mem::take(&mut self.event_handlers);
+        self.clear();
 
-    pub fn paint_stacking_context<F>(
-        &mut self,
-        clip_bounds: Option<RectF>,
-        z_index: Option<usize>,
-        f: F,
-    ) where
-        F: FnOnce(&mut Self),
-    {
-        self.push_stacking_context(clip_bounds, z_index);
-        f(self);
-        self.pop_stacking_context();
+        Scene {
+            scale_factor,
+            stacking_contexts,
+            event_handlers,
+        }
     }
 
     pub fn push_stacking_context(&mut self, clip_bounds: Option<RectF>, z_index: Option<usize>) {
@@ -342,15 +291,6 @@ impl SceneBuilder {
         assert!(!self.active_stacking_context_stack.is_empty());
     }
 
-    pub fn paint_layer<F>(&mut self, clip_bounds: Option<RectF>, f: F)
-    where
-        F: FnOnce(&mut Self),
-    {
-        self.push_layer(clip_bounds);
-        f(self);
-        self.pop_layer();
-    }
-
     pub fn push_layer(&mut self, clip_bounds: Option<RectF>) {
         self.active_stacking_context().push_layer(clip_bounds);
     }
@@ -606,99 +546,6 @@ impl Layer {
     }
 }
 
-impl Border {
-    pub fn new(width: f32, color: Color) -> Self {
-        Self {
-            width,
-            color,
-            overlay: false,
-            top: false,
-            left: false,
-            bottom: false,
-            right: false,
-        }
-    }
-
-    pub fn all(width: f32, color: Color) -> Self {
-        Self {
-            width,
-            color,
-            overlay: false,
-            top: true,
-            left: true,
-            bottom: true,
-            right: true,
-        }
-    }
-
-    pub fn top(width: f32, color: Color) -> Self {
-        let mut border = Self::new(width, color);
-        border.top = true;
-        border
-    }
-
-    pub fn left(width: f32, color: Color) -> Self {
-        let mut border = Self::new(width, color);
-        border.left = true;
-        border
-    }
-
-    pub fn bottom(width: f32, color: Color) -> Self {
-        let mut border = Self::new(width, color);
-        border.bottom = true;
-        border
-    }
-
-    pub fn right(width: f32, color: Color) -> Self {
-        let mut border = Self::new(width, color);
-        border.right = true;
-        border
-    }
-
-    pub fn with_sides(mut self, top: bool, left: bool, bottom: bool, right: bool) -> Self {
-        self.top = top;
-        self.left = left;
-        self.bottom = bottom;
-        self.right = right;
-        self
-    }
-
-    pub fn top_width(&self) -> f32 {
-        if self.top {
-            self.width
-        } else {
-            0.0
-        }
-    }
-
-    pub fn left_width(&self) -> f32 {
-        if self.left {
-            self.width
-        } else {
-            0.0
-        }
-    }
-}
-
-impl ToJson for Border {
-    fn to_json(&self) -> serde_json::Value {
-        let mut value = json!({});
-        if self.top {
-            value["top"] = json!(self.width);
-        }
-        if self.right {
-            value["right"] = json!(self.width);
-        }
-        if self.bottom {
-            value["bottom"] = json!(self.width);
-        }
-        if self.left {
-            value["left"] = json!(self.width);
-        }
-        value
-    }
-}
-
 impl MouseRegion {
     pub fn id(&self) -> MouseRegionId {
         self.id

crates/gpui/src/text_layout.rs 🔗

@@ -9,7 +9,6 @@ use crate::{
     platform::FontSystem,
     scene,
     window::WindowContext,
-    SceneBuilder,
 };
 use ordered_float::OrderedFloat;
 use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
@@ -284,7 +283,6 @@ impl Line {
 
     pub fn paint(
         &self,
-        scene: &mut SceneBuilder,
         origin: Vector2F,
         visible_bounds: RectF,
         line_height: f32,
@@ -347,7 +345,7 @@ impl Line {
                 }
 
                 if let Some((underline_origin, underline_style)) = finished_underline {
-                    scene.push_underline(scene::Underline {
+                    cx.scene().push_underline(scene::Underline {
                         origin: underline_origin,
                         width: glyph_origin.x() - underline_origin.x(),
                         thickness: underline_style.thickness.into(),
@@ -357,14 +355,14 @@ impl Line {
                 }
 
                 if glyph.is_emoji {
-                    scene.push_image_glyph(scene::ImageGlyph {
+                    cx.scene().push_image_glyph(scene::ImageGlyph {
                         font_id: run.font_id,
                         font_size: self.layout.font_size,
                         id: glyph.id,
                         origin: glyph_origin,
                     });
                 } else {
-                    scene.push_glyph(scene::Glyph {
+                    cx.scene().push_glyph(scene::Glyph {
                         font_id: run.font_id,
                         font_size: self.layout.font_size,
                         id: glyph.id,
@@ -377,7 +375,7 @@ impl Line {
 
         if let Some((underline_start, underline_style)) = underline.take() {
             let line_end_x = origin.x() + self.layout.width;
-            scene.push_underline(scene::Underline {
+            cx.scene().push_underline(scene::Underline {
                 origin: underline_start,
                 width: line_end_x - underline_start.x(),
                 color: underline_style.color.unwrap(),
@@ -389,7 +387,6 @@ impl Line {
 
     pub fn paint_wrapped(
         &self,
-        scene: &mut SceneBuilder,
         origin: Vector2F,
         visible_bounds: RectF,
         line_height: f32,
@@ -417,7 +414,7 @@ impl Line {
                 {
                     boundaries.next();
                     if let Some((underline_origin, underline_style)) = underline {
-                        scene.push_underline(scene::Underline {
+                        cx.scene().push_underline(scene::Underline {
                             origin: underline_origin,
                             width: glyph_origin.x() - underline_origin.x(),
                             thickness: underline_style.thickness.into(),
@@ -461,7 +458,7 @@ impl Line {
                 }
 
                 if let Some((underline_origin, underline_style)) = finished_underline {
-                    scene.push_underline(scene::Underline {
+                    cx.scene().push_underline(scene::Underline {
                         origin: underline_origin,
                         width: glyph_origin.x() - underline_origin.x(),
                         thickness: underline_style.thickness.into(),
@@ -477,14 +474,14 @@ impl Line {
                 );
                 if glyph_bounds.intersects(visible_bounds) {
                     if glyph.is_emoji {
-                        scene.push_image_glyph(scene::ImageGlyph {
+                        cx.scene().push_image_glyph(scene::ImageGlyph {
                             font_id: run.font_id,
                             font_size: self.layout.font_size,
                             id: glyph.id,
                             origin: glyph_bounds.origin() + baseline_offset,
                         });
                     } else {
-                        scene.push_glyph(scene::Glyph {
+                        cx.scene().push_glyph(scene::Glyph {
                             font_id: run.font_id,
                             font_size: self.layout.font_size,
                             id: glyph.id,
@@ -498,7 +495,7 @@ impl Line {
 
         if let Some((underline_origin, underline_style)) = underline.take() {
             let line_end_x = glyph_origin.x() + self.layout.width - prev_position;
-            scene.push_underline(scene::Underline {
+            cx.scene().push_underline(scene::Underline {
                 origin: underline_origin,
                 width: line_end_x - underline_origin.x(),
                 thickness: underline_style.thickness.into(),

crates/gpui2/Cargo.toml 🔗

@@ -0,0 +1,32 @@
+[package]
+name = "gpui2"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+name = "gpui2"
+path = "src/gpui2.rs"
+
+[features]
+test-support = ["gpui/test-support"]
+
+[dependencies]
+anyhow.workspace = true
+derive_more.workspace = true
+gpui = { path = "../gpui" }
+log.workspace = true
+futures.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/gpui/playground/src/adapter.rs → crates/gpui2/src/adapter.rs 🔗

@@ -1,8 +1,8 @@
-use crate::{layout_context::LayoutContext, paint_context::PaintContext};
+use crate::ViewContext;
 use gpui::{geometry::rect::RectF, LayoutEngine, LayoutId};
 use util::ResultExt;
 
-/// Makes a new, playground-style element into a legacy element.
+/// Makes a new, gpui2-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> {
@@ -13,12 +13,11 @@ impl<V: 'static> gpui::Element<V> for AdapterElement<V> {
         &mut self,
         constraint: gpui::SizeConstraint,
         view: &mut V,
-        cx: &mut gpui::LayoutContext<V>,
+        cx: &mut gpui::ViewContext<V>,
     ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
         cx.push_layout_engine(LayoutEngine::new());
 
-        let size = constraint.max;
-        let mut cx = LayoutContext::new(cx);
+        let mut cx = ViewContext::new(cx);
         let layout_id = self.0.layout(view, &mut cx).log_err();
         if let Some(layout_id) = layout_id {
             cx.layout_engine()
@@ -37,41 +36,40 @@ impl<V: 'static> gpui::Element<V> for AdapterElement<V> {
 
     fn paint(
         &mut self,
-        scene: &mut gpui::SceneBuilder,
         bounds: RectF,
-        visible_bounds: RectF,
+        _visible_bounds: RectF,
         layout_data: &mut Option<(LayoutEngine, LayoutId)>,
         view: &mut V,
-        legacy_cx: &mut gpui::PaintContext<V>,
+        cx: &mut gpui::ViewContext<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, &mut cx);
-        *layout_data = legacy_cx.pop_layout_engine().zip(Some(layout_id));
+        cx.push_layout_engine(layout_engine);
+        self.0
+            .paint(view, bounds.origin(), &mut ViewContext::new(cx));
+        *layout_data = 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>,
+        _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>,
+        _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/gpui/playground/src/color.rs → crates/gpui2/src/color.rs 🔗

@@ -1,8 +1,9 @@
 #![allow(dead_code)]
 
-use std::{num::ParseIntError, ops::Range};
-
+use serde::de::{self, Deserialize, Deserializer, Visitor};
 use smallvec::SmallVec;
+use std::fmt;
+use std::{num::ParseIntError, ops::Range};
 
 pub fn rgb<C: From<Rgba>>(hex: u32) -> C {
     let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
@@ -19,6 +20,40 @@ pub struct Rgba {
     pub a: f32,
 }
 
+struct RgbaVisitor;
+
+impl<'de> Visitor<'de> for RgbaVisitor {
+    type Value = Rgba;
+
+    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        formatter.write_str("a string in the format #rrggbb or #rrggbbaa")
+    }
+
+    fn visit_str<E: de::Error>(self, value: &str) -> Result<Rgba, E> {
+        if value.len() == 7 || value.len() == 9 {
+            let r = u8::from_str_radix(&value[1..3], 16).unwrap() as f32 / 255.0;
+            let g = u8::from_str_radix(&value[3..5], 16).unwrap() as f32 / 255.0;
+            let b = u8::from_str_radix(&value[5..7], 16).unwrap() as f32 / 255.0;
+            let a = if value.len() == 9 {
+                u8::from_str_radix(&value[7..9], 16).unwrap() as f32 / 255.0
+            } else {
+                1.0
+            };
+            Ok(Rgba { r, g, b, a })
+        } else {
+            Err(E::custom(
+                "Bad format for RGBA. Expected #rrggbb or #rrggbbaa.",
+            ))
+        }
+    }
+}
+
+impl<'de> Deserialize<'de> for Rgba {
+    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+        deserializer.deserialize_str(RgbaVisitor)
+    }
+}
+
 pub trait Lerp {
     fn lerp(&self, level: f32) -> Hsla;
 }
@@ -219,6 +254,19 @@ impl Into<gpui::color::Color> for Hsla {
     }
 }
 
+impl<'de> Deserialize<'de> for Hsla {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        // First, deserialize it into Rgba
+        let rgba = Rgba::deserialize(deserializer)?;
+
+        // Then, use the From<Rgba> for Hsla implementation to convert it
+        Ok(Hsla::from(rgba))
+    }
+}
+
 pub struct ColorScale {
     colors: SmallVec<[Hsla; 2]>,
     positions: SmallVec<[f32; 2]>,

crates/gpui2/src/element.rs 🔗

@@ -0,0 +1,186 @@
+pub use crate::ViewContext;
+use anyhow::Result;
+use gpui::geometry::vector::Vector2F;
+pub use gpui::{Layout, LayoutId};
+use smallvec::SmallVec;
+
+pub trait Element<V: 'static>: 'static + IntoElement<V> {
+    type PaintState;
+
+    fn layout(
+        &mut self,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
+    ) -> Result<(LayoutId, Self::PaintState)>
+    where
+        Self: Sized;
+
+    fn paint(
+        &mut self,
+        view: &mut V,
+        parent_origin: Vector2F,
+        layout: &Layout,
+        state: &mut Self::PaintState,
+        cx: &mut ViewContext<V>,
+    ) where
+        Self: Sized;
+
+    fn into_any(self) -> AnyElement<V>
+    where
+        Self: 'static + Sized,
+    {
+        AnyElement(Box::new(StatefulElement {
+            element: self,
+            phase: ElementPhase::Init,
+        }))
+    }
+}
+
+/// 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 ViewContext<V>) -> Result<LayoutId>;
+    fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut ViewContext<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,
+    },
+    #[allow(dead_code)]
+    PostPaint {
+        layout: Layout,
+        paint_state: E::PaintState,
+    },
+    Error(String),
+}
+
+impl<V: 'static, E: Element<V>> std::fmt::Debug for ElementPhase<V, E> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            ElementPhase::Init => write!(f, "Init"),
+            ElementPhase::PostLayout { layout_id, .. } => {
+                write!(f, "PostLayout with layout id: {:?}", layout_id)
+            }
+            ElementPhase::PostPaint { layout, .. } => {
+                write!(f, "PostPaint with layout: {:?}", layout)
+            }
+            ElementPhase::Error(err) => write!(f, "Error: {}", err),
+        }
+    }
+}
+
+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 ViewContext<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 ViewContext<V>) {
+        self.phase = match std::mem::take(&mut self.phase) {
+            ElementPhase::PostLayout {
+                layout_id,
+                mut paint_state,
+            } => match cx.computed_layout(layout_id) {
+                Ok(layout) => {
+                    self.element
+                        .paint(view, parent_origin, &layout, &mut paint_state, cx);
+                    ElementPhase::PostPaint {
+                        layout,
+                        paint_state,
+                    }
+                }
+                Err(error) => ElementPhase::Error(error.to_string()),
+            },
+            ElementPhase::PostPaint {
+                layout,
+                mut paint_state,
+            } => {
+                self.element
+                    .paint(view, parent_origin, &layout, &mut paint_state, cx);
+                ElementPhase::PostPaint {
+                    layout,
+                    paint_state,
+                }
+            }
+            phase @ ElementPhase::Error(_) => phase,
+
+            phase @ _ => {
+                panic!("invalid element phase to call paint: {:?}", phase);
+            }
+        };
+    }
+}
+
+/// 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 ViewContext<V>) -> Result<LayoutId> {
+        self.0.layout(view, cx)
+    }
+
+    pub fn paint(&mut self, view: &mut V, parent_origin: Vector2F, cx: &mut ViewContext<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/elements.rs 🔗

@@ -0,0 +1,10 @@
+pub mod div;
+pub mod hoverable;
+mod img;
+pub mod pressable;
+pub mod svg;
+pub mod text;
+
+pub use div::div;
+pub use img::img;
+pub use svg::svg;

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

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

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

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

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

@@ -0,0 +1,110 @@
+use crate as gpui2;
+use crate::{
+    style::{Style, StyleHelpers, Styleable},
+    Element,
+};
+use futures::FutureExt;
+use gpui::geometry::vector::Vector2F;
+use gpui::scene;
+use gpui2_macros::IntoElement;
+use refineable::RefinementCascade;
+use util::arc_cow::ArcCow;
+use util::ResultExt;
+
+#[derive(IntoElement)]
+pub struct Img {
+    style: RefinementCascade<Style>,
+    uri: Option<ArcCow<'static, str>>,
+}
+
+pub fn img() -> Img {
+    Img {
+        style: RefinementCascade::default(),
+        uri: None,
+    }
+}
+
+impl Img {
+    pub fn uri(mut self, uri: impl Into<ArcCow<'static, str>>) -> Self {
+        self.uri = Some(uri.into());
+        self
+    }
+}
+
+impl<V: 'static> Element<V> for Img {
+    type PaintState = ();
+
+    fn layout(
+        &mut self,
+        _: &mut V,
+        cx: &mut crate::ViewContext<V>,
+    ) -> anyhow::Result<(gpui::LayoutId, Self::PaintState)>
+    where
+        Self: Sized,
+    {
+        let style = self.computed_style();
+        let layout_id = cx.add_layout_node(style, [])?;
+        Ok((layout_id, ()))
+    }
+
+    fn paint(
+        &mut self,
+        _: &mut V,
+        parent_origin: Vector2F,
+        layout: &gpui::Layout,
+        _: &mut Self::PaintState,
+        cx: &mut crate::ViewContext<V>,
+    ) where
+        Self: Sized,
+    {
+        let style = self.computed_style();
+        let bounds = layout.bounds + parent_origin;
+
+        style.paint_background(bounds, cx);
+
+        if let Some(uri) = &self.uri {
+            let image_future = cx.image_cache.get(uri.clone());
+            if let Some(data) = image_future
+                .clone()
+                .now_or_never()
+                .and_then(ResultExt::log_err)
+            {
+                let rem_size = cx.rem_size();
+                cx.scene().push_image(scene::Image {
+                    bounds,
+                    border: gpui::Border {
+                        color: style.border_color.unwrap_or_default().into(),
+                        top: style.border_widths.top.to_pixels(rem_size),
+                        right: style.border_widths.right.to_pixels(rem_size),
+                        bottom: style.border_widths.bottom.to_pixels(rem_size),
+                        left: style.border_widths.left.to_pixels(rem_size),
+                    },
+                    corner_radii: style.corner_radii.to_gpui(bounds.size(), rem_size),
+                    grayscale: false,
+                    data,
+                })
+            } else {
+                cx.spawn(|this, mut cx| async move {
+                    if image_future.await.log_err().is_some() {
+                        this.update(&mut cx, |_, cx| cx.notify()).ok();
+                    }
+                })
+                .detach();
+            }
+        }
+    }
+}
+
+impl Styleable for Img {
+    type Style = Style;
+
+    fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
+        &mut self.style
+    }
+
+    fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
+        self.style.base()
+    }
+}
+
+impl StyleHelpers for Img {}

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

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

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

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

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

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

crates/gpui2/src/gpui2.rs 🔗

@@ -0,0 +1,22 @@
+pub mod adapter;
+pub mod color;
+pub mod element;
+pub mod elements;
+pub mod interactive;
+pub mod style;
+pub mod view;
+pub mod view_context;
+
+pub use color::*;
+pub use element::{AnyElement, Element, IntoElement, Layout, ParentElement};
+pub use geometry::{
+    rect::RectF,
+    vector::{vec2f, Vector2F},
+};
+pub use gpui::*;
+pub use gpui2_macros::{Element, *};
+pub use interactive::*;
+pub use platform::{Platform, WindowBounds, WindowOptions};
+pub use util::arc_cow::ArcCow;
+pub use view::*;
+pub use view_context::ViewContext;

crates/gpui2/src/interactive.rs 🔗

@@ -0,0 +1,165 @@
+use gpui::{
+    geometry::rect::RectF,
+    platform::{MouseButton, MouseButtonEvent},
+    EventContext,
+};
+use smallvec::SmallVec;
+use std::{cell::Cell, rc::Rc};
+
+use crate::ViewContext;
+
+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(move |view, event, cx| {
+                if event.button == button {
+                    handler(view, event, cx)
+                }
+            }));
+        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(move |view, event, cx| {
+                if event.button == button {
+                    handler(view, event, cx)
+                }
+            }));
+        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(move |view, event, cx| {
+                if event.button == button {
+                    handler(view, event, cx)
+                }
+            }));
+        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(move |view, event, cx| {
+                if event.button == button {
+                    handler(view, event, cx)
+                }
+            }));
+        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 ViewContext<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/style.rs 🔗

@@ -0,0 +1,604 @@
+use crate::{
+    color::Hsla,
+    elements::hoverable::{hoverable, Hoverable},
+    elements::pressable::{pressable, Pressable},
+    ViewContext,
+};
+pub use fonts::Style as FontStyle;
+pub use fonts::Weight as FontWeight;
+pub use gpui::taffy::style::{
+    AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
+    Overflow, Position,
+};
+use gpui::{
+    fonts::{self, TextStyleRefinement},
+    geometry::{
+        rect::RectF, relative, vector::Vector2F, AbsoluteLength, DefiniteLength, Edges,
+        EdgesRefinement, Length, Point, PointRefinement, Size, SizeRefinement,
+    },
+    scene, taffy, WindowContext,
+};
+use gpui2_macros::styleable_helpers;
+use refineable::{Refineable, RefinementCascade};
+use std::sync::Arc;
+
+#[derive(Clone, Refineable, Debug)]
+#[refineable(debug)]
+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_widths: Edges<AbsoluteLength>,
+
+    // 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 border color of this element
+    pub border_color: Option<Hsla>,
+
+    /// 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>,
+
+    /// The font size in rems.
+    pub font_size: Option<f32>,
+
+    pub font_family: Option<Arc<str>>,
+
+    pub font_weight: Option<FontWeight>,
+
+    pub font_style: Option<FontStyle>,
+}
+
+impl Style {
+    pub fn text_style(&self, cx: &WindowContext) -> Option<TextStyleRefinement> {
+        if self.text_color.is_none()
+            && self.font_size.is_none()
+            && self.font_family.is_none()
+            && self.font_weight.is_none()
+            && self.font_style.is_none()
+        {
+            return None;
+        }
+
+        Some(TextStyleRefinement {
+            color: self.text_color.map(Into::into),
+            font_family: self.font_family.clone(),
+            font_size: self.font_size.map(|size| size * cx.rem_size()),
+            font_weight: self.font_weight,
+            font_style: self.font_style,
+            underline: None,
+        })
+    }
+
+    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_widths.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.
+    pub fn paint_background<V: 'static>(&self, bounds: RectF, cx: &mut ViewContext<V>) {
+        let rem_size = cx.rem_size();
+        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(bounds.size(), rem_size),
+                border: Default::default(),
+            });
+        }
+    }
+
+    /// Paints the foreground of an element styled with this style.
+    pub fn paint_foreground<V: 'static>(&self, bounds: RectF, cx: &mut ViewContext<V>) {
+        let rem_size = cx.rem_size();
+
+        if let Some(color) = self.border_color {
+            let border = self.border_widths.to_pixels(rem_size);
+            if !border.is_empty() {
+                cx.scene().push_quad(gpui::Quad {
+                    bounds,
+                    background: None,
+                    corner_radii: self.corner_radii.to_gpui(bounds.size(), rem_size),
+                    border: scene::Border {
+                        color: color.into(),
+                        top: border.top,
+                        right: border.right,
+                        bottom: border.bottom,
+                        left: border.left,
+                    },
+                });
+            }
+        }
+    }
+}
+
+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_widths: Edges::<AbsoluteLength>::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,
+            border_color: None,
+            corner_radii: CornerRadii::default(),
+            text_color: None,
+            font_size: Some(1.),
+            font_family: None,
+            font_weight: None,
+            font_style: None,
+        }
+    }
+}
+
+#[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, Debug)]
+#[refineable(debug)]
+pub struct CornerRadii {
+    top_left: AbsoluteLength,
+    top_right: AbsoluteLength,
+    bottom_left: AbsoluteLength,
+    bottom_right: AbsoluteLength,
+}
+
+impl CornerRadii {
+    pub fn to_gpui(&self, box_size: Vector2F, rem_size: f32) -> gpui::scene::CornerRadii {
+        let max_radius = box_size.x().min(box_size.y()) / 2.;
+
+        gpui::scene::CornerRadii {
+            top_left: self.top_left.to_pixels(rem_size).min(max_radius),
+            top_right: self.top_right.to_pixels(rem_size).min(max_radius),
+            bottom_left: self.bottom_left.to_pixels(rem_size).min(max_radius),
+            bottom_right: self.bottom_right.to_pixels(rem_size).min(max_radius),
+        }
+    }
+}
+
+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 hover(self) -> Hoverable<Self>
+    where
+        Self: Sized,
+    {
+        hoverable(self)
+    }
+
+    fn active(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
+    }
+
+    /// size_{n}: Sets width & height to {n}
+    ///
+    /// Example:
+    /// size_1: Sets width & height to 1
+    fn size(mut self, size: Length) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().size.height = Some(size);
+        self.declared_style().size.width = Some(size);
+        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_1(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().flex_grow = Some(1.);
+        self.declared_style().flex_shrink = Some(1.);
+        self.declared_style().flex_basis = Some(relative(0.));
+        self
+    }
+
+    fn flex_auto(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().flex_grow = Some(1.);
+        self.declared_style().flex_shrink = Some(1.);
+        self.declared_style().flex_basis = Some(Length::Auto);
+        self
+    }
+
+    fn flex_initial(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().flex_grow = Some(0.);
+        self.declared_style().flex_shrink = Some(1.);
+        self.declared_style().flex_basis = Some(Length::Auto);
+        self
+    }
+
+    fn flex_none(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().flex_grow = Some(0.);
+        self.declared_style().flex_shrink = Some(0.);
+        self
+    }
+
+    fn grow(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().flex_grow = Some(1.);
+        self
+    }
+
+    fn items_start(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().align_items = Some(AlignItems::FlexStart);
+        self
+    }
+
+    fn items_end(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().align_items = Some(AlignItems::FlexEnd);
+        self
+    }
+
+    fn items_center(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().align_items = Some(AlignItems::Center);
+        self
+    }
+
+    fn justify_between(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().justify_content = Some(JustifyContent::SpaceBetween);
+        self
+    }
+
+    fn justify_center(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().justify_content = Some(JustifyContent::Center);
+        self
+    }
+
+    fn justify_start(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().justify_content = Some(JustifyContent::Start);
+        self
+    }
+
+    fn justify_end(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().justify_content = Some(JustifyContent::End);
+        self
+    }
+
+    fn justify_around(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().justify_content = Some(JustifyContent::SpaceAround);
+        self
+    }
+
+    fn fill<F>(mut self, fill: F) -> Self
+    where
+        F: Into<Fill>,
+        Self: Sized,
+    {
+        self.declared_style().fill = Some(fill.into());
+        self
+    }
+
+    fn border_color<C>(mut self, border_color: C) -> Self
+    where
+        C: Into<Hsla>,
+        Self: Sized,
+    {
+        self.declared_style().border_color = Some(border_color.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
+    }
+
+    fn text_xs(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().font_size = Some(0.75);
+        self
+    }
+
+    fn text_sm(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().font_size = Some(0.875);
+        self
+    }
+
+    fn text_base(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().font_size = Some(1.0);
+        self
+    }
+
+    fn text_lg(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().font_size = Some(1.125);
+        self
+    }
+
+    fn text_xl(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().font_size = Some(1.25);
+        self
+    }
+
+    fn text_2xl(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().font_size = Some(1.5);
+        self
+    }
+
+    fn text_3xl(mut self) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().font_size = Some(1.875);
+        self
+    }
+
+    fn font(mut self, family_name: impl Into<Arc<str>>) -> Self
+    where
+        Self: Sized,
+    {
+        self.declared_style().font_family = Some(family_name.into());
+        self
+    }
+}

crates/gpui2/src/view_context.rs 🔗

@@ -0,0 +1,79 @@
+use std::{any::TypeId, rc::Rc};
+
+use crate::{element::LayoutId, style::Style};
+use anyhow::{anyhow, Result};
+use derive_more::{Deref, DerefMut};
+use gpui::{geometry::Size, scene::EventHandler, EventContext, Layout, MeasureParams};
+pub use gpui::{taffy::tree::NodeId, ViewContext as LegacyViewContext};
+
+#[derive(Deref, DerefMut)]
+pub struct ViewContext<'a, 'b, 'c, V> {
+    #[deref]
+    #[deref_mut]
+    pub(crate) legacy_cx: &'c mut LegacyViewContext<'a, 'b, V>,
+}
+
+impl<'a, 'b, 'c, V: 'static> ViewContext<'a, 'b, 'c, V> {
+    pub fn new(legacy_cx: &'c mut LegacyViewContext<'a, 'b, 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_size();
+        let style = style.to_taffy(rem_size);
+        let id = self
+            .legacy_cx
+            .layout_engine()
+            .ok_or_else(|| anyhow!("no layout engine"))?
+            .add_node(style, 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_size();
+        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)
+    }
+
+    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/gpui/playground_macros/Cargo.toml → crates/gpui2_macros/Cargo.toml 🔗

@@ -1,11 +1,11 @@
 [package]
-name = "playground_macros"
+name = "gpui2_macros"
 version = "0.1.0"
 edition = "2021"
 publish = false
 
 [lib]
-path = "src/playground_macros.rs"
+path = "src/gpui2_macros.rs"
 proc-macro = true
 
 [dependencies]

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

@@ -59,28 +59,30 @@ 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 Layout = Option<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::Layout<V, Self::Layout>> {
-                let mut element = self.render(view, cx).into_any();
-                let layout_id = element.layout(view, cx)?;
-                Ok(playground::element::Layout::new(layout_id, Some(element)))
+                cx: &mut gpui2::ViewContext<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))
             }
 
             fn paint(
                 &mut self,
                 view: &mut V,
-                layout: &mut playground::element::Layout<V, Self::Layout>,
-                cx: &mut playground::element::PaintContext<V>,
+                parent_origin: gpui2::Vector2F,
+                _: &gpui2::element::Layout,
+                rendered_element: &mut Self::PaintState,
+                cx: &mut gpui2::ViewContext<V>,
             ) {
-                layout.paint(view, cx);
+                rendered_element.paint(view, parent_origin, cx);
             }
         }
 

crates/gpui/playground_macros/src/derive_into_element.rs → 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;

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

@@ -3,7 +3,6 @@ use proc_macro::TokenStream;
 mod derive_element;
 mod derive_into_element;
 mod styleable_helpers;
-mod tailwind_lengths;
 
 #[proc_macro]
 pub fn styleable_helpers(args: TokenStream) -> TokenStream {
@@ -19,8 +18,3 @@ pub fn derive_element(input: TokenStream) -> TokenStream {
 pub fn derive_into_element(input: TokenStream) -> TokenStream {
     derive_into_element::derive_into_element(input)
 }
-
-#[proc_macro_attribute]
-pub fn tailwind_lengths(attr: TokenStream, item: TokenStream) -> TokenStream {
-    tailwind_lengths::tailwind_lengths(attr, item)
-}

crates/gpui2_macros/src/styleable_helpers.rs 🔗

@@ -0,0 +1,327 @@
+use proc_macro::TokenStream;
+use proc_macro2::TokenStream as TokenStream2;
+use quote::{format_ident, quote};
+use syn::{
+    parse::{Parse, ParseStream, Result},
+    parse_macro_input,
+};
+
+struct StyleableMacroInput;
+
+impl Parse for StyleableMacroInput {
+    fn parse(_input: ParseStream) -> Result<Self> {
+        Ok(StyleableMacroInput)
+    }
+}
+
+pub fn styleable_helpers(input: TokenStream) -> TokenStream {
+    let _ = parse_macro_input!(input as StyleableMacroInput);
+    let methods = generate_methods();
+    let output = quote! {
+        #(#methods)*
+    };
+
+    output.into()
+}
+
+fn generate_methods() -> Vec<TokenStream2> {
+    let mut methods = Vec::new();
+
+    for (prefix, auto_allowed, fields) in box_prefixes() {
+        for (suffix, length_tokens) in box_suffixes() {
+            if auto_allowed || suffix != "auto" {
+                let method = generate_method(prefix, suffix, &fields, length_tokens);
+                methods.push(method);
+            }
+        }
+    }
+
+    for (prefix, fields) in corner_prefixes() {
+        for (suffix, radius_tokens) in corner_suffixes() {
+            let method = generate_method(prefix, suffix, &fields, radius_tokens);
+            methods.push(method);
+        }
+    }
+
+    for (prefix, fields) in border_prefixes() {
+        for (suffix, width_tokens) in border_suffixes() {
+            let method = generate_method(prefix, suffix, &fields, width_tokens);
+            methods.push(method);
+        }
+    }
+
+    methods
+}
+
+fn generate_method(
+    prefix: &'static str,
+    suffix: &'static str,
+    fields: &Vec<TokenStream2>,
+    length_tokens: TokenStream2,
+) -> TokenStream2 {
+    let method_name = if suffix.is_empty() {
+        format_ident!("{}", prefix)
+    } else {
+        format_ident!("{}_{}", prefix, suffix)
+    };
+
+    let field_assignments = fields
+        .iter()
+        .map(|field_tokens| {
+            quote! {
+                style.#field_tokens = Some(gpui::geometry::#length_tokens);
+            }
+        })
+        .collect::<Vec<_>>();
+
+    let method = quote! {
+        fn #method_name(mut self) -> Self where Self: std::marker::Sized {
+            let mut style = self.declared_style();
+            #(#field_assignments)*
+            self
+        }
+    };
+
+    method
+}
+
+fn box_prefixes() -> Vec<(&'static str, bool, Vec<TokenStream2>)> {
+    vec![
+        ("w", true, vec![quote! { size.width }]),
+        ("h", true, vec![quote! { size.height }]),
+        (
+            "size",
+            true,
+            vec![quote! {size.width}, quote! {size.height}],
+        ),
+        ("min_w", false, vec![quote! { min_size.width }]),
+        ("min_h", false, vec![quote! { min_size.height }]),
+        ("max_w", false, vec![quote! { max_size.width }]),
+        ("max_h", false, vec![quote! { max_size.height }]),
+        (
+            "m",
+            true,
+            vec![
+                quote! { margin.top },
+                quote! { margin.bottom },
+                quote! { margin.left },
+                quote! { margin.right },
+            ],
+        ),
+        ("mt", true, vec![quote! { margin.top }]),
+        ("mb", true, vec![quote! { margin.bottom }]),
+        (
+            "my",
+            true,
+            vec![quote! { margin.top }, quote! { margin.bottom }],
+        ),
+        (
+            "mx",
+            true,
+            vec![quote! { margin.left }, quote! { margin.right }],
+        ),
+        ("ml", true, vec![quote! { margin.left }]),
+        ("mr", true, vec![quote! { margin.right }]),
+        (
+            "p",
+            false,
+            vec![
+                quote! { padding.top },
+                quote! { padding.bottom },
+                quote! { padding.left },
+                quote! { padding.right },
+            ],
+        ),
+        ("pt", false, vec![quote! { padding.top }]),
+        ("pb", false, vec![quote! { padding.bottom }]),
+        (
+            "px",
+            false,
+            vec![quote! { padding.left }, quote! { padding.right }],
+        ),
+        (
+            "py",
+            false,
+            vec![quote! { padding.top }, quote! { padding.bottom }],
+        ),
+        ("pl", false, vec![quote! { padding.left }]),
+        ("pr", false, vec![quote! { padding.right }]),
+        ("top", true, vec![quote! { inset.top }]),
+        ("bottom", true, vec![quote! { inset.bottom }]),
+        ("left", true, vec![quote! { inset.left }]),
+        ("right", true, vec![quote! { inset.right }]),
+        (
+            "gap",
+            false,
+            vec![quote! { gap.width }, quote! { gap.height }],
+        ),
+        ("gap_x", false, vec![quote! { gap.width }]),
+        ("gap_y", false, vec![quote! { gap.height }]),
+    ]
+}
+
+fn box_suffixes() -> Vec<(&'static str, TokenStream2)> {
+    vec![
+        ("0", quote! { pixels(0.) }),
+        ("0p5", quote! { rems(0.125) }),
+        ("1", quote! { rems(0.25) }),
+        ("1p5", quote! { rems(0.375) }),
+        ("2", quote! { rems(0.5) }),
+        ("2p5", quote! { rems(0.625) }),
+        ("3", quote! { rems(0.75) }),
+        ("3p5", quote! { rems(0.875) }),
+        ("4", quote! { rems(1.) }),
+        ("5", quote! { rems(1.25) }),
+        ("6", quote! { rems(1.5) }),
+        ("7", quote! { rems(1.75) }),
+        ("8", quote! { rems(2.0) }),
+        ("9", quote! { rems(2.25) }),
+        ("10", quote! { rems(2.5) }),
+        ("11", quote! { rems(2.75) }),
+        ("12", quote! { rems(3.) }),
+        ("16", quote! { rems(4.) }),
+        ("20", quote! { rems(5.) }),
+        ("24", quote! { rems(6.) }),
+        ("32", quote! { rems(8.) }),
+        ("40", quote! { rems(10.) }),
+        ("48", quote! { rems(12.) }),
+        ("56", quote! { rems(14.) }),
+        ("64", quote! { rems(16.) }),
+        ("72", quote! { rems(18.) }),
+        ("80", quote! { rems(20.) }),
+        ("96", quote! { rems(24.) }),
+        ("auto", quote! { auto() }),
+        ("px", quote! { pixels(1.) }),
+        ("full", quote! { relative(1.) }),
+        ("1_2", quote! { relative(0.5) }),
+        ("1_3", quote! { relative(1./3.) }),
+        ("2_3", quote! { relative(2./3.) }),
+        ("1_4", quote! { relative(0.25) }),
+        ("2_4", quote! { relative(0.5) }),
+        ("3_4", quote! { relative(0.75) }),
+        ("1_5", quote! { relative(0.2) }),
+        ("2_5", quote! { relative(0.4) }),
+        ("3_5", quote! { relative(0.6) }),
+        ("4_5", quote! { relative(0.8) }),
+        ("1_6", quote! { relative(1./6.) }),
+        ("5_6", quote! { relative(5./6.) }),
+        ("1_12", quote! { relative(1./12.) }),
+        // ("screen_50", quote! { DefiniteLength::Vh(50.0) }),
+        // ("screen_75", quote! { DefiniteLength::Vh(75.0) }),
+        // ("screen", quote! { DefiniteLength::Vh(100.0) }),
+    ]
+}
+
+fn corner_prefixes() -> Vec<(&'static str, Vec<TokenStream2>)> {
+    vec![
+        (
+            "rounded",
+            vec![
+                quote! { corner_radii.top_left },
+                quote! { corner_radii.top_right },
+                quote! { corner_radii.bottom_right },
+                quote! { corner_radii.bottom_left },
+            ],
+        ),
+        (
+            "rounded_t",
+            vec![
+                quote! { corner_radii.top_left },
+                quote! { corner_radii.top_right },
+            ],
+        ),
+        (
+            "rounded_b",
+            vec![
+                quote! { corner_radii.bottom_left },
+                quote! { corner_radii.bottom_right },
+            ],
+        ),
+        (
+            "rounded_r",
+            vec![
+                quote! { corner_radii.top_right },
+                quote! { corner_radii.bottom_right },
+            ],
+        ),
+        (
+            "rounded_l",
+            vec![
+                quote! { corner_radii.top_left },
+                quote! { corner_radii.bottom_left },
+            ],
+        ),
+        ("rounded_tl", vec![quote! { corner_radii.top_left }]),
+        ("rounded_tr", vec![quote! { corner_radii.top_right }]),
+        ("rounded_bl", vec![quote! { corner_radii.bottom_left }]),
+        ("rounded_br", vec![quote! { corner_radii.bottom_right }]),
+    ]
+}
+
+fn corner_suffixes() -> Vec<(&'static str, TokenStream2)> {
+    vec![
+        ("none", quote! { pixels(0.) }),
+        ("sm", quote! { rems(0.125) }),
+        ("md", quote! { rems(0.25) }),
+        ("lg", quote! { rems(0.5) }),
+        ("xl", quote! { rems(0.75) }),
+        ("2xl", quote! { rems(1.) }),
+        ("3xl", quote! { rems(1.5) }),
+        ("full", quote! {  pixels(9999.) }),
+    ]
+}
+
+fn border_prefixes() -> Vec<(&'static str, Vec<TokenStream2>)> {
+    vec![
+        (
+            "border",
+            vec![
+                quote! { border_widths.top },
+                quote! { border_widths.right },
+                quote! { border_widths.bottom },
+                quote! { border_widths.left },
+            ],
+        ),
+        ("border_t", vec![quote! { border_widths.top }]),
+        ("border_b", vec![quote! { border_widths.bottom }]),
+        ("border_r", vec![quote! { border_widths.right }]),
+        ("border_l", vec![quote! { border_widths.left }]),
+        (
+            "border_x",
+            vec![
+                quote! { border_widths.left },
+                quote! { border_widths.right },
+            ],
+        ),
+        (
+            "border_y",
+            vec![
+                quote! { border_widths.top },
+                quote! { border_widths.bottom },
+            ],
+        ),
+    ]
+}
+
+fn border_suffixes() -> Vec<(&'static str, TokenStream2)> {
+    vec![
+        ("", quote! { pixels(1.) }),
+        ("0", quote! { pixels(0.) }),
+        ("1", quote! { pixels(1.) }),
+        ("2", quote! { pixels(2.) }),
+        ("3", quote! { pixels(3.) }),
+        ("4", quote! { pixels(4.) }),
+        ("5", quote! { pixels(5.) }),
+        ("6", quote! { pixels(6.) }),
+        ("7", quote! { pixels(7.) }),
+        ("8", quote! { pixels(8.) }),
+        ("9", quote! { pixels(9.) }),
+        ("10", quote! { pixels(10.) }),
+        ("11", quote! { pixels(11.) }),
+        ("12", quote! { pixels(12.) }),
+        ("16", quote! { pixels(16.) }),
+        ("20", quote! { pixels(20.) }),
+        ("24", quote! { pixels(24.) }),
+        ("32", quote! { pixels(32.) }),
+    ]
+}

crates/gpui_macros/src/gpui_macros.rs 🔗

@@ -329,7 +329,7 @@ pub fn element_derive(input: TokenStream) -> TokenStream {
                 &mut self,
                 constraint: gpui::SizeConstraint,
                 view: &mut V,
-                cx: &mut gpui::LayoutContext<V>,
+                cx: &mut gpui::ViewContext<V>,
             ) -> (gpui::geometry::vector::Vector2F, gpui::elements::AnyElement<V>) {
                 let mut element = self.render(view, cx).into_any();
                 let size = element.layout(constraint, view, cx);
@@ -338,14 +338,13 @@ pub fn element_derive(input: TokenStream) -> TokenStream {
 
             fn paint(
                 &mut self,
-                scene: &mut gpui::SceneBuilder,
                 bounds: gpui::geometry::rect::RectF,
                 visible_bounds: gpui::geometry::rect::RectF,
                 element: &mut gpui::elements::AnyElement<V>,
                 view: &mut V,
-                cx: &mut gpui::PaintContext<V>,
+                cx: &mut gpui::ViewContext<V>,
             ) {
-                element.paint(scene, bounds.origin(), visible_bounds, view, cx);
+                element.paint(bounds.origin(), visible_bounds, view, cx);
             }
 
             fn rect_for_text_range(

crates/refineable/derive_refineable/src/derive_refineable.rs 🔗

@@ -12,9 +12,14 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
         ident,
         data,
         generics,
+        attrs,
         ..
     } = parse_macro_input!(input);
 
+    let impl_debug_on_refinement = attrs
+        .iter()
+        .any(|attr| attr.path.is_ident("refineable") && attr.tokens.to_string().contains("debug"));
+
     let refinement_ident = format_ident!("{}Refinement", ident);
     let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
 
@@ -120,6 +125,41 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
         })
         .collect();
 
+    let debug_impl = if impl_debug_on_refinement {
+        let refinement_field_debugs: Vec<TokenStream2> = fields
+            .iter()
+            .map(|field| {
+                let name = &field.ident;
+                quote! {
+                    if self.#name.is_some() {
+                        debug_struct.field(stringify!(#name), &self.#name);
+                    } else {
+                        all_some = false;
+                    }
+                }
+            })
+            .collect();
+
+        quote! {
+            impl #impl_generics std::fmt::Debug for #refinement_ident #ty_generics
+                #where_clause
+            {
+                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+                    let mut debug_struct = f.debug_struct(stringify!(#refinement_ident));
+                    let mut all_some = true;
+                    #( #refinement_field_debugs )*
+                    if all_some {
+                        debug_struct.finish()
+                    } else {
+                        debug_struct.finish_non_exhaustive()
+                    }
+                }
+            }
+        }
+    } else {
+        quote! {}
+    };
+
     let gen = quote! {
         #[derive(Default, Clone)]
         pub struct #refinement_ident #impl_generics {
@@ -145,8 +185,22 @@ pub fn derive_refineable(input: TokenStream) -> TokenStream {
                 #( #refinement_field_assignments )*
             }
         }
-    };
 
+        impl #impl_generics #refinement_ident #ty_generics
+            #where_clause
+        {
+            pub fn is_some(&self) -> bool {
+                #(
+                    if self.#field_names.is_some() {
+                        return true;
+                    }
+                )*
+                false
+            }
+        }
+
+        #debug_impl
+    };
     gen.into()
 }
 

crates/refineable/src/refineable.rs 🔗

@@ -1,7 +1,7 @@
 pub use derive_refineable::Refineable;
 
-pub trait Refineable {
-    type Refinement: Default;
+pub trait Refineable: Clone {
+    type Refinement: Refineable<Refinement = Self::Refinement> + Default;
 
     fn refine(&mut self, refinement: &Self::Refinement);
     fn refined(mut self, refinement: &Self::Refinement) -> Self
@@ -11,4 +11,46 @@ pub trait Refineable {
         self.refine(refinement);
         self
     }
+    fn from_refinement(refinement: &Self::Refinement) -> Self
+    where
+        Self: Default + Sized,
+    {
+        Self::default().refined(refinement)
+    }
+}
+
+pub struct RefinementCascade<S: Refineable>(Vec<Option<S::Refinement>>);
+
+impl<S: Refineable + Default> Default for RefinementCascade<S> {
+    fn default() -> Self {
+        Self(vec![Some(Default::default())])
+    }
+}
+
+#[derive(Copy, Clone)]
+pub struct CascadeSlot(usize);
+
+impl<S: Refineable + Default> RefinementCascade<S> {
+    pub fn reserve(&mut self) -> CascadeSlot {
+        self.0.push(None);
+        return CascadeSlot(self.0.len() - 1);
+    }
+
+    pub fn base(&mut self) -> &mut S::Refinement {
+        self.0[0].as_mut().unwrap()
+    }
+
+    pub fn set(&mut self, slot: CascadeSlot, refinement: Option<S::Refinement>) {
+        self.0[slot.0] = refinement
+    }
+
+    pub fn merged(&self) -> S::Refinement {
+        let mut merged = self.0[0].clone().unwrap();
+        for refinement in self.0.iter().skip(1) {
+            if let Some(refinement) = refinement {
+                merged.refine(refinement);
+            }
+        }
+        merged
+    }
 }

crates/gpui/playground/Cargo.lock → crates/storybook/Cargo.lock 🔗

@@ -1686,7 +1686,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
 
 [[package]]
-name = "playground"
+name = "storybook"
 version = "0.1.0"
 dependencies = [
  "gpui",

crates/storybook/Cargo.toml 🔗

@@ -0,0 +1,23 @@
+[package]
+name = "storybook"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[[bin]]
+name = "storybook"
+path = "src/storybook.rs"
+
+[dependencies]
+gpui2 = { path = "../gpui2" }
+anyhow.workspace = true
+log.workspace = true
+rust-embed.workspace = true
+serde.workspace = true
+settings = { path = "../settings" }
+simplelog = "0.9"
+theme = { path = "../theme" }
+util = { path = "../util" }
+
+[dev-dependencies]
+gpui2 = { path = "../gpui2", features = ["test-support"] }

crates/storybook/src/collab_panel.rs 🔗

@@ -0,0 +1,177 @@
+use crate::theme::{theme, Theme};
+use gpui2::{
+    elements::{div, div::ScrollState, img, svg},
+    style::{StyleHelpers, Styleable},
+    ArcCow, Element, IntoElement, ParentElement, ViewContext,
+};
+use std::marker::PhantomData;
+
+#[derive(Element)]
+pub struct CollabPanelElement<V: 'static> {
+    view_type: PhantomData<V>,
+    scroll_state: ScrollState,
+}
+
+// When I improve child view rendering, I'd like to have V implement a trait  that
+// provides the scroll state, among other things.
+pub fn collab_panel<V: 'static>(scroll_state: ScrollState) -> CollabPanelElement<V> {
+    CollabPanelElement {
+        view_type: PhantomData,
+        scroll_state,
+    }
+}
+
+impl<V: 'static> CollabPanelElement<V> {
+    fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+        let theme = theme(cx);
+
+        // Panel
+        div()
+            .w_64()
+            .h_full()
+            .flex()
+            .flex_col()
+            .font("Zed Sans Extended")
+            .text_color(theme.middle.base.default.foreground)
+            .border_color(theme.middle.base.default.border)
+            .border()
+            .fill(theme.middle.base.default.background)
+            .child(
+                div()
+                    .w_full()
+                    .flex()
+                    .flex_col()
+                    .overflow_y_scroll(self.scroll_state.clone())
+                    // List Container
+                    .child(
+                        div()
+                            .fill(theme.lowest.base.default.background)
+                            .pb_1()
+                            .border_color(theme.lowest.base.default.border)
+                            .border_b()
+                            //:: https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-parent-state
+                            // .group()
+                            // List Section Header
+                            .child(self.list_section_header("#CRDB", true, theme))
+                            // List Item Large
+                            .child(self.list_item(
+                                "http://github.com/maxbrunsfeld.png?s=50",
+                                "maxbrunsfeld",
+                                theme,
+                            )),
+                    )
+                    .child(
+                        div()
+                            .py_2()
+                            .flex()
+                            .flex_col()
+                            .child(self.list_section_header("CHANNELS", true, theme)),
+                    )
+                    .child(
+                        div()
+                            .py_2()
+                            .flex()
+                            .flex_col()
+                            .child(self.list_section_header("CONTACTS", true, theme))
+                            .children(
+                                std::iter::repeat_with(|| {
+                                    vec![
+                                        self.list_item(
+                                            "http://github.com/as-cii.png?s=50",
+                                            "as-cii",
+                                            theme,
+                                        ),
+                                        self.list_item(
+                                            "http://github.com/nathansobo.png?s=50",
+                                            "nathansobo",
+                                            theme,
+                                        ),
+                                        self.list_item(
+                                            "http://github.com/maxbrunsfeld.png?s=50",
+                                            "maxbrunsfeld",
+                                            theme,
+                                        ),
+                                    ]
+                                })
+                                .take(10)
+                                .flatten(),
+                            ),
+                    ),
+            )
+            .child(
+                div()
+                    .h_7()
+                    .px_2()
+                    .border_t()
+                    .border_color(theme.middle.variant.default.border)
+                    .flex()
+                    .items_center()
+                    .child(
+                        div()
+                            .text_sm()
+                            .text_color(theme.middle.variant.default.foreground)
+                            .child("Find..."),
+                    ),
+            )
+    }
+
+    fn list_section_header(
+        &self,
+        label: impl Into<ArcCow<'static, str>>,
+        expanded: bool,
+        theme: &Theme,
+    ) -> impl Element<V> {
+        div()
+            .h_7()
+            .px_2()
+            .flex()
+            .justify_between()
+            .items_center()
+            .child(div().flex().gap_1().text_sm().child(label))
+            .child(
+                div().flex().h_full().gap_1().items_center().child(
+                    svg()
+                        .path(if expanded {
+                            "icons/radix/caret-down.svg"
+                        } else {
+                            "icons/radix/caret-up.svg"
+                        })
+                        .w_3p5()
+                        .h_3p5()
+                        .fill(theme.middle.variant.default.foreground),
+                ),
+            )
+    }
+
+    fn list_item(
+        &self,
+        avatar_uri: impl Into<ArcCow<'static, str>>,
+        label: impl Into<ArcCow<'static, str>>,
+        theme: &Theme,
+    ) -> impl Element<V> {
+        div()
+            .h_7()
+            .px_2()
+            .flex()
+            .items_center()
+            .hover()
+            .fill(theme.lowest.variant.hovered.background)
+            .active()
+            .fill(theme.lowest.variant.pressed.background)
+            .child(
+                div()
+                    .flex()
+                    .items_center()
+                    .gap_1()
+                    .text_sm()
+                    .child(
+                        img()
+                            .uri(avatar_uri)
+                            .size_3p5()
+                            .rounded_full()
+                            .fill(theme.middle.positive.default.foreground),
+                    )
+                    .child(label),
+            )
+    }
+}

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

@@ -1,16 +1,11 @@
-use crate::{
-    div::div,
-    element::{Element, ParentElement},
-    style::StyleHelpers,
-    text::ArcCow,
-    themes::rose_pine,
+use gpui2::{
+    elements::div, interactive::Interactive, platform::MouseButton, style::StyleHelpers, ArcCow,
+    Element, EventContext, IntoElement, ParentElement, ViewContext,
 };
-use gpui::ViewContext;
-use playground_macros::Element;
 use std::{marker::PhantomData, rc::Rc};
 
 struct ButtonHandlers<V, D> {
-    click: Option<Rc<dyn Fn(&mut V, &D, &mut ViewContext<V>)>>,
+    click: Option<Rc<dyn Fn(&mut V, &D, &mut EventContext<V>)>>,
 }
 
 impl<V, D> Default for ButtonHandlers<V, D> {
@@ -19,7 +14,6 @@ impl<V, D> Default for ButtonHandlers<V, D> {
     }
 }
 
-use crate as playground;
 #[derive(Element)]
 pub struct Button<V: 'static, D: 'static> {
     handlers: ButtonHandlers<V, D>,
@@ -53,7 +47,7 @@ impl<V: 'static> Button<V, ()> {
     }
 }
 
-// Impl block for *any* button.
+// 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());
@@ -65,12 +59,13 @@ impl<V: 'static, D: 'static> Button<V, D> {
         self
     }
 
-    // pub fn click(self, handler: impl Fn(&mut V, &D, &mut ViewContext<V>) + 'static) -> Self {
-    //     let data = self.data.clone();
-    //     Self::click(self, MouseButton::Left, move |view, _, cx| {
-    //         handler(view, data.as_ref(), cx);
-    //     })
-    // }
+    pub fn on_click(
+        mut self,
+        handler: impl Fn(&mut V, &D, &mut EventContext<V>) + 'static,
+    ) -> Self {
+        self.handlers.click = Some(Rc::new(handler));
+        self
+    }
 }
 
 pub fn button<V>() -> Button<V, ()> {
@@ -78,23 +73,25 @@ pub fn button<V>() -> Button<V, ()> {
 }
 
 impl<V: 'static, D: 'static> Button<V, D> {
-    fn render(&mut self, view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
-        // TODO: Drive theme from the context
+    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(rose_pine::dawn().error(0.5))
+            // .fill(colors.error(0.5))
             .h_4()
             .children(self.label.clone());
 
-        button
-
-        // TODO: Event handling
-        // if let Some(handler) = self.handlers.click.clone() {
-        //     let data = self.data.clone();
-        //     // button.mouse_down(MouseButton::Left, move |view, event, cx| {
-        //     //     handler(view, data.as_ref(), cx)
-        //     // })
-        // } else {
-        //     button
-        // }
+        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/storybook/src/element_ext.rs 🔗

@@ -0,0 +1,22 @@
+use crate::theme::{Theme, Themed};
+use gpui2::Element;
+use std::marker::PhantomData;
+
+pub trait ElementExt<V: 'static>: Element<V> {
+    fn themed(self, theme: Theme) -> Themed<V, Self>
+    where
+        Self: Sized;
+}
+
+impl<V: 'static, E: Element<V>> ElementExt<V> for E {
+    fn themed(self, theme: Theme) -> Themed<V, Self>
+    where
+        Self: Sized,
+    {
+        Themed {
+            child: self,
+            theme,
+            view_type: PhantomData,
+        }
+    }
+}

crates/storybook/src/storybook.rs 🔗

@@ -0,0 +1,109 @@
+#![allow(dead_code, unused_variables)]
+
+use crate::theme::Theme;
+use ::theme as legacy_theme;
+use element_ext::ElementExt;
+use gpui2::{serde_json, vec2f, view, Element, RectF, ViewContext, WindowBounds};
+use legacy_theme::ThemeSettings;
+use log::LevelFilter;
+use settings::{default_settings, SettingsStore};
+use simplelog::SimpleLogger;
+
+mod collab_panel;
+mod components;
+mod element_ext;
+mod theme;
+mod workspace;
+
+gpui2::actions! {
+    storybook,
+    [ToggleInspector]
+}
+
+fn main() {
+    SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
+
+    gpui2::App::new(Assets).unwrap().run(|cx| {
+        let mut store = SettingsStore::default();
+        store
+            .set_default_settings(default_settings().as_ref(), cx)
+            .unwrap();
+        cx.set_global(store);
+        legacy_theme::init(Assets, cx);
+        // load_embedded_fonts(cx.platform().as_ref());
+
+        cx.add_window(
+            gpui2::WindowOptions {
+                bounds: WindowBounds::Fixed(RectF::new(vec2f(0., 0.), vec2f(1400., 900.))),
+                center: true,
+                ..Default::default()
+            },
+            |cx| {
+                view(|cx| {
+                    cx.enable_inspector();
+                    storybook(&mut ViewContext::new(cx))
+                })
+            },
+        );
+        cx.platform().activate(true);
+    });
+}
+
+fn storybook<V: 'static>(cx: &mut ViewContext<V>) -> impl Element<V> {
+    workspace().themed(current_theme(cx))
+}
+
+// Nathan: During the transition to gpui2, 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 gpui2::AssetSource;
+use rust_embed::RustEmbed;
+use workspace::workspace;
+
+#[derive(RustEmbed)]
+#[folder = "../../assets"]
+#[include = "themes/**/*"]
+#[include = "fonts/**/*"]
+#[include = "icons/**/*"]
+#[exclude = "*.DS_Store"]
+pub struct Assets;
+
+impl AssetSource for Assets {
+    fn load(&self, path: &str) -> Result<std::borrow::Cow<[u8]>> {
+        Self::get(path)
+            .map(|f| f.data)
+            .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))
+    }
+
+    fn list(&self, path: &str) -> Vec<std::borrow::Cow<'static, str>> {
+        Self::iter().filter(|p| p.starts_with(path)).collect()
+    }
+}
+
+// fn load_embedded_fonts(platform: &dyn gpui2::Platform) {
+//     let font_paths = Assets.list("fonts");
+//     let mut embedded_fonts = Vec::new();
+//     for font_path in &font_paths {
+//         if font_path.ends_with(".ttf") {
+//             let font_path = &*font_path;
+//             let font_bytes = Assets.load(font_path).unwrap().to_vec();
+//             embedded_fonts.push(Arc::from(font_bytes));
+//         }
+//     }
+//     platform.fonts().add_fonts(&embedded_fonts).unwrap();
+// }

crates/storybook/src/theme.rs 🔗

@@ -0,0 +1,192 @@
+use gpui2::{
+    color::Hsla, element::Element, serde_json, AppContext, IntoElement, Vector2F, ViewContext,
+    WindowContext,
+};
+use serde::{de::Visitor, Deserialize, Deserializer};
+use std::{collections::HashMap, fmt, marker::PhantomData};
+use theme::ThemeSettings;
+
+#[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>,
+}
+
+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)
+}
+
+#[derive(IntoElement)]
+pub struct Themed<V: 'static, E: Element<V>> {
+    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 ViewContext<V>,
+    ) -> anyhow::Result<(gpui2::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,
+        parent_origin: Vector2F,
+        layout: &gpui2::Layout,
+        state: &mut Self::PaintState,
+        cx: &mut ViewContext<V>,
+    ) where
+        Self: Sized,
+    {
+        cx.push_theme(self.theme.clone());
+        self.child.paint(view, parent_origin, layout, state, cx);
+        cx.pop_theme();
+    }
+}
+
+fn preferred_theme<V: 'static>(cx: &AppContext) -> 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()
+}
+
+pub fn theme<'a>(cx: &'a WindowContext) -> &'a Theme {
+    cx.theme::<Theme>()
+}

crates/storybook/src/workspace.rs 🔗

@@ -0,0 +1,435 @@
+use crate::{collab_panel::collab_panel, theme::theme};
+use gpui2::{
+    elements::{div, div::ScrollState, img, svg},
+    style::{StyleHelpers, Styleable},
+    Element, IntoElement, ParentElement, ViewContext,
+};
+
+#[derive(Element, Default)]
+struct WorkspaceElement {
+    left_scroll_state: ScrollState,
+    right_scroll_state: ScrollState,
+}
+
+pub fn workspace<V: 'static>() -> impl Element<V> {
+    WorkspaceElement::default()
+}
+
+impl WorkspaceElement {
+    fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+        let theme = theme(cx);
+
+        div()
+            .size_full()
+            .flex()
+            .flex_col()
+            .font("Zed Sans Extended")
+            .gap_0()
+            .justify_start()
+            .items_start()
+            .text_color(theme.lowest.base.default.foreground)
+            .fill(theme.middle.base.default.background)
+            .child(titlebar())
+            .child(
+                div()
+                    .flex_1()
+                    .w_full()
+                    .flex()
+                    .flex_row()
+                    .overflow_hidden()
+                    .child(collab_panel(self.left_scroll_state.clone()))
+                    .child(div().h_full().flex_1())
+                    .child(collab_panel(self.right_scroll_state.clone())),
+            )
+            .child(statusbar())
+    }
+}
+
+#[derive(Element)]
+struct TitleBar;
+
+pub fn titlebar<V: 'static>() -> impl Element<V> {
+    TitleBar
+}
+
+impl TitleBar {
+    fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+        let theme = theme(cx);
+        div()
+            .flex()
+            .items_center()
+            .justify_between()
+            .w_full()
+            .h_8()
+            .fill(theme.lowest.base.default.background)
+            .child(self.left_group(cx))
+            .child(self.right_group(cx))
+    }
+
+    fn left_group<V: 'static>(&mut self, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+        let theme = theme(cx);
+        div()
+            .flex()
+            .items_center()
+            .h_full()
+            .gap_4()
+            .px_2()
+            // === Traffic Lights === //
+            .child(
+                div()
+                    .flex()
+                    .items_center()
+                    .gap_2()
+                    .child(
+                        div()
+                            .w_3()
+                            .h_3()
+                            .rounded_full()
+                            .fill(theme.lowest.positive.default.foreground),
+                    )
+                    .child(
+                        div()
+                            .w_3()
+                            .h_3()
+                            .rounded_full()
+                            .fill(theme.lowest.warning.default.foreground),
+                    )
+                    .child(
+                        div()
+                            .w_3()
+                            .h_3()
+                            .rounded_full()
+                            .fill(theme.lowest.negative.default.foreground),
+                    ),
+            )
+            // === Project Info === //
+            .child(
+                div()
+                    .flex()
+                    .items_center()
+                    .gap_1()
+                    .child(
+                        div()
+                            .h_full()
+                            .flex()
+                            .items_center()
+                            .justify_center()
+                            .px_2()
+                            .rounded_md()
+                            .hover()
+                            .fill(theme.lowest.base.hovered.background)
+                            .active()
+                            .fill(theme.lowest.base.pressed.background)
+                            .child(div().text_sm().child("project")),
+                    )
+                    .child(
+                        div()
+                            .h_full()
+                            .flex()
+                            .items_center()
+                            .justify_center()
+                            .px_2()
+                            .rounded_md()
+                            .text_color(theme.lowest.variant.default.foreground)
+                            .hover()
+                            .fill(theme.lowest.base.hovered.background)
+                            .active()
+                            .fill(theme.lowest.base.pressed.background)
+                            .child(div().text_sm().child("branch")),
+                    ),
+            )
+    }
+
+    fn right_group<V: 'static>(&mut self, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+        let theme = theme(cx);
+        div()
+            .flex()
+            .items_center()
+            .h_full()
+            .gap_3()
+            .px_2()
+            // === Actions === //
+            .child(
+                div().child(
+                    div().flex().items_center().gap_1().child(
+                        div().size_4().flex().items_center().justify_center().child(
+                            svg()
+                                .path("icons/exit.svg")
+                                .size_4()
+                                .fill(theme.lowest.base.default.foreground),
+                        ),
+                    ),
+                ),
+            )
+            .child(div().w_px().h_3().fill(theme.lowest.base.default.border))
+            // === Comms === //
+            .child(
+                div().child(
+                    div()
+                        .flex()
+                        .items_center()
+                        .gap_px()
+                        .child(
+                            div()
+                                .px_2()
+                                .py_1()
+                                .rounded_md()
+                                .h_full()
+                                .flex()
+                                .items_center()
+                                .justify_center()
+                                .hover()
+                                .fill(theme.lowest.base.hovered.background)
+                                .active()
+                                .fill(theme.lowest.base.pressed.background)
+                                .child(
+                                    svg()
+                                        .path("icons/microphone.svg")
+                                        .size_3p5()
+                                        .fill(theme.lowest.base.default.foreground),
+                                ),
+                        )
+                        .child(
+                            div()
+                                .px_2()
+                                .py_1()
+                                .rounded_md()
+                                .h_full()
+                                .flex()
+                                .items_center()
+                                .justify_center()
+                                .hover()
+                                .fill(theme.lowest.base.hovered.background)
+                                .active()
+                                .fill(theme.lowest.base.pressed.background)
+                                .child(
+                                    svg()
+                                        .path("icons/radix/speaker-loud.svg")
+                                        .size_3p5()
+                                        .fill(theme.lowest.base.default.foreground),
+                                ),
+                        )
+                        .child(
+                            div()
+                                .px_2()
+                                .py_1()
+                                .rounded_md()
+                                .h_full()
+                                .flex()
+                                .items_center()
+                                .justify_center()
+                                .hover()
+                                .fill(theme.lowest.base.hovered.background)
+                                .active()
+                                .fill(theme.lowest.base.pressed.background)
+                                .child(
+                                    svg()
+                                        .path("icons/radix/desktop.svg")
+                                        .size_3p5()
+                                        .fill(theme.lowest.base.default.foreground),
+                                ),
+                        ),
+                ),
+            )
+            .child(div().w_px().h_3().fill(theme.lowest.base.default.border))
+            // User Group
+            .child(
+                div().child(
+                    div()
+                        .px_1()
+                        .py_1()
+                        .flex()
+                        .items_center()
+                        .justify_center()
+                        .rounded_md()
+                        .gap_0p5()
+                        .hover()
+                        .fill(theme.lowest.base.hovered.background)
+                        .active()
+                        .fill(theme.lowest.base.pressed.background)
+                        .child(
+                            img()
+                                .uri("https://avatars.githubusercontent.com/u/1714999?v=4")
+                                .size_4()
+                                .rounded_md()
+                                .fill(theme.middle.on.default.foreground),
+                        )
+                        .child(
+                            svg()
+                                .path("icons/caret_down_8.svg")
+                                .w_2()
+                                .h_2()
+                                .fill(theme.lowest.variant.default.foreground),
+                        ),
+                ),
+            )
+    }
+}
+
+// ================================================================================ //
+
+#[derive(Element)]
+struct StatusBar;
+
+pub fn statusbar<V: 'static>() -> impl Element<V> {
+    StatusBar
+}
+
+impl StatusBar {
+    fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+        let theme = theme(cx);
+        div()
+            .flex()
+            .items_center()
+            .justify_between()
+            .w_full()
+            .h_8()
+            .fill(theme.lowest.base.default.background)
+            .child(self.left_group(cx))
+            .child(self.right_group(cx))
+    }
+
+    fn left_group<V: 'static>(&mut self, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+        let theme = theme(cx);
+        div()
+            .flex()
+            .items_center()
+            .h_full()
+            .gap_4()
+            .px_2()
+            // === Tools === //
+            .child(
+                div()
+                    .flex()
+                    .items_center()
+                    .gap_1()
+                    .child(
+                        div()
+                            .w_6()
+                            .h_full()
+                            .flex()
+                            .items_center()
+                            .justify_center()
+                            .child(
+                                svg()
+                                    .path("icons/project.svg")
+                                    .w_4()
+                                    .h_4()
+                                    .fill(theme.lowest.base.default.foreground),
+                            ),
+                    )
+                    .child(
+                        div()
+                            .w_6()
+                            .h_full()
+                            .flex()
+                            .items_center()
+                            .justify_center()
+                            .child(
+                                svg()
+                                    .path("icons/conversations.svg")
+                                    .w_4()
+                                    .h_4()
+                                    .fill(theme.lowest.base.default.foreground),
+                            ),
+                    )
+                    .child(
+                        div()
+                            .w_6()
+                            .h_full()
+                            .flex()
+                            .items_center()
+                            .justify_center()
+                            .child(
+                                svg()
+                                    .path("icons/file_icons/notebook.svg")
+                                    .w_4()
+                                    .h_4()
+                                    .fill(theme.lowest.accent.default.foreground),
+                            ),
+                    ),
+            )
+            // === Diagnostics === //
+            .child(
+                div()
+                    .flex()
+                    .items_center()
+                    .gap_2()
+                    .child(
+                        div()
+                            .h_full()
+                            .flex()
+                            .items_center()
+                            .justify_center()
+                            .gap_0p5()
+                            .px_1()
+                            .text_color(theme.lowest.variant.default.foreground)
+                            .hover()
+                            .fill(theme.lowest.base.hovered.background)
+                            .active()
+                            .fill(theme.lowest.base.pressed.background)
+                            .child(
+                                svg()
+                                    .path("icons/error.svg")
+                                    .w_4()
+                                    .h_4()
+                                    .fill(theme.lowest.negative.default.foreground),
+                            )
+                            .child(div().text_sm().child("2")),
+                    )
+                    .child(
+                        div()
+                            .text_sm()
+                            .text_color(theme.lowest.variant.default.foreground)
+                            .child("Something is wrong"),
+                    ),
+            )
+    }
+
+    fn right_group<V: 'static>(&mut self, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+        let theme = theme(cx);
+        div()
+            .flex()
+            .items_center()
+            .h_full()
+            .gap_4()
+            .px_2()
+            // === Tools === //
+            .child(
+                div()
+                    .flex()
+                    .items_center()
+                    .gap_1()
+                    .child(
+                        div()
+                            .w_6()
+                            .h_full()
+                            .flex()
+                            .items_center()
+                            .justify_center()
+                            .child(
+                                svg()
+                                    .path("icons/check_circle.svg")
+                                    .w_4()
+                                    .h_4()
+                                    .fill(theme.lowest.base.default.foreground),
+                            ),
+                    )
+                    .child(
+                        div()
+                            .w_6()
+                            .h_full()
+                            .flex()
+                            .items_center()
+                            .justify_center()
+                            .child(
+                                svg()
+                                    .path("icons/copilot.svg")
+                                    .w_4()
+                                    .h_4()
+                                    .fill(theme.lowest.accent.default.foreground),
+                            ),
+                    ),
+            )
+    }
+}

crates/terminal_view/src/terminal_element.rs 🔗

@@ -10,9 +10,8 @@ use gpui::{
     platform::{CursorStyle, MouseButton},
     serde_json::json,
     text_layout::{Line, RunStyle},
-    AnyElement, Element, EventContext, FontCache, LayoutContext, ModelContext, MouseRegion,
-    PaintContext, Quad, SceneBuilder, SizeConstraint, TextLayoutCache, ViewContext,
-    WeakModelHandle,
+    AnyElement, Element, EventContext, FontCache, ModelContext, MouseRegion, Quad, SizeConstraint,
+    TextLayoutCache, ViewContext, WeakModelHandle, WindowContext,
 };
 use itertools::Itertools;
 use language::CursorShape;
@@ -86,12 +85,11 @@ impl LayoutCell {
 
     fn paint(
         &self,
-        scene: &mut SceneBuilder,
         origin: Vector2F,
         layout: &LayoutState,
         visible_bounds: RectF,
         _view: &mut TerminalView,
-        cx: &mut ViewContext<TerminalView>,
+        cx: &mut WindowContext,
     ) {
         let pos = {
             let point = self.point;
@@ -102,7 +100,7 @@ impl LayoutCell {
         };
 
         self.text
-            .paint(scene, pos, visible_bounds, layout.size.line_height, cx);
+            .paint(pos, visible_bounds, layout.size.line_height, cx);
     }
 }
 
@@ -132,11 +130,10 @@ impl LayoutRect {
 
     fn paint(
         &self,
-        scene: &mut SceneBuilder,
         origin: Vector2F,
         layout: &LayoutState,
         _view: &mut TerminalView,
-        _cx: &mut ViewContext<TerminalView>,
+        cx: &mut ViewContext<TerminalView>,
     ) {
         let position = {
             let point = self.point;
@@ -150,7 +147,7 @@ impl LayoutRect {
             layout.size.line_height,
         );
 
-        scene.push_quad(Quad {
+        cx.scene().push_quad(Quad {
             bounds: RectF::new(position, size),
             background: Some(self.color),
             border: Default::default(),
@@ -387,7 +384,6 @@ impl TerminalElement {
 
     fn attach_mouse_handlers(
         &self,
-        scene: &mut SceneBuilder,
         origin: Vector2F,
         visible_bounds: RectF,
         mode: TermMode,
@@ -518,7 +514,7 @@ impl TerminalElement {
                 )
         }
 
-        scene.push_mouse_region(region);
+        cx.scene().push_mouse_region(region);
     }
 }
 
@@ -530,7 +526,7 @@ impl Element<TerminalView> for TerminalElement {
         &mut self,
         constraint: gpui::SizeConstraint,
         view: &mut TerminalView,
-        cx: &mut LayoutContext<TerminalView>,
+        cx: &mut ViewContext<TerminalView>,
     ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
         let settings = settings::get::<ThemeSettings>(cx);
         let terminal_settings = settings::get::<TerminalSettings>(cx);
@@ -733,25 +729,24 @@ impl Element<TerminalView> for TerminalElement {
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         layout: &mut Self::LayoutState,
         view: &mut TerminalView,
-        cx: &mut PaintContext<TerminalView>,
+        cx: &mut ViewContext<TerminalView>,
     ) -> Self::PaintState {
         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
 
         //Setup element stuff
         let clip_bounds = Some(visible_bounds);
 
-        scene.paint_layer(clip_bounds, |scene| {
+        cx.paint_layer(clip_bounds, |cx| {
             let origin = bounds.origin() + vec2f(layout.gutter, 0.);
 
             // Elements are ephemeral, only at paint time do we know what could be clicked by a mouse
-            self.attach_mouse_handlers(scene, origin, visible_bounds, layout.mode, cx);
+            self.attach_mouse_handlers(origin, visible_bounds, layout.mode, cx);
 
-            scene.push_cursor_region(gpui::CursorRegion {
+            cx.scene().push_cursor_region(gpui::CursorRegion {
                 bounds,
                 style: if layout.hyperlink_tooltip.is_some() {
                     CursorStyle::PointingHand
@@ -760,9 +755,9 @@ impl Element<TerminalView> for TerminalElement {
                 },
             });
 
-            scene.paint_layer(clip_bounds, |scene| {
+            cx.paint_layer(clip_bounds, |cx| {
                 //Start with a background color
-                scene.push_quad(Quad {
+                cx.scene().push_quad(Quad {
                     bounds: RectF::new(bounds.origin(), bounds.size()),
                     background: Some(layout.background_color),
                     border: Default::default(),
@@ -770,12 +765,12 @@ impl Element<TerminalView> for TerminalElement {
                 });
 
                 for rect in &layout.rects {
-                    rect.paint(scene, origin, layout, view, cx)
+                    rect.paint(origin, layout, view, cx);
                 }
             });
 
             //Draw Highlighted Backgrounds
-            scene.paint_layer(clip_bounds, |scene| {
+            cx.paint_layer(clip_bounds, |cx| {
                 for (relative_highlighted_range, color) in layout.relative_highlighted_ranges.iter()
                 {
                     if let Some((start_y, highlighted_range_lines)) =
@@ -789,29 +784,29 @@ impl Element<TerminalView> for TerminalElement {
                             //Copied from editor. TODO: move to theme or something
                             corner_radius: 0.15 * layout.size.line_height,
                         };
-                        hr.paint(bounds, scene);
+                        hr.paint(bounds, cx);
                     }
                 }
             });
 
             //Draw the text cells
-            scene.paint_layer(clip_bounds, |scene| {
+            cx.paint_layer(clip_bounds, |cx| {
                 for cell in &layout.cells {
-                    cell.paint(scene, origin, layout, visible_bounds, view, cx);
+                    cell.paint(origin, layout, visible_bounds, view, cx);
                 }
             });
 
             //Draw cursor
             if self.cursor_visible {
                 if let Some(cursor) = &layout.cursor {
-                    scene.paint_layer(clip_bounds, |scene| {
-                        cursor.paint(scene, origin, cx);
+                    cx.paint_layer(clip_bounds, |cx| {
+                        cursor.paint(origin, cx);
                     })
                 }
             }
 
             if let Some(element) = &mut layout.hyperlink_tooltip {
-                element.paint(scene, origin, visible_bounds, view, cx)
+                element.paint(origin, visible_bounds, view, cx)
             }
         });
     }

crates/theme/src/theme.rs 🔗

@@ -6,15 +6,16 @@ pub mod ui;
 use components::{action_button::ButtonStyle, disclosure::DisclosureStyle, ToggleIconButtonStyle};
 use gpui::{
     color::Color,
-    elements::{ContainerStyle, ImageStyle, LabelStyle, Shadow, SvgStyle, TooltipStyle},
+    elements::{Border, ContainerStyle, ImageStyle, LabelStyle, Shadow, SvgStyle, TooltipStyle},
     fonts::{HighlightStyle, TextStyle},
-    platform, AppContext, AssetSource, Border, MouseState,
+    platform, AppContext, AssetSource, MouseState,
 };
+use parking_lot::Mutex;
 use schemars::JsonSchema;
 use serde::{de::DeserializeOwned, Deserialize};
 use serde_json::Value;
 use settings::SettingsStore;
-use std::{collections::HashMap, ops::Deref, sync::Arc};
+use std::{any::Any, collections::HashMap, ops::Deref, sync::Arc};
 use ui::{CheckboxStyle, CopilotCTAButton, IconStyle, ModalStyle};
 
 pub use theme_registry::*;
@@ -67,6 +68,14 @@ pub struct Theme {
     pub welcome: WelcomeStyle,
     pub titlebar: Titlebar,
     pub component_test: ComponentTest,
+    // Nathan: New elements are styled in Rust, directly from the base theme.
+    // We store it on the legacy theme so we can mix both kinds of elements during the transition.
+    #[schemars(skip)]
+    pub base_theme: serde_json::Value,
+    // A place to cache deserialized base theme.
+    #[serde(skip_deserializing)]
+    #[schemars(skip)]
+    pub deserialized_base_theme: Mutex<Option<Box<dyn Any + Send + Sync>>>,
 }
 
 #[derive(Deserialize, Default, Clone, JsonSchema)]

crates/util/src/arc_cow.rs 🔗

@@ -0,0 +1,74 @@
+use std::sync::Arc;
+
+#[derive(PartialEq, Eq)]
+pub enum ArcCow<'a, T: ?Sized> {
+    Borrowed(&'a T),
+    Owned(Arc<T>),
+}
+
+use std::hash::{Hash, Hasher};
+
+impl<'a, T: ?Sized + Hash> Hash for ArcCow<'a, T> {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        match self {
+            Self::Borrowed(borrowed) => Hash::hash(borrowed, state),
+            Self::Owned(owned) => Hash::hash(&**owned, state),
+        }
+    }
+}
+
+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<'a, T: ?Sized + ToOwned> std::borrow::Borrow<T> for ArcCow<'a, T> {
+    fn borrow(&self) -> &T {
+        match self {
+            ArcCow::Borrowed(borrowed) => borrowed,
+            ArcCow::Owned(owned) => owned.as_ref(),
+        }
+    }
+}
+
+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/util/src/http.rs 🔗

@@ -2,7 +2,7 @@ pub use anyhow::{anyhow, Result};
 use futures::future::BoxFuture;
 use isahc::config::{Configurable, RedirectPolicy};
 pub use isahc::{
-    http::{Method, Uri},
+    http::{Method, StatusCode, Uri},
     Error,
 };
 pub use isahc::{AsyncBody, Request, Response};

crates/util/src/util.rs 🔗

@@ -1,3 +1,4 @@
+pub mod arc_cow;
 pub mod channel;
 pub mod fs;
 pub mod github;
@@ -246,9 +247,16 @@ where
     }
 }
 
-struct Defer<F: FnOnce()>(Option<F>);
+pub struct Deferred<F: FnOnce()>(Option<F>);
 
-impl<F: FnOnce()> Drop for Defer<F> {
+impl<F: FnOnce()> Deferred<F> {
+    /// Drop without running the deferred function.
+    pub fn cancel(mut self) {
+        self.0.take();
+    }
+}
+
+impl<F: FnOnce()> Drop for Deferred<F> {
     fn drop(&mut self) {
         if let Some(f) = self.0.take() {
             f()
@@ -256,8 +264,9 @@ impl<F: FnOnce()> Drop for Defer<F> {
     }
 }
 
-pub fn defer<F: FnOnce()>(f: F) -> impl Drop {
-    Defer(Some(f))
+/// Run the given function when the returned value is dropped (unless it's cancelled).
+pub fn defer<F: FnOnce()>(f: F) -> Deferred<F> {
+    Deferred(Some(f))
 }
 
 pub struct RandomCharIter<T: Rng> {

crates/workspace/src/pane.rs 🔗

@@ -25,8 +25,8 @@ use gpui::{
     keymap_matcher::KeymapContext,
     platform::{CursorStyle, MouseButton, NavigationDirection, PromptLevel},
     Action, AnyViewHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity, EventContext,
-    LayoutContext, ModelHandle, MouseRegion, PaintContext, Quad, Task, View, ViewContext,
-    ViewHandle, WeakViewHandle, WindowContext,
+    ModelHandle, MouseRegion, Quad, Task, View, ViewContext, ViewHandle, WeakViewHandle,
+    WindowContext,
 };
 use project::{Project, ProjectEntryId, ProjectPath};
 use serde::Deserialize;
@@ -1506,10 +1506,10 @@ impl Pane {
                 None
             };
 
-            Canvas::new(move |scene, bounds, _, _, _| {
+            Canvas::new(move |bounds, _, _, cx| {
                 if let Some(color) = icon_color {
                     let square = RectF::new(bounds.origin(), vec2f(diameter, diameter));
-                    scene.push_quad(Quad {
+                    cx.scene().push_quad(Quad {
                         bounds: square,
                         background: Some(color),
                         border: Default::default(),
@@ -2066,7 +2066,7 @@ impl<V: 'static> Element<V> for PaneBackdrop<V> {
         &mut self,
         constraint: gpui::SizeConstraint,
         view: &mut V,
-        cx: &mut LayoutContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> (Vector2F, Self::LayoutState) {
         let size = self.child.layout(constraint, view, cx);
         (size, ())
@@ -2074,25 +2074,24 @@ impl<V: 'static> Element<V> for PaneBackdrop<V> {
 
     fn paint(
         &mut self,
-        scene: &mut gpui::SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut V,
-        cx: &mut PaintContext<V>,
+        cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
         let background = theme::current(cx).editor.background;
 
         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
 
-        scene.push_quad(gpui::Quad {
+        cx.scene().push_quad(gpui::Quad {
             bounds: RectF::new(bounds.origin(), bounds.size()),
             background: Some(background),
             ..Default::default()
         });
 
         let child_view_id = self.child_view;
-        scene.push_mouse_region(
+        cx.scene().push_mouse_region(
             MouseRegion::new::<Self>(child_view_id, 0, visible_bounds).on_down(
                 gpui::platform::MouseButton::Left,
                 move |_, _: &mut V, cx| {
@@ -2102,10 +2101,9 @@ impl<V: 'static> Element<V> for PaneBackdrop<V> {
             ),
         );
 
-        scene.paint_layer(Some(bounds), |scene| {
-            self.child
-                .paint(scene, bounds.origin(), visible_bounds, view, cx)
-        })
+        cx.scene().push_layer(Some(bounds));
+        self.child.paint(bounds.origin(), visible_bounds, view, cx);
+        cx.scene().pop_layer();
     }
 
     fn rect_for_text_range(

crates/workspace/src/pane/dragged_item_receiver.rs 🔗

@@ -50,7 +50,7 @@ where
         Stack::new()
             .with_child(render_child(state, cx))
             .with_children(drag_position.map(|drag_position| {
-                Canvas::new(move |scene, bounds, _, _, cx| {
+                Canvas::new(move |bounds, _, _, cx| {
                     if bounds.contains_point(drag_position) {
                         let overlay_region = split_margin
                             .and_then(|split_margin| {
@@ -60,14 +60,15 @@ where
                             .map(|(dir, margin)| dir.along_edge(bounds, margin))
                             .unwrap_or(bounds);
 
-                        scene.paint_stacking_context(None, None, |scene| {
-                            scene.push_quad(Quad {
-                                bounds: overlay_region,
-                                background: Some(overlay_color(cx)),
-                                border: Default::default(),
-                                corner_radii: Default::default(),
-                            });
+                        cx.scene().push_stacking_context(None, None);
+                        let background = overlay_color(cx);
+                        cx.scene().push_quad(Quad {
+                            bounds: overlay_region,
+                            background: Some(background),
+                            border: Default::default(),
+                            corner_radii: Default::default(),
                         });
+                        cx.scene().pop_stacking_context();
                     }
                 })
             }))

crates/workspace/src/pane_group.rs 🔗

@@ -9,7 +9,7 @@ use gpui::{
     elements::*,
     geometry::{rect::RectF, vector::Vector2F},
     platform::{CursorStyle, MouseButton},
-    AnyViewHandle, Axis, Border, ModelHandle, ViewContext, ViewHandle,
+    AnyViewHandle, Axis, ModelHandle, ViewContext, ViewHandle,
 };
 use project::Project;
 use serde::Deserialize;
@@ -594,8 +594,8 @@ mod element {
         json::{self, ToJson},
         platform::{CursorStyle, MouseButton},
         scene::MouseDrag,
-        AnyElement, Axis, CursorRegion, Element, EventContext, LayoutContext, MouseRegion,
-        PaintContext, RectFExt, SceneBuilder, SizeConstraint, Vector2FExt, ViewContext,
+        AnyElement, Axis, CursorRegion, Element, EventContext, MouseRegion, RectFExt,
+        SizeConstraint, Vector2FExt, ViewContext,
     };
 
     use crate::{
@@ -641,7 +641,7 @@ mod element {
             remaining_flex: &mut f32,
             cross_axis_max: &mut f32,
             view: &mut Workspace,
-            cx: &mut LayoutContext<Workspace>,
+            cx: &mut ViewContext<Workspace>,
         ) {
             let flexes = self.flexes.borrow();
             let cross_axis = self.axis.invert();
@@ -789,7 +789,7 @@ mod element {
             &mut self,
             constraint: SizeConstraint,
             view: &mut Workspace,
-            cx: &mut LayoutContext<Workspace>,
+            cx: &mut ViewContext<Workspace>,
         ) -> (Vector2F, Self::LayoutState) {
             debug_assert!(self.children.len() == self.flexes.borrow().len());
 
@@ -851,19 +851,18 @@ mod element {
 
         fn paint(
             &mut self,
-            scene: &mut SceneBuilder,
             bounds: RectF,
             visible_bounds: RectF,
             remaining_space: &mut Self::LayoutState,
             view: &mut Workspace,
-            cx: &mut PaintContext<Workspace>,
+            cx: &mut ViewContext<Workspace>,
         ) -> Self::PaintState {
             let can_resize = settings::get::<WorkspaceSettings>(cx).active_pane_magnification == 1.;
             let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
 
             let overflowing = *remaining_space < 0.;
             if overflowing {
-                scene.push_layer(Some(visible_bounds));
+                cx.scene().push_layer(Some(visible_bounds));
             }
 
             let mut child_origin = bounds.origin();
@@ -874,7 +873,7 @@ mod element {
             let mut children_iter = self.children.iter_mut().enumerate().peekable();
             while let Some((ix, child)) = children_iter.next() {
                 let child_start = child_origin.clone();
-                child.paint(scene, child_origin, visible_bounds, view, cx);
+                child.paint(child_origin, visible_bounds, view, cx);
 
                 bounding_boxes.push(Some(RectF::new(child_origin, child.size())));
 
@@ -884,7 +883,7 @@ mod element {
                 }
 
                 if can_resize && children_iter.peek().is_some() {
-                    scene.push_stacking_context(None, None);
+                    cx.scene().push_stacking_context(None, None);
 
                     let handle_origin = match self.axis {
                         Axis::Horizontal => child_origin - vec2f(HANDLE_HITBOX_SIZE / 2., 0.0),
@@ -907,7 +906,7 @@ mod element {
                         Axis::Vertical => CursorStyle::ResizeUpDown,
                     };
 
-                    scene.push_cursor_region(CursorRegion {
+                    cx.scene().push_cursor_region(CursorRegion {
                         bounds: handle_bounds,
                         style,
                     });
@@ -940,14 +939,14 @@ mod element {
                                 }
                             }
                         });
-                    scene.push_mouse_region(mouse_region);
+                    cx.scene().push_mouse_region(mouse_region);
 
-                    scene.pop_stacking_context();
+                    cx.scene().pop_stacking_context();
                 }
             }
 
             if overflowing {
-                scene.pop_layer();
+                cx.scene().pop_layer();
             }
         }
 

crates/workspace/src/shared_screen.rs 🔗

@@ -73,14 +73,14 @@ impl View for SharedScreen {
 
         let frame = self.frame.clone();
         MouseEventHandler::new::<Focus, _>(0, cx, |_, cx| {
-            Canvas::new(move |scene, bounds, _, _, _| {
+            Canvas::new(move |bounds, _, _, cx| {
                 if let Some(frame) = frame.clone() {
                     let size = constrain_size_preserving_aspect_ratio(
                         bounds.size(),
                         vec2f(frame.width() as f32, frame.height() as f32),
                     );
                     let origin = bounds.origin() + (bounds.size() / 2.) - size / 2.;
-                    scene.push_surface(gpui::platform::mac::Surface {
+                    cx.scene().push_surface(gpui::platform::mac::Surface {
                         bounds: RectF::new(origin, size),
                         image_buffer: frame.image(),
                     });

crates/workspace/src/status_bar.rs 🔗

@@ -8,8 +8,8 @@ use gpui::{
         vector::{vec2f, Vector2F},
     },
     json::{json, ToJson},
-    AnyElement, AnyViewHandle, Entity, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
-    Subscription, View, ViewContext, ViewHandle, WindowContext,
+    AnyElement, AnyViewHandle, Entity, SizeConstraint, Subscription, View, ViewContext, ViewHandle,
+    WindowContext,
 };
 
 pub trait StatusItemView: View {
@@ -208,7 +208,7 @@ impl Element<StatusBar> for StatusBarElement {
         &mut self,
         mut constraint: SizeConstraint,
         view: &mut StatusBar,
-        cx: &mut LayoutContext<StatusBar>,
+        cx: &mut ViewContext<StatusBar>,
     ) -> (Vector2F, Self::LayoutState) {
         let max_width = constraint.max.x();
         constraint.min = vec2f(0., constraint.min.y());
@@ -226,23 +226,20 @@ impl Element<StatusBar> for StatusBarElement {
 
     fn paint(
         &mut self,
-        scene: &mut SceneBuilder,
         bounds: RectF,
         visible_bounds: RectF,
         _: &mut Self::LayoutState,
         view: &mut StatusBar,
-        cx: &mut PaintContext<StatusBar>,
+        cx: &mut ViewContext<StatusBar>,
     ) -> Self::PaintState {
         let origin_y = bounds.upper_right().y();
         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
 
         let left_origin = vec2f(bounds.lower_left().x(), origin_y);
-        self.left
-            .paint(scene, left_origin, visible_bounds, view, cx);
+        self.left.paint(left_origin, visible_bounds, view, cx);
 
         let right_origin = vec2f(bounds.upper_right().x() - self.right.size().x(), origin_y);
-        self.right
-            .paint(scene, right_origin, visible_bounds, view, cx);
+        self.right.paint(right_origin, visible_bounds, view, cx);
     }
 
     fn rect_for_text_range(

crates/workspace/src/workspace.rs 🔗

@@ -3628,13 +3628,13 @@ fn notify_of_new_dock(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppCo
                             "Looking for the dock? Try ctrl-`!\nshift-escape now zooms your pane.",
                             text,
                         )
-                        .with_custom_runs(vec![26..32, 34..46], |_, bounds, scene, cx| {
+                        .with_custom_runs(vec![26..32, 34..46], |_, bounds, cx| {
                             let code_span_background_color = settings::get::<ThemeSettings>(cx)
                                 .theme
                                 .editor
                                 .document_highlight_read_background;
 
-                            scene.push_quad(gpui::Quad {
+                            cx.scene().push_quad(gpui::Quad {
                                 bounds,
                                 background: Some(code_span_background_color),
                                 border: Default::default(),

styles/src/build_themes.ts 🔗

@@ -30,6 +30,9 @@ function write_themes(themes: Theme[], output_directory: string) {
         setTheme(theme)
 
         const style_tree = app()
+        // Nathan: New elements will read directly from the theme colors.
+        // Adding this during the transition. Afterwards, we can port all themes to Rust.
+        style_tree.base_theme = theme
         const style_tree_json = JSON.stringify(style_tree, null, 2)
         const temp_path = path.join(temp_directory, `${theme.name}.json`)
         const out_path = path.join(output_directory, `${theme.name}.json`)

test.rs 🔗

@@ -17,10 +17,10 @@ use simplelog::SimpleLogger;
 use themes::{rose_pine, ThemeColors};
 use view::view;
 mod adapter {
+    use crate::element::AnyElement;
     use crate::element::{LayoutContext, PaintContext};
     use gpui::{geometry::rect::RectF, LayoutEngine};
     use util::ResultExt;
-    use crate::element::AnyElement;
     pub struct Adapter<V>(pub(crate) AnyElement<V>);
     impl<V: 'static> gpui::Element<V> for Adapter<V> {
         type LayoutState = Option<LayoutEngine>;
@@ -90,8 +90,8 @@ mod adapter {
 }
 mod color {
     #![allow(dead_code)]
-    use std::{num::ParseIntError, ops::Range};
     use smallvec::SmallVec;
+    use std::{num::ParseIntError, ops::Range};
     pub fn rgb<C: From<Rgba>>(hex: u32) -> C {
         let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
         let g = ((hex >> 8) & 0xFF) as f32 / 255.0;
@@ -130,16 +130,7 @@ mod color {
     impl ::core::fmt::Debug for Rgba {
         fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
             ::core::fmt::Formatter::debug_struct_field4_finish(
-                f,
-                "Rgba",
-                "r",
-                &self.r,
-                "g",
-                &self.g,
-                "b",
-                &self.b,
-                "a",
-                &&self.a,
+                f, "Rgba", "r", &self.r, "g", &self.g, "b", &self.b, "a", &&self.a,
             )
         }
     }
@@ -185,7 +176,12 @@ mod color {
                 4 => (xm, m, cm),
                 _ => (cm, m, xm),
             };
-            Rgba { r, g, b, a: color.a }
+            Rgba {
+                r,
+                g,
+                b,
+                a: color.a,
+            }
         }
     }
     impl TryFrom<&'_ str> for Rgba {
@@ -239,16 +235,7 @@ mod color {
     impl ::core::fmt::Debug for Hsla {
         fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
             ::core::fmt::Formatter::debug_struct_field4_finish(
-                f,
-                "Hsla",
-                "h",
-                &self.h,
-                "s",
-                &self.s,
-                "l",
-                &self.l,
-                "a",
-                &&self.a,
+                f, "Hsla", "h", &self.h, "s", &self.s, "l", &self.l, "a", &&self.a,
             )
         }
     }
@@ -258,8 +245,7 @@ mod color {
     impl ::core::cmp::PartialEq for Hsla {
         #[inline]
         fn eq(&self, other: &Hsla) -> bool {
-            self.h == other.h && self.s == other.s && self.l == other.l
-                && self.a == other.a
+            self.h == other.h && self.s == other.s && self.l == other.l && self.a == other.a
         }
     }
     pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
@@ -271,7 +257,12 @@ mod color {
         }
     }
     pub fn black() -> Hsla {
-        Hsla { h: 0., s: 0., l: 0., a: 1. }
+        Hsla {
+            h: 0.,
+            s: 0.,
+            l: 0.,
+            a: 1.,
+        }
     }
     impl From<Rgba> for Hsla {
         fn from(color: Rgba) -> Self {
@@ -298,7 +289,12 @@ mod color {
             } else {
                 ((r - g) / delta + 4.0) / 6.0
             };
-            Hsla { h, s, l, a: color.a }
+            Hsla {
+                h,
+                s,
+                l,
+                a: color.a,
+            }
         }
     }
     impl Hsla {
@@ -364,8 +360,7 @@ mod color {
             positions: SmallVec::new(),
         };
         let num_colors: f32 = scale.colors.len() as f32 - 1.0;
-        scale
-            .positions = (0..scale.colors.len())
+        scale.positions = (0..scale.colors.len())
             .map(|i| i as f32 / num_colors)
             .collect();
         scale
@@ -375,12 +370,10 @@ mod color {
             if true {
                 if !(0.0 <= t && t <= 1.0) {
                     {
-                        ::core::panicking::panic_fmt(
-                            format_args!(
-                                "t value {0} is out of range. Expected value in range 0.0 to 1.0",
-                                t,
-                            ),
-                        );
+                        ::core::panicking::panic_fmt(format_args!(
+                            "t value {0} is out of range. Expected value in range 0.0 to 1.0",
+                            t,
+                        ));
                     }
                 }
             }
@@ -412,10 +405,12 @@ mod color {
 mod components {
     use crate::{
         element::{Element, ElementMetadata},
-        frame, text::ArcCow, themes::rose_pine,
+        frame,
+        text::ArcCow,
+        themes::rose_pine,
     };
     use gpui::{platform::MouseButton, ViewContext};
-    use playground_macros::Element;
+    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>)>>,
@@ -498,18 +493,11 @@ mod components {
             self.icon = Some(icon.into());
             self
         }
-        pub fn click(
-            self,
-            handler: impl Fn(&mut V, &D, &mut ViewContext<V>) + 'static,
-        ) -> Self {
+        pub fn click(self, handler: impl Fn(&mut V, &D, &mut ViewContext<V>) + 'static) -> Self {
             let data = self.data.clone();
-            Element::click(
-                self,
-                MouseButton::Left,
-                move |view, _, cx| {
-                    handler(view, data.as_ref(), cx);
-                },
-            )
+            Element::click(self, MouseButton::Left, move |view, _, cx| {
+                handler(view, data.as_ref(), cx);
+            })
         }
     }
     pub fn button<V>() -> Button<V, ()> {
@@ -523,11 +511,9 @@ mod components {
                 .children(self.label.clone());
             if let Some(handler) = self.handlers.click.clone() {
                 let data = self.data.clone();
-                button
-                    .mouse_down(
-                        MouseButton::Left,
-                        move |view, event, cx| { handler(view, data.as_ref(), cx) },
-                    )
+                button.mouse_down(MouseButton::Left, move |view, event, cx| {
+                    handler(view, data.as_ref(), cx)
+                })
             } else {
                 button
             }
@@ -535,8 +521,11 @@ mod components {
     }
 }
 mod element {
+    pub use crate::paint_context::PaintContext;
     use crate::{
-        adapter::Adapter, color::Hsla, hoverable::Hoverable,
+        adapter::Adapter,
+        color::Hsla,
+        hoverable::Hoverable,
         style::{Display, Fill, OptionalStyle, Overflow, Position},
     };
     use anyhow::Result;
@@ -546,12 +535,12 @@ mod element {
         platform::{MouseButton, MouseButtonEvent},
         EngineLayout, EventContext, RenderContext, ViewContext,
     };
-    use playground_macros::tailwind_lengths;
+    use gpui2_macros::tailwind_lengths;
     use std::{
         any::{Any, TypeId},
-        cell::Cell, rc::Rc,
+        cell::Cell,
+        rc::Rc,
     };
-    pub use crate::paint_context::PaintContext;
     pub use taffy::tree::NodeId;
     pub struct Layout<'a, E: ?Sized> {
         pub from_engine: EngineLayout,
@@ -627,33 +616,24 @@ mod element {
             Self: Sized,
         {
             let pressed: Rc<Cell<bool>> = Default::default();
-            self.mouse_down(
-                    button,
-                    {
-                        let pressed = pressed.clone();
-                        move |_, _, _| {
-                            pressed.set(true);
-                        }
-                    },
-                )
-                .mouse_up_outside(
-                    button,
-                    {
-                        let pressed = pressed.clone();
-                        move |_, _, _| {
-                            pressed.set(false);
-                        }
-                    },
-                )
-                .mouse_up(
-                    button,
-                    move |view, event, event_cx| {
-                        if pressed.get() {
-                            pressed.set(false);
-                            handler(view, event, event_cx);
-                        }
-                    },
-                )
+            self.mouse_down(button, {
+                let pressed = pressed.clone();
+                move |_, _, _| {
+                    pressed.set(true);
+                }
+            })
+            .mouse_up_outside(button, {
+                let pressed = pressed.clone();
+                move |_, _, _| {
+                    pressed.set(false);
+                }
+            })
+            .mouse_up(button, move |view, event, event_cx| {
+                if pressed.get() {
+                    pressed.set(false);
+                    handler(view, event, event_cx);
+                }
+            })
         }
         fn mouse_down(
             mut self,
@@ -663,17 +643,16 @@ mod element {
         where
             Self: Sized,
         {
-            self.handlers_mut()
-                .push(EventHandler {
-                    handler: Rc::new(move |view, event, event_cx| {
-                        let event = event.downcast_ref::<MouseButtonEvent>().unwrap();
-                        if event.button == button && event.is_down {
-                            handler(view, event, event_cx);
-                        }
-                    }),
-                    event_type: TypeId::of::<MouseButtonEvent>(),
-                    outside_bounds: false,
-                });
+            self.handlers_mut().push(EventHandler {
+                handler: Rc::new(move |view, event, event_cx| {
+                    let event = event.downcast_ref::<MouseButtonEvent>().unwrap();
+                    if event.button == button && event.is_down {
+                        handler(view, event, event_cx);
+                    }
+                }),
+                event_type: TypeId::of::<MouseButtonEvent>(),
+                outside_bounds: false,
+            });
             self
         }
         fn mouse_down_outside(
@@ -684,17 +663,16 @@ mod element {
         where
             Self: Sized,
         {
-            self.handlers_mut()
-                .push(EventHandler {
-                    handler: Rc::new(move |view, event, event_cx| {
-                        let event = event.downcast_ref::<MouseButtonEvent>().unwrap();
-                        if event.button == button && event.is_down {
-                            handler(view, event, event_cx);
-                        }
-                    }),
-                    event_type: TypeId::of::<MouseButtonEvent>(),
-                    outside_bounds: true,
-                });
+            self.handlers_mut().push(EventHandler {
+                handler: Rc::new(move |view, event, event_cx| {
+                    let event = event.downcast_ref::<MouseButtonEvent>().unwrap();
+                    if event.button == button && event.is_down {
+                        handler(view, event, event_cx);
+                    }
+                }),
+                event_type: TypeId::of::<MouseButtonEvent>(),
+                outside_bounds: true,
+            });
             self
         }
         fn mouse_up(
@@ -705,17 +683,16 @@ mod element {
         where
             Self: Sized,
         {
-            self.handlers_mut()
-                .push(EventHandler {
-                    handler: Rc::new(move |view, event, event_cx| {
-                        let event = event.downcast_ref::<MouseButtonEvent>().unwrap();
-                        if event.button == button && !event.is_down {
-                            handler(view, event, event_cx);
-                        }
-                    }),
-                    event_type: TypeId::of::<MouseButtonEvent>(),
-                    outside_bounds: false,
-                });
+            self.handlers_mut().push(EventHandler {
+                handler: Rc::new(move |view, event, event_cx| {
+                    let event = event.downcast_ref::<MouseButtonEvent>().unwrap();
+                    if event.button == button && !event.is_down {
+                        handler(view, event, event_cx);
+                    }
+                }),
+                event_type: TypeId::of::<MouseButtonEvent>(),
+                outside_bounds: false,
+            });
             self
         }
         fn mouse_up_outside(
@@ -726,17 +703,16 @@ mod element {
         where
             Self: Sized,
         {
-            self.handlers_mut()
-                .push(EventHandler {
-                    handler: Rc::new(move |view, event, event_cx| {
-                        let event = event.downcast_ref::<MouseButtonEvent>().unwrap();
-                        if event.button == button && !event.is_down {
-                            handler(view, event, event_cx);
-                        }
-                    }),
-                    event_type: TypeId::of::<MouseButtonEvent>(),
-                    outside_bounds: true,
-                });
+            self.handlers_mut().push(EventHandler {
+                handler: Rc::new(move |view, event, event_cx| {
+                    let event = event.downcast_ref::<MouseButtonEvent>().unwrap();
+                    if event.button == button && !event.is_down {
+                        handler(view, event, event_cx);
+                    }
+                }),
+                event_type: TypeId::of::<MouseButtonEvent>(),
+                outside_bounds: true,
+            });
             self
         }
         fn block(mut self) -> Self
@@ -764,9 +740,7 @@ mod element {
         where
             Self: Sized,
         {
-            self
-                .declared_style()
-                .overflow = OptionalPoint {
+            self.declared_style().overflow = OptionalPoint {
                 x: Some(Overflow::Visible),
                 y: Some(Overflow::Visible),
             };
@@ -776,9 +750,7 @@ mod element {
         where
             Self: Sized,
         {
-            self
-                .declared_style()
-                .overflow = OptionalPoint {
+            self.declared_style().overflow = OptionalPoint {
                 x: Some(Overflow::Hidden),
                 y: Some(Overflow::Hidden),
             };
@@ -788,9 +760,7 @@ mod element {
         where
             Self: Sized,
         {
-            self
-                .declared_style()
-                .overflow = OptionalPoint {
+            self.declared_style().overflow = OptionalPoint {
                 x: Some(Overflow::Scroll),
                 y: Some(Overflow::Scroll),
             };
@@ -4485,11 +4455,7 @@ mod element {
         layout: Option<(NodeId, Box<dyn Any>)>,
     }
     impl<V: 'static> AnyElement<V> {
-        pub fn layout(
-            &mut self,
-            view: &mut V,
-            cx: &mut LayoutContext<V>,
-        ) -> Result<NodeId> {
+        pub fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<NodeId> {
             let pushed_text_style = self.push_text_style(cx);
             let (node_id, layout) = self.element.layout(view, cx)?;
             self.layout = Some((node_id, layout));
@@ -4511,30 +4477,25 @@ mod element {
         }
         pub fn paint(&mut self, view: &mut V, cx: &mut PaintContext<V>) -> Result<()> {
             let pushed_text_style = self.push_text_style(cx);
-            let (layout_node_id, element_layout) = self
-                .layout
-                .as_mut()
-                .expect("paint called before layout");
+            let (layout_node_id, element_layout) =
+                self.layout.as_mut().expect("paint called before layout");
             let layout = Layout {
                 from_engine: cx
                     .layout_engine()
                     .unwrap()
                     .computed_layout(*layout_node_id)
-                    .expect(
-                        "you can currently only use playground elements within an adapter",
-                    ),
+                    .expect("make sure you're using this within a gpui2 adapter element"),
                 from_element: element_layout.as_mut(),
             };
             let style = self.element.style();
             let fill_color = style.fill.flatten().and_then(|fill| fill.color());
             if let Some(fill_color) = fill_color {
-                cx.scene
-                    .push_quad(gpui::scene::Quad {
-                        bounds: layout.from_engine.bounds,
-                        background: Some(fill_color.into()),
-                        border: Default::default(),
-                        corner_radii: Default::default(),
-                    });
+                cx.scene.push_quad(gpui::scene::Quad {
+                    bounds: layout.from_engine.bounds,
+                    background: Some(fill_color.into()),
+                    border: Default::default(),
+                    corner_radii: Default::default(),
+                });
             }
             for event_handler in self.element.handlers_mut().iter().cloned() {
                 let EngineLayout { order, bounds } = layout.from_engine;
@@ -4547,10 +4508,7 @@ mod element {
                         bounds,
                         outside_bounds: event_handler.outside_bounds,
                         event_handler: Rc::new(move |view, event, window_cx, view_id| {
-                            let mut view_context = ViewContext::mutable(
-                                window_cx,
-                                view_id,
-                            );
+                            let mut view_context = ViewContext::mutable(window_cx, view_id);
                             let mut event_context = EventContext::new(&mut view_context);
                             view_event_handler(
                                 view.downcast_mut().unwrap(),
@@ -4607,14 +4565,14 @@ mod element {
 mod frame {
     use crate::{
         element::{
-            AnyElement, Element, EventHandler, IntoElement, Layout, LayoutContext,
-            NodeId, PaintContext,
+            AnyElement, Element, EventHandler, IntoElement, Layout, LayoutContext, NodeId,
+            PaintContext,
         },
         style::{OptionalStyle, Style},
     };
     use anyhow::{anyhow, Result};
     use gpui::LayoutNodeId;
-    use playground_macros::IntoElement;
+    use gpui2_macros::IntoElement;
     #[element_crate = "crate"]
     pub struct Frame<V: 'static> {
         style: OptionalStyle,
@@ -4656,12 +4614,13 @@ mod frame {
             let style: Style = self.style.into();
             let node_id = cx
                 .layout_engine()
-                .ok_or_else(|| ::anyhow::__private::must_use({
-                    let error = ::anyhow::__private::format_err(
-                        format_args!("no layout engine"),
-                    );
-                    error
-                }))?
+                .ok_or_else(|| {
+                    ::anyhow::__private::must_use({
+                        let error =
+                            ::anyhow::__private::format_err(format_args!("no layout engine"));
+                        error
+                    })
+                })?
                 .add_node(style.to_taffy(rem_size), child_layout_node_ids)?;
             Ok((node_id, ()))
         }
@@ -4687,18 +4646,23 @@ mod frame {
             I: IntoIterator<Item = E>,
             E: IntoElement<V>,
         {
-            self.children.extend(children.into_iter().map(|e| e.into_any_element()));
+            self.children
+                .extend(children.into_iter().map(|e| e.into_any_element()));
             self
         }
     }
 }
 mod hoverable {
-    use std::{cell::Cell, marker::PhantomData, rc::Rc};
+    use crate::{
+        element::Element,
+        style::{OptionalStyle, Style},
+    };
     use gpui::{
         geometry::{rect::RectF, vector::Vector2F},
-        scene::MouseMove, EngineLayout,
+        scene::MouseMove,
+        EngineLayout,
     };
-    use crate::{element::Element, style::{OptionalStyle, Style}};
+    use std::{cell::Cell, marker::PhantomData, rc::Rc};
     pub struct Hoverable<V, E> {
         hover_style: OptionalStyle,
         computed_style: Option<Style>,
@@ -4760,10 +4724,10 @@ mod hoverable {
     }
 }
 mod paint_context {
-    use std::{any::TypeId, rc::Rc};
     use derive_more::{Deref, DerefMut};
     use gpui::{geometry::rect::RectF, EventContext, RenderContext, ViewContext};
     pub use gpui::{LayoutContext, PaintContext as LegacyPaintContext};
+    use std::{any::TypeId, rc::Rc};
     pub use taffy::tree::NodeId;
     pub struct PaintContext<'a, 'b, 'c, 'd, V> {
         #[deref]
@@ -4833,13 +4797,12 @@ mod paint_context {
 mod style {
     use crate::color::Hsla;
     use gpui::geometry::{
-        DefinedLength, Edges, Length, OptionalEdges, OptionalPoint, OptionalSize, Point,
-        Size,
+        DefinedLength, Edges, Length, OptionalEdges, OptionalPoint, OptionalSize, Point, Size,
     };
     use optional::Optional;
     pub use taffy::style::{
-        AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap,
-        JustifyContent, Overflow, Position,
+        AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
+        Overflow, Position,
     };
     pub struct Style {
         /// What layout strategy should be used?
@@ -5194,9 +5157,7 @@ mod style {
         #[inline]
         fn clone(&self) -> Fill {
             match self {
-                Fill::Color(__self_0) => {
-                    Fill::Color(::core::clone::Clone::clone(__self_0))
-                }
+                Fill::Color(__self_0) => Fill::Color(::core::clone::Clone::clone(__self_0)),
             }
         }
     }
@@ -5257,32 +5218,25 @@ mod text {
             let text = self.text.clone();
             let layout = Arc::new(Mutex::new(None));
             let style: Style = self.metadata.style.into();
-            let node_id = layout_engine
-                .add_measured_node(
-                    style.to_taffy(rem_size),
-                    {
-                        let layout = layout.clone();
-                        move |params| {
-                            let line_layout = fonts
-                                .layout_line(
-                                    text.as_ref(),
-                                    text_style.font_size,
-                                    &[(text.len(), text_style.to_run())],
-                                );
-                            let size = Size {
-                                width: line_layout.width,
-                                height: line_height,
-                            };
-                            layout
-                                .lock()
-                                .replace(TextLayout {
-                                    line_layout: Arc::new(line_layout),
-                                    line_height,
-                                });
-                            size
-                        }
-                    },
-                )?;
+            let node_id = layout_engine.add_measured_node(style.to_taffy(rem_size), {
+                let layout = layout.clone();
+                move |params| {
+                    let line_layout = fonts.layout_line(
+                        text.as_ref(),
+                        text_style.font_size,
+                        &[(text.len(), text_style.to_run())],
+                    );
+                    let size = Size {
+                        width: line_layout.width,
+                        height: line_height,
+                    };
+                    layout.lock().replace(TextLayout {
+                        line_layout: Arc::new(line_layout),
+                        line_height,
+                    });
+                    size
+                }
+            })?;
             Ok((node_id, layout))
         }
         fn paint<'a>(
@@ -5369,11 +5323,11 @@ mod themes {
     use crate::color::{Hsla, Lerp};
     use std::ops::Range;
     pub mod rose_pine {
-        use std::ops::Range;
         use crate::{
             color::{hsla, rgb, Hsla},
             ThemeColors,
         };
+        use std::ops::Range;
         pub struct RosePineThemes {
             pub default: RosePinePalette,
             pub dawn: RosePinePalette,
@@ -5426,7 +5380,7 @@ mod themes {
                     "highlight_med",
                     "highlight_high",
                 ];
-                let values: &[&dyn ::core::fmt::Debug] = &[
+                let values: &[&dyn::core::fmt::Debug] = &[
                     &self.base,
                     &self.surface,
                     &self.overlay,
@@ -5636,35 +5590,29 @@ mod view {
     }
 }
 fn main() {
-    SimpleLogger::init(LevelFilter::Info, Default::default())
-        .expect("could not initialize logger");
-    gpui::App::new(())
-        .unwrap()
-        .run(|cx| {
-            cx.add_window(
-                WindowOptions {
-                    bounds: gpui::platform::WindowBounds::Fixed(
-                        RectF::new(vec2f(0., 0.), vec2f(400., 300.)),
-                    ),
-                    center: true,
-                    ..Default::default()
-                },
-                |_| view(|_| playground(&rose_pine::moon())),
-            );
-            cx.platform().activate(true);
-        });
+    SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
+    gpui::App::new(()).unwrap().run(|cx| {
+        cx.add_window(
+            WindowOptions {
+                bounds: gpui::platform::WindowBounds::Fixed(RectF::new(
+                    vec2f(0., 0.),
+                    vec2f(400., 300.),
+                )),
+                center: true,
+                ..Default::default()
+            },
+            |_| view(|_| storybook(&rose_pine::moon())),
+        );
+        cx.platform().activate(true);
+    });
 }
-fn playground<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
+fn storybook<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
     frame()
         .text_color(black())
         .h_full()
         .w_half()
         .fill(theme.success(0.5))
-        .child(
-            button()
-                .label("Hello")
-                .click(|_, _, _| {
-                    ::std::io::_print(format_args!("click!\n"));
-                }),
-        )
+        .child(button().label("Hello").click(|_, _, _| {
+            ::std::io::_print(format_args!("click!\n"));
+        }))
 }