Add a derive macro for Element

Nathan Sobo created

To turn any struct into a composite element, you can implement a render method
with the following signature:

fn render<V: View>(&mut self, view: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V>;

Then add #[derive(Element)] to the struct definition.

This will make it easier to introduce higher-level components that are expressed in
terms of other elements.

Change summary

crates/gpui/src/elements.rs           | 92 ----------------------------
crates/gpui/src/gpui.rs               |  2 
crates/gpui_macros/src/gpui_macros.rs | 69 +++++++++++++++++++++
3 files changed, 69 insertions(+), 94 deletions(-)

Detailed changes

crates/gpui/src/elements.rs 🔗

@@ -41,13 +41,7 @@ use collections::HashMap;
 use core::panic;
 use json::ToJson;
 use smallvec::SmallVec;
-use std::{
-    any::Any,
-    borrow::Cow,
-    marker::PhantomData,
-    mem,
-    ops::{Deref, DerefMut, Range},
-};
+use std::{any::Any, borrow::Cow, mem, ops::Range};
 
 pub trait Element<V: View>: 'static {
     type LayoutState;
@@ -567,90 +561,6 @@ impl<V: View> RootElement<V> {
     }
 }
 
-pub trait Component<V: View>: 'static {
-    fn render(&self, view: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V>;
-}
-
-pub struct ComponentHost<V: View, C: Component<V>> {
-    component: C,
-    view_type: PhantomData<V>,
-}
-
-impl<V: View, C: Component<V>> ComponentHost<V, C> {
-    pub fn new(c: C) -> Self {
-        Self {
-            component: c,
-            view_type: PhantomData,
-        }
-    }
-}
-
-impl<V: View, C: Component<V>> Deref for ComponentHost<V, C> {
-    type Target = C;
-
-    fn deref(&self) -> &Self::Target {
-        &self.component
-    }
-}
-
-impl<V: View, C: Component<V>> DerefMut for ComponentHost<V, C> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.component
-    }
-}
-
-impl<V: View, C: Component<V>> Element<V> for ComponentHost<V, C> {
-    type LayoutState = AnyElement<V>;
-    type PaintState = ();
-
-    fn layout(
-        &mut self,
-        constraint: SizeConstraint,
-        view: &mut V,
-        cx: &mut LayoutContext<V>,
-    ) -> (Vector2F, AnyElement<V>) {
-        let mut element = self.component.render(view, cx);
-        let size = element.layout(constraint, view, cx);
-        (size, element)
-    }
-
-    fn paint(
-        &mut self,
-        scene: &mut SceneBuilder,
-        bounds: RectF,
-        visible_bounds: RectF,
-        element: &mut AnyElement<V>,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) {
-        element.paint(scene, bounds.origin(), visible_bounds, view, cx);
-    }
-
-    fn rect_for_text_range(
-        &self,
-        range_utf16: Range<usize>,
-        _: RectF,
-        _: RectF,
-        element: &AnyElement<V>,
-        _: &(),
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> Option<RectF> {
-        element.rect_for_text_range(range_utf16, view, cx)
-    }
-
-    fn debug(
-        &self,
-        _: RectF,
-        element: &AnyElement<V>,
-        _: &(),
-        view: &V,
-        cx: &ViewContext<V>,
-    ) -> serde_json::Value {
-        element.debug(view, cx)
-    }
-}
-
 pub trait AnyRootElement {
     fn layout(
         &mut self,

crates/gpui/src/gpui.rs 🔗

@@ -26,7 +26,7 @@ pub mod color;
 pub mod json;
 pub mod keymap_matcher;
 pub mod platform;
-pub use gpui_macros::test;
+pub use gpui_macros::{test, Element};
 pub use window::{Axis, SizeConstraint, Vector2FExt, WindowContext};
 
 pub use anyhow;

crates/gpui_macros/src/gpui_macros.rs 🔗

@@ -3,8 +3,8 @@ use proc_macro2::Ident;
 use quote::{format_ident, quote};
 use std::mem;
 use syn::{
-    parse_macro_input, parse_quote, spanned::Spanned as _, AttributeArgs, FnArg, ItemFn, Lit, Meta,
-    NestedMeta, Type,
+    parse_macro_input, parse_quote, spanned::Spanned as _, AttributeArgs, DeriveInput, FnArg,
+    ItemFn, Lit, Meta, NestedMeta, Type,
 };
 
 #[proc_macro_attribute]
@@ -275,3 +275,68 @@ fn parse_bool(literal: &Lit) -> Result<bool, TokenStream> {
 
     result.map_err(|err| TokenStream::from(err.into_compile_error()))
 }
+
+#[proc_macro_derive(Element)]
+pub fn element_derive(input: TokenStream) -> TokenStream {
+    // Parse the input tokens into a syntax tree
+    let input = parse_macro_input!(input as DeriveInput);
+
+    // The name of the struct/enum
+    let name = input.ident;
+
+    let expanded = quote! {
+        impl<V: gpui::View> gpui::elements::Element<V> for #name {
+            type LayoutState = gpui::elements::AnyElement<V>;
+            type PaintState = ();
+
+            fn layout(
+                &mut self,
+                constraint: gpui::SizeConstraint,
+                view: &mut V,
+                cx: &mut gpui::LayoutContext<V>,
+            ) -> (gpui::geometry::vector::Vector2F, gpui::elements::AnyElement<V>) {
+                let mut element = self.render(view, cx);
+                let size = element.layout(constraint, view, cx);
+                (size, element)
+            }
+
+            fn paint(
+                &mut self,
+                scene: &mut gpui::SceneBuilder,
+                bounds: gpui::geometry::rect::RectF,
+                visible_bounds: gpui::geometry::rect::RectF,
+                element: &mut gpui::elements::AnyElement<V>,
+                view: &mut V,
+                cx: &mut gpui::ViewContext<V>,
+            ) {
+                element.paint(scene, bounds.origin(), visible_bounds, view, cx);
+            }
+
+            fn rect_for_text_range(
+                &self,
+                range_utf16: std::ops::Range<usize>,
+                _: gpui::geometry::rect::RectF,
+                _: gpui::geometry::rect::RectF,
+                element: &gpui::elements::AnyElement<V>,
+                _: &(),
+                view: &V,
+                cx: &gpui::ViewContext<V>,
+            ) -> Option<gpui::geometry::rect::RectF> {
+                element.rect_for_text_range(range_utf16, view, cx)
+            }
+
+            fn debug(
+                &self,
+                _: gpui::geometry::rect::RectF,
+                element: &gpui::elements::AnyElement<V>,
+                _: &(),
+                view: &V,
+                cx: &gpui::ViewContext<V>,
+            ) -> serde_json::Value {
+                element.debug(view, cx)
+            }
+        }
+    };
+    // Return generated code
+    TokenStream::from(expanded)
+}