raui_derive/
lib.rs

1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use quote::quote;
5use syn::{
6    DeriveInput, FnArg, Ident, ItemFn, Pat, PatIdent, Path, Result, Token, Type, TypePath,
7    TypeReference,
8    parse::{Parse, ParseStream},
9    parse_macro_input, parse_str,
10    punctuated::Punctuated,
11};
12
13#[derive(Debug, Clone)]
14struct IdentList {
15    values: Punctuated<Ident, Token![,]>,
16}
17
18impl Parse for IdentList {
19    fn parse(input: ParseStream) -> Result<Self> {
20        Ok(Self {
21            values: input.parse_terminated(Ident::parse)?,
22        })
23    }
24}
25
26fn unpack_context(ty: &Type, pat: &Pat) -> Option<Ident> {
27    match ty {
28        Type::Path(TypePath { path, .. }) => {
29            if let Some(segment) = path.segments.iter().next_back() {
30                if segment.ident == "WidgetContext" {
31                    if let Pat::Ident(PatIdent { ident, .. }) = pat {
32                        return Some(ident.to_owned());
33                    }
34                }
35            }
36        }
37        Type::Reference(TypeReference { elem, .. }) => {
38            return unpack_context(elem, pat);
39        }
40        _ => {}
41    }
42    None
43}
44
45fn is_arg_context(arg: &FnArg) -> Option<Ident> {
46    if let FnArg::Typed(pat) = arg {
47        unpack_context(&pat.ty, &pat.pat)
48    } else {
49        None
50    }
51}
52
53// The links won't be broken when built in the context of the `raui` crate
54/// An attribute macro that allows you to add hooks that will execute before your component body
55///
56/// > **See Also:** [`macro@post_hooks`] for an alternative that runs _after_ your component body
57///
58/// Hooks allow you to create reusable logic that can be applied to multiple components.
59#[proc_macro_attribute]
60pub fn pre_hooks(attr: TokenStream, input: TokenStream) -> TokenStream {
61    let ItemFn {
62        attrs,
63        vis,
64        sig,
65        block,
66    } = parse_macro_input!(input as ItemFn);
67    let context = sig
68        .inputs
69        .iter()
70        .find_map(is_arg_context)
71        .unwrap_or_else(|| panic!("Could not find function context argument!"));
72    let list = parse_macro_input!(attr as IdentList);
73    let hooks = list
74        .values
75        .into_iter()
76        .map(|v| quote! { #context.use_hook(#v); });
77
78    let tokens = quote! {
79        #(#attrs)*
80        #vis #sig {
81            #({#hooks})*
82            #block
83        }
84    };
85    tokens.into()
86}
87
88/// Allows you to execute re-usable logic after your component body
89///
90/// See [`macro@pre_hooks`]
91#[proc_macro_attribute]
92pub fn post_hooks(attr: TokenStream, input: TokenStream) -> TokenStream {
93    let ItemFn {
94        attrs,
95        vis,
96        sig,
97        block,
98    } = parse_macro_input!(input as ItemFn);
99    let context = sig
100        .inputs
101        .iter()
102        .find_map(is_arg_context)
103        .unwrap_or_else(|| panic!("Could not find function context argument!"));
104    let list = parse_macro_input!(attr as IdentList);
105    let hooks = list
106        .values
107        .into_iter()
108        .map(|v| quote! { #context.use_hook(#v); });
109
110    let tokens = quote! {
111        #(#attrs)*
112        #vis #sig {
113            let result = {
114                #block
115            };
116            #({#hooks})*
117            result
118        }
119    };
120    tokens.into()
121}
122
123// The links won't be broken when built in the context of the `raui` crate
124/// Derive macro for the [`PropsData`][raui_core::props::PropsData] trait
125///
126/// # Example
127///
128/// ```ignore
129/// #[derive(PropsData, Debug, Default, Copy, Clone, Serialize, Deserialize)]
130/// #[props_data(crate::props::PropsData)]
131/// #[prefab(crate::Prefab)]
132/// pub struct ButtonProps {
133///     #[serde(default)]
134///     pub selected: bool,
135///     #[serde(default)]
136///     pub trigger: bool,
137///     #[serde(default)]
138///     pub context: bool,
139///     #[serde(default)]
140///     pub pointer: Vec2,
141/// }
142/// ```
143#[proc_macro_derive(PropsData, attributes(remote, props_data, prefab))]
144pub fn derive_props(input: TokenStream) -> TokenStream {
145    let DeriveInput { ident, attrs, .. } = parse_macro_input!(input as DeriveInput);
146
147    let mut path = Path::from(ident);
148    let mut props_data = parse_str::<Path>("PropsData").unwrap();
149    let mut prefab = parse_str::<Path>("Prefab").unwrap();
150    for attr in attrs {
151        if let Some(ident) = attr.path.get_ident() {
152            if ident == "remote" {
153                path = attr.parse_args::<Path>().unwrap();
154            } else if ident == "props_data" {
155                props_data = attr.parse_args::<Path>().unwrap();
156            } else if ident == "prefab" {
157                prefab = attr.parse_args::<Path>().unwrap();
158            }
159        }
160    }
161
162    let tokens = quote! {
163        impl #props_data for #path
164        where
165            Self: Clone,
166        {
167            fn clone_props(&self) -> Box<dyn #props_data> {
168                Box::new(self.clone())
169            }
170
171            fn as_any(&self) -> &dyn std::any::Any {
172                self
173            }
174        }
175
176        impl #prefab for #path {}
177    };
178    tokens.into()
179}
180
181// The links won't be broken when built in the context of the `raui` crate
182/// Derive macro for the [`MessageData`][raui_core::messenger::MessageData] trait
183///
184/// # Example
185///
186/// ```ignore
187/// #[derive(MessageData, Debug, Clone)]
188/// pub enum AppMessage {
189///     ShowPopup(usize),
190///     ClosePopup,
191/// }
192/// ```
193#[proc_macro_derive(MessageData, attributes(remote, message_data))]
194pub fn derive_message(input: TokenStream) -> TokenStream {
195    let DeriveInput { ident, attrs, .. } = parse_macro_input!(input as DeriveInput);
196
197    let mut path = Path::from(ident);
198    let mut message_data = parse_str::<Path>("MessageData").unwrap();
199    for attr in attrs {
200        if let Some(ident) = attr.path.get_ident() {
201            if ident == "remote" {
202                path = attr.parse_args::<Path>().unwrap();
203            } else if ident == "message_data" {
204                message_data = attr.parse_args::<Path>().unwrap();
205            }
206        }
207    }
208
209    let tokens = quote! {
210        impl #message_data for #path
211        where
212            Self: Clone,
213        {
214            fn clone_message(&self) -> Box<dyn #message_data> {
215                Box::new(self.clone())
216            }
217
218            fn as_any(&self) -> &dyn std::any::Any {
219                self
220            }
221        }
222    };
223    tokens.into()
224}