WIP

Nathan Sobo created

Change summary

crates/gpui3/src/app.rs                   |  7 ++++
crates/gpui3/src/elements.rs              |  2 +
crates/gpui3/src/elements/stateless.rs    | 31 ++++++++++++++++++++
crates/gpui3/src/window.rs                | 10 ++++++
crates/gpui3_macros/src/derive_element.rs | 20 ++++++------
crates/storybook2/src/collab_panel.rs     | 37 +++++++++++++-----------
crates/storybook2/src/workspace.rs        | 24 ++++++----------
7 files changed, 88 insertions(+), 43 deletions(-)

Detailed changes

crates/gpui3/src/app.rs 🔗

@@ -29,6 +29,7 @@ impl App {
 pub struct AppContext {
     platform: Rc<dyn Platform>,
     text_system: Arc<TextSystem>,
+    pub(crate) unit_entity_id: EntityId,
     pub(crate) entities: SlotMap<EntityId, Option<Box<dyn Any>>>,
     pub(crate) windows: SlotMap<WindowId, Option<Window>>,
     // We recycle this memory across layout requests.
@@ -38,10 +39,14 @@ pub struct AppContext {
 impl AppContext {
     pub fn new(platform: Rc<dyn Platform>) -> Self {
         let text_system = Arc::new(TextSystem::new(platform.text_system()));
+        let mut entities = SlotMap::with_key();
+        let unit_entity_id = entities.insert(Some(Box::new(()) as Box<dyn Any>));
+
         AppContext {
             platform,
             text_system,
-            entities: SlotMap::with_key(),
+            unit_entity_id,
+            entities,
             windows: SlotMap::with_key(),
             layout_id_buffer: Default::default(),
         }

crates/gpui3/src/elements.rs 🔗

@@ -1,9 +1,11 @@
 mod div;
 mod img;
+mod stateless;
 mod svg;
 mod text;
 
 pub use div::*;
 pub use img::*;
+pub use stateless::*;
 pub use svg::*;
 pub use text::*;

crates/gpui3/src/elements/stateless.rs 🔗

@@ -0,0 +1,31 @@
+use std::marker::PhantomData;
+
+use crate::Element;
+
+pub struct Stateless<E: Element<State = ()>, S> {
+    element: E,
+    parent_state_type: PhantomData<S>,
+}
+
+impl<E: Element<State = ()>, S: 'static> Element for Stateless<E, S> {
+    type State = S;
+    type FrameState = E::FrameState;
+
+    fn layout(
+        &mut self,
+        _: &mut Self::State,
+        cx: &mut crate::ViewContext<Self::State>,
+    ) -> anyhow::Result<(crate::LayoutId, Self::FrameState)> {
+        cx.erase_state(|cx| self.element.layout(&mut (), cx))
+    }
+
+    fn paint(
+        &mut self,
+        layout: crate::Layout,
+        _: &mut Self::State,
+        frame_state: &mut Self::FrameState,
+        cx: &mut crate::ViewContext<Self::State>,
+    ) -> anyhow::Result<()> {
+        cx.erase_state(|cx| self.element.paint(layout, &mut (), frame_state, cx))
+    }
+}

crates/gpui3/src/window.rs 🔗

@@ -220,6 +220,16 @@ impl<'a, 'w, T: 'static> ViewContext<'a, 'w, T> {
             entity_type: PhantomData,
         }
     }
+
+    pub fn erase_state<R>(&mut self, f: impl FnOnce(&mut ViewContext<()>) -> R) -> R {
+        let unit_entity_id = self.unit_entity_id;
+        let mut cx = ViewContext::mutable(
+            &mut *self.window_cx.app,
+            &mut *self.window_cx.window,
+            unit_entity_id,
+        );
+        f(&mut cx)
+    }
 }
 
 impl<'a, 'w, T: 'static> Context for ViewContext<'a, 'w, T> {

crates/gpui3_macros/src/derive_element.rs 🔗

@@ -1,32 +1,32 @@
 use proc_macro::TokenStream;
-use proc_macro2::Ident;
 use quote::quote;
-use syn::{parse_macro_input, parse_quote, DeriveInput, GenericParam};
+use syn::{parse_macro_input, DeriveInput, GenericParam};
 
 pub fn derive_element(input: TokenStream) -> TokenStream {
     let ast = parse_macro_input!(input as DeriveInput);
     let type_name = ast.ident;
-    let mut state_type: Option<Ident> = None;
 
-    for param in ast.generics.params.iter() {
+    let mut state_type = quote! { () };
+
+    for param in &ast.generics.params {
         if let GenericParam::Type(type_param) = param {
-            state_type = Some(type_param.ident.clone())
+            let type_ident = &type_param.ident;
+            state_type = quote! {#type_ident};
         }
     }
 
-    let state_type_name = state_type.unwrap_or_else(|| parse_quote! { () });
     let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
 
     let gen = quote! {
         impl #impl_generics gpui3::Element for #type_name #ty_generics
         #where_clause
         {
-            type State = #state_type_name;
-            type FrameState = gpui3::AnyElement<#state_type_name>;
+            type State = #state_type;
+            type FrameState = gpui3::AnyElement<#state_type>;
 
             fn layout(
                 &mut self,
-                state: &mut #state_type_name,
+                state: &mut #state_type,
                 cx: &mut gpui3::ViewContext<V>,
             ) -> anyhow::Result<(gpui3::LayoutId, Self::FrameState)> {
                 let mut rendered_element = self.render(state, cx).into_element().into_any();
@@ -37,7 +37,7 @@ pub fn derive_element(input: TokenStream) -> TokenStream {
             fn paint(
                 &mut self,
                 layout: &gpui3::Layout,
-                state: &mut #state_type_name,
+                state: &mut #state_type,
                 rendered_element: &mut Self::FrameState,
                 cx: &mut gpui3::ViewContext<V>,
             ) {

crates/storybook2/src/collab_panel.rs 🔗

@@ -1,26 +1,29 @@
 use crate::theme::{theme, Theme};
 use gpui3::{
-    div, img, svg, ArcCow, Element, IntoAnyElement, ParentElement, ScrollState, StyleHelpers,
-    ViewContext,
+    div, img, svg, view, AppContext, ArcCow, Context, Element, IntoAnyElement, ParentElement,
+    ScrollState, StyleHelpers, View, ViewContext, WindowContext,
 };
-use std::marker::PhantomData;
 
-pub struct CollabPanelElement<V: 'static> {
-    view_type: PhantomData<V>,
+struct CollabPanel {
     scroll_state: ScrollState,
 }
 
-// When I improve child view rendering, I'd like to have V implement a trait  that
-// provides the scroll state, among other things.
-pub fn collab_panel<V: 'static>(scroll_state: ScrollState) -> CollabPanelElement<V> {
-    CollabPanelElement {
-        view_type: PhantomData,
-        scroll_state,
+pub fn collab_panel(cx: &mut WindowContext) -> View<CollabPanel> {
+    view(cx.entity(|cx| CollabPanel::new(cx)), |panel, cx| {
+        panel.render(cx)
+    })
+}
+
+impl CollabPanel {
+    fn new(_: &mut AppContext) -> Self {
+        CollabPanel {
+            scroll_state: ScrollState::default(),
+        }
     }
 }
 
-impl<V: 'static> CollabPanelElement<V> {
-    fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl Element {
+impl CollabPanel {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element<State = Self> {
         let theme = theme(cx);
 
         // Panel
@@ -115,10 +118,10 @@ impl<V: 'static> CollabPanelElement<V> {
 
     fn list_section_header(
         &self,
-        label: impl IntoAnyElement<V>,
+        label: impl IntoAnyElement<Self>,
         expanded: bool,
         theme: &Theme,
-    ) -> impl Element<State = V> {
+    ) -> impl Element<State = Self> {
         div()
             .h_7()
             .px_2()
@@ -144,9 +147,9 @@ impl<V: 'static> CollabPanelElement<V> {
     fn list_item(
         &self,
         avatar_uri: impl Into<ArcCow<'static, str>>,
-        label: impl IntoAnyElement<V>,
+        label: impl IntoAnyElement<Self>,
         theme: &Theme,
-    ) -> impl Element<State = V> {
+    ) -> impl Element<State = Self> {
         div()
             .h_7()
             .px_2()

crates/storybook2/src/workspace.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{collab_panel::collab_panel, theme::theme};
 use gpui3::{div, img, svg, Element, ParentElement, ScrollState, StyleHelpers, ViewContext};
 
-#[derive(Element, Default)]
+#[derive(Default)]
 struct WorkspaceElement {
     left_scroll_state: ScrollState,
     right_scroll_state: ScrollState,
@@ -271,18 +271,12 @@ impl TitleBar {
 
 // ================================================================================ //
 
-struct StatusBar;
+mod statusbar {
+    use gpui3::WindowContext;
 
-pub fn statusbar<V: 'static>() -> impl Element<State = V> {
-    StatusBar
-}
+    use super::*;
 
-impl StatusBar {
-    fn render<V: 'static>(
-        &mut self,
-        _: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> impl Element<State = V> {
+    pub fn statusbar<V: 'static>(_: &mut V, cx: &mut ViewContext<V>) -> impl Element<State = V> {
         let theme = theme(cx);
         div()
             .flex()
@@ -291,11 +285,11 @@ impl StatusBar {
             .w_full()
             .h_8()
             .fill(theme.lowest.base.default.background)
-            .child(self.left_group(cx))
-            .child(self.right_group(cx))
+            .child(left_group(cx))
+            .child(right_group(cx))
     }
 
-    fn left_group<V: 'static>(&mut self, cx: &mut ViewContext<V>) -> impl Element<State = V> {
+    fn left_group<V: 'static>(cx: &mut ViewContext<V>) -> impl Element<State = V> {
         let theme = theme(cx);
         div()
             .flex()
@@ -392,7 +386,7 @@ impl StatusBar {
             )
     }
 
-    fn right_group<V: 'static>(&mut self, cx: &mut ViewContext<V>) -> impl Element<State = V> {
+    fn right_group<S: 'static>(cx: &mut ViewContext<S>) -> impl Element<State = S> {
         let theme = theme(cx);
         div()
             .flex()