Merge branch 'main' into search2

Piotr Osiewicz created

Change summary

crates/auto_update2/src/update_notification.rs   |   4 
crates/collab_ui2/src/collab_panel.rs            |   4 
crates/collab_ui2/src/collab_titlebar_item.rs    |   8 
crates/command_palette2/src/command_palette.rs   |   7 
crates/diagnostics2/src/diagnostics.rs           |  16 
crates/diagnostics2/src/items.rs                 |   6 
crates/diagnostics2/src/toolbar_controls.rs      |   4 
crates/editor2/src/editor.rs                     |  30 
crates/editor2/src/editor_tests.rs               |   2 
crates/editor2/src/element.rs                    |  71 +
crates/editor2/src/items.rs                      |   2 
crates/file_finder2/src/file_finder.rs           |   6 
crates/go_to_line2/src/go_to_line.rs             |   5 
crates/gpui2/src/app.rs                          |  12 
crates/gpui2/src/app/async_context.rs            |   6 
crates/gpui2/src/app/entity_map.rs               |  10 
crates/gpui2/src/app/test_context.rs             |  12 
crates/gpui2/src/element.rs                      | 561 ++++++++++++-----
crates/gpui2/src/elements/div.rs                 | 162 ++--
crates/gpui2/src/elements/img.rs                 |  39 
crates/gpui2/src/elements/overlay.rs             |  40 
crates/gpui2/src/elements/svg.rs                 |  39 
crates/gpui2/src/elements/text.rs                | 275 ++++++--
crates/gpui2/src/elements/uniform_list.rs        |  65 +-
crates/gpui2/src/gpui2.rs                        |   6 
crates/gpui2/src/interactive.rs                  |  22 
crates/gpui2/src/prelude.rs                      |   5 
crates/gpui2/src/view.rs                         | 207 +++--
crates/gpui2/src/window.rs                       |  16 
crates/gpui2_macros/src/derive_render_once.rs    |  64 ++
crates/gpui2_macros/src/gpui2_macros.rs          |  16 
crates/picker2/src/picker2.rs                    |   8 
crates/project_panel2/src/project_panel.rs       |  11 
crates/search2/src/buffer_search.rs              |   8 
crates/search2/src/search.rs                     |   8 
crates/search2/src/search_bar.rs                 |   4 
crates/storybook2/src/stories/colors.rs          |   4 
crates/storybook2/src/stories/focus.rs           |   2 
crates/storybook2/src/stories/kitchen_sink.rs    |   2 
crates/storybook2/src/stories/picker.rs          |   9 
crates/storybook2/src/stories/scroll.rs          |   2 
crates/storybook2/src/stories/text.rs            |   4 
crates/storybook2/src/stories/z_index.rs         |  24 
crates/storybook2/src/storybook2.rs              |   2 
crates/storybook3/src/storybook3.rs              |   2 
crates/terminal_view2/src/terminal_panel.rs      |   4 
crates/terminal_view2/src/terminal_view.rs       |  16 
crates/theme2/src/story.rs                       |  17 
crates/theme2/src/styles/players.rs              |   4 
crates/ui2/docs/hello-world.md                   |  10 
crates/ui2/src/components/avatar.rs              |  37 
crates/ui2/src/components/button.rs              |  87 ++
crates/ui2/src/components/checkbox.rs            | 132 ++++
crates/ui2/src/components/context_menu.rs        |  89 +-
crates/ui2/src/components/details.rs             |  33 
crates/ui2/src/components/divider.rs             |  23 
crates/ui2/src/components/facepile.rs            |  23 
crates/ui2/src/components/icon.rs                |  25 
crates/ui2/src/components/icon_button.rs         | 116 +-
crates/ui2/src/components/indicator.rs           |  22 
crates/ui2/src/components/input.rs               |  86 +-
crates/ui2/src/components/keybinding.rs          |  75 +-
crates/ui2/src/components/label.rs               | 109 +-
crates/ui2/src/components/list.rs                | 184 ++++-
crates/ui2/src/components/modal.rs               |  63 +
crates/ui2/src/components/notification_toast.rs  |   4 
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               |  37 +
crates/ui2/src/components/toggle.rs              |   4 
crates/ui2/src/components/tool_divider.rs        |  13 
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                          |  16 
crates/ui2/src/to_extract/assistant_panel.rs     |  38 
crates/ui2/src/to_extract/breadcrumb.rs          |  45 
crates/ui2/src/to_extract/buffer.rs              |  31 
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   |  32 
crates/ui2/src/to_extract/multi_buffer.rs        |  19 
crates/ui2/src/to_extract/notifications_panel.rs | 184 +++--
crates/ui2/src/to_extract/panes.rs               |  87 +-
crates/ui2/src/to_extract/project_panel.rs       |  44 +
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            |  77 ++
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                    |  13 
crates/workspace2/src/modal_layer.rs             |   2 
crates/workspace2/src/notifications.rs           |   6 
crates/workspace2/src/pane.rs                    |  16 
crates/workspace2/src/pane_group.rs              |  17 
crates/workspace2/src/status_bar.rs              |  10 
crates/workspace2/src/toolbar.rs                 |   6 
crates/workspace2/src/workspace2.rs              |  10 
108 files changed, 2,779 insertions(+), 1,691 deletions(-)

Detailed changes

crates/auto_update2/src/update_notification.rs 🔗

@@ -1,4 +1,4 @@
-use gpui::{div, Div, EventEmitter, ParentComponent, Render, SemanticVersion, ViewContext};
+use gpui::{div, Div, EventEmitter, ParentElement, Render, SemanticVersion, ViewContext};
 use menu::Cancel;
 use workspace::notifications::NotificationEvent;
 
@@ -8,7 +8,7 @@ pub struct UpdateNotification {
 
 impl EventEmitter<NotificationEvent> for UpdateNotification {}
 
-impl Render for UpdateNotification {
+impl Render<Self> for UpdateNotification {
     type Element = Div<Self>;
 
     fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> Self::Element {

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;
@@ -3294,7 +3294,7 @@ impl CollabPanel {
 //     .with_width(size.x())
 // }
 
-impl Render for CollabPanel {
+impl Render<Self> for CollabPanel {
     type Element = Focusable<Self, Div<Self>>;
 
     fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {

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, Div, InteractiveElement, Model, ParentElement, Render, RenderOnce,
+    Stateful, StatefulInteractiveElement, Styled, Subscription, ViewContext, VisualContext,
+    WeakView, WindowBounds,
 };
 use project::Project;
 use theme::ActiveTheme;
@@ -81,7 +81,7 @@ pub struct CollabTitlebarItem {
     _subscriptions: Vec<Subscription>,
 }
 
-impl Render for CollabTitlebarItem {
+impl Render<Self> for CollabTitlebarItem {
     type Element = Stateful<Self, Div<Self>>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

crates/command_palette2/src/command_palette.rs 🔗

@@ -1,9 +1,8 @@
 use collections::{CommandPaletteFilter, HashMap};
 use fuzzy::{StringMatch, StringMatchCandidate};
 use gpui::{
-    actions, div, prelude::*, Action, AppContext, Component, Div, EventEmitter, FocusHandle,
-    FocusableView, Keystroke, Manager, ParentComponent, Render, Styled, View, ViewContext,
-    VisualContext, WeakView,
+    actions, div, prelude::*, Action, AppContext, Div, EventEmitter, FocusHandle, FocusableView,
+    Keystroke, Manager, ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
 };
 use picker::{Picker, PickerDelegate};
 use std::{
@@ -77,7 +76,7 @@ impl FocusableView for CommandPalette {
     }
 }
 
-impl Render for CommandPalette {
+impl Render<Self> for CommandPalette {
     type Element = Div<Self>;
 
     fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {

crates/diagnostics2/src/diagnostics.rs 🔗

@@ -13,9 +13,9 @@ use editor::{
 };
 use futures::future::try_join_all;
 use gpui::{
-    actions, div, AnyElement, AnyView, AppContext, Component, Context, Div, EventEmitter,
-    FocusEvent, FocusHandle, Focusable, FocusableComponent, FocusableView, InteractiveComponent,
-    Model, ParentComponent, Render, SharedString, Styled, Subscription, Task, View, ViewContext,
+    actions, div, AnyElement, AnyView, AppContext, Context, Div, EventEmitter, FocusEvent,
+    FocusHandle, Focusable, FocusableElement, FocusableView, InteractiveElement, Model,
+    ParentElement, Render, RenderOnce, SharedString, Styled, Subscription, Task, View, ViewContext,
     VisualContext, WeakView,
 };
 use language::{
@@ -90,7 +90,7 @@ struct DiagnosticGroupState {
 
 impl EventEmitter<ItemEvent> for ProjectDiagnosticsEditor {}
 
-impl Render for ProjectDiagnosticsEditor {
+impl Render<Self> for ProjectDiagnosticsEditor {
     type Element = Focusable<Self, Div<Self>>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@@ -792,13 +792,15 @@ fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
             .when_some(diagnostic.code.as_ref(), |stack, code| {
                 stack.child(Label::new(code.clone()))
             })
-            .render()
+            .render_into_any()
     })
 }
 
 pub(crate) fn render_summary<T: 'static>(summary: &DiagnosticSummary) -> AnyElement<T> {
     if summary.error_count == 0 && summary.warning_count == 0 {
-        Label::new("No problems").render()
+        let label = Label::new("No problems");
+        label.render_into_any()
+        //.render()
     } else {
         h_stack()
             .bg(gpui::red())
@@ -806,7 +808,7 @@ pub(crate) fn render_summary<T: 'static>(summary: &DiagnosticSummary) -> AnyElem
             .child(Label::new(summary.error_count.to_string()))
             .child(IconElement::new(Icon::ExclamationTriangle))
             .child(Label::new(summary.warning_count.to_string()))
-            .render()
+            .render_into_any()
     }
 }
 

crates/diagnostics2/src/items.rs 🔗

@@ -1,8 +1,8 @@
 use collections::HashSet;
 use editor::{Editor, GoToDiagnostic};
 use gpui::{
-    rems, Div, EventEmitter, InteractiveComponent, ParentComponent, Render, Stateful,
-    StatefulInteractiveComponent, Styled, Subscription, View, ViewContext, WeakView,
+    rems, Div, EventEmitter, InteractiveElement, ParentElement, Render, Stateful,
+    StatefulInteractiveElement, Styled, Subscription, View, ViewContext, WeakView,
 };
 use language::Diagnostic;
 use lsp::LanguageServerId;
@@ -21,7 +21,7 @@ pub struct DiagnosticIndicator {
     _observe_active_editor: Option<Subscription>,
 }
 
-impl Render for DiagnosticIndicator {
+impl Render<Self> for DiagnosticIndicator {
     type Element = Stateful<Self, Div<Self>>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

crates/diagnostics2/src/toolbar_controls.rs 🔗

@@ -1,5 +1,5 @@
 use crate::ProjectDiagnosticsEditor;
-use gpui::{div, Div, EventEmitter, ParentComponent, Render, ViewContext, WeakView};
+use gpui::{div, Div, EventEmitter, ParentElement, Render, ViewContext, WeakView};
 use ui::{Icon, IconButton, Tooltip};
 use workspace::{item::ItemHandle, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
 
@@ -7,7 +7,7 @@ pub struct ToolbarControls {
     editor: Option<WeakView<ProjectDiagnosticsEditor>>,
 }
 
-impl Render for ToolbarControls {
+impl Render<Self> for ToolbarControls {
     type Element = Div<Self>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

crates/editor2/src/editor.rs 🔗

@@ -42,9 +42,9 @@ 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,
-    Subscription, Task, TextStyle, UniformListScrollHandle, View, ViewContext, VisualContext,
-    WeakView, WindowContext,
+    Hsla, InputHandler, KeyContext, Model, MouseButton, ParentElement, Pixels, Render,
+    SharedString, Styled, Subscription, Task, TextStyle, UniformListScrollHandle, View,
+    ViewContext, VisualContext, WeakView, WindowContext,
 };
 use highlight_matching_bracket::refresh_matching_bracket_highlights;
 use hover_popover::{hide_hover, HoverState};
@@ -1580,7 +1580,8 @@ impl CodeActionsMenu {
                                     )
                                     .map(|task| task.detach_and_log_err(cx));
                             })
-                            .child(action.lsp_action.title.clone())
+                            // TASK: It would be good to make lsp_action.title a SharedString to avoid allocating here.
+                            .child(SharedString::from(action.lsp_action.title.clone()))
                     })
                     .collect()
             },
@@ -1595,7 +1596,7 @@ impl CodeActionsMenu {
                 .max_by_key(|(_, action)| action.lsp_action.title.chars().count())
                 .map(|(ix, _)| ix),
         )
-        .render();
+        .render_into_any();
 
         if self.deployed_from_indicator {
             *cursor_position.column_mut() = 0;
@@ -4354,19 +4355,19 @@ impl Editor {
         style: &EditorStyle,
         is_active: bool,
         cx: &mut ViewContext<Self>,
-    ) -> Option<AnyElement<Self>> {
+    ) -> Option<IconButton<Self>> {
         if self.available_code_actions.is_some() {
             Some(
-                IconButton::new("code_actions_indicator", ui::Icon::Bolt)
-                    .on_click(|editor: &mut Editor, cx| {
+                IconButton::new("code_actions_indicator", ui::Icon::Bolt).on_click(
+                    |editor: &mut Editor, cx| {
                         editor.toggle_code_actions(
                             &ToggleCodeActions {
                                 deployed_from_indicator: true,
                             },
                             cx,
                         );
-                    })
-                    .render(),
+                    },
+                ),
             )
         } else {
             None
@@ -4381,7 +4382,7 @@ impl Editor {
         line_height: Pixels,
         gutter_margin: Pixels,
         cx: &mut ViewContext<Self>,
-    ) -> Vec<Option<AnyElement<Self>>> {
+    ) -> Vec<Option<IconButton<Self>>> {
         fold_data
             .iter()
             .enumerate()
@@ -4403,7 +4404,6 @@ impl Editor {
                                     }
                                 })
                                 .color(ui::TextColor::Muted)
-                                .render()
                         })
                     })
                     .flatten()
@@ -7794,7 +7794,7 @@ impl Editor {
                                                     cx.editor_style.diagnostic_style.clone(),
                                             },
                                         )))
-                                        .render()
+                                        .render_into_any()
                                 }
                             }),
                             disposition: BlockDisposition::Below,
@@ -9383,7 +9383,7 @@ impl FocusableView for Editor {
     }
 }
 
-impl Render for Editor {
+impl Render<Self> for Editor {
     type Element = EditorElement;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@@ -10001,7 +10001,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_into_any()
     })
 }
 

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().into_any()),
             }],
             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, RenderOnce,
+    ScrollWheelEvent, ShapedLine, SharedString, Size, StatefulInteractiveElement, Style, Styled,
     TextRun, TextStyle, View, ViewContext, WindowContext, WrappedLine,
 };
 use itertools::Itertools;
@@ -488,8 +488,9 @@ impl EditorElement {
             }
         }
 
-        for (ix, fold_indicator) in layout.fold_indicators.iter_mut().enumerate() {
-            if let Some(fold_indicator) = fold_indicator.as_mut() {
+        for (ix, fold_indicator) in layout.fold_indicators.drain(..).enumerate() {
+            if let Some(mut fold_indicator) = fold_indicator {
+                let mut fold_indicator = fold_indicator.render_into_any();
                 let available_space = size(
                     AvailableSpace::MinContent,
                     AvailableSpace::Definite(line_height * 0.55),
@@ -509,20 +510,21 @@ impl EditorElement {
             }
         }
 
-        if let Some(indicator) = layout.code_actions_indicator.as_mut() {
+        if let Some(indicator) = layout.code_actions_indicator.take() {
+            let mut button = indicator.button.render_into_any();
             let available_space = size(
                 AvailableSpace::MinContent,
                 AvailableSpace::Definite(line_height),
             );
-            let indicator_size = indicator.element.measure(available_space, editor, cx);
+            let indicator_size = button.measure(available_space, editor, cx);
+
             let mut x = Pixels::ZERO;
             let mut y = indicator.row as f32 * line_height - scroll_top;
             // Center indicator.
             x += ((layout.gutter_padding + layout.gutter_margin) - indicator_size.width) / 2.;
             y += (line_height - indicator_size.height) / 2.;
-            indicator
-                .element
-                .draw(bounds.origin + point(x, y), available_space, editor, cx);
+
+            button.draw(bounds.origin + point(x, y), available_space, editor, cx);
         }
     }
 
@@ -840,7 +842,7 @@ impl EditorElement {
                     }
                 });
 
-                if let Some((position, context_menu)) = layout.context_menu.as_mut() {
+                if let Some((position, mut context_menu)) = layout.context_menu.take() {
                     cx.with_z_index(1, |cx| {
                         let line_height = self.style.text.line_height_in_pixels(cx.rem_size());
                         let available_space = size(
@@ -1224,7 +1226,7 @@ impl EditorElement {
         let scroll_left = scroll_position.x * layout.position_map.em_width;
         let scroll_top = scroll_position.y * layout.position_map.line_height;
 
-        for block in &mut layout.blocks {
+        for block in layout.blocks.drain(..) {
             let mut origin = bounds.origin
                 + point(
                     Pixels::ZERO,
@@ -1810,7 +1812,7 @@ impl EditorElement {
                     .render_code_actions_indicator(&style, active, cx)
                     .map(|element| CodeActionsIndicator {
                         row: newest_selection_head.row(),
-                        element,
+                        button: element,
                     });
             }
         }
@@ -2043,15 +2045,20 @@ impl EditorElement {
                         // Can't use .and_then() because `.file_name()` and `.parent()` return references :(
                         if let Some(path) = path {
                             filename = path.file_name().map(|f| f.to_string_lossy().to_string());
-                            parent_path =
-                                path.parent().map(|p| p.to_string_lossy().to_string() + "/");
+                            parent_path = path
+                                .parent()
+                                .map(|p| SharedString::from(p.to_string_lossy().to_string() + "/"));
                         }
 
                         h_stack()
                             .id("path header block")
                             .size_full()
                             .bg(gpui::red())
-                            .child(filename.unwrap_or_else(|| "untitled".to_string()))
+                            .child(
+                                filename
+                                    .map(SharedString::from)
+                                    .unwrap_or_else(|| "untitled".into()),
+                            )
                             .children(parent_path)
                             .children(jump_icon) // .p_x(gutter_padding)
                     } else {
@@ -2063,7 +2070,7 @@ impl EditorElement {
                             .child("⋯")
                             .children(jump_icon) // .p_x(gutter_padding)
                     };
-                    element.render()
+                    element.into_any()
                 }
             };
 
@@ -2393,18 +2400,14 @@ enum Invisible {
 }
 
 impl Element<Editor> for EditorElement {
-    type ElementState = ();
-
-    fn element_id(&self) -> Option<gpui::ElementId> {
-        Some(self.editor_id.into())
-    }
+    type State = ();
 
     fn layout(
         &mut self,
         editor: &mut Editor,
-        element_state: Option<Self::ElementState>,
+        element_state: Option<Self::State>,
         cx: &mut gpui::ViewContext<Editor>,
-    ) -> (gpui::LayoutId, Self::ElementState) {
+    ) -> (gpui::LayoutId, Self::State) {
         editor.style = Some(self.style.clone()); // Long-term, we'd like to eliminate this.
 
         let rem_size = cx.rem_size();
@@ -2420,10 +2423,10 @@ impl Element<Editor> for EditorElement {
     }
 
     fn paint(
-        &mut self,
+        mut self,
         bounds: Bounds<gpui::Pixels>,
         editor: &mut Editor,
-        element_state: &mut Self::ElementState,
+        element_state: &mut Self::State,
         cx: &mut gpui::ViewContext<Editor>,
     ) {
         let mut layout = self.compute_layout(editor, cx, bounds);
@@ -2470,9 +2473,15 @@ impl Element<Editor> for EditorElement {
     }
 }
 
-impl Component<Editor> for EditorElement {
-    fn render(self) -> AnyElement<Editor> {
-        AnyElement::new(self)
+impl RenderOnce<Editor> for EditorElement {
+    type Element = Self;
+
+    fn element_id(&self) -> Option<gpui::ElementId> {
+        Some(self.editor_id.into())
+    }
+
+    fn render_once(self) -> Self::Element {
+        self
     }
 }
 
@@ -3100,14 +3109,14 @@ pub struct LayoutState {
     context_menu: Option<(DisplayPoint, AnyElement<Editor>)>,
     code_actions_indicator: Option<CodeActionsIndicator>,
     // hover_popovers: Option<(DisplayPoint, Vec<AnyElement<Editor>>)>,
-    fold_indicators: Vec<Option<AnyElement<Editor>>>,
+    fold_indicators: Vec<Option<IconButton<Editor>>>,
     tab_invisible: ShapedLine,
     space_invisible: ShapedLine,
 }
 
 struct CodeActionsIndicator {
     row: u32,
-    element: AnyElement<Editor>,
+    button: IconButton<Editor>,
 }
 
 struct PositionMap {

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, Div, EventEmitter, FocusHandle, FocusableView,
-    InteractiveComponent, Manager, Model, ParentComponent, Render, Styled, Task, View, ViewContext,
+    actions, div, AppContext, Div, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
+    Manager, Model, ParentElement, Render, RenderOnce, Styled, Task, View, ViewContext,
     VisualContext, WeakView,
 };
 use picker::{Picker, PickerDelegate};
@@ -117,7 +117,7 @@ impl FocusableView for FileFinder {
         self.picker.focus_handle(cx)
     }
 }
-impl Render for FileFinder {
+impl Render<Self> for FileFinder {
     type Element = Div<Self>;
 
     fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {

crates/go_to_line2/src/go_to_line.rs 🔗

@@ -1,8 +1,7 @@
 use editor::{display_map::ToDisplayPoint, scroll::autoscroll::Autoscroll, Editor};
 use gpui::{
     actions, div, prelude::*, AppContext, Div, EventEmitter, FocusHandle, FocusableView, Manager,
-    ParentComponent, Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext,
-    WindowContext,
+    Render, SharedString, Styled, Subscription, View, ViewContext, VisualContext, WindowContext,
 };
 use text::{Bias, Point};
 use theme::ActiveTheme;
@@ -145,7 +144,7 @@ impl GoToLine {
     }
 }
 
-impl Render for GoToLine {
+impl Render<Self> for GoToLine {
     type Element = Div<Self>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

crates/gpui2/src/app.rs 🔗

@@ -14,7 +14,7 @@ use smallvec::SmallVec;
 pub use test_context::*;
 
 use crate::{
-    current_platform, image_cache::ImageCache, Action, ActionRegistry, AnyBox, AnyView,
+    current_platform, image_cache::ImageCache, Action, ActionRegistry, Any, AnyView,
     AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context,
     DispatchPhase, DisplayId, Entity, EventEmitter, FocusEvent, FocusHandle, FocusId,
     ForegroundExecutor, KeyBinding, Keymap, LayoutId, PathPromptOptions, Pixels, Platform,
@@ -28,7 +28,7 @@ use futures::{channel::oneshot, future::LocalBoxFuture, Future};
 use parking_lot::Mutex;
 use slotmap::SlotMap;
 use std::{
-    any::{type_name, Any, TypeId},
+    any::{type_name, TypeId},
     cell::{Ref, RefCell, RefMut},
     marker::PhantomData,
     mem,
@@ -194,7 +194,7 @@ pub struct AppContext {
     asset_source: Arc<dyn AssetSource>,
     pub(crate) image_cache: ImageCache,
     pub(crate) text_style_stack: Vec<TextStyleRefinement>,
-    pub(crate) globals_by_type: HashMap<TypeId, AnyBox>,
+    pub(crate) globals_by_type: HashMap<TypeId, Box<dyn Any>>,
     pub(crate) entities: EntityMap,
     pub(crate) new_view_observers: SubscriberSet<TypeId, NewViewListener>,
     pub(crate) windows: SlotMap<WindowId, Option<Window>>,
@@ -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>,
@@ -1104,12 +1104,12 @@ pub(crate) enum Effect {
 
 /// Wraps a global variable value during `update_global` while the value has been moved to the stack.
 pub(crate) struct GlobalLease<G: 'static> {
-    global: AnyBox,
+    global: Box<dyn Any>,
     global_type: PhantomData<G>,
 }
 
 impl<G: 'static> GlobalLease<G> {
-    fn new(global: AnyBox) -> Self {
+    fn new(global: Box<dyn Any>) -> Self {
         GlobalLease {
             global,
             global_type: PhantomData,

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/entity_map.rs 🔗

@@ -1,10 +1,10 @@
-use crate::{private::Sealed, AnyBox, AppContext, Context, Entity, ModelContext};
+use crate::{private::Sealed, AppContext, Context, Entity, ModelContext};
 use anyhow::{anyhow, Result};
 use derive_more::{Deref, DerefMut};
 use parking_lot::{RwLock, RwLockUpgradableReadGuard};
 use slotmap::{SecondaryMap, SlotMap};
 use std::{
-    any::{type_name, TypeId},
+    any::{type_name, Any, TypeId},
     fmt::{self, Display},
     hash::{Hash, Hasher},
     marker::PhantomData,
@@ -31,7 +31,7 @@ impl Display for EntityId {
 }
 
 pub(crate) struct EntityMap {
-    entities: SecondaryMap<EntityId, AnyBox>,
+    entities: SecondaryMap<EntityId, Box<dyn Any>>,
     ref_counts: Arc<RwLock<EntityRefCounts>>,
 }
 
@@ -102,7 +102,7 @@ impl EntityMap {
         );
     }
 
-    pub fn take_dropped(&mut self) -> Vec<(EntityId, AnyBox)> {
+    pub fn take_dropped(&mut self) -> Vec<(EntityId, Box<dyn Any>)> {
         let mut ref_counts = self.ref_counts.write();
         let dropped_entity_ids = mem::take(&mut ref_counts.dropped_entity_ids);
 
@@ -122,7 +122,7 @@ impl EntityMap {
 }
 
 pub struct Lease<'a, T> {
-    entity: Option<AnyBox>,
+    entity: Option<Box<dyn Any>>,
     pub model: &'a Model<T>,
     entity_type: PhantomData<T>,
 }

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

@@ -127,7 +127,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))
@@ -144,7 +144,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));
@@ -568,7 +568,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))
@@ -590,7 +590,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))
@@ -618,7 +618,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,
@@ -629,7 +629,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,27 +3,24 @@ use crate::{
 };
 use derive_more::{Deref, DerefMut};
 pub(crate) use smallvec::SmallVec;
-use std::{any::Any, fmt::Debug, mem};
+use std::{any::Any, fmt::Debug, marker::PhantomData};
 
-pub trait Element<V: 'static> {
-    type ElementState: 'static;
+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 element_id(&self) -> Option<ElementId>;
 
-    fn layout(
-        &mut self,
-        view_state: &mut V,
-        element_state: Option<Self::ElementState>,
-        cx: &mut ViewContext<V>,
-    ) -> (LayoutId, Self::ElementState);
+    fn render_once(self) -> Self::Element;
 
-    fn paint(
-        &mut self,
-        bounds: Bounds<Pixels>,
-        view_state: &mut V,
-        element_state: &mut Self::ElementState,
-        cx: &mut ViewContext<V>,
-    );
+    fn render_into_any(self) -> AnyElement<V> {
+        self.render_once().into_any()
+    }
 
     fn draw<T, R>(
         self,
@@ -31,57 +28,187 @@ pub trait Element<V: 'static> {
         available_space: Size<T>,
         view_state: &mut V,
         cx: &mut ViewContext<V>,
-        f: impl FnOnce(&Self::ElementState, &mut ViewContext<V>) -> R,
+        f: impl FnOnce(&mut <Self::Element as Element<V>>::State, &mut ViewContext<V>) -> R,
     ) -> R
     where
-        Self: Sized,
         T: Clone + Default + Debug + Into<AvailableSpace>,
     {
-        let mut element = RenderedElement {
-            element: self,
-            phase: ElementRenderPhase::Start,
+        let element = self.render_once();
+        let element_id = element.element_id();
+        let element = DrawableElement {
+            element: Some(element),
+            phase: ElementDrawPhase::Start,
         };
-        element.draw(origin, available_space.map(Into::into), view_state, cx);
-        if let ElementRenderPhase::Painted { frame_state } = &element.phase {
-            if let Some(frame_state) = frame_state.as_ref() {
-                f(&frame_state, cx)
+        let frame_state = DrawableElement::draw(
+            element,
+            origin,
+            available_space.map(Into::into),
+            view_state,
+            cx,
+        );
+
+        if let Some(mut frame_state) = frame_state {
+            f(&mut frame_state, cx)
+        } else {
+            cx.with_element_state(element_id.unwrap(), |element_state, cx| {
+                let mut element_state = element_state.unwrap();
+                let result = f(&mut element_state, cx);
+                (result, element_state)
+            })
+        }
+    }
+
+    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 {
-                let element_id = element
-                    .element
-                    .element_id()
-                    .expect("we either have some frame_state or some element_id");
-                cx.with_element_state(element_id, |element_state, cx| {
-                    let element_state = element_state.unwrap();
-                    let result = f(&element_state, cx);
-                    (result, element_state)
-                })
+                this
             }
-        } else {
-            unreachable!()
+        })
+    }
+}
+
+pub trait Element<V: 'static>: 'static + RenderOnce<V> {
+    type State: 'static;
+
+    fn layout(
+        &mut self,
+        view_state: &mut V,
+        element_state: Option<Self::State>,
+        cx: &mut ViewContext<V>,
+    ) -> (LayoutId, Self::State);
+
+    fn paint(
+        self,
+        bounds: Bounds<Pixels>,
+        view_state: &mut V,
+        element_state: &mut Self::State,
+        cx: &mut ViewContext<V>,
+    );
+
+    fn into_any(self) -> AnyElement<V> {
+        AnyElement::new(self)
+    }
+}
+
+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 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 element_id(&self) -> Option<ElementId> {
+        None
+    }
+
+    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 Component<V>) -> Self
+    fn child(mut self, child: impl RenderOnce<V>) -> Self
     where
         Self: Sized,
     {
-        self.children_mut().push(child.render());
+        self.children_mut().push(child.render_once().into_any());
         self
     }
 
-    fn children(mut self, iter: impl IntoIterator<Item = impl Component<V>>) -> Self
+    fn children(mut self, children: impl IntoIterator<Item = impl RenderOnce<V>>) -> Self
     where
         Self: Sized,
     {
-        self.children_mut()
-            .extend(iter.into_iter().map(|item| item.render()));
+        self.children_mut().extend(
+            children
+                .into_iter()
+                .map(|child| child.render_once().into_any()),
+        );
         self
     }
 }
@@ -105,105 +232,103 @@ trait ElementObject<V> {
     );
 }
 
-struct RenderedElement<V: 'static, E: Element<V>> {
-    element: E,
-    phase: ElementRenderPhase<E::ElementState>,
+pub struct DrawableElement<V: 'static, E: Element<V>> {
+    element: Option<E>,
+    phase: ElementDrawPhase<E::State>,
 }
 
 #[derive(Default)]
-enum ElementRenderPhase<V> {
+enum ElementDrawPhase<S> {
     #[default]
     Start,
     LayoutRequested {
         layout_id: LayoutId,
-        frame_state: Option<V>,
+        frame_state: Option<S>,
     },
     LayoutComputed {
         layout_id: LayoutId,
         available_space: Size<AvailableSpace>,
-        frame_state: Option<V>,
-    },
-    Painted {
-        frame_state: Option<V>,
+        frame_state: Option<S>,
     },
 }
 
-/// Internal struct that wraps an element to store Layout and ElementState after the element is rendered.
-/// It's allocated as a trait object to erase the element type and wrapped in AnyElement<E::State> for
-/// improved usability.
-impl<V, E: Element<V>> RenderedElement<V, E> {
+/// A wrapper around an implementer of [Element] that allows it to be drawn in a window.
+impl<V, E: Element<V>> DrawableElement<V, E> {
     fn new(element: E) -> Self {
-        RenderedElement {
-            element,
-            phase: ElementRenderPhase::Start,
+        DrawableElement {
+            element: Some(element),
+            phase: ElementDrawPhase::Start,
         }
     }
-}
 
-impl<V, E> ElementObject<V> for RenderedElement<V, E>
-where
-    E: Element<V>,
-    E::ElementState: 'static,
-{
     fn element_id(&self) -> Option<ElementId> {
-        self.element.element_id()
+        self.element.as_ref()?.element_id()
     }
 
     fn layout(&mut self, state: &mut V, cx: &mut ViewContext<V>) -> LayoutId {
-        let (layout_id, frame_state) = match mem::take(&mut self.phase) {
-            ElementRenderPhase::Start => {
-                if let Some(id) = self.element.element_id() {
-                    let layout_id = cx.with_element_state(id, |element_state, cx| {
-                        self.element.layout(state, element_state, cx)
-                    });
-                    (layout_id, None)
-                } else {
-                    let (layout_id, frame_state) = self.element.layout(state, None, cx);
-                    (layout_id, Some(frame_state))
-                }
-            }
-            ElementRenderPhase::LayoutRequested { .. }
-            | ElementRenderPhase::LayoutComputed { .. }
-            | ElementRenderPhase::Painted { .. } => {
-                panic!("element rendered twice")
-            }
+        let (layout_id, frame_state) = if let Some(id) = self.element.as_ref().unwrap().element_id()
+        {
+            let layout_id = cx.with_element_state(id, |element_state, cx| {
+                self.element
+                    .as_mut()
+                    .unwrap()
+                    .layout(state, element_state, cx)
+            });
+            (layout_id, None)
+        } else {
+            let (layout_id, frame_state) = self.element.as_mut().unwrap().layout(state, None, cx);
+            (layout_id, Some(frame_state))
         };
 
-        self.phase = ElementRenderPhase::LayoutRequested {
+        self.phase = ElementDrawPhase::LayoutRequested {
             layout_id,
             frame_state,
         };
         layout_id
     }
 
-    fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) {
-        self.phase = match mem::take(&mut self.phase) {
-            ElementRenderPhase::LayoutRequested {
+    fn paint(mut self, view_state: &mut V, cx: &mut ViewContext<V>) -> Option<E::State> {
+        match self.phase {
+            ElementDrawPhase::LayoutRequested {
                 layout_id,
-                mut frame_state,
+                frame_state,
             }
-            | ElementRenderPhase::LayoutComputed {
+            | ElementDrawPhase::LayoutComputed {
                 layout_id,
-                mut frame_state,
+                frame_state,
                 ..
             } => {
                 let bounds = cx.layout_bounds(layout_id);
-                if let Some(id) = self.element.element_id() {
-                    cx.with_element_state(id, |element_state, cx| {
+
+                if let Some(mut frame_state) = frame_state {
+                    self.element
+                        .take()
+                        .unwrap()
+                        .paint(bounds, view_state, &mut frame_state, cx);
+                    Some(frame_state)
+                } else {
+                    let element_id = self
+                        .element
+                        .as_ref()
+                        .unwrap()
+                        .element_id()
+                        .expect("if we don't have frame state, we should have element state");
+                    cx.with_element_state(element_id, |element_state, cx| {
                         let mut element_state = element_state.unwrap();
-                        self.element
-                            .paint(bounds, view_state, &mut element_state, cx);
+                        self.element.take().unwrap().paint(
+                            bounds,
+                            view_state,
+                            &mut element_state,
+                            cx,
+                        );
                         ((), element_state)
                     });
-                } else {
-                    self.element
-                        .paint(bounds, view_state, frame_state.as_mut().unwrap(), cx);
+                    None
                 }
-                ElementRenderPhase::Painted { frame_state }
             }
 
             _ => panic!("must call layout before paint"),
-        };
+        }
     }
 
     fn measure(
@@ -212,25 +337,25 @@ where
         view_state: &mut V,
         cx: &mut ViewContext<V>,
     ) -> Size<Pixels> {
-        if matches!(&self.phase, ElementRenderPhase::Start) {
+        if matches!(&self.phase, ElementDrawPhase::Start) {
             self.layout(view_state, cx);
         }
 
         let layout_id = match &mut self.phase {
-            ElementRenderPhase::LayoutRequested {
+            ElementDrawPhase::LayoutRequested {
                 layout_id,
                 frame_state,
             } => {
                 cx.compute_layout(*layout_id, available_space);
                 let layout_id = *layout_id;
-                self.phase = ElementRenderPhase::LayoutComputed {
+                self.phase = ElementDrawPhase::LayoutComputed {
                     layout_id,
                     available_space,
                     frame_state: frame_state.take(),
                 };
                 layout_id
             }
-            ElementRenderPhase::LayoutComputed {
+            ElementDrawPhase::LayoutComputed {
                 layout_id,
                 available_space: prev_available_space,
                 ..
@@ -248,27 +373,105 @@ where
     }
 
     fn draw(
-        &mut self,
+        mut self,
         origin: Point<Pixels>,
         available_space: Size<AvailableSpace>,
         view_state: &mut V,
         cx: &mut ViewContext<V>,
-    ) {
+    ) -> Option<E::State> {
         self.measure(available_space, view_state, cx);
         cx.with_absolute_element_offset(origin, |cx| self.paint(view_state, cx))
     }
 }
 
+// impl<V: 'static, E: Element<V>> Element<V> for DrawableElement<V, E> {
+//     type State = <E::Element as Element<V>>::State;
+
+//     fn layout(
+//         &mut self,
+//         view_state: &mut V,
+//         element_state: Option<Self::State>,
+//         cx: &mut ViewContext<V>,
+//     ) -> (LayoutId, Self::State) {
+
+//     }
+
+//     fn paint(
+//         self,
+//         bounds: Bounds<Pixels>,
+//         view_state: &mut V,
+//         element_state: &mut Self::State,
+//         cx: &mut ViewContext<V>,
+//     ) {
+//         todo!()
+//     }
+// }
+
+// impl<V: 'static, E: 'static + Element<V>> RenderOnce<V> for DrawableElement<V, E> {
+//     type Element = Self;
+
+//     fn element_id(&self) -> Option<ElementId> {
+//         self.element.as_ref()?.element_id()
+//     }
+
+//     fn render_once(self) -> Self::Element {
+//         self
+//     }
+// }
+
+impl<V, E> ElementObject<V> for Option<DrawableElement<V, E>>
+where
+    E: Element<V>,
+    E::State: 'static,
+{
+    fn element_id(&self) -> Option<ElementId> {
+        self.as_ref().unwrap().element_id()
+    }
+
+    fn layout(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) -> LayoutId {
+        DrawableElement::layout(self.as_mut().unwrap(), view_state, cx)
+    }
+
+    fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) {
+        DrawableElement::paint(self.take().unwrap(), view_state, cx);
+    }
+
+    fn measure(
+        &mut self,
+        available_space: Size<AvailableSpace>,
+        view_state: &mut V,
+        cx: &mut ViewContext<V>,
+    ) -> Size<Pixels> {
+        DrawableElement::measure(self.as_mut().unwrap(), available_space, view_state, cx)
+    }
+
+    fn draw(
+        &mut self,
+        origin: Point<Pixels>,
+        available_space: Size<AvailableSpace>,
+        view_state: &mut V,
+        cx: &mut ViewContext<V>,
+    ) {
+        DrawableElement::draw(
+            self.take().unwrap(),
+            origin,
+            available_space,
+            view_state,
+            cx,
+        );
+    }
+}
+
 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,
         E: 'static + Element<V>,
-        E::ElementState: Any,
+        E::State: Any,
     {
-        AnyElement(Box::new(RenderedElement::new(element)))
+        AnyElement(Box::new(Some(DrawableElement::new(element))) as Box<dyn ElementObject<V>>)
     }
 
     pub fn element_id(&self) -> Option<ElementId> {
@@ -279,7 +482,7 @@ impl<V> AnyElement<V> {
         self.0.layout(view_state, cx)
     }
 
-    pub fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) {
+    pub fn paint(mut self, view_state: &mut V, cx: &mut ViewContext<V>) {
         self.0.paint(view_state, cx)
     }
 
@@ -295,7 +498,7 @@ impl<V> AnyElement<V> {
 
     /// Initializes this element and performs layout in the available space, then paints it at the given origin.
     pub fn draw(
-        &mut self,
+        mut self,
         origin: Point<Pixels>,
         available_space: Size<AvailableSpace>,
         view_state: &mut V,
@@ -303,99 +506,93 @@ impl<V> AnyElement<V> {
     ) {
         self.0.draw(origin, available_space, view_state, cx)
     }
-}
 
-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)
-    }
-
-    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> {
-        self
+    /// 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, E, F> Element<V> for Option<F>
-where
-    V: 'static,
-    E: 'static + Component<V>,
-    F: FnOnce(&mut V, &mut ViewContext<'_, V>) -> E + 'static,
-{
-    type ElementState = AnyElement<V>;
-
-    fn element_id(&self) -> Option<ElementId> {
-        None
-    }
+impl<V: 'static> Element<V> for AnyElement<V> {
+    type State = ();
 
     fn layout(
         &mut self,
         view_state: &mut V,
-        _: Option<Self::ElementState>,
+        _: Option<Self::State>,
         cx: &mut ViewContext<V>,
-    ) -> (LayoutId, Self::ElementState) {
-        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, rendered_element)
+    ) -> (LayoutId, Self::State) {
+        let layout_id = self.layout(view_state, cx);
+        (layout_id, ())
     }
 
     fn paint(
-        &mut self,
+        self,
         _bounds: Bounds<Pixels>,
         view_state: &mut V,
-        rendered_element: &mut Self::ElementState,
+        _: &mut Self::State,
         cx: &mut ViewContext<V>,
     ) {
-        rendered_element.paint(view_state, cx)
+        self.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: 'static> RenderOnce<V> for AnyElement<V> {
+    type Element = Self;
+
+    fn element_id(&self) -> Option<ElementId> {
+        AnyElement::element_id(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))
+    fn render_once(self) -> Self::Element {
+        self
     }
 }
+
+// impl<V, E, F> Element<V> for Option<F>
+// where
+//     V: 'static,
+//     E: Element<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 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>,
+//         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> 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,31 +587,27 @@ 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
     }
 }
 
 impl<V: 'static> Element<V> for Div<V> {
-    type ElementState = DivState;
-
-    fn element_id(&self) -> Option<ElementId> {
-        self.interactivity.element_id.clone()
-    }
+    type State = DivState;
 
     fn layout(
         &mut self,
         view_state: &mut V,
-        element_state: Option<Self::ElementState>,
+        element_state: Option<Self::State>,
         cx: &mut ViewContext<V>,
-    ) -> (LayoutId, Self::ElementState) {
+    ) -> (LayoutId, Self::State) {
         let mut child_layout_ids = SmallVec::new();
         let mut interactivity = mem::take(&mut self.interactivity);
         let (layout_id, interactive_state) = interactivity.layout(
@@ -639,10 +635,10 @@ impl<V: 'static> Element<V> for Div<V> {
     }
 
     fn paint(
-        &mut self,
+        self,
         bounds: Bounds<Pixels>,
         view_state: &mut V,
-        element_state: &mut Self::ElementState,
+        element_state: &mut Self::State,
         cx: &mut ViewContext<V>,
     ) {
         let mut child_min = point(Pixels::MAX, Pixels::MAX);
@@ -658,8 +654,7 @@ impl<V: 'static> Element<V> for Div<V> {
             (child_max - child_min).into()
         };
 
-        let mut interactivity = mem::take(&mut self.interactivity);
-        interactivity.paint(
+        self.interactivity.paint(
             bounds,
             content_size,
             &mut element_state.interactive_state,
@@ -679,7 +674,7 @@ impl<V: 'static> Element<V> for Div<V> {
                         cx.with_text_style(style.text_style().cloned(), |cx| {
                             cx.with_content_mask(style.overflow_mask(bounds), |cx| {
                                 cx.with_element_offset(scroll_offset, |cx| {
-                                    for child in &mut self.children {
+                                    for child in self.children {
                                         child.paint(view_state, cx);
                                     }
                                 })
@@ -689,13 +684,18 @@ impl<V: 'static> Element<V> for Div<V> {
                 })
             },
         );
-        self.interactivity = interactivity;
     }
 }
 
-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 element_id(&self) -> Option<ElementId> {
+        self.interactivity.element_id.clone()
+    }
+
+    fn render_once(self) -> Self::Element {
+        self
     }
 }
 
@@ -770,7 +770,7 @@ where
     }
 
     pub fn paint(
-        &mut self,
+        mut self,
         bounds: Bounds<Pixels>,
         content_size: Size<Pixels>,
         element_state: &mut InteractiveElementState,
@@ -786,25 +786,25 @@ where
             }
         }
 
-        for listener in self.mouse_down_listeners.drain(..) {
+        for listener in self.mouse_down_listeners {
             cx.on_mouse_event(move |state, event: &MouseDownEvent, phase, cx| {
                 listener(state, event, &bounds, phase, cx);
             })
         }
 
-        for listener in self.mouse_up_listeners.drain(..) {
+        for listener in self.mouse_up_listeners {
             cx.on_mouse_event(move |state, event: &MouseUpEvent, phase, cx| {
                 listener(state, event, &bounds, phase, cx);
             })
         }
 
-        for listener in self.mouse_move_listeners.drain(..) {
+        for listener in self.mouse_move_listeners {
             cx.on_mouse_event(move |state, event: &MouseMoveEvent, phase, cx| {
                 listener(state, event, &bounds, phase, cx);
             })
         }
 
-        for listener in self.scroll_wheel_listeners.drain(..) {
+        for listener in self.scroll_wheel_listeners {
             cx.on_mouse_event(move |state, event: &ScrollWheelEvent, phase, cx| {
                 listener(state, event, &bounds, phase, cx);
             })
@@ -840,7 +840,7 @@ where
         }
 
         if cx.active_drag.is_some() {
-            let drop_listeners = mem::take(&mut self.drop_listeners);
+            let drop_listeners = self.drop_listeners;
             cx.on_mouse_event(move |view, event: &MouseUpEvent, phase, cx| {
                 if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
                     if let Some(drag_state_type) =
@@ -1062,24 +1062,24 @@ where
             self.key_context.clone(),
             element_state.focus_handle.clone(),
             |_, cx| {
-                for listener in self.key_down_listeners.drain(..) {
+                for listener in self.key_down_listeners {
                     cx.on_key_event(move |state, event: &KeyDownEvent, phase, cx| {
                         listener(state, event, phase, cx);
                     })
                 }
 
-                for listener in self.key_up_listeners.drain(..) {
+                for listener in self.key_up_listeners {
                     cx.on_key_event(move |state, event: &KeyUpEvent, phase, cx| {
                         listener(state, event, phase, cx);
                     })
                 }
 
-                for (action_type, listener) in self.action_listeners.drain(..) {
+                for (action_type, listener) in self.action_listeners {
                     cx.on_action(action_type, listener)
                 }
 
                 if let Some(focus_handle) = element_state.focus_handle.as_ref() {
-                    for listener in self.focus_listeners.drain(..) {
+                    for listener in self.focus_listeners {
                         let focus_handle = focus_handle.clone();
                         cx.on_focus_changed(move |view, event, cx| {
                             listener(view, &focus_handle, event, cx)
@@ -1264,19 +1264,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>
 {
 }
@@ -1293,49 +1293,51 @@ where
 
 impl<V, E> Element<V> for Focusable<V, E>
 where
-    V: 'static,
+    V: 'static + Render<V>,
     E: Element<V>,
 {
-    type ElementState = E::ElementState;
-
-    fn element_id(&self) -> Option<ElementId> {
-        self.element.element_id()
-    }
+    type State = E::State;
 
     fn layout(
         &mut self,
         view_state: &mut V,
-        element_state: Option<Self::ElementState>,
+        element_state: Option<Self::State>,
         cx: &mut ViewContext<V>,
-    ) -> (LayoutId, Self::ElementState) {
+    ) -> (LayoutId, Self::State) {
         self.element.layout(view_state, element_state, cx)
     }
 
     fn paint(
-        &mut self,
+        self,
         bounds: Bounds<Pixels>,
         view_state: &mut V,
-        element_state: &mut Self::ElementState,
+        element_state: &mut Self::State,
         cx: &mut ViewContext<V>,
     ) {
-        self.element.paint(bounds, view_state, element_state, cx);
+        self.element.paint(bounds, view_state, element_state, cx)
     }
 }
 
-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 element_id(&self) -> Option<ElementId> {
+        self.element.element_id()
+    }
+
+    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()
@@ -1357,71 +1359,73 @@ 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
     V: 'static,
     E: Element<V>,
 {
-    type ElementState = E::ElementState;
-
-    fn element_id(&self) -> Option<ElementId> {
-        self.element.element_id()
-    }
+    type State = E::State;
 
     fn layout(
         &mut self,
         view_state: &mut V,
-        element_state: Option<Self::ElementState>,
+        element_state: Option<Self::State>,
         cx: &mut ViewContext<V>,
-    ) -> (LayoutId, Self::ElementState) {
+    ) -> (LayoutId, Self::State) {
         self.element.layout(view_state, element_state, cx)
     }
 
     fn paint(
-        &mut self,
+        self,
         bounds: Bounds<Pixels>,
         view_state: &mut V,
-        element_state: &mut Self::ElementState,
+        element_state: &mut Self::State,
         cx: &mut ViewContext<V>,
     ) {
         self.element.paint(bounds, view_state, element_state, cx)
     }
 }
 
-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 element_id(&self) -> Option<ElementId> {
+        self.element.element_id()
+    }
+
+    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,35 +34,25 @@ where
     }
 }
 
-impl<V> Component<V> for Img<V> {
-    fn render(self) -> AnyElement<V> {
-        AnyElement::new(self)
-    }
-}
-
 impl<V> Element<V> for Img<V> {
-    type ElementState = InteractiveElementState;
-
-    fn element_id(&self) -> Option<crate::ElementId> {
-        self.interactivity.element_id.clone()
-    }
+    type State = InteractiveElementState;
 
     fn layout(
         &mut self,
         _view_state: &mut V,
-        element_state: Option<Self::ElementState>,
+        element_state: Option<Self::State>,
         cx: &mut ViewContext<V>,
-    ) -> (LayoutId, Self::ElementState) {
+    ) -> (LayoutId, Self::State) {
         self.interactivity.layout(element_state, cx, |style, cx| {
             cx.request_layout(&style, None)
         })
     }
 
     fn paint(
-        &mut self,
+        self,
         bounds: Bounds<Pixels>,
         _view_state: &mut V,
-        element_state: &mut Self::ElementState,
+        element_state: &mut Self::State,
         cx: &mut ViewContext<V>,
     ) {
         self.interactivity.paint(
@@ -102,13 +91,25 @@ impl<V> Element<V> for Img<V> {
     }
 }
 
+impl<V: 'static> RenderOnce<V> for Img<V> {
+    type Element = Self;
+
+    fn element_id(&self) -> Option<crate::ElementId> {
+        self.interactivity.element_id.clone()
+    }
+
+    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,31 +51,21 @@ 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 ElementState = OverlayState;
-
-    fn element_id(&self) -> Option<crate::ElementId> {
-        None
-    }
+    type State = OverlayState;
 
     fn layout(
         &mut self,
         view_state: &mut V,
-        _: Option<Self::ElementState>,
+        _: Option<Self::State>,
         cx: &mut crate::ViewContext<V>,
-    ) -> (crate::LayoutId, Self::ElementState) {
+    ) -> (crate::LayoutId, Self::State) {
         let child_layout_ids = self
             .children
             .iter_mut()
@@ -92,10 +82,10 @@ impl<V: 'static> Element<V> for Overlay<V> {
     }
 
     fn paint(
-        &mut self,
+        self,
         bounds: crate::Bounds<crate::Pixels>,
         view_state: &mut V,
-        element_state: &mut Self::ElementState,
+        element_state: &mut Self::State,
         cx: &mut crate::ViewContext<V>,
     ) {
         if element_state.child_layout_ids.is_empty() {
@@ -156,13 +146,25 @@ impl<V: 'static> Element<V> for Overlay<V> {
         }
 
         cx.with_element_offset(desired.origin - bounds.origin, |cx| {
-            for child in &mut self.children {
+            for child in self.children {
                 child.paint(view_state, cx);
             }
         })
     }
 }
 
+impl<V: 'static> RenderOnce<V> for Overlay<V> {
+    type Element = Self;
+
+    fn element_id(&self) -> Option<crate::ElementId> {
+        None
+    }
+
+    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,35 +23,25 @@ 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 ElementState = InteractiveElementState;
-
-    fn element_id(&self) -> Option<ElementId> {
-        self.interactivity.element_id.clone()
-    }
+    type State = InteractiveElementState;
 
     fn layout(
         &mut self,
         _view_state: &mut V,
-        element_state: Option<Self::ElementState>,
+        element_state: Option<Self::State>,
         cx: &mut ViewContext<V>,
-    ) -> (LayoutId, Self::ElementState) {
+    ) -> (LayoutId, Self::State) {
         self.interactivity.layout(element_state, cx, |style, cx| {
             cx.request_layout(&style, None)
         })
     }
 
     fn paint(
-        &mut self,
+        self,
         bounds: Bounds<Pixels>,
         _view_state: &mut V,
-        element_state: &mut Self::ElementState,
+        element_state: &mut Self::State,
         cx: &mut ViewContext<V>,
     ) where
         Self: Sized,
@@ -66,13 +55,25 @@ impl<V> Element<V> for Svg<V> {
     }
 }
 
+impl<V: 'static> RenderOnce<V> for Svg<V> {
+    type Element = Self;
+
+    fn element_id(&self) -> Option<ElementId> {
+        self.interactivity.element_id.clone()
+    }
+
+    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,50 +1,116 @@
 use crate::{
-    AnyElement, BorrowWindow, Bounds, Component, Element, ElementId, LayoutId, Pixels,
-    SharedString, Size, TextRun, ViewContext, WrappedLine,
+    BorrowWindow, Bounds, Element, ElementId, LayoutId, Pixels, RenderOnce, SharedString, Size,
+    TextRun, ViewContext, WindowContext, WrappedLine,
 };
+use anyhow::anyhow;
 use parking_lot::{Mutex, MutexGuard};
 use smallvec::SmallVec;
 use std::{cell::Cell, rc::Rc, sync::Arc};
 use util::ResultExt;
 
-pub struct Text {
+impl<V: 'static> Element<V> for &'static str {
+    type State = TextState;
+
+    fn layout(
+        &mut self,
+        _: &mut V,
+        _: Option<Self::State>,
+        cx: &mut ViewContext<V>,
+    ) -> (LayoutId, Self::State) {
+        let mut state = TextState::default();
+        let layout_id = state.layout(SharedString::from(*self), None, cx);
+        (layout_id, state)
+    }
+
+    fn paint(
+        self,
+        bounds: Bounds<Pixels>,
+        _: &mut V,
+        state: &mut TextState,
+        cx: &mut ViewContext<V>,
+    ) {
+        state.paint(bounds, self, cx)
+    }
+}
+
+impl<V: 'static> RenderOnce<V> for &'static str {
+    type Element = Self;
+
+    fn element_id(&self) -> Option<ElementId> {
+        None
+    }
+
+    fn render_once(self) -> Self::Element {
+        self
+    }
+}
+
+impl<V: 'static> Element<V> for SharedString {
+    type State = TextState;
+
+    fn layout(
+        &mut self,
+        _: &mut V,
+        _: Option<Self::State>,
+        cx: &mut ViewContext<V>,
+    ) -> (LayoutId, Self::State) {
+        let mut state = TextState::default();
+        let layout_id = state.layout(self.clone(), None, cx);
+        (layout_id, state)
+    }
+
+    fn paint(
+        self,
+        bounds: Bounds<Pixels>,
+        _: &mut V,
+        state: &mut TextState,
+        cx: &mut ViewContext<V>,
+    ) {
+        let text_str: &str = self.as_ref();
+        state.paint(bounds, text_str, cx)
+    }
+}
+
+impl<V: 'static> RenderOnce<V> for SharedString {
+    type Element = Self;
+
+    fn element_id(&self) -> Option<ElementId> {
+        Some(self.clone().into())
+    }
+
+    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 {
-    type ElementState = TextState;
-
-    fn element_id(&self) -> Option<crate::ElementId> {
-        None
-    }
+impl<V: 'static> Element<V> for StyledText {
+    type State = TextState;
 
     fn layout(
         &mut self,
         _view: &mut V,
-        element_state: Option<Self::ElementState>,
+        element_state: Option<Self::State>,
         cx: &mut ViewContext<V>,
-    ) -> (LayoutId, Self::ElementState) {
+    ) -> (LayoutId, Self::State) {
         let element_state = element_state.unwrap_or_default();
         let text_system = cx.text_system().clone();
         let text_style = cx.text_style();
@@ -118,16 +184,16 @@ impl<V: 'static> Element<V> for Text {
     }
 
     fn paint(
-        &mut self,
+        self,
         bounds: Bounds<Pixels>,
         _: &mut V,
-        element_state: &mut Self::ElementState,
+        element_state: &mut Self::State,
         cx: &mut ViewContext<V>,
     ) {
         let element_state = element_state.lock();
         let element_state = element_state
             .as_ref()
-            .ok_or_else(|| anyhow::anyhow!("measurement has not been performed on {}", &self.text))
+            .ok_or_else(|| anyhow!("measurement has not been performed on {}", &self.text))
             .unwrap();
 
         let line_height = element_state.line_height;
@@ -139,15 +205,21 @@ impl<V: 'static> Element<V> for Text {
     }
 }
 
-#[derive(Default, Clone)]
-pub struct TextState(Arc<Mutex<Option<TextStateInner>>>);
+impl<V: 'static> RenderOnce<V> for StyledText {
+    type Element = Self;
 
-impl TextState {
-    fn lock(&self) -> MutexGuard<Option<TextStateInner>> {
-        self.0.lock()
+    fn element_id(&self) -> Option<crate::ElementId> {
+        None
+    }
+
+    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,
@@ -155,9 +227,108 @@ struct TextStateInner {
     size: Option<Size<Pixels>>,
 }
 
+impl TextState {
+    fn lock(&self) -> MutexGuard<Option<TextStateInner>> {
+        self.0.lock()
+    }
+
+    fn layout(
+        &mut self,
+        text: SharedString,
+        runs: Option<Vec<TextRun>>,
+        cx: &mut WindowContext,
+    ) -> LayoutId {
+        let text_system = cx.text_system().clone();
+        let text_style = cx.text_style();
+        let font_size = text_style.font_size.to_pixels(cx.rem_size());
+        let line_height = text_style
+            .line_height
+            .to_pixels(font_size.into(), cx.rem_size());
+        let text = SharedString::from(text);
+
+        let rem_size = cx.rem_size();
+
+        let runs = if let Some(runs) = runs {
+            runs
+        } else {
+            vec![text_style.to_run(text.len())]
+        };
+
+        let layout_id = cx.request_measured_layout(Default::default(), rem_size, {
+            let element_state = self.clone();
+
+            move |known_dimensions, available_space| {
+                let wrap_width = known_dimensions.width.or(match available_space.width {
+                    crate::AvailableSpace::Definite(x) => Some(x),
+                    _ => None,
+                });
+
+                if let Some(text_state) = element_state.0.lock().as_ref() {
+                    if text_state.size.is_some()
+                        && (wrap_width.is_none() || wrap_width == text_state.wrap_width)
+                    {
+                        return text_state.size.unwrap();
+                    }
+                }
+
+                let Some(lines) = text_system
+                    .shape_text(
+                        &text,
+                        font_size,
+                        &runs[..],
+                        wrap_width, // Wrap if we know the width.
+                    )
+                    .log_err()
+                else {
+                    element_state.lock().replace(TextStateInner {
+                        lines: Default::default(),
+                        line_height,
+                        wrap_width,
+                        size: Some(Size::default()),
+                    });
+                    return Size::default();
+                };
+
+                let mut size: Size<Pixels> = Size::default();
+                for line in &lines {
+                    let line_size = line.size(line_height);
+                    size.height += line_size.height;
+                    size.width = size.width.max(line_size.width);
+                }
+
+                element_state.lock().replace(TextStateInner {
+                    lines,
+                    line_height,
+                    wrap_width,
+                    size: Some(size),
+                });
+
+                size
+            }
+        });
+
+        layout_id
+    }
+
+    fn paint(&mut self, bounds: Bounds<Pixels>, text: &str, cx: &mut WindowContext) {
+        let element_state = self.lock();
+        let element_state = element_state
+            .as_ref()
+            .ok_or_else(|| anyhow!("measurement has not been performed on {}", text))
+            .unwrap();
+
+        let line_height = element_state.line_height;
+        let mut line_origin = bounds.origin;
+        for line in &element_state.lines {
+            line.paint(line_origin, line_height, cx).log_err();
+            line_origin.y += line.size(line_height).height;
+        }
+    }
+}
+
 struct InteractiveText {
     id: ElementId,
-    text: Text,
+    text: StyledText,
 }
 
 struct InteractiveTextState {
@@ -166,18 +337,14 @@ struct InteractiveTextState {
 }
 
 impl<V: 'static> Element<V> for InteractiveText {
-    type ElementState = InteractiveTextState;
-
-    fn element_id(&self) -> Option<ElementId> {
-        Some(self.id.clone())
-    }
+    type State = InteractiveTextState;
 
     fn layout(
         &mut self,
         view_state: &mut V,
-        element_state: Option<Self::ElementState>,
+        element_state: Option<Self::State>,
         cx: &mut ViewContext<V>,
-    ) -> (LayoutId, Self::ElementState) {
+    ) -> (LayoutId, Self::State) {
         if let Some(InteractiveTextState {
             text_state,
             clicked_range_ixs,
@@ -200,10 +367,10 @@ impl<V: 'static> Element<V> for InteractiveText {
     }
 
     fn paint(
-        &mut self,
+        self,
         bounds: Bounds<Pixels>,
         view_state: &mut V,
-        element_state: &mut Self::ElementState,
+        element_state: &mut Self::State,
         cx: &mut ViewContext<V>,
     ) {
         self.text
@@ -211,34 +378,14 @@ 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> RenderOnce<V> for InteractiveText {
+    type Element = Self;
 
-impl<V: 'static> Component<V> for &'static str {
-    fn render(self) -> AnyElement<V> {
-        Text {
-            text: self.into(),
-            runs: None,
-        }
-        .render()
+    fn element_id(&self) -> Option<ElementId> {
+        Some(self.id.clone())
     }
-}
 
-// 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,24 +1,24 @@
 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, mem, ops::Range, rc::Rc};
+use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
 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 {
@@ -102,18 +102,14 @@ pub struct UniformListState {
 }
 
 impl<V: 'static> Element<V> for UniformList<V> {
-    type ElementState = UniformListState;
-
-    fn element_id(&self) -> Option<crate::ElementId> {
-        Some(self.id.clone())
-    }
+    type State = UniformListState;
 
     fn layout(
         &mut self,
         view_state: &mut V,
-        element_state: Option<Self::ElementState>,
+        element_state: Option<Self::State>,
         cx: &mut ViewContext<V>,
-    ) -> (LayoutId, Self::ElementState) {
+    ) -> (LayoutId, Self::State) {
         let max_items = self.item_count;
         let rem_size = cx.rem_size();
         let item_size = element_state
@@ -159,10 +155,10 @@ impl<V: 'static> Element<V> for UniformList<V> {
     }
 
     fn paint(
-        &mut self,
+        self,
         bounds: Bounds<crate::Pixels>,
         view_state: &mut V,
-        element_state: &mut Self::ElementState,
+        element_state: &mut Self::State,
         cx: &mut ViewContext<V>,
     ) {
         let style =
@@ -183,14 +179,17 @@ impl<V: 'static> Element<V> for UniformList<V> {
             height: item_size.height * self.item_count,
         };
 
-        let mut interactivity = mem::take(&mut self.interactivity);
         let shared_scroll_offset = element_state
             .interactive
             .scroll_offset
             .get_or_insert_with(Rc::default)
             .clone();
 
-        interactivity.paint(
+        let item_height = self
+            .measure_item(view_state, Some(padded_bounds.size.width), cx)
+            .height;
+
+        self.interactivity.paint(
             bounds,
             content_size,
             &mut element_state.interactive,
@@ -209,9 +208,6 @@ impl<V: 'static> Element<V> for UniformList<V> {
                     style.paint(bounds, cx);
 
                     if self.item_count > 0 {
-                        let item_height = self
-                            .measure_item(view_state, Some(padded_bounds.size.width), cx)
-                            .height;
                         if let Some(scroll_handle) = self.scroll_handle.clone() {
                             scroll_handle.0.borrow_mut().replace(ScrollHandleState {
                                 item_height,
@@ -233,9 +229,9 @@ impl<V: 'static> Element<V> for UniformList<V> {
                                 self.item_count,
                             );
 
-                        let mut items = (self.render_items)(view_state, visible_range.clone(), cx);
+                        let items = (self.render_items)(view_state, visible_range.clone(), cx);
                         cx.with_z_index(1, |cx| {
-                            for (item, ix) in items.iter_mut().zip(visible_range) {
+                            for (item, ix) in items.into_iter().zip(visible_range) {
                                 let item_origin = padded_bounds.origin
                                     + point(px(0.), item_height * ix + scroll_offset.y);
                                 let available_space = size(
@@ -249,7 +245,18 @@ impl<V: 'static> Element<V> for UniformList<V> {
                 })
             },
         );
-        self.interactivity = interactivity;
+    }
+}
+
+impl<V> RenderOnce<V> for UniformList<V> {
+    type Element = Self;
+
+    fn element_id(&self) -> Option<crate::ElementId> {
+        Some(self.id.clone())
+    }
+
+    fn render_once(self) -> Self::Element {
+        self
     }
 }
 
@@ -287,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 🔗

@@ -78,8 +78,6 @@ use std::{
 };
 use taffy::TaffyLayoutEngine;
 
-type AnyBox = Box<dyn Any>;
-
 pub trait Context {
     type Result<T>;
 
@@ -123,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,
@@ -136,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,6 +1,5 @@
 use crate::{
-    div, point, Component, Div, FocusHandle, Keystroke, Modifiers, Pixels, Point, Render,
-    ViewContext,
+    div, point, Div, Element, FocusHandle, Keystroke, Modifiers, Pixels, Point, Render, ViewContext,
 };
 use smallvec::SmallVec;
 use std::{any::Any, fmt::Debug, marker::PhantomData, ops::Deref, path::PathBuf};
@@ -64,7 +63,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 +74,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 +192,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 +285,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, Render, Stateful, TestAppContext, VisualContext,
     };
 
     struct TestView {
@@ -298,7 +297,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 {
@@ -307,12 +306,7 @@ mod test {
                     .key_context("parent")
                     .on_key_down(|this: &mut TestView, _, _, _| this.saw_key_down = true)
                     .on_action(|this: &mut TestView, _: &TestAction, _| this.saw_action = true)
-                    .child(|this: &mut Self, _cx: &mut ViewContext<Self>| {
-                        div()
-                            .key_context("nested")
-                            .track_focus(&this.focus_handle)
-                            .render()
-                    }),
+                    .child(div().key_context("nested").track_focus(&self.focus_handle)),
             )
         }
     }

crates/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,7 @@
 use crate::{
-    private::Sealed, AnyBox, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace,
-    BorrowWindow, Bounds, Component, Element, ElementId, Entity, EntityId, Flatten, FocusHandle,
-    FocusableView, LayoutId, Model, Pixels, Point, Size, ViewContext, VisualContext, WeakModel,
+    private::Sealed, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, BorrowWindow,
+    Bounds, Element, ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView, LayoutId,
+    Model, Pixels, Point, Render, RenderOnce, Size, ViewContext, VisualContext, WeakModel,
     WindowContext,
 };
 use anyhow::{Context, Result};
@@ -10,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> {}
@@ -65,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),
         }
     }
 
@@ -105,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>,
 }
@@ -164,7 +152,7 @@ impl<V> Eq for WeakView<V> {}
 pub struct AnyView {
     model: AnyModel,
     layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, Box<dyn Any>),
-    paint: fn(&AnyView, &mut AnyBox, &mut WindowContext),
+    paint: fn(&AnyView, Box<dyn Any>, &mut WindowContext),
 }
 
 impl AnyView {
@@ -202,22 +190,16 @@ impl AnyView {
         cx: &mut WindowContext,
     ) {
         cx.with_absolute_element_offset(origin, |cx| {
-            let (layout_id, mut rendered_element) = (self.layout)(self, cx);
+            let (layout_id, rendered_element) = (self.layout)(self, cx);
             cx.window
                 .layout_engine
                 .compute_layout(layout_id, available_space);
-            (self.paint)(self, &mut rendered_element, cx);
+            (self.paint)(self, rendered_element, cx);
         })
     }
 }
 
-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(),
@@ -227,37 +209,87 @@ impl<V: Render> From<View<V>> for AnyView {
     }
 }
 
-impl<ParentViewState: 'static> Element<ParentViewState> for AnyView {
-    type ElementState = Box<dyn Any>;
+impl<V: 'static + Render<V>, ParentV: 'static> Element<ParentV> for View<V> {
+    type State = Option<AnyElement<V>>;
+
+    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 element_id(&self) -> Option<ElementId> {
         Some(self.model.entity_id.into())
     }
 
+    fn render_once(self) -> Self::Element {
+        self
+    }
+}
+
+impl<V: 'static> Element<V> for AnyView {
+    type State = Option<Box<dyn Any>>;
+
     fn layout(
         &mut self,
-        _view_state: &mut ParentViewState,
-        _element_state: Option<Self::ElementState>,
-        cx: &mut ViewContext<ParentViewState>,
-    ) -> (LayoutId, Self::ElementState) {
-        (self.layout)(self, cx)
+        _view_state: &mut V,
+        _element_state: Option<Self::State>,
+        cx: &mut ViewContext<V>,
+    ) -> (LayoutId, Self::State) {
+        let (layout_id, rendered_element) = (self.layout)(self, cx);
+        (layout_id, Some(rendered_element))
     }
 
     fn paint(
-        &mut self,
+        mut self,
         _bounds: Bounds<Pixels>,
-        _view_state: &mut ParentViewState,
-        rendered_element: &mut Self::ElementState,
-        cx: &mut ViewContext<ParentViewState>,
+        _view_state: &mut V,
+        rendered_element: &mut Self::State,
+        cx: &mut ViewContext<V>,
     ) {
-        (self.paint)(self, rendered_element, cx)
+        (self.paint)(&mut self, rendered_element.take().unwrap(), cx)
+    }
+}
+
+impl<ParentV: 'static> RenderOnce<ParentV> for AnyView {
+    type Element = Self;
+
+    fn element_id(&self) -> Option<ElementId> {
+        Some(self.model.entity_id.into())
+    }
+
+    fn render_once(self) -> Self::Element {
+        self
     }
 }
 
 pub struct AnyWeakView {
     model: AnyWeakModel,
     layout: fn(&AnyView, &mut WindowContext) -> (LayoutId, Box<dyn Any>),
-    paint: fn(&AnyView, &mut AnyBox, &mut WindowContext),
+    paint: fn(&AnyView, Box<dyn Any>, &mut WindowContext),
 }
 
 impl AnyWeakView {
@@ -271,7 +303,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(),
@@ -281,67 +313,58 @@ impl<V: Render> From<WeakView<V>> for AnyWeakView {
     }
 }
 
-// impl<T, E> Render for T
-// where
-//     T: 'static + FnMut(&mut WindowContext) -> E,
-//     E: 'static + Send + Element<T>,
-// {
-//     type Element = E;
-
-//     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-//         (self)(cx)
-//     }
-// }
-
-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 ElementState = AnyElement<ViewState>;
-
-    fn element_id(&self) -> Option<ElementId> {
-        Some(self.view.entity_id().into())
-    }
+    type State = Option<AnyElement<V>>;
 
     fn layout(
         &mut self,
-        _: &mut ParentViewState,
-        _: Option<Self::ElementState>,
-        cx: &mut ViewContext<ParentViewState>,
-    ) -> (LayoutId, Self::ElementState) {
+        _: &mut ParentV,
+        _: Option<Self::State>,
+        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, element)
+            (layout_id, Some(element))
         })
     }
 
     fn paint(
-        &mut self,
+        self,
         _: Bounds<Pixels>,
-        _: &mut ParentViewState,
-        element: &mut Self::ElementState,
-        cx: &mut ViewContext<ParentViewState>,
+        _: &mut ParentV,
+        element: &mut Self::State,
+        cx: &mut ViewContext<ParentV>,
     ) {
-        self.view.update(cx, |view, cx| element.paint(view, cx))
+        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 element_id(&self) -> Option<ElementId> {
+        self.element.as_ref().unwrap().element_id()
+    }
+
+    fn render_once(self) -> Self::Element {
+        self
     }
 }
 
@@ -349,7 +372,7 @@ 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>) {
@@ -363,14 +386,14 @@ mod any_view {
         })
     }
 
-    pub(crate) fn paint<V: Render>(
+    pub(crate) fn paint<V: 'static + Render<V>>(
         view: &AnyView,
-        element: &mut Box<dyn Any>,
+        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_mut::<AnyElement<V>>().unwrap();
+            let element = element.downcast::<AnyElement<V>>().unwrap();
             view.update(cx, |view, cx| element.paint(view, cx))
         })
     }

crates/gpui2/src/window.rs 🔗

@@ -1,5 +1,5 @@
 use crate::{
-    key_dispatch::DispatchActionListener, px, size, Action, AnyBox, AnyDrag, AnyView, AppContext,
+    key_dispatch::DispatchActionListener, px, size, Action, AnyDrag, AnyView, AppContext,
     AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle,
     DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId,
     EventEmitter, FileDropEvent, Flatten, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla,
@@ -187,7 +187,7 @@ 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;
 }
 
@@ -232,7 +232,7 @@ pub struct Window {
 
 // #[derive(Default)]
 pub(crate) struct Frame {
-    pub(crate) element_states: HashMap<GlobalElementId, AnyBox>,
+    pub(crate) element_states: HashMap<GlobalElementId, Box<dyn Any>>,
     mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyMouseListener)>>,
     pub(crate) dispatch_tree: DispatchTree,
     pub(crate) focus_listeners: Vec<AnyFocusListener>,
@@ -1520,7 +1520,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 {
@@ -1559,7 +1559,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 {
@@ -2335,7 +2335,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>> {
@@ -2355,7 +2355,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)
     }
@@ -2400,7 +2400,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_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 element_id(&self) -> Option<ElementId> {
+                None
+            }
+
+            fn render_once(self) -> Self::Element {
+                gpui::CompositeElement::new(self)
+            }
+        }
+    };
+
+    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,16 +1,12 @@
 mod action;
 mod derive_component;
+mod derive_render_once;
 mod register_action;
 mod style_helpers;
 mod test;
 
 use proc_macro::TokenStream;
 
-#[proc_macro]
-pub fn style_helpers(args: TokenStream) -> TokenStream {
-    style_helpers::style_helpers(args)
-}
-
 #[proc_macro_derive(Action)]
 pub fn action(input: TokenStream) -> TokenStream {
     action::action(input)
@@ -26,6 +22,16 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
     derive_component::derive_component(input)
 }
 
+#[proc_macro_derive(RenderOnce, attributes(view))]
+pub fn derive_render_once(input: TokenStream) -> TokenStream {
+    derive_render_once::derive_render_once(input)
+}
+
+#[proc_macro]
+pub fn style_helpers(input: TokenStream) -> TokenStream {
+    style_helpers::style_helpers(input)
+}
+
 #[proc_macro_attribute]
 pub fn test(args: TokenStream, function: TokenStream) -> TokenStream {
     test::test(args, function)

crates/picker2/src/picker2.rs 🔗

@@ -1,7 +1,7 @@
 use editor::Editor;
 use gpui::{
-    div, prelude::*, uniform_list, AppContext, Component, Div, FocusHandle, FocusableView,
-    MouseButton, Render, Task, UniformListScrollHandle, View, ViewContext, WindowContext,
+    div, prelude::*, uniform_list, AppContext, Div, FocusHandle, FocusableView, MouseButton,
+    Render, Task, UniformListScrollHandle, View, ViewContext, WindowContext,
 };
 use std::{cmp, sync::Arc};
 use ui::{prelude::*, v_stack, Divider, Label, TextColor};
@@ -15,7 +15,7 @@ pub struct Picker<D: PickerDelegate> {
 }
 
 pub trait PickerDelegate: Sized + 'static {
-    type ListItem: Component<Picker<Self>>;
+    type ListItem: RenderOnce<Picker<Self>>;
 
     fn match_count(&self) -> usize;
     fn selected_index(&self) -> usize;
@@ -180,7 +180,7 @@ impl<D: PickerDelegate> Picker<D> {
     }
 }
 
-impl<D: PickerDelegate> Render for Picker<D> {
+impl<D: PickerDelegate> Render<Self> for Picker<D> {
     type Element = Div<Self>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

crates/project_panel2/src/project_panel.rs 🔗

@@ -9,10 +9,10 @@ use file_associations::FileAssociations;
 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,
+    ClipboardItem, Div, EventEmitter, FocusHandle, Focusable, FocusableView, InteractiveElement,
+    Model, MouseButton, ParentElement, Pixels, Point, PromptLevel, Render, RenderOnce, Stateful,
+    StatefulInteractiveElement, Styled, Task, UniformListScrollHandle, View, ViewContext,
+    VisualContext as _, WeakView, WindowContext,
 };
 use menu::{Confirm, SelectNext, SelectPrev};
 use project::{
@@ -247,7 +247,6 @@ impl ProjectPanel {
             let mut old_dock_position = this.position(cx);
             ProjectPanelSettings::register(cx);
             cx.observe_global::<SettingsStore>(move |this, cx| {
-                dbg!("OLA!");
                 let new_dock_position = this.position(cx);
                 if new_dock_position != old_dock_position {
                     old_dock_position = new_dock_position;
@@ -1424,7 +1423,7 @@ impl ProjectPanel {
     }
 }
 
-impl Render for ProjectPanel {
+impl Render<Self> for ProjectPanel {
     type Element = Focusable<Self, Stateful<Self, Div<Self>>>;
 
     fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> Self::Element {

crates/search2/src/buffer_search.rs 🔗

@@ -10,8 +10,8 @@ use collections::HashMap;
 use editor::Editor;
 use futures::channel::oneshot;
 use gpui::{
-    actions, div, red, Action, AppContext, Component, Div, EventEmitter, InteractiveComponent,
-    ParentComponent as _, Render, Styled, Subscription, Task, View, ViewContext,
+    actions, div, red, Action, AppContext, Div, EventEmitter, InteractiveElement as _,
+    ParentElement as _, Render, RenderOnce, Styled, Subscription, Task, View, ViewContext,
     VisualContext as _, WindowContext,
 };
 use project::search::SearchQuery;
@@ -63,7 +63,7 @@ pub struct BufferSearchBar {
 
 impl EventEmitter<Event> for BufferSearchBar {}
 impl EventEmitter<workspace::ToolbarItemEvent> for BufferSearchBar {}
-impl Render for BufferSearchBar {
+impl Render<Self> for BufferSearchBar {
     type Element = Div<Self>;
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         // let query_container_style = if self.query_contains_error {
@@ -534,7 +534,7 @@ impl BufferSearchBar {
         self.update_matches(cx)
     }
 
-    fn render_action_button(&self) -> impl Component<Self> {
+    fn render_action_button(&self) -> impl RenderOnce<Self> {
         // let tooltip_style = theme.tooltip.clone();
 
         // let style = theme.search.action_button.clone();

crates/search2/src/search.rs 🔗

@@ -1,6 +1,6 @@
 use bitflags::bitflags;
 pub use buffer_search::BufferSearchBar;
-use gpui::{actions, Action, AppContext, Component};
+use gpui::{actions, Action, AppContext, RenderOnce};
 pub use mode::SearchMode;
 use project::search::SearchQuery;
 use ui::ButtonVariant;
@@ -82,7 +82,7 @@ impl SearchOptions {
         options
     }
 
-    pub fn as_button<V: 'static>(&self, active: bool) -> impl Component<V> {
+    pub fn as_button<V: 'static>(&self, active: bool) -> impl RenderOnce<V> {
         ui::IconButton::new(0, self.icon())
             .on_click({
                 let action = self.to_toggle_action();
@@ -95,7 +95,7 @@ impl SearchOptions {
     }
 }
 
-fn toggle_replace_button<V: 'static>(active: bool) -> impl Component<V> {
+fn toggle_replace_button<V: 'static>(active: bool) -> impl RenderOnce<V> {
     // todo: add toggle_replace button
     ui::IconButton::new(0, ui::Icon::Replace)
         .on_click(|_: &mut V, cx| {
@@ -109,7 +109,7 @@ fn toggle_replace_button<V: 'static>(active: bool) -> impl Component<V> {
 fn render_replace_button<V: 'static>(
     action: impl Action + 'static + Send + Sync,
     icon: ui::Icon,
-) -> impl Component<V> {
+) -> impl RenderOnce<V> {
     // todo: add tooltip
     ui::IconButton::new(0, icon).on_click(move |_: &mut V, cx| {
         cx.dispatch_action(action.boxed_clone());

crates/search2/src/search_bar.rs 🔗

@@ -1,6 +1,6 @@
 use std::sync::Arc;
 
-use gpui::{Component, ViewContext};
+use gpui::{RenderOnce, ViewContext};
 use ui::{Button, ButtonVariant, IconButton};
 
 use crate::mode::SearchMode;
@@ -9,7 +9,7 @@ pub(super) fn render_nav_button<V: 'static>(
     icon: ui::Icon,
     _active: bool,
     on_click: impl Fn(&mut V, &mut ViewContext<V>) + 'static + Send + Sync,
-) -> impl Component<V> {
+) -> impl RenderOnce<V> {
     // let tooltip_style = cx.theme().tooltip.clone();
     // let cursor_style = if active {
     //     CursorStyle::PointingHand

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

@@ -5,7 +5,7 @@ use ui::prelude::*;
 
 pub struct ColorsStory;
 
-impl Render for ColorsStory {
+impl Render<Self> for ColorsStory {
     type Element = Div<Self>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@@ -28,7 +28,7 @@ impl Render for ColorsStory {
                                 div()
                                     .w(px(75.))
                                     .line_height(px(24.))
-                                    .child(scale.name().to_string()),
+                                    .child(scale.name().clone()),
                             )
                             .child(
                                 div()

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

@@ -26,7 +26,7 @@ impl FocusStory {
     }
 }
 
-impl Render for FocusStory {
+impl Render<Self> for FocusStory {
     type Element = Focusable<Self, Stateful<Self, Div<Self>>>;
 
     fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {

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

@@ -11,7 +11,7 @@ impl KitchenSinkStory {
     }
 }
 
-impl Render for KitchenSinkStory {
+impl Render<Self> for KitchenSinkStory {
     type Element = Stateful<Self, Div<Self>>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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

@@ -1,5 +1,7 @@
 use fuzzy::StringMatchCandidate;
-use gpui::{div, prelude::*, Div, KeyBinding, Render, Styled, Task, View, WindowContext};
+use gpui::{
+    div, prelude::*, Div, KeyBinding, Render, SharedString, Styled, Task, View, WindowContext,
+};
 use picker::{Picker, PickerDelegate};
 use std::sync::Arc;
 use theme2::ActiveTheme;
@@ -54,7 +56,8 @@ impl PickerDelegate for Delegate {
         let Some(candidate_ix) = self.matches.get(ix) else {
             return div();
         };
-        let candidate = self.candidates[*candidate_ix].string.clone();
+        // TASK: Make StringMatchCandidate::string a SharedString
+        let candidate = SharedString::from(self.candidates[*candidate_ix].string.clone());
 
         div()
             .text_color(colors.text)
@@ -202,7 +205,7 @@ impl PickerStory {
     }
 }
 
-impl Render for PickerStory {
+impl Render<Self> for PickerStory {
     type Element = Div<Self>;
 
     fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {

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

@@ -10,7 +10,7 @@ impl ScrollStory {
     }
 }
 
-impl Render for ScrollStory {
+impl Render<Self> for ScrollStory {
     type Element = Stateful<Self, Div<Self>>;
 
     fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {

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

@@ -1,5 +1,5 @@
 use gpui::{
-    blue, div, red, white, Div, ParentComponent, Render, Styled, View, VisualContext, WindowContext,
+    blue, div, red, white, Div, ParentElement, Render, Styled, View, VisualContext, WindowContext,
 };
 use ui::v_stack;
 
@@ -11,7 +11,7 @@ impl TextStory {
     }
 }
 
-impl Render for TextStory {
+impl Render<Self> for TextStory {
     type Element = Div<Self>;
 
     fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {

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

@@ -1,4 +1,4 @@
-use gpui::{px, rgb, Div, Hsla, Render};
+use gpui::{px, rgb, Div, Hsla, Render, RenderOnce};
 use ui::prelude::*;
 
 use crate::story::Story;
@@ -7,7 +7,7 @@ use crate::story::Story;
 /// [https://developer.mozilla.org/en-US/docs/Web/CSS/z-index](https://developer.mozilla.org/en-US/docs/Web/CSS/z-index).
 pub struct ZIndexStory;
 
-impl Render for ZIndexStory {
+impl Render<Self> for ZIndexStory {
     type Element = Div<Self>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@@ -79,17 +79,15 @@ trait Styles: Styled + Sized {
 
 impl<V: 'static> Styles for Div<V> {}
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 struct ZIndexExample {
     z_index: u32,
 }
 
-impl ZIndexExample {
-    pub fn new(z_index: u32) -> Self {
-        Self { z_index }
-    }
+impl<V: 'static> Component<V> for ZIndexExample {
+    type Rendered = Div<V>;
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         div()
             .relative()
             .size_full()
@@ -109,14 +107,14 @@ impl ZIndexExample {
                     // HACK: Simulate `text-align: center`.
                     .pl(px(24.))
                     .z_index(self.z_index)
-                    .child(format!(
+                    .child(SharedString::from(format!(
                         "z-index: {}",
                         if self.z_index == 0 {
                             "auto".to_string()
                         } else {
                             self.z_index.to_string()
                         }
-                    )),
+                    ))),
             )
             // Blue blocks.
             .child(
@@ -173,3 +171,9 @@ impl ZIndexExample {
             )
     }
 }
+
+impl ZIndexExample {
+    pub fn new(z_index: u32) -> Self {
+        Self { z_index }
+    }
+}

crates/storybook2/src/storybook2.rs 🔗

@@ -105,7 +105,7 @@ impl StoryWrapper {
     }
 }
 
-impl Render for StoryWrapper {
+impl Render<Self> for StoryWrapper {
     type Element = Div<Self>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

crates/storybook3/src/storybook3.rs 🔗

@@ -60,7 +60,7 @@ struct TestView {
     story: AnyView,
 }
 
-impl Render for TestView {
+impl Render<Self> for TestView {
     type Element = Div<Self>;
 
     fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {

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;
@@ -335,7 +335,7 @@ impl TerminalPanel {
 
 impl EventEmitter<PanelEvent> for TerminalPanel {}
 
-impl Render for TerminalPanel {
+impl Render<Self> for TerminalPanel {
     type Element = Div<Self>;
 
     fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {

crates/terminal_view2/src/terminal_view.rs 🔗

@@ -9,10 +9,10 @@ pub mod terminal_panel;
 // use crate::terminal_element::TerminalElement;
 use editor::{scroll::autoscroll::Autoscroll, Editor};
 use gpui::{
-    actions, div, 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,
+    actions, div, Action, AnyElement, AppContext, DispatchPhase, Div, Element, 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;
@@ -537,7 +537,7 @@ impl TerminalView {
     }
 }
 
-impl Render for TerminalView {
+impl Render<Self> for TerminalView {
     type Element = Focusable<Self, Div<Self>>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@@ -577,7 +577,7 @@ impl Render for TerminalView {
             .children(
                 self.context_menu
                     .clone()
-                    .map(|context_menu| div().z_index(1).absolute().child(context_menu.render())),
+                    .map(|context_menu| div().z_index(1).absolute().child(context_menu)),
             )
             .track_focus(&self.focus_handle)
             .on_focus_in(Self::focus_in)
@@ -755,8 +755,8 @@ impl Item for TerminalView {
 
         div()
             .child(IconElement::new(Icon::Terminal))
-            .child(title)
-            .render()
+            .child(Label::new(title))
+            .into_any()
     }
 
     fn clone_on_split(

crates/theme2/src/story.rs 🔗

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

crates/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/docs/hello-world.md 🔗

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

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

@@ -1,27 +1,16 @@
-use gpui::img;
-
 use crate::prelude::*;
+use gpui::{img, Img, RenderOnce};
 
-#[derive(Component)]
+#[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,
-        }
-    }
-
-    pub fn shape(mut self, shape: Shape) -> Self {
-        self.shape = shape;
-        self
-    }
+impl<V: 'static> Component<V> for Avatar {
+    type Rendered = Img<V>;
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         let mut img = img();
 
         if self.shape == Shape::Circle {
@@ -37,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::*;
 
@@ -48,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::{DefiniteLength, Hsla, MouseButton, StatefulInteractiveComponent, WindowContext};
+use gpui::{
+    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 {
@@ -164,7 +219,7 @@ impl<V: 'static> Button<V> {
         self.icon.map(|i| IconElement::new(i).color(icon_color))
     }
 
-    pub fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    pub fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let (icon_color, label_color) = match (self.disabled, self.color) {
             (true, _) => (TextColor::Disabled, TextColor::Disabled),
             (_, None) => (TextColor::Default, TextColor::Default),
@@ -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 Component<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, 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 {
@@ -42,7 +166,7 @@ impl<V: 'static> Checkbox<V> {
         self
     }
 
-    pub fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    pub fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let group_id = format!("checkbox_group_{:?}", self.id);
 
         let icon = match self.checked {
@@ -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 🔗

@@ -1,12 +1,12 @@
 use std::cell::RefCell;
 use std::rc::Rc;
 
-use crate::prelude::*;
-use crate::{v_stack, Label, List, ListEntry, ListItem, ListSeparator, ListSubHeader};
+use crate::{prelude::*, v_stack, List, ListItem};
+use crate::{ListEntry, ListSeparator, ListSubHeader};
 use gpui::{
     overlay, px, Action, AnchorCorner, AnyElement, AppContext, Bounds, DispatchPhase, Div,
     EventEmitter, FocusHandle, FocusableView, LayoutId, ManagedView, Manager, MouseButton,
-    MouseDownEvent, Pixels, Point, Render, View, VisualContext, WeakView,
+    MouseDownEvent, Pixels, Point, Render, RenderOnce, View, VisualContext, WeakView,
 };
 
 pub enum ContextMenuItem<V> {
@@ -24,15 +24,15 @@ pub struct ContextMenu<V> {
     handle: WeakView<V>,
 }
 
-impl<V: Render> FocusableView for ContextMenu<V> {
+impl<V: 'static> FocusableView for ContextMenu<V> {
     fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
         self.focus_handle.clone()
     }
 }
 
-impl<V: Render> EventEmitter<Manager> for ContextMenu<V> {}
+impl<V: 'static> EventEmitter<Manager> for ContextMenu<V> {}
 
-impl<V: Render> ContextMenu<V> {
+impl<V: 'static> ContextMenu<V> {
     pub fn build(
         cx: &mut ViewContext<V>,
         f: impl FnOnce(Self, &mut ViewContext<Self>) -> Self,
@@ -86,7 +86,7 @@ impl<V: Render> ContextMenu<V> {
     }
 }
 
-impl<V: Render> Render for ContextMenu<V> {
+impl<V: 'static> Render<Self> for ContextMenu<V> {
     type Element = Div<Self>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@@ -129,7 +129,7 @@ impl<V: Render> Render for ContextMenu<V> {
 }
 
 pub struct MenuHandle<V: 'static, M: ManagedView> {
-    id: Option<ElementId>,
+    id: ElementId,
     child_builder: Option<Box<dyn FnOnce(bool) -> AnyElement<V> + 'static>>,
     menu_builder: Option<Rc<dyn Fn(&mut V, &mut ViewContext<V>) -> View<M> + 'static>>,
 
@@ -138,18 +138,13 @@ pub struct MenuHandle<V: 'static, M: ManagedView> {
 }
 
 impl<V: 'static, M: ManagedView> MenuHandle<V, M> {
-    pub fn id(mut self, id: impl Into<ElementId>) -> Self {
-        self.id = Some(id.into());
-        self
-    }
-
     pub fn menu(mut self, f: impl Fn(&mut V, &mut ViewContext<V>) -> View<M> + 'static) -> Self {
         self.menu_builder = Some(Rc::new(f));
         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
     }
 
@@ -167,9 +162,9 @@ impl<V: 'static, M: ManagedView> MenuHandle<V, M> {
     }
 }
 
-pub fn menu_handle<V: 'static, M: ManagedView>() -> MenuHandle<V, M> {
+pub fn menu_handle<V: 'static, M: ManagedView>(id: impl Into<ElementId>) -> MenuHandle<V, M> {
     MenuHandle {
-        id: None,
+        id: id.into(),
         child_builder: None,
         menu_builder: None,
         anchor: None,
@@ -185,18 +180,14 @@ pub struct MenuHandleState<V, M> {
     menu_element: Option<AnyElement<V>>,
 }
 impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> {
-    type ElementState = MenuHandleState<V, M>;
-
-    fn element_id(&self) -> Option<gpui::ElementId> {
-        Some(self.id.clone().expect("menu_handle must have an id()"))
-    }
+    type State = MenuHandleState<V, M>;
 
     fn layout(
         &mut self,
         view_state: &mut V,
-        element_state: Option<Self::ElementState>,
+        element_state: Option<Self::State>,
         cx: &mut crate::ViewContext<V>,
-    ) -> (gpui::LayoutId, Self::ElementState) {
+    ) -> (gpui::LayoutId, Self::State) {
         let (menu, position) = if let Some(element_state) = element_state {
             (element_state.menu, element_state.position)
         } else {
@@ -212,9 +203,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
@@ -244,22 +235,22 @@ impl<V: 'static, M: ManagedView> Element<V> for MenuHandle<V, M> {
     }
 
     fn paint(
-        &mut self,
+        self,
         bounds: Bounds<gpui::Pixels>,
         view_state: &mut V,
-        element_state: &mut Self::ElementState,
+        element_state: &mut Self::State,
         cx: &mut crate::ViewContext<V>,
     ) {
-        if let Some(child) = element_state.child_element.as_mut() {
+        if let Some(child) = element_state.child_element.take() {
             child.paint(view_state, cx);
         }
 
-        if let Some(menu) = element_state.menu_element.as_mut() {
+        if let Some(menu) = element_state.menu_element.take() {
             menu.paint(view_state, cx);
             return;
         }
 
-        let Some(builder) = self.menu_builder.clone() else {
+        let Some(builder) = self.menu_builder else {
             return;
         };
         let menu = element_state.menu.clone();
@@ -300,9 +291,15 @@ 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 element_id(&self) -> Option<gpui::ElementId> {
+        Some(self.id.clone())
+    }
+
+    fn render_once(self) -> Self::Element {
+        self
     }
 }
 
@@ -312,12 +309,12 @@ pub use stories::*;
 #[cfg(feature = "stories")]
 mod stories {
     use super::*;
-    use crate::story::Story;
+    use crate::{story::Story, Label};
     use gpui::{actions, Div, Render};
 
     actions!(PrintCurrentDate, PrintBestFood);
 
-    fn build_menu<V: Render>(
+    fn build_menu<V: Render<V>>(
         cx: &mut ViewContext<V>,
         header: impl Into<SharedString>,
     ) -> View<ContextMenu<V>> {
@@ -337,7 +334,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 {
@@ -360,28 +357,24 @@ mod stories {
                         .flex_col()
                         .justify_between()
                         .child(
-                            menu_handle()
-                                .id("test2")
+                            menu_handle("test2")
                                 .child(|is_open| {
                                     Label::new(if is_open {
                                         "TOP LEFT"
                                     } else {
                                         "RIGHT CLICK ME"
                                     })
-                                    .render()
                                 })
                                 .menu(move |_, cx| build_menu(cx, "top left")),
                         )
                         .child(
-                            menu_handle()
-                                .id("test1")
+                            menu_handle("test1")
                                 .child(|is_open| {
                                     Label::new(if is_open {
                                         "BOTTOM LEFT"
                                     } else {
                                         "RIGHT CLICK ME"
                                     })
-                                    .render()
                                 })
                                 .anchor(AnchorCorner::BottomLeft)
                                 .attach(AnchorCorner::TopLeft)
@@ -394,29 +387,25 @@ mod stories {
                         .flex_col()
                         .justify_between()
                         .child(
-                            menu_handle()
-                                .id("test3")
+                            menu_handle("test3")
                                 .child(|is_open| {
                                     Label::new(if is_open {
                                         "TOP RIGHT"
                                     } else {
                                         "RIGHT CLICK ME"
                                     })
-                                    .render()
                                 })
                                 .anchor(AnchorCorner::TopRight)
                                 .menu(move |_, cx| build_menu(cx, "top right")),
                         )
                         .child(
-                            menu_handle()
-                                .id("test4")
+                            menu_handle("test4")
                                 .child(|is_open| {
                                     Label::new(if is_open {
                                         "BOTTOM RIGHT"
                                     } 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 Component<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::{Div, RenderOnce};
+
 use crate::prelude::*;
 
 enum DividerDirection {
@@ -5,12 +7,29 @@ enum DividerDirection {
     Vertical,
 }
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct Divider {
     direction: DividerDirection,
     inset: bool,
 }
 
+impl<V: 'static> Component<V> for Divider {
+    type Rendered = Div<V>;
+
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+        div()
+            .map(|this| match self.direction {
+                DividerDirection::Horizontal => {
+                    this.h_px().w_full().when(self.inset, |this| this.mx_1p5())
+                }
+                DividerDirection::Vertical => {
+                    this.w_px().h_full().when(self.inset, |this| this.my_1p5())
+                }
+            })
+            .bg(cx.theme().colors().border_variant)
+    }
+}
+
 impl Divider {
     pub fn horizontal() -> Self {
         Self {
@@ -31,7 +50,7 @@ impl Divider {
         self
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         div()
             .map(|this| match self.direction {
                 DividerDirection::Horizontal => {

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

@@ -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 Component<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::*;
@@ -133,13 +133,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 {
@@ -167,7 +184,7 @@ impl IconElement {
         self
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let svg_size = match self.size {
             IconSize::Small => rems(0.75),
             IconSize::Medium => rems(0.9375),
@@ -195,7 +212,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,64 @@ 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()
+            // Nate: Trying to figure out the right places we want to show a
+            // hover state here. I think it is a bit heavy to have it on every
+            // place we use an icon button.
+            // .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,58 +137,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 Component<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()
-            // Nate: Trying to figure out the right places we want to show a
-            // hover state here. I think it is a bit heavy to have it on every
-            // place we use an icon button.
-            // .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,16 +1,30 @@
-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
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         div()
             .rounded_full()
             .border_2()

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

@@ -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 Component<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,7 @@
-use gpui::{actions, Action};
-use strum::EnumIter;
-
 use crate::prelude::*;
+use gpui::{Action, Div, RenderOnce};
 
-#[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 +10,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))
-    }
-
-    pub fn new(key_binding: gpui::KeyBinding) -> Self {
-        Self { key_binding }
-    }
+impl<V: 'static> Component<V> for KeyBinding {
+    type Rendered = Div<V>;
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
         div()
             .flex()
             .gap_2()
@@ -42,17 +31,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 Component<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> Self::Rendered {
+        let _view: &mut V = view;
         div()
             .px_2()
             .py_0()
@@ -64,20 +65,10 @@ impl Key {
     }
 }
 
-// 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)]
-pub enum ModifierKey {
-    Control,
-    Alt,
-    Command,
-    Shift,
-}
-
-actions!(NoAction);
-
-pub fn binding(key: &str) -> gpui::KeyBinding {
-    gpui::KeyBinding::new(key, NoAction {}, None)
+impl Key {
+    pub fn new(key: impl Into<SharedString>) -> Self {
+        Self { key: key.into() }
+    }
 }
 
 #[cfg(feature = "stories")]
@@ -87,12 +78,18 @@ pub use stories::*;
 mod stories {
     use super::*;
     pub use crate::KeyBinding;
-    use crate::{binding, Story};
-    use gpui::{Div, Render};
+    use crate::Story;
+    use gpui::{actions, Div, Render};
     use itertools::Itertools;
     pub struct KeybindingStory;
 
-    impl Render for KeybindingStory {
+    actions!(NoAction);
+
+    pub fn binding(key: &str) -> gpui::KeyBinding {
+        gpui::KeyBinding::new(key, NoAction {}, None)
+    }
+
+    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 Component<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 Component<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,7 +1,6 @@
+use gpui::{div, Div, RenderOnce, Stateful, StatefulInteractiveElement};
 use std::rc::Rc;
 
-use gpui::{div, Div, Stateful, StatefulInteractiveComponent};
-
 use crate::settings::user_settings;
 use crate::{
     disclosure_control, h_stack, v_stack, Avatar, Icon, IconElement, IconSize, Label, Toggle,
@@ -24,7 +23,7 @@ pub enum ListHeaderMeta {
     Text(Label),
 }
 
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct ListHeader {
     label: SharedString,
     left_icon: Option<Icon>,
@@ -33,33 +32,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,
-        }
-    }
+impl<V: 'static> Component<V> for ListHeader {
+    type Rendered = Div<V>;
 
-    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
-    }
-
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<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 {
@@ -81,11 +57,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()
@@ -119,7 +90,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>,
@@ -140,7 +198,7 @@ impl ListSubHeader {
         self
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         h_stack().flex_1().w_full().relative().py_1().child(
             div()
                 .h_6()
@@ -200,14 +258,6 @@ impl<V: 'static> From<ListSubHeader> for ListItem<V> {
 }
 
 impl<V: 'static> ListItem<V> {
-    fn render(self, view: &mut V, ix: usize, cx: &mut ViewContext<V>) -> impl Component<V> {
-        match self {
-            ListItem::Entry(entry) => div().child(entry.render(ix, cx)),
-            ListItem::Separator(separator) => div().child(separator.render(view, cx)),
-            ListItem::Header(header) => div().child(header.render(view, cx)),
-        }
-    }
-
     pub fn new(label: Label) -> Self {
         Self::Entry(ListEntry::new(label))
     }
@@ -219,8 +269,17 @@ impl<V: 'static> ListItem<V> {
             None
         }
     }
+
+    fn render(self, view: &mut V, ix: usize, cx: &mut ViewContext<V>) -> Div<V> {
+        match self {
+            ListItem::Entry(entry) => div().child(entry.render(ix, cx)),
+            ListItem::Separator(separator) => div().child(separator.render(view, cx)),
+            ListItem::Header(header) => div().child(header.render(view, cx)),
+        }
+    }
 }
 
+// #[derive(RenderOnce)]
 pub struct ListEntry<V> {
     disabled: bool,
     // TODO: Reintroduce this
@@ -376,20 +435,24 @@ impl<V: 'static> ListEntry<V> {
     }
 }
 
-#[derive(Clone, Component)]
+#[derive(RenderOnce, Clone)]
 pub struct ListSeparator;
 
 impl ListSeparator {
     pub fn new() -> Self {
         Self
     }
+}
+
+impl<V: 'static> Component<V> for ListSeparator {
+    type Rendered = Div<V>;
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<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<V: 'static> {
     items: Vec<ListItem<V>>,
     /// Message to display when the list is empty
@@ -399,6 +462,31 @@ pub struct List<V: 'static> {
     toggle: Toggle,
 }
 
+impl<V: 'static> Component<V> for List<V> {
+    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
+                    .into_iter()
+                    .enumerate()
+                    .map(|(ix, item)| item.render(view, ix, cx)),
+            ),
+            (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<V: 'static> List<V> {
     pub fn new(items: Vec<ListItem<V>>) -> Self {
         Self {
@@ -424,7 +512,7 @@ impl<V: 'static> List<V> {
         self
     }
 
-    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render(self, view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let list_content = match (self.items.is_empty(), self.toggle) {
             (false, _) => div().children(
                 self.items

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

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

@@ -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 Component<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 Component<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 Component<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 Component<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 Component<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,5 +1,6 @@
 use crate::prelude::*;
-use gpui::{prelude::*, AnyElement};
+use gpui::{prelude::*, AnyElement, RenderOnce};
+use gpui::{Div, Element};
 use smallvec::SmallVec;
 
 #[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
@@ -21,12 +22,38 @@ pub enum ToastOrigin {
 /// they are actively showing the a process in progress.
 ///
 /// Only one toast may be visible at a time.
-#[derive(Component)]
+#[derive(RenderOnce)]
 pub struct Toast<V: 'static> {
     origin: ToastOrigin,
     children: SmallVec<[AnyElement<V>; 2]>,
 }
 
+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 {
         Self {
@@ -35,7 +62,7 @@ impl<V: 'static> Toast<V> {
         }
     }
 
-    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let mut div = div();
 
         if self.origin == ToastOrigin::Bottom {
@@ -58,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
     }
@@ -77,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, ParentComponent};
+use gpui::{div, Element, ParentElement};
 
 use crate::{Icon, IconElement, IconSize, TextColor};
 
@@ -44,7 +44,7 @@ impl From<bool> for Toggle {
     }
 }
 
-pub fn disclosure_control<V: 'static>(toggle: Toggle) -> impl Component<V> {
+pub fn disclosure_control<V: 'static>(toggle: Toggle) -> impl Element<V> {
     match (toggle.is_toggleable(), toggle.is_toggled()) {
         (false, _) => div(),
         (_, true) => div().child(

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

@@ -1,14 +1,23 @@
 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
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         div().w_px().h_3().bg(cx.theme().colors().border)
     }
 }

crates/ui2/src/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 Component<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 Component<V> {
+    pub fn title_for<V: 'static, T>(cx: &mut ViewContext<V>) -> impl Element<V> {
         Self::title(cx, std::any::type_name::<T>())
     }
 
-    pub fn label<V: 'static>(cx: &mut ViewContext<V>, label: &str) -> impl Component<V> {
+    pub fn label<V: 'static>(
+        cx: &mut ViewContext<V>,
+        label: 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 Component<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 Component<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 {
@@ -154,7 +169,7 @@ impl Buffer {
         self
     }
 
-    fn render_row<V: 'static>(row: BufferRow, cx: &WindowContext) -> impl Component<V> {
+    fn render_row<V: 'static>(row: BufferRow, cx: &WindowContext) -> impl Element<V> {
         let line_background = if row.current {
             cx.theme().colors().editor_active_line_background
         } else {
@@ -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())),
                     ),
                 )
             })
@@ -202,7 +217,7 @@ impl Buffer {
             }))
     }
 
-    fn render_rows<V: 'static>(&self, cx: &WindowContext) -> Vec<impl Component<V>> {
+    fn render_rows<V: 'static>(&self, cx: &WindowContext) -> Vec<impl Element<V>> {
         match &self.rows {
             Some(rows) => rows
                 .rows
@@ -213,7 +228,7 @@ impl Buffer {
         }
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let rows = self.render_rows(cx);
 
         v_stack()
@@ -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 Component<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 Component<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 Component<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 Component<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 Component<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,17 +1,42 @@
 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() }
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         div().id(self.id.clone()).child(
             Palette::new("palette")
                 .items(vec![
@@ -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 Component<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 Component<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,
@@ -241,7 +324,7 @@ impl<V> Notification<V> {
         self
     }
 
-    fn render_meta_items(&self, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render_meta_items(&self, cx: &mut ViewContext<V>) -> impl Element<V> {
         if let Some(meta) = &self.meta {
             h_stack().children(
                 meta.items
@@ -260,87 +343,12 @@ impl<V> Notification<V> {
         }
     }
 
-    fn render_slot(&self, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render_slot(&self, cx: &mut ViewContext<V>) -> impl Element<V> {
         match &self.slot {
-            ActorOrIcon::Actor(actor) => Avatar::new(actor.avatar.clone()).render(),
-            ActorOrIcon::Icon(icon) => IconElement::new(icon.clone()).render(),
+            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 Component<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 Component<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 Component<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,18 +3,56 @@ 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() }
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         div()
             .id(self.id.clone())
             .flex()
@@ -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 Component<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 Component<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,17 +1,84 @@
-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
     }
 
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
         let can_navigate_back = true;
         let can_navigate_forward = false;
 
@@ -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 Component<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 Component<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 Component<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 Component<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 Component<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 Component<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 Component<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 Component<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,
+    div, px, Action, AnchorCorner, AnyView, AppContext, Div, Entity, EntityId, EventEmitter,
+    FocusHandle, FocusableView, ParentElement, Render, RenderOnce, SharedString, Styled,
     Subscription, View, ViewContext, VisualContext, WeakView, WindowContext,
 };
 use schemars::JsonSchema;
@@ -476,7 +476,7 @@ impl Dock {
     }
 }
 
-impl Render for Dock {
+impl Render<Self> for Dock {
     type Element = Div<Self>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@@ -662,7 +662,7 @@ impl PanelButtons {
 // }
 
 // here be kittens
-impl Render for PanelButtons {
+impl Render<Self> for PanelButtons {
     type Element = Div<Self>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@@ -705,8 +705,7 @@ impl Render for PanelButtons {
                 };
 
                 Some(
-                    menu_handle()
-                        .id(name)
+                    menu_handle(name)
                         .menu(move |_, cx| {
                             const POSITIONS: [DockPosition; 3] = [
                                 DockPosition::Left,
@@ -781,7 +780,7 @@ pub mod test {
         }
     }
 
-    impl Render for TestPanel {
+    impl Render<Self> for TestPanel {
         type Element = Div<Self>;
 
         fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {

crates/workspace2/src/modal_layer.rs 🔗

@@ -71,7 +71,7 @@ impl ModalLayer {
     }
 }
 
-impl Render for ModalLayer {
+impl Render<Self> for ModalLayer {
     type Element = Div<Self>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

crates/workspace2/src/notifications.rs 🔗

@@ -13,9 +13,9 @@ pub enum NotificationEvent {
     Dismiss,
 }
 
-pub trait Notification: EventEmitter<NotificationEvent> + Render {}
+pub trait Notification: EventEmitter<NotificationEvent> + Render<Self> {}
 
-impl<V: EventEmitter<NotificationEvent> + Render> Notification for V {}
+impl<V: EventEmitter<NotificationEvent> + Render<Self>> Notification for V {}
 
 pub trait NotificationHandle: Send {
     fn id(&self) -> EntityId;
@@ -253,7 +253,7 @@ pub mod simple_message_notification {
         // }
     }
 
-    impl Render for MessageNotification {
+    impl Render<Self> for MessageNotification {
         type Element = Div<Self>;
 
         fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

crates/workspace2/src/pane.rs 🔗

@@ -7,9 +7,9 @@ use crate::{
 use anyhow::Result;
 use collections::{HashMap, HashSet, VecDeque};
 use gpui::{
-    actions, prelude::*, Action, AppContext, AsyncWindowContext, Component, Div, EntityId,
-    EventEmitter, FocusHandle, Focusable, FocusableView, Model, Pixels, Point, PromptLevel, Render,
-    Task, View, ViewContext, VisualContext, WeakView, WindowContext,
+    actions, prelude::*, Action, AppContext, AsyncWindowContext, Div, EntityId, EventEmitter,
+    FocusHandle, Focusable, FocusableView, Model, Pixels, Point, PromptLevel, Render, Task, View,
+    ViewContext, VisualContext, WeakView, WindowContext,
 };
 use parking_lot::Mutex;
 use project2::{Project, ProjectEntryId, ProjectPath};
@@ -594,7 +594,7 @@ impl Pane {
         self.items.iter()
     }
 
-    pub fn items_of_type<T: Render>(&self) -> impl '_ + Iterator<Item = View<T>> {
+    pub fn items_of_type<T: Render<T>>(&self) -> impl '_ + Iterator<Item = View<T>> {
         self.items
             .iter()
             .filter_map(|item| item.to_any().downcast().ok())
@@ -1344,7 +1344,7 @@ impl Pane {
         item: &Box<dyn ItemHandle>,
         detail: usize,
         cx: &mut ViewContext<'_, Pane>,
-    ) -> impl Component<Self> {
+    ) -> impl RenderOnce<Self> {
         let label = item.tab_content(Some(detail), cx);
         let close_icon = || {
             let id = item.item_id();
@@ -1437,7 +1437,7 @@ impl Pane {
             )
     }
 
-    fn render_tab_bar(&mut self, cx: &mut ViewContext<'_, Pane>) -> impl Component<Self> {
+    fn render_tab_bar(&mut self, cx: &mut ViewContext<'_, Pane>) -> impl RenderOnce<Self> {
         div()
             .group("tab_bar")
             .id("tab_bar")
@@ -1891,7 +1891,7 @@ impl FocusableView for Pane {
     }
 }
 
-impl Render for Pane {
+impl Render<Self> for Pane {
     type Element = Focusable<Self, Div<Self>>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@@ -2945,7 +2945,7 @@ struct DraggedTab {
     title: String,
 }
 
-impl Render for DraggedTab {
+impl Render<Self> for DraggedTab {
     type Element = Div<Self>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

crates/workspace2/src/pane_group.rs 🔗

@@ -6,7 +6,9 @@ use db2::sqlez::{
     bindable::{Bind, Column, StaticColumnCount},
     statement::Statement,
 };
-use gpui::{point, size, AnyElement, AnyWeakView, Bounds, Model, Pixels, Point, View, ViewContext};
+use gpui::{
+    point, size, AnyWeakView, Bounds, Div, Model, Pixels, Point, RenderOnce, View, ViewContext,
+};
 use parking_lot::Mutex;
 use project2::Project;
 use serde::Deserialize;
@@ -130,7 +132,7 @@ impl PaneGroup {
         zoomed: Option<&AnyWeakView>,
         app_state: &Arc<AppState>,
         cx: &mut ViewContext<Workspace>,
-    ) -> impl Component<Workspace> {
+    ) -> impl RenderOnce<Workspace> {
         self.root.render(
             project,
             0,
@@ -202,7 +204,7 @@ impl Member {
         zoomed: Option<&AnyWeakView>,
         app_state: &Arc<AppState>,
         cx: &mut ViewContext<Workspace>,
-    ) -> impl Component<Workspace> {
+    ) -> impl RenderOnce<Workspace> {
         match self {
             Member::Pane(pane) => {
                 // todo!()
@@ -212,7 +214,7 @@ impl Member {
                 //     Some(pane)
                 // };
 
-                div().size_full().child(pane.clone()).render()
+                div().size_full().child(pane.clone())
 
                 //         Stack::new()
                 //             .with_child(pane_element.contained().with_border(leader_border))
@@ -559,7 +561,7 @@ impl PaneAxis {
         zoomed: Option<&AnyWeakView>,
         app_state: &Arc<AppState>,
         cx: &mut ViewContext<Workspace>,
-    ) -> AnyElement<Workspace> {
+    ) -> Div<Workspace> {
         debug_assert!(self.members.len() == self.flexes.lock().len());
 
         div()
@@ -582,11 +584,10 @@ impl PaneAxis {
                             app_state,
                             cx,
                         )
-                        .render(),
-                    Member::Pane(pane) => pane.clone().render(),
+                        .render_into_any(),
+                    Member::Pane(pane) => pane.clone().render_into_any(),
                 }
             }))
-            .render()
 
         // let mut pane_axis = PaneAxisElement::new(
         //     self.axis,

crates/workspace2/src/status_bar.rs 🔗

@@ -2,14 +2,14 @@ use std::any::TypeId;
 
 use crate::{ItemHandle, Pane};
 use gpui::{
-    div, AnyView, Component, Div, ParentComponent, Render, Styled, Subscription, View, ViewContext,
+    div, AnyView, Div, ParentElement, Render, RenderOnce, Styled, Subscription, View, ViewContext,
     WindowContext,
 };
 use theme2::ActiveTheme;
 use ui::h_stack;
 use util::ResultExt;
 
-pub trait StatusItemView: Render {
+pub trait StatusItemView: Render<Self> {
     fn set_active_pane_item(
         &mut self,
         active_pane_item: Option<&dyn crate::ItemHandle>,
@@ -34,7 +34,7 @@ pub struct StatusBar {
     _observe_active_pane: Subscription,
 }
 
-impl Render for StatusBar {
+impl Render<Self> for StatusBar {
     type Element = Div<Self>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
@@ -53,14 +53,14 @@ impl Render for StatusBar {
 }
 
 impl StatusBar {
-    fn render_left_tools(&self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
+    fn render_left_tools(&self, cx: &mut ViewContext<Self>) -> impl RenderOnce<Self> {
         h_stack()
             .items_center()
             .gap_2()
             .children(self.left_items.iter().map(|item| item.to_any()))
     }
 
-    fn render_right_tools(&self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
+    fn render_right_tools(&self, cx: &mut ViewContext<Self>) -> impl RenderOnce<Self> {
         h_stack()
             .items_center()
             .gap_2()

crates/workspace2/src/toolbar.rs 🔗

@@ -1,6 +1,6 @@
 use crate::ItemHandle;
 use gpui::{
-    AnyView, Div, Entity, EntityId, EventEmitter, ParentComponent, Render, Styled, View,
+    AnyView, Div, Entity, EntityId, EventEmitter, ParentElement as _, Render, Styled, View,
     ViewContext, WindowContext,
 };
 use theme2::ActiveTheme;
@@ -10,7 +10,7 @@ pub enum ToolbarItemEvent {
     ChangeLocation(ToolbarItemLocation),
 }
 
-pub trait ToolbarItemView: Render + EventEmitter<ToolbarItemEvent> {
+pub trait ToolbarItemView: Render<Self> + EventEmitter<ToolbarItemEvent> {
     fn set_active_pane_item(
         &mut self,
         active_pane_item: Option<&dyn crate::ItemHandle>,
@@ -76,7 +76,7 @@ impl Toolbar {
     }
 }
 
-impl Render for Toolbar {
+impl Render<Self> for Toolbar {
     type Element = Div<Self>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {

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;
@@ -3594,7 +3594,7 @@ impl FocusableView for Workspace {
     }
 }
 
-impl Render for Workspace {
+impl Render<Self> for Workspace {
     type Element = Div<Self>;
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {