1use 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#[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}