Add a derive macro for Element

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.
This commit is contained in:
Nathan Sobo 2023-06-25 07:51:50 -06:00
parent 82bd5fb564
commit bede668b14
3 changed files with 69 additions and 94 deletions

View File

@ -41,13 +41,7 @@ use collections::HashMap;
use core::panic; use core::panic;
use json::ToJson; use json::ToJson;
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{ use std::{any::Any, borrow::Cow, mem, ops::Range};
any::Any,
borrow::Cow,
marker::PhantomData,
mem,
ops::{Deref, DerefMut, Range},
};
pub trait Element<V: View>: 'static { pub trait Element<V: View>: 'static {
type LayoutState; 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 { pub trait AnyRootElement {
fn layout( fn layout(
&mut self, &mut self,

View File

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

View File

@ -3,8 +3,8 @@ use proc_macro2::Ident;
use quote::{format_ident, quote}; use quote::{format_ident, quote};
use std::mem; use std::mem;
use syn::{ use syn::{
parse_macro_input, parse_quote, spanned::Spanned as _, AttributeArgs, FnArg, ItemFn, Lit, Meta, parse_macro_input, parse_quote, spanned::Spanned as _, AttributeArgs, DeriveInput, FnArg,
NestedMeta, Type, ItemFn, Lit, Meta, NestedMeta, Type,
}; };
#[proc_macro_attribute] #[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())) 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)
}