WIP: Trying to find a composable approach to styling that plays nice with layout engine

Nathan Sobo created

Change summary

crates/gpui/playground/docs/thoughts.md                |  39 +++
crates/gpui/playground/src/div.rs                      |  83 +++++++
crates/gpui/playground/src/playground.rs               |   1 
crates/gpui/playground_macros/src/playground_macros.rs |   6 
crates/gpui/playground_macros/src/styleable_trait.rs   | 125 ++++++++++++
5 files changed, 254 insertions(+)

Detailed changes

crates/gpui/playground/docs/thoughts.md 🔗

@@ -1,3 +1,25 @@
+Much of element styling is now handled by an external engine.
+
+
+How do I make an element hover.
+
+There's a hover style.
+
+Hoverable needs to wrap another element. That element can be styled.
+
+```rs
+struct Hoverable<E: Element> {
+
+}
+
+impl<V> Element<V> for Hoverable {
+
+}
+
+```
+
+
+
 ```rs
 #[derive(Styled, Interactive)]
 pub struct Div {
@@ -30,4 +52,21 @@ struct Interactions<V> {
 }
 
 
+```
+
+
+```rs
+
+
+trait Stylable {
+    type Style;
+
+    fn with_style(self, style: Self::Style) -> Self;
+}
+
+
+
+
+
+
 ```

crates/gpui/playground/src/div.rs 🔗

@@ -0,0 +1,83 @@
+use crate::style::StyleRefinement;
+use playground_macros::styleable_trait;
+use refineable::Refineable;
+
+trait Element<V> {
+    type Style;
+
+    fn hover(self) -> Hover<V, Self>
+    where
+        Self: Sized,
+        Self::Style: Refineable,
+        <Self::Style as Refineable>::Refinement: Default,
+    {
+        Hover {
+            child: self,
+            style: <<Self as Element<V>>::Style as Refineable>::Refinement::default(),
+        }
+    }
+}
+
+use crate as playground;
+styleable_trait!();
+
+struct Hover<V, E: Element<V>>
+where
+    E::Style: Refineable,
+{
+    child: E,
+    style: <E::Style as Refineable>::Refinement,
+}
+
+struct Div {
+    style: StyleRefinement,
+}
+
+impl Styleable for Div {
+    fn declared_style(&mut self) -> &mut StyleRefinement {
+        &mut self.style
+    }
+}
+
+fn div() -> Div {
+    Div {
+        style: Default::default(),
+    }
+}
+
+impl<V> Element<V> for Div {
+    type Style = StyleRefinement;
+}
+
+#[test]
+fn test() {
+    let elt = div().w_auto();
+}
+
+// trait Element<V: 'static> {
+//     type Style;
+
+//     fn layout()
+// }
+
+// trait Stylable<V: 'static>: Element<V> {
+//     type Style;
+
+//     fn with_style(self, style: Self::Style) -> Self;
+// }
+
+// pub struct HoverStyle<S> {
+//     default: S,
+//     hovered: S,
+// }
+
+// struct Hover<V: 'static, C: Stylable<V>> {
+//     child: C,
+//     style: HoverStyle<C::Style>,
+// }
+
+// impl<V: 'static, C: Stylable<V>> Hover<V, C> {
+//     fn new(child: C, style: HoverStyle<C::Style>) -> Self {
+//         Self { child, style }
+//     }
+// }

crates/gpui/playground_macros/src/playground_macros.rs 🔗

@@ -2,8 +2,14 @@ use proc_macro::TokenStream;
 
 mod derive_element;
 mod derive_into_element;
+mod styleable_trait;
 mod tailwind_lengths;
 
+#[proc_macro]
+pub fn styleable_trait(args: TokenStream) -> TokenStream {
+    styleable_trait::styleable_trait(args)
+}
+
 #[proc_macro_derive(Element, attributes(element_crate))]
 pub fn derive_element(input: TokenStream) -> TokenStream {
     derive_element::derive_element(input)

crates/gpui/playground_macros/src/styleable_trait.rs 🔗

@@ -0,0 +1,125 @@
+use proc_macro::TokenStream;
+use proc_macro2::TokenStream as TokenStream2;
+use quote::{format_ident, quote};
+
+fn tailwind_lengths() -> Vec<(&'static str, TokenStream2)> {
+    vec![
+        ("0", quote! { DefinedLength::Pixels(0.) }),
+        ("1", quote! { DefinedLength::Rems(0.25) }),
+        ("2", quote! { DefinedLength::Rems(0.5) }),
+        ("3", quote! { DefinedLength::Rems(0.75) }),
+        ("4", quote! { DefinedLength::Rems(1.0) }),
+        ("5", quote! { DefinedLength::Rems(1.25) }),
+        ("6", quote! { DefinedLength::Rems(1.5) }),
+        ("8", quote! { DefinedLength::Rems(2.0) }),
+        ("10", quote! { DefinedLength::Rems(2.5) }),
+        ("12", quote! { DefinedLength::Rems(3.0) }),
+        ("16", quote! { DefinedLength::Rems(4.0) }),
+        ("20", quote! { DefinedLength::Rems(5.0) }),
+        ("24", quote! { DefinedLength::Rems(6.0) }),
+        ("32", quote! { DefinedLength::Rems(8.0) }),
+        ("40", quote! { DefinedLength::Rems(10.0) }),
+        ("48", quote! { DefinedLength::Rems(12.0) }),
+        ("56", quote! { DefinedLength::Rems(14.0) }),
+        ("64", quote! { DefinedLength::Rems(16.0) }),
+        ("auto", quote! { Length::Auto }),
+        ("px", quote! { DefinedLength::Pixels(1.0) }),
+        ("full", quote! { DefinedLength::Percent(100.0) }),
+        // ("screen_50", quote! { DefinedLength::Vh(50.0) }),
+        // ("screen_75", quote! { DefinedLength::Vh(75.0) }),
+        // ("screen", quote! { DefinedLength::Vh(100.0) }),
+    ]
+}
+
+fn tailwind_prefixes() -> Vec<(&'static str, bool, Vec<TokenStream2>)> {
+    vec![
+        ("w", true, vec![quote! { size.width }]),
+        ("h", true, vec![quote! { size.height }]),
+        ("min_w", false, vec![quote! { min_size.width }]),
+        ("min_h", false, vec![quote! { min_size.height }]),
+        ("max_w", false, vec![quote! { max_size.width }]),
+        ("max_h", false, vec![quote! { max_size.height }]),
+        (
+            "m",
+            true,
+            vec![quote! { margin.top }, quote! { margin.bottom }],
+        ),
+        ("mt", true, vec![quote! { margin.top }]),
+        ("mb", true, vec![quote! { margin.bottom }]),
+        (
+            "mx",
+            true,
+            vec![quote! { margin.left }, quote! { margin.right }],
+        ),
+        ("ml", true, vec![quote! { margin.left }]),
+        ("mr", true, vec![quote! { margin.right }]),
+        (
+            "p",
+            false,
+            vec![quote! { padding.top }, quote! { padding.bottom }],
+        ),
+        ("pt", false, vec![quote! { padding.top }]),
+        ("pb", false, vec![quote! { padding.bottom }]),
+        (
+            "px",
+            false,
+            vec![quote! { padding.left }, quote! { padding.right }],
+        ),
+        ("pl", false, vec![quote! { padding.left }]),
+        ("pr", false, vec![quote! { padding.right }]),
+        ("top", true, vec![quote! { inset.top }]),
+        ("bottom", true, vec![quote! { inset.bottom }]),
+        ("left", true, vec![quote! { inset.left }]),
+        ("right", true, vec![quote! { inset.right }]),
+    ]
+}
+
+pub fn styleable_trait(_item: TokenStream) -> TokenStream {
+    let mut methods = Vec::new();
+
+    for (prefix, auto_allowed, fields) in tailwind_prefixes() {
+        for (suffix, length_tokens) in tailwind_lengths() {
+            if !auto_allowed && suffix == "auto" {
+                // Conditional to skip "auto"
+                continue;
+            }
+
+            let method_name = format_ident!("{}_{}", prefix, suffix);
+
+            let field_assignments = fields
+                .iter()
+                .map(|field_tokens| {
+                    quote! {
+                        style.#field_tokens = Some(gpui::geometry::#length_tokens.into());
+                    }
+                })
+                .collect::<Vec<_>>();
+
+            let method = quote! {
+                fn #method_name(mut self) -> Self where Self: Sized {
+                    let mut style = self.declared_style();
+                    #(#field_assignments)*
+                    self
+                }
+            };
+
+            methods.push(method);
+        }
+    }
+
+    let output = quote! {
+        pub trait Styleable {
+            fn declared_style(&mut self) -> &mut playground::style::StyleRefinement;
+
+            fn style(&mut self) -> playground::style::Style {
+                let mut style = playground::style::Style::default();
+                style.refine(self.declared_style());
+                style
+            }
+
+            #(#methods)*
+        }
+    };
+
+    output.into()
+}