Add an Element derive macro for building components out of other elements (#2643)

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

```rs
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. Instead of calling functions that
return elements, we can now make any struct into an element fairly
easily. The advantage is that we can use method chaining to express
optional state on these components, and they blend in better with other
elements.

cc @mikayla-maki @osiewicz @iamnbutler 

Release Notes:

- N/A
This commit is contained in:
Nathan Sobo 2023-06-25 08:12:02 -06:00 committed by GitHub
commit 3f4a06f576
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 69 additions and 94 deletions

View File

@ -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,

View File

@ -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;

View File

@ -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)
}