diff --git a/components/style/macros.rs b/components/style/macros.rs index 7899ef61083..569d5e7d54c 100644 --- a/components/style/macros.rs +++ b/components/style/macros.rs @@ -110,6 +110,7 @@ macro_rules! define_keyword_type { } impl $crate::values::computed::ComputedValueAsSpecified for $name {} + impl $crate::values::animated::AnimatedValueAsComputed for $name {} no_viewport_percentage!($name); }; } diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs index 476d53c090b..7686d1be53a 100644 --- a/components/style/properties/helpers/animated_properties.mako.rs +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -31,7 +31,10 @@ use std::cmp; #[cfg(feature = "gecko")] use fnv::FnvHashMap; use style_traits::ParseError; use super::ComputedValues; -use values::{Auto, CSSFloat, CustomIdent, Either}; +#[cfg(any(feature = "gecko", feature = "testing"))] +use values::Auto; +use values::{CSSFloat, CustomIdent, Either}; +use values::animated::ToAnimatedValue; use values::animated::effects::BoxShadowList as AnimatedBoxShadowList; use values::animated::effects::Filter as AnimatedFilter; use values::animated::effects::FilterList as AnimatedFilterList; @@ -408,7 +411,8 @@ impl AnimatedProperty { }; % endif % if not prop.is_animatable_with_computed_value: - let value: longhands::${prop.ident}::computed_value::T = value.into(); + let value: longhands::${prop.ident}::computed_value::T = + ToAnimatedValue::from_animated_value(value); % endif style.mutate_${prop.style_struct.ident.strip("_")}().set_${prop.ident}(value); } @@ -425,13 +429,21 @@ impl AnimatedProperty { -> AnimatedProperty { match *property { % for prop in data.longhands: - % if prop.animatable: - AnimatableLonghand::${prop.camel_case} => { - AnimatedProperty::${prop.camel_case}( - old_style.get_${prop.style_struct.ident.strip("_")}().clone_${prop.ident}().into(), - new_style.get_${prop.style_struct.ident.strip("_")}().clone_${prop.ident}().into()) - } - % endif + % if prop.animatable: + AnimatableLonghand::${prop.camel_case} => { + let old_computed = old_style.get_${prop.style_struct.ident.strip("_")}().clone_${prop.ident}(); + let new_computed = new_style.get_${prop.style_struct.ident.strip("_")}().clone_${prop.ident}(); + AnimatedProperty::${prop.camel_case}( + % if prop.is_animatable_with_computed_value: + old_computed, + new_computed, + % else: + old_computed.to_animated_value(), + new_computed.to_animated_value(), + % endif + ) + } + % endif % endfor } } @@ -492,7 +504,7 @@ impl AnimationValue { % if prop.is_animatable_with_computed_value: from % else: - &from.clone().into() + &ToAnimatedValue::from_animated_value(from.clone()) % endif )) % if prop.boxed: @@ -520,13 +532,14 @@ impl AnimationValue { longhands::system_font::resolve_system_font(sf, context); } % endif - Some(AnimationValue::${prop.camel_case}( - % if prop.is_animatable_with_computed_value: - val.to_computed_value(context) - % else: - From::from(val.to_computed_value(context)) - % endif - )) + let computed = val.to_computed_value(context); + Some(AnimationValue::${prop.camel_case}( + % if prop.is_animatable_with_computed_value: + computed + % else: + computed.to_animated_value() + % endif + )) }, % endif % endfor @@ -555,7 +568,7 @@ impl AnimationValue { }, }; % if not prop.is_animatable_with_computed_value: - let computed = From::from(computed); + let computed = computed.to_animated_value(); % endif Some(AnimationValue::${prop.camel_case}(computed)) }, @@ -617,13 +630,16 @@ impl AnimationValue { % for prop in data.longhands: % if prop.animatable: AnimatableLonghand::${prop.camel_case} => { + let computed = computed_values + .get_${prop.style_struct.ident.strip("_")}() + .clone_${prop.ident}(); AnimationValue::${prop.camel_case}( % if prop.is_animatable_with_computed_value: - computed_values.get_${prop.style_struct.ident.strip("_")}().clone_${prop.ident}()) + computed % else: - From::from(computed_values.get_${prop.style_struct.ident.strip("_")}() - .clone_${prop.ident}())) + computed.to_animated_value() % endif + ) } % endif % endfor @@ -2697,25 +2713,6 @@ impl Animatable for Either } } -impl From for RGBA { - fn from(extended_rgba: IntermediateRGBA) -> RGBA { - // RGBA::from_floats clamps each component values. - RGBA::from_floats(extended_rgba.red, - extended_rgba.green, - extended_rgba.blue, - extended_rgba.alpha) - } -} - -impl From for IntermediateRGBA { - fn from(rgba: RGBA) -> IntermediateRGBA { - IntermediateRGBA::new(rgba.red_f32(), - rgba.green_f32(), - rgba.blue_f32(), - rgba.alpha_f32()) - } -} - #[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] /// Unlike RGBA, each component value may exceed the range [0.0, 1.0]. @@ -2744,6 +2741,31 @@ impl IntermediateRGBA { } } +impl ToAnimatedValue for RGBA { + type AnimatedValue = IntermediateRGBA; + + #[inline] + fn to_animated_value(self) -> Self::AnimatedValue { + IntermediateRGBA::new( + self.red_f32(), + self.green_f32(), + self.blue_f32(), + self.alpha_f32(), + ) + } + + #[inline] + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + // RGBA::from_floats clamps each component values. + RGBA::from_floats( + animated.red, + animated.green, + animated.blue, + animated.alpha, + ) + } +} + /// Unlike Animatable for RGBA we don't clamp any component values. impl Animatable for IntermediateRGBA { #[inline] @@ -2798,24 +2820,6 @@ impl Animatable for IntermediateRGBA { } } -impl From> for Either { - fn from(from: Either) -> Either { - match from { - Either::First(from) => Either::First(from.into()), - Either::Second(Auto) => Either::Second(Auto), - } - } -} - -impl From> for Either { - fn from(from: Either) -> Either { - match from { - Either::First(from) => Either::First(from.into()), - Either::Second(Auto) => Either::Second(Auto), - } - } -} - #[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] #[allow(missing_docs)] @@ -2856,6 +2860,26 @@ impl IntermediateColor { } } +impl ToAnimatedValue for Color { + type AnimatedValue = IntermediateColor; + + #[inline] + fn to_animated_value(self) -> Self::AnimatedValue { + IntermediateColor { + color: self.color.to_animated_value(), + foreground_ratio: self.foreground_ratio as f32 * (1. / 255.), + } + } + + #[inline] + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + Color { + color: RGBA::from_animated_value(animated.color), + foreground_ratio: (animated.foreground_ratio * 255.).round() as u8, + } + } +} + impl Animatable for IntermediateColor { #[inline] fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result { @@ -2934,42 +2958,12 @@ impl Animatable for IntermediateColor { } } -impl From for IntermediateColor { - fn from(color: Color) -> IntermediateColor { - IntermediateColor { - color: color.color.into(), - foreground_ratio: color.foreground_ratio as f32 * (1. / 255.), - } - } -} - -impl From for Color { - fn from(color: IntermediateColor) -> Color { - Color { - color: color.color.into(), - foreground_ratio: (color.foreground_ratio * 255.).round() as u8, - } - } -} - /// Animatable SVGPaint pub type IntermediateSVGPaint = SVGPaint; + /// Animatable SVGPaintKind pub type IntermediateSVGPaintKind = SVGPaintKind; -impl From<::values::computed::SVGPaint> for IntermediateSVGPaint { - fn from(paint: ::values::computed::SVGPaint) -> IntermediateSVGPaint { - paint.convert(|color| (*color).into()) - } -} - -impl From for ::values::computed::SVGPaint { - fn from(paint: IntermediateSVGPaint) -> ::values::computed::SVGPaint { - paint.convert(|color| (*color).into()) - } -} - - impl Animatable for IntermediateSVGPaint { #[inline] fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result { diff --git a/components/style/values/animated/effects.rs b/components/style/values/animated/effects.rs index 3d5cb9ea1a7..fb8fb96d3ef 100644 --- a/components/style/values/animated/effects.rs +++ b/components/style/values/animated/effects.rs @@ -12,11 +12,8 @@ use properties::longhands::text_shadow::computed_value::T as ComputedTextShadowL use std::cmp; #[cfg(not(feature = "gecko"))] use values::Impossible; +use values::animated::ToAnimatedValue; use values::computed::{Angle, Number}; -use values::computed::effects::BoxShadow as ComputedBoxShadow; -#[cfg(feature = "gecko")] -use values::computed::effects::Filter as ComputedFilter; -use values::computed::effects::SimpleShadow as ComputedSimpleShadow; use values::computed::length::Length; use values::generics::effects::BoxShadow as GenericBoxShadow; use values::generics::effects::Filter as GenericFilter; @@ -51,15 +48,17 @@ pub struct TextShadowList(pub Vec); /// An animated value for the `drop-shadow()` filter. pub type SimpleShadow = GenericSimpleShadow; -impl From for ComputedBoxShadowList { - fn from(list: BoxShadowList) -> Self { - ComputedBoxShadowList(list.0.into_iter().map(|s| s.into()).collect()) - } -} +impl ToAnimatedValue for ComputedBoxShadowList { + type AnimatedValue = BoxShadowList; -impl From for BoxShadowList { - fn from(list: ComputedBoxShadowList) -> Self { - BoxShadowList(list.0.into_iter().map(|s| s.into()).collect()) + #[inline] + fn to_animated_value(self) -> Self::AnimatedValue { + BoxShadowList(self.0.to_animated_value()) + } + + #[inline] + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + ComputedBoxShadowList(ToAnimatedValue::from_animated_value(animated.0)) } } @@ -114,15 +113,17 @@ impl Animatable for BoxShadowList { } } -impl From for ComputedTextShadowList { - fn from(list: TextShadowList) -> Self { - ComputedTextShadowList(list.0.into_iter().map(|s| s.into()).collect()) - } -} +impl ToAnimatedValue for ComputedTextShadowList { + type AnimatedValue = TextShadowList; -impl From for TextShadowList { - fn from(list: ComputedTextShadowList) -> Self { - TextShadowList(list.0.into_iter().map(|s| s.into()).collect()) + #[inline] + fn to_animated_value(self) -> Self::AnimatedValue { + TextShadowList(self.0.to_animated_value()) + } + + #[inline] + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + ComputedTextShadowList(ToAnimatedValue::from_animated_value(animated.0)) } } @@ -170,28 +171,6 @@ impl Animatable for TextShadowList { } } -impl From for BoxShadow { - #[inline] - fn from(shadow: ComputedBoxShadow) -> Self { - BoxShadow { - base: shadow.base.into(), - spread: shadow.spread, - inset: shadow.inset, - } - } -} - -impl From for ComputedBoxShadow { - #[inline] - fn from(shadow: BoxShadow) -> Self { - ComputedBoxShadow { - base: shadow.base.into(), - spread: shadow.spread, - inset: shadow.inset, - } - } -} - impl Animatable for BoxShadow { #[inline] fn add_weighted( @@ -227,99 +206,32 @@ impl Animatable for BoxShadow { } } -impl From for FilterList { + +impl ToAnimatedValue for ComputedFilterList { + type AnimatedValue = FilterList; + #[cfg(not(feature = "gecko"))] #[inline] - fn from(filters: ComputedFilterList) -> Self { - FilterList(filters.0) + fn to_animated_value(self) -> Self::AnimatedValue { + FilterList(self.0) } #[cfg(feature = "gecko")] #[inline] - fn from(filters: ComputedFilterList) -> Self { - FilterList(filters.0.into_iter().map(|f| f.into()).collect()) + fn to_animated_value(self) -> Self::AnimatedValue { + FilterList(self.0.to_animated_value()) } -} -impl From for ComputedFilterList { #[cfg(not(feature = "gecko"))] #[inline] - fn from(filters: FilterList) -> Self { - ComputedFilterList(filters.0) + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + ComputedFilterList(animated.0) } #[cfg(feature = "gecko")] #[inline] - fn from(filters: FilterList) -> Self { - ComputedFilterList(filters.0.into_iter().map(|f| f.into()).collect()) - } -} - -#[cfg(feature = "gecko")] -impl From for Filter { - #[inline] - fn from(filter: ComputedFilter) -> Self { - match filter { - GenericFilter::Blur(angle) => GenericFilter::Blur(angle), - GenericFilter::Brightness(factor) => GenericFilter::Brightness(factor), - GenericFilter::Contrast(factor) => GenericFilter::Contrast(factor), - GenericFilter::Grayscale(factor) => GenericFilter::Grayscale(factor), - GenericFilter::HueRotate(factor) => GenericFilter::HueRotate(factor), - GenericFilter::Invert(factor) => GenericFilter::Invert(factor), - GenericFilter::Opacity(factor) => GenericFilter::Opacity(factor), - GenericFilter::Saturate(factor) => GenericFilter::Saturate(factor), - GenericFilter::Sepia(factor) => GenericFilter::Sepia(factor), - GenericFilter::DropShadow(shadow) => { - GenericFilter::DropShadow(shadow.into()) - }, - GenericFilter::Url(url) => GenericFilter::Url(url), - } - } -} - -#[cfg(feature = "gecko")] -impl From for ComputedFilter { - #[inline] - fn from(filter: Filter) -> Self { - match filter { - GenericFilter::Blur(angle) => GenericFilter::Blur(angle), - GenericFilter::Brightness(factor) => GenericFilter::Brightness(factor), - GenericFilter::Contrast(factor) => GenericFilter::Contrast(factor), - GenericFilter::Grayscale(factor) => GenericFilter::Grayscale(factor), - GenericFilter::HueRotate(factor) => GenericFilter::HueRotate(factor), - GenericFilter::Invert(factor) => GenericFilter::Invert(factor), - GenericFilter::Opacity(factor) => GenericFilter::Opacity(factor), - GenericFilter::Saturate(factor) => GenericFilter::Saturate(factor), - GenericFilter::Sepia(factor) => GenericFilter::Sepia(factor), - GenericFilter::DropShadow(shadow) => { - GenericFilter::DropShadow(shadow.into()) - }, - GenericFilter::Url(url) => GenericFilter::Url(url.clone()) - } - } -} - -impl From for SimpleShadow { - #[inline] - fn from(shadow: ComputedSimpleShadow) -> Self { - SimpleShadow { - color: shadow.color.into(), - horizontal: shadow.horizontal, - vertical: shadow.vertical, - blur: shadow.blur, - } - } -} - -impl From for ComputedSimpleShadow { - #[inline] - fn from(shadow: SimpleShadow) -> Self { - ComputedSimpleShadow { - color: shadow.color.into(), - horizontal: shadow.horizontal, - vertical: shadow.vertical, - blur: shadow.blur, - } + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + ComputedFilterList(ToAnimatedValue::from_animated_value(animated.0)) } } diff --git a/components/style/values/animated/mod.rs b/components/style/values/animated/mod.rs index 944cc8c2520..f671d1e207f 100644 --- a/components/style/values/animated/mod.rs +++ b/components/style/values/animated/mod.rs @@ -8,4 +8,83 @@ //! computed values and need yet another intermediate representation. This //! module's raison d'ĂȘtre is to ultimately contain all these types. +use app_units::Au; +use values::computed::Angle as ComputedAngle; +use values::specified::url::SpecifiedUrl; + pub mod effects; + +/// Conversion between computed values and intermediate values for animations. +/// +/// Notably, colors are represented as four floats during animations. +pub trait ToAnimatedValue { + /// The type of the animated value. + type AnimatedValue; + + /// Converts this value to an animated value. + fn to_animated_value(self) -> Self::AnimatedValue; + + /// Converts back an animated value into a computed value. + fn from_animated_value(animated: Self::AnimatedValue) -> Self; +} + +impl ToAnimatedValue for Option +where + T: ToAnimatedValue, +{ + type AnimatedValue = Option<::AnimatedValue>; + + #[inline] + fn to_animated_value(self) -> Self::AnimatedValue { + self.map(T::to_animated_value) + } + + #[inline] + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + animated.map(T::from_animated_value) + } +} + +impl ToAnimatedValue for Vec +where + T: ToAnimatedValue, +{ + type AnimatedValue = Vec<::AnimatedValue>; + + #[inline] + fn to_animated_value(self) -> Self::AnimatedValue { + self.into_iter().map(T::to_animated_value).collect() + } + + #[inline] + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + animated.into_iter().map(T::from_animated_value).collect() + } +} + +/// Marker trait for computed values with the same representation during animations. +pub trait AnimatedValueAsComputed {} + +impl AnimatedValueAsComputed for Au {} +impl AnimatedValueAsComputed for ComputedAngle {} +impl AnimatedValueAsComputed for SpecifiedUrl {} +impl AnimatedValueAsComputed for bool {} +impl AnimatedValueAsComputed for f32 {} + +impl ToAnimatedValue for T +where + T: AnimatedValueAsComputed, +{ + type AnimatedValue = Self; + + #[inline] + fn to_animated_value(self) -> Self { + self + } + + #[inline] + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + animated + } +} + diff --git a/components/style/values/generics/effects.rs b/components/style/values/generics/effects.rs index d4b9670ff39..f0a6cbc0b8b 100644 --- a/components/style/values/generics/effects.rs +++ b/components/style/values/generics/effects.rs @@ -11,7 +11,7 @@ use values::specified::url::SpecifiedUrl; /// A generic value for a single `box-shadow`. #[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[derive(Clone, Debug, HasViewportPercentage, PartialEq)] +#[derive(Clone, Debug, HasViewportPercentage, PartialEq, ToAnimatedValue)] pub struct BoxShadow { /// The base shadow. pub base: SimpleShadow, @@ -23,7 +23,7 @@ pub struct BoxShadow { /// A generic value for a single `filter`. #[cfg_attr(feature = "servo", derive(Deserialize, HeapSizeOf, Serialize))] -#[derive(Clone, Debug, HasViewportPercentage, PartialEq, ToComputedValue, ToCss)] +#[derive(Clone, Debug, HasViewportPercentage, PartialEq, ToAnimatedValue, ToComputedValue, ToCss)] pub enum Filter { /// `blur()` #[css(function)] @@ -65,7 +65,7 @@ pub enum Filter { /// Contrary to the canonical order from the spec, the color is serialised /// first, like in Gecko and Webkit. #[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[derive(Clone, Debug, HasViewportPercentage, PartialEq, ToCss)] +#[derive(Clone, Debug, HasViewportPercentage, PartialEq, ToAnimatedValue, ToCss)] pub struct SimpleShadow { /// Color. pub color: Color, diff --git a/components/style/values/generics/mod.rs b/components/style/values/generics/mod.rs index 1a5ad79afea..7c3492e94e2 100644 --- a/components/style/values/generics/mod.rs +++ b/components/style/values/generics/mod.rs @@ -254,8 +254,8 @@ impl ToCss for FontSettingTagFloat { /// An SVG paint value /// /// https://www.w3.org/TR/SVG2/painting.html#SpecifyingPaint -#[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] +#[derive(Clone, Debug, PartialEq, ToAnimatedValue)] pub struct SVGPaint { /// The paint source pub kind: SVGPaintKind, @@ -269,7 +269,7 @@ pub struct SVGPaint { /// to have a fallback, Gecko lets the context /// properties have a fallback as well. #[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[derive(Clone, Debug, PartialEq, ToCss)] +#[derive(Clone, Debug, PartialEq, ToAnimatedValue, ToCss)] pub enum SVGPaintKind { /// `none` None, diff --git a/components/style/values/mod.rs b/components/style/values/mod.rs index ed503eda930..caaee1bc36a 100644 --- a/components/style/values/mod.rs +++ b/components/style/values/mod.rs @@ -52,7 +52,7 @@ impl Parse for Impossible { /// A struct representing one of two kinds of values. #[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[derive(Clone, Copy, HasViewportPercentage, PartialEq, ToComputedValue, ToCss)] +#[derive(Clone, Copy, HasViewportPercentage, PartialEq, ToAnimatedValue, ToComputedValue, ToCss)] pub enum Either { /// The first value. First(A), diff --git a/components/style_derive/lib.rs b/components/style_derive/lib.rs index 6264e8186e1..683d6c2c563 100644 --- a/components/style_derive/lib.rs +++ b/components/style_derive/lib.rs @@ -10,6 +10,7 @@ extern crate synstructure; use proc_macro::TokenStream; mod has_viewport_percentage; +mod to_animated_value; mod to_computed_value; mod to_css; @@ -19,6 +20,12 @@ pub fn derive_has_viewport_percentage(stream: TokenStream) -> TokenStream { has_viewport_percentage::derive(input).to_string().parse().unwrap() } +#[proc_macro_derive(ToAnimatedValue)] +pub fn derive_to_animated_value(stream: TokenStream) -> TokenStream { + let input = syn::parse_derive_input(&stream.to_string()).unwrap(); + to_animated_value::derive(input).to_string().parse().unwrap() +} + #[proc_macro_derive(ToComputedValue)] pub fn derive_to_computed_value(stream: TokenStream) -> TokenStream { let input = syn::parse_derive_input(&stream.to_string()).unwrap(); diff --git a/components/style_derive/to_animated_value.rs b/components/style_derive/to_animated_value.rs new file mode 100644 index 00000000000..ab13a26d5c8 --- /dev/null +++ b/components/style_derive/to_animated_value.rs @@ -0,0 +1,141 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use quote; +use syn; +use synstructure; + +pub fn derive(input: syn::DeriveInput) -> quote::Tokens { + let name = &input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + let mut where_clause = where_clause.clone(); + for param in &input.generics.ty_params { + where_clause.predicates.push(where_predicate(syn::Ty::Path(None, param.ident.clone().into()), None)); + } + + let animated_value_type = syn::Path::from(syn::PathSegment { + ident: name.clone(), + parameters: syn::PathParameters::AngleBracketed(syn::AngleBracketedParameterData { + lifetimes: input.generics.lifetimes.iter().map(|l| { + l.lifetime.clone() + }).collect(), + types: input.generics.ty_params.iter().map(|ty| { + syn::Ty::Path( + Some(syn::QSelf { + ty: Box::new(syn::Ty::Path(None, ty.ident.clone().into())), + position: 3, + }), + syn::Path { + global: true, + segments: vec![ + "values".into(), + "animated".into(), + "ToAnimatedValue".into(), + "AnimatedValue".into(), + ], + }, + ) + }).collect(), + .. Default::default() + }), + }); + + let to_body = match_body(&input, |field| { + quote!(::values::animated::ToAnimatedValue::to_animated_value(#field)) + }); + let from_body = match_body(&input, |field| { + quote!(::values::animated::ToAnimatedValue::from_animated_value(#field)) + }); + + quote! { + impl #impl_generics ::values::animated::ToAnimatedValue for #name #ty_generics #where_clause { + type AnimatedValue = #animated_value_type; + + #[allow(unused_variables)] + #[inline] + fn to_animated_value(self) -> Self::AnimatedValue { + match self { + #to_body + } + } + + #[inline] + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + match animated { + #from_body + } + } + } + } +} + +fn match_body(input: &syn::DeriveInput, f: F) -> quote::Tokens +where + F: Fn(&synstructure::BindingInfo) -> quote::Tokens, +{ + let by_value = synstructure::BindStyle::Move.into(); + synstructure::each_variant(&input, &by_value, |fields, variant| { + let name = if let syn::Body::Enum(_) = input.body { + format!("{}::{}", input.ident, variant.ident).into() + } else { + variant.ident.clone() + }; + let (animated_value, computed_fields) = synstructure::match_pattern(&name, &variant.data, &by_value); + let fields_pairs = fields.iter().zip(computed_fields.iter()); + let mut computations = quote!(); + computations.append_all(fields_pairs.map(|(field, computed_field)| { + let expr = f(field); + quote!(let #computed_field = #expr;) + })); + Some(quote!( + #computations + #animated_value + )) + }) +} + +/// `#ty: ::values::animated::ToAnimatedValue` +fn where_predicate(ty: syn::Ty, animated_value: Option) -> syn::WherePredicate { + syn::WherePredicate::BoundPredicate(syn::WhereBoundPredicate { + bound_lifetimes: vec![], + bounded_ty: ty, + bounds: vec![syn::TyParamBound::Trait( + syn::PolyTraitRef { + bound_lifetimes: vec![], + trait_ref: trait_ref(animated_value), + }, + syn::TraitBoundModifier::None + )], + }) +} + +/// `::values::animated::ToAnimatedValue` +fn trait_ref(animated_value: Option) -> syn::Path { + syn::Path { + global: true, + segments: vec![ + "values".into(), + "animated".into(), + syn::PathSegment { + ident: "ToAnimatedValue".into(), + parameters: syn::PathParameters::AngleBracketed( + syn::AngleBracketedParameterData { + bindings: trait_bindings(animated_value), + .. Default::default() + } + ), + } + ], + } +} + +/// `AnimatedValue = #animated_value,` +fn trait_bindings(animated_value: Option) -> Vec { + animated_value.into_iter().map(|ty| { + syn::TypeBinding { + ident: "AnimatedValue".into(), + ty: ty, + } + }).collect() +} diff --git a/tests/unit/style/animated_properties.rs b/tests/unit/style/animated_properties.rs index 5a16f1c4cf6..9cd6fabc440 100644 --- a/tests/unit/style/animated_properties.rs +++ b/tests/unit/style/animated_properties.rs @@ -7,12 +7,13 @@ use cssparser::RGBA; use style::properties::animated_properties::{Animatable, IntermediateRGBA}; use style::properties::longhands::transform::computed_value::ComputedOperation as TransformOperation; use style::properties::longhands::transform::computed_value::T as TransformList; +use style::values::animated::ToAnimatedValue; use style::values::specified::length::Percentage; fn interpolate_rgba(from: RGBA, to: RGBA, progress: f64) -> RGBA { - let from: IntermediateRGBA = from.into(); - let to: IntermediateRGBA = to.into(); - from.interpolate(&to, progress).unwrap().into() + let from = from.to_animated_value(); + let to = to.to_animated_value(); + RGBA::from_animated_value(from.interpolate(&to, progress).unwrap()) } // Color