mina_macros 0.1.0

Macros for mina
Documentation
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
use quote::{format_ident, quote};
use syn::{
    parse_macro_input, spanned::Spanned, Data, DeriveInput, Error, Field, Fields, Meta, Path,
    Result, Visibility,
};

pub fn animate_impl(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    expand_animate(input)
        .unwrap_or_else(Error::into_compile_error)
        .into()
}

fn expand_animate(input: DeriveInput) -> Result<TokenStream2> {
    let DeriveInput {
        ident: name,
        data,
        generics: _generics, // Not supported yet
        attrs: _attrs,
        vis,
    } = input;
    let Data::Struct(struct_data) = data else {
        return Err(Error::new(Span::call_site(), "derive(Animate) requires a struct type."));
    };
    let Fields::Named(fields) = struct_data.fields else {
        return Err(Error::new(
            struct_data.fields.span(),
            "derive(Animate) requires a struct with named fields."));
    };

    let anim_fields = fields
        .named
        .iter()
        .filter(|f| is_animatable(f))
        .collect::<Vec<_>>();
    let anim_fields = if anim_fields.is_empty() {
        fields.named.iter().collect()
    } else {
        anim_fields
    };

    let builder_shortcuts = builder_shortcuts(&name);
    let timeline_struct = timeline_struct(&name, &vis, &anim_fields)?;
    let timeline_builder_impl = timeline_builder_impl(&name, &anim_fields);
    let keyframe_struct = keyframe_struct(&name, &vis, &anim_fields);
    let keyframe_builder = keyframe_builder(&name, &vis, &anim_fields);
    let animate = quote! {
        #builder_shortcuts
        #timeline_struct
        #timeline_builder_impl
        #keyframe_struct
        #keyframe_builder
    };

    Ok(animate)
}

fn builder_shortcuts(target_name: &Ident) -> TokenStream2 {
    let builder_name = format_ident!("{target_name}KeyframeBuilder");
    let data_name = format_ident!("{target_name}KeyframeData");
    quote! {
        impl #target_name {
            pub fn keyframe(normalized_time: f32) -> #builder_name {
                #builder_name::new(normalized_time)
            }

            pub fn timeline() -> ::mina::TimelineConfiguration<#data_name> {
                ::mina::TimelineConfiguration::default()
            }
        }
    }
}

fn is_animatable(field: &Field) -> bool {
    field.attrs.iter().any(|attr| {
        let Meta::Path(ref path) = attr.meta else { return false; };
        is_simple_path(path, "animate")
    })
}

fn is_simple_path<'a>(path: &Path, name: impl Into<&'a str>) -> bool {
    path.segments.len() == 1
        && path.segments[0].arguments.is_none()
        && path.segments[0].ident == name.into()
}

fn keyframe_builder(
    target_name: &Ident,
    target_visibility: &Visibility,
    target_fields: &[&Field],
) -> TokenStream2 {
    let builder_name = format_ident!("{target_name}KeyframeBuilder");
    let data_name = format_ident!("{target_name}KeyframeData");
    let setters = target_fields.iter().map(|f| {
        let Field {
            ident: field_name,
            ty,
            ..
        } = f;
        quote! {
            pub fn #field_name(mut self, #field_name: #ty) -> Self {
                self.data.#field_name = std::option::Option::Some(#field_name);
                self
            }
        }
    });
    let from_data_assignments = target_fields.iter().map(|f| {
        let Field { ident: field_name, .. } = f;
        quote! { self.data.#field_name = std::option::Option::Some(values.#field_name) }
    });
    quote! {
        #target_visibility struct #builder_name {
            data: #data_name,
            easing: std::option::Option<::mina::Easing>,
            normalized_time: f32,
        }

        impl #builder_name {
            fn new(normalized_time: f32) -> Self {
                Self {
                    normalized_time,
                    data: std::default::Default::default(),
                    easing: None,
                }
            }

            fn values_from(mut self, normalized_time: f32, values: &#target_name) -> Self {
                #(#from_data_assignments);*;
                self
            }

            #(#setters)*
        }

        impl ::mina::KeyframeBuilder for #builder_name {
            type Data = #data_name;

            fn build(&self) -> ::mina::Keyframe<#data_name> {
                ::mina::Keyframe::new(
                    self.normalized_time, self.data.clone(), self.easing.clone())
            }

            fn easing(mut self, easing: ::mina::Easing) -> Self {
                self.easing = std::option::Option::Some(easing);
                self
            }
        }
    }
}

fn keyframe_struct(
    target_name: &Ident,
    target_visibility: &Visibility,
    target_fields: &[&Field],
) -> TokenStream2 {
    let name = format_ident!("{target_name}KeyframeData");
    let fields = target_fields.iter().map(|f| {
        let Field { ident, ty, .. } = f;
        quote! { #ident: std::option::Option<#ty> }
    });
    let values_struct = quote! {
        #[derive(std::clone::Clone, std::fmt::Debug, std::default::Default)]
        #target_visibility struct #name {
            #(#fields),*
        }
    };
    values_struct
}

fn timeline_builder_impl(target_name: &Ident, target_fields: &[&Field]) -> TokenStream2 {
    let timeline_name = format_ident!("{target_name}Timeline");
    let keyframe_data_name = format_ident!("{target_name}KeyframeData");
    let sub_timeline_initializers = target_fields.iter().map(|f| {
        let field_name = f.ident.as_ref().unwrap();
        let sub_name = format_ident!("t_{field_name}");
        quote! {
            #sub_name: ::mina::SubTimeline::from_keyframes(
                &args.keyframes,
                std::default::Default::default(),
                |keyframe| keyframe.#field_name,
                args.default_easing.clone()

            )
        }
    });
    quote! {
        impl ::mina::TimelineBuilder<#timeline_name>
        for ::mina::TimelineConfiguration<#keyframe_data_name>
        {
            fn build(self) -> #timeline_name {
                let args = ::mina::TimelineBuilderArguments::from(self);
                #timeline_name {
                    timescale: args.timescale,
                    #(#sub_timeline_initializers),*,
                    boundary_times: args.boundary_times,
                }
            }
        }

        impl ::mina::TimelineOrBuilder<#timeline_name>
        for ::mina::TimelineConfiguration<#keyframe_data_name>
        {
            fn build(self) -> ::mina::MergedTimeline<#timeline_name> {
                ::mina::MergedTimeline::of([::mina::TimelineBuilder::build(self)])
            }
        }
    }
}

fn timeline_struct(
    target_name: &Ident,
    target_visibility: &Visibility,
    target_fields: &[&Field],
) -> Result<TokenStream2> {
    let name = format_ident!("{target_name}Timeline");
    let fields = target_fields
        .iter()
        .map(|f| {
            let Field { ident, ty, .. } = f;
            let name = format_ident!("t_{}", ident.as_ref().unwrap());
            Ok(quote! { #name: ::mina::SubTimeline<#ty> })
        })
        .collect::<Result<Vec<_>>>()?;
    let value_assignments = target_fields.iter().map(|f| {
        let field_name = f.ident.as_ref().unwrap();
        let sub_name = format_ident!("t_{field_name}");
        quote! {
            if let Some(#field_name) = self
                .#sub_name
                .value_at(normalized_time, frame_index, enable_start_override)
            {
                target.#field_name = #field_name;
            }
        }
    });
    let start_value_assignments = target_fields.iter().map(|f| {
        let field_name = f.ident.as_ref().unwrap();
        let sub_name = format_ident!("t_{field_name}");
        quote! {
            self.#sub_name.override_start_value(values.#field_name);
        }
    });
    let timeline_struct = quote! {
        #[derive(std::clone::Clone, std::fmt::Debug)]
        #target_visibility struct #name {
            boundary_times: std::vec::Vec<f32>,
            timescale: ::mina::TimeScale,
            #(#fields),*
        }

        impl ::mina::Timeline for #name {
            type Target = #target_name;

            fn start_with(&mut self, values: &Self::Target) {
                #(#start_value_assignments)*
            }

            fn update(&self, target: &mut Self::Target, time: f32) {
                let Some((normalized_time, frame_index, enable_start_override)) =
                    ::mina::prepare_frame(time, self.boundary_times.as_slice(), &self.timescale)
                else {
                    return;
                };
                #(#value_assignments)*
            }
        }

        impl ::mina::TimelineOrBuilder<#name> for #name {
            fn build(self) -> ::mina::MergedTimeline<#name> {
                ::mina::MergedTimeline::of([self])
            }
        }
    };
    Ok(timeline_struct)
}