From ab9356e9d83635978a1cb305d4f017f9a1d57dec Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 17 Aug 2023 09:39:23 -0600 Subject: [PATCH] Add a proc macro for deriving override structs with optional fields --- crates/gpui/playground/src/components.rs | 5 +- .../gpui/playground/src/{frame.rs => div.rs} | 38 +++--- crates/gpui/playground/src/element.rs | 100 +++++++-------- crates/gpui/playground/src/playground.rs | 8 +- crates/gpui/playground/src/style.rs | 11 +- crates/gpui/playground/src/text.rs | 10 +- .../playground_macros/src/derive_element.rs | 8 +- .../playground_macros/src/derive_overrides.rs | 119 ++++++++++++++++++ .../src/playground_macros.rs | 6 + 9 files changed, 209 insertions(+), 96 deletions(-) rename crates/gpui/playground/src/{frame.rs => div.rs} (67%) create mode 100644 crates/gpui/playground_macros/src/derive_overrides.rs diff --git a/crates/gpui/playground/src/components.rs b/crates/gpui/playground/src/components.rs index eecbc3104b..48ecb8d1d7 100644 --- a/crates/gpui/playground/src/components.rs +++ b/crates/gpui/playground/src/components.rs @@ -1,6 +1,6 @@ use crate::{ + div, element::{Element, ElementMetadata}, - frame, text::ArcCow, themes::rose_pine, }; @@ -82,8 +82,9 @@ pub fn button() -> Button { impl Button { fn render(&mut self, view: &mut V, cx: &mut ViewContext) -> impl Element { // TODO: Drive theme from the context - let button = frame() + let button = div() .fill(rose_pine::dawn().error(0.5)) + // .hover_fill(rose_pine::dawn().error(0.6)) .h_4() .children(self.label.clone()); diff --git a/crates/gpui/playground/src/frame.rs b/crates/gpui/playground/src/div.rs similarity index 67% rename from crates/gpui/playground/src/frame.rs rename to crates/gpui/playground/src/div.rs index 8749d86199..9ec77c69f6 100644 --- a/crates/gpui/playground/src/frame.rs +++ b/crates/gpui/playground/src/div.rs @@ -1,8 +1,5 @@ -use crate::{ - element::{ - AnyElement, Element, EventHandler, IntoElement, Layout, LayoutContext, NodeId, PaintContext, - }, - style::ElementStyle, +use crate::element::{ + AnyElement, Element, ElementMetadata, IntoElement, Layout, LayoutContext, NodeId, PaintContext, }; use anyhow::{anyhow, Result}; use gpui::LayoutNodeId; @@ -10,29 +7,23 @@ use playground_macros::IntoElement; #[derive(IntoElement)] #[element_crate = "crate"] -pub struct Frame { - style: ElementStyle, - handlers: Vec>, +pub struct Div { + metadata: ElementMetadata, children: Vec>, } -pub fn frame() -> Frame { - Frame { - style: ElementStyle::default(), - handlers: Vec::new(), +pub fn div() -> Div { + Div { + metadata: ElementMetadata::default(), children: Vec::new(), } } -impl Element for Frame { +impl Element for Div { type Layout = (); - fn style_mut(&mut self) -> &mut ElementStyle { - &mut self.style - } - - fn handlers_mut(&mut self) -> &mut Vec> { - &mut self.handlers + fn metadata(&mut self) -> &mut ElementMetadata { + &mut self.metadata } fn layout( @@ -50,7 +41,10 @@ impl Element for Frame { let node_id = cx .layout_engine() .ok_or_else(|| anyhow!("no layout engine"))? - .add_node(self.style.to_taffy(rem_size), child_layout_node_ids)?; + .add_node( + self.metadata.style.to_taffy(rem_size), + child_layout_node_ids, + )?; Ok((node_id, ())) } @@ -58,7 +52,7 @@ impl Element for Frame { fn paint(&mut self, layout: Layout<()>, view: &mut V, cx: &mut PaintContext) -> Result<()> { cx.scene.push_quad(gpui::scene::Quad { bounds: layout.from_engine.bounds, - background: self.style.fill.color().map(Into::into), + background: self.metadata.style.fill.color().map(Into::into), border: Default::default(), corner_radii: Default::default(), }); @@ -70,7 +64,7 @@ impl Element for Frame { } } -impl Frame { +impl Div { pub fn child(mut self, child: impl IntoElement) -> Self { self.children.push(child.into_any_element()); self diff --git a/crates/gpui/playground/src/element.rs b/crates/gpui/playground/src/element.rs index af99bea925..7d5d972cfc 100644 --- a/crates/gpui/playground/src/element.rs +++ b/crates/gpui/playground/src/element.rs @@ -1,7 +1,7 @@ use crate::{ adapter::Adapter, color::Hsla, - style::{Display, ElementStyle, Fill, Overflow, Position}, + style::{Display, ElementStyle, ElementStyleOverrides, Fill, Overflow, Position}, }; use anyhow::Result; pub use gpui::LayoutContext; @@ -27,6 +27,7 @@ pub struct Layout<'a, E: ?Sized> { pub struct ElementMetadata { pub style: ElementStyle, + pub hover_style: ElementStyleOverrides, pub handlers: Vec>, } @@ -50,6 +51,7 @@ impl Default for ElementMetadata { fn default() -> Self { Self { style: ElementStyle::default(), + hover_style: ElementStyleOverrides::default(), handlers: Vec::new(), } } @@ -58,8 +60,7 @@ impl Default for ElementMetadata { pub trait Element: 'static { type Layout: 'static; - fn style_mut(&mut self) -> &mut ElementStyle; - fn handlers_mut(&mut self) -> &mut Vec>; + fn metadata(&mut self) -> &mut ElementMetadata; fn layout(&mut self, view: &mut V, cx: &mut LayoutContext) -> Result<(NodeId, Self::Layout)>; @@ -126,7 +127,7 @@ pub trait Element: 'static { where Self: Sized, { - self.handlers_mut().push(EventHandler { + self.metadata().handlers.push(EventHandler { handler: Rc::new(move |view, event, event_cx| { let event = event.downcast_ref::().unwrap(); if event.button == button && event.is_down { @@ -147,7 +148,7 @@ pub trait Element: 'static { where Self: Sized, { - self.handlers_mut().push(EventHandler { + self.metadata().handlers.push(EventHandler { handler: Rc::new(move |view, event, event_cx| { let event = event.downcast_ref::().unwrap(); if event.button == button && event.is_down { @@ -168,7 +169,7 @@ pub trait Element: 'static { where Self: Sized, { - self.handlers_mut().push(EventHandler { + self.metadata().handlers.push(EventHandler { handler: Rc::new(move |view, event, event_cx| { let event = event.downcast_ref::().unwrap(); if event.button == button && !event.is_down { @@ -189,7 +190,7 @@ pub trait Element: 'static { where Self: Sized, { - self.handlers_mut().push(EventHandler { + self.metadata().handlers.push(EventHandler { handler: Rc::new(move |view, event, event_cx| { let event = event.downcast_ref::().unwrap(); if event.button == button && !event.is_down { @@ -208,7 +209,7 @@ pub trait Element: 'static { where Self: Sized, { - self.style_mut().display = Display::Block; + self.metadata().style.display = Display::Block; self } @@ -216,7 +217,7 @@ pub trait Element: 'static { where Self: Sized, { - self.style_mut().display = Display::Flex; + self.metadata().style.display = Display::Flex; self } @@ -224,7 +225,7 @@ pub trait Element: 'static { where Self: Sized, { - self.style_mut().display = Display::Grid; + self.metadata().style.display = Display::Grid; self } @@ -234,8 +235,8 @@ pub trait Element: 'static { where Self: Sized, { - self.style_mut().overflow.x = Overflow::Visible; - self.style_mut().overflow.y = Overflow::Visible; + self.metadata().style.overflow.x = Overflow::Visible; + self.metadata().style.overflow.y = Overflow::Visible; self } @@ -243,8 +244,8 @@ pub trait Element: 'static { where Self: Sized, { - self.style_mut().overflow.x = Overflow::Hidden; - self.style_mut().overflow.y = Overflow::Hidden; + self.metadata().style.overflow.x = Overflow::Hidden; + self.metadata().style.overflow.y = Overflow::Hidden; self } @@ -252,8 +253,8 @@ pub trait Element: 'static { where Self: Sized, { - self.style_mut().overflow.x = Overflow::Scroll; - self.style_mut().overflow.y = Overflow::Scroll; + self.metadata().style.overflow.x = Overflow::Scroll; + self.metadata().style.overflow.y = Overflow::Scroll; self } @@ -261,7 +262,7 @@ pub trait Element: 'static { where Self: Sized, { - self.style_mut().overflow.x = Overflow::Visible; + self.metadata().style.overflow.x = Overflow::Visible; self } @@ -269,7 +270,7 @@ pub trait Element: 'static { where Self: Sized, { - self.style_mut().overflow.x = Overflow::Hidden; + self.metadata().style.overflow.x = Overflow::Hidden; self } @@ -277,7 +278,7 @@ pub trait Element: 'static { where Self: Sized, { - self.style_mut().overflow.x = Overflow::Scroll; + self.metadata().style.overflow.x = Overflow::Scroll; self } @@ -285,7 +286,7 @@ pub trait Element: 'static { where Self: Sized, { - self.style_mut().overflow.y = Overflow::Visible; + self.metadata().style.overflow.y = Overflow::Visible; self } @@ -293,7 +294,7 @@ pub trait Element: 'static { where Self: Sized, { - self.style_mut().overflow.y = Overflow::Hidden; + self.metadata().style.overflow.y = Overflow::Hidden; self } @@ -301,7 +302,7 @@ pub trait Element: 'static { where Self: Sized, { - self.style_mut().overflow.y = Overflow::Scroll; + self.metadata().style.overflow.y = Overflow::Scroll; self } @@ -311,7 +312,7 @@ pub trait Element: 'static { where Self: Sized, { - self.style_mut().position = Position::Relative; + self.metadata().style.position = Position::Relative; self } @@ -319,7 +320,7 @@ pub trait Element: 'static { where Self: Sized, { - self.style_mut().position = Position::Absolute; + self.metadata().style.position = Position::Absolute; self } @@ -329,10 +330,10 @@ pub trait Element: 'static { where Self: Sized, { - self.style_mut().inset.top = length; - self.style_mut().inset.right = length; - self.style_mut().inset.bottom = length; - self.style_mut().inset.left = length; + self.metadata().style.inset.top = length; + self.metadata().style.inset.right = length; + self.metadata().style.inset.bottom = length; + self.metadata().style.inset.left = length; self } @@ -340,7 +341,7 @@ pub trait Element: 'static { where Self: Sized, { - self.style_mut().size.width = width.into(); + self.metadata().style.size.width = width.into(); self } @@ -348,7 +349,7 @@ pub trait Element: 'static { where Self: Sized, { - self.style_mut().size.width = Length::Auto; + self.metadata().style.size.width = Length::Auto; self } @@ -357,7 +358,7 @@ pub trait Element: 'static { where Self: Sized, { - self.style_mut().size.width = length; + self.metadata().style.size.width = length; self } @@ -366,7 +367,7 @@ pub trait Element: 'static { where Self: Sized, { - self.style_mut().min_size.width = length; + self.metadata().style.min_size.width = length; self } @@ -374,7 +375,7 @@ pub trait Element: 'static { where Self: Sized, { - self.style_mut().size.height = height.into(); + self.metadata().style.size.height = height.into(); self } @@ -382,7 +383,7 @@ pub trait Element: 'static { where Self: Sized, { - self.style_mut().size.height = Length::Auto; + self.metadata().style.size.height = Length::Auto; self } @@ -391,7 +392,7 @@ pub trait Element: 'static { where Self: Sized, { - self.style_mut().size.height = height; + self.metadata().style.size.height = height; self } @@ -400,7 +401,7 @@ pub trait Element: 'static { where Self: Sized, { - self.style_mut().min_size.height = length; + self.metadata().style.min_size.height = length; self } @@ -408,7 +409,7 @@ pub trait Element: 'static { where Self: Sized, { - self.style_mut().fill = fill.into(); + self.metadata().style.fill = fill.into(); self } @@ -416,15 +417,14 @@ pub trait Element: 'static { where Self: Sized, { - self.style_mut().text_color = Some(color.into()); + self.metadata().style.text_color = Some(color.into()); self } } // Object-safe counterpart of Element used by AnyElement to store elements as trait objects. trait ElementObject { - fn style_mut(&mut self) -> &mut ElementStyle; - fn handlers_mut(&mut self) -> &mut Vec>; + fn metadata(&mut self) -> &mut ElementMetadata; fn layout(&mut self, view: &mut V, cx: &mut LayoutContext) -> Result<(NodeId, Box)>; fn paint( @@ -436,12 +436,8 @@ trait ElementObject { } impl> ElementObject for E { - fn style_mut(&mut self) -> &mut ElementStyle { - Element::style_mut(self) - } - - fn handlers_mut(&mut self) -> &mut Vec> { - Element::handlers_mut(self) + fn metadata(&mut self) -> &mut ElementMetadata { + Element::metadata(self) } fn layout( @@ -490,7 +486,7 @@ impl AnyElement { } pub fn push_text_style(&mut self, cx: &mut impl RenderContext) -> bool { - let text_style = self.element.style_mut().text_style(); + let text_style = self.element.metadata().style.text_style(); if let Some(text_style) = text_style { let mut current_text_style = cx.text_style(); text_style.apply(&mut current_text_style); @@ -516,7 +512,7 @@ impl AnyElement { from_element: element_layout.as_mut(), }; - for event_handler in self.element.handlers_mut().iter().cloned() { + for event_handler in self.element.metadata().handlers.iter().cloned() { let EngineLayout { order, bounds } = layout.from_engine; let view_id = cx.view_id(); @@ -551,12 +547,8 @@ impl AnyElement { impl Element for AnyElement { type Layout = (); - fn style_mut(&mut self) -> &mut ElementStyle { - self.element.style_mut() - } - - fn handlers_mut(&mut self) -> &mut Vec> { - self.element.handlers_mut() + fn metadata(&mut self) -> &mut ElementMetadata { + self.element.metadata() } fn layout( diff --git a/crates/gpui/playground/src/playground.rs b/crates/gpui/playground/src/playground.rs index 6549171792..9e05d86d58 100644 --- a/crates/gpui/playground/src/playground.rs +++ b/crates/gpui/playground/src/playground.rs @@ -1,11 +1,11 @@ #![allow(dead_code, unused_variables)] use color::black; use components::button; +use div::div; use element::Element; -use frame::frame; use gpui::{ geometry::{rect::RectF, vector::vec2f}, - platform::{MouseButton, WindowOptions}, + platform::WindowOptions, }; use log::LevelFilter; use simplelog::SimpleLogger; @@ -16,8 +16,8 @@ use view::view; mod adapter; mod color; mod components; +mod div; mod element; -mod frame; mod paint_context; mod style; mod text; @@ -44,7 +44,7 @@ fn main() { } fn playground(theme: &ThemeColors) -> impl Element { - frame() + div() .text_color(black()) .h_full() .w_half() diff --git a/crates/gpui/playground/src/style.rs b/crates/gpui/playground/src/style.rs index 915b4a958d..2caf58065b 100644 --- a/crates/gpui/playground/src/style.rs +++ b/crates/gpui/playground/src/style.rs @@ -1,11 +1,20 @@ use crate::color::Hsla; use gpui::geometry::{DefinedLength, Edges, Length, Point, Size}; +use playground_macros::Overrides; pub use taffy::style::{ AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent, Overflow, Position, }; -#[derive(Clone)] +pub trait Overrides { + type Base; + + fn is_some(&self) -> bool; + fn apply(&self, base: &mut Self::Base); +} + +#[derive(Clone, Overrides)] +#[overrides_crate = "crate"] pub struct ElementStyle { /// What layout strategy should be used? pub display: Display, diff --git a/crates/gpui/playground/src/text.rs b/crates/gpui/playground/src/text.rs index 0c67eb1c2a..b29d5e8925 100644 --- a/crates/gpui/playground/src/text.rs +++ b/crates/gpui/playground/src/text.rs @@ -1,4 +1,4 @@ -use crate::element::{Element, ElementMetadata, EventHandler, IntoElement}; +use crate::element::{Element, ElementMetadata, IntoElement}; use gpui::{geometry::Size, text_layout::LineLayout, RenderContext}; use parking_lot::Mutex; use std::sync::Arc; @@ -22,8 +22,8 @@ pub struct Text { impl Element for Text { type Layout = Arc>>; - fn style_mut(&mut self) -> &mut crate::style::ElementStyle { - &mut self.metadata.style + fn metadata(&mut self) -> &mut crate::element::ElementMetadata { + &mut self.metadata } fn layout( @@ -91,10 +91,6 @@ impl Element for Text { ); Ok(()) } - - fn handlers_mut(&mut self) -> &mut Vec> { - &mut self.metadata.handlers - } } pub struct TextLayout { diff --git a/crates/gpui/playground_macros/src/derive_element.rs b/crates/gpui/playground_macros/src/derive_element.rs index 6d5922c621..11909871b3 100644 --- a/crates/gpui/playground_macros/src/derive_element.rs +++ b/crates/gpui/playground_macros/src/derive_element.rs @@ -79,12 +79,8 @@ pub fn derive_element(input: TokenStream) -> TokenStream { { type Layout = #crate_name::element::AnyElement; - fn style_mut(&mut self) -> &mut #crate_name::style::ElementStyle { - &mut self.metadata.style - } - - fn handlers_mut(&mut self) -> &mut Vec<#crate_name::element::EventHandler> { - &mut self.metadata.handlers + fn metadata(&mut self) -> &mut #crate_name::element::ElementMetadata { + &mut self.metadata } fn layout( diff --git a/crates/gpui/playground_macros/src/derive_overrides.rs b/crates/gpui/playground_macros/src/derive_overrides.rs new file mode 100644 index 0000000000..2169bcba95 --- /dev/null +++ b/crates/gpui/playground_macros/src/derive_overrides.rs @@ -0,0 +1,119 @@ +extern crate proc_macro; +use proc_macro::TokenStream; +use quote::{format_ident, quote}; +use syn::{parse_macro_input, Data, DeriveInput, Fields, Lit, Meta}; + +/// When deriving Overrides on a struct Foo, builds a new struct FooOverrides +/// that implements the Overrides trait so it can be applied to Foo. +pub fn derive_overrides(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let crate_name: String = input + .attrs + .iter() + .find_map(|attr| { + if attr.path.is_ident("overrides_crate") { + match attr.parse_meta() { + Ok(Meta::NameValue(nv)) => { + if let Lit::Str(s) = nv.lit { + Some(s.value()) + } else { + None + } + } + _ => None, + } + } else { + None + } + }) + .unwrap_or_else(|| String::from("playground")); + let crate_name = format_ident!("{}", crate_name); + + let ident = input.ident; + let new_ident = syn::Ident::new(&format!("{}Overrides", ident), ident.span()); + let data = match input.data { + Data::Struct(s) => s, + _ => panic!("Override can only be derived for structs"), + }; + + let fields = match data.fields { + Fields::Named(fields) => fields.named, + _ => panic!("Override can only be derived for structs with named fields"), + }; + + let new_fields = fields + .iter() + .map(|f| { + let name = &f.ident; + let ty = &f.ty; + + if let syn::Type::Path(typepath) = ty { + if typepath.path.segments.last().unwrap().ident == "Option" { + return quote! { #name: #ty }; + } + } + quote! { #name: Option<#ty> } + }) + .collect::>(); + + let names = fields.iter().map(|f| &f.ident); + let is_some_implementation = names.clone().map(|name| { + quote! { + self.#name.is_some() + } + }); + + let apply_implementation = fields.iter().map(|f| { + let name = &f.ident; + let ty = &f.ty; + + if let syn::Type::Path(typepath) = ty { + if typepath.path.segments.last().unwrap().ident == "Option" { + return quote! { + base.#name = self.#name.clone(); + }; + } + } + + quote! { + if let Some(value) = &self.#name { + base.#name = value.clone(); + } + } + }); + + let default_implementation = names.map(|name| { + quote! { + #name: None, + } + }); + + let expanded = quote! { + pub struct #new_ident { + #(#new_fields,)* + } + + impl #crate_name::style::Overrides for #new_ident { + type Base = #ident; + + fn is_some(&self) -> bool { + #(#is_some_implementation)||* + } + + fn apply(&self, base: &mut Self::Base) { + #(#apply_implementation)* + } + } + + impl Default for #new_ident { + fn default() -> Self { + Self { + #(#default_implementation)* + } + } + } + }; + + TokenStream::from(expanded) +} diff --git a/crates/gpui/playground_macros/src/playground_macros.rs b/crates/gpui/playground_macros/src/playground_macros.rs index 4b0de71015..fc8c976913 100644 --- a/crates/gpui/playground_macros/src/playground_macros.rs +++ b/crates/gpui/playground_macros/src/playground_macros.rs @@ -2,6 +2,7 @@ use proc_macro::TokenStream; mod derive_element; mod derive_into_element; +mod derive_overrides; mod tailwind_lengths; #[proc_macro_derive(Element, attributes(element_crate))] @@ -14,6 +15,11 @@ pub fn derive_into_element(input: TokenStream) -> TokenStream { derive_into_element::derive_into_element(input) } +#[proc_macro_derive(Overrides, attributes(overrides_crate))] +pub fn derive_overrides(input: TokenStream) -> TokenStream { + derive_overrides::derive_overrides(input) +} + #[proc_macro_attribute] pub fn tailwind_lengths(attr: TokenStream, item: TokenStream) -> TokenStream { tailwind_lengths::tailwind_lengths(attr, item)