Merge branch 'zed2-render' into zed2

Max Brunsfeld created

Change summary

crates/gpui2/src/app.rs                          |   8 
crates/gpui2/src/app/entity_map.rs               |  11 
crates/gpui2/src/element.rs                      |   2 
crates/gpui2/src/gpui2.rs                        |  33 +--
crates/gpui2/src/interactive.rs                  |  69 ++++----
crates/gpui2/src/view.rs                         | 118 ++++++++-----
crates/gpui2/src/window.rs                       |  63 +++----
crates/storybook2/src/stories/colors.rs          |  12 
crates/storybook2/src/stories/focus.rs           | 149 ++++++++---------
crates/storybook2/src/stories/kitchen_sink.rs    |  27 +-
crates/storybook2/src/stories/scroll.rs          |  86 +++++-----
crates/storybook2/src/stories/text.rs            |  31 +-
crates/storybook2/src/stories/z_index.rs         |   9 
crates/storybook2/src/story_selector.rs          | 106 +++--------
crates/storybook2/src/storybook2.rs              |  17 -
crates/ui2/src/components/assistant_panel.rs     |  15 
crates/ui2/src/components/breadcrumb.rs          |  18 -
crates/ui2/src/components/buffer.rs              |  13 
crates/ui2/src/components/buffer_search.rs       |  16 
crates/ui2/src/components/chat_panel.rs          |   8 
crates/ui2/src/components/collab_panel.rs        |  11 
crates/ui2/src/components/command_palette.rs     |   9 
crates/ui2/src/components/context_menu.rs        |  11 
crates/ui2/src/components/copilot.rs             |   9 
crates/ui2/src/components/editor_pane.rs         |  18 +-
crates/ui2/src/components/facepile.rs            |  11 
crates/ui2/src/components/keybinding.rs          |  14 
crates/ui2/src/components/language_selector.rs   |  11 
crates/ui2/src/components/multi_buffer.rs        |  11 
crates/ui2/src/components/notifications_panel.rs |  11 
crates/ui2/src/components/palette.rs             | 101 ++++++-----
crates/ui2/src/components/panel.rs               |  13 
crates/ui2/src/components/panes.rs               |  13 -
crates/ui2/src/components/project_panel.rs       |  11 
crates/ui2/src/components/recent_projects.rs     |  11 
crates/ui2/src/components/tab.rs                 |  31 ++-
crates/ui2/src/components/tab_bar.rs             |  11 
crates/ui2/src/components/terminal.rs            |  12 
crates/ui2/src/components/theme_selector.rs      |   9 
crates/ui2/src/components/title_bar.rs           |  39 ++--
crates/ui2/src/components/toast.rs               |  11 
crates/ui2/src/components/toolbar.rs             |  11 
crates/ui2/src/components/traffic_lights.rs      |   9 
crates/ui2/src/components/workspace.rs           |  38 ++--
crates/ui2/src/elements/avatar.rs                |  11 
crates/ui2/src/elements/button.rs                |  17 -
crates/ui2/src/elements/details.rs               |  13 
crates/ui2/src/elements/icon.rs                  |   8 
crates/ui2/src/elements/input.rs                 |  11 
crates/ui2/src/elements/label.rs                 |  11 
crates/ui2/src/static_data.rs                    |   8 
51 files changed, 665 insertions(+), 661 deletions(-)

Detailed changes

crates/gpui2/src/app.rs 🔗

@@ -15,7 +15,7 @@ pub use test_context::*;
 use crate::{
     current_platform, image_cache::ImageCache, Action, AnyBox, AnyView, AppMetadata, AssetSource,
     ClipboardItem, Context, DispatchPhase, DisplayId, Executor, FocusEvent, FocusHandle, FocusId,
-    KeyBinding, Keymap, LayoutId, MainThread, MainThreadOnly, Pixels, Platform, Point,
+    KeyBinding, Keymap, LayoutId, MainThread, MainThreadOnly, Pixels, Platform, Point, Render,
     SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement,
     TextSystem, View, Window, WindowContext, WindowHandle, WindowId,
 };
@@ -815,7 +815,7 @@ impl MainThread<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: 'static>(
+    pub fn open_window<V: Render>(
         &mut self,
         options: crate::WindowOptions,
         build_root_view: impl FnOnce(&mut WindowContext) -> View<V> + Send + 'static,
@@ -898,10 +898,8 @@ impl<G: 'static> DerefMut for GlobalLease<G> {
 /// Contains state associated with an active drag operation, started by dragging an element
 /// within the window or by dragging into the app from the underlying platform.
 pub(crate) struct AnyDrag {
-    pub drag_handle_view: Option<AnyView>,
+    pub view: AnyView,
     pub cursor_offset: Point<Pixels>,
-    pub state: AnyBox,
-    pub state_type: TypeId,
 }
 
 #[cfg(test)]

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

@@ -147,7 +147,7 @@ pub struct Slot<T>(Model<T>);
 
 pub struct AnyModel {
     pub(crate) entity_id: EntityId,
-    entity_type: TypeId,
+    pub(crate) entity_type: TypeId,
     entity_map: Weak<RwLock<EntityRefCounts>>,
 }
 
@@ -247,8 +247,8 @@ impl Eq for AnyModel {}
 pub struct Model<T> {
     #[deref]
     #[deref_mut]
-    any_model: AnyModel,
-    entity_type: PhantomData<T>,
+    pub(crate) any_model: AnyModel,
+    pub(crate) entity_type: PhantomData<T>,
 }
 
 unsafe impl<T> Send for Model<T> {}
@@ -272,6 +272,11 @@ impl<T: 'static> Model<T> {
         }
     }
 
+    /// Convert this into a dynamically typed model.
+    pub fn into_any(self) -> AnyModel {
+        self.any_model
+    }
+
     pub fn read<'a>(&self, cx: &'a AppContext) -> &'a T {
         cx.entities.read(self)
     }

crates/gpui2/src/element.rs 🔗

@@ -4,7 +4,7 @@ pub(crate) use smallvec::SmallVec;
 use std::{any::Any, mem};
 
 pub trait Element<V: 'static> {
-    type ElementState: 'static;
+    type ElementState: 'static + Send;
 
     fn id(&self) -> Option<ElementId>;
 

crates/gpui2/src/gpui2.rs 🔗

@@ -90,13 +90,11 @@ pub trait Context {
 pub trait VisualContext: Context {
     type ViewContext<'a, 'w, V>;
 
-    fn build_view<E, V>(
+    fn build_view<V>(
         &mut self,
-        build_model: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V,
-        render: impl Fn(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static,
+        build_view_state: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        E: Component<V>,
         V: 'static + Send;
 
     fn update_view<V: 'static, R>(
@@ -171,27 +169,22 @@ impl<C: Context> Context for MainThread<C> {
 impl<C: VisualContext> VisualContext for MainThread<C> {
     type ViewContext<'a, 'w, V> = MainThread<C::ViewContext<'a, 'w, V>>;
 
-    fn build_view<E, V>(
+    fn build_view<V>(
         &mut self,
-        build_model: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V,
-        render: impl Fn(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static,
+        build_view_state: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V,
     ) -> Self::Result<View<V>>
     where
-        E: Component<V>,
         V: 'static + Send,
     {
-        self.0.build_view(
-            |cx| {
-                let cx = unsafe {
-                    mem::transmute::<
-                        &mut C::ViewContext<'_, '_, V>,
-                        &mut MainThread<C::ViewContext<'_, '_, V>>,
-                    >(cx)
-                };
-                build_model(cx)
-            },
-            render,
-        )
+        self.0.build_view(|cx| {
+            let cx = unsafe {
+                mem::transmute::<
+                    &mut C::ViewContext<'_, '_, V>,
+                    &mut MainThread<C::ViewContext<'_, '_, V>>,
+                >(cx)
+            };
+            build_view_state(cx)
+        })
     }
 
     fn update_view<V: 'static, R>(

crates/gpui2/src/interactive.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{
-    point, px, Action, AnyBox, AnyDrag, AppContext, BorrowWindow, Bounds, Component,
-    DispatchContext, DispatchPhase, Element, ElementId, FocusHandle, KeyMatch, Keystroke,
-    Modifiers, Overflow, Pixels, Point, SharedString, Size, Style, StyleRefinement, View,
+    div, point, px, Action, AnyDrag, AnyView, AppContext, BorrowWindow, Bounds, Component,
+    DispatchContext, DispatchPhase, Div, Element, ElementId, FocusHandle, KeyMatch, Keystroke,
+    Modifiers, Overflow, Pixels, Point, Render, SharedString, Size, Style, StyleRefinement, View,
     ViewContext,
 };
 use collections::HashMap;
@@ -258,17 +258,17 @@ pub trait StatelessInteractive<V: 'static>: Element<V> {
         self
     }
 
-    fn on_drop<S: 'static>(
+    fn on_drop<W: 'static + Send>(
         mut self,
-        listener: impl Fn(&mut V, S, &mut ViewContext<V>) + Send + 'static,
+        listener: impl Fn(&mut V, View<W>, &mut ViewContext<V>) + Send + 'static,
     ) -> Self
     where
         Self: Sized,
     {
         self.stateless_interaction().drop_listeners.push((
-            TypeId::of::<S>(),
-            Box::new(move |view, drag_state, cx| {
-                listener(view, *drag_state.downcast().unwrap(), cx);
+            TypeId::of::<W>(),
+            Box::new(move |view, dragged_view, cx| {
+                listener(view, dragged_view.downcast().unwrap(), cx);
             }),
         ));
         self
@@ -314,36 +314,22 @@ pub trait StatefulInteractive<V: 'static>: StatelessInteractive<V> {
         self
     }
 
-    fn on_drag<S, R, E>(
+    fn on_drag<W>(
         mut self,
-        listener: impl Fn(&mut V, &mut ViewContext<V>) -> Drag<S, R, V, E> + Send + 'static,
+        listener: impl Fn(&mut V, &mut ViewContext<V>) -> View<W> + Send + 'static,
     ) -> Self
     where
         Self: Sized,
-        S: Any + Send,
-        R: Fn(&mut V, &mut ViewContext<V>) -> E,
-        R: 'static + Send,
-        E: Component<V>,
+        W: 'static + Send + Render,
     {
         debug_assert!(
             self.stateful_interaction().drag_listener.is_none(),
             "calling on_drag more than once on the same element is not supported"
         );
         self.stateful_interaction().drag_listener =
-            Some(Box::new(move |view_state, cursor_offset, cx| {
-                let drag = listener(view_state, cx);
-                let drag_handle_view = Some(
-                    View::for_handle(cx.model().upgrade().unwrap(), move |view_state, cx| {
-                        (drag.render_drag_handle)(view_state, cx)
-                    })
-                    .into_any(),
-                );
-                AnyDrag {
-                    drag_handle_view,
-                    cursor_offset,
-                    state: Box::new(drag.state),
-                    state_type: TypeId::of::<S>(),
-                }
+            Some(Box::new(move |view_state, cursor_offset, cx| AnyDrag {
+                view: listener(view_state, cx).into_any(),
+                cursor_offset,
             }));
         self
     }
@@ -412,7 +398,7 @@ pub trait ElementInteraction<V: 'static>: 'static + Send {
         if let Some(drag) = cx.active_drag.take() {
             for (state_type, group_drag_style) in &self.as_stateless().group_drag_over_styles {
                 if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) {
-                    if *state_type == drag.state_type
+                    if *state_type == drag.view.entity_type()
                         && group_bounds.contains_point(&mouse_position)
                     {
                         style.refine(&group_drag_style.style);
@@ -421,7 +407,8 @@ pub trait ElementInteraction<V: 'static>: 'static + Send {
             }
 
             for (state_type, drag_over_style) in &self.as_stateless().drag_over_styles {
-                if *state_type == drag.state_type && bounds.contains_point(&mouse_position) {
+                if *state_type == drag.view.entity_type() && bounds.contains_point(&mouse_position)
+                {
                     style.refine(drag_over_style);
                 }
             }
@@ -509,7 +496,7 @@ pub trait ElementInteraction<V: 'static>: 'static + Send {
             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) =
-                        cx.active_drag.as_ref().map(|drag| drag.state_type)
+                        cx.active_drag.as_ref().map(|drag| drag.view.entity_type())
                     {
                         for (drop_state_type, listener) in &drop_listeners {
                             if *drop_state_type == drag_state_type {
@@ -517,7 +504,7 @@ pub trait ElementInteraction<V: 'static>: 'static + Send {
                                     .active_drag
                                     .take()
                                     .expect("checked for type drag state type above");
-                                listener(view, drag.state, cx);
+                                listener(view, drag.view.clone(), cx);
                                 cx.notify();
                                 cx.stop_propagation();
                             }
@@ -685,7 +672,7 @@ impl<V> From<ElementId> for StatefulInteraction<V> {
     }
 }
 
-type DropListener<V> = dyn Fn(&mut V, AnyBox, &mut ViewContext<V>) + 'static + Send;
+type DropListener<V> = dyn Fn(&mut V, AnyView, &mut ViewContext<V>) + 'static + Send;
 
 pub struct StatelessInteraction<V> {
     pub dispatch_context: DispatchContext,
@@ -866,7 +853,7 @@ pub struct Drag<S, R, V, E>
 where
     R: Fn(&mut V, &mut ViewContext<V>) -> E,
     V: 'static,
-    E: Component<V>,
+    E: Component<()>,
 {
     pub state: S,
     pub render_drag_handle: R,
@@ -877,7 +864,7 @@ impl<S, R, V, E> Drag<S, R, V, E>
 where
     R: Fn(&mut V, &mut ViewContext<V>) -> E,
     V: 'static,
-    E: Component<V>,
+    E: Component<()>,
 {
     pub fn new(state: S, render_drag_handle: R) -> Self {
         Drag {
@@ -888,6 +875,10 @@ where
     }
 }
 
+// impl<S, R, V, E> Render for Drag<S, R, V, E> {
+//     // fn render(&mut self, cx: ViewContext<Self>) ->
+// }
+
 #[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
 pub enum MouseButton {
     Left,
@@ -995,6 +986,14 @@ impl Deref for MouseExitEvent {
 #[derive(Debug, Clone, Default)]
 pub struct ExternalPaths(pub(crate) SmallVec<[PathBuf; 2]>);
 
+impl Render for ExternalPaths {
+    type Element = Div<Self>;
+
+    fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
+        div() // Intentionally left empty because the platform will render icons for the dragged files
+    }
+}
+
 #[derive(Debug, Clone)]
 pub enum FileDropEvent {
     Entered {

crates/gpui2/src/view.rs 🔗

@@ -1,50 +1,34 @@
 use crate::{
-    AnyBox, AnyElement, AvailableSpace, BorrowWindow, Bounds, Component, Element, ElementId,
-    EntityId, LayoutId, Model, Pixels, Size, ViewContext, VisualContext, WeakModel, WindowContext,
+    AnyBox, AnyElement, AnyModel, AppContext, AvailableSpace, BorrowWindow, Bounds, Component,
+    Element, ElementId, EntityId, LayoutId, Model, Pixels, Size, ViewContext, VisualContext,
+    WeakModel, WindowContext,
 };
 use anyhow::{Context, Result};
-use parking_lot::Mutex;
-use std::{
-    marker::PhantomData,
-    sync::{Arc, Weak},
-};
+use std::{any::TypeId, marker::PhantomData, sync::Arc};
 
-pub struct View<V> {
-    pub(crate) state: Model<V>,
-    render: Arc<Mutex<dyn Fn(&mut V, &mut ViewContext<V>) -> AnyElement<V> + Send + 'static>>,
+pub trait Render: 'static + Sized {
+    type Element: Element<Self> + 'static + Send;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element;
 }
 
-impl<V: 'static> View<V> {
-    pub fn for_handle<E>(
-        state: Model<V>,
-        render: impl Fn(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static,
-    ) -> View<V>
-    where
-        E: Component<V>,
-    {
-        View {
-            state,
-            render: Arc::new(Mutex::new(
-                move |state: &mut V, cx: &mut ViewContext<'_, '_, V>| render(state, cx).render(),
-            )),
-        }
-    }
+pub struct View<V> {
+    pub(crate) model: Model<V>,
 }
 
-impl<V: 'static> View<V> {
+impl<V: Render> View<V> {
     pub fn into_any(self) -> AnyView {
         AnyView(Arc::new(self))
     }
+}
 
+impl<V: 'static> View<V> {
     pub fn downgrade(&self) -> WeakView<V> {
         WeakView {
-            state: self.state.downgrade(),
-            render: Arc::downgrade(&self.render),
+            model: self.model.downgrade(),
         }
     }
-}
 
-impl<V: 'static> View<V> {
     pub fn update<C, R>(
         &self,
         cx: &mut C,
@@ -55,18 +39,21 @@ impl<V: 'static> View<V> {
     {
         cx.update_view(self, f)
     }
+
+    pub fn read<'a>(&self, cx: &'a AppContext) -> &'a V {
+        self.model.read(cx)
+    }
 }
 
 impl<V> Clone for View<V> {
     fn clone(&self) -> Self {
         Self {
-            state: self.state.clone(),
-            render: self.render.clone(),
+            model: self.model.clone(),
         }
     }
 }
 
-impl<V: 'static, ParentViewState: 'static> Component<ParentViewState> for View<V> {
+impl<V: Render, ParentViewState: 'static> Component<ParentViewState> for View<V> {
     fn render(self) -> AnyElement<ParentViewState> {
         AnyElement::new(EraseViewState {
             view: self,
@@ -75,11 +62,14 @@ impl<V: 'static, ParentViewState: 'static> Component<ParentViewState> for View<V
     }
 }
 
-impl<V: 'static> Element<()> for View<V> {
+impl<V> Element<()> for View<V>
+where
+    V: Render,
+{
     type ElementState = AnyElement<V>;
 
     fn id(&self) -> Option<crate::ElementId> {
-        Some(ElementId::View(self.state.entity_id))
+        Some(ElementId::View(self.model.entity_id))
     }
 
     fn initialize(
@@ -89,7 +79,7 @@ impl<V: 'static> Element<()> for View<V> {
         cx: &mut ViewContext<()>,
     ) -> Self::ElementState {
         self.update(cx, |state, cx| {
-            let mut any_element = (self.render.lock())(state, cx);
+            let mut any_element = AnyElement::new(state.render(cx));
             any_element.initialize(state, cx);
             any_element
         })
@@ -116,15 +106,13 @@ impl<V: 'static> Element<()> for View<V> {
 }
 
 pub struct WeakView<V> {
-    pub(crate) state: WeakModel<V>,
-    render: Weak<Mutex<dyn Fn(&mut V, &mut ViewContext<V>) -> AnyElement<V> + Send + 'static>>,
+    pub(crate) model: WeakModel<V>,
 }
 
 impl<V: 'static> WeakView<V> {
     pub fn upgrade(&self) -> Option<View<V>> {
-        let state = self.state.upgrade()?;
-        let render = self.render.upgrade()?;
-        Some(View { state, render })
+        let model = self.model.upgrade()?;
+        Some(View { model })
     }
 
     pub fn update<R>(
@@ -140,8 +128,7 @@ impl<V: 'static> WeakView<V> {
 impl<V> Clone for WeakView<V> {
     fn clone(&self) -> Self {
         Self {
-            state: self.state.clone(),
-            render: self.render.clone(),
+            model: self.model.clone(),
         }
     }
 }
@@ -153,13 +140,13 @@ struct EraseViewState<V, ParentV> {
 
 unsafe impl<V, ParentV> Send for EraseViewState<V, ParentV> {}
 
-impl<V: 'static, ParentV: 'static> Component<ParentV> for EraseViewState<V, ParentV> {
+impl<V: Render, ParentV: 'static> Component<ParentV> for EraseViewState<V, ParentV> {
     fn render(self) -> AnyElement<ParentV> {
         AnyElement::new(self)
     }
 }
 
-impl<V: 'static, ParentV: 'static> Element<ParentV> for EraseViewState<V, ParentV> {
+impl<V: Render, ParentV: 'static> Element<ParentV> for EraseViewState<V, ParentV> {
     type ElementState = AnyBox;
 
     fn id(&self) -> Option<crate::ElementId> {
@@ -196,23 +183,36 @@ impl<V: 'static, ParentV: 'static> Element<ParentV> for EraseViewState<V, Parent
 }
 
 trait ViewObject: Send + Sync {
+    fn entity_type(&self) -> TypeId;
     fn entity_id(&self) -> EntityId;
+    fn model(&self) -> AnyModel;
     fn initialize(&self, cx: &mut WindowContext) -> AnyBox;
     fn layout(&self, element: &mut AnyBox, cx: &mut WindowContext) -> LayoutId;
     fn paint(&self, bounds: Bounds<Pixels>, element: &mut AnyBox, cx: &mut WindowContext);
 }
 
-impl<V: 'static> ViewObject for View<V> {
+impl<V> ViewObject for View<V>
+where
+    V: Render,
+{
+    fn entity_type(&self) -> TypeId {
+        TypeId::of::<V>()
+    }
+
     fn entity_id(&self) -> EntityId {
-        self.state.entity_id
+        self.model.entity_id
+    }
+
+    fn model(&self) -> AnyModel {
+        self.model.clone().into_any()
     }
 
     fn initialize(&self, cx: &mut WindowContext) -> AnyBox {
         cx.with_element_id(self.entity_id(), |_global_id, cx| {
             self.update(cx, |state, cx| {
-                let mut any_element = Box::new((self.render.lock())(state, cx));
+                let mut any_element = Box::new(AnyElement::new(state.render(cx)));
                 any_element.initialize(state, cx);
-                any_element as AnyBox
+                any_element
             })
         })
     }
@@ -240,6 +240,14 @@ impl<V: 'static> ViewObject for View<V> {
 pub struct AnyView(Arc<dyn ViewObject>);
 
 impl AnyView {
+    pub fn downcast<V: 'static + Send>(self) -> Option<View<V>> {
+        self.0.model().downcast().map(|model| View { model })
+    }
+
+    pub(crate) fn entity_type(&self) -> TypeId {
+        self.0.entity_type()
+    }
+
     pub(crate) fn draw(&self, available_space: Size<AvailableSpace>, cx: &mut WindowContext) {
         let mut rendered_element = self.0.initialize(cx);
         let layout_id = self.0.layout(&mut rendered_element, cx);
@@ -309,6 +317,18 @@ impl<ParentV: 'static> Component<ParentV> for EraseAnyViewState<ParentV> {
     }
 }
 
+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)
+    }
+}
+
 impl<ParentV: 'static> Element<ParentV> for EraseAnyViewState<ParentV> {
     type ElementState = AnyBox;
 

crates/gpui2/src/window.rs 🔗

@@ -1,11 +1,11 @@
 use crate::{
     px, size, Action, AnyBox, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
     Bounds, BoxShadow, Context, Corners, DevicePixels, DispatchContext, DisplayId, Edges, Effect,
-    EntityId, EventEmitter, ExternalPaths, FileDropEvent, FocusEvent, FontId, GlobalElementId,
-    GlyphId, Hsla, ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke,
-    LayoutId, MainThread, MainThreadOnly, Model, ModelContext, Modifiers, MonochromeSprite,
-    MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas,
-    PlatformWindow, Point, PolychromeSprite, Quad, Reference, RenderGlyphParams, RenderImageParams,
+    EntityId, EventEmitter, FileDropEvent, FocusEvent, FontId, GlobalElementId, GlyphId, Hsla,
+    ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeyMatcher, Keystroke, LayoutId,
+    MainThread, MainThreadOnly, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton,
+    MouseDownEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformWindow,
+    Point, PolychromeSprite, Quad, Reference, RenderGlyphParams, RenderImageParams,
     RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, Subscription,
     TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, WeakModel, WeakView,
     WindowOptions, SUBPIXEL_VARIANTS,
@@ -891,15 +891,13 @@ impl<'a, 'w> WindowContext<'a, 'w> {
             root_view.draw(available_space, cx);
         });
 
-        if let Some(mut active_drag) = self.app.active_drag.take() {
+        if let Some(active_drag) = self.app.active_drag.take() {
             self.stack(1, |cx| {
                 let offset = cx.mouse_position() - active_drag.cursor_offset;
                 cx.with_element_offset(Some(offset), |cx| {
                     let available_space =
                         size(AvailableSpace::MinContent, AvailableSpace::MinContent);
-                    if let Some(drag_handle_view) = &mut active_drag.drag_handle_view {
-                        drag_handle_view.draw(available_space, cx);
-                    }
+                    active_drag.view.draw(available_space, cx);
                     cx.active_drag = Some(active_drag);
                 });
             });
@@ -967,12 +965,12 @@ impl<'a, 'w> WindowContext<'a, 'w> {
             InputEvent::FileDrop(file_drop) => match file_drop {
                 FileDropEvent::Entered { position, files } => {
                     self.window.mouse_position = position;
-                    self.active_drag.get_or_insert_with(|| AnyDrag {
-                        drag_handle_view: None,
-                        cursor_offset: position,
-                        state: Box::new(files),
-                        state_type: TypeId::of::<ExternalPaths>(),
-                    });
+                    if self.active_drag.is_none() {
+                        self.active_drag = Some(AnyDrag {
+                            view: self.build_view(|_| files).into_any(),
+                            cursor_offset: position,
+                        });
+                    }
                     InputEvent::MouseDown(MouseDownEvent {
                         position,
                         button: MouseButton::Left,
@@ -1273,21 +1271,17 @@ impl Context for WindowContext<'_, '_> {
 impl VisualContext for WindowContext<'_, '_> {
     type ViewContext<'a, 'w, V> = ViewContext<'a, 'w, V>;
 
-    /// Builds a new view in the current window. The first argument is a function that builds
-    /// an entity representing the view's state. It is invoked with a `ViewContext` that provides
-    /// entity-specific access to the window and application state during construction. The second
-    /// argument is a render function that returns a component based on the view's state.
-    fn build_view<E, V>(
+    fn build_view<V>(
         &mut self,
         build_view_state: impl FnOnce(&mut Self::ViewContext<'_, '_, V>) -> V,
-        render: impl Fn(&mut V, &mut ViewContext<'_, '_, V>) -> E + Send + 'static,
     ) -> Self::Result<View<V>>
     where
-        E: crate::Component<V>,
         V: 'static + Send,
     {
         let slot = self.app.entities.reserve();
-        let view = View::for_handle(slot.clone(), render);
+        let view = View {
+            model: slot.clone(),
+        };
         let mut cx = ViewContext::mutable(&mut *self.app, &mut *self.window, view.downgrade());
         let entity = build_view_state(&mut cx);
         self.entities.insert(slot, entity);
@@ -1300,7 +1294,7 @@ impl VisualContext for WindowContext<'_, '_> {
         view: &View<T>,
         update: impl FnOnce(&mut T, &mut Self::ViewContext<'_, '_, T>) -> R,
     ) -> Self::Result<R> {
-        let mut lease = self.app.entities.lease(&view.state);
+        let mut lease = self.app.entities.lease(&view.model);
         let mut cx = ViewContext::mutable(&mut *self.app, &mut *self.window, view.downgrade());
         let result = update(&mut *lease, &mut cx);
         cx.app.entities.end_lease(lease);
@@ -1556,7 +1550,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> {
     }
 
     pub fn model(&self) -> WeakModel<V> {
-        self.view.state.clone()
+        self.view.model.clone()
     }
 
     pub fn stack<R>(&mut self, order: u32, f: impl FnOnce(&mut Self) -> R) -> R {
@@ -1635,7 +1629,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> {
     ) -> Subscription {
         let window_handle = self.window.handle;
         self.app.release_listeners.insert(
-            self.view.state.entity_id,
+            self.view.model.entity_id,
             Box::new(move |this, cx| {
                 let this = this.downcast_mut().expect("invalid entity type");
                 // todo!("are we okay with silently swallowing the error?")
@@ -1668,7 +1662,7 @@ impl<'a, 'w, V: 'static> ViewContext<'a, 'w, V> {
     pub fn notify(&mut self) {
         self.window_cx.notify();
         self.window_cx.app.push_effect(Effect::Notify {
-            emitter: self.view.state.entity_id,
+            emitter: self.view.model.entity_id,
         });
     }
 
@@ -1848,7 +1842,7 @@ where
     V::Event: Any + Send,
 {
     pub fn emit(&mut self, event: V::Event) {
-        let emitter = self.view.state.entity_id;
+        let emitter = self.view.model.entity_id;
         self.app.push_effect(Effect::Emit {
             emitter,
             event: Box::new(event),
@@ -1882,16 +1876,11 @@ impl<'a, 'w, V> Context for ViewContext<'a, 'w, V> {
 impl<V: 'static> VisualContext for ViewContext<'_, '_, V> {
     type ViewContext<'a, 'w, V2> = ViewContext<'a, 'w, V2>;
 
-    fn build_view<E, V2>(
+    fn build_view<W: 'static + Send>(
         &mut self,
-        build_view: impl FnOnce(&mut Self::ViewContext<'_, '_, V2>) -> V2,
-        render: impl Fn(&mut V2, &mut ViewContext<'_, '_, V2>) -> E + Send + 'static,
-    ) -> Self::Result<View<V2>>
-    where
-        E: crate::Component<V2>,
-        V2: 'static + Send,
-    {
-        self.window_cx.build_view(build_view, render)
+        build_view: impl FnOnce(&mut Self::ViewContext<'_, '_, W>) -> W,
+    ) -> Self::Result<View<W>> {
+        self.window_cx.build_view(build_view)
     }
 
     fn update_view<V2: 'static, R>(

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

@@ -1,13 +1,13 @@
-use gpui2::px;
-use ui::prelude::*;
-
 use crate::story::Story;
+use gpui2::{px, Div, Render};
+use ui::prelude::*;
 
-#[derive(Component)]
 pub struct ColorsStory;
 
-impl ColorsStory {
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+impl Render for ColorsStory {
+    type Element = Div<Self>;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         let color_scales = theme2::default_color_scales();
 
         Story::container(cx)

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

@@ -1,6 +1,6 @@
 use gpui2::{
-    div, Focusable, KeyBinding, ParentElement, StatelessInteractive, Styled, View, VisualContext,
-    WindowContext,
+    div, Div, FocusEnabled, Focusable, KeyBinding, ParentElement, Render, StatefulInteraction,
+    StatelessInteractive, Styled, View, VisualContext, WindowContext,
 };
 use serde::Deserialize;
 use theme2::theme;
@@ -14,12 +14,10 @@ struct ActionB;
 #[derive(Clone, Default, PartialEq, Deserialize)]
 struct ActionC;
 
-pub struct FocusStory {
-    text: View<()>,
-}
+pub struct FocusStory {}
 
 impl FocusStory {
-    pub fn view(cx: &mut WindowContext) -> View<()> {
+    pub fn view(cx: &mut WindowContext) -> View<Self> {
         cx.bind_keys([
             KeyBinding::new("cmd-a", ActionA, Some("parent")),
             KeyBinding::new("cmd-a", ActionB, Some("child-1")),
@@ -27,8 +25,16 @@ impl FocusStory {
         ]);
         cx.register_action_type::<ActionA>();
         cx.register_action_type::<ActionB>();
-        let theme = theme(cx);
 
+        cx.build_view(move |cx| Self {})
+    }
+}
+
+impl Render for FocusStory {
+    type Element = Div<Self, StatefulInteraction<Self>, FocusEnabled<Self>>;
+
+    fn render(&mut self, cx: &mut gpui2::ViewContext<Self>) -> Self::Element {
+        let theme = theme(cx);
         let color_1 = theme.git_created;
         let color_2 = theme.git_modified;
         let color_3 = theme.git_deleted;
@@ -38,80 +44,73 @@ impl FocusStory {
         let child_1 = cx.focus_handle();
         let child_2 = cx.focus_handle();
 
-        cx.build_view(
-            |_| (),
-            move |_, cx| {
+        div()
+            .id("parent")
+            .focusable()
+            .context("parent")
+            .on_action(|_, action: &ActionA, phase, cx| {
+                println!("Action A dispatched on parent during {:?}", phase);
+            })
+            .on_action(|_, action: &ActionB, phase, cx| {
+                println!("Action B dispatched on parent during {:?}", phase);
+            })
+            .on_focus(|_, _, _| println!("Parent focused"))
+            .on_blur(|_, _, _| println!("Parent blurred"))
+            .on_focus_in(|_, _, _| println!("Parent focus_in"))
+            .on_focus_out(|_, _, _| println!("Parent focus_out"))
+            .on_key_down(|_, event, phase, _| {
+                println!("Key down on parent {:?} {:?}", phase, event)
+            })
+            .on_key_up(|_, event, phase, _| println!("Key up on parent {:?} {:?}", phase, event))
+            .size_full()
+            .bg(color_1)
+            .focus(|style| style.bg(color_2))
+            .focus_in(|style| style.bg(color_3))
+            .child(
                 div()
-                    .id("parent")
-                    .focusable()
-                    .context("parent")
-                    .on_action(|_, action: &ActionA, phase, cx| {
-                        println!("Action A dispatched on parent during {:?}", phase);
-                    })
+                    .track_focus(&child_1)
+                    .context("child-1")
                     .on_action(|_, action: &ActionB, phase, cx| {
-                        println!("Action B dispatched on parent during {:?}", phase);
+                        println!("Action B dispatched on child 1 during {:?}", phase);
+                    })
+                    .w_full()
+                    .h_6()
+                    .bg(color_4)
+                    .focus(|style| style.bg(color_5))
+                    .in_focus(|style| style.bg(color_6))
+                    .on_focus(|_, _, _| println!("Child 1 focused"))
+                    .on_blur(|_, _, _| println!("Child 1 blurred"))
+                    .on_focus_in(|_, _, _| println!("Child 1 focus_in"))
+                    .on_focus_out(|_, _, _| println!("Child 1 focus_out"))
+                    .on_key_down(|_, event, phase, _| {
+                        println!("Key down on child 1 {:?} {:?}", phase, event)
+                    })
+                    .on_key_up(|_, event, phase, _| {
+                        println!("Key up on child 1 {:?} {:?}", phase, event)
+                    })
+                    .child("Child 1"),
+            )
+            .child(
+                div()
+                    .track_focus(&child_2)
+                    .context("child-2")
+                    .on_action(|_, action: &ActionC, phase, cx| {
+                        println!("Action C dispatched on child 2 during {:?}", phase);
                     })
-                    .on_focus(|_, _, _| println!("Parent focused"))
-                    .on_blur(|_, _, _| println!("Parent blurred"))
-                    .on_focus_in(|_, _, _| println!("Parent focus_in"))
-                    .on_focus_out(|_, _, _| println!("Parent focus_out"))
+                    .w_full()
+                    .h_6()
+                    .bg(color_4)
+                    .on_focus(|_, _, _| println!("Child 2 focused"))
+                    .on_blur(|_, _, _| println!("Child 2 blurred"))
+                    .on_focus_in(|_, _, _| println!("Child 2 focus_in"))
+                    .on_focus_out(|_, _, _| println!("Child 2 focus_out"))
                     .on_key_down(|_, event, phase, _| {
-                        println!("Key down on parent {:?} {:?}", phase, event)
+                        println!("Key down on child 2 {:?} {:?}", phase, event)
                     })
                     .on_key_up(|_, event, phase, _| {
-                        println!("Key up on parent {:?} {:?}", phase, event)
+                        println!("Key up on child 2 {:?} {:?}", phase, event)
                     })
-                    .size_full()
-                    .bg(color_1)
-                    .focus(|style| style.bg(color_2))
-                    .focus_in(|style| style.bg(color_3))
-                    .child(
-                        div()
-                            .track_focus(&child_1)
-                            .context("child-1")
-                            .on_action(|_, action: &ActionB, phase, cx| {
-                                println!("Action B dispatched on child 1 during {:?}", phase);
-                            })
-                            .w_full()
-                            .h_6()
-                            .bg(color_4)
-                            .focus(|style| style.bg(color_5))
-                            .in_focus(|style| style.bg(color_6))
-                            .on_focus(|_, _, _| println!("Child 1 focused"))
-                            .on_blur(|_, _, _| println!("Child 1 blurred"))
-                            .on_focus_in(|_, _, _| println!("Child 1 focus_in"))
-                            .on_focus_out(|_, _, _| println!("Child 1 focus_out"))
-                            .on_key_down(|_, event, phase, _| {
-                                println!("Key down on child 1 {:?} {:?}", phase, event)
-                            })
-                            .on_key_up(|_, event, phase, _| {
-                                println!("Key up on child 1 {:?} {:?}", phase, event)
-                            })
-                            .child("Child 1"),
-                    )
-                    .child(
-                        div()
-                            .track_focus(&child_2)
-                            .context("child-2")
-                            .on_action(|_, action: &ActionC, phase, cx| {
-                                println!("Action C dispatched on child 2 during {:?}", phase);
-                            })
-                            .w_full()
-                            .h_6()
-                            .bg(color_4)
-                            .on_focus(|_, _, _| println!("Child 2 focused"))
-                            .on_blur(|_, _, _| println!("Child 2 blurred"))
-                            .on_focus_in(|_, _, _| println!("Child 2 focus_in"))
-                            .on_focus_out(|_, _, _| println!("Child 2 focus_out"))
-                            .on_key_down(|_, event, phase, _| {
-                                println!("Key down on child 2 {:?} {:?}", phase, event)
-                            })
-                            .on_key_up(|_, event, phase, _| {
-                                println!("Key up on child 2 {:?} {:?}", phase, event)
-                            })
-                            .child("Child 2"),
-                    )
-            },
-        )
+                    .child("Child 2"),
+            )
     }
 }

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

@@ -1,26 +1,23 @@
-use gpui2::{AppContext, Context, View};
+use crate::{
+    story::Story,
+    story_selector::{ComponentStory, ElementStory},
+};
+use gpui2::{Div, Render, StatefulInteraction, View, VisualContext};
 use strum::IntoEnumIterator;
 use ui::prelude::*;
 
-use crate::story::Story;
-use crate::story_selector::{ComponentStory, ElementStory};
-
-pub struct KitchenSinkStory {}
+pub struct KitchenSinkStory;
 
 impl KitchenSinkStory {
-    pub fn new() -> Self {
-        Self {}
+    pub fn view(cx: &mut WindowContext) -> View<Self> {
+        cx.build_view(|cx| Self)
     }
+}
 
-    pub fn view(cx: &mut AppContext) -> View<Self> {
-        {
-            let state = cx.build_model(|cx| Self::new());
-            let render = Self::render;
-            View::for_handle(state, render)
-        }
-    }
+impl Render for KitchenSinkStory {
+    type Element = Div<Self, StatefulInteraction<Self>>;
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         let element_stories = ElementStory::iter()
             .map(|selector| selector.story(cx))
             .collect::<Vec<_>>();

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

@@ -1,54 +1,54 @@
 use gpui2::{
-    div, px, Component, ParentElement, SharedString, Styled, View, VisualContext, WindowContext,
+    div, px, Component, Div, ParentElement, Render, SharedString, StatefulInteraction, Styled,
+    View, VisualContext, WindowContext,
 };
 use theme2::theme;
 
-pub struct ScrollStory {
-    text: View<()>,
-}
+pub struct ScrollStory;
 
 impl ScrollStory {
-    pub fn view(cx: &mut WindowContext) -> View<()> {
-        cx.build_view(|cx| (), move |_, cx| checkerboard(cx, 1))
+    pub fn view(cx: &mut WindowContext) -> View<ScrollStory> {
+        cx.build_view(|cx| ScrollStory)
     }
 }
 
-fn checkerboard<S>(cx: &mut WindowContext, depth: usize) -> impl Component<S>
-where
-    S: 'static + Send + Sync,
-{
-    let theme = theme(cx);
-    let color_1 = theme.git_created;
-    let color_2 = theme.git_modified;
+impl Render for ScrollStory {
+    type Element = Div<Self, StatefulInteraction<Self>>;
+
+    fn render(&mut self, cx: &mut gpui2::ViewContext<Self>) -> Self::Element {
+        let theme = theme(cx);
+        let color_1 = theme.git_created;
+        let color_2 = theme.git_modified;
 
-    div()
-        .id("parent")
-        .bg(theme.background)
-        .size_full()
-        .overflow_scroll()
-        .children((0..10).map(|row| {
-            div()
-                .w(px(1000.))
-                .h(px(100.))
-                .flex()
-                .flex_row()
-                .children((0..10).map(|column| {
-                    let id = SharedString::from(format!("{}, {}", row, column));
-                    let bg = if row % 2 == column % 2 {
-                        color_1
-                    } else {
-                        color_2
-                    };
-                    div().id(id).bg(bg).size(px(100. / depth as f32)).when(
-                        row >= 5 && column >= 5,
-                        |d| {
-                            d.overflow_scroll()
-                                .child(div().size(px(50.)).bg(color_1))
-                                .child(div().size(px(50.)).bg(color_2))
-                                .child(div().size(px(50.)).bg(color_1))
-                                .child(div().size(px(50.)).bg(color_2))
-                        },
-                    )
-                }))
-        }))
+        div()
+            .id("parent")
+            .bg(theme.background)
+            .size_full()
+            .overflow_scroll()
+            .children((0..10).map(|row| {
+                div()
+                    .w(px(1000.))
+                    .h(px(100.))
+                    .flex()
+                    .flex_row()
+                    .children((0..10).map(|column| {
+                        let id = SharedString::from(format!("{}, {}", row, column));
+                        let bg = if row % 2 == column % 2 {
+                            color_1
+                        } else {
+                            color_2
+                        };
+                        div().id(id).bg(bg).size(px(100. as f32)).when(
+                            row >= 5 && column >= 5,
+                            |d| {
+                                d.overflow_scroll()
+                                    .child(div().size(px(50.)).bg(color_1))
+                                    .child(div().size(px(50.)).bg(color_2))
+                                    .child(div().size(px(50.)).bg(color_1))
+                                    .child(div().size(px(50.)).bg(color_2))
+                            },
+                        )
+                    }))
+            }))
+    }
 }

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

@@ -1,20 +1,21 @@
-use gpui2::{div, white, ParentElement, Styled, View, VisualContext, WindowContext};
+use gpui2::{div, white, Div, ParentElement, Render, Styled, View, VisualContext, WindowContext};
 
-pub struct TextStory {
-    text: View<()>,
-}
+pub struct TextStory;
 
 impl TextStory {
-    pub fn view(cx: &mut WindowContext) -> View<()> {
-        cx.build_view(|cx| (), |_, cx| {
-            div()
-                .size_full()
-                .bg(white())
-                .child(concat!(
-                    "The quick brown fox jumps over the lazy dog. ",
-                    "Meanwhile, the lazy dog decided it was time for a change. ",
-                    "He started daily workout routines, ate healthier and became the fastest dog in town.",
-                ))
-        })
+    pub fn view(cx: &mut WindowContext) -> View<Self> {
+        cx.build_view(|cx| Self)
+    }
+}
+
+impl Render for TextStory {
+    type Element = Div<Self>;
+
+    fn render(&mut self, cx: &mut gpui2::ViewContext<Self>) -> Self::Element {
+        div().size_full().bg(white()).child(concat!(
+            "The quick brown fox jumps over the lazy dog. ",
+            "Meanwhile, the lazy dog decided it was time for a change. ",
+            "He started daily workout routines, ate healthier and became the fastest dog in town.",
+        ))
     }
 }

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

@@ -1,15 +1,16 @@
-use gpui2::{px, rgb, Div, Hsla};
+use gpui2::{px, rgb, Div, Hsla, Render};
 use ui::prelude::*;
 
 use crate::story::Story;
 
 /// A reimplementation of the MDN `z-index` example, found here:
 /// [https://developer.mozilla.org/en-US/docs/Web/CSS/z-index](https://developer.mozilla.org/en-US/docs/Web/CSS/z-index).
-#[derive(Component)]
 pub struct ZIndexStory;
 
-impl ZIndexStory {
-    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+impl Render for ZIndexStory {
+    type Element = Div<Self>;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         Story::container(cx)
             .child(Story::title(cx, "z-index"))
             .child(

crates/storybook2/src/story_selector.rs 🔗

@@ -7,7 +7,7 @@ use clap::builder::PossibleValue;
 use clap::ValueEnum;
 use gpui2::{AnyView, VisualContext};
 use strum::{EnumIter, EnumString, IntoEnumIterator};
-use ui::prelude::*;
+use ui::{prelude::*, AvatarStory, ButtonStory, DetailsStory, IconStory, InputStory, LabelStory};
 
 #[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)]
 #[strum(serialize_all = "snake_case")]
@@ -28,19 +28,17 @@ pub enum ElementStory {
 impl ElementStory {
     pub fn story(&self, cx: &mut WindowContext) -> AnyView {
         match self {
-            Self::Avatar => { cx.build_view(|cx| (), |_, _| ui::AvatarStory.render()) }.into_any(),
-            Self::Button => { cx.build_view(|cx| (), |_, _| ui::ButtonStory.render()) }.into_any(),
-            Self::Colors => { cx.build_view(|cx| (), |_, _| ColorsStory.render()) }.into_any(),
-            Self::Details => {
-                { cx.build_view(|cx| (), |_, _| ui::DetailsStory.render()) }.into_any()
-            }
+            Self::Colors => cx.build_view(|_| ColorsStory).into_any(),
+            Self::Avatar => cx.build_view(|_| AvatarStory).into_any(),
+            Self::Button => cx.build_view(|_| ButtonStory).into_any(),
+            Self::Details => cx.build_view(|_| DetailsStory).into_any(),
             Self::Focus => FocusStory::view(cx).into_any(),
-            Self::Icon => { cx.build_view(|cx| (), |_, _| ui::IconStory.render()) }.into_any(),
-            Self::Input => { cx.build_view(|cx| (), |_, _| ui::InputStory.render()) }.into_any(),
-            Self::Label => { cx.build_view(|cx| (), |_, _| ui::LabelStory.render()) }.into_any(),
+            Self::Icon => cx.build_view(|_| IconStory).into_any(),
+            Self::Input => cx.build_view(|_| InputStory).into_any(),
+            Self::Label => cx.build_view(|_| LabelStory).into_any(),
             Self::Scroll => ScrollStory::view(cx).into_any(),
             Self::Text => TextStory::view(cx).into_any(),
-            Self::ZIndex => { cx.build_view(|cx| (), |_, _| ZIndexStory.render()) }.into_any(),
+            Self::ZIndex => cx.build_view(|_| ZIndexStory).into_any(),
         }
     }
 }
@@ -79,69 +77,31 @@ pub enum ComponentStory {
 impl ComponentStory {
     pub fn story(&self, cx: &mut WindowContext) -> AnyView {
         match self {
-            Self::AssistantPanel => {
-                { cx.build_view(|cx| (), |_, _| ui::AssistantPanelStory.render()) }.into_any()
-            }
-            Self::Buffer => { cx.build_view(|cx| (), |_, _| ui::BufferStory.render()) }.into_any(),
-            Self::Breadcrumb => {
-                { cx.build_view(|cx| (), |_, _| ui::BreadcrumbStory.render()) }.into_any()
-            }
-            Self::ChatPanel => {
-                { cx.build_view(|cx| (), |_, _| ui::ChatPanelStory.render()) }.into_any()
-            }
-            Self::CollabPanel => {
-                { cx.build_view(|cx| (), |_, _| ui::CollabPanelStory.render()) }.into_any()
-            }
-            Self::CommandPalette => {
-                { cx.build_view(|cx| (), |_, _| ui::CommandPaletteStory.render()) }.into_any()
-            }
-            Self::ContextMenu => {
-                { cx.build_view(|cx| (), |_, _| ui::ContextMenuStory.render()) }.into_any()
-            }
-            Self::Facepile => {
-                { cx.build_view(|cx| (), |_, _| ui::FacepileStory.render()) }.into_any()
-            }
-            Self::Keybinding => {
-                { cx.build_view(|cx| (), |_, _| ui::KeybindingStory.render()) }.into_any()
-            }
-            Self::LanguageSelector => {
-                { cx.build_view(|cx| (), |_, _| ui::LanguageSelectorStory.render()) }.into_any()
-            }
-            Self::MultiBuffer => {
-                { cx.build_view(|cx| (), |_, _| ui::MultiBufferStory.render()) }.into_any()
-            }
-            Self::NotificationsPanel => {
-                { cx.build_view(|cx| (), |_, _| ui::NotificationsPanelStory.render()) }.into_any()
-            }
-            Self::Palette => {
-                { cx.build_view(|cx| (), |_, _| ui::PaletteStory.render()) }.into_any()
-            }
-            Self::Panel => { cx.build_view(|cx| (), |_, _| ui::PanelStory.render()) }.into_any(),
-            Self::ProjectPanel => {
-                { cx.build_view(|cx| (), |_, _| ui::ProjectPanelStory.render()) }.into_any()
-            }
-            Self::RecentProjects => {
-                { cx.build_view(|cx| (), |_, _| ui::RecentProjectsStory.render()) }.into_any()
-            }
-            Self::Tab => { cx.build_view(|cx| (), |_, _| ui::TabStory.render()) }.into_any(),
-            Self::TabBar => { cx.build_view(|cx| (), |_, _| ui::TabBarStory.render()) }.into_any(),
-            Self::Terminal => {
-                { cx.build_view(|cx| (), |_, _| ui::TerminalStory.render()) }.into_any()
-            }
-            Self::ThemeSelector => {
-                { cx.build_view(|cx| (), |_, _| ui::ThemeSelectorStory.render()) }.into_any()
-            }
+            Self::AssistantPanel => cx.build_view(|_| ui::AssistantPanelStory).into_any(),
+            Self::Buffer => cx.build_view(|_| ui::BufferStory).into_any(),
+            Self::Breadcrumb => cx.build_view(|_| ui::BreadcrumbStory).into_any(),
+            Self::ChatPanel => cx.build_view(|_| ui::ChatPanelStory).into_any(),
+            Self::CollabPanel => cx.build_view(|_| ui::CollabPanelStory).into_any(),
+            Self::CommandPalette => cx.build_view(|_| ui::CommandPaletteStory).into_any(),
+            Self::ContextMenu => cx.build_view(|_| ui::ContextMenuStory).into_any(),
+            Self::Facepile => cx.build_view(|_| ui::FacepileStory).into_any(),
+            Self::Keybinding => cx.build_view(|_| ui::KeybindingStory).into_any(),
+            Self::LanguageSelector => cx.build_view(|_| ui::LanguageSelectorStory).into_any(),
+            Self::MultiBuffer => cx.build_view(|_| ui::MultiBufferStory).into_any(),
+            Self::NotificationsPanel => cx.build_view(|cx| ui::NotificationsPanelStory).into_any(),
+            Self::Palette => cx.build_view(|cx| ui::PaletteStory).into_any(),
+            Self::Panel => cx.build_view(|cx| ui::PanelStory).into_any(),
+            Self::ProjectPanel => cx.build_view(|_| ui::ProjectPanelStory).into_any(),
+            Self::RecentProjects => cx.build_view(|_| ui::RecentProjectsStory).into_any(),
+            Self::Tab => cx.build_view(|_| ui::TabStory).into_any(),
+            Self::TabBar => cx.build_view(|_| ui::TabBarStory).into_any(),
+            Self::Terminal => cx.build_view(|_| ui::TerminalStory).into_any(),
+            Self::ThemeSelector => cx.build_view(|_| ui::ThemeSelectorStory).into_any(),
+            Self::Toast => cx.build_view(|_| ui::ToastStory).into_any(),
+            Self::Toolbar => cx.build_view(|_| ui::ToolbarStory).into_any(),
+            Self::TrafficLights => cx.build_view(|_| ui::TrafficLightsStory).into_any(),
+            Self::Copilot => cx.build_view(|_| ui::CopilotModalStory).into_any(),
             Self::TitleBar => ui::TitleBarStory::view(cx).into_any(),
-            Self::Toast => { cx.build_view(|cx| (), |_, _| ui::ToastStory.render()) }.into_any(),
-            Self::Toolbar => {
-                { cx.build_view(|cx| (), |_, _| ui::ToolbarStory.render()) }.into_any()
-            }
-            Self::TrafficLights => {
-                { cx.build_view(|cx| (), |_, _| ui::TrafficLightsStory.render()) }.into_any()
-            }
-            Self::Copilot => {
-                { cx.build_view(|cx| (), |_, _| ui::CopilotModalStory.render()) }.into_any()
-            }
             Self::Workspace => ui::WorkspaceStory::view(cx).into_any(),
         }
     }

crates/storybook2/src/storybook2.rs 🔗

@@ -9,8 +9,8 @@ use std::sync::Arc;
 
 use clap::Parser;
 use gpui2::{
-    div, px, size, AnyView, AppContext, Bounds, ViewContext, VisualContext, WindowBounds,
-    WindowOptions,
+    div, px, size, AnyView, AppContext, Bounds, Div, Render, ViewContext, VisualContext,
+    WindowBounds, WindowOptions,
 };
 use log::LevelFilter;
 use settings2::{default_settings, Settings, SettingsStore};
@@ -82,12 +82,7 @@ fn main() {
                 }),
                 ..Default::default()
             },
-            move |cx| {
-                cx.build_view(
-                    |cx| StoryWrapper::new(selector.story(cx)),
-                    StoryWrapper::render,
-                )
-            },
+            move |cx| cx.build_view(|cx| StoryWrapper::new(selector.story(cx))),
         );
 
         cx.activate(true);
@@ -103,8 +98,12 @@ impl StoryWrapper {
     pub(crate) fn new(story: AnyView) -> Self {
         Self { story }
     }
+}
+
+impl Render for StoryWrapper {
+    type Element = Div<Self>;
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
         div()
             .flex()
             .flex_col()

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

@@ -1,7 +1,6 @@
-use gpui2::{rems, AbsoluteLength};
-
 use crate::prelude::*;
 use crate::{Icon, IconButton, Label, Panel, PanelSide};
+use gpui2::{rems, AbsoluteLength};
 
 #[derive(Component)]
 pub struct AssistantPanel {
@@ -76,15 +75,15 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::Story;
-
     use super::*;
-
-    #[derive(Component)]
+    use crate::Story;
+    use gpui2::{Div, Render};
     pub struct AssistantPanelStory;
 
-    impl AssistantPanelStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for AssistantPanelStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, AssistantPanel>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -73,21 +73,17 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use std::str::FromStr;
-
-    use crate::Story;
-
     use super::*;
+    use crate::Story;
+    use gpui2::Render;
+    use std::str::FromStr;
 
-    #[derive(Component)]
     pub struct BreadcrumbStory;
 
-    impl BreadcrumbStory {
-        fn render<V: 'static>(
-            self,
-            view_state: &mut V,
-            cx: &mut ViewContext<V>,
-        ) -> impl Component<V> {
+    impl Render for BreadcrumbStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             let theme = theme(cx);
 
             Story::container(cx)

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

@@ -233,20 +233,19 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use gpui2::rems;
-
+    use super::*;
     use crate::{
         empty_buffer_example, hello_world_rust_buffer_example,
         hello_world_rust_buffer_with_status_example, Story,
     };
+    use gpui2::{rems, Div, Render};
 
-    use super::*;
-
-    #[derive(Component)]
     pub struct BufferStory;
 
-    impl BufferStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for BufferStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             let theme = theme(cx);
 
             Story::container(cx)

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

@@ -1,4 +1,4 @@
-use gpui2::{AppContext, Context, View};
+use gpui2::{Div, Render, View, VisualContext};
 
 use crate::prelude::*;
 use crate::{h_stack, Icon, IconButton, IconColor, Input};
@@ -21,15 +21,15 @@ impl BufferSearch {
         cx.notify();
     }
 
-    pub fn view(cx: &mut AppContext) -> View<Self> {
-        {
-            let state = cx.build_model(|cx| Self::new());
-            let render = Self::render;
-            View::for_handle(state, render)
-        }
+    pub fn view(cx: &mut WindowContext) -> View<Self> {
+        cx.build_view(|cx| Self::new())
     }
+}
+
+impl Render for BufferSearch {
+    type Element = Div<Self>;
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {
         let theme = theme(cx);
 
         h_stack().bg(theme.toolbar).p_2().child(

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

@@ -108,16 +108,18 @@ pub use stories::*;
 #[cfg(feature = "stories")]
 mod stories {
     use chrono::DateTime;
+    use gpui2::{Div, Render};
 
     use crate::{Panel, Story};
 
     use super::*;
 
-    #[derive(Component)]
     pub struct ChatPanelStory;
 
-    impl ChatPanelStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for ChatPanelStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, ChatPanel>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -89,15 +89,16 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::Story;
-
     use super::*;
+    use crate::Story;
+    use gpui2::{Div, Render};
 
-    #[derive(Component)]
     pub struct CollabPanelStory;
 
-    impl CollabPanelStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for CollabPanelStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, CollabPanel>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -27,15 +27,18 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
+    use gpui2::{Div, Render};
+
     use crate::Story;
 
     use super::*;
 
-    #[derive(Component)]
     pub struct CommandPaletteStory;
 
-    impl CommandPaletteStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for CommandPaletteStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, CommandPalette>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -68,15 +68,16 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::story::Story;
-
     use super::*;
+    use crate::story::Story;
+    use gpui2::{Div, Render};
 
-    #[derive(Component)]
     pub struct ContextMenuStory;
 
-    impl ContextMenuStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for ContextMenuStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, ContextMenu>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -25,15 +25,18 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
+    use gpui2::{Div, Render};
+
     use crate::Story;
 
     use super::*;
 
-    #[derive(Component)]
     pub struct CopilotModalStory;
 
-    impl CopilotModalStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for CopilotModalStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, CopilotModal>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -1,6 +1,6 @@
 use std::path::PathBuf;
 
-use gpui2::{AppContext, Context, View};
+use gpui2::{Div, Render, View, VisualContext};
 
 use crate::prelude::*;
 use crate::{
@@ -20,7 +20,7 @@ pub struct EditorPane {
 
 impl EditorPane {
     pub fn new(
-        cx: &mut AppContext,
+        cx: &mut ViewContext<Self>,
         tabs: Vec<Tab>,
         path: PathBuf,
         symbols: Vec<Symbol>,
@@ -42,15 +42,15 @@ impl EditorPane {
         cx.notify();
     }
 
-    pub fn view(cx: &mut AppContext) -> View<Self> {
-        {
-            let state = cx.build_model(|cx| hello_world_rust_editor_with_status_example(cx));
-            let render = Self::render;
-            View::for_handle(state, render)
-        }
+    pub fn view(cx: &mut WindowContext) -> View<Self> {
+        cx.build_view(|cx| hello_world_rust_editor_with_status_example(cx))
     }
+}
+
+impl Render for EditorPane {
+    type Element = Div<Self>;
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {
         v_stack()
             .w_full()
             .h_full()

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

@@ -31,15 +31,16 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::{static_players, Story};
-
     use super::*;
+    use crate::{static_players, Story};
+    use gpui2::{Div, Render};
 
-    #[derive(Component)]
     pub struct FacepileStory;
 
-    impl FacepileStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for FacepileStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             let players = static_players();
 
             Story::container(cx)

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

@@ -158,17 +158,17 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use itertools::Itertools;
-
-    use crate::Story;
-
     use super::*;
+    use crate::Story;
+    use gpui2::{Div, Render};
+    use itertools::Itertools;
 
-    #[derive(Component)]
     pub struct KeybindingStory;
 
-    impl KeybindingStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for KeybindingStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             let all_modifier_permutations = ModifierKey::iter().permutations(2);
 
             Story::container(cx)

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

@@ -38,15 +38,16 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::Story;
-
     use super::*;
+    use crate::Story;
+    use gpui2::{Div, Render};
 
-    #[derive(Component)]
     pub struct LanguageSelectorStory;
 
-    impl LanguageSelectorStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for LanguageSelectorStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, LanguageSelector>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -40,15 +40,16 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::{hello_world_rust_buffer_example, Story};
-
     use super::*;
+    use crate::{hello_world_rust_buffer_example, Story};
+    use gpui2::{Div, Render};
 
-    #[derive(Component)]
     pub struct MultiBufferStory;
 
-    impl MultiBufferStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for MultiBufferStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             let theme = theme(cx);
 
             Story::container(cx)

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

@@ -48,15 +48,16 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::{Panel, Story};
-
     use super::*;
+    use crate::{Panel, Story};
+    use gpui2::{Div, Render};
 
-    #[derive(Component)]
     pub struct NotificationsPanelStory;
 
-    impl NotificationsPanelStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for NotificationsPanelStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, NotificationsPanel>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -152,58 +152,71 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
+    use gpui2::{Div, Render};
+
     use crate::{ModifierKeys, Story};
 
     use super::*;
 
-    #[derive(Component)]
     pub struct PaletteStory;
 
-    impl PaletteStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
-            Story::container(cx)
-                .child(Story::title_for::<_, Palette>(cx))
-                .child(Story::label(cx, "Default"))
-                .child(Palette::new("palette-1"))
-                .child(Story::label(cx, "With Items"))
-                .child(
-                    Palette::new("palette-2")
-                        .placeholder("Execute a command...")
-                        .items(vec![
-                            PaletteItem::new("theme selector: toggle").keybinding(
-                                Keybinding::new_chord(
-                                    ("k".to_string(), ModifierKeys::new().command(true)),
-                                    ("t".to_string(), ModifierKeys::new().command(true)),
+    impl Render for PaletteStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+            {
+                Story::container(cx)
+                    .child(Story::title_for::<_, Palette>(cx))
+                    .child(Story::label(cx, "Default"))
+                    .child(Palette::new("palette-1"))
+                    .child(Story::label(cx, "With Items"))
+                    .child(
+                        Palette::new("palette-2")
+                            .placeholder("Execute a command...")
+                            .items(vec![
+                                PaletteItem::new("theme selector: toggle").keybinding(
+                                    Keybinding::new_chord(
+                                        ("k".to_string(), ModifierKeys::new().command(true)),
+                                        ("t".to_string(), ModifierKeys::new().command(true)),
+                                    ),
+                                ),
+                                PaletteItem::new("assistant: inline assist").keybinding(
+                                    Keybinding::new(
+                                        "enter".to_string(),
+                                        ModifierKeys::new().command(true),
+                                    ),
+                                ),
+                                PaletteItem::new("assistant: quote selection").keybinding(
+                                    Keybinding::new(
+                                        ">".to_string(),
+                                        ModifierKeys::new().command(true),
+                                    ),
+                                ),
+                                PaletteItem::new("assistant: toggle focus").keybinding(
+                                    Keybinding::new(
+                                        "?".to_string(),
+                                        ModifierKeys::new().command(true),
+                                    ),
                                 ),
-                            ),
-                            PaletteItem::new("assistant: inline assist").keybinding(
-                                Keybinding::new(
-                                    "enter".to_string(),
-                                    ModifierKeys::new().command(true),
+                                PaletteItem::new("auto update: check"),
+                                PaletteItem::new("auto update: view release notes"),
+                                PaletteItem::new("branches: open recent").keybinding(
+                                    Keybinding::new(
+                                        "b".to_string(),
+                                        ModifierKeys::new().command(true).alt(true),
+                                    ),
                                 ),
-                            ),
-                            PaletteItem::new("assistant: quote selection").keybinding(
-                                Keybinding::new(">".to_string(), ModifierKeys::new().command(true)),
-                            ),
-                            PaletteItem::new("assistant: toggle focus").keybinding(
-                                Keybinding::new("?".to_string(), ModifierKeys::new().command(true)),
-                            ),
-                            PaletteItem::new("auto update: check"),
-                            PaletteItem::new("auto update: view release notes"),
-                            PaletteItem::new("branches: open recent").keybinding(Keybinding::new(
-                                "b".to_string(),
-                                ModifierKeys::new().command(true).alt(true),
-                            )),
-                            PaletteItem::new("chat panel: toggle focus"),
-                            PaletteItem::new("cli: install"),
-                            PaletteItem::new("client: sign in"),
-                            PaletteItem::new("client: sign out"),
-                            PaletteItem::new("editor: cancel").keybinding(Keybinding::new(
-                                "escape".to_string(),
-                                ModifierKeys::new(),
-                            )),
-                        ]),
-                )
+                                PaletteItem::new("chat panel: toggle focus"),
+                                PaletteItem::new("cli: install"),
+                                PaletteItem::new("client: sign in"),
+                                PaletteItem::new("client: sign out"),
+                                PaletteItem::new("editor: cancel").keybinding(Keybinding::new(
+                                    "escape".to_string(),
+                                    ModifierKeys::new(),
+                                )),
+                            ]),
+                    )
+            }
         }
     }
 }

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

@@ -128,17 +128,18 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::{Label, Story};
-
     use super::*;
+    use crate::{Label, Story};
+    use gpui2::{Div, Render};
 
-    #[derive(Component)]
     pub struct PanelStory;
 
-    impl PanelStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for PanelStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
-                .child(Story::title_for::<_, Panel<V>>(cx))
+                .child(Story::title_for::<_, Panel<Self>>(cx))
                 .child(Story::label(cx, "Default"))
                 .child(
                     Panel::new("panel", cx).child(

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

@@ -1,4 +1,4 @@
-use gpui2::{hsla, red, AnyElement, ElementId, ExternalPaths, Hsla, Length, Size};
+use gpui2::{hsla, red, AnyElement, ElementId, ExternalPaths, Hsla, Length, Size, View};
 use smallvec::SmallVec;
 
 use crate::prelude::*;
@@ -18,13 +18,6 @@ pub struct Pane<V: 'static> {
     children: SmallVec<[AnyElement<V>; 2]>,
 }
 
-// impl<V: 'static> IntoAnyElement<V> for Pane<V> {
-//     fn into_any(self) -> AnyElement<V> {
-//         (move |view_state: &mut V, cx: &mut ViewContext<'_, '_, V>| self.render(view_state, cx))
-//             .into_any()
-//     }
-// }
-
 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
@@ -57,8 +50,8 @@ impl<V: 'static> Pane<V> {
                     .z_index(1)
                     .id("drag-target")
                     .drag_over::<ExternalPaths>(|d| d.bg(red()))
-                    .on_drop(|_, files: ExternalPaths, _| {
-                        dbg!("dropped files!", files);
+                    .on_drop(|_, files: View<ExternalPaths>, cx| {
+                        dbg!("dropped files!", files.read(cx));
                     })
                     .absolute()
                     .inset_0(),

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

@@ -57,15 +57,16 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::{Panel, Story};
-
     use super::*;
+    use crate::{Panel, Story};
+    use gpui2::{Div, Render};
 
-    #[derive(Component)]
     pub struct ProjectPanelStory;
 
-    impl ProjectPanelStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for ProjectPanelStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, ProjectPanel>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -34,15 +34,16 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::Story;
-
     use super::*;
+    use crate::Story;
+    use gpui2::{Div, Render};
 
-    #[derive(Component)]
     pub struct RecentProjectsStory;
 
-    impl RecentProjectsStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for RecentProjectsStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, RecentProjects>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -1,5 +1,6 @@
 use crate::prelude::*;
 use crate::{Icon, IconColor, IconElement, Label, LabelColor};
+use gpui2::{black, red, Div, ElementId, Render, View, VisualContext};
 
 #[derive(Component, Clone)]
 pub struct Tab {
@@ -19,6 +20,14 @@ struct TabDragState {
     title: String,
 }
 
+impl Render for TabDragState {
+    type Element = Div<Self>;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+        div().w_8().h_4().bg(red())
+    }
+}
+
 impl Tab {
     pub fn new(id: impl Into<ElementId>) -> Self {
         Self {
@@ -118,12 +127,10 @@ impl Tab {
 
         div()
             .id(self.id.clone())
-            .on_drag(move |_view, _cx| {
-                Drag::new(drag_state.clone(), |view, cx| div().w_8().h_4().bg(red()))
-            })
+            .on_drag(move |_view, cx| cx.build_view(|cx| drag_state.clone()))
             .drag_over::<TabDragState>(|d| d.bg(black()))
-            .on_drop(|_view, state: TabDragState, cx| {
-                dbg!(state);
+            .on_drop(|_view, state: View<TabDragState>, cx| {
+                dbg!(state.read(cx));
             })
             .px_2()
             .py_0p5()
@@ -160,23 +167,21 @@ impl Tab {
     }
 }
 
-use gpui2::{black, red, Drag, ElementId};
 #[cfg(feature = "stories")]
 pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use strum::IntoEnumIterator;
-
-    use crate::{h_stack, v_stack, Icon, Story};
-
     use super::*;
+    use crate::{h_stack, v_stack, Icon, Story};
+    use strum::IntoEnumIterator;
 
-    #[derive(Component)]
     pub struct TabStory;
 
-    impl TabStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for TabStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             let git_statuses = GitStatus::iter();
             let fs_statuses = FileSystemStatus::iter();
 

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

@@ -92,15 +92,16 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::Story;
-
     use super::*;
+    use crate::Story;
+    use gpui2::{Div, Render};
 
-    #[derive(Component)]
     pub struct TabBarStory;
 
-    impl TabBarStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for TabBarStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, TabBar>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -83,15 +83,15 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::Story;
-
     use super::*;
-
-    #[derive(Component)]
+    use crate::Story;
+    use gpui2::{Div, Render};
     pub struct TerminalStory;
 
-    impl TerminalStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for TerminalStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, Terminal>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -39,15 +39,18 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
+    use gpui2::{Div, Render};
+
     use crate::Story;
 
     use super::*;
 
-    #[derive(Component)]
     pub struct ThemeSelectorStory;
 
-    impl ThemeSelectorStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for ThemeSelectorStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, ThemeSelector>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -1,7 +1,7 @@
 use std::sync::atomic::AtomicBool;
 use std::sync::Arc;
 
-use gpui2::{AppContext, Context, ModelContext, View};
+use gpui2::{Div, Render, View, VisualContext};
 
 use crate::prelude::*;
 use crate::settings::user_settings;
@@ -28,7 +28,7 @@ pub struct TitleBar {
 }
 
 impl TitleBar {
-    pub fn new(cx: &mut ModelContext<Self>) -> Self {
+    pub fn new(cx: &mut ViewContext<Self>) -> Self {
         let is_active = Arc::new(AtomicBool::new(true));
         let active = is_active.clone();
 
@@ -80,15 +80,15 @@ impl TitleBar {
         cx.notify();
     }
 
-    pub fn view(cx: &mut AppContext, livestream: Option<Livestream>) -> View<Self> {
-        {
-            let state = cx.build_model(|cx| Self::new(cx).set_livestream(livestream));
-            let render = Self::render;
-            View::for_handle(state, render)
-        }
+    pub fn view(cx: &mut WindowContext, livestream: Option<Livestream>) -> View<Self> {
+        cx.build_view(|cx| Self::new(cx).set_livestream(livestream))
     }
+}
 
-    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
+impl Render for TitleBar {
+    type Element = Div<Self>;
+
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {
         let theme = theme(cx);
         let settings = user_settings(cx);
 
@@ -187,26 +187,25 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::Story;
-
     use super::*;
+    use crate::Story;
 
     pub struct TitleBarStory {
         title_bar: View<TitleBar>,
     }
 
     impl TitleBarStory {
-        pub fn view(cx: &mut AppContext) -> View<Self> {
-            {
-                let state = cx.build_model(|cx| Self {
-                    title_bar: TitleBar::view(cx, None),
-                });
-                let render = Self::render;
-                View::for_handle(state, render)
-            }
+        pub fn view(cx: &mut WindowContext) -> View<Self> {
+            cx.build_view(|cx| Self {
+                title_bar: TitleBar::view(cx, None),
+            })
         }
+    }
+
+    impl Render for TitleBarStory {
+        type Element = Div<Self>;
 
-        fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {
             Story::container(cx)
                 .child(Story::title_for::<_, TitleBar>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -72,17 +72,20 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
+    use gpui2::{Div, Render};
+
     use crate::{Label, Story};
 
     use super::*;
 
-    #[derive(Component)]
     pub struct ToastStory;
 
-    impl ToastStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for ToastStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
-                .child(Story::title_for::<_, Toast<V>>(cx))
+                .child(Story::title_for::<_, Toast<Self>>(cx))
                 .child(Story::label(cx, "Default"))
                 .child(Toast::new(ToastOrigin::Bottom).child(Label::new("label")))
         }

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

@@ -75,19 +75,22 @@ mod stories {
     use std::path::PathBuf;
     use std::str::FromStr;
 
+    use gpui2::{Div, Render};
+
     use crate::{Breadcrumb, HighlightedText, Icon, IconButton, Story, Symbol};
 
     use super::*;
 
-    #[derive(Component)]
     pub struct ToolbarStory;
 
-    impl ToolbarStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for ToolbarStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             let theme = theme(cx);
 
             Story::container(cx)
-                .child(Story::title_for::<_, Toolbar<V>>(cx))
+                .child(Story::title_for::<_, Toolbar<Self>>(cx))
                 .child(Story::label(cx, "Default"))
                 .child(
                     Toolbar::new()

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

@@ -77,15 +77,18 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
+    use gpui2::{Div, Render};
+
     use crate::Story;
 
     use super::*;
 
-    #[derive(Component)]
     pub struct TrafficLightsStory;
 
-    impl TrafficLightsStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for TrafficLightsStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, TrafficLights>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -1,7 +1,7 @@
 use std::sync::Arc;
 
 use chrono::DateTime;
-use gpui2::{px, relative, rems, AppContext, Context, Size, View};
+use gpui2::{px, relative, rems, Div, Render, Size, View, VisualContext};
 
 use crate::{prelude::*, NotificationsPanel};
 use crate::{
@@ -44,7 +44,7 @@ pub struct Workspace {
 }
 
 impl Workspace {
-    pub fn new(cx: &mut AppContext) -> Self {
+    pub fn new(cx: &mut ViewContext<Self>) -> Self {
         Self {
             title_bar: TitleBar::view(cx, None),
             editor_1: EditorPane::view(cx),
@@ -170,15 +170,15 @@ impl Workspace {
         cx.notify();
     }
 
-    pub fn view(cx: &mut AppContext) -> View<Self> {
-        {
-            let state = cx.build_model(|cx| Self::new(cx));
-            let render = Self::render;
-            View::for_handle(state, render)
-        }
+    pub fn view(cx: &mut WindowContext) -> View<Self> {
+        cx.build_view(|cx| Self::new(cx))
     }
+}
+
+impl Render for Workspace {
+    type Element = Div<Self>;
 
-    pub fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Component<Self> {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> Div<Self> {
         let theme = theme(cx);
 
         // HACK: This should happen inside of `debug_toggle_user_settings`, but
@@ -355,9 +355,8 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use gpui2::VisualContext;
-
     use super::*;
+    use gpui2::VisualContext;
 
     pub struct WorkspaceStory {
         workspace: View<Workspace>,
@@ -365,12 +364,17 @@ mod stories {
 
     impl WorkspaceStory {
         pub fn view(cx: &mut WindowContext) -> View<Self> {
-            cx.build_view(
-                |cx| Self {
-                    workspace: Workspace::view(cx),
-                },
-                |view, cx| view.workspace.clone(),
-            )
+            cx.build_view(|cx| Self {
+                workspace: Workspace::view(cx),
+            })
+        }
+    }
+
+    impl Render for WorkspaceStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+            div().child(self.workspace.clone())
         }
     }
 }

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

@@ -43,15 +43,16 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::Story;
-
     use super::*;
+    use crate::Story;
+    use gpui2::{Div, Render};
 
-    #[derive(Component)]
     pub struct AvatarStory;
 
-    impl AvatarStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for AvatarStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, Avatar>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -219,22 +219,21 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use gpui2::rems;
-    use strum::IntoEnumIterator;
-
-    use crate::{h_stack, v_stack, LabelColor, Story};
-
     use super::*;
+    use crate::{h_stack, v_stack, LabelColor, Story};
+    use gpui2::{rems, Div, Render};
+    use strum::IntoEnumIterator;
 
-    #[derive(Component)]
     pub struct ButtonStory;
 
-    impl ButtonStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for ButtonStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             let states = InteractionState::iter();
 
             Story::container(cx)
-                .child(Story::title_for::<_, Button<V>>(cx))
+                .child(Story::title_for::<_, Button<Self>>(cx))
                 .child(
                     div()
                         .flex()

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

@@ -46,17 +46,18 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::{Button, Story};
-
     use super::*;
+    use crate::{Button, Story};
+    use gpui2::{Div, Render};
 
-    #[derive(Component)]
     pub struct DetailsStory;
 
-    impl DetailsStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for DetailsStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
-                .child(Story::title_for::<_, Details<V>>(cx))
+                .child(Story::title_for::<_, Details<Self>>(cx))
                 .child(Story::label(cx, "Default"))
                 .child(Details::new("The quick brown fox jumps over the lazy dog"))
                 .child(Story::label(cx, "With meta"))

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

@@ -191,17 +191,19 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
+    use gpui2::{Div, Render};
     use strum::IntoEnumIterator;
 
     use crate::Story;
 
     use super::*;
 
-    #[derive(Component)]
     pub struct IconStory;
 
-    impl IconStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for IconStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             let icons = Icon::iter();
 
             Story::container(cx)

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

@@ -112,15 +112,16 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::Story;
-
     use super::*;
+    use crate::Story;
+    use gpui2::{Div, Render};
 
-    #[derive(Component)]
     pub struct InputStory;
 
-    impl InputStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for InputStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, Input>(cx))
                 .child(Story::label(cx, "Default"))

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

@@ -197,15 +197,16 @@ pub use stories::*;
 
 #[cfg(feature = "stories")]
 mod stories {
-    use crate::Story;
-
     use super::*;
+    use crate::Story;
+    use gpui2::{Div, Render};
 
-    #[derive(Component)]
     pub struct LabelStory;
 
-    impl LabelStory {
-        fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
+    impl Render for LabelStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
             Story::container(cx)
                 .child(Story::title_for::<_, Label>(cx))
                 .child(Story::label(cx, "Default"))

crates/ui2/src/static_data.rs 🔗

@@ -1,7 +1,7 @@
 use std::path::PathBuf;
 use std::str::FromStr;
 
-use gpui2::{AppContext, WindowContext};
+use gpui2::ViewContext;
 use rand::Rng;
 use theme2::Theme;
 
@@ -628,7 +628,7 @@ pub fn example_editor_actions() -> Vec<PaletteItem> {
     ]
 }
 
-pub fn empty_editor_example(cx: &mut WindowContext) -> EditorPane {
+pub fn empty_editor_example(cx: &mut ViewContext<EditorPane>) -> EditorPane {
     EditorPane::new(
         cx,
         static_tabs_example(),
@@ -642,7 +642,7 @@ pub fn empty_buffer_example() -> Buffer {
     Buffer::new("empty-buffer").set_rows(Some(BufferRows::default()))
 }
 
-pub fn hello_world_rust_editor_example(cx: &mut WindowContext) -> EditorPane {
+pub fn hello_world_rust_editor_example(cx: &mut ViewContext<EditorPane>) -> EditorPane {
     let theme = theme(cx);
 
     EditorPane::new(
@@ -781,7 +781,7 @@ pub fn hello_world_rust_buffer_rows(theme: &Theme) -> Vec<BufferRow> {
     ]
 }
 
-pub fn hello_world_rust_editor_with_status_example(cx: &mut AppContext) -> EditorPane {
+pub fn hello_world_rust_editor_with_status_example(cx: &mut ViewContext<EditorPane>) -> EditorPane {
     let theme = theme(cx);
 
     EditorPane::new(