WIP: Work toward eliminating Component trait

Nathan Sobo created

This refactor enhances the overall design by promoting reusable and composable UI component structures within the Zed project codebase.

Change summary

crates/gpui2/src/element.rs                      | 83 +++++++++++++++++
crates/gpui2/src/interactive.rs                  |  7 -
crates/storybook2/src/stories/z_index.rs         |  2 
crates/theme2/src/story.rs                       | 17 ++-
crates/ui2/docs/hello-world.md                   | 10 +-
crates/ui2/src/components/avatar.rs              |  2 
crates/ui2/src/components/button.rs              |  4 
crates/ui2/src/components/checkbox.rs            |  4 
crates/ui2/src/components/details.rs             |  2 
crates/ui2/src/components/divider.rs             |  2 
crates/ui2/src/components/facepile.rs            |  2 
crates/ui2/src/components/icon.rs                |  2 
crates/ui2/src/components/icon_button.rs         |  2 
crates/ui2/src/components/indicator.rs           |  2 
crates/ui2/src/components/input.rs               |  2 
crates/ui2/src/components/keybinding.rs          |  4 
crates/ui2/src/components/label.rs               |  4 
crates/ui2/src/components/list.rs                | 12 +-
crates/ui2/src/components/modal.rs               |  2 
crates/ui2/src/components/notification_toast.rs  |  2 
crates/ui2/src/components/palette.rs             |  4 
crates/ui2/src/components/panel.rs               |  2 
crates/ui2/src/components/player_stack.rs        |  2 
crates/ui2/src/components/tab.rs                 |  2 
crates/ui2/src/components/toast.rs               | 33 ++++++
crates/ui2/src/components/toggle.rs              |  4 
crates/ui2/src/components/tool_divider.rs        |  2 
crates/ui2/src/story.rs                          |  6 
crates/ui2/src/to_extract/assistant_panel.rs     |  2 
crates/ui2/src/to_extract/breadcrumb.rs          |  2 
crates/ui2/src/to_extract/buffer.rs              |  6 
crates/ui2/src/to_extract/chat_panel.rs          |  4 
crates/ui2/src/to_extract/collab_panel.rs        |  2 
crates/ui2/src/to_extract/command_palette.rs     |  2 
crates/ui2/src/to_extract/copilot.rs             |  2 
crates/ui2/src/to_extract/language_selector.rs   |  2 
crates/ui2/src/to_extract/multi_buffer.rs        |  2 
crates/ui2/src/to_extract/notifications_panel.rs |  8 
crates/ui2/src/to_extract/panes.rs               |  4 
crates/ui2/src/to_extract/project_panel.rs       |  2 
crates/ui2/src/to_extract/recent_projects.rs     |  2 
crates/ui2/src/to_extract/tab_bar.rs             |  2 
crates/ui2/src/to_extract/terminal.rs            |  2 
crates/ui2/src/to_extract/theme_selector.rs      |  2 
crates/ui2/src/to_extract/toolbar.rs             | 10 +-
crates/ui2/src/to_extract/traffic_lights.rs      |  4 
46 files changed, 192 insertions(+), 90 deletions(-)

Detailed changes

crates/gpui2/src/element.rs 🔗

@@ -65,25 +65,26 @@ pub struct GlobalElementId(SmallVec<[ElementId; 32]>);
 pub trait ParentComponent<V: 'static> {
     fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]>;
 
-    fn child(mut self, child: impl Component<V>) -> Self
+    fn child(mut self, child: impl Element<V>) -> Self
     where
         Self: Sized,
     {
-        self.children_mut().push(child.render());
+        self.children_mut().push(child.into_any());
         self
     }
 
-    fn children(mut self, iter: impl IntoIterator<Item = impl Component<V>>) -> Self
+    fn children(mut self, children: impl IntoIterator<Item = impl Element<V>>) -> Self
     where
         Self: Sized,
     {
         self.children_mut()
-            .extend(iter.into_iter().map(|item| item.render()));
+            .extend(children.into_iter().map(Element::into_any));
         self
     }
 }
 
 trait ElementObject<V> {
+    fn element_id(&self) -> Option<ElementId>;
     fn layout(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) -> LayoutId;
     fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>);
     fn measure(
@@ -132,6 +133,10 @@ impl<V, E: Element<V>> DrawableElement<V, E> {
         }
     }
 
+    fn element_id(&self) -> Option<ElementId> {
+        self.element.as_ref()?.element_id()
+    }
+
     fn layout(&mut self, state: &mut V, cx: &mut ViewContext<V>) -> LayoutId {
         let (layout_id, frame_state) = if let Some(id) = self.element.as_ref().unwrap().element_id()
         {
@@ -256,6 +261,10 @@ where
     E: Element<V>,
     E::State: 'static,
 {
+    fn element_id(&self) -> Option<ElementId> {
+        self.as_ref().unwrap().element_id()
+    }
+
     fn layout(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) -> LayoutId {
         DrawableElement::layout(self.as_mut().unwrap(), view_state, cx)
     }
@@ -302,6 +311,10 @@ impl<V> AnyElement<V> {
         AnyElement(Box::new(Some(DrawableElement::new(element))) as Box<dyn ElementObject<V>>)
     }
 
+    pub fn element_id(&self) -> Option<ElementId> {
+        self.0.element_id()
+    }
+
     pub fn layout(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) -> LayoutId {
         self.0.layout(view_state, cx)
     }
@@ -332,6 +345,34 @@ impl<V> AnyElement<V> {
     }
 }
 
+impl<V: 'static> Element<V> for AnyElement<V> {
+    type State = ();
+
+    fn element_id(&self) -> Option<ElementId> {
+        AnyElement::element_id(self)
+    }
+
+    fn layout(
+        &mut self,
+        view_state: &mut V,
+        _: Option<Self::State>,
+        cx: &mut ViewContext<V>,
+    ) -> (LayoutId, Self::State) {
+        let layout_id = self.layout(view_state, cx);
+        (layout_id, ())
+    }
+
+    fn paint(
+        self,
+        bounds: Bounds<Pixels>,
+        view_state: &mut V,
+        _: &mut Self::State,
+        cx: &mut ViewContext<V>,
+    ) {
+        self.paint(view_state, cx);
+    }
+}
+
 pub trait Component<V> {
     fn render(self) -> AnyElement<V>;
 
@@ -426,3 +467,37 @@ where
         AnyElement::new(Some(self))
     }
 }
+
+// impl<V, E, F> Element<V> for F
+// where
+//     V: 'static,
+//     E: 'static + Component<V>,
+//     F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static,
+// {
+//     type State = Option<AnyElement<V>>;
+
+//     fn element_id(&self) -> Option<ElementId> {
+//         None
+//     }
+
+//     fn layout(
+//         &mut self,
+//         view_state: &mut V,
+//         element_state: Option<Self::State>,
+//         cx: &mut ViewContext<V>,
+//     ) -> (LayoutId, Self::State) {
+
+//         self(view_state)
+
+//     }
+
+//     fn paint(
+//         self,
+//         bounds: Bounds<Pixels>,
+//         view_state: &mut V,
+//         element_state: &mut Self::State,
+//         cx: &mut ViewContext<V>,
+//     ) {
+//         todo!()
+//     }
+// }

crates/gpui2/src/interactive.rs 🔗

@@ -307,12 +307,7 @@ mod test {
                     .key_context("parent")
                     .on_key_down(|this: &mut TestView, _, _, _| this.saw_key_down = true)
                     .on_action(|this: &mut TestView, _: &TestAction, _| this.saw_action = true)
-                    .child(|this: &mut Self, _cx: &mut ViewContext<Self>| {
-                        div()
-                            .key_context("nested")
-                            .track_focus(&this.focus_handle)
-                            .render()
-                    }),
+                    .child(div().key_context("nested").track_focus(&self.focus_handle)),
             )
         }
     }

crates/storybook2/src/stories/z_index.rs 🔗

@@ -89,7 +89,7 @@ impl ZIndexExample {
         Self { z_index }
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         div()
             .relative()
             .size_full()

crates/theme2/src/story.rs 🔗

@@ -1,4 +1,4 @@
-use gpui::{div, Component, Div, ParentComponent, Styled, ViewContext};
+use gpui::{div, Component, Div, Element, ParentComponent, SharedString, Styled, ViewContext};
 
 use crate::ActiveTheme;
 
@@ -16,23 +16,26 @@ impl Story {
             .bg(cx.theme().colors().background)
     }
 
-    pub fn title<V: 'static>(cx: &mut ViewContext<V>, title: &str) -> impl Component<V> {
+    pub fn title<V: 'static>(cx: &mut ViewContext<V>, title: SharedString) -> impl Element<V> {
         div()
             .text_xl()
             .text_color(cx.theme().colors().text)
-            .child(title.to_owned())
+            .child(title)
     }
 
-    pub fn title_for<V: 'static, T>(cx: &mut ViewContext<V>) -> impl Component<V> {
-        Self::title(cx, std::any::type_name::<T>())
+    pub fn title_for<V: 'static, T>(cx: &mut ViewContext<V>) -> impl Element<V> {
+        Self::title(cx, std::any::type_name::<T>().into())
     }
 
-    pub fn label<V: 'static>(cx: &mut ViewContext<V>, label: &str) -> impl Component<V> {
+    pub fn label<V: 'static>(
+        cx: &mut ViewContext<V>,
+        label: impl Into<SharedString>,
+    ) -> impl Element<V> {
         div()
             .mt_4()
             .mb_2()
             .text_xs()
             .text_color(cx.theme().colors().text)
-            .child(label.to_owned())
+            .child(label.into())
     }
 }

crates/ui2/docs/hello-world.md 🔗

@@ -49,13 +49,13 @@ use gpui::hsla
 
 impl<V: 'static> TodoList<V> {
     // ...
-    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         div().size_4().bg(hsla(50.0/360.0, 1.0, 0.5, 1.0))
     }
 }
 ~~~
 
-Every component needs a render method, and it should return `impl Component<V>`. This basic component will render a 16x16px yellow square on the screen.
+Every component needs a render method, and it should return `impl Element<V>`. This basic component will render a 16x16px yellow square on the screen.
 
 A couple of questions might come to mind:
 
@@ -87,7 +87,7 @@ We can access the current theme's colors like this:
 ~~~rust
 impl<V: 'static> TodoList<V> {
     // ...
-    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let color = cx.theme().colors()
 
         div().size_4().hsla(50.0/360.0, 1.0, 0.5, 1.0)
@@ -102,7 +102,7 @@ use gpui::hsla
 
 impl<V: 'static> TodoList<V> {
     // ...
-    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let color = cx.theme().colors()
 
         div().size_4().bg(color.surface)
@@ -117,7 +117,7 @@ use gpui::hsla
 
 impl<V: 'static> TodoList<V> {
     // ...
-    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let color = cx.theme().colors()
 
         div()

crates/ui2/src/components/avatar.rs 🔗

@@ -21,7 +21,7 @@ impl Avatar {
         self
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let mut img = img();
 
         if self.shape == Shape::Circle {

crates/ui2/src/components/button.rs 🔗

@@ -164,7 +164,7 @@ impl<V: 'static> Button<V> {
         self.icon.map(|i| IconElement::new(i).color(icon_color))
     }
 
-    pub fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    pub fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let (icon_color, label_color) = match (self.disabled, self.color) {
             (true, _) => (TextColor::Disabled, TextColor::Disabled),
             (_, None) => (TextColor::Default, TextColor::Default),
@@ -222,7 +222,7 @@ impl<V: 'static> ButtonGroup<V> {
         Self { buttons }
     }
 
-    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let mut el = h_stack().text_ui();
 
         for button in self.buttons {

crates/ui2/src/components/checkbox.rs 🔗

@@ -1,4 +1,4 @@
-use gpui::{div, prelude::*, Component, ElementId, Styled, ViewContext};
+use gpui::{div, prelude::*, Component, Element, ElementId, Styled, ViewContext};
 use std::sync::Arc;
 use theme2::ActiveTheme;
 
@@ -42,7 +42,7 @@ impl<V: 'static> Checkbox<V> {
         self
     }
 
-    pub fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    pub fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let group_id = format!("checkbox_group_{:?}", self.id);
 
         let icon = match self.checked {

crates/ui2/src/components/details.rs 🔗

@@ -27,7 +27,7 @@ impl<V: 'static> Details<V> {
         self
     }
 
-    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         v_stack()
             .p_1()
             .gap_0p5()

crates/ui2/src/components/divider.rs 🔗

@@ -31,7 +31,7 @@ impl Divider {
         self
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         div()
             .map(|this| match self.direction {
                 DividerDirection::Horizontal => {

crates/ui2/src/components/facepile.rs 🔗

@@ -13,7 +13,7 @@ impl Facepile {
         }
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let player_count = self.players.len();
         let player_list = self.players.iter().enumerate().map(|(ix, player)| {
             let isnt_last = ix < player_count - 1;

crates/ui2/src/components/icon.rs 🔗

@@ -163,7 +163,7 @@ impl IconElement {
         self
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let svg_size = match self.size {
             IconSize::Small => rems(0.75),
             IconSize::Medium => rems(0.9375),

crates/ui2/src/components/icon_button.rs 🔗

@@ -80,7 +80,7 @@ impl<V: 'static> IconButton<V> {
         self.on_click(move |this, cx| cx.dispatch_action(action.boxed_clone()))
     }
 
-    fn render(mut self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render(mut self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let icon_color = match (self.state, self.color) {
             (InteractionState::Disabled, _) => TextColor::Disabled,
             (InteractionState::Active, _) => TextColor::Selected,

crates/ui2/src/components/indicator.rs 🔗

@@ -10,7 +10,7 @@ impl UnreadIndicator {
         Self
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         div()
             .rounded_full()
             .border_2()

crates/ui2/src/components/input.rs 🔗

@@ -55,7 +55,7 @@ impl Input {
         self
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let (input_bg, input_hover_bg, input_active_bg) = match self.variant {
             InputVariant::Ghost => (
                 cx.theme().colors().ghost_element_background,

crates/ui2/src/components/keybinding.rs 🔗

@@ -24,7 +24,7 @@ impl KeyBinding {
         Self { key_binding }
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         div()
             .flex()
             .gap_2()
@@ -52,7 +52,7 @@ impl Key {
         Self { key: key.into() }
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         div()
             .px_2()
             .py_0()

crates/ui2/src/components/label.rs 🔗

@@ -100,7 +100,7 @@ impl Label {
         self
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         div()
             .when(self.strikethrough, |this| {
                 this.relative().child(
@@ -161,7 +161,7 @@ impl HighlightedLabel {
         self
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let highlight_color = cx.theme().colors().text_accent;
         let mut text_style = cx.text_style().clone();
 

crates/ui2/src/components/list.rs 🔗

@@ -57,7 +57,7 @@ impl ListHeader {
         self
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let disclosure_control = disclosure_control(self.toggle);
 
         let meta = match self.meta {
@@ -138,7 +138,7 @@ impl ListSubHeader {
         self
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         h_stack().flex_1().w_full().relative().py_1().child(
             div()
                 .h_6()
@@ -198,7 +198,7 @@ impl From<ListSubHeader> for ListItem {
 }
 
 impl ListItem {
-    fn render<V: 'static>(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         match self {
             ListItem::Entry(entry) => div().child(entry.render(view, cx)),
             ListItem::Separator(separator) => div().child(separator.render(view, cx)),
@@ -307,7 +307,7 @@ impl ListEntry {
         self
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let settings = user_settings(cx);
 
         let left_content = match self.left_slot.clone() {
@@ -385,7 +385,7 @@ impl ListSeparator {
         Self
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         div().h_px().w_full().bg(cx.theme().colors().border_variant)
     }
 }
@@ -425,7 +425,7 @@ impl List {
         self
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let list_content = match (self.items.is_empty(), self.toggle) {
             (false, _) => div().children(self.items),
             (true, Toggle::Toggled(false)) => div(),

crates/ui2/src/components/modal.rs 🔗

@@ -38,7 +38,7 @@ impl<V: 'static> Modal<V> {
         self
     }
 
-    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         v_stack()
             .id(self.id.clone())
             .w_96()

crates/ui2/src/components/notification_toast.rs 🔗

@@ -22,7 +22,7 @@ impl NotificationToast {
         self
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         h_stack()
             .z_index(5)
             .absolute()

crates/ui2/src/components/palette.rs 🔗

@@ -42,7 +42,7 @@ impl Palette {
         self
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         v_stack()
             .id(self.id.clone())
             .w_96()
@@ -135,7 +135,7 @@ impl PaletteItem {
         self
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         div()
             .flex()
             .flex_row()

crates/ui2/src/components/panel.rs 🔗

@@ -92,7 +92,7 @@ impl<V: 'static> Panel<V> {
         self
     }
 
-    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let current_size = self.width.unwrap_or(self.initial_width);
 
         v_stack()

crates/ui2/src/components/player_stack.rs 🔗

@@ -13,7 +13,7 @@ impl PlayerStack {
         }
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let player = self.player_with_call_status.get_player();
 
         let followers = self

crates/ui2/src/components/tab.rs 🔗

@@ -86,7 +86,7 @@ impl Tab {
         self
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let has_fs_conflict = self.fs_status == FileSystemStatus::Conflict;
         let is_deleted = self.fs_status == FileSystemStatus::Deleted;
 

crates/ui2/src/components/toast.rs 🔗

@@ -21,12 +21,41 @@ pub enum ToastOrigin {
 /// they are actively showing the a process in progress.
 ///
 /// Only one toast may be visible at a time.
-#[derive(Component)]
 pub struct Toast<V: 'static> {
     origin: ToastOrigin,
     children: SmallVec<[AnyElement<V>; 2]>,
 }
 
+impl<V: 'static> Element<V> for Toast<V> {
+    type State = Option<AnyElement<V>>;
+
+    fn element_id(&self) -> Option<ElementId> {
+        None
+    }
+
+    fn layout(
+        &mut self,
+        view_state: &mut V,
+        _element_state: Option<Self::State>,
+        cx: &mut ViewContext<V>,
+    ) -> (gpui::LayoutId, Self::State) {
+        let mut element = self.render(view_state, cx).into_any();
+        let layout_id = element.layout(view_state, cx);
+        (layout_id, Some(element))
+    }
+
+    fn paint(
+        self,
+        bounds: gpui::Bounds<gpui::Pixels>,
+        view_state: &mut V,
+        element: &mut Self::State,
+        cx: &mut ViewContext<V>,
+    ) {
+        let element = element.take().unwrap();
+        element.paint(view_state, cx);
+    }
+}
+
 impl<V: 'static> Toast<V> {
     pub fn new(origin: ToastOrigin) -> Self {
         Self {
@@ -35,7 +64,7 @@ impl<V: 'static> Toast<V> {
         }
     }
 
-    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let mut div = div();
 
         if self.origin == ToastOrigin::Bottom {

crates/ui2/src/components/toggle.rs 🔗

@@ -1,4 +1,4 @@
-use gpui::{div, Component, ParentComponent};
+use gpui::{div, Component, Element, ParentComponent};
 
 use crate::{Icon, IconElement, IconSize, TextColor};
 
@@ -44,7 +44,7 @@ impl From<bool> for Toggle {
     }
 }
 
-pub fn disclosure_control<V: 'static>(toggle: Toggle) -> impl Component<V> {
+pub fn disclosure_control<V: 'static>(toggle: Toggle) -> impl Element<V> {
     match (toggle.is_toggleable(), toggle.is_toggled()) {
         (false, _) => div(),
         (_, true) => div().child(

crates/ui2/src/components/tool_divider.rs 🔗

@@ -8,7 +8,7 @@ impl ToolDivider {
         Self
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         div().w_px().h_3().bg(cx.theme().colors().border)
     }
 }

crates/ui2/src/story.rs 🔗

@@ -15,18 +15,18 @@ impl Story {
             .bg(cx.theme().colors().background)
     }
 
-    pub fn title<V: 'static>(cx: &mut ViewContext<V>, title: &str) -> impl Component<V> {
+    pub fn title<V: 'static>(cx: &mut ViewContext<V>, title: &str) -> impl Element<V> {
         div()
             .text_xl()
             .text_color(cx.theme().colors().text)
             .child(title.to_owned())
     }
 
-    pub fn title_for<V: 'static, T>(cx: &mut ViewContext<V>) -> impl Component<V> {
+    pub fn title_for<V: 'static, T>(cx: &mut ViewContext<V>) -> impl Element<V> {
         Self::title(cx, std::any::type_name::<T>())
     }
 
-    pub fn label<V: 'static>(cx: &mut ViewContext<V>, label: &str) -> impl Component<V> {
+    pub fn label<V: 'static>(cx: &mut ViewContext<V>, label: &str) -> impl Element<V> {
         div()
             .mt_4()
             .mb_2()

crates/ui2/src/to_extract/assistant_panel.rs 🔗

@@ -21,7 +21,7 @@ impl AssistantPanel {
         self
     }
 
-    fn render<V: 'static>(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         Panel::new(self.id.clone(), cx)
             .children(vec![div()
                 .flex()

crates/ui2/src/to_extract/breadcrumb.rs 🔗

@@ -22,7 +22,7 @@ impl Breadcrumb {
             .text_color(cx.theme().colors().text_muted)
     }
 
-    fn render<V: 'static>(self, view_state: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, view_state: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let symbols_len = self.symbols.len();
 
         h_stack()

crates/ui2/src/to_extract/buffer.rs 🔗

@@ -154,7 +154,7 @@ impl Buffer {
         self
     }
 
-    fn render_row<V: 'static>(row: BufferRow, cx: &WindowContext) -> impl Component<V> {
+    fn render_row<V: 'static>(row: BufferRow, cx: &WindowContext) -> impl Element<V> {
         let line_background = if row.current {
             cx.theme().colors().editor_active_line_background
         } else {
@@ -202,7 +202,7 @@ impl Buffer {
             }))
     }
 
-    fn render_rows<V: 'static>(&self, cx: &WindowContext) -> Vec<impl Component<V>> {
+    fn render_rows<V: 'static>(&self, cx: &WindowContext) -> Vec<impl Element<V>> {
         match &self.rows {
             Some(rows) => rows
                 .rows
@@ -213,7 +213,7 @@ impl Buffer {
         }
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let rows = self.render_rows(cx);
 
         v_stack()

crates/ui2/src/to_extract/chat_panel.rs 🔗

@@ -21,7 +21,7 @@ impl ChatPanel {
         self
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         div()
             .id(self.element_id.clone())
             .flex()
@@ -83,7 +83,7 @@ impl ChatMessage {
         }
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         div()
             .flex()
             .flex_col()

crates/ui2/src/to_extract/collab_panel.rs 🔗

@@ -14,7 +14,7 @@ impl CollabPanel {
         Self { id: id.into() }
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         v_stack()
             .id(self.id.clone())
             .h_full()

crates/ui2/src/to_extract/command_palette.rs 🔗

@@ -11,7 +11,7 @@ impl CommandPalette {
         Self { id: id.into() }
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         div().id(self.id.clone()).child(
             Palette::new("palette")
                 .items(example_editor_actions())

crates/ui2/src/to_extract/copilot.rs 🔗

@@ -10,7 +10,7 @@ impl CopilotModal {
         Self { id: id.into() }
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         div().id(self.id.clone()).child(
             Modal::new("some-id")
                 .title("Connect Copilot to Zed")

crates/ui2/src/to_extract/language_selector.rs 🔗

@@ -11,7 +11,7 @@ impl LanguageSelector {
         Self { id: id.into() }
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         div().id(self.id.clone()).child(
             Palette::new("palette")
                 .items(vec![

crates/ui2/src/to_extract/multi_buffer.rs 🔗

@@ -11,7 +11,7 @@ impl MultiBuffer {
         Self { buffers }
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         v_stack()
             .w_full()
             .h_full()

crates/ui2/src/to_extract/notifications_panel.rs 🔗

@@ -15,7 +15,7 @@ impl NotificationsPanel {
         Self { id: id.into() }
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         div()
             .id(self.id.clone())
             .flex()
@@ -241,7 +241,7 @@ impl<V> Notification<V> {
         self
     }
 
-    fn render_meta_items(&self, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render_meta_items(&self, cx: &mut ViewContext<V>) -> impl Element<V> {
         if let Some(meta) = &self.meta {
             h_stack().children(
                 meta.items
@@ -260,14 +260,14 @@ impl<V> Notification<V> {
         }
     }
 
-    fn render_slot(&self, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render_slot(&self, cx: &mut ViewContext<V>) -> impl Element<V> {
         match &self.slot {
             ActorOrIcon::Actor(actor) => Avatar::new(actor.avatar.clone()).render(),
             ActorOrIcon::Icon(icon) => IconElement::new(icon.clone()).render(),
         }
     }
 
-    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         div()
             .relative()
             .id(self.id.clone())

crates/ui2/src/to_extract/panes.rs 🔗

@@ -35,7 +35,7 @@ impl<V: 'static> Pane<V> {
         self
     }
 
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         div()
             .id(self.id.clone())
             .flex()
@@ -89,7 +89,7 @@ impl<V: 'static> PaneGroup<V> {
         }
     }
 
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         if !self.panes.is_empty() {
             let el = div()
                 .flex()

crates/ui2/src/to_extract/project_panel.rs 🔗

@@ -14,7 +14,7 @@ impl ProjectPanel {
         Self { id: id.into() }
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         div()
             .id(self.id.clone())
             .flex()

crates/ui2/src/to_extract/recent_projects.rs 🔗

@@ -11,7 +11,7 @@ impl RecentProjects {
         Self { id: id.into() }
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         div().id(self.id.clone()).child(
             Palette::new("palette")
                 .items(vec![

crates/ui2/src/to_extract/tab_bar.rs 🔗

@@ -23,7 +23,7 @@ impl TabBar {
         self
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let (can_navigate_back, can_navigate_forward) = self.can_navigate;
 
         div()

crates/ui2/src/to_extract/terminal.rs 🔗

@@ -11,7 +11,7 @@ impl Terminal {
         Self
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let can_navigate_back = true;
         let can_navigate_forward = false;
 

crates/ui2/src/to_extract/theme_selector.rs 🔗

@@ -11,7 +11,7 @@ impl ThemeSelector {
         Self { id: id.into() }
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         div().child(
             Palette::new(self.id.clone())
                 .items(vec![

crates/ui2/src/to_extract/toolbar.rs 🔗

@@ -20,7 +20,7 @@ impl<V: 'static> Toolbar<V> {
         }
     }
 
-    pub fn left_item(mut self, child: impl Component<V>) -> Self
+    pub fn left_item(mut self, child: impl Element<V>) -> Self
     where
         Self: Sized,
     {
@@ -28,7 +28,7 @@ impl<V: 'static> Toolbar<V> {
         self
     }
 
-    pub fn left_items(mut self, iter: impl IntoIterator<Item = impl Component<V>>) -> Self
+    pub fn left_items(mut self, iter: impl IntoIterator<Item = impl Element<V>>) -> Self
     where
         Self: Sized,
     {
@@ -37,7 +37,7 @@ impl<V: 'static> Toolbar<V> {
         self
     }
 
-    pub fn right_item(mut self, child: impl Component<V>) -> Self
+    pub fn right_item(mut self, child: impl Element<V>) -> Self
     where
         Self: Sized,
     {
@@ -45,7 +45,7 @@ impl<V: 'static> Toolbar<V> {
         self
     }
 
-    pub fn right_items(mut self, iter: impl IntoIterator<Item = impl Component<V>>) -> Self
+    pub fn right_items(mut self, iter: impl IntoIterator<Item = impl Element<V>>) -> Self
     where
         Self: Sized,
     {
@@ -54,7 +54,7 @@ impl<V: 'static> Toolbar<V> {
         self
     }
 
-    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         div()
             .bg(cx.theme().colors().toolbar_background)
             .p_2()

crates/ui2/src/to_extract/traffic_lights.rs 🔗

@@ -21,7 +21,7 @@ impl TrafficLight {
         }
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let system_colors = &cx.theme().styles.system;
 
         let fill = match (self.window_has_focus, self.color) {
@@ -52,7 +52,7 @@ impl TrafficLights {
         self
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         div()
             .flex()
             .items_center()