Skip to main content

aura_anim_macros/
lib.rs

1//! Derive macros for Aura animation values.
2
3use proc_macro::TokenStream;
4use proc_macro_crate::{FoundCrate, crate_name};
5use quote::{format_ident, quote};
6use syn::{Data, DeriveInput, Fields, parse_macro_input, parse_quote};
7
8/// Derives field-by-field interpolation for a struct.
9///
10/// Every field must implement `Animatable`. Named, tuple, and unit structs are
11/// supported.
12#[proc_macro_derive(Animatable)]
13pub fn derive_animatable(input: TokenStream) -> TokenStream {
14    let input = parse_macro_input!(input as DeriveInput);
15    expand(input)
16        .unwrap_or_else(syn::Error::into_compile_error)
17        .into()
18}
19
20fn expand(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
21    let path = crate_path();
22    let name = input.ident;
23    let Data::Struct(data) = input.data else {
24        return Err(syn::Error::new_spanned(
25            name,
26            "Animatable can only be derived for structs",
27        ));
28    };
29
30    let mut generics = input.generics;
31    let field_types = data
32        .fields
33        .iter()
34        .map(|field| field.ty.clone())
35        .collect::<Vec<_>>();
36    let where_clause = generics.make_where_clause();
37    for field_type in &field_types {
38        where_clause
39            .predicates
40            .push(parse_quote!(#field_type: #path::Animatable));
41    }
42    let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
43
44    let interpolate_body = match data.fields {
45        Fields::Named(fields) => {
46            let names = fields
47                .named
48                .iter()
49                .map(|field| field.ident.as_ref().unwrap())
50                .collect::<Vec<_>>();
51            quote! {
52                Self {
53                    #(
54                        #names: #path::Interpolate::interpolate_progress(
55                            &from.#names,
56                            &to.#names,
57                            progress,
58                        )
59                    ),*
60                }
61            }
62        }
63        Fields::Unnamed(fields) => {
64            let indexes = (0..fields.unnamed.len())
65                .map(syn::Index::from)
66                .collect::<Vec<_>>();
67            quote! {
68                Self(
69                    #(
70                        #path::Interpolate::interpolate_progress(
71                            &from.#indexes,
72                            &to.#indexes,
73                            progress,
74                        )
75                    ),*
76                )
77            }
78        }
79        Fields::Unit => quote!(Self),
80    };
81
82    let interpolate_impl = quote! {
83        impl #impl_generics #path::Interpolate for #name #type_generics #where_clause {
84            fn interpolate_progress(
85                from: &Self,
86                to: &Self,
87                progress: #path::InterpolationProgress,
88            ) -> Self {
89                #interpolate_body
90            }
91        }
92    };
93
94    Ok(interpolate_impl)
95}
96
97fn crate_path() -> proc_macro2::TokenStream {
98    for package in ["aura-anim-core", "aura-anim"] {
99        if let Ok(found) = crate_name(package) {
100            return match found {
101                FoundCrate::Itself if package == "aura-anim-core" => quote!(crate),
102                FoundCrate::Itself => quote!(::aura_anim),
103                FoundCrate::Name(name) => {
104                    let ident = format_ident!("{name}");
105                    quote!(::#ident)
106                }
107            };
108        }
109    }
110
111    quote!(::aura_anim)
112}