palette_derive/alpha/
with_alpha.rs

1use proc_macro::TokenStream;
2use proc_macro2::{Span, TokenStream as TokenStream2};
3
4use quote::quote;
5use syn::{parse_quote, DeriveInput, Generics, Ident, Type};
6
7use crate::{
8    meta::{
9        parse_field_attributes, parse_namespaced_attributes, FieldAttributes, IdentOrIndex,
10        TypeItemAttributes,
11    },
12    util,
13};
14
15pub fn derive(item: TokenStream) -> ::std::result::Result<TokenStream, Vec<::syn::parse::Error>> {
16    let DeriveInput {
17        ident,
18        generics: original_generics,
19        data,
20        attrs,
21        ..
22    } = syn::parse(item).map_err(|error| vec![error])?;
23    let generics = original_generics;
24
25    let (item_meta, item_errors) = parse_namespaced_attributes::<TypeItemAttributes>(attrs);
26
27    let (fields_meta, field_errors) = if let syn::Data::Struct(struct_data) = data {
28        parse_field_attributes::<FieldAttributes>(struct_data.fields)
29    } else {
30        return Err(vec![syn::Error::new(
31            Span::call_site(),
32            "only structs are supported",
33        )]);
34    };
35
36    let implementation = if let Some((alpha_property, alpha_type)) = fields_meta.alpha_property {
37        implement_for_internal_alpha(&ident, &generics, &alpha_property, &alpha_type, &item_meta)
38    } else {
39        implement_for_external_alpha(&ident, &generics, &item_meta)
40    };
41
42    let item_errors = item_errors
43        .into_iter()
44        .map(|error| error.into_compile_error());
45    let field_errors = field_errors
46        .into_iter()
47        .map(|error| error.into_compile_error());
48
49    Ok(quote! {
50        #(#item_errors)*
51        #(#field_errors)*
52
53        #implementation
54    }
55    .into())
56}
57
58fn implement_for_internal_alpha(
59    ident: &Ident,
60    generics: &Generics,
61    alpha_property: &IdentOrIndex,
62    alpha_type: &Type,
63    item_meta: &TypeItemAttributes,
64) -> TokenStream2 {
65    let with_alpha_trait_path = util::path(["WithAlpha"], item_meta.internal);
66    let stimulus_trait_path = util::path(["stimulus", "Stimulus"], item_meta.internal);
67
68    let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
69
70    quote! {
71        #[automatically_derived]
72        impl #impl_generics #with_alpha_trait_path<#alpha_type> for #ident #type_generics #where_clause {
73            type Color = Self;
74            type WithAlpha = Self;
75
76            fn with_alpha(mut self, alpha: #alpha_type) -> Self::WithAlpha {
77                self.#alpha_property = alpha;
78                self
79            }
80
81            fn without_alpha(mut self) -> Self::Color {
82                self.#alpha_property = #stimulus_trait_path::max_intensity();
83                self
84            }
85
86            fn split(mut self) -> (Self::Color, #alpha_type) {
87                let opaque_alpha = #stimulus_trait_path::max_intensity();
88                let alpha = core::mem::replace(&mut self.#alpha_property, opaque_alpha);
89                (self, alpha)
90            }
91        }
92    }
93}
94
95fn implement_for_external_alpha(
96    ident: &Ident,
97    generics: &Generics,
98    item_meta: &TypeItemAttributes,
99) -> TokenStream2 {
100    let with_alpha_trait_path = util::path(["WithAlpha"], item_meta.internal);
101    let stimulus_trait_path = util::path(["stimulus", "Stimulus"], item_meta.internal);
102    let alpha_path = util::path(["Alpha"], item_meta.internal);
103
104    let (_, type_generics, _) = generics.split_for_impl();
105
106    let alpha_type: Type = parse_quote!(_A);
107    let mut impl_generics = generics.clone();
108    impl_generics.params.push(parse_quote!(_A));
109    impl_generics
110        .make_where_clause()
111        .predicates
112        .push(parse_quote!(_A: #stimulus_trait_path));
113    let (impl_generics, _, where_clause) = impl_generics.split_for_impl();
114
115    quote! {
116        #[automatically_derived]
117        impl #impl_generics #with_alpha_trait_path<#alpha_type> for #ident #type_generics #where_clause {
118            type Color = Self;
119            type WithAlpha = #alpha_path<Self, #alpha_type>;
120
121            fn with_alpha(self, alpha: #alpha_type) -> Self::WithAlpha {
122                #alpha_path {
123                    color: self,
124                    alpha
125                }
126            }
127
128            fn without_alpha(self) -> Self::Color {
129                self
130            }
131
132            fn split(self) -> (Self::Color, #alpha_type) {
133                (self, #stimulus_trait_path::max_intensity())
134            }
135        }
136    }
137}