grapes_macros/
lib.rs

1mod utils;
2pub(crate) use utils::*;
3
4use proc_macro::TokenStream;
5use proc_macro2::TokenStream as TokenStream2;
6use quote::quote;
7use syn::{Data, DeriveInput, Fields, parse_macro_input};
8
9/// Generates code that will have to be written anyway
10#[proc_macro_derive(Component, attributes(root, state))]
11pub fn component(input: TokenStream) -> TokenStream {
12    let input = parse_macro_input!(input as DeriveInput);
13    let struct_name = &input.ident;
14
15    let (impl_generics, ty_generics, where_clause) =
16        input.generics.split_for_impl();
17
18    let mut maybe_root_ts: Option<TokenStream2> = None;
19    let mut maybe_state_ts: Option<TokenStream2> = None;
20
21    if let Data::Struct(data_struct) = input.data
22        && let Fields::Named(named_fields) = data_struct.fields
23    {
24        for field in named_fields.named.iter() {
25            for attr in &field.attrs {
26                if attr.path().is_ident("root") {
27                    let field_name = &field.ident;
28
29                    if maybe_root_ts.is_some() {
30                        return error(
31                            struct_name,
32                            "Only one field can have the #[root] attribute.",
33                        );
34                    }
35
36                    let expanded = quote! {
37                        impl #impl_generics Component for #struct_name #ty_generics #where_clause {}
38
39                        impl #impl_generics AsRef<::grapes::gtk::Widget> for #struct_name #ty_generics #where_clause {
40                            fn as_ref(&self) -> &::grapes::gtk::Widget {
41                                use ::grapes::gtk::prelude::Cast;
42                                self.#field_name.as_ref()
43                            }
44                        }
45                    };
46
47                    maybe_root_ts = Some(expanded);
48                } else if attr.path().is_ident("state") {
49                    let field_name = &field.ident;
50                    let field_type = &field.ty;
51
52                    if let Some(_) = maybe_state_ts {
53                        return error(
54                            attr.path(),
55                            "Using the #[state] attribute twice",
56                        );
57                    }
58
59                    let generic_type = match extract_generic(field_type) {
60                        Some(t) => t,
61                        None => {
62                            return error(
63                                struct_name,
64                                "Can't extract T from State<T>",
65                            );
66                        }
67                    };
68
69                    let expanded = quote! {
70                        impl #impl_generics ::grapes::Updateable for #struct_name #ty_generics #where_clause {
71                            type Message = #generic_type;
72
73                            fn update(&self, value: #generic_type) {
74                                self.#field_name.set(value);
75                            }
76                        }
77                    };
78
79                    maybe_state_ts = Some(expanded);
80                }
81            }
82        }
83    }
84
85    match (maybe_root_ts, maybe_state_ts) {
86        (None, _) => error(
87            struct_name,
88            "One of the fields must have the #[root] attribute.",
89        ),
90        (Some(root_ts), None) => root_ts.into(),
91        (Some(root_ts), Some(state_ts)) => {
92            let mut ts = TokenStream2::new();
93            ts.extend(root_ts);
94            ts.extend(state_ts);
95            ts.into()
96        }
97    }
98}
99
100/// Generates code that will have to be written anyway
101#[proc_macro_derive(WindowComponent, attributes(root))]
102pub fn window_component(input: TokenStream) -> TokenStream {
103    let input = parse_macro_input!(input as DeriveInput);
104    let struct_name = &input.ident;
105
106    let mut maybe_root_ts: Option<TokenStream2> = None;
107
108    if let Data::Struct(data_struct) = input.data
109        && let Fields::Named(named_fields) = data_struct.fields
110    {
111        for field in named_fields.named.iter() {
112            for attr in &field.attrs {
113                if attr.path().is_ident("root") {
114                    let field_name = &field.ident;
115
116                    if maybe_root_ts.is_some() {
117                        return error(
118                            struct_name,
119                            "Only one field can have the #[root] attribute.",
120                        );
121                    }
122
123                    let expanded = quote! {
124                        impl WindowComponent for #struct_name {
125                            /// Window present
126                            fn present(&self) {
127                                self.#field_name.present();
128                            }
129
130                            /// Window destroy
131                            fn destroy(&self) {
132                                self.#field_name.destroy();
133                            }
134                        }
135                    };
136
137                    maybe_root_ts = Some(expanded);
138                }
139            }
140        }
141    }
142
143    match maybe_root_ts {
144        None => error(
145            struct_name,
146            "One of the fields must have the #[root] attribute.",
147        ),
148        Some(root_ts) => root_ts.into(),
149    }
150}