Element refinement passing on ui2

Nathan Sobo created

Change summary

crates/collab_ui2/src/collab_panel.rs            |   2 
crates/collab_ui2/src/collab_titlebar_item.rs    |   6 
crates/command_palette2/src/command_palette.rs   |   2 
crates/editor2/src/editor.rs                     |  12 
crates/editor2/src/editor_tests.rs               |   2 
crates/editor2/src/element.rs                    |   6 
crates/editor2/src/items.rs                      |   2 
crates/file_finder2/src/file_finder.rs           |   4 
crates/go_to_line2/src/go_to_line.rs             |   2 
crates/gpui2/src/app.rs                          |   2 
crates/gpui2/src/app/async_context.rs            |   6 
crates/gpui2/src/app/test_context.rs             |  12 
crates/gpui2/src/element.rs                      | 271 ++++++++++-------
crates/gpui2/src/elements/div.rs                 |  80 ++--
crates/gpui2/src/elements/img.rs                 |  21 
crates/gpui2/src/elements/overlay.rs             |  20 
crates/gpui2/src/elements/svg.rs                 |  21 
crates/gpui2/src/elements/text.rs                |  88 ++---
crates/gpui2/src/elements/uniform_list.rs        |  30 +
crates/gpui2/src/gpui2.rs                        |   4 
crates/gpui2/src/interactive.rs                  |  14 
crates/gpui2/src/prelude.rs                      |   5 
crates/gpui2/src/view.rs                         | 156 ++++++---
crates/gpui2/src/window.rs                       |  18 
crates/gpui2_macros/src/derive_element.rs        |  75 ----
crates/gpui2_macros/src/derive_render_once.rs    |  64 ++++
crates/gpui2_macros/src/gpui2_macros.rs          |   8 
crates/project_panel2/src/project_panel.rs       |   6 
crates/storybook2/src/stories/text.rs            |   4 
crates/storybook2/src/stories/z_index.rs         |   2 
crates/terminal_view2/src/terminal_panel.rs      |   2 
crates/terminal_view2/src/terminal_view.rs       |   7 
crates/theme2/src/story.rs                       |   2 
crates/theme2/src/styles/players.rs              |   4 
crates/ui2/src/components/avatar.rs              |  36 +-
crates/ui2/src/components/button.rs              |  85 ++++
crates/ui2/src/components/checkbox.rs            | 130 ++++++++
crates/ui2/src/components/context_menu.rs        |  29 
crates/ui2/src/components/details.rs             |  33 +
crates/ui2/src/components/divider.rs             |   4 
crates/ui2/src/components/facepile.rs            |  23 
crates/ui2/src/components/icon.rs                |  23 +
crates/ui2/src/components/icon_button.rs         | 110 +++---
crates/ui2/src/components/indicator.rs           |  20 +
crates/ui2/src/components/input.rs               |  86 ++--
crates/ui2/src/components/keybinding.rs          |  54 ++-
crates/ui2/src/components/label.rs               | 109 +++---
crates/ui2/src/components/list.rs                | 169 ++++++++--
crates/ui2/src/components/modal.rs               |  63 ++-
crates/ui2/src/components/notification_toast.rs  |   2 
crates/ui2/src/components/palette.rs             | 127 ++++---
crates/ui2/src/components/panel.rs               |  54 +-
crates/ui2/src/components/player_stack.rs        |  22 
crates/ui2/src/components/tab.rs                 | 128 ++++----
crates/ui2/src/components/toast.rs               |  64 +--
crates/ui2/src/components/toggle.rs              |   2 
crates/ui2/src/components/tool_divider.rs        |  11 
crates/ui2/src/components/tooltip.rs             |   4 
crates/ui2/src/prelude.rs                        |   4 
crates/ui2/src/static_data.rs                    |  66 ++--
crates/ui2/src/story.rs                          |  14 
crates/ui2/src/to_extract/assistant_panel.rs     |  38 +-
crates/ui2/src/to_extract/breadcrumb.rs          |  45 +-
crates/ui2/src/to_extract/buffer.rs              |  25 +
crates/ui2/src/to_extract/buffer_search.rs       |   5 
crates/ui2/src/to_extract/chat_panel.rs          |  60 ++-
crates/ui2/src/to_extract/collab_panel.rs        |  20 
crates/ui2/src/to_extract/command_palette.rs     |  19 
crates/ui2/src/to_extract/copilot.rs             |  33 +
crates/ui2/src/to_extract/editor_pane.rs         |   4 
crates/ui2/src/to_extract/language_selector.rs   |  30 +
crates/ui2/src/to_extract/multi_buffer.rs        |  19 
crates/ui2/src/to_extract/notifications_panel.rs | 180 ++++++-----
crates/ui2/src/to_extract/panes.rs               |  87 +++--
crates/ui2/src/to_extract/project_panel.rs       |  42 ++
crates/ui2/src/to_extract/recent_projects.rs     |  19 
crates/ui2/src/to_extract/status_bar.rs          |  48 +-
crates/ui2/src/to_extract/tab_bar.rs             |  38 +-
crates/ui2/src/to_extract/terminal.rs            |  75 ++++
crates/ui2/src/to_extract/theme_selector.rs      |  20 
crates/ui2/src/to_extract/title_bar.rs           |   6 
crates/ui2/src/to_extract/toolbar.rs             |  54 +-
crates/ui2/src/to_extract/traffic_lights.rs      |  55 ++-
crates/ui2/src/to_extract/workspace.rs           |   6 
crates/workspace2/src/dock.rs                    |   2 
crates/workspace2/src/status_bar.rs              |   4 
crates/workspace2/src/workspace2.rs              |   8 
87 files changed, 2,034 insertions(+), 1,340 deletions(-)

Detailed changes

crates/collab_ui2/src/collab_panel.rs 🔗

@@ -160,7 +160,7 @@ use std::sync::Arc;
 use db::kvp::KEY_VALUE_STORE;
 use gpui::{
     actions, div, serde_json, AppContext, AsyncWindowContext, Div, EventEmitter, FocusHandle,
-    Focusable, FocusableView, InteractiveComponent, ParentComponent, Render, View, ViewContext,
+    Focusable, FocusableView, InteractiveElement, ParentElement, Render, View, ViewContext,
     VisualContext, WeakView,
 };
 use project::Fs;

crates/collab_ui2/src/collab_titlebar_item.rs 🔗

@@ -31,9 +31,9 @@ use std::sync::Arc;
 use call::ActiveCall;
 use client::{Client, UserStore};
 use gpui::{
-    div, px, rems, AppContext, Component, Div, InteractiveComponent, Model, ParentComponent,
-    Render, Stateful, StatefulInteractiveComponent, Styled, Subscription, ViewContext,
-    VisualContext, WeakView, WindowBounds,
+    div, px, rems, AppContext, Component, Div, InteractiveElement, Model, ParentElement, Render,
+    Stateful, StatefulInteractiveElement, Styled, Subscription, ViewContext, VisualContext,
+    WeakView, WindowBounds,
 };
 use project::Project;
 use theme::ActiveTheme;

crates/command_palette2/src/command_palette.rs 🔗

@@ -2,7 +2,7 @@ use collections::{CommandPaletteFilter, HashMap};
 use fuzzy::{StringMatch, StringMatchCandidate};
 use gpui::{
     actions, div, prelude::*, Action, AppContext, Component, Dismiss, Div, FocusHandle, Keystroke,
-    ManagedView, ParentComponent, Render, Styled, View, ViewContext, VisualContext, WeakView,
+    ManagedView, ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
 };
 use picker::{Picker, PickerDelegate};
 use std::{

crates/editor2/src/editor.rs 🔗

@@ -42,7 +42,7 @@ use gpui::{
     actions, div, point, prelude::*, px, relative, rems, size, uniform_list, Action, AnyElement,
     AppContext, AsyncWindowContext, BackgroundExecutor, Bounds, ClipboardItem, Component, Context,
     EventEmitter, FocusHandle, FocusableView, FontFeatures, FontStyle, FontWeight, HighlightStyle,
-    Hsla, InputHandler, KeyContext, Model, MouseButton, ParentComponent, Pixels, Render, Styled,
+    Hsla, InputHandler, KeyContext, Model, MouseButton, ParentElement, Pixels, Render, Styled,
     Subscription, Task, TextStyle, UniformListScrollHandle, View, ViewContext, VisualContext,
     WeakView, WindowContext,
 };
@@ -1595,7 +1595,7 @@ impl CodeActionsMenu {
                 .max_by_key(|(_, action)| action.lsp_action.title.chars().count())
                 .map(|(ix, _)| ix),
         )
-        .render();
+        .render_once();
 
         if self.deployed_from_indicator {
             *cursor_position.column_mut() = 0;
@@ -4365,7 +4365,7 @@ impl Editor {
                             cx,
                         );
                     })
-                    .render(),
+                    .into_any(),
             )
         } else {
             None
@@ -4401,7 +4401,7 @@ impl Editor {
                                         editor.fold_at(&FoldAt { buffer_row }, cx);
                                     }
                                 })
-                                .render()
+                                .into_any()
                         })
                     })
                     .flatten()
@@ -7792,7 +7792,7 @@ impl Editor {
                                                     cx.editor_style.diagnostic_style.clone(),
                                             },
                                         )))
-                                        .render()
+                                        .render_once()
                                 }
                             }),
                             disposition: BlockDisposition::Below,
@@ -9994,7 +9994,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend
                 cx.write_to_clipboard(ClipboardItem::new(message.clone()));
             })
             .tooltip(|_, cx| Tooltip::text("Copy diagnostic message", cx))
-            .render()
+            .render_once()
     })
 }
 

crates/editor2/src/editor_tests.rs 🔗

@@ -3048,7 +3048,7 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
                 position: snapshot.anchor_after(Point::new(2, 0)),
                 disposition: BlockDisposition::Below,
                 height: 1,
-                render: Arc::new(|_| div().render()),
+                render: Arc::new(|_| div().render_once()),
             }],
             Some(Autoscroll::fit()),
             cx,

crates/editor2/src/element.rs 🔗

@@ -20,9 +20,9 @@ use collections::{BTreeMap, HashMap};
 use gpui::{
     div, point, px, relative, size, transparent_black, Action, AnyElement, AvailableSpace,
     BorrowWindow, Bounds, Component, ContentMask, Corners, DispatchPhase, Edges, Element,
-    ElementId, ElementInputHandler, Entity, EntityId, Hsla, InteractiveComponent, LineLayout,
-    MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentComponent, Pixels,
-    ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveComponent, Style, Styled,
+    ElementId, ElementInputHandler, Entity, EntityId, Hsla, InteractiveElement, LineLayout,
+    MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement, Pixels,
+    ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled,
     TextRun, TextStyle, View, ViewContext, WindowContext, WrappedLine,
 };
 use itertools::Itertools;

crates/editor2/src/items.rs 🔗

@@ -9,7 +9,7 @@ use collections::HashSet;
 use futures::future::try_join_all;
 use gpui::{
     div, point, AnyElement, AppContext, AsyncAppContext, Entity, EntityId, EventEmitter,
-    FocusHandle, Model, ParentComponent, Pixels, SharedString, Styled, Subscription, Task, View,
+    FocusHandle, Model, ParentElement, Pixels, SharedString, Styled, Subscription, Task, View,
     ViewContext, VisualContext, WeakView,
 };
 use language::{

crates/file_finder2/src/file_finder.rs 🔗

@@ -2,8 +2,8 @@ use collections::HashMap;
 use editor::{scroll::autoscroll::Autoscroll, Bias, Editor};
 use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
 use gpui::{
-    actions, div, AppContext, Component, Dismiss, Div, FocusHandle, InteractiveComponent,
-    ManagedView, Model, ParentComponent, Render, Styled, Task, View, ViewContext, VisualContext,
+    actions, div, AppContext, Component, Dismiss, Div, FocusHandle, InteractiveElement,
+    ManagedView, Model, ParentElement, Render, Styled, Task, View, ViewContext, VisualContext,
     WeakView,
 };
 use picker::{Picker, PickerDelegate};

crates/go_to_line2/src/go_to_line.rs 🔗

@@ -1,6 +1,6 @@
 use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor};
 use gpui::{
-    actions, div, prelude::*, AppContext, Dismiss, Div, FocusHandle, ManagedView, ParentComponent,
+    actions, div, prelude::*, AppContext, Dismiss, Div, FocusHandle, ManagedView, ParentElement,
     Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext, WindowContext,
 };
 use text::{Bias, Point};

crates/gpui2/src/app.rs 🔗

@@ -424,7 +424,7 @@ impl AppContext {
     /// Opens a new window with the given option and the root view returned by the given function.
     /// The function is invoked with a `WindowContext`, which can be used to interact with window-specific
     /// functionality.
-    pub fn open_window<V: Render>(
+    pub fn open_window<V: 'static + Render<V>>(
         &mut self,
         options: crate::WindowOptions,
         build_root_view: impl FnOnce(&mut WindowContext) -> View<V>,

crates/gpui2/src/app/async_context.rs 🔗

@@ -115,7 +115,7 @@ impl AsyncAppContext {
         build_root_view: impl FnOnce(&mut WindowContext) -> View<V>,
     ) -> Result<WindowHandle<V>>
     where
-        V: Render,
+        V: 'static + Render<V>,
     {
         let app = self
             .app
@@ -286,7 +286,7 @@ impl VisualContext for AsyncWindowContext {
         build_view_state: impl FnOnce(&mut ViewContext<'_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        V: 'static + Render,
+        V: 'static + Render<V>,
     {
         self.window
             .update(self, |_, cx| cx.build_view(build_view_state))
@@ -306,7 +306,7 @@ impl VisualContext for AsyncWindowContext {
         build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        V: Render,
+        V: 'static + Render<V>,
     {
         self.window
             .update(self, |_, cx| cx.replace_root_view(build_view))

crates/gpui2/src/app/test_context.rs 🔗

@@ -126,7 +126,7 @@ impl TestAppContext {
     pub fn add_window<F, V>(&mut self, build_window: F) -> WindowHandle<V>
     where
         F: FnOnce(&mut ViewContext<V>) -> V,
-        V: Render,
+        V: 'static + Render<V>,
     {
         let mut cx = self.app.borrow_mut();
         cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window))
@@ -143,7 +143,7 @@ impl TestAppContext {
     pub fn add_window_view<F, V>(&mut self, build_window: F) -> (View<V>, &mut VisualTestContext)
     where
         F: FnOnce(&mut ViewContext<V>) -> V,
-        V: Render,
+        V: 'static + Render<V>,
     {
         let mut cx = self.app.borrow_mut();
         let window = cx.open_window(WindowOptions::default(), |cx| cx.build_view(build_window));
@@ -543,7 +543,7 @@ impl<'a> VisualContext for VisualTestContext<'a> {
         build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        V: 'static + Render,
+        V: 'static + Render<V>,
     {
         self.window
             .update(self.cx, |_, cx| cx.build_view(build_view))
@@ -565,7 +565,7 @@ impl<'a> VisualContext for VisualTestContext<'a> {
         build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        V: Render,
+        V: 'static + Render<V>,
     {
         self.window
             .update(self.cx, |_, cx| cx.replace_root_view(build_view))
@@ -582,7 +582,7 @@ impl<'a> VisualContext for VisualTestContext<'a> {
 }
 
 impl AnyWindowHandle {
-    pub fn build_view<V: Render + 'static>(
+    pub fn build_view<V: Render<V> + 'static>(
         &self,
         cx: &mut TestAppContext,
         build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
@@ -593,7 +593,7 @@ impl AnyWindowHandle {
 
 pub struct EmptyView {}
 
-impl Render for EmptyView {
+impl Render<Self> for EmptyView {
     type Element = Div<Self>;
 
     fn render(&mut self, _cx: &mut crate::ViewContext<Self>) -> Self::Element {

crates/gpui2/src/element.rs 🔗

@@ -3,9 +3,53 @@ use crate::{
 };
 use derive_more::{Deref, DerefMut};
 pub(crate) use smallvec::SmallVec;
-use std::{any::Any, fmt::Debug};
+use std::{any::Any, fmt::Debug, marker::PhantomData};
 
-pub trait Element<V: 'static>: 'static + Sized {
+pub trait Render<V: 'static>: 'static + Sized {
+    type Element: Element<V> + 'static;
+
+    fn render(&mut self, cx: &mut ViewContext<V>) -> Self::Element;
+}
+
+pub trait RenderOnce<V: 'static>: Sized {
+    type Element: Element<V> + 'static;
+
+    fn render_once(self) -> Self::Element;
+
+    fn render_into_any(self) -> AnyElement<V> {
+        self.render_once().into_any()
+    }
+
+    fn map<U>(self, f: impl FnOnce(Self) -> U) -> U
+    where
+        Self: Sized,
+        U: RenderOnce<V>,
+    {
+        f(self)
+    }
+
+    fn when(self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self
+    where
+        Self: Sized,
+    {
+        self.map(|this| if condition { then(this) } else { this })
+    }
+
+    fn when_some<T>(self, option: Option<T>, then: impl FnOnce(Self, T) -> Self) -> Self
+    where
+        Self: Sized,
+    {
+        self.map(|this| {
+            if let Some(value) = option {
+                then(this, value)
+            } else {
+                this
+            }
+        })
+    }
+}
+
+pub trait Element<V: 'static>: 'static + RenderOnce<V> {
     type State: 'static;
 
     fn element_id(&self) -> Option<ElementId>;
@@ -59,26 +103,105 @@ pub trait Element<V: 'static>: 'static + Sized {
     }
 }
 
+pub trait Component<V: 'static>: 'static {
+    type Rendered: RenderOnce<V>;
+
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered;
+}
+
+pub struct CompositeElement<V, C> {
+    component: Option<C>,
+    view_type: PhantomData<V>,
+}
+
+pub struct CompositeElementState<V: 'static, C: Component<V>> {
+    rendered_element: Option<<C::Rendered as RenderOnce<V>>::Element>,
+    rendered_element_state: <<C::Rendered as RenderOnce<V>>::Element as Element<V>>::State,
+}
+
+impl<V, C> CompositeElement<V, C> {
+    pub fn new(component: C) -> Self {
+        CompositeElement {
+            component: Some(component),
+            view_type: PhantomData,
+        }
+    }
+}
+
+impl<V: 'static, C: Component<V>> Element<V> for CompositeElement<V, C> {
+    type State = CompositeElementState<V, C>;
+
+    fn element_id(&self) -> Option<ElementId> {
+        None
+    }
+
+    fn layout(
+        &mut self,
+        view: &mut V,
+        state: Option<Self::State>,
+        cx: &mut ViewContext<V>,
+    ) -> (LayoutId, Self::State) {
+        let mut element = self
+            .component
+            .take()
+            .unwrap()
+            .render(view, cx)
+            .render_once();
+        let (layout_id, state) = element.layout(view, state.map(|s| s.rendered_element_state), cx);
+        let state = CompositeElementState {
+            rendered_element: Some(element),
+            rendered_element_state: state,
+        };
+        (layout_id, state)
+    }
+
+    fn paint(
+        self,
+        bounds: Bounds<Pixels>,
+        view: &mut V,
+        state: &mut Self::State,
+        cx: &mut ViewContext<V>,
+    ) {
+        state.rendered_element.take().unwrap().paint(
+            bounds,
+            view,
+            &mut state.rendered_element_state,
+            cx,
+        );
+    }
+}
+
+impl<V: 'static, C: Component<V>> RenderOnce<V> for CompositeElement<V, C> {
+    type Element = Self;
+
+    fn render_once(self) -> Self::Element {
+        self
+    }
+}
+
 #[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)]
 pub struct GlobalElementId(SmallVec<[ElementId; 32]>);
 
-pub trait ParentComponent<V: 'static> {
+pub trait ParentElement<V: 'static> {
     fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]>;
 
-    fn child(mut self, child: impl Element<V>) -> Self
+    fn child(mut self, child: impl RenderOnce<V>) -> Self
     where
         Self: Sized,
     {
-        self.children_mut().push(child.into_any());
+        self.children_mut().push(child.render_once().into_any());
         self
     }
 
-    fn children(mut self, children: impl IntoIterator<Item = impl Element<V>>) -> Self
+    fn children(mut self, children: impl IntoIterator<Item = impl RenderOnce<V>>) -> Self
     where
         Self: Sized,
     {
-        self.children_mut()
-            .extend(children.into_iter().map(Element::into_any));
+        self.children_mut().extend(
+            children
+                .into_iter()
+                .map(|child| child.render_once().into_any()),
+        );
         self
     }
 }
@@ -301,7 +424,7 @@ where
 
 pub struct AnyElement<V>(Box<dyn ElementObject<V>>);
 
-impl<V> AnyElement<V> {
+impl<V: 'static> AnyElement<V> {
     pub fn new<E>(element: E) -> Self
     where
         V: 'static,
@@ -343,6 +466,11 @@ impl<V> AnyElement<V> {
     ) {
         self.0.draw(origin, available_space, view_state, cx)
     }
+
+    /// Converts this `AnyElement` into a trait object that can be stored and manipulated.
+    pub fn into_any(self) -> AnyElement<V> {
+        AnyElement::new(self)
+    }
 }
 
 impl<V: 'static> Element<V> for AnyElement<V> {
@@ -373,105 +501,18 @@ impl<V: 'static> Element<V> for AnyElement<V> {
     }
 }
 
-pub trait Component<V> {
-    fn render(self) -> AnyElement<V>;
-
-    fn map<U>(self, f: impl FnOnce(Self) -> U) -> U
-    where
-        Self: Sized,
-        U: Component<V>,
-    {
-        f(self)
-    }
+impl<V: 'static> RenderOnce<V> for AnyElement<V> {
+    type Element = Self;
 
-    fn when(self, condition: bool, then: impl FnOnce(Self) -> Self) -> Self
-    where
-        Self: Sized,
-    {
-        self.map(|this| if condition { then(this) } else { this })
-    }
-
-    fn when_some<T>(self, option: Option<T>, then: impl FnOnce(Self, T) -> Self) -> Self
-    where
-        Self: Sized,
-    {
-        self.map(|this| {
-            if let Some(value) = option {
-                then(this, value)
-            } else {
-                this
-            }
-        })
-    }
-}
-
-impl<V> Component<V> for AnyElement<V> {
-    fn render(self) -> AnyElement<V> {
+    fn render_once(self) -> Self::Element {
         self
     }
 }
 
-impl<V, E, F> Element<V> for Option<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,
-        _: Option<Self::State>,
-        cx: &mut ViewContext<V>,
-    ) -> (LayoutId, Self::State) {
-        let render = self.take().unwrap();
-        let mut rendered_element = (render)(view_state, cx).render();
-        let layout_id = rendered_element.layout(view_state, cx);
-        (layout_id, Some(rendered_element))
-    }
-
-    fn paint(
-        self,
-        _bounds: Bounds<Pixels>,
-        view_state: &mut V,
-        rendered_element: &mut Self::State,
-        cx: &mut ViewContext<V>,
-    ) {
-        rendered_element.take().unwrap().paint(view_state, cx);
-    }
-}
-
-impl<V, E, F> Component<V> for Option<F>
-where
-    V: 'static,
-    E: 'static + Component<V>,
-    F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static,
-{
-    fn render(self) -> AnyElement<V> {
-        AnyElement::new(self)
-    }
-}
-
-impl<V, E, F> Component<V> for F
-where
-    V: 'static,
-    E: 'static + Component<V>,
-    F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static,
-{
-    fn render(self) -> AnyElement<V> {
-        AnyElement::new(Some(self))
-    }
-}
-
-// impl<V, E, F> Element<V> for F
+// impl<V, E, F> Element<V> for Option<F>
 // where
 //     V: 'static,
-//     E: 'static + Component<V>,
+//     E: Element<V>,
 //     F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static,
 // {
 //     type State = Option<AnyElement<V>>;
@@ -483,21 +524,35 @@ where
 //     fn layout(
 //         &mut self,
 //         view_state: &mut V,
-//         element_state: Option<Self::State>,
+//         _: Option<Self::State>,
 //         cx: &mut ViewContext<V>,
 //     ) -> (LayoutId, Self::State) {
-
-//         self(view_state)
-
+//         let render = self.take().unwrap();
+//         let mut element = (render)(view_state, cx).into_any();
+//         let layout_id = element.layout(view_state, cx);
+//         (layout_id, Some(element))
 //     }
 
 //     fn paint(
 //         self,
-//         bounds: Bounds<Pixels>,
+//         _bounds: Bounds<Pixels>,
 //         view_state: &mut V,
-//         element_state: &mut Self::State,
+//         rendered_element: &mut Self::State,
 //         cx: &mut ViewContext<V>,
 //     ) {
-//         todo!()
+//         rendered_element.take().unwrap().paint(view_state, cx);
+//     }
+// }
+
+// impl<V, E, F> RenderOnce<V> for Option<F>
+// where
+//     V: 'static,
+//     E: Element<V>,
+//     F: FnOnce(&mut V, &mut ViewContext<V>) -> E + 'static,
+// {
+//     type Element = Self;
+
+//     fn render(self) -> Self::Element {
+//         self
 //     }
 // }

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

@@ -1,9 +1,9 @@
 use crate::{
     point, px, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, BorrowAppContext,
-    BorrowWindow, Bounds, ClickEvent, Component, DispatchPhase, Element, ElementId, FocusEvent,
-    FocusHandle, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent,
-    MouseMoveEvent, MouseUpEvent, ParentComponent, Pixels, Point, Render, ScrollWheelEvent,
-    SharedString, Size, Style, StyleRefinement, Styled, Task, View, ViewContext, Visibility,
+    BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusEvent, FocusHandle,
+    KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent,
+    MouseUpEvent, ParentElement, Pixels, Point, Render, RenderOnce, ScrollWheelEvent, SharedString,
+    Size, Style, StyleRefinement, Styled, Task, View, ViewContext, Visibility,
 };
 use collections::HashMap;
 use refineable::Refineable;
@@ -28,7 +28,7 @@ pub struct GroupStyle {
     pub style: StyleRefinement,
 }
 
-pub trait InteractiveComponent<V: 'static>: Sized + Element<V> {
+pub trait InteractiveElement<V: 'static>: Sized + Element<V> {
     fn interactivity(&mut self) -> &mut Interactivity<V>;
 
     fn group(mut self, group: impl Into<SharedString>) -> Self {
@@ -314,7 +314,7 @@ pub trait InteractiveComponent<V: 'static>: Sized + Element<V> {
     }
 }
 
-pub trait StatefulInteractiveComponent<V: 'static, E: Element<V>>: InteractiveComponent<V> {
+pub trait StatefulInteractiveElement<V: 'static, E: Element<V>>: InteractiveElement<V> {
     fn focusable(mut self) -> Focusable<V, Self> {
         self.interactivity().focusable = true;
         Focusable {
@@ -381,7 +381,7 @@ pub trait StatefulInteractiveComponent<V: 'static, E: Element<V>>: InteractiveCo
     ) -> Self
     where
         Self: Sized,
-        W: 'static + Render,
+        W: 'static + Render<W>,
     {
         debug_assert!(
             self.interactivity().drag_listener.is_none(),
@@ -425,7 +425,7 @@ pub trait StatefulInteractiveComponent<V: 'static, E: Element<V>>: InteractiveCo
     }
 }
 
-pub trait FocusableComponent<V: 'static>: InteractiveComponent<V> {
+pub trait FocusableElement<V: 'static>: InteractiveElement<V> {
     fn focus(mut self, f: impl FnOnce(StyleRefinement) -> StyleRefinement) -> Self
     where
         Self: Sized,
@@ -587,13 +587,13 @@ impl<V> Styled for Div<V> {
     }
 }
 
-impl<V: 'static> InteractiveComponent<V> for Div<V> {
+impl<V: 'static> InteractiveElement<V> for Div<V> {
     fn interactivity(&mut self) -> &mut Interactivity<V> {
         &mut self.interactivity
     }
 }
 
-impl<V: 'static> ParentComponent<V> for Div<V> {
+impl<V: 'static> ParentElement<V> for Div<V> {
     fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
         &mut self.children
     }
@@ -691,9 +691,11 @@ impl<V: 'static> Element<V> for Div<V> {
     }
 }
 
-impl<V: 'static> Component<V> for Div<V> {
-    fn render(self) -> AnyElement<V> {
-        AnyElement::new(self)
+impl<V: 'static> RenderOnce<V> for Div<V> {
+    type Element = Self;
+
+    fn render_once(self) -> Self::Element {
+        self
     }
 }
 
@@ -1257,19 +1259,19 @@ pub struct Focusable<V, E> {
     view_type: PhantomData<V>,
 }
 
-impl<V: 'static, E: InteractiveComponent<V>> FocusableComponent<V> for Focusable<V, E> {}
+impl<V: 'static + Render<V>, E: InteractiveElement<V>> FocusableElement<V> for Focusable<V, E> {}
 
-impl<V, E> InteractiveComponent<V> for Focusable<V, E>
+impl<V, E> InteractiveElement<V> for Focusable<V, E>
 where
-    V: 'static,
-    E: InteractiveComponent<V>,
+    V: 'static + Render<V>,
+    E: InteractiveElement<V>,
 {
     fn interactivity(&mut self) -> &mut Interactivity<V> {
         self.element.interactivity()
     }
 }
 
-impl<V: 'static, E: StatefulInteractiveComponent<V, E>> StatefulInteractiveComponent<V, E>
+impl<V: 'static + Render<V>, E: StatefulInteractiveElement<V, E>> StatefulInteractiveElement<V, E>
     for Focusable<V, E>
 {
 }
@@ -1286,7 +1288,7 @@ where
 
 impl<V, E> Element<V> for Focusable<V, E>
 where
-    V: 'static,
+    V: 'static + Render<V>,
     E: Element<V>,
 {
     type State = E::State;
@@ -1315,20 +1317,22 @@ where
     }
 }
 
-impl<V, E> Component<V> for Focusable<V, E>
+impl<V, E> RenderOnce<V> for Focusable<V, E>
 where
-    V: 'static,
-    E: 'static + Element<V>,
+    V: 'static + Render<V>,
+    E: Element<V>,
 {
-    fn render(self) -> AnyElement<V> {
-        AnyElement::new(self)
+    type Element = Self;
+
+    fn render_once(self) -> Self::Element {
+        self
     }
 }
 
-impl<V, E> ParentComponent<V> for Focusable<V, E>
+impl<V, E> ParentElement<V> for Focusable<V, E>
 where
     V: 'static,
-    E: ParentComponent<V>,
+    E: ParentElement<V>,
 {
     fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
         self.element.children_mut()
@@ -1350,25 +1354,25 @@ where
     }
 }
 
-impl<V, E> StatefulInteractiveComponent<V, E> for Stateful<V, E>
+impl<V, E> StatefulInteractiveElement<V, E> for Stateful<V, E>
 where
     V: 'static,
     E: Element<V>,
-    Self: InteractiveComponent<V>,
+    Self: InteractiveElement<V>,
 {
 }
 
-impl<V, E> InteractiveComponent<V> for Stateful<V, E>
+impl<V, E> InteractiveElement<V> for Stateful<V, E>
 where
     V: 'static,
-    E: InteractiveComponent<V>,
+    E: InteractiveElement<V>,
 {
     fn interactivity(&mut self) -> &mut Interactivity<V> {
         self.element.interactivity()
     }
 }
 
-impl<V: 'static, E: FocusableComponent<V>> FocusableComponent<V> for Stateful<V, E> {}
+impl<V: 'static, E: FocusableElement<V>> FocusableElement<V> for Stateful<V, E> {}
 
 impl<V, E> Element<V> for Stateful<V, E>
 where
@@ -1401,20 +1405,22 @@ where
     }
 }
 
-impl<V, E> Component<V> for Stateful<V, E>
+impl<V, E> RenderOnce<V> for Stateful<V, E>
 where
     V: 'static,
-    E: 'static + Element<V>,
+    E: Element<V>,
 {
-    fn render(self) -> AnyElement<V> {
-        AnyElement::new(self)
+    type Element = Self;
+
+    fn render_once(self) -> Self::Element {
+        self
     }
 }
 
-impl<V, E> ParentComponent<V> for Stateful<V, E>
+impl<V, E> ParentElement<V> for Stateful<V, E>
 where
     V: 'static,
-    E: ParentComponent<V>,
+    E: ParentElement<V>,
 {
     fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
         self.element.children_mut()

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

@@ -1,7 +1,6 @@
 use crate::{
-    AnyElement, BorrowWindow, Bounds, Component, Element, InteractiveComponent,
-    InteractiveElementState, Interactivity, LayoutId, Pixels, SharedString, StyleRefinement,
-    Styled, ViewContext,
+    BorrowWindow, Bounds, Element, InteractiveElement, InteractiveElementState, Interactivity,
+    LayoutId, Pixels, RenderOnce, SharedString, StyleRefinement, Styled, ViewContext,
 };
 use futures::FutureExt;
 use util::ResultExt;
@@ -35,12 +34,6 @@ where
     }
 }
 
-impl<V> Component<V> for Img<V> {
-    fn render(self) -> AnyElement<V> {
-        AnyElement::new(self)
-    }
-}
-
 impl<V> Element<V> for Img<V> {
     type State = InteractiveElementState;
 
@@ -102,13 +95,21 @@ impl<V> Element<V> for Img<V> {
     }
 }
 
+impl<V: 'static> RenderOnce<V> for Img<V> {
+    type Element = Self;
+
+    fn render_once(self) -> Self::Element {
+        self
+    }
+}
+
 impl<V> Styled for Img<V> {
     fn style(&mut self) -> &mut StyleRefinement {
         &mut self.interactivity.base_style
     }
 }
 
-impl<V> InteractiveComponent<V> for Img<V> {
+impl<V> InteractiveElement<V> for Img<V> {
     fn interactivity(&mut self) -> &mut Interactivity<V> {
         &mut self.interactivity
     }

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

@@ -2,8 +2,8 @@ use smallvec::SmallVec;
 use taffy::style::{Display, Position};
 
 use crate::{
-    point, AnyElement, BorrowWindow, Bounds, Component, Element, LayoutId, ParentComponent, Pixels,
-    Point, Size, Style,
+    point, AnyElement, BorrowWindow, Bounds, Element, LayoutId, ParentElement, Pixels, Point,
+    RenderOnce, Size, Style,
 };
 
 pub struct OverlayState {
@@ -51,18 +51,12 @@ impl<V> Overlay<V> {
     }
 }
 
-impl<V: 'static> ParentComponent<V> for Overlay<V> {
+impl<V: 'static> ParentElement<V> for Overlay<V> {
     fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
         &mut self.children
     }
 }
 
-impl<V: 'static> Component<V> for Overlay<V> {
-    fn render(self) -> AnyElement<V> {
-        AnyElement::new(self)
-    }
-}
-
 impl<V: 'static> Element<V> for Overlay<V> {
     type State = OverlayState;
 
@@ -163,6 +157,14 @@ impl<V: 'static> Element<V> for Overlay<V> {
     }
 }
 
+impl<V: 'static> RenderOnce<V> for Overlay<V> {
+    type Element = Self;
+
+    fn render_once(self) -> Self::Element {
+        self
+    }
+}
+
 enum Axis {
     Horizontal,
     Vertical,

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

@@ -1,7 +1,6 @@
 use crate::{
-    AnyElement, Bounds, Component, Element, ElementId, InteractiveComponent,
-    InteractiveElementState, Interactivity, LayoutId, Pixels, SharedString, StyleRefinement,
-    Styled, ViewContext,
+    Bounds, Element, ElementId, InteractiveElement, InteractiveElementState, Interactivity,
+    LayoutId, Pixels, RenderOnce, SharedString, StyleRefinement, Styled, ViewContext,
 };
 use util::ResultExt;
 
@@ -24,12 +23,6 @@ impl<V> Svg<V> {
     }
 }
 
-impl<V> Component<V> for Svg<V> {
-    fn render(self) -> AnyElement<V> {
-        AnyElement::new(self)
-    }
-}
-
 impl<V> Element<V> for Svg<V> {
     type State = InteractiveElementState;
 
@@ -66,13 +59,21 @@ impl<V> Element<V> for Svg<V> {
     }
 }
 
+impl<V: 'static> RenderOnce<V> for Svg<V> {
+    type Element = Self;
+
+    fn render_once(self) -> Self::Element {
+        self
+    }
+}
+
 impl<V> Styled for Svg<V> {
     fn style(&mut self) -> &mut StyleRefinement {
         &mut self.interactivity.base_style
     }
 }
 
-impl<V> InteractiveComponent<V> for Svg<V> {
+impl<V> InteractiveElement<V> for Svg<V> {
     fn interactivity(&mut self) -> &mut Interactivity<V> {
         &mut self.interactivity
     }

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

@@ -1,6 +1,6 @@
 use crate::{
-    AnyElement, BorrowWindow, Bounds, Component, Element, ElementId, LayoutId, Pixels,
-    SharedString, Size, TextRun, ViewContext, WindowContext, WrappedLine,
+    BorrowWindow, Bounds, Element, ElementId, LayoutId, Pixels, RenderOnce, SharedString, Size,
+    TextRun, ViewContext, WindowContext, WrappedLine,
 };
 use anyhow::anyhow;
 use parking_lot::{Mutex, MutexGuard};
@@ -37,6 +37,14 @@ impl<V: 'static> Element<V> for &'static str {
     }
 }
 
+impl<V: 'static> RenderOnce<V> for &'static str {
+    type Element = Self;
+
+    fn render_once(self) -> Self::Element {
+        self
+    }
+}
+
 impl<V: 'static> Element<V> for SharedString {
     type State = TextState;
 
@@ -67,32 +75,34 @@ impl<V: 'static> Element<V> for SharedString {
     }
 }
 
-pub struct Text {
+impl<V: 'static> RenderOnce<V> for SharedString {
+    type Element = Self;
+
+    fn render_once(self) -> Self::Element {
+        self
+    }
+}
+
+pub struct StyledText {
     text: SharedString,
     runs: Option<Vec<TextRun>>,
 }
 
-impl Text {
+impl StyledText {
     /// Renders text with runs of different styles.
     ///
     /// Callers are responsible for setting the correct style for each run.
     /// For text with a uniform style, you can usually avoid calling this constructor
     /// and just pass text directly.
-    pub fn styled(text: SharedString, runs: Vec<TextRun>) -> Self {
-        Text {
+    pub fn new(text: SharedString, runs: Vec<TextRun>) -> Self {
+        StyledText {
             text,
             runs: Some(runs),
         }
     }
 }
 
-impl<V: 'static> Component<V> for Text {
-    fn render(self) -> AnyElement<V> {
-        AnyElement::new(self)
-    }
-}
-
-impl<V: 'static> Element<V> for Text {
+impl<V: 'static> Element<V> for StyledText {
     type State = TextState;
 
     fn element_id(&self) -> Option<crate::ElementId> {
@@ -181,9 +191,22 @@ impl<V: 'static> Element<V> for Text {
     }
 }
 
+impl<V: 'static> RenderOnce<V> for StyledText {
+    type Element = Self;
+
+    fn render_once(self) -> Self::Element {
+        self
+    }
+}
+
 #[derive(Default, Clone)]
 pub struct TextState(Arc<Mutex<Option<TextStateInner>>>);
 
+struct TextStateInner {
+    lines: SmallVec<[WrappedLine; 1]>,
+    line_height: Pixels,
+}
+
 impl TextState {
     fn lock(&self) -> MutexGuard<Option<TextStateInner>> {
         self.0.lock()
@@ -264,14 +287,9 @@ impl TextState {
     }
 }
 
-struct TextStateInner {
-    lines: SmallVec<[WrappedLine; 1]>,
-    line_height: Pixels,
-}
-
 struct InteractiveText {
     id: ElementId,
-    text: Text,
+    text: StyledText,
 }
 
 struct InteractiveTextState {
@@ -325,34 +343,10 @@ impl<V: 'static> Element<V> for InteractiveText {
     }
 }
 
-impl<V: 'static> Component<V> for SharedString {
-    fn render(self) -> AnyElement<V> {
-        Text {
-            text: self,
-            runs: None,
-        }
-        .render()
-    }
-}
-
-impl<V: 'static> Component<V> for &'static str {
-    fn render(self) -> AnyElement<V> {
-        Text {
-            text: self.into(),
-            runs: None,
-        }
-        .render()
-    }
-}
+impl<V: 'static> RenderOnce<V> for InteractiveText {
+    type Element = Self;
 
-// TODO: Figure out how to pass `String` to `child` without this.
-// This impl doesn't exist in the `gpui2` crate.
-impl<V: 'static> Component<V> for String {
-    fn render(self) -> AnyElement<V> {
-        Text {
-            text: self.into(),
-            runs: None,
-        }
-        .render()
+    fn render_once(self) -> Self::Element {
+        self
     }
 }

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

@@ -1,7 +1,7 @@
 use crate::{
-    point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, Component, Element,
-    ElementId, InteractiveComponent, InteractiveElementState, Interactivity, LayoutId, Pixels,
-    Point, Size, StyleRefinement, Styled, ViewContext,
+    point, px, size, AnyElement, AvailableSpace, BorrowWindow, Bounds, Element, ElementId,
+    InteractiveElement, InteractiveElementState, Interactivity, LayoutId, Pixels, Point,
+    RenderOnce, Size, StyleRefinement, Styled, ViewContext,
 };
 use smallvec::SmallVec;
 use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
@@ -10,15 +10,15 @@ use taffy::style::Overflow;
 /// uniform_list provides lazy rendering for a set of items that are of uniform height.
 /// When rendered into a container with overflow-y: hidden and a fixed (or max) height,
 /// uniform_list will only render the visibile subset of items.
-pub fn uniform_list<I, V, C>(
+pub fn uniform_list<I, V, E>(
     id: I,
     item_count: usize,
-    f: impl 'static + Fn(&mut V, Range<usize>, &mut ViewContext<V>) -> Vec<C>,
+    f: impl 'static + Fn(&mut V, Range<usize>, &mut ViewContext<V>) -> Vec<E>,
 ) -> UniformList<V>
 where
     I: Into<ElementId>,
     V: 'static,
-    C: Component<V>,
+    E: Element<V>,
 {
     let id = id.into();
     let mut style = StyleRefinement::default();
@@ -32,7 +32,7 @@ where
         render_items: Box::new(move |view, visible_range, cx| {
             f(view, visible_range, cx)
                 .into_iter()
-                .map(|component| component.render())
+                .map(|component| component.into_any())
                 .collect()
         }),
         interactivity: Interactivity {
@@ -252,6 +252,14 @@ impl<V: 'static> Element<V> for UniformList<V> {
     }
 }
 
+impl<V> RenderOnce<V> for UniformList<V> {
+    type Element = Self;
+
+    fn render_once(self) -> Self::Element {
+        self
+    }
+}
+
 impl<V> UniformList<V> {
     pub fn with_width_from_item(mut self, item_index: Option<usize>) -> Self {
         self.item_to_measure_index = item_index.unwrap_or(0);
@@ -286,14 +294,8 @@ impl<V> UniformList<V> {
     }
 }
 
-impl<V> InteractiveComponent<V> for UniformList<V> {
+impl<V> InteractiveElement<V> for UniformList<V> {
     fn interactivity(&mut self) -> &mut crate::Interactivity<V> {
         &mut self.interactivity
     }
 }
-
-impl<V: 'static> Component<V> for UniformList<V> {
-    fn render(self) -> AnyElement<V> {
-        AnyElement::new(self)
-    }
-}

crates/gpui2/src/gpui2.rs 🔗

@@ -121,7 +121,7 @@ pub trait VisualContext: Context {
         build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        V: 'static + Render;
+        V: 'static + Render<V>;
 
     fn update_view<V: 'static, R>(
         &mut self,
@@ -134,7 +134,7 @@ pub trait VisualContext: Context {
         build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        V: Render;
+        V: 'static + Render<V>;
 
     fn focus_view<V>(&mut self, view: &View<V>) -> Self::Result<()>
     where

crates/gpui2/src/interactive.rs 🔗

@@ -1,5 +1,5 @@
 use crate::{
-    div, point, Component, Div, FocusHandle, Keystroke, Modifiers, Pixels, Point, Render,
+    div, point, Div, Element, FocusHandle, Keystroke, Modifiers, Pixels, Point, Render,
     ViewContext,
 };
 use smallvec::SmallVec;
@@ -64,7 +64,7 @@ pub struct Drag<S, R, V, E>
 where
     R: Fn(&mut V, &mut ViewContext<V>) -> E,
     V: 'static,
-    E: Component<()>,
+    E: Element<()>,
 {
     pub state: S,
     pub render_drag_handle: R,
@@ -75,7 +75,7 @@ impl<S, R, V, E> Drag<S, R, V, E>
 where
     R: Fn(&mut V, &mut ViewContext<V>) -> E,
     V: 'static,
-    E: Component<()>,
+    E: Element<()>,
 {
     pub fn new(state: S, render_drag_handle: R) -> Self {
         Drag {
@@ -193,7 +193,7 @@ impl Deref for MouseExitEvent {
 #[derive(Debug, Clone, Default)]
 pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
 
-impl Render for ExternalPaths {
+impl Render<Self> for ExternalPaths {
     type Element = Div<Self>;
 
     fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
@@ -286,8 +286,8 @@ pub struct FocusEvent {
 #[cfg(test)]
 mod test {
     use crate::{
-        self as gpui, div, Component, Div, FocusHandle, InteractiveComponent, KeyBinding,
-        Keystroke, ParentComponent, Render, Stateful, TestAppContext, ViewContext, VisualContext,
+        self as gpui, div, Div, FocusHandle, InteractiveElement, KeyBinding, Keystroke,
+        ParentElement, Stateful, TestAppContext, Render, VisualContext,
     };
 
     struct TestView {
@@ -298,7 +298,7 @@ mod test {
 
     actions!(TestAction);
 
-    impl Render for TestView {
+    impl Render<Self> for TestView {
         type Element = Stateful<Self, Div<Self>>;
 
         fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> Self::Element {

crates/gpui2/src/prelude.rs 🔗

@@ -1,4 +1,5 @@
 pub use crate::{
-    BorrowAppContext, BorrowWindow, Component, Context, FocusableComponent, InteractiveComponent,
-    ParentComponent, Refineable, Render, StatefulInteractiveComponent, Styled, VisualContext,
+    BorrowAppContext, BorrowWindow, Component, Context, Element, FocusableElement,
+    InteractiveElement, ParentElement, Refineable, Render, RenderOnce, StatefulInteractiveElement,
+    Styled, VisualContext,
 };

crates/gpui2/src/view.rs 🔗

@@ -1,7 +1,8 @@
 use crate::{
     private::Sealed, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, BorrowWindow,
-    Bounds, Component, Element, ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView,
-    LayoutId, Model, Pixels, Point, Size, ViewContext, VisualContext, WeakModel, WindowContext,
+    Bounds, Element, ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView, LayoutId,
+    Model, Pixels, Point, Render, RenderOnce, Size, ViewContext, VisualContext, WeakModel,
+    WindowContext,
 };
 use anyhow::{Context, Result};
 use std::{
@@ -9,14 +10,8 @@ use std::{
     hash::{Hash, Hasher},
 };
 
-pub trait Render: 'static + Sized {
-    type Element: Element<Self> + 'static;
-
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element;
-}
-
 pub struct View<V> {
-    pub(crate) model: Model<V>,
+    pub model: Model<V>,
 }
 
 impl<V> Sealed for View<V> {}
@@ -64,13 +59,13 @@ impl<V: 'static> View<V> {
         self.model.read(cx)
     }
 
-    pub fn render_with<C>(&self, component: C) -> RenderViewWith<C, V>
+    pub fn render_with<E>(&self, component: E) -> RenderViewWith<E, V>
     where
-        C: 'static + Component<V>,
+        E: 'static + Element<V>,
     {
         RenderViewWith {
             view: self.clone(),
-            component: Some(component),
+            element: Some(component),
         }
     }
 
@@ -104,12 +99,6 @@ impl<V> PartialEq for View<V> {
 
 impl<V> Eq for View<V> {}
 
-impl<V: Render, ParentViewState: 'static> Component<ParentViewState> for View<V> {
-    fn render(self) -> AnyElement<ParentViewState> {
-        AnyElement::new(AnyView::from(self))
-    }
-}
-
 pub struct WeakView<V> {
     pub(crate) model: WeakModel<V>,
 }
@@ -206,13 +195,7 @@ impl AnyView {
     }
 }
 
-impl<V: 'static> Component<V> for AnyView {
-    fn render(self) -> AnyElement<V> {
-        AnyElement::new(self)
-    }
-}
-
-impl<V: Render> From<View<V>> for AnyView {
+impl<V: 'static + Render<V>> From<View<V>> for AnyView {
     fn from(value: View<V>) -> Self {
         AnyView {
             model: value.model.into_any(),
@@ -222,7 +205,48 @@ impl<V: Render> From<View<V>> for AnyView {
     }
 }
 
-impl<ParentViewState: 'static> Element<ParentViewState> for AnyView {
+impl<V: 'static + Render<V>, ParentV: 'static> Element<ParentV> for View<V> {
+    type State = Option<AnyElement<V>>;
+
+    fn element_id(&self) -> Option<ElementId> {
+        Some(self.model.entity_id.into())
+    }
+
+    fn layout(
+        &mut self,
+        _parent_view: &mut ParentV,
+        _state: Option<Self::State>,
+        cx: &mut ViewContext<ParentV>,
+    ) -> (LayoutId, Self::State) {
+        self.update(cx, |view, cx| {
+            let mut element = view.render(cx).into_any();
+            let layout_id = element.layout(view, cx);
+            (layout_id, Some(element))
+        })
+    }
+
+    fn paint(
+        self,
+        _: Bounds<Pixels>,
+        _parent: &mut ParentV,
+        element: &mut Self::State,
+        cx: &mut ViewContext<ParentV>,
+    ) {
+        self.update(cx, |view, cx| {
+            element.take().unwrap().paint(view, cx);
+        });
+    }
+}
+
+impl<V: 'static + Render<V>, ParentV: 'static> RenderOnce<ParentV> for View<V> {
+    type Element = View<V>;
+
+    fn render_once(self) -> Self::Element {
+        self
+    }
+}
+
+impl<V: 'static> Element<V> for AnyView {
     type State = Option<Box<dyn Any>>;
 
     fn element_id(&self) -> Option<ElementId> {
@@ -231,9 +255,9 @@ impl<ParentViewState: 'static> Element<ParentViewState> for AnyView {
 
     fn layout(
         &mut self,
-        _view_state: &mut ParentViewState,
+        _view_state: &mut V,
         _element_state: Option<Self::State>,
-        cx: &mut ViewContext<ParentViewState>,
+        cx: &mut ViewContext<V>,
     ) -> (LayoutId, Self::State) {
         let (layout_id, rendered_element) = (self.layout)(self, cx);
         (layout_id, Some(rendered_element))
@@ -242,14 +266,22 @@ impl<ParentViewState: 'static> Element<ParentViewState> for AnyView {
     fn paint(
         mut self,
         _bounds: Bounds<Pixels>,
-        _view_state: &mut ParentViewState,
+        _view_state: &mut V,
         rendered_element: &mut Self::State,
-        cx: &mut ViewContext<ParentViewState>,
+        cx: &mut ViewContext<V>,
     ) {
         (self.paint)(&mut self, rendered_element.take().unwrap(), cx)
     }
 }
 
+impl<ParentV: 'static> RenderOnce<ParentV> for AnyView {
+    type Element = Self;
+
+    fn render_once(self) -> Self::Element {
+        self
+    }
+}
+
 pub struct AnyWeakView {
     model: AnyWeakModel,
     layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, Box<dyn Any>),
@@ -267,7 +299,7 @@ impl AnyWeakView {
     }
 }
 
-impl<V: Render> From<WeakView<V>> for AnyWeakView {
+impl<V: 'static + Render<V>> From<WeakView<V>> for AnyWeakView {
     fn from(view: WeakView<V>) -> Self {
         Self {
             model: view.model.into(),
@@ -277,10 +309,10 @@ impl<V: Render> From<WeakView<V>> for AnyWeakView {
     }
 }
 
-impl<T, E> Render for T
+impl<F, E> Render<F> for F
 where
-    T: 'static + FnMut(&mut WindowContext) -> E,
-    E: 'static + Send + Element<T>,
+    F: 'static + FnMut(&mut WindowContext) -> E,
+    E: 'static + Send + Element<F>,
 {
     type Element = E;
 
@@ -289,29 +321,18 @@ where
     }
 }
 
-pub struct RenderViewWith<C, V> {
+pub struct RenderViewWith<E, V> {
     view: View<V>,
-    component: Option<C>,
-}
-
-impl<C, ParentViewState, ViewState> Component<ParentViewState> for RenderViewWith<C, ViewState>
-where
-    C: 'static + Component<ViewState>,
-    ParentViewState: 'static,
-    ViewState: 'static,
-{
-    fn render(self) -> AnyElement<ParentViewState> {
-        AnyElement::new(self)
-    }
+    element: Option<E>,
 }
 
-impl<C, ParentViewState, ViewState> Element<ParentViewState> for RenderViewWith<C, ViewState>
+impl<E, ParentV, V> Element<ParentV> for RenderViewWith<E, V>
 where
-    C: 'static + Component<ViewState>,
-    ParentViewState: 'static,
-    ViewState: 'static,
+    E: 'static + Element<V>,
+    ParentV: 'static,
+    V: 'static,
 {
-    type State = Option<AnyElement<ViewState>>;
+    type State = Option<AnyElement<V>>;
 
     fn element_id(&self) -> Option<ElementId> {
         Some(self.view.entity_id().into())
@@ -319,12 +340,12 @@ where
 
     fn layout(
         &mut self,
-        _: &mut ParentViewState,
+        _: &mut ParentV,
         _: Option<Self::State>,
-        cx: &mut ViewContext<ParentViewState>,
+        cx: &mut ViewContext<ParentV>,
     ) -> (LayoutId, Self::State) {
         self.view.update(cx, |view, cx| {
-            let mut element = self.component.take().unwrap().render();
+            let mut element = self.element.take().unwrap().into_any();
             let layout_id = element.layout(view, cx);
             (layout_id, Some(element))
         })
@@ -333,20 +354,33 @@ where
     fn paint(
         self,
         _: Bounds<Pixels>,
-        _: &mut ParentViewState,
+        _: &mut ParentV,
         element: &mut Self::State,
-        cx: &mut ViewContext<ParentViewState>,
+        cx: &mut ViewContext<ParentV>,
     ) {
         self.view
             .update(cx, |view, cx| element.take().unwrap().paint(view, cx))
     }
 }
 
+impl<E, V, ParentV> RenderOnce<ParentV> for RenderViewWith<E, V>
+where
+    E: 'static + Element<V>,
+    V: 'static,
+    ParentV: 'static,
+{
+    type Element = Self;
+
+    fn render_once(self) -> Self::Element {
+        self
+    }
+}
+
 mod any_view {
     use crate::{AnyElement, AnyView, BorrowWindow, LayoutId, Render, WindowContext};
     use std::any::Any;
 
-    pub(crate) fn layout<V: Render>(
+    pub(crate) fn layout<V: 'static + Render<V>>(
         view: &AnyView,
         cx: &mut WindowContext,
     ) -> (LayoutId, Box<dyn Any>) {
@@ -360,7 +394,11 @@ mod any_view {
         })
     }
 
-    pub(crate) fn paint<V: Render>(view: &AnyView, element: Box<dyn Any>, cx: &mut WindowContext) {
+    pub(crate) fn paint<V: 'static + Render<V>>(
+        view: &AnyView,
+        element: Box<dyn Any>,
+        cx: &mut WindowContext,
+    ) {
         cx.with_element_id(Some(view.model.entity_id), |cx| {
             let view = view.clone().downcast::<V>().unwrap();
             let element = element.downcast::<AnyElement<V>>().unwrap();

crates/gpui2/src/window.rs 🔗

@@ -6,8 +6,8 @@ use crate::{
     InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, LayoutId, Model, ModelContext,
     Modifiers, MonochromeSprite, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path,
     Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
-    PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams,
-    RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet,
+    PolychromeSprite, PromptLevel, Quad, RenderGlyphParams, RenderImageParams, RenderSvgParams,
+    Render, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, SubscriberSet,
     Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext,
     WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS,
 };
@@ -187,13 +187,13 @@ impl Drop for FocusHandle {
 
 /// FocusableView allows users of your view to easily
 /// focus it (using cx.focus_view(view))
-pub trait FocusableView: Render {
+pub trait FocusableView: 'static + Render<Self> {
     fn focus_handle(&self, cx: &AppContext) -> FocusHandle;
 }
 
 /// ManagedView is a view (like a Modal, Popover, Menu, etc.)
 /// where the lifecycle of the view is handled by another view.
-pub trait ManagedView: Render {
+pub trait ManagedView: 'static + Render<Self> {
     fn focus_handle(&self, cx: &AppContext) -> FocusHandle;
 }
 
@@ -1525,7 +1525,7 @@ impl VisualContext for WindowContext<'_> {
         build_view_state: impl FnOnce(&mut ViewContext<'_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        V: 'static + Render,
+        V: 'static + Render<V>,
     {
         let slot = self.app.entities.reserve();
         let view = View {
@@ -1564,7 +1564,7 @@ impl VisualContext for WindowContext<'_> {
         build_view: impl FnOnce(&mut ViewContext<'_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        V: Render,
+        V: 'static + Render<V>,
     {
         let slot = self.app.entities.reserve();
         let view = View {
@@ -2326,7 +2326,7 @@ impl<V> Context for ViewContext<'_, V> {
 }
 
 impl<V: 'static> VisualContext for ViewContext<'_, V> {
-    fn build_view<W: Render + 'static>(
+    fn build_view<W: Render<W> + 'static>(
         &mut self,
         build_view_state: impl FnOnce(&mut ViewContext<'_, W>) -> W,
     ) -> Self::Result<View<W>> {
@@ -2346,7 +2346,7 @@ impl<V: 'static> VisualContext for ViewContext<'_, V> {
         build_view: impl FnOnce(&mut ViewContext<'_, W>) -> W,
     ) -> Self::Result<View<W>>
     where
-        W: Render,
+        W: 'static + Render<W>,
     {
         self.window_cx.replace_root_view(build_view)
     }
@@ -2387,7 +2387,7 @@ pub struct WindowHandle<V> {
     state_type: PhantomData<V>,
 }
 
-impl<V: 'static + Render> WindowHandle<V> {
+impl<V: 'static + Render<V>> WindowHandle<V> {
     pub fn new(id: WindowId) -> Self {
         WindowHandle {
             any_handle: AnyWindowHandle {

crates/gpui2_macros/src/derive_element.rs 🔗

@@ -1,75 +0,0 @@
-use proc_macro::TokenStream;
-use quote::quote;
-use syn::{parse_macro_input, DeriveInput, GenericParam};
-
-pub fn derive_element(input: TokenStream) -> TokenStream {
-    let ast = parse_macro_input!(input as DeriveInput);
-    let type_name = ast.ident;
-
-    let mut view_state_ty = quote! { V };
-
-    for param in &ast.generics.params {
-        if let GenericParam::Type(type_param) = param {
-            let type_ident = &type_param.ident;
-            view_state_ty = quote! {#type_ident};
-            break;
-        }
-    }
-
-    let attrs = &ast.attrs;
-    for attr in attrs {
-        if attr.path.is_ident("element") {
-            match attr.parse_meta() {
-                Ok(syn::Meta::List(i)) => {
-                    for nested_meta in i.nested {
-                        if let syn::NestedMeta::Meta(syn::Meta::NameValue(nv)) = nested_meta {
-                            if nv.path.is_ident("view_state") {
-                                if let syn::Lit::Str(lit_str) = nv.lit {
-                                    view_state_ty = lit_str.value().parse().unwrap();
-                                }
-                            }
-                        }
-                    }
-                }
-                _ => (),
-            }
-        }
-    }
-
-    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
-
-    let gen = quote! {
-        impl #impl_generics gpui::Element<#view_state_ty> for #type_name #ty_generics
-        #where_clause
-        {
-            type State = Option<gpui::AnyElement<#view_state_ty>>;
-
-            fn element_id(&self) -> Option<gpui::ElementId> {
-                None
-            }
-
-            fn layout(
-                &mut self,
-                view_state: &mut #view_state_ty,
-                _element_state: Option<Self::State>,
-                cx: &mut gpui::ViewContext<#view_state_ty>,
-            ) -> (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 #view_state_ty,
-                rendered_element: &mut Self::State,
-                cx: &mut gpui::ViewContext<#view_state_ty>,
-            ) {
-                rendered_element.take().unwrap().paint(view_state, cx)
-            }
-        }
-    };
-
-    gen.into()
-}

crates/gpui2_macros/src/derive_render_once.rs 🔗

@@ -0,0 +1,64 @@
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::{parse_macro_input, parse_quote, DeriveInput};
+
+pub fn derive_render_once(input: TokenStream) -> TokenStream {
+    let ast = parse_macro_input!(input as DeriveInput);
+    let type_name = &ast.ident;
+
+    let mut trait_generics = ast.generics.clone();
+    let view_type = if let Some(view_type) = specified_view_type(&ast) {
+        quote! { #view_type }
+    } else {
+        if let Some(first_type_param) = ast.generics.params.iter().find_map(|param| {
+            if let syn::GenericParam::Type(type_param) = param {
+                Some(type_param.ident.clone())
+            } else {
+                None
+            }
+        }) {
+            quote! { #first_type_param }
+        } else {
+            trait_generics.params.push(parse_quote! { V: 'static });
+            quote! { V }
+        }
+    };
+
+    let (impl_generics, _, where_clause) = trait_generics.split_for_impl();
+    let (_, type_generics, _) = ast.generics.split_for_impl();
+
+    let gen = quote! {
+        impl #impl_generics gpui::RenderOnce<#view_type> for #type_name #type_generics
+        #where_clause
+        {
+            type Element = gpui::CompositeElement<#view_type, Self>;
+
+            fn render_once(self) -> Self::Element {
+                gpui::CompositeElement::new(self)
+            }
+        }
+    };
+
+    if type_name == "Avatar" {
+        println!("{gen}");
+    }
+
+    gen.into()
+}
+
+fn specified_view_type(ast: &DeriveInput) -> Option<proc_macro2::Ident> {
+    ast.attrs.iter().find_map(|attr| {
+        if attr.path.is_ident("view") {
+            if let Ok(syn::Meta::NameValue(meta_name_value)) = attr.parse_meta() {
+                if let syn::Lit::Str(lit_str) = meta_name_value.lit {
+                    return Some(
+                        lit_str
+                            .parse::<syn::Ident>()
+                            .expect("Failed to parse view_type"),
+                    );
+                }
+            }
+        }
+        None
+    })
+}

crates/gpui2_macros/src/gpui2_macros.rs 🔗

@@ -1,6 +1,6 @@
 mod action;
 mod derive_component;
-mod derive_element;
+mod derive_render_once;
 mod register_action;
 mod style_helpers;
 mod test;
@@ -22,9 +22,9 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
     derive_component::derive_component(input)
 }
 
-#[proc_macro_derive(Element, attributes(element))]
-pub fn derive_element(input: TokenStream) -> TokenStream {
-    derive_element::derive_element(input)
+#[proc_macro_derive(RenderOnce, attributes(view))]
+pub fn derive_render_once(input: TokenStream) -> TokenStream {
+    derive_render_once::derive_render_once(input)
 }
 
 #[proc_macro]

crates/project_panel2/src/project_panel.rs 🔗

@@ -10,9 +10,9 @@ use anyhow::{anyhow, Result};
 use gpui::{
     actions, div, px, uniform_list, Action, AppContext, AssetSource, AsyncWindowContext,
     ClipboardItem, Component, Div, EventEmitter, FocusHandle, Focusable, FocusableView,
-    InteractiveComponent, Model, MouseButton, ParentComponent, Pixels, Point, PromptLevel, Render,
-    Stateful, StatefulInteractiveComponent, Styled, Task, UniformListScrollHandle, View,
-    ViewContext, VisualContext as _, WeakView, WindowContext,
+    InteractiveElement, Model, MouseButton, ParentElement, Pixels, Point, PromptLevel, Render,
+    Stateful, StatefulInteractiveElement, Styled, Task, UniformListScrollHandle, View, ViewContext,
+    VisualContext as _, WeakView, WindowContext,
 };
 use menu::{Confirm, SelectNext, SelectPrev};
 use project::{

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

@@ -1,4 +1,6 @@
-use gpui::{div, white, Div, ParentComponent, Render, Styled, View, VisualContext, WindowContext};
+use gpui::{
+    div, white, Div, ParentElement, Render, Styled, View, VisualContext, WindowContext,
+};
 
 pub struct TextStory;
 

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

@@ -79,7 +79,7 @@ trait Styles: Styled + Sized {
 
 impl<V: 'static> Styles for Div<V> {}
 
-#[derive(Component)]
+// #[derive(RenderOnce)]
 struct ZIndexExample {
     z_index: u32,
 }

crates/terminal_view2/src/terminal_panel.rs 🔗

@@ -4,7 +4,7 @@ use crate::TerminalView;
 use db::kvp::KEY_VALUE_STORE;
 use gpui::{
     actions, div, serde_json, AppContext, AsyncWindowContext, Div, Entity, EventEmitter,
-    FocusHandle, FocusableView, ParentComponent, Render, Subscription, Task, View, ViewContext,
+    FocusHandle, FocusableView, ParentElement, Render, Subscription, Task, View, ViewContext,
     VisualContext, WeakView, WindowContext,
 };
 use project::Fs;

crates/terminal_view2/src/terminal_view.rs 🔗

@@ -10,10 +10,9 @@ pub mod terminal_panel;
 use editor::{scroll::autoscroll::Autoscroll, Editor};
 use gpui::{
     actions, div, img, red, Action, AnyElement, AppContext, Component, DispatchPhase, Div,
-    EventEmitter, FocusEvent, FocusHandle, Focusable, FocusableComponent, FocusableView,
-    InputHandler, InteractiveComponent, KeyDownEvent, Keystroke, Model, MouseButton,
-    ParentComponent, Pixels, Render, SharedString, Styled, Task, View, ViewContext, VisualContext,
-    WeakView,
+    EventEmitter, FocusEvent, FocusHandle, Focusable, FocusableElement, FocusableView,
+    InputHandler, InteractiveElement, KeyDownEvent, Keystroke, Model, MouseButton, ParentElement,
+    Pixels, Render, SharedString, Styled, Task, View, ViewContext, VisualContext, WeakView,
 };
 use language::Bias;
 use persistence::TERMINAL_DB;

crates/theme2/src/story.rs 🔗

@@ -1,4 +1,4 @@
-use gpui::{div, Component, Div, Element, ParentComponent, SharedString, Styled, ViewContext};
+use gpui::{div, Div, Element, ParentElement, SharedString, Styled, ViewContext};
 
 use crate::ActiveTheme;
 

crates/theme2/src/styles/players.rs 🔗

@@ -143,11 +143,11 @@ use crate::{amber, blue, jade, lime, orange, pink, purple, red};
 mod stories {
     use super::*;
     use crate::{ActiveTheme, Story};
-    use gpui::{div, img, px, Div, ParentComponent, Render, Styled, ViewContext};
+    use gpui::{div, img, px, Div, ParentElement, Render, Styled, ViewContext};
 
     pub struct PlayerStory;
 
-    impl Render for PlayerStory {
+    impl Render<Self> for PlayerStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -1,26 +1,16 @@
 use crate::prelude::*;
-use gpui::img;
+use gpui::{img, Img, RenderOnce};
 
-#[derive(Element)]
+#[derive(RenderOnce)]
 pub struct Avatar {
     src: SharedString,
     shape: Shape,
 }
 
-impl Avatar {
-    pub fn new(src: impl Into<SharedString>) -> Self {
-        Self {
-            src: src.into(),
-            shape: Shape::Circle,
-        }
-    }
+impl<V: 'static> Component<V> for Avatar {
+    type Rendered = Img<V>;
 
-    pub fn shape(mut self, shape: Shape) -> Self {
-        self.shape = shape;
-        self
-    }
-
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         let mut img = img();
 
         if self.shape == Shape::Circle {
@@ -36,6 +26,20 @@ impl Avatar {
     }
 }
 
+impl Avatar {
+    pub fn new(src: impl Into<SharedString>) -> Self {
+        Self {
+            src: src.into(),
+            shape: Shape::Circle,
+        }
+    }
+
+    pub fn shape(mut self, shape: Shape) -> Self {
+        self.shape = shape;
+        self
+    }
+}
+
 #[cfg(feature = "stories")]
 pub use stories::*;
 
@@ -47,7 +51,7 @@ mod stories {
 
     pub struct AvatarStory;
 
-    impl Render for AvatarStory {
+    impl Render<Self> for AvatarStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -1,6 +1,9 @@
 use std::sync::Arc;
 
-use gpui::{div, DefiniteLength, Hsla, MouseButton, StatefulInteractiveComponent, WindowContext};
+use gpui::{
+    div, DefiniteLength, Div, Hsla, MouseButton, RenderOnce, Stateful, StatefulInteractiveElement,
+    WindowContext,
+};
 
 use crate::prelude::*;
 use crate::{h_stack, Icon, IconButton, IconElement, Label, LineHeightStyle, TextColor};
@@ -76,7 +79,7 @@ impl<V: 'static> Default for ButtonHandlers<V> {
     }
 }
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct Button<V: 'static> {
     disabled: bool,
     handlers: ButtonHandlers<V>,
@@ -88,6 +91,58 @@ pub struct Button<V: 'static> {
     color: Option<TextColor>,
 }
 
+impl<V: 'static> Component<V> for Button<V> {
+    type Rendered = Stateful<V, Div<V>>;
+
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+        let _view: &mut V = view;
+        let (icon_color, label_color) = match (self.disabled, self.color) {
+            (true, _) => (TextColor::Disabled, TextColor::Disabled),
+            (_, None) => (TextColor::Default, TextColor::Default),
+            (_, Some(color)) => (TextColor::from(color), color),
+        };
+
+        let mut button = h_stack()
+            .id(SharedString::from(format!("{}", self.label)))
+            .relative()
+            .p_1()
+            .text_ui()
+            .rounded_md()
+            .bg(self.variant.bg_color(cx))
+            .cursor_pointer()
+            .hover(|style| style.bg(self.variant.bg_color_hover(cx)))
+            .active(|style| style.bg(self.variant.bg_color_active(cx)));
+
+        match (self.icon, self.icon_position) {
+            (Some(_), Some(IconPosition::Left)) => {
+                button = button
+                    .gap_1()
+                    .child(self.render_label(label_color))
+                    .children(self.render_icon(icon_color))
+            }
+            (Some(_), Some(IconPosition::Right)) => {
+                button = button
+                    .gap_1()
+                    .children(self.render_icon(icon_color))
+                    .child(self.render_label(label_color))
+            }
+            (_, _) => button = button.child(self.render_label(label_color)),
+        }
+
+        if let Some(width) = self.width {
+            button = button.w(width).justify_center();
+        }
+
+        if let Some(click_handler) = self.handlers.click.clone() {
+            button = button.on_mouse_down(MouseButton::Left, move |state, event, cx| {
+                click_handler(state, cx);
+            });
+        }
+
+        button
+    }
+}
+
 impl<V: 'static> Button<V> {
     pub fn new(label: impl Into<SharedString>) -> Self {
         Self {
@@ -212,24 +267,28 @@ impl<V: 'static> Button<V> {
     }
 }
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct ButtonGroup<V: 'static> {
     buttons: Vec<Button<V>>,
 }
 
-impl<V: 'static> ButtonGroup<V> {
-    pub fn new(buttons: Vec<Button<V>>) -> Self {
-        Self { buttons }
-    }
+impl<V: 'static> Component<V> for ButtonGroup<V> {
+    type Rendered = Div<V>;
 
-    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
-        let mut el = h_stack().text_ui();
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+        let mut group = h_stack();
 
-        for button in self.buttons {
-            el = el.child(button.render(_view, cx));
+        for button in self.buttons.into_iter() {
+            group = group.child(button.render(view, cx));
         }
 
-        el
+        group
+    }
+}
+
+impl<V: 'static> ButtonGroup<V> {
+    pub fn new(buttons: Vec<Button<V>>) -> Self {
+        Self { buttons }
     }
 }
 
@@ -245,7 +304,7 @@ mod stories {
 
     pub struct ButtonStory;
 
-    impl Render for ButtonStory {
+    impl Render<Self> for ButtonStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -1,4 +1,4 @@
-use gpui::{div, prelude::*, Component, Element, ElementId, Styled, ViewContext};
+use gpui::{div, prelude::*, Div, Element, ElementId, RenderOnce, Stateful, Styled, ViewContext};
 use std::sync::Arc;
 use theme2::ActiveTheme;
 
@@ -11,7 +11,7 @@ pub type CheckHandler<V> = Arc<dyn Fn(Selection, &mut V, &mut ViewContext<V>) +
 /// Checkboxes are used for multiple choices, not for mutually exclusive choices.
 /// Each checkbox works independently from other checkboxes in the list,
 /// therefore checking an additional box does not affect any other selections.
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct Checkbox<V: 'static> {
     id: ElementId,
     checked: Selection,
@@ -19,6 +19,130 @@ pub struct Checkbox<V: 'static> {
     on_click: Option<CheckHandler<V>>,
 }
 
+impl<V: 'static> Component<V> for Checkbox<V> {
+    type Rendered = Stateful<V, Div<V>>;
+
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+        let group_id = format!("checkbox_group_{:?}", self.id);
+
+        let icon = match self.checked {
+            // When selected, we show a checkmark.
+            Selection::Selected => {
+                Some(
+                    IconElement::new(Icon::Check)
+                        .size(crate::IconSize::Small)
+                        .color(
+                            // If the checkbox is disabled we change the color of the icon.
+                            if self.disabled {
+                                TextColor::Disabled
+                            } else {
+                                TextColor::Selected
+                            },
+                        ),
+                )
+            }
+            // In an indeterminate state, we show a dash.
+            Selection::Indeterminate => {
+                Some(
+                    IconElement::new(Icon::Dash)
+                        .size(crate::IconSize::Small)
+                        .color(
+                            // If the checkbox is disabled we change the color of the icon.
+                            if self.disabled {
+                                TextColor::Disabled
+                            } else {
+                                TextColor::Selected
+                            },
+                        ),
+                )
+            }
+            // When unselected, we show nothing.
+            Selection::Unselected => None,
+        };
+
+        // A checkbox could be in an indeterminate state,
+        // for example the indeterminate state could represent:
+        //  - a group of options of which only some are selected
+        //  - an enabled option that is no longer available
+        //  - a previously agreed to license that has been updated
+        //
+        // For the sake of styles we treat the indeterminate state as selected,
+        // but it's icon will be different.
+        let selected =
+            self.checked == Selection::Selected || self.checked == Selection::Indeterminate;
+
+        // We could use something like this to make the checkbox background when selected:
+        //
+        // ~~~rust
+        // ...
+        // .when(selected, |this| {
+        //     this.bg(cx.theme().colors().element_selected)
+        // })
+        // ~~~
+        //
+        // But we use a match instead here because the checkbox might be disabled,
+        // and it could be disabled _while_ it is selected, as well as while it is not selected.
+        let (bg_color, border_color) = match (self.disabled, selected) {
+            (true, _) => (
+                cx.theme().colors().ghost_element_disabled,
+                cx.theme().colors().border_disabled,
+            ),
+            (false, true) => (
+                cx.theme().colors().element_selected,
+                cx.theme().colors().border,
+            ),
+            (false, false) => (
+                cx.theme().colors().element_background,
+                cx.theme().colors().border,
+            ),
+        };
+
+        div()
+            .id(self.id)
+            // Rather than adding `px_1()` to add some space around the checkbox,
+            // we use a larger parent element to create a slightly larger
+            // click area for the checkbox.
+            .size_5()
+            // Because we've enlarged the click area, we need to create a
+            // `group` to pass down interactivity events to the checkbox.
+            .group(group_id.clone())
+            .child(
+                div()
+                    .flex()
+                    // This prevent the flex element from growing
+                    // or shrinking in response to any size changes
+                    .flex_none()
+                    // The combo of `justify_center()` and `items_center()`
+                    // is used frequently to center elements in a flex container.
+                    //
+                    // We use this to center the icon in the checkbox.
+                    .justify_center()
+                    .items_center()
+                    .m_1()
+                    .size_4()
+                    .rounded_sm()
+                    .bg(bg_color)
+                    .border()
+                    .border_color(border_color)
+                    // We only want the interactivity states to fire when we
+                    // are in a checkbox that isn't disabled.
+                    .when(!self.disabled, |this| {
+                        // Here instead of `hover()` we use `group_hover()`
+                        // to pass it the group id.
+                        this.group_hover(group_id.clone(), |el| {
+                            el.bg(cx.theme().colors().element_hover)
+                        })
+                    })
+                    .children(icon),
+            )
+            .when_some(
+                self.on_click.filter(|_| !self.disabled),
+                |this, on_click| {
+                    this.on_click(move |view, _, cx| on_click(self.checked.inverse(), view, cx))
+                },
+            )
+    }
+}
 impl<V: 'static> Checkbox<V> {
     pub fn new(id: impl Into<ElementId>, checked: Selection) -> Self {
         Self {
@@ -175,7 +299,7 @@ mod stories {
 
     pub struct CheckboxStory;
 
-    impl Render for CheckboxStory {
+    impl Render<Self> for CheckboxStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -5,7 +5,8 @@ use crate::prelude::*;
 use crate::{v_stack, Label, List, ListEntry, ListItem, ListSeparator, ListSubHeader};
 use gpui::{
     overlay, px, Action, AnchorCorner, AnyElement, Bounds, Dismiss, DispatchPhase, Div,
-    FocusHandle, LayoutId, ManagedView, MouseButton, MouseDownEvent, Pixels, Point, Render, View,
+    FocusHandle, LayoutId, ManagedView, MouseButton, MouseDownEvent, Pixels, Point, Render,
+    RenderOnce, View,
 };
 
 pub struct ContextMenu {
@@ -52,7 +53,7 @@ impl ContextMenu {
     }
 }
 
-impl Render for ContextMenu {
+impl Render<Self> for ContextMenu {
     type Element = Div<Self>;
     // todo!()
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@@ -96,8 +97,8 @@ impl<V: 'static, M: ManagedView> MenuHandle<V, M> {
         self
     }
 
-    pub fn child<R: Component<V>>(mut self, f: impl FnOnce(bool) -> R + 'static) -> Self {
-        self.child_builder = Some(Box::new(|b| f(b).render()));
+    pub fn child<R: RenderOnce<V>>(mut self, f: impl FnOnce(bool) -> R + 'static) -> Self {
+        self.child_builder = Some(Box::new(|b| f(b).render_once().into_any()));
         self
     }
 
@@ -160,9 +161,9 @@ impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> {
             }
             overlay = overlay.position(*position.borrow());
 
-            let mut view = overlay.child(menu.clone()).render();
-            menu_layout_id = Some(view.layout(view_state, cx));
-            view
+            let mut element = overlay.child(menu.clone()).into_any();
+            menu_layout_id = Some(element.layout(view_state, cx));
+            element
         });
 
         let mut child_element = self
@@ -247,9 +248,11 @@ impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> {
     }
 }
 
-impl<V: 'static, M: ManagedView> Component<V> for MenuHandle<V, M> {
-    fn render(self) -> AnyElement<V> {
-        AnyElement::new(self)
+impl<V: 'static, M: ManagedView> RenderOnce<V> for MenuHandle<V, M> {
+    type Element = Self;
+
+    fn render_once(self) -> Self::Element {
+        self
     }
 }
 
@@ -275,7 +278,7 @@ mod stories {
 
     pub struct ContextMenuStory;
 
-    impl Render for ContextMenuStory {
+    impl Render<Self> for ContextMenuStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@@ -302,7 +305,6 @@ mod stories {
                                     } else {
                                         "RIGHT CLICK ME"
                                     })
-                                    .render()
                                 })
                                 .menu(move |_, cx| build_menu(cx, "top left")),
                         )
@@ -315,7 +317,6 @@ mod stories {
                                     } else {
                                         "RIGHT CLICK ME"
                                     })
-                                    .render()
                                 })
                                 .anchor(AnchorCorner::BottomLeft)
                                 .attach(AnchorCorner::TopLeft)
@@ -336,7 +337,6 @@ mod stories {
                                     } else {
                                         "RIGHT CLICK ME"
                                     })
-                                    .render()
                                 })
                                 .anchor(AnchorCorner::TopRight)
                                 .menu(move |_, cx| build_menu(cx, "top right")),
@@ -350,7 +350,6 @@ mod stories {
                                     } else {
                                         "RIGHT CLICK ME"
                                     })
-                                    .render()
                                 })
                                 .anchor(AnchorCorner::BottomRight)
                                 .attach(AnchorCorner::TopRight)

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

@@ -1,13 +1,29 @@
 use crate::prelude::*;
 use crate::{v_stack, ButtonGroup};
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct Details<V: 'static> {
     text: &'static str,
     meta: Option<&'static str>,
     actions: Option<ButtonGroup<V>>,
 }
 
+impl<V: 'static> Component<V> for Details<V> {
+    type Rendered = Div<V>;
+
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+        v_stack()
+            .p_1()
+            .gap_0p5()
+            .text_ui_sm()
+            .text_color(cx.theme().colors().text)
+            .size_full()
+            .child(self.text)
+            .children(self.meta.map(|m| m))
+            .children(self.actions.map(|a| a))
+    }
+}
+
 impl<V: 'static> Details<V> {
     pub fn new(text: &'static str) -> Self {
         Self {
@@ -26,20 +42,9 @@ impl<V: 'static> Details<V> {
         self.actions = Some(actions);
         self
     }
-
-    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
-        v_stack()
-            .p_1()
-            .gap_0p5()
-            .text_ui_sm()
-            .text_color(cx.theme().colors().text)
-            .size_full()
-            .child(self.text)
-            .children(self.meta.map(|m| m))
-            .children(self.actions.map(|a| a))
-    }
 }
 
+use gpui::{Div, RenderOnce};
 #[cfg(feature = "stories")]
 pub use stories::*;
 
@@ -51,7 +56,7 @@ mod stories {
 
     pub struct DetailsStory;
 
-    impl Render for DetailsStory {
+    impl Render<Self> for DetailsStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -1,3 +1,5 @@
+use gpui::RenderOnce;
+
 use crate::prelude::*;
 
 enum DividerDirection {
@@ -5,7 +7,7 @@ enum DividerDirection {
     Vertical,
 }
 
-#[derive(Component)]
+// #[derive(RenderOnce)]
 pub struct Divider {
     direction: DividerDirection,
     inset: bool,

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

@@ -1,19 +1,15 @@
 use crate::prelude::*;
 use crate::{Avatar, Player};
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct Facepile {
     players: Vec<Player>,
 }
 
-impl Facepile {
-    pub fn new<P: Iterator<Item = Player>>(players: P) -> Self {
-        Self {
-            players: players.collect(),
-        }
-    }
+impl<V: 'static> Component<V> for Facepile {
+    type Rendered = Div<V>;
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         let player_count = self.players.len();
         let player_list = self.players.iter().enumerate().map(|(ix, player)| {
             let isnt_last = ix < player_count - 1;
@@ -26,6 +22,15 @@ impl Facepile {
     }
 }
 
+impl Facepile {
+    pub fn new<P: Iterator<Item = Player>>(players: P) -> Self {
+        Self {
+            players: players.collect(),
+        }
+    }
+}
+
+use gpui::{Div, RenderOnce};
 #[cfg(feature = "stories")]
 pub use stories::*;
 
@@ -37,7 +42,7 @@ mod stories {
 
     pub struct FacepileStory;
 
-    impl Render for FacepileStory {
+    impl Render<Self> for FacepileStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -1,4 +1,4 @@
-use gpui::{rems, svg};
+use gpui::{rems, svg, RenderOnce, Svg};
 use strum::EnumIter;
 
 use crate::prelude::*;
@@ -129,13 +129,30 @@ impl Icon {
     }
 }
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct IconElement {
     path: SharedString,
     color: TextColor,
     size: IconSize,
 }
 
+impl<V: 'static> Component<V> for IconElement {
+    type Rendered = Svg<V>;
+
+    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+        let svg_size = match self.size {
+            IconSize::Small => rems(0.75),
+            IconSize::Medium => rems(0.9375),
+        };
+
+        svg()
+            .size(svg_size)
+            .flex_none()
+            .path(self.path)
+            .text_color(self.color.color(cx))
+    }
+}
+
 impl IconElement {
     pub fn new(icon: Icon) -> Self {
         Self {
@@ -191,7 +208,7 @@ mod stories {
 
     pub struct IconStory;
 
-    impl Render for IconStory {
+    impl Render<Self> for IconStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -1,5 +1,5 @@
 use crate::{h_stack, prelude::*, ClickHandler, Icon, IconElement};
-use gpui::{prelude::*, Action, AnyView, MouseButton};
+use gpui::{prelude::*, Action, AnyView, Div, MouseButton, Stateful};
 use std::sync::Arc;
 
 struct IconButtonHandlers<V: 'static> {
@@ -12,7 +12,7 @@ impl<V: 'static> Default for IconButtonHandlers<V> {
     }
 }
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct IconButton<V: 'static> {
     id: ElementId,
     icon: Icon,
@@ -24,6 +24,61 @@ pub struct IconButton<V: 'static> {
     handlers: IconButtonHandlers<V>,
 }
 
+impl<V: 'static> Component<V> for IconButton<V> {
+    type Rendered = Stateful<V, Div<V>>;
+
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+        let icon_color = match (self.state, self.color) {
+            (InteractionState::Disabled, _) => TextColor::Disabled,
+            (InteractionState::Active, _) => TextColor::Selected,
+            _ => self.color,
+        };
+
+        let (mut bg_color, bg_hover_color, bg_active_color) = match self.variant {
+            ButtonVariant::Filled => (
+                cx.theme().colors().element_background,
+                cx.theme().colors().element_hover,
+                cx.theme().colors().element_active,
+            ),
+            ButtonVariant::Ghost => (
+                cx.theme().colors().ghost_element_background,
+                cx.theme().colors().ghost_element_hover,
+                cx.theme().colors().ghost_element_active,
+            ),
+        };
+
+        if self.selected {
+            bg_color = bg_hover_color;
+        }
+
+        let mut button = h_stack()
+            .id(self.id.clone())
+            .justify_center()
+            .rounded_md()
+            .p_1()
+            .bg(bg_color)
+            .cursor_pointer()
+            .hover(|style| style.bg(bg_hover_color))
+            .active(|style| style.bg(bg_active_color))
+            .child(IconElement::new(self.icon).color(icon_color));
+
+        if let Some(click_handler) = self.handlers.click.clone() {
+            button = button.on_mouse_down(MouseButton::Left, move |state, event, cx| {
+                cx.stop_propagation();
+                click_handler(state, cx);
+            })
+        }
+
+        if let Some(tooltip) = self.tooltip {
+            if !self.selected {
+                button = button.tooltip(move |view: &mut V, cx| (tooltip)(view, cx))
+            }
+        }
+
+        button
+    }
+}
+
 impl<V: 'static> IconButton<V> {
     pub fn new(id: impl Into<ElementId>, icon: Icon) -> Self {
         Self {
@@ -79,55 +134,4 @@ impl<V: 'static> IconButton<V> {
     pub fn action(self, action: Box<dyn Action>) -> Self {
         self.on_click(move |this, cx| cx.dispatch_action(action.boxed_clone()))
     }
-
-    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,
-            _ => self.color,
-        };
-
-        let (mut bg_color, bg_hover_color, bg_active_color) = match self.variant {
-            ButtonVariant::Filled => (
-                cx.theme().colors().element_background,
-                cx.theme().colors().element_hover,
-                cx.theme().colors().element_active,
-            ),
-            ButtonVariant::Ghost => (
-                cx.theme().colors().ghost_element_background,
-                cx.theme().colors().ghost_element_hover,
-                cx.theme().colors().ghost_element_active,
-            ),
-        };
-
-        if self.selected {
-            bg_color = bg_hover_color;
-        }
-
-        let mut button = h_stack()
-            .id(self.id.clone())
-            .justify_center()
-            .rounded_md()
-            .p_1()
-            .bg(bg_color)
-            .cursor_pointer()
-            .hover(|style| style.bg(bg_hover_color))
-            .active(|style| style.bg(bg_active_color))
-            .child(IconElement::new(self.icon).color(icon_color));
-
-        if let Some(click_handler) = self.handlers.click.clone() {
-            button = button.on_mouse_down(MouseButton::Left, move |state, event, cx| {
-                cx.stop_propagation();
-                click_handler(state, cx);
-            })
-        }
-
-        if let Some(tooltip) = self.tooltip.take() {
-            if !self.selected {
-                button = button.tooltip(move |view: &mut V, cx| (tooltip)(view, cx))
-            }
-        }
-
-        button
-    }
 }

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

@@ -1,10 +1,24 @@
-use gpui::px;
-
 use crate::prelude::*;
+use gpui::{px, Div, RenderOnce};
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct UnreadIndicator;
 
+impl<V: 'static> Component<V> for UnreadIndicator {
+    type Rendered = Div<V>;
+
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+        div()
+            .rounded_full()
+            .border_2()
+            .border_color(cx.theme().colors().surface_background)
+            .w(px(9.0))
+            .h(px(9.0))
+            .z_index(2)
+            .bg(cx.theme().status().info)
+    }
+}
+
 impl UnreadIndicator {
     pub fn new() -> Self {
         Self

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

@@ -1,5 +1,5 @@
 use crate::{prelude::*, Label};
-use gpui::prelude::*;
+use gpui::{prelude::*, Div, RenderOnce, Stateful};
 
 #[derive(Default, PartialEq)]
 pub enum InputVariant {
@@ -8,7 +8,7 @@ pub enum InputVariant {
     Filled,
 }
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct Input {
     placeholder: SharedString,
     value: String,
@@ -18,44 +18,10 @@ pub struct Input {
     is_active: bool,
 }
 
-impl Input {
-    pub fn new(placeholder: impl Into<SharedString>) -> Self {
-        Self {
-            placeholder: placeholder.into(),
-            value: "".to_string(),
-            state: InteractionState::default(),
-            variant: InputVariant::default(),
-            disabled: false,
-            is_active: false,
-        }
-    }
+impl<V: 'static> Component<V> for Input {
+    type Rendered = Stateful<V, Div<V>>;
 
-    pub fn value(mut self, value: String) -> Self {
-        self.value = value;
-        self
-    }
-
-    pub fn state(mut self, state: InteractionState) -> Self {
-        self.state = state;
-        self
-    }
-
-    pub fn variant(mut self, variant: InputVariant) -> Self {
-        self.variant = variant;
-        self
-    }
-
-    pub fn disabled(mut self, disabled: bool) -> Self {
-        self.disabled = disabled;
-        self
-    }
-
-    pub fn is_active(mut self, is_active: bool) -> Self {
-        self.is_active = is_active;
-        self
-    }
-
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         let (input_bg, input_hover_bg, input_active_bg) = match self.variant {
             InputVariant::Ghost => (
                 cx.theme().colors().ghost_element_background,
@@ -93,7 +59,7 @@ impl Input {
             .active(|style| style.bg(input_active_bg))
             .flex()
             .items_center()
-            .child(div().flex().items_center().text_ui_sm().map(|this| {
+            .child(div().flex().items_center().text_ui_sm().map(move |this| {
                 if self.value.is_empty() {
                     this.child(placeholder_label)
                 } else {
@@ -103,6 +69,44 @@ impl Input {
     }
 }
 
+impl Input {
+    pub fn new(placeholder: impl Into<SharedString>) -> Self {
+        Self {
+            placeholder: placeholder.into(),
+            value: "".to_string(),
+            state: InteractionState::default(),
+            variant: InputVariant::default(),
+            disabled: false,
+            is_active: false,
+        }
+    }
+
+    pub fn value(mut self, value: String) -> Self {
+        self.value = value;
+        self
+    }
+
+    pub fn state(mut self, state: InteractionState) -> Self {
+        self.state = state;
+        self
+    }
+
+    pub fn variant(mut self, variant: InputVariant) -> Self {
+        self.variant = variant;
+        self
+    }
+
+    pub fn disabled(mut self, disabled: bool) -> Self {
+        self.disabled = disabled;
+        self
+    }
+
+    pub fn is_active(mut self, is_active: bool) -> Self {
+        self.is_active = is_active;
+        self
+    }
+}
+
 #[cfg(feature = "stories")]
 pub use stories::*;
 
@@ -114,7 +118,7 @@ mod stories {
 
     pub struct InputStory;
 
-    impl Render for InputStory {
+    impl Render<Self> for InputStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -1,9 +1,8 @@
-use gpui::Action;
-use strum::EnumIter;
-
 use crate::prelude::*;
+use gpui::{Action, Div, RenderOnce};
+use strum::EnumIter;
 
-#[derive(Component, Clone)]
+#[derive(RenderOnce, Clone)]
 pub struct KeyBinding {
     /// A keybinding consists of a key and a set of modifier keys.
     /// More then one keybinding produces a chord.
@@ -12,19 +11,10 @@ pub struct KeyBinding {
     key_binding: gpui::KeyBinding,
 }
 
-impl KeyBinding {
-    pub fn for_action(action: &dyn Action, cx: &mut WindowContext) -> Option<Self> {
-        // todo! this last is arbitrary, we want to prefer users key bindings over defaults,
-        // and vim over normal (in vim mode), etc.
-        let key_binding = cx.bindings_for_action(action).last().cloned()?;
-        Some(Self::new(key_binding))
-    }
+impl<V: 'static> Component<V> for KeyBinding {
+    type Rendered = Div<V>;
 
-    pub fn new(key_binding: gpui::KeyBinding) -> Self {
-        Self { key_binding }
-    }
-
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         div()
             .flex()
             .gap_2()
@@ -42,17 +32,29 @@ impl KeyBinding {
     }
 }
 
-#[derive(Component)]
+impl KeyBinding {
+    pub fn for_action(action: &dyn Action, cx: &mut WindowContext) -> Option<Self> {
+        // todo! this last is arbitrary, we want to prefer users key bindings over defaults,
+        // and vim over normal (in vim mode), etc.
+        let key_binding = cx.bindings_for_action(action).last().cloned()?;
+        Some(Self::new(key_binding))
+    }
+
+    pub fn new(key_binding: gpui::KeyBinding) -> Self {
+        Self { key_binding }
+    }
+}
+
+#[derive(RenderOnce)]
 pub struct Key {
     key: SharedString,
 }
 
-impl Key {
-    pub fn new(key: impl Into<SharedString>) -> Self {
-        Self { key: key.into() }
-    }
+impl<V: 'static> Component<V> for Key {
+    type Rendered = Div<V>;
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+        let _view: &mut V = view;
         div()
             .px_2()
             .py_0()
@@ -64,6 +66,12 @@ impl Key {
     }
 }
 
+impl Key {
+    pub fn new(key: impl Into<SharedString>) -> Self {
+        Self { key: key.into() }
+    }
+}
+
 // NOTE: The order the modifier keys appear in this enum impacts the order in
 // which they are rendered in the UI.
 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
@@ -92,7 +100,7 @@ mod stories {
         gpui::KeyBinding::new(key, NoAction {}, None)
     }
 
-    impl Render for KeybindingStory {
+    impl Render<Self> for KeybindingStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -1,7 +1,6 @@
-use gpui::{relative, Hsla, Text, TextRun, WindowContext};
-
 use crate::prelude::*;
 use crate::styled_ext::StyledExt;
+use gpui::{relative, Div, Hsla, RenderOnce, StyledText, TextRun, WindowContext};
 
 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
 pub enum LabelSize {
@@ -60,7 +59,7 @@ pub enum LineHeightStyle {
     UILabel,
 }
 
-#[derive(Clone, Component)]
+#[derive(Clone, RenderOnce)]
 pub struct Label {
     label: SharedString,
     size: LabelSize,
@@ -69,38 +68,10 @@ pub struct Label {
     strikethrough: bool,
 }
 
-impl Label {
-    pub fn new(label: impl Into<SharedString>) -> Self {
-        Self {
-            label: label.into(),
-            size: LabelSize::Default,
-            line_height_style: LineHeightStyle::default(),
-            color: TextColor::Default,
-            strikethrough: false,
-        }
-    }
-
-    pub fn size(mut self, size: LabelSize) -> Self {
-        self.size = size;
-        self
-    }
-
-    pub fn color(mut self, color: TextColor) -> Self {
-        self.color = color;
-        self
-    }
-
-    pub fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {
-        self.line_height_style = line_height_style;
-        self
-    }
-
-    pub fn set_strikethrough(mut self, strikethrough: bool) -> Self {
-        self.strikethrough = strikethrough;
-        self
-    }
+impl<V: 'static> Component<V> for Label {
+    type Rendered = Div<V>;
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         div()
             .when(self.strikethrough, |this| {
                 this.relative().child(
@@ -124,24 +95,13 @@ impl Label {
     }
 }
 
-#[derive(Component)]
-pub struct HighlightedLabel {
-    label: SharedString,
-    size: LabelSize,
-    color: TextColor,
-    highlight_indices: Vec<usize>,
-    strikethrough: bool,
-}
-
-impl HighlightedLabel {
-    /// shows a label with the given characters highlighted.
-    /// characters are identified by utf8 byte position.
-    pub fn new(label: impl Into<SharedString>, highlight_indices: Vec<usize>) -> Self {
+impl Label {
+    pub fn new(label: impl Into<SharedString>) -> Self {
         Self {
             label: label.into(),
             size: LabelSize::Default,
+            line_height_style: LineHeightStyle::default(),
             color: TextColor::Default,
-            highlight_indices,
             strikethrough: false,
         }
     }
@@ -156,12 +116,30 @@ impl HighlightedLabel {
         self
     }
 
+    pub fn line_height_style(mut self, line_height_style: LineHeightStyle) -> Self {
+        self.line_height_style = line_height_style;
+        self
+    }
+
     pub fn set_strikethrough(mut self, strikethrough: bool) -> Self {
         self.strikethrough = strikethrough;
         self
     }
+}
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+#[derive(RenderOnce)]
+pub struct HighlightedLabel {
+    label: SharedString,
+    size: LabelSize,
+    color: TextColor,
+    highlight_indices: Vec<usize>,
+    strikethrough: bool,
+}
+
+impl<V: 'static> Component<V> for HighlightedLabel {
+    type Rendered = Div<V>;
+
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         let highlight_color = cx.theme().colors().text_accent;
         let mut text_style = cx.text_style().clone();
 
@@ -214,7 +192,36 @@ impl HighlightedLabel {
                 LabelSize::Default => this.text_ui(),
                 LabelSize::Small => this.text_ui_sm(),
             })
-            .child(Text::styled(self.label, runs))
+            .child(StyledText::new(self.label, runs))
+    }
+}
+
+impl HighlightedLabel {
+    /// shows a label with the given characters highlighted.
+    /// characters are identified by utf8 byte position.
+    pub fn new(label: impl Into<SharedString>, highlight_indices: Vec<usize>) -> Self {
+        Self {
+            label: label.into(),
+            size: LabelSize::Default,
+            color: TextColor::Default,
+            highlight_indices,
+            strikethrough: false,
+        }
+    }
+
+    pub fn size(mut self, size: LabelSize) -> Self {
+        self.size = size;
+        self
+    }
+
+    pub fn color(mut self, color: TextColor) -> Self {
+        self.color = color;
+        self
+    }
+
+    pub fn set_strikethrough(mut self, strikethrough: bool) -> Self {
+        self.strikethrough = strikethrough;
+        self
     }
 }
 
@@ -235,7 +242,7 @@ mod stories {
 
     pub struct LabelStory;
 
-    impl Render for LabelStory {
+    impl Render<Self> for LabelStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -1,4 +1,4 @@
-use gpui::{div, Action};
+use gpui::{div, Action, Div, RenderOnce};
 
 use crate::settings::user_settings;
 use crate::{
@@ -22,7 +22,7 @@ pub enum ListHeaderMeta {
     Text(Label),
 }
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct ListHeader {
     label: SharedString,
     left_icon: Option<Icon>,
@@ -31,33 +31,10 @@ pub struct ListHeader {
     toggle: Toggle,
 }
 
-impl ListHeader {
-    pub fn new(label: impl Into<SharedString>) -> Self {
-        Self {
-            label: label.into(),
-            left_icon: None,
-            meta: None,
-            variant: ListItemVariant::default(),
-            toggle: Toggle::NotToggleable,
-        }
-    }
-
-    pub fn toggle(mut self, toggle: Toggle) -> Self {
-        self.toggle = toggle;
-        self
-    }
-
-    pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
-        self.left_icon = left_icon;
-        self
-    }
-
-    pub fn meta(mut self, meta: Option<ListHeaderMeta>) -> Self {
-        self.meta = meta;
-        self
-    }
+impl<V: 'static> Component<V> for ListHeader {
+    type Rendered = Div<V>;
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         let disclosure_control = disclosure_control(self.toggle);
 
         let meta = match self.meta {
@@ -79,11 +56,6 @@ impl ListHeader {
         h_stack()
             .w_full()
             .bg(cx.theme().colors().surface_background)
-            // TODO: Add focus state
-            // .when(self.state == InteractionState::Focused, |this| {
-            //     this.border()
-            //         .border_color(cx.theme().colors().border_focused)
-            // })
             .relative()
             .child(
                 div()
@@ -117,7 +89,94 @@ impl ListHeader {
     }
 }
 
-#[derive(Component, Clone)]
+impl ListHeader {
+    pub fn new(label: impl Into<SharedString>) -> Self {
+        Self {
+            label: label.into(),
+            left_icon: None,
+            meta: None,
+            variant: ListItemVariant::default(),
+            toggle: Toggle::NotToggleable,
+        }
+    }
+
+    pub fn toggle(mut self, toggle: Toggle) -> Self {
+        self.toggle = toggle;
+        self
+    }
+
+    pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
+        self.left_icon = left_icon;
+        self
+    }
+
+    pub fn meta(mut self, meta: Option<ListHeaderMeta>) -> Self {
+        self.meta = meta;
+        self
+    }
+
+    // before_ship!("delete")
+    // 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 {
+    //         Some(ListHeaderMeta::Tools(icons)) => div().child(
+    //             h_stack()
+    //                 .gap_2()
+    //                 .items_center()
+    //                 .children(icons.into_iter().map(|i| {
+    //                     IconElement::new(i)
+    //                         .color(TextColor::Muted)
+    //                         .size(IconSize::Small)
+    //                 })),
+    //         ),
+    //         Some(ListHeaderMeta::Button(label)) => div().child(label),
+    //         Some(ListHeaderMeta::Text(label)) => div().child(label),
+    //         None => div(),
+    //     };
+
+    //     h_stack()
+    //         .w_full()
+    //         .bg(cx.theme().colors().surface_background)
+    //         // TODO: Add focus state
+    //         // .when(self.state == InteractionState::Focused, |this| {
+    //         //     this.border()
+    //         //         .border_color(cx.theme().colors().border_focused)
+    //         // })
+    //         .relative()
+    //         .child(
+    //             div()
+    //                 .h_5()
+    //                 .when(self.variant == ListItemVariant::Inset, |this| this.px_2())
+    //                 .flex()
+    //                 .flex_1()
+    //                 .items_center()
+    //                 .justify_between()
+    //                 .w_full()
+    //                 .gap_1()
+    //                 .child(
+    //                     h_stack()
+    //                         .gap_1()
+    //                         .child(
+    //                             div()
+    //                                 .flex()
+    //                                 .gap_1()
+    //                                 .items_center()
+    //                                 .children(self.left_icon.map(|i| {
+    //                                     IconElement::new(i)
+    //                                         .color(TextColor::Muted)
+    //                                         .size(IconSize::Small)
+    //                                 }))
+    //                                 .child(Label::new(self.label.clone()).color(TextColor::Muted)),
+    //                         )
+    //                         .child(disclosure_control),
+    //                 )
+    //                 .child(meta),
+    //         )
+    // }
+}
+
+#[derive(Clone)]
 pub struct ListSubHeader {
     label: SharedString,
     left_icon: Option<Icon>,
@@ -172,7 +231,7 @@ pub enum ListEntrySize {
     Medium,
 }
 
-#[derive(Component, Clone)]
+#[derive(RenderOnce, Clone)]
 pub enum ListItem {
     Entry(ListEntry),
     Separator(ListSeparator),
@@ -197,15 +256,19 @@ impl From<ListSubHeader> for ListItem {
     }
 }
 
-impl ListItem {
-    fn render<V: 'static>(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+impl<V: 'static> Component<V> for ListItem {
+    type Rendered = Div<V>;
+
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         match self {
             ListItem::Entry(entry) => div().child(entry.render(view, cx)),
             ListItem::Separator(separator) => div().child(separator.render(view, cx)),
             ListItem::Header(header) => div().child(header.render(view, cx)),
         }
     }
+}
 
+impl ListItem {
     pub fn new(label: Label) -> Self {
         Self::Entry(ListEntry::new(label))
     }
@@ -219,7 +282,7 @@ impl ListItem {
     }
 }
 
-#[derive(Component)]
+// #[derive(RenderOnce)]
 pub struct ListEntry {
     disabled: bool,
     // TODO: Reintroduce this
@@ -377,20 +440,24 @@ impl ListEntry {
     }
 }
 
-#[derive(Clone, Component)]
+#[derive(RenderOnce, Clone)]
 pub struct ListSeparator;
 
 impl ListSeparator {
     pub fn new() -> Self {
         Self
     }
+}
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+impl<V: 'static> Component<V> for ListSeparator {
+    type Rendered = Div<V>;
+
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         div().h_px().w_full().bg(cx.theme().colors().border_variant)
     }
 }
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct List {
     items: Vec<ListItem>,
     /// Message to display when the list is empty
@@ -400,6 +467,26 @@ pub struct List {
     toggle: Toggle,
 }
 
+impl<V: 'static> Component<V> for List {
+    type Rendered = Div<V>;
+
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+        let list_content = match (self.items.is_empty(), self.toggle) {
+            (false, _) => div().children(self.items),
+            (true, Toggle::Toggled(false)) => div(),
+            (true, _) => {
+                div().child(Label::new(self.empty_message.clone()).color(TextColor::Muted))
+            }
+        };
+
+        v_stack()
+            .w_full()
+            .py_1()
+            .children(self.header.map(|header| header))
+            .child(list_content)
+    }
+}
+
 impl List {
     pub fn new(items: Vec<ListItem>) -> Self {
         Self {

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

@@ -1,9 +1,9 @@
-use gpui::AnyElement;
+use gpui::{AnyElement, Div, RenderOnce, Stateful};
 use smallvec::SmallVec;
 
 use crate::{h_stack, prelude::*, v_stack, Button, Icon, IconButton, Label};
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct Modal<V: 'static> {
     id: ElementId,
     title: Option<SharedString>,
@@ -12,33 +12,11 @@ pub struct Modal<V: 'static> {
     children: SmallVec<[AnyElement<V>; 2]>,
 }
 
-impl<V: 'static> Modal<V> {
-    pub fn new(id: impl Into<ElementId>) -> Self {
-        Self {
-            id: id.into(),
-            title: None,
-            primary_action: None,
-            secondary_action: None,
-            children: SmallVec::new(),
-        }
-    }
-
-    pub fn title(mut self, title: impl Into<SharedString>) -> Self {
-        self.title = Some(title.into());
-        self
-    }
-
-    pub fn primary_action(mut self, action: Button<V>) -> Self {
-        self.primary_action = Some(action);
-        self
-    }
+impl<V: 'static> Component<V> for Modal<V> {
+    type Rendered = Stateful<V, Div<V>>;
 
-    pub fn secondary_action(mut self, action: Button<V>) -> Self {
-        self.secondary_action = Some(action);
-        self
-    }
-
-    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+        let _view: &mut V = view;
         v_stack()
             .id(self.id.clone())
             .w_96()
@@ -74,7 +52,34 @@ impl<V: 'static> Modal<V> {
     }
 }
 
-impl<V: 'static> ParentComponent<V> for Modal<V> {
+impl<V: 'static> Modal<V> {
+    pub fn new(id: impl Into<ElementId>) -> Self {
+        Self {
+            id: id.into(),
+            title: None,
+            primary_action: None,
+            secondary_action: None,
+            children: SmallVec::new(),
+        }
+    }
+
+    pub fn title(mut self, title: impl Into<SharedString>) -> Self {
+        self.title = Some(title.into());
+        self
+    }
+
+    pub fn primary_action(mut self, action: Button<V>) -> Self {
+        self.primary_action = Some(action);
+        self
+    }
+
+    pub fn secondary_action(mut self, action: Button<V>) -> Self {
+        self.secondary_action = Some(action);
+        self
+    }
+}
+
+impl<V: 'static> ParentElement<V> for Modal<V> {
     fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
         &mut self.children
     }

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

@@ -3,7 +3,7 @@ use gpui::rems;
 use crate::prelude::*;
 use crate::{h_stack, Icon};
 
-#[derive(Component)]
+// #[derive(RenderOnce)]
 pub struct NotificationToast {
     label: SharedString,
     icon: Option<Icon>,

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

@@ -1,7 +1,9 @@
 use crate::{h_stack, prelude::*, v_stack, KeyBinding, Label};
 use gpui::prelude::*;
+use gpui::Div;
+use gpui::Stateful;
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct Palette {
     id: ElementId,
     input_placeholder: SharedString,
@@ -10,41 +12,12 @@ pub struct Palette {
     default_order: OrderMethod,
 }
 
-impl Palette {
-    pub fn new(id: impl Into<ElementId>) -> Self {
-        Self {
-            id: id.into(),
-            input_placeholder: "Find something...".into(),
-            empty_string: "No items found.".into(),
-            items: vec![],
-            default_order: OrderMethod::default(),
-        }
-    }
-
-    pub fn items(mut self, items: Vec<PaletteItem>) -> Self {
-        self.items = items;
-        self
-    }
-
-    pub fn placeholder(mut self, input_placeholder: impl Into<SharedString>) -> Self {
-        self.input_placeholder = input_placeholder.into();
-        self
-    }
-
-    pub fn empty_string(mut self, empty_string: impl Into<SharedString>) -> Self {
-        self.empty_string = empty_string.into();
-        self
-    }
+impl<V: 'static> Component<V> for Palette {
+    type Rendered = Stateful<V, Div<V>>;
 
-    // TODO: Hook up sort order
-    pub fn default_order(mut self, default_order: OrderMethod) -> Self {
-        self.default_order = default_order;
-        self
-    }
-
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         v_stack()
-            .id(self.id.clone())
+            .id(self.id)
             .w_96()
             .rounded_lg()
             .bg(cx.theme().colors().elevated_surface_background)
@@ -53,9 +26,11 @@ impl Palette {
             .child(
                 v_stack()
                     .gap_px()
-                    .child(v_stack().py_0p5().px_1().child(div().px_2().py_0p5().child(
-                        Label::new(self.input_placeholder.clone()).color(TextColor::Placeholder),
-                    )))
+                    .child(v_stack().py_0p5().px_1().child(
+                        div().px_2().py_0p5().child(
+                            Label::new(self.input_placeholder).color(TextColor::Placeholder),
+                        ),
+                    ))
                     .child(
                         div()
                             .h_px()
@@ -72,12 +47,9 @@ impl Palette {
                             .overflow_y_scroll()
                             .children(
                                 vec![if self.items.is_empty() {
-                                    Some(
-                                        h_stack().justify_between().px_2().py_1().child(
-                                            Label::new(self.empty_string.clone())
-                                                .color(TextColor::Muted),
-                                        ),
-                                    )
+                                    Some(h_stack().justify_between().px_2().py_1().child(
+                                        Label::new(self.empty_string).color(TextColor::Muted),
+                                    ))
                                 } else {
                                     None
                                 }]
@@ -104,13 +76,64 @@ impl Palette {
     }
 }
 
-#[derive(Component)]
+impl Palette {
+    pub fn new(id: impl Into<ElementId>) -> Self {
+        Self {
+            id: id.into(),
+            input_placeholder: "Find something...".into(),
+            empty_string: "No items found.".into(),
+            items: vec![],
+            default_order: OrderMethod::default(),
+        }
+    }
+
+    pub fn items(mut self, items: Vec<PaletteItem>) -> Self {
+        self.items = items;
+        self
+    }
+
+    pub fn placeholder(mut self, input_placeholder: impl Into<SharedString>) -> Self {
+        self.input_placeholder = input_placeholder.into();
+        self
+    }
+
+    pub fn empty_string(mut self, empty_string: impl Into<SharedString>) -> Self {
+        self.empty_string = empty_string.into();
+        self
+    }
+
+    // TODO: Hook up sort order
+    pub fn default_order(mut self, default_order: OrderMethod) -> Self {
+        self.default_order = default_order;
+        self
+    }
+}
+
+#[derive(RenderOnce)]
 pub struct PaletteItem {
     pub label: SharedString,
     pub sublabel: Option<SharedString>,
     pub key_binding: Option<KeyBinding>,
 }
 
+impl<V: 'static> Component<V> for PaletteItem {
+    type Rendered = Div<V>;
+
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+        div()
+            .flex()
+            .flex_row()
+            .grow()
+            .justify_between()
+            .child(
+                v_stack()
+                    .child(Label::new(self.label))
+                    .children(self.sublabel.map(|sublabel| Label::new(sublabel))),
+            )
+            .children(self.key_binding)
+    }
+}
+
 impl PaletteItem {
     pub fn new(label: impl Into<SharedString>) -> Self {
         Self {
@@ -134,20 +157,6 @@ impl PaletteItem {
         self.key_binding = key_binding.into();
         self
     }
-
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
-        div()
-            .flex()
-            .flex_row()
-            .grow()
-            .justify_between()
-            .child(
-                v_stack()
-                    .child(Label::new(self.label.clone()))
-                    .children(self.sublabel.clone().map(|sublabel| Label::new(sublabel))),
-            )
-            .children(self.key_binding)
-    }
 }
 
 use gpui::ElementId;
@@ -164,7 +173,7 @@ mod stories {
 
     pub struct PaletteStory;
 
-    impl Render for PaletteStory {
+    impl Render<Self> for PaletteStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -1,4 +1,4 @@
-use gpui::{prelude::*, AbsoluteLength, AnyElement};
+use gpui::{prelude::*, AbsoluteLength, AnyElement, Div, RenderOnce, Stateful};
 use smallvec::SmallVec;
 
 use crate::prelude::*;
@@ -38,7 +38,7 @@ pub enum PanelSide {
 
 use std::collections::HashSet;
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct Panel<V: 'static> {
     id: ElementId,
     current_side: PanelSide,
@@ -49,6 +49,30 @@ pub struct Panel<V: 'static> {
     children: SmallVec<[AnyElement<V>; 2]>,
 }
 
+impl<V: 'static> Component<V> for Panel<V> {
+    type Rendered = Stateful<V, Div<V>>;
+
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+        let current_size = self.width.unwrap_or(self.initial_width);
+
+        v_stack()
+            .id(self.id.clone())
+            .flex_initial()
+            .map(|this| match self.current_side {
+                PanelSide::Left | PanelSide::Right => this.h_full().w(current_size),
+                PanelSide::Bottom => this,
+            })
+            .map(|this| match self.current_side {
+                PanelSide::Left => this.border_r(),
+                PanelSide::Right => this.border_l(),
+                PanelSide::Bottom => this.border_b().w_full().h(current_size),
+            })
+            .bg(cx.theme().colors().surface_background)
+            .border_color(cx.theme().colors().border)
+            .children(self.children)
+    }
+}
+
 impl<V: 'static> Panel<V> {
     pub fn new(id: impl Into<ElementId>, cx: &mut WindowContext) -> Self {
         let settings = user_settings(cx);
@@ -91,29 +115,9 @@ impl<V: 'static> Panel<V> {
         }
         self
     }
-
-    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()
-            .id(self.id.clone())
-            .flex_initial()
-            .map(|this| match self.current_side {
-                PanelSide::Left | PanelSide::Right => this.h_full().w(current_size),
-                PanelSide::Bottom => this,
-            })
-            .map(|this| match self.current_side {
-                PanelSide::Left => this.border_r(),
-                PanelSide::Right => this.border_l(),
-                PanelSide::Bottom => this.border_b().w_full().h(current_size),
-            })
-            .bg(cx.theme().colors().surface_background)
-            .border_color(cx.theme().colors().border)
-            .children(self.children)
-    }
 }
 
-impl<V: 'static> ParentComponent<V> for Panel<V> {
+impl<V: 'static> ParentElement<V> for Panel<V> {
     fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
         &mut self.children
     }
@@ -126,11 +130,11 @@ pub use stories::*;
 mod stories {
     use super::*;
     use crate::{Label, Story};
-    use gpui::{Div, InteractiveComponent, Render};
+    use gpui::{Div, InteractiveElement, Render};
 
     pub struct PanelStory;
 
-    impl Render for PanelStory {
+    impl Render<Self> for PanelStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -1,19 +1,17 @@
+use gpui::{Div, RenderOnce};
+
 use crate::prelude::*;
 use crate::{Avatar, Facepile, PlayerWithCallStatus};
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct PlayerStack {
     player_with_call_status: PlayerWithCallStatus,
 }
 
-impl PlayerStack {
-    pub fn new(player_with_call_status: PlayerWithCallStatus) -> Self {
-        Self {
-            player_with_call_status,
-        }
-    }
+impl<V: 'static> Component<V> for PlayerStack {
+    type Rendered = Div<V>;
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         let player = self.player_with_call_status.get_player();
 
         let followers = self
@@ -59,3 +57,11 @@ impl PlayerStack {
             )
     }
 }
+
+impl PlayerStack {
+    pub fn new(player_with_call_status: PlayerWithCallStatus) -> Self {
+        Self {
+            player_with_call_status,
+        }
+    }
+}

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

@@ -1,8 +1,8 @@
 use crate::prelude::*;
 use crate::{Icon, IconElement, Label, TextColor};
-use gpui::{prelude::*, red, Div, ElementId, Render, View};
+use gpui::{prelude::*, red, Div, ElementId, Render, RenderOnce, Stateful, View};
 
-#[derive(Component, Clone)]
+#[derive(RenderOnce, Clone)]
 pub struct Tab {
     id: ElementId,
     title: String,
@@ -20,7 +20,7 @@ struct TabDragState {
     title: String,
 }
 
-impl Render for TabDragState {
+impl Render<Self> for TabDragState {
     type Element = Div<Self>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@@ -28,65 +28,10 @@ impl Render for TabDragState {
     }
 }
 
-impl Tab {
-    pub fn new(id: impl Into<ElementId>) -> Self {
-        Self {
-            id: id.into(),
-            title: "untitled".to_string(),
-            icon: None,
-            current: false,
-            dirty: false,
-            fs_status: FileSystemStatus::None,
-            git_status: GitStatus::None,
-            diagnostic_status: DiagnosticStatus::None,
-            close_side: IconSide::Right,
-        }
-    }
-
-    pub fn current(mut self, current: bool) -> Self {
-        self.current = current;
-        self
-    }
-
-    pub fn title(mut self, title: String) -> Self {
-        self.title = title;
-        self
-    }
-
-    pub fn icon<I>(mut self, icon: I) -> Self
-    where
-        I: Into<Option<Icon>>,
-    {
-        self.icon = icon.into();
-        self
-    }
-
-    pub fn dirty(mut self, dirty: bool) -> Self {
-        self.dirty = dirty;
-        self
-    }
+impl<V: 'static> Component<V> for Tab {
+    type Rendered = Stateful<V, Div<V>>;
 
-    pub fn fs_status(mut self, fs_status: FileSystemStatus) -> Self {
-        self.fs_status = fs_status;
-        self
-    }
-
-    pub fn git_status(mut self, git_status: GitStatus) -> Self {
-        self.git_status = git_status;
-        self
-    }
-
-    pub fn diagnostic_status(mut self, diagnostic_status: DiagnosticStatus) -> Self {
-        self.diagnostic_status = diagnostic_status;
-        self
-    }
-
-    pub fn close_side(mut self, close_side: IconSide) -> Self {
-        self.close_side = close_side;
-        self
-    }
-
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         let has_fs_conflict = self.fs_status == FileSystemStatus::Conflict;
         let is_deleted = self.fs_status == FileSystemStatus::Deleted;
 
@@ -164,6 +109,65 @@ impl Tab {
     }
 }
 
+impl Tab {
+    pub fn new(id: impl Into<ElementId>) -> Self {
+        Self {
+            id: id.into(),
+            title: "untitled".to_string(),
+            icon: None,
+            current: false,
+            dirty: false,
+            fs_status: FileSystemStatus::None,
+            git_status: GitStatus::None,
+            diagnostic_status: DiagnosticStatus::None,
+            close_side: IconSide::Right,
+        }
+    }
+
+    pub fn current(mut self, current: bool) -> Self {
+        self.current = current;
+        self
+    }
+
+    pub fn title(mut self, title: String) -> Self {
+        self.title = title;
+        self
+    }
+
+    pub fn icon<I>(mut self, icon: I) -> Self
+    where
+        I: Into<Option<Icon>>,
+    {
+        self.icon = icon.into();
+        self
+    }
+
+    pub fn dirty(mut self, dirty: bool) -> Self {
+        self.dirty = dirty;
+        self
+    }
+
+    pub fn fs_status(mut self, fs_status: FileSystemStatus) -> Self {
+        self.fs_status = fs_status;
+        self
+    }
+
+    pub fn git_status(mut self, git_status: GitStatus) -> Self {
+        self.git_status = git_status;
+        self
+    }
+
+    pub fn diagnostic_status(mut self, diagnostic_status: DiagnosticStatus) -> Self {
+        self.diagnostic_status = diagnostic_status;
+        self
+    }
+
+    pub fn close_side(mut self, close_side: IconSide) -> Self {
+        self.close_side = close_side;
+        self
+    }
+}
+
 #[cfg(feature = "stories")]
 pub use stories::*;
 
@@ -175,7 +179,7 @@ mod stories {
 
     pub struct TabStory;
 
-    impl Render for TabStory {
+    impl Render<Self> for TabStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -1,6 +1,6 @@
 use crate::prelude::*;
-use gpui::Element;
-use gpui::{prelude::*, AnyElement};
+use gpui::{prelude::*, AnyElement, RenderOnce};
+use gpui::{Div, Element};
 use smallvec::SmallVec;
 
 #[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
@@ -22,41 +22,37 @@ pub enum ToastOrigin {
 /// they are actively showing the a process in progress.
 ///
 /// Only one toast may be visible at a time.
-#[derive(Element)]
+#[derive(RenderOnce)]
 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> Component<V> for Toast<V> {
+    type Rendered = Div<V>;
+
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+        let mut div = div();
+
+        if self.origin == ToastOrigin::Bottom {
+            div = div.right_1_2();
+        } else {
+            div = div.right_2();
+        }
+
+        div.z_index(5)
+            .absolute()
+            .bottom_9()
+            .flex()
+            .py_1()
+            .px_1p5()
+            .rounded_lg()
+            .shadow_md()
+            .overflow_hidden()
+            .bg(cx.theme().colors().elevated_surface_background)
+            .children(self.children)
+    }
+}
 
 impl<V: 'static> Toast<V> {
     pub fn new(origin: ToastOrigin) -> Self {
@@ -89,7 +85,7 @@ impl<V: 'static> Toast<V> {
     }
 }
 
-impl<V: 'static> ParentComponent<V> for Toast<V> {
+impl<V: 'static> ParentElement<V> for Toast<V> {
     fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
         &mut self.children
     }
@@ -108,7 +104,7 @@ mod stories {
 
     pub struct ToastStory;
 
-    impl Render for ToastStory {
+    impl Render<Self> for ToastStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -1,4 +1,4 @@
-use gpui::{div, Component, Element, ParentComponent};
+use gpui::{div, Element, ParentElement};
 
 use crate::{Icon, IconElement, IconSize, TextColor};
 

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

@@ -1,8 +1,17 @@
 use crate::prelude::*;
+use gpui::{Div, RenderOnce};
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct ToolDivider;
 
+impl<V: 'static> Component<V> for ToolDivider {
+    type Rendered = Div<V>;
+
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+        div().w_px().h_3().bg(cx.theme().colors().border)
+    }
+}
+
 impl ToolDivider {
     pub fn new() -> Self {
         Self

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

@@ -1,4 +1,4 @@
-use gpui::{overlay, Action, AnyView, Overlay, Render, VisualContext};
+use gpui::{overlay, Action, AnyView, Overlay, Render, RenderOnce, VisualContext};
 use settings2::Settings;
 use theme2::{ActiveTheme, ThemeSettings};
 
@@ -67,7 +67,7 @@ impl Tooltip {
     }
 }
 
-impl Render for Tooltip {
+impl Render<Self> for Tooltip {
     type Element = Overlay<Self>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

crates/ui2/src/prelude.rs 🔗

@@ -1,8 +1,8 @@
 use gpui::rems;
 use gpui::Rems;
 pub use gpui::{
-    div, Component, Element, ElementId, InteractiveComponent, ParentComponent, SharedString,
-    Styled, ViewContext, WindowContext,
+    div, Component, Element, ElementId, InteractiveElement, ParentElement, SharedString, Styled,
+    ViewContext, WindowContext,
 };
 
 pub use crate::elevation::*;

crates/ui2/src/static_data.rs 🔗

@@ -745,11 +745,11 @@ pub fn hello_world_rust_editor_example(cx: &mut ViewContext<EditorPane>) -> Edit
         PathBuf::from_str("crates/ui/src/static_data.rs").unwrap(),
         vec![Symbol(vec![
             HighlightedText {
-                text: "fn ".to_string(),
+                text: "fn ".into(),
                 color: cx.theme().syntax_color("keyword"),
             },
             HighlightedText {
-                text: "main".to_string(),
+                text: "main".into(),
                 color: cx.theme().syntax_color("function"),
             },
         ])],
@@ -779,15 +779,15 @@ pub fn hello_world_rust_buffer_rows(cx: &AppContext) -> Vec<BufferRow> {
             line: Some(HighlightedLine {
                 highlighted_texts: vec![
                     HighlightedText {
-                        text: "fn ".to_string(),
+                        text: "fn ".into(),
                         color: cx.theme().syntax_color("keyword"),
                     },
                     HighlightedText {
-                        text: "main".to_string(),
+                        text: "main".into(),
                         color: cx.theme().syntax_color("function"),
                     },
                     HighlightedText {
-                        text: "() {".to_string(),
+                        text: "() {".into(),
                         color: cx.theme().colors().text,
                     },
                 ],
@@ -803,7 +803,7 @@ pub fn hello_world_rust_buffer_rows(cx: &AppContext) -> Vec<BufferRow> {
             line: Some(HighlightedLine {
                 highlighted_texts: vec![HighlightedText {
                     text: "    // Statements here are executed when the compiled binary is called."
-                        .to_string(),
+                        .into(),
                     color: cx.theme().syntax_color("comment"),
                 }],
             }),
@@ -826,7 +826,7 @@ pub fn hello_world_rust_buffer_rows(cx: &AppContext) -> Vec<BufferRow> {
             current: false,
             line: Some(HighlightedLine {
                 highlighted_texts: vec![HighlightedText {
-                    text: "    // Print text to the console.".to_string(),
+                    text: "    // Print text to the console.".into(),
                     color: cx.theme().syntax_color("comment"),
                 }],
             }),
@@ -841,15 +841,15 @@ pub fn hello_world_rust_buffer_rows(cx: &AppContext) -> Vec<BufferRow> {
             line: Some(HighlightedLine {
                 highlighted_texts: vec![
                     HighlightedText {
-                        text: "    println!(".to_string(),
+                        text: "    println!(".into(),
                         color: cx.theme().colors().text,
                     },
                     HighlightedText {
-                        text: "\"Hello, world!\"".to_string(),
+                        text: "\"Hello, world!\"".into(),
                         color: cx.theme().syntax_color("string"),
                     },
                     HighlightedText {
-                        text: ");".to_string(),
+                        text: ");".into(),
                         color: cx.theme().colors().text,
                     },
                 ],
@@ -864,7 +864,7 @@ pub fn hello_world_rust_buffer_rows(cx: &AppContext) -> Vec<BufferRow> {
             current: false,
             line: Some(HighlightedLine {
                 highlighted_texts: vec![HighlightedText {
-                    text: "}".to_string(),
+                    text: "}".into(),
                     color: cx.theme().colors().text,
                 }],
             }),
@@ -882,11 +882,11 @@ pub fn hello_world_rust_editor_with_status_example(cx: &mut ViewContext<EditorPa
         PathBuf::from_str("crates/ui/src/static_data.rs").unwrap(),
         vec![Symbol(vec![
             HighlightedText {
-                text: "fn ".to_string(),
+                text: "fn ".into(),
                 color: cx.theme().syntax_color("keyword"),
             },
             HighlightedText {
-                text: "main".to_string(),
+                text: "main".into(),
                 color: cx.theme().syntax_color("function"),
             },
         ])],
@@ -916,15 +916,15 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &AppContext) -> Vec<BufferRo
             line: Some(HighlightedLine {
                 highlighted_texts: vec![
                     HighlightedText {
-                        text: "fn ".to_string(),
+                        text: "fn ".into(),
                         color: cx.theme().syntax_color("keyword"),
                     },
                     HighlightedText {
-                        text: "main".to_string(),
+                        text: "main".into(),
                         color: cx.theme().syntax_color("function"),
                     },
                     HighlightedText {
-                        text: "() {".to_string(),
+                        text: "() {".into(),
                         color: cx.theme().colors().text,
                     },
                 ],
@@ -940,7 +940,7 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &AppContext) -> Vec<BufferRo
             line: Some(HighlightedLine {
                 highlighted_texts: vec![HighlightedText {
                     text: "// Statements here are executed when the compiled binary is called."
-                        .to_string(),
+                        .into(),
                     color: cx.theme().syntax_color("comment"),
                 }],
             }),
@@ -963,7 +963,7 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &AppContext) -> Vec<BufferRo
             current: false,
             line: Some(HighlightedLine {
                 highlighted_texts: vec![HighlightedText {
-                    text: "    // Print text to the console.".to_string(),
+                    text: "    // Print text to the console.".into(),
                     color: cx.theme().syntax_color("comment"),
                 }],
             }),
@@ -978,15 +978,15 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &AppContext) -> Vec<BufferRo
             line: Some(HighlightedLine {
                 highlighted_texts: vec![
                     HighlightedText {
-                        text: "    println!(".to_string(),
+                        text: "    println!(".into(),
                         color: cx.theme().colors().text,
                     },
                     HighlightedText {
-                        text: "\"Hello, world!\"".to_string(),
+                        text: "\"Hello, world!\"".into(),
                         color: cx.theme().syntax_color("string"),
                     },
                     HighlightedText {
-                        text: ");".to_string(),
+                        text: ");".into(),
                         color: cx.theme().colors().text,
                     },
                 ],
@@ -1001,7 +1001,7 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &AppContext) -> Vec<BufferRo
             current: false,
             line: Some(HighlightedLine {
                 highlighted_texts: vec![HighlightedText {
-                    text: "}".to_string(),
+                    text: "}".into(),
                     color: cx.theme().colors().text,
                 }],
             }),
@@ -1015,7 +1015,7 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &AppContext) -> Vec<BufferRo
             current: false,
             line: Some(HighlightedLine {
                 highlighted_texts: vec![HighlightedText {
-                    text: "".to_string(),
+                    text: "".into(),
                     color: cx.theme().colors().text,
                 }],
             }),
@@ -1029,7 +1029,7 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &AppContext) -> Vec<BufferRo
             current: false,
             line: Some(HighlightedLine {
                 highlighted_texts: vec![HighlightedText {
-                    text: "// Marshall and Nate were here".to_string(),
+                    text: "// Marshall and Nate were here".into(),
                     color: cx.theme().syntax_color("comment"),
                 }],
             }),
@@ -1042,7 +1042,7 @@ pub fn hello_world_rust_with_status_buffer_rows(cx: &AppContext) -> Vec<BufferRo
 
 pub fn terminal_buffer(cx: &AppContext) -> Buffer {
     Buffer::new("terminal")
-        .set_title("zed — fish".to_string())
+        .set_title(Some("zed — fish".into()))
         .set_rows(Some(BufferRows {
             show_line_numbers: false,
             rows: terminal_buffer_rows(cx),
@@ -1060,31 +1060,31 @@ pub fn terminal_buffer_rows(cx: &AppContext) -> Vec<BufferRow> {
             line: Some(HighlightedLine {
                 highlighted_texts: vec![
                     HighlightedText {
-                        text: "maxdeviant ".to_string(),
+                        text: "maxdeviant ".into(),
                         color: cx.theme().syntax_color("keyword"),
                     },
                     HighlightedText {
-                        text: "in ".to_string(),
+                        text: "in ".into(),
                         color: cx.theme().colors().text,
                     },
                     HighlightedText {
-                        text: "profaned-capital ".to_string(),
+                        text: "profaned-capital ".into(),
                         color: cx.theme().syntax_color("function"),
                     },
                     HighlightedText {
-                        text: "in ".to_string(),
+                        text: "in ".into(),
                         color: cx.theme().colors().text,
                     },
                     HighlightedText {
-                        text: "~/p/zed ".to_string(),
+                        text: "~/p/zed ".into(),
                         color: cx.theme().syntax_color("function"),
                     },
                     HighlightedText {
-                        text: "on ".to_string(),
+                        text: "on ".into(),
                         color: cx.theme().colors().text,
                     },
                     HighlightedText {
-                        text: " gpui2-ui ".to_string(),
+                        text: " gpui2-ui ".into(),
                         color: cx.theme().syntax_color("keyword"),
                     },
                 ],
@@ -1099,7 +1099,7 @@ pub fn terminal_buffer_rows(cx: &AppContext) -> Vec<BufferRow> {
             current: false,
             line: Some(HighlightedLine {
                 highlighted_texts: vec![HighlightedText {
-                    text: "λ ".to_string(),
+                    text: "λ ".into(),
                     color: cx.theme().syntax_color("string"),
                 }],
             }),

crates/ui2/src/story.rs 🔗

@@ -15,23 +15,29 @@ impl Story {
             .bg(cx.theme().colors().background)
     }
 
-    pub fn title<V: 'static>(cx: &mut ViewContext<V>, title: &str) -> impl Element<V> {
+    pub fn title<V: 'static>(
+        cx: &mut ViewContext<V>,
+        title: impl Into<SharedString>,
+    ) -> impl Element<V> {
         div()
             .text_xl()
             .text_color(cx.theme().colors().text)
-            .child(title.to_owned())
+            .child(title.into())
     }
 
     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 Element<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/src/to_extract/assistant_panel.rs 🔗

@@ -1,27 +1,17 @@
 use crate::prelude::*;
 use crate::{Icon, IconButton, Label, Panel, PanelSide};
-use gpui::{prelude::*, rems, AbsoluteLength};
+use gpui::{prelude::*, rems, AbsoluteLength, RenderOnce};
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct AssistantPanel {
     id: ElementId,
     current_side: PanelSide,
 }
 
-impl AssistantPanel {
-    pub fn new(id: impl Into<ElementId>) -> Self {
-        Self {
-            id: id.into(),
-            current_side: PanelSide::default(),
-        }
-    }
+impl<V: 'static> Component<V> for AssistantPanel {
+    type Rendered = Panel<V>;
 
-    pub fn side(mut self, side: PanelSide) -> Self {
-        self.current_side = side;
-        self
-    }
-
-    fn render<V: 'static>(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         Panel::new(self.id.clone(), cx)
             .children(vec![div()
                 .flex()
@@ -64,12 +54,26 @@ impl AssistantPanel {
                         .overflow_y_scroll()
                         .child(Label::new("Is this thing on?")),
                 )
-                .render()])
+                .into_any()])
             .side(self.current_side)
             .width(AbsoluteLength::Rems(rems(32.)))
     }
 }
 
+impl AssistantPanel {
+    pub fn new(id: impl Into<ElementId>) -> Self {
+        Self {
+            id: id.into(),
+            current_side: PanelSide::default(),
+        }
+    }
+
+    pub fn side(mut self, side: PanelSide) -> Self {
+        self.current_side = side;
+        self
+    }
+}
+
 #[cfg(feature = "stories")]
 pub use stories::*;
 
@@ -80,7 +84,7 @@ mod stories {
     use gpui::{Div, Render};
     pub struct AssistantPanelStory;
 
-    impl Render for AssistantPanelStory {
+    impl Render<Self> for AssistantPanelStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -1,30 +1,21 @@
 use crate::{h_stack, prelude::*, HighlightedText};
-use gpui::{prelude::*, Div};
+use gpui::{prelude::*, Div, Stateful};
 use std::path::PathBuf;
 
 #[derive(Clone)]
 pub struct Symbol(pub Vec<HighlightedText>);
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct Breadcrumb {
     path: PathBuf,
     symbols: Vec<Symbol>,
 }
 
-impl Breadcrumb {
-    pub fn new(path: PathBuf, symbols: Vec<Symbol>) -> Self {
-        Self { path, symbols }
-    }
-
-    fn render_separator<V: 'static>(&self, cx: &WindowContext) -> Div<V> {
-        div()
-            .child(" › ")
-            .text_color(cx.theme().colors().text_muted)
-    }
+impl<V: 'static> Component<V> for Breadcrumb {
+    type Rendered = Stateful<V, Div<V>>;
 
-    fn render<V: 'static>(self, view_state: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, view_state: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         let symbols_len = self.symbols.len();
-
         h_stack()
             .id("breadcrumb")
             .px_1()
@@ -33,7 +24,9 @@ impl Breadcrumb {
             .rounded_md()
             .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
             .active(|style| style.bg(cx.theme().colors().ghost_element_active))
-            .child(self.path.clone().to_str().unwrap().to_string())
+            .child(SharedString::from(
+                self.path.clone().to_str().unwrap().to_string(),
+            ))
             .child(if !self.symbols.is_empty() {
                 self.render_separator(cx)
             } else {
@@ -64,6 +57,18 @@ impl Breadcrumb {
     }
 }
 
+impl Breadcrumb {
+    pub fn new(path: PathBuf, symbols: Vec<Symbol>) -> Self {
+        Self { path, symbols }
+    }
+
+    fn render_separator<V: 'static>(&self, cx: &WindowContext) -> Div<V> {
+        div()
+            .child(" › ")
+            .text_color(cx.theme().colors().text_muted)
+    }
+}
+
 #[cfg(feature = "stories")]
 pub use stories::*;
 
@@ -76,7 +81,7 @@ mod stories {
 
     pub struct BreadcrumbStory;
 
-    impl Render for BreadcrumbStory {
+    impl Render<Self> for BreadcrumbStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@@ -88,21 +93,21 @@ mod stories {
                     vec![
                         Symbol(vec![
                             HighlightedText {
-                                text: "impl ".to_string(),
+                                text: "impl ".into(),
                                 color: cx.theme().syntax_color("keyword"),
                             },
                             HighlightedText {
-                                text: "BreadcrumbStory".to_string(),
+                                text: "BreadcrumbStory".into(),
                                 color: cx.theme().syntax_color("function"),
                             },
                         ]),
                         Symbol(vec![
                             HighlightedText {
-                                text: "fn ".to_string(),
+                                text: "fn ".into(),
                                 color: cx.theme().syntax_color("keyword"),
                             },
                             HighlightedText {
-                                text: "render".to_string(),
+                                text: "render".into(),
                                 color: cx.theme().syntax_color("function"),
                             },
                         ]),

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

@@ -1,4 +1,4 @@
-use gpui::{Hsla, WindowContext};
+use gpui::{Div, Hsla, RenderOnce, WindowContext};
 
 use crate::prelude::*;
 use crate::{h_stack, v_stack, Icon, IconElement};
@@ -11,7 +11,7 @@ pub struct PlayerCursor {
 
 #[derive(Default, PartialEq, Clone)]
 pub struct HighlightedText {
-    pub text: String,
+    pub text: SharedString,
     pub color: Hsla,
 }
 
@@ -107,7 +107,7 @@ impl BufferRow {
     }
 }
 
-#[derive(Component, Clone)]
+#[derive(RenderOnce, Clone)]
 pub struct Buffer {
     id: ElementId,
     rows: Option<BufferRows>,
@@ -117,6 +117,21 @@ pub struct Buffer {
     path: Option<String>,
 }
 
+impl<V: 'static> Component<V> for Buffer {
+    type Rendered = Div<V>;
+
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+        let rows = self.render_rows(cx);
+
+        v_stack()
+            .flex_1()
+            .w_full()
+            .h_full()
+            .bg(cx.theme().colors().editor_background)
+            .children(rows)
+    }
+}
+
 impl Buffer {
     pub fn new(id: impl Into<ElementId>) -> Self {
         Self {
@@ -186,7 +201,7 @@ impl Buffer {
                     h_stack().justify_end().px_0p5().w_3().child(
                         div()
                             .text_color(line_number_color)
-                            .child(row.line_number.to_string()),
+                            .child(SharedString::from(row.line_number.to_string())),
                     ),
                 )
             })
@@ -239,7 +254,7 @@ mod stories {
 
     pub struct BufferStory;
 
-    impl Render for BufferStory {
+    impl Render<Self> for BufferStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -1,7 +1,6 @@
-use gpui::{Div, Render, View, VisualContext};
-
 use crate::prelude::*;
 use crate::{h_stack, Icon, IconButton, Input, TextColor};
+use gpui::{Div, Render, RenderOnce, View, VisualContext};
 
 #[derive(Clone)]
 pub struct BufferSearch {
@@ -26,7 +25,7 @@ impl BufferSearch {
     }
 }
 
-impl Render for BufferSearch {
+impl Render<Self> for BufferSearch {
     type Element = Div<Self>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {

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

@@ -1,27 +1,17 @@
 use crate::{prelude::*, Icon, IconButton, Input, Label};
 use chrono::NaiveDateTime;
-use gpui::prelude::*;
+use gpui::{prelude::*, Div, Stateful};
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct ChatPanel {
     element_id: ElementId,
     messages: Vec<ChatMessage>,
 }
 
-impl ChatPanel {
-    pub fn new(element_id: impl Into<ElementId>) -> Self {
-        Self {
-            element_id: element_id.into(),
-            messages: Vec::new(),
-        }
-    }
-
-    pub fn messages(mut self, messages: Vec<ChatMessage>) -> Self {
-        self.messages = messages;
-        self
-    }
+impl<V: 'static> Component<V> for ChatPanel {
+    type Rendered = Stateful<V, Div<V>>;
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         div()
             .id(self.element_id.clone())
             .flex()
@@ -67,23 +57,31 @@ impl ChatPanel {
     }
 }
 
-#[derive(Component)]
+impl ChatPanel {
+    pub fn new(element_id: impl Into<ElementId>) -> Self {
+        Self {
+            element_id: element_id.into(),
+            messages: Vec::new(),
+        }
+    }
+
+    pub fn messages(mut self, messages: Vec<ChatMessage>) -> Self {
+        self.messages = messages;
+        self
+    }
+}
+
+#[derive(RenderOnce)]
 pub struct ChatMessage {
     author: String,
     text: String,
     sent_at: NaiveDateTime,
 }
 
-impl ChatMessage {
-    pub fn new(author: String, text: String, sent_at: NaiveDateTime) -> Self {
-        Self {
-            author,
-            text,
-            sent_at,
-        }
-    }
+impl<V: 'static> Component<V> for ChatMessage {
+    type Rendered = Div<V>;
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         div()
             .flex()
             .flex_col()
@@ -101,6 +99,16 @@ impl ChatMessage {
     }
 }
 
+impl ChatMessage {
+    pub fn new(author: String, text: String, sent_at: NaiveDateTime) -> Self {
+        Self {
+            author,
+            text,
+            sent_at,
+        }
+    }
+}
+
 #[cfg(feature = "stories")]
 pub use stories::*;
 
@@ -115,7 +123,7 @@ mod stories {
 
     pub struct ChatPanelStory;
 
-    impl Render for ChatPanelStory {
+    impl Render<Self> for ChatPanelStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -2,19 +2,17 @@ use crate::{
     prelude::*, static_collab_panel_channels, static_collab_panel_current_call, v_stack, Icon,
     List, ListHeader, Toggle,
 };
-use gpui::prelude::*;
+use gpui::{prelude::*, Div, Stateful};
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct CollabPanel {
     id: ElementId,
 }
 
-impl CollabPanel {
-    pub fn new(id: impl Into<ElementId>) -> Self {
-        Self { id: id.into() }
-    }
+impl<V: 'static> Component<V> for CollabPanel {
+    type Rendered = Stateful<V, Div<V>>;
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         v_stack()
             .id(self.id.clone())
             .h_full()
@@ -86,6 +84,12 @@ impl CollabPanel {
     }
 }
 
+impl CollabPanel {
+    pub fn new(id: impl Into<ElementId>) -> Self {
+        Self { id: id.into() }
+    }
+}
+
 #[cfg(feature = "stories")]
 pub use stories::*;
 
@@ -97,7 +101,7 @@ mod stories {
 
     pub struct CollabPanelStory;
 
-    impl Render for CollabPanelStory {
+    impl Render<Self> for CollabPanelStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -1,17 +1,15 @@
 use crate::prelude::*;
 use crate::{example_editor_actions, OrderMethod, Palette};
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct CommandPalette {
     id: ElementId,
 }
 
-impl CommandPalette {
-    pub fn new(id: impl Into<ElementId>) -> Self {
-        Self { id: id.into() }
-    }
+impl<V: 'static> Component<V> for CommandPalette {
+    type Rendered = Stateful<V, Div<V>>;
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         div().id(self.id.clone()).child(
             Palette::new("palette")
                 .items(example_editor_actions())
@@ -22,6 +20,13 @@ impl CommandPalette {
     }
 }
 
+impl CommandPalette {
+    pub fn new(id: impl Into<ElementId>) -> Self {
+        Self { id: id.into() }
+    }
+}
+
+use gpui::{Div, RenderOnce, Stateful};
 #[cfg(feature = "stories")]
 pub use stories::*;
 
@@ -35,7 +40,7 @@ mod stories {
 
     pub struct CommandPaletteStory;
 
-    impl Render for CommandPaletteStory {
+    impl Render<Self> for CommandPaletteStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -1,39 +1,42 @@
 use crate::{prelude::*, Button, Label, Modal, TextColor};
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct CopilotModal {
     id: ElementId,
 }
 
+impl<V: 'static> Component<V> for CopilotModal {
+    type Rendered = Stateful<V, Div<V>>;
+
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+        div().id(self.id.clone()).child(
+                Modal::new("some-id")
+                    .title("Connect Copilot to Zed")
+                    .child(Label::new("You can update your settings or sign out from the Copilot menu in the status bar.").color(TextColor::Muted))
+                    .primary_action(Button::new("Connect to Github").variant(ButtonVariant::Filled)),
+            )
+    }
+}
+
 impl CopilotModal {
     pub fn new(id: impl Into<ElementId>) -> Self {
         Self { id: id.into() }
     }
-
-    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")
-                .child(Label::new("You can update your settings or sign out from the Copilot menu in the status bar.").color(TextColor::Muted))
-                .primary_action(Button::new("Connect to Github").variant(ButtonVariant::Filled)),
-        )
-    }
 }
 
+use gpui::{Div, RenderOnce, Stateful};
 #[cfg(feature = "stories")]
 pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use gpui::{Div, Render};
-
-    use crate::Story;
-
     use super::*;
+    use crate::Story;
+    use gpui::{Div, Render};
 
     pub struct CopilotModalStory;
 
-    impl Render for CopilotModalStory {
+    impl Render<Self> for CopilotModalStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -1,6 +1,6 @@
 use std::path::PathBuf;
 
-use gpui::{Div, Render, View, VisualContext};
+use gpui::{Div, Render, RenderOnce, View, VisualContext};
 
 use crate::prelude::*;
 use crate::{
@@ -47,7 +47,7 @@ impl EditorPane {
     }
 }
 
-impl Render for EditorPane {
+impl Render<Self> for EditorPane {
     type Element = Div<Self>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {

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

@@ -1,11 +1,36 @@
 use crate::prelude::*;
 use crate::{OrderMethod, Palette, PaletteItem};
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct LanguageSelector {
     id: ElementId,
 }
 
+impl<V: 'static> Component<V> for LanguageSelector {
+    type Rendered = Stateful<V, Div<V>>;
+
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+        div().id(self.id.clone()).child(
+            Palette::new("palette")
+                .items(vec![
+                    PaletteItem::new("C"),
+                    PaletteItem::new("C++"),
+                    PaletteItem::new("CSS"),
+                    PaletteItem::new("Elixir"),
+                    PaletteItem::new("Elm"),
+                    PaletteItem::new("ERB"),
+                    PaletteItem::new("Rust (current)"),
+                    PaletteItem::new("Scheme"),
+                    PaletteItem::new("TOML"),
+                    PaletteItem::new("TypeScript"),
+                ])
+                .placeholder("Select a language...")
+                .empty_string("No matches")
+                .default_order(OrderMethod::Ascending),
+        )
+    }
+}
+
 impl LanguageSelector {
     pub fn new(id: impl Into<ElementId>) -> Self {
         Self { id: id.into() }
@@ -33,6 +58,7 @@ impl LanguageSelector {
     }
 }
 
+use gpui::{Div, RenderOnce, Stateful};
 #[cfg(feature = "stories")]
 pub use stories::*;
 
@@ -44,7 +70,7 @@ mod stories {
 
     pub struct LanguageSelectorStory;
 
-    impl Render for LanguageSelectorStory {
+    impl Render<Self> for LanguageSelectorStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -1,17 +1,15 @@
 use crate::prelude::*;
 use crate::{v_stack, Buffer, Icon, IconButton, Label};
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct MultiBuffer {
     buffers: Vec<Buffer>,
 }
 
-impl MultiBuffer {
-    pub fn new(buffers: Vec<Buffer>) -> Self {
-        Self { buffers }
-    }
+impl<V: 'static> Component<V> for MultiBuffer {
+    type Rendered = Div<V>;
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         v_stack()
             .w_full()
             .h_full()
@@ -33,6 +31,13 @@ impl MultiBuffer {
     }
 }
 
+impl MultiBuffer {
+    pub fn new(buffers: Vec<Buffer>) -> Self {
+        Self { buffers }
+    }
+}
+
+use gpui::{Div, RenderOnce};
 #[cfg(feature = "stories")]
 pub use stories::*;
 
@@ -44,7 +49,7 @@ mod stories {
 
     pub struct MultiBufferStory;
 
-    impl Render for MultiBufferStory {
+    impl Render<Self> for MultiBufferStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -3,19 +3,17 @@ use crate::{
     v_stack, Avatar, ButtonOrIconButton, ClickHandler, Icon, IconElement, Label, LineHeightStyle,
     ListHeader, ListHeaderMeta, ListSeparator, PublicPlayer, TextColor, UnreadIndicator,
 };
-use gpui::prelude::*;
+use gpui::{prelude::*, Div, Stateful};
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct NotificationsPanel {
     id: ElementId,
 }
 
-impl NotificationsPanel {
-    pub fn new(id: impl Into<ElementId>) -> Self {
-        Self { id: id.into() }
-    }
+impl<V: 'static> Component<V> for NotificationsPanel {
+    type Rendered = Stateful<V, Div<V>>;
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         div()
             .id(self.id.clone())
             .flex()
@@ -56,6 +54,12 @@ impl NotificationsPanel {
     }
 }
 
+impl NotificationsPanel {
+    pub fn new(id: impl Into<ElementId>) -> Self {
+        Self { id: id.into() }
+    }
+}
+
 pub struct NotificationAction<V: 'static> {
     button: ButtonOrIconButton<V>,
     tooltip: SharedString,
@@ -102,7 +106,7 @@ impl<V: 'static> Default for NotificationHandlers<V> {
     }
 }
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct Notification<V: 'static> {
     id: ElementId,
     slot: ActorOrIcon,
@@ -116,6 +120,85 @@ pub struct Notification<V: 'static> {
     handlers: NotificationHandlers<V>,
 }
 
+impl<V: 'static> Component<V> for Notification<V> {
+    type Rendered = Stateful<V, Div<V>>;
+
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+        div()
+            .relative()
+            .id(self.id.clone())
+            .p_1()
+            .flex()
+            .flex_col()
+            .w_full()
+            .children(
+                Some(
+                    div()
+                        .absolute()
+                        .left(px(3.0))
+                        .top_3()
+                        .z_index(2)
+                        .child(UnreadIndicator::new()),
+                )
+                .filter(|_| self.unread),
+            )
+            .child(
+                v_stack()
+                    .z_index(1)
+                    .gap_1()
+                    .w_full()
+                    .child(
+                        h_stack()
+                            .w_full()
+                            .gap_2()
+                            .child(self.render_slot(cx))
+                            .child(div().flex_1().child(Label::new(self.message.clone()))),
+                    )
+                    .child(
+                        h_stack()
+                            .justify_between()
+                            .child(
+                                h_stack()
+                                    .gap_1()
+                                    .child(
+                                        Label::new(naive_format_distance_from_now(
+                                            self.date_received,
+                                            true,
+                                            true,
+                                        ))
+                                        .color(TextColor::Muted),
+                                    )
+                                    .child(self.render_meta_items(cx)),
+                            )
+                            .child(match (self.actions, self.action_taken) {
+                                // Show nothing
+                                (None, _) => div(),
+                                // Show the taken_message
+                                (Some(_), Some(action_taken)) => h_stack()
+                                    .children(action_taken.taken_message.0.map(|icon| {
+                                        IconElement::new(icon).color(crate::TextColor::Muted)
+                                    }))
+                                    .child(
+                                        Label::new(action_taken.taken_message.1.clone())
+                                            .color(TextColor::Muted),
+                                    ),
+                                // Show the actions
+                                (Some(actions), None) => {
+                                    h_stack().children(actions.map(|action| match action.button {
+                                        ButtonOrIconButton::Button(button) => {
+                                            button.render_into_any()
+                                        }
+                                        ButtonOrIconButton::IconButton(icon_button) => {
+                                            icon_button.render_into_any()
+                                        }
+                                    }))
+                                }
+                            }),
+                    ),
+            )
+    }
+}
+
 impl<V> Notification<V> {
     fn new(
         id: ElementId,
@@ -262,85 +345,10 @@ impl<V> Notification<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(),
+            ActorOrIcon::Actor(actor) => Avatar::new(actor.avatar.clone()).render_into_any(),
+            ActorOrIcon::Icon(icon) => IconElement::new(icon.clone()).render_into_any(),
         }
     }
-
-    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
-        div()
-            .relative()
-            .id(self.id.clone())
-            .p_1()
-            .flex()
-            .flex_col()
-            .w_full()
-            .children(
-                Some(
-                    div()
-                        .absolute()
-                        .left(px(3.0))
-                        .top_3()
-                        .z_index(2)
-                        .child(UnreadIndicator::new()),
-                )
-                .filter(|_| self.unread),
-            )
-            .child(
-                v_stack()
-                    .z_index(1)
-                    .gap_1()
-                    .w_full()
-                    .child(
-                        h_stack()
-                            .w_full()
-                            .gap_2()
-                            .child(self.render_slot(cx))
-                            .child(div().flex_1().child(Label::new(self.message.clone()))),
-                    )
-                    .child(
-                        h_stack()
-                            .justify_between()
-                            .child(
-                                h_stack()
-                                    .gap_1()
-                                    .child(
-                                        Label::new(naive_format_distance_from_now(
-                                            self.date_received,
-                                            true,
-                                            true,
-                                        ))
-                                        .color(TextColor::Muted),
-                                    )
-                                    .child(self.render_meta_items(cx)),
-                            )
-                            .child(match (self.actions, self.action_taken) {
-                                // Show nothing
-                                (None, _) => div(),
-                                // Show the taken_message
-                                (Some(_), Some(action_taken)) => h_stack()
-                                    .children(action_taken.taken_message.0.map(|icon| {
-                                        IconElement::new(icon).color(crate::TextColor::Muted)
-                                    }))
-                                    .child(
-                                        Label::new(action_taken.taken_message.1.clone())
-                                            .color(TextColor::Muted),
-                                    ),
-                                // Show the actions
-                                (Some(actions), None) => {
-                                    h_stack().children(actions.map(|action| match action.button {
-                                        ButtonOrIconButton::Button(button) => {
-                                            Component::render(button)
-                                        }
-                                        ButtonOrIconButton::IconButton(icon_button) => {
-                                            Component::render(icon_button)
-                                        }
-                                    }))
-                                }
-                            }),
-                    ),
-            )
-    }
 }
 
 use chrono::NaiveDateTime;
@@ -356,7 +364,7 @@ mod stories {
 
     pub struct NotificationsPanelStory;
 
-    impl Render for NotificationsPanelStory {
+    impl Render<Self> for NotificationsPanelStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -1,4 +1,7 @@
-use gpui::{hsla, red, AnyElement, ElementId, ExternalPaths, Hsla, Length, Size, View};
+use gpui::{
+    hsla, red, AnyElement, Div, ElementId, ExternalPaths, Hsla, Length, RenderOnce, Size, Stateful,
+    View,
+};
 use smallvec::SmallVec;
 
 use crate::prelude::*;
@@ -10,7 +13,7 @@ pub enum SplitDirection {
     Vertical,
 }
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct Pane<V: 'static> {
     id: ElementId,
     size: Size<Length>,
@@ -18,24 +21,10 @@ pub struct Pane<V: 'static> {
     children: SmallVec<[AnyElement<V>; 2]>,
 }
 
-impl<V: 'static> Pane<V> {
-    pub fn new(id: impl Into<ElementId>, size: Size<Length>) -> Self {
-        // Fill is only here for debugging purposes, remove before release
+impl<V: 'static> Component<V> for Pane<V> {
+    type Rendered = Stateful<V, Div<V>>;
 
-        Self {
-            id: id.into(),
-            size,
-            fill: hsla(0.3, 0.3, 0.3, 1.),
-            children: SmallVec::new(),
-        }
-    }
-
-    pub fn fill(mut self, fill: Hsla) -> Self {
-        self.fill = fill;
-        self
-    }
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         div()
             .id(self.id.clone())
             .flex()
@@ -59,37 +48,41 @@ impl<V: 'static> Pane<V> {
     }
 }
 
-impl<V: 'static> ParentComponent<V> for Pane<V> {
+impl<V: 'static> Pane<V> {
+    pub fn new(id: impl Into<ElementId>, size: Size<Length>) -> Self {
+        // Fill is only here for debugging purposes, remove before release
+
+        Self {
+            id: id.into(),
+            size,
+            fill: hsla(0.3, 0.3, 0.3, 1.),
+            children: SmallVec::new(),
+        }
+    }
+
+    pub fn fill(mut self, fill: Hsla) -> Self {
+        self.fill = fill;
+        self
+    }
+}
+
+impl<V: 'static> ParentElement<V> for Pane<V> {
     fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
         &mut self.children
     }
 }
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct PaneGroup<V: 'static> {
     groups: Vec<PaneGroup<V>>,
     panes: Vec<Pane<V>>,
     split_direction: SplitDirection,
 }
 
-impl<V: 'static> PaneGroup<V> {
-    pub fn new_groups(groups: Vec<PaneGroup<V>>, split_direction: SplitDirection) -> Self {
-        Self {
-            groups,
-            panes: Vec::new(),
-            split_direction,
-        }
-    }
+impl<V: 'static> Component<V> for PaneGroup<V> {
+    type Rendered = Div<V>;
 
-    pub fn new_panes(panes: Vec<Pane<V>>, split_direction: SplitDirection) -> Self {
-        Self {
-            groups: Vec::new(),
-            panes,
-            split_direction,
-        }
-    }
-
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         if !self.panes.is_empty() {
             let el = div()
                 .flex()
@@ -126,3 +119,21 @@ impl<V: 'static> PaneGroup<V> {
         unreachable!()
     }
 }
+
+impl<V: 'static> PaneGroup<V> {
+    pub fn new_groups(groups: Vec<PaneGroup<V>>, split_direction: SplitDirection) -> Self {
+        Self {
+            groups,
+            panes: Vec::new(),
+            split_direction,
+        }
+    }
+
+    pub fn new_panes(panes: Vec<Pane<V>>, split_direction: SplitDirection) -> Self {
+        Self {
+            groups: Vec::new(),
+            panes,
+            split_direction,
+        }
+    }
+}

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

@@ -3,12 +3,50 @@ use crate::{
     ListHeader,
 };
 use gpui::prelude::*;
+use gpui::Div;
+use gpui::Stateful;
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct ProjectPanel {
     id: ElementId,
 }
 
+impl<V: 'static> Component<V> for ProjectPanel {
+    type Rendered = Stateful<V, Div<V>>;
+
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+        div()
+            .id(self.id.clone())
+            .flex()
+            .flex_col()
+            .size_full()
+            .bg(cx.theme().colors().surface_background)
+            .child(
+                div()
+                    .id("project-panel-contents")
+                    .w_full()
+                    .flex()
+                    .flex_col()
+                    .overflow_y_scroll()
+                    .child(
+                        List::new(static_project_panel_single_items())
+                            .header(ListHeader::new("FILES"))
+                            .empty_message("No files in directory"),
+                    )
+                    .child(
+                        List::new(static_project_panel_project_items())
+                            .header(ListHeader::new("PROJECT"))
+                            .empty_message("No folders in directory"),
+                    ),
+            )
+            .child(
+                Input::new("Find something...")
+                    .value("buffe".to_string())
+                    .state(InteractionState::Focused),
+            )
+    }
+}
+
 impl ProjectPanel {
     pub fn new(id: impl Into<ElementId>) -> Self {
         Self { id: id.into() }
@@ -59,7 +97,7 @@ mod stories {
 
     pub struct ProjectPanelStory;
 
-    impl Render for ProjectPanelStory {
+    impl Render<Self> for ProjectPanelStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -1,17 +1,15 @@
 use crate::prelude::*;
 use crate::{OrderMethod, Palette, PaletteItem};
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct RecentProjects {
     id: ElementId,
 }
 
-impl RecentProjects {
-    pub fn new(id: impl Into<ElementId>) -> Self {
-        Self { id: id.into() }
-    }
+impl<V: 'static> Component<V> for RecentProjects {
+    type Rendered = Stateful<V, Div<V>>;
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         div().id(self.id.clone()).child(
             Palette::new("palette")
                 .items(vec![
@@ -29,6 +27,13 @@ impl RecentProjects {
     }
 }
 
+impl RecentProjects {
+    pub fn new(id: impl Into<ElementId>) -> Self {
+        Self { id: id.into() }
+    }
+}
+
+use gpui::{Div, RenderOnce, Stateful};
 #[cfg(feature = "stories")]
 pub use stories::*;
 
@@ -40,7 +45,7 @@ mod stories {
 
     pub struct RecentProjectsStory;
 
-    impl Render for RecentProjectsStory {
+    impl Render<Self> for RecentProjectsStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -1,5 +1,7 @@
 use std::sync::Arc;
 
+use gpui::{Div, RenderOnce};
+
 use crate::prelude::*;
 use crate::{Button, Icon, IconButton, TextColor, ToolDivider, Workspace};
 
@@ -28,14 +30,31 @@ impl Default for ToolGroup {
     }
 }
 
-#[derive(Component)]
-#[component(view_type = "Workspace")]
+#[derive(RenderOnce)]
+#[view = "Workspace"]
 pub struct StatusBar {
     left_tools: Option<ToolGroup>,
     right_tools: Option<ToolGroup>,
     bottom_tools: Option<ToolGroup>,
 }
 
+impl Component<Workspace> for StatusBar {
+    type Rendered = Div<Workspace>;
+
+    fn render(self, view: &mut Workspace, cx: &mut ViewContext<Workspace>) -> Self::Rendered {
+        div()
+            .py_0p5()
+            .px_1()
+            .flex()
+            .items_center()
+            .justify_between()
+            .w_full()
+            .bg(cx.theme().colors().status_bar_background)
+            .child(self.left_tools(view, cx))
+            .child(self.right_tools(view, cx))
+    }
+}
+
 impl StatusBar {
     pub fn new() -> Self {
         Self {
@@ -81,28 +100,7 @@ impl StatusBar {
         self
     }
 
-    fn render(
-        self,
-        view: &mut Workspace,
-        cx: &mut ViewContext<Workspace>,
-    ) -> impl Component<Workspace> {
-        div()
-            .py_0p5()
-            .px_1()
-            .flex()
-            .items_center()
-            .justify_between()
-            .w_full()
-            .bg(cx.theme().colors().status_bar_background)
-            .child(self.left_tools(view, cx))
-            .child(self.right_tools(view, cx))
-    }
-
-    fn left_tools(
-        &self,
-        workspace: &mut Workspace,
-        cx: &WindowContext,
-    ) -> impl Component<Workspace> {
+    fn left_tools(&self, workspace: &mut Workspace, cx: &WindowContext) -> impl Element<Workspace> {
         div()
             .flex()
             .items_center()
@@ -133,7 +131,7 @@ impl StatusBar {
         &self,
         workspace: &mut Workspace,
         cx: &WindowContext,
-    ) -> impl Component<Workspace> {
+    ) -> impl Element<Workspace> {
         div()
             .flex()
             .items_center()

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

@@ -1,7 +1,9 @@
 use crate::{prelude::*, Icon, IconButton, Tab};
 use gpui::prelude::*;
+use gpui::Div;
+use gpui::Stateful;
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct TabBar {
     id: ElementId,
     /// Backwards, Forwards
@@ -9,21 +11,10 @@ pub struct TabBar {
     tabs: Vec<Tab>,
 }
 
-impl TabBar {
-    pub fn new(id: impl Into<ElementId>, tabs: Vec<Tab>) -> Self {
-        Self {
-            id: id.into(),
-            can_navigate: (false, false),
-            tabs,
-        }
-    }
+impl<V: 'static> Component<V> for TabBar {
+    type Rendered = Stateful<V, Div<V>>;
 
-    pub fn can_navigate(mut self, can_navigate: (bool, bool)) -> Self {
-        self.can_navigate = can_navigate;
-        self
-    }
-
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         let (can_navigate_back, can_navigate_forward) = self.can_navigate;
 
         div()
@@ -92,6 +83,21 @@ impl TabBar {
     }
 }
 
+impl TabBar {
+    pub fn new(id: impl Into<ElementId>, tabs: Vec<Tab>) -> Self {
+        Self {
+            id: id.into(),
+            can_navigate: (false, false),
+            tabs,
+        }
+    }
+
+    pub fn can_navigate(mut self, can_navigate: (bool, bool)) -> Self {
+        self.can_navigate = can_navigate;
+        self
+    }
+}
+
 use gpui::ElementId;
 #[cfg(feature = "stories")]
 pub use stories::*;
@@ -104,7 +110,7 @@ mod stories {
 
     pub struct TabBarStory;
 
-    impl Render for TabBarStory {
+    impl Render<Self> for TabBarStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -1,11 +1,78 @@
-use gpui::{relative, rems, Size};
-
 use crate::prelude::*;
 use crate::{Icon, IconButton, Pane, Tab};
+use gpui::{relative, rems, Div, RenderOnce, Size};
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct Terminal;
 
+impl<V: 'static> Component<V> for Terminal {
+    type Rendered = Div<V>;
+
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+        let can_navigate_back = true;
+        let can_navigate_forward = false;
+
+        div()
+            .flex()
+            .flex_col()
+            .w_full()
+            .child(
+                // Terminal Tabs.
+                div()
+                    .w_full()
+                    .flex()
+                    .bg(cx.theme().colors().surface_background)
+                    .child(
+                        div().px_1().flex().flex_none().gap_2().child(
+                            div()
+                                .flex()
+                                .items_center()
+                                .gap_px()
+                                .child(
+                                    IconButton::new("arrow_left", Icon::ArrowLeft).state(
+                                        InteractionState::Enabled.if_enabled(can_navigate_back),
+                                    ),
+                                )
+                                .child(IconButton::new("arrow_right", Icon::ArrowRight).state(
+                                    InteractionState::Enabled.if_enabled(can_navigate_forward),
+                                )),
+                        ),
+                    )
+                    .child(
+                        div().w_0().flex_1().h_full().child(
+                            div()
+                                .flex()
+                                .child(
+                                    Tab::new(1)
+                                        .title("zed — fish".to_string())
+                                        .icon(Icon::Terminal)
+                                        .close_side(IconSide::Right)
+                                        .current(true),
+                                )
+                                .child(
+                                    Tab::new(2)
+                                        .title("zed — fish".to_string())
+                                        .icon(Icon::Terminal)
+                                        .close_side(IconSide::Right)
+                                        .current(false),
+                                ),
+                        ),
+                    ),
+            )
+            // Terminal Pane.
+            .child(
+                Pane::new(
+                    "terminal",
+                    Size {
+                        width: relative(1.).into(),
+                        height: rems(36.).into(),
+                    },
+                )
+                .child(crate::static_data::terminal_buffer(cx)),
+            )
+    }
+}
+
 impl Terminal {
     pub fn new() -> Self {
         Self
@@ -86,7 +153,7 @@ mod stories {
     use gpui::{Div, Render};
     pub struct TerminalStory;
 
-    impl Render for TerminalStory {
+    impl Render<Self> for TerminalStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -1,17 +1,16 @@
 use crate::prelude::*;
 use crate::{OrderMethod, Palette, PaletteItem};
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct ThemeSelector {
     id: ElementId,
 }
 
-impl ThemeSelector {
-    pub fn new(id: impl Into<ElementId>) -> Self {
-        Self { id: id.into() }
-    }
+impl<V: 'static> Component<V> for ThemeSelector {
+    type Rendered = Div<V>;
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+        let cx: &mut ViewContext<V> = cx;
         div().child(
             Palette::new(self.id.clone())
                 .items(vec![
@@ -34,6 +33,13 @@ impl ThemeSelector {
     }
 }
 
+impl ThemeSelector {
+    pub fn new(id: impl Into<ElementId>) -> Self {
+        Self { id: id.into() }
+    }
+}
+
+use gpui::{Div, RenderOnce};
 #[cfg(feature = "stories")]
 pub use stories::*;
 
@@ -47,7 +53,7 @@ mod stories {
 
     pub struct ThemeSelectorStory;
 
-    impl Render for ThemeSelectorStory {
+    impl Render<Self> for ThemeSelectorStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -1,7 +1,7 @@
 use std::sync::atomic::AtomicBool;
 use std::sync::Arc;
 
-use gpui::{Div, Render, View, VisualContext};
+use gpui::{Div, Render, RenderOnce, View, VisualContext};
 
 use crate::prelude::*;
 use crate::settings::user_settings;
@@ -85,7 +85,7 @@ impl TitleBar {
     }
 }
 
-impl Render for TitleBar {
+impl Render<Self> for TitleBar {
     type Element = Div<Self>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {
@@ -205,7 +205,7 @@ mod stories {
         }
     }
 
-    impl Render for TitleBarStory {
+    impl Render<Self> for TitleBarStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {

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

@@ -1,4 +1,4 @@
-use gpui::AnyElement;
+use gpui::{AnyElement, Div, RenderOnce};
 use smallvec::SmallVec;
 
 use crate::prelude::*;
@@ -6,12 +6,26 @@ use crate::prelude::*;
 #[derive(Clone)]
 pub struct ToolbarItem {}
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct Toolbar<V: 'static> {
     left_items: SmallVec<[AnyElement<V>; 2]>,
     right_items: SmallVec<[AnyElement<V>; 2]>,
 }
 
+impl<V: 'static> Component<V> for Toolbar<V> {
+    type Rendered = Div<V>;
+
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+        div()
+            .bg(cx.theme().colors().toolbar_background)
+            .p_2()
+            .flex()
+            .justify_between()
+            .child(div().flex().children(self.left_items))
+            .child(div().flex().children(self.right_items))
+    }
+}
+
 impl<V: 'static> Toolbar<V> {
     pub fn new() -> Self {
         Self {
@@ -20,49 +34,39 @@ impl<V: 'static> Toolbar<V> {
         }
     }
 
-    pub fn left_item(mut self, child: impl Element<V>) -> Self
+    pub fn left_item(mut self, child: impl RenderOnce<V>) -> Self
     where
         Self: Sized,
     {
-        self.left_items.push(child.render());
+        self.left_items.push(child.render_into_any());
         self
     }
 
-    pub fn left_items(mut self, iter: impl IntoIterator<Item = impl Element<V>>) -> Self
+    pub fn left_items(mut self, iter: impl IntoIterator<Item = impl RenderOnce<V>>) -> Self
     where
         Self: Sized,
     {
         self.left_items
-            .extend(iter.into_iter().map(|item| item.render()));
+            .extend(iter.into_iter().map(|item| item.render_into_any()));
         self
     }
 
-    pub fn right_item(mut self, child: impl Element<V>) -> Self
+    pub fn right_item(mut self, child: impl RenderOnce<V>) -> Self
     where
         Self: Sized,
     {
-        self.right_items.push(child.render());
+        self.right_items.push(child.render_into_any());
         self
     }
 
-    pub fn right_items(mut self, iter: impl IntoIterator<Item = impl Element<V>>) -> Self
+    pub fn right_items(mut self, iter: impl IntoIterator<Item = impl RenderOnce<V>>) -> Self
     where
         Self: Sized,
     {
         self.right_items
-            .extend(iter.into_iter().map(|item| item.render()));
+            .extend(iter.into_iter().map(|item| item.render_into_any()));
         self
     }
-
-    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
-        div()
-            .bg(cx.theme().colors().toolbar_background)
-            .p_2()
-            .flex()
-            .justify_between()
-            .child(div().flex().children(self.left_items))
-            .child(div().flex().children(self.right_items))
-    }
 }
 
 #[cfg(feature = "stories")]
@@ -81,7 +85,7 @@ mod stories {
 
     pub struct ToolbarStory;
 
-    impl Render for ToolbarStory {
+    impl Render<Self> for ToolbarStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@@ -95,21 +99,21 @@ mod stories {
                             vec![
                                 Symbol(vec![
                                     HighlightedText {
-                                        text: "impl ".to_string(),
+                                        text: "impl ".into(),
                                         color: cx.theme().syntax_color("keyword"),
                                     },
                                     HighlightedText {
-                                        text: "ToolbarStory".to_string(),
+                                        text: "ToolbarStory".into(),
                                         color: cx.theme().syntax_color("function"),
                                     },
                                 ]),
                                 Symbol(vec![
                                     HighlightedText {
-                                        text: "fn ".to_string(),
+                                        text: "fn ".into(),
                                         color: cx.theme().syntax_color("keyword"),
                                     },
                                     HighlightedText {
-                                        text: "render".to_string(),
+                                        text: "render".into(),
                                         color: cx.theme().syntax_color("function"),
                                     },
                                 ]),

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

@@ -7,21 +7,16 @@ enum TrafficLightColor {
     Green,
 }
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 struct TrafficLight {
     color: TrafficLightColor,
     window_has_focus: bool,
 }
 
-impl TrafficLight {
-    fn new(color: TrafficLightColor, window_has_focus: bool) -> Self {
-        Self {
-            color,
-            window_has_focus,
-        }
-    }
+impl<V: 'static> Component<V> for TrafficLight {
+    type Rendered = Div<V>;
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         let system_colors = &cx.theme().styles.system;
 
         let fill = match (self.window_has_focus, self.color) {
@@ -35,24 +30,24 @@ impl TrafficLight {
     }
 }
 
-#[derive(Component)]
-pub struct TrafficLights {
-    window_has_focus: bool,
-}
-
-impl TrafficLights {
-    pub fn new() -> Self {
+impl TrafficLight {
+    fn new(color: TrafficLightColor, window_has_focus: bool) -> Self {
         Self {
-            window_has_focus: true,
+            color,
+            window_has_focus,
         }
     }
+}
 
-    pub fn window_has_focus(mut self, window_has_focus: bool) -> Self {
-        self.window_has_focus = window_has_focus;
-        self
-    }
+#[derive(RenderOnce)]
+pub struct TrafficLights {
+    window_has_focus: bool,
+}
+
+impl<V: 'static> Component<V> for TrafficLights {
+    type Rendered = Div<V>;
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         div()
             .flex()
             .items_center()
@@ -72,6 +67,20 @@ impl TrafficLights {
     }
 }
 
+impl TrafficLights {
+    pub fn new() -> Self {
+        Self {
+            window_has_focus: true,
+        }
+    }
+
+    pub fn window_has_focus(mut self, window_has_focus: bool) -> Self {
+        self.window_has_focus = window_has_focus;
+        self
+    }
+}
+
+use gpui::{Div, RenderOnce};
 #[cfg(feature = "stories")]
 pub use stories::*;
 
@@ -85,7 +94,7 @@ mod stories {
 
     pub struct TrafficLightsStory;
 
-    impl Render for TrafficLightsStory {
+    impl Render<Self> for TrafficLightsStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -1,7 +1,7 @@
 use std::sync::Arc;
 
 use chrono::DateTime;
-use gpui::{px, relative, Div, Render, Size, View, VisualContext};
+use gpui::{px, relative, Div, Render, RenderOnce, Size, View, VisualContext};
 use settings2::Settings;
 use theme2::ThemeSettings;
 
@@ -191,7 +191,7 @@ impl Workspace {
     }
 }
 
-impl Render for Workspace {
+impl Render<Self> for Workspace {
     type Element = Div<Self>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {
@@ -388,7 +388,7 @@ mod stories {
         }
     }
 
-    impl Render for WorkspaceStory {
+    impl Render<Self> for WorkspaceStory {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

crates/workspace2/src/dock.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{status_bar::StatusItemView, Axis, Workspace};
 use gpui::{
     div, px, Action, AnchorCorner, AnyView, AppContext, Component, Div, Entity, EntityId,
-    EventEmitter, FocusHandle, FocusableView, ParentComponent, Render, SharedString, Styled,
+    EventEmitter, FocusHandle, FocusableView, ParentElement, Render, SharedString, Styled,
     Subscription, View, ViewContext, VisualContext, WeakView, WindowContext,
 };
 use schemars::JsonSchema;

crates/workspace2/src/status_bar.rs 🔗

@@ -2,8 +2,8 @@ use std::any::TypeId;
 
 use crate::{ItemHandle, Pane};
 use gpui::{
-    div, AnyView, Component, Div, ParentComponent, Render, Styled, Subscription, View, ViewContext,
-    WindowContext,
+    div, AnyView, Component, Div, ParentElement, Render, Styled, Subscription, View,
+    ViewContext, WindowContext,
 };
 use theme2::ActiveTheme;
 use ui::h_stack;

crates/workspace2/src/workspace2.rs 🔗

@@ -31,10 +31,10 @@ use futures::{
 use gpui::{
     actions, div, point, size, Action, AnyModel, AnyView, AnyWeakView, AppContext, AsyncAppContext,
     AsyncWindowContext, Bounds, Context, Div, Entity, EntityId, EventEmitter, FocusHandle,
-    FocusableView, GlobalPixels, InteractiveComponent, KeyContext, ManagedView, Model,
-    ModelContext, ParentComponent, PathPromptOptions, Point, PromptLevel, Render, Size, Styled,
-    Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext,
-    WindowHandle, WindowOptions,
+    FocusableView, GlobalPixels, InteractiveElement, KeyContext, ManagedView, Model, ModelContext,
+    ParentElement, PathPromptOptions, Point, PromptLevel, Render, Size, Styled, Subscription, Task,
+    View, ViewContext, VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle,
+    WindowOptions,
 };
 use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem};
 use itertools::Itertools;