zng_app_proc_macros/
wgt_property_attrs.rs

1use quote::ToTokens;
2use syn::{parse::Parse, *};
3
4use crate::util::crate_core;
5
6pub(crate) fn expand_easing(args: proc_macro::TokenStream, input: proc_macro::TokenStream) -> proc_macro::TokenStream {
7    let data = parse_macro_input!(input as PropertyAttrData);
8    let args = parse_macro_input!(args as Args);
9    let PropertyAttrData {
10        builder,
11        is_unset: property_is_unset,
12        property,
13        is_when,
14        ..
15    } = &data;
16    let Args { duration, easing } = &args;
17
18    if *property_is_unset {
19        return quote! {
20            compile_error!{"cannot set `easing` in unset assign\n\n   note: you can use `#[easing(unset)]` to unset in a normal assign to unset easing"}
21            #data
22        }.into();
23    }
24
25    let is_unset = args.is_unset();
26
27    let core = crate_core();
28    let name = "zng::core::widget::easing";
29
30    let property_ident = &property.segments.last().unwrap().ident;
31    let meta_ident = ident!("{property_ident}_");
32    let property_meta = if property.get_ident().is_some() {
33        quote! {
34            #builder.#meta_ident()
35        }
36    } else {
37        quote! {
38            #property::#meta_ident(#core::widget::base::WidgetImpl::base(#builder))
39        }
40    };
41
42    if *is_when {
43        if is_unset {
44            return quote! {
45                compile_error!{"cannot unset `easing` in when assign, try `#[easing(0.ms())]`"}
46                #data
47            }
48            .into();
49        }
50
51        return quote! {
52            {
53                let __data__ = #core::widget::easing_property::easing_when_data(
54                    #property_meta.input_types(),
55                    {
56                        use #core::layout::unit::TimeUnits as _;
57                        #duration
58                    },
59                    std::sync::Arc::new({
60                        use #core::var::animation::easing::*;
61                        #easing
62                    }),
63                );
64                let id__ = #property_meta.id();
65                #core::widget::base::WidgetImpl::base(&mut *#builder).push_when_build_action_data__(
66                    id__,
67                    #name,
68                    __data__,
69                );
70            }
71            #data
72        }
73        .into();
74    }
75
76    let r = if is_unset {
77        quote! {
78            #core::widget::easing_property::easing_property_unset(
79                #property_meta.input_types()
80            );
81            let id__ = #property_meta.id();
82            #core::widget::base::WidgetImpl::base(&mut *#builder).push_unset_property_build_action__(
83                id__,
84                #name,
85            );
86            #data
87        }
88    } else {
89        quote! {
90            {
91                let __actions__ = #core::widget::easing_property::easing_property(
92                    #property_meta.input_types(),
93                    {
94                        use #core::layout::unit::TimeUnits as _;
95                        #duration
96                    },
97                    std::sync::Arc::new({
98                        use #core::var::animation::easing::*;
99                        #easing
100                    }),
101                );
102                if !__actions__.is_empty() {
103                    let id__ = #property_meta.id();
104                    #core::widget::base::WidgetImpl::base(&mut *#builder).push_property_build_action__(
105                        id__,
106                        #name,
107                        __actions__
108                    );
109                }
110            }
111            #data
112        }
113    };
114    r.into()
115}
116
117struct Args {
118    duration: Expr,
119    easing: Expr,
120}
121impl Args {
122    fn is_unset(&self) -> bool {
123        match &self.duration {
124            Expr::Path(p) => p.path.get_ident().map(|id| id == &ident!("unset")).unwrap_or(false),
125            _ => false,
126        }
127    }
128}
129impl Parse for Args {
130    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
131        Ok(Args {
132            duration: input.parse()?,
133            easing: {
134                if input.peek(Token![,]) {
135                    let _ = input.parse::<Token![,]>();
136                    input.parse()?
137                } else {
138                    parse_quote!(linear)
139                }
140            },
141        })
142    }
143}
144
145pub(crate) struct PropertyAttrData {
146    /// Other custom property attributes that must be expanded.
147    ///
148    /// `PropertyAttrData::to_tokens` only generates tokens if there are pending attrs.
149    pub pending_attrs: Vec<Attribute>,
150    /// ident of the "fake" data module used to expand this attribute, is unique in scope.
151    pub data_ident: Ident,
152
153    /// mut local: WidgetBuilder.
154    pub builder: Ident,
155    /// If is property `unset!`.
156    pub is_unset: bool,
157    /// path to the property function/struct.
158    pub property: Path,
159    /// If is inside `when` block.
160    pub is_when: bool,
161}
162impl Parse for PropertyAttrData {
163    fn parse(input: parse::ParseStream) -> Result<Self> {
164        let item_mod: ItemMod = input.parse()?;
165
166        let mut builder = None;
167        let mut is_unset = false;
168        let mut property = None;
169        let mut is_when = false;
170
171        for item in item_mod.content.unwrap_or_else(|| non_user_error!("")).1 {
172            let mut f = match item {
173                Item::Fn(f) => f,
174                _ => non_user_error!(""),
175            };
176
177            let stmt = f.block.stmts.pop().unwrap_or_else(|| non_user_error!(""));
178            let expr = match stmt {
179                Stmt::Expr(e, _) => e,
180                _ => non_user_error!(""),
181            };
182
183            if f.sig.ident == ident!("builder_ident") {
184                let path = match expr {
185                    Expr::Path(p) => p.path,
186                    _ => non_user_error!(""),
187                };
188
189                let ident = match path.get_ident() {
190                    Some(i) => i.clone(),
191                    None => non_user_error!(""),
192                };
193
194                builder = Some(ident);
195            } else if f.sig.ident == ident!("property_path") {
196                let path = match expr {
197                    Expr::Path(p) => p.path,
198                    _ => non_user_error!(""),
199                };
200
201                property = Some(path);
202            } else if f.sig.ident == ident!("is_when") {
203                let lit = match expr {
204                    Expr::Lit(l) => l,
205                    _ => non_user_error!(""),
206                };
207
208                let lit_bool = match lit.lit {
209                    Lit::Bool(b) => b,
210                    _ => non_user_error!(""),
211                };
212
213                is_when = lit_bool.value();
214            } else if f.sig.ident == ident!("is_unset") {
215                let lit = match expr {
216                    Expr::Lit(l) => l,
217                    _ => non_user_error!(""),
218                };
219
220                let lit_bool = match lit.lit {
221                    Lit::Bool(b) => b,
222                    _ => non_user_error!(""),
223                };
224
225                is_unset = lit_bool.value();
226            } else {
227                non_user_error!("")
228            }
229        }
230
231        Ok(Self {
232            pending_attrs: item_mod.attrs,
233            data_ident: item_mod.ident,
234            builder: builder.unwrap_or_else(|| non_user_error!("")),
235            is_unset,
236            property: property.unwrap_or_else(|| non_user_error!("")),
237            is_when,
238        })
239    }
240}
241impl ToTokens for PropertyAttrData {
242    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
243        if self.pending_attrs.is_empty() {
244            return;
245        }
246
247        let span = self.pending_attrs[0].pound_token.span;
248
249        let Self {
250            pending_attrs,
251            data_ident,
252            builder,
253            is_unset,
254            property,
255            is_when,
256        } = self;
257
258        tokens.extend(quote_spanned! {span=>
259            #(#pending_attrs)*
260            mod #data_ident {
261                fn builder_ident() {
262                    #builder
263                }
264
265                fn property_path() {
266                    #property
267                }
268
269                fn is_unset() {
270                    #is_unset
271                }
272
273                fn is_when() {
274                    #is_when
275                }
276            }
277        })
278    }
279}