/* 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 https://mozilla.org/MPL/2.0/. */ //! Animated values. //! //! Some values, notably colors, cannot be interpolated directly with their //! computed values and need yet another intermediate representation. This //! module's raison d'ĂȘtre is to ultimately contain all these types. use crate::color::AbsoluteColor; use crate::properties::{PropertyId, ComputedValues}; use crate::values::computed::url::ComputedUrl; use crate::values::computed::{Angle, Image, Length}; use crate::values::specified::SVGPathData; use crate::values::CSSFloat; use app_units::Au; use smallvec::SmallVec; use std::cmp; pub mod color; pub mod effects; mod font; mod grid; pub mod lists; mod svg; pub mod transform; /// The category a property falls into for ordering purposes. /// /// https://drafts.csswg.org/web-animations/#calculating-computed-keyframes #[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)] enum PropertyCategory { Custom, PhysicalLonghand, LogicalLonghand, Shorthand, } impl PropertyCategory { fn of(id: &PropertyId) -> Self { match *id { PropertyId::NonCustom(id) => match id.longhand_or_shorthand() { Ok(id) => { if id.is_logical() { PropertyCategory::LogicalLonghand } else { PropertyCategory::PhysicalLonghand } }, Err(..) => PropertyCategory::Shorthand, }, PropertyId::Custom(..) => PropertyCategory::Custom, } } } /// A comparator to sort PropertyIds such that physical longhands are sorted /// before logical longhands and shorthands, shorthands with fewer components /// are sorted before shorthands with more components, and otherwise shorthands /// are sorted by IDL name as defined by [Web Animations][property-order]. /// /// Using this allows us to prioritize values specified by longhands (or smaller /// shorthand subsets) when longhands and shorthands are both specified on the /// one keyframe. /// /// [property-order] https://drafts.csswg.org/web-animations/#calculating-computed-keyframes pub fn compare_property_priority(a: &PropertyId, b: &PropertyId) -> cmp::Ordering { let a_category = PropertyCategory::of(a); let b_category = PropertyCategory::of(b); if a_category != b_category { return a_category.cmp(&b_category); } if a_category != PropertyCategory::Shorthand { return cmp::Ordering::Equal; } let a = a.as_shorthand().unwrap(); let b = b.as_shorthand().unwrap(); // Within shorthands, sort by the number of subproperties, then by IDL // name. let subprop_count_a = a.longhands().count(); let subprop_count_b = b.longhands().count(); subprop_count_a .cmp(&subprop_count_b) .then_with(|| a.idl_name_sort_order().cmp(&b.idl_name_sort_order())) } /// A helper function to animate two multiplicative factor. pub fn animate_multiplicative_factor( this: CSSFloat, other: CSSFloat, procedure: Procedure, ) -> Result { Ok((this - 1.).animate(&(other - 1.), procedure)? + 1.) } /// Animate from one value to another. /// /// This trait is derivable with `#[derive(Animate)]`. The derived /// implementation uses a `match` expression with identical patterns for both /// `self` and `other`, calling `Animate::animate` on each fields of the values. /// If a field is annotated with `#[animation(constant)]`, the two values should /// be equal or an error is returned. /// /// If a variant is annotated with `#[animation(error)]`, the corresponding /// `match` arm returns an error. /// /// Trait bounds for type parameter `Foo` can be opted out of with /// `#[animation(no_bound(Foo))]` on the type definition, trait bounds for /// fields can be opted into with `#[animation(field_bound)]` on the field. pub trait Animate: Sized { /// Animate a value towards another one, given an animation procedure. fn animate(&self, other: &Self, procedure: Procedure) -> Result; } /// An animation procedure. /// /// #[allow(missing_docs)] #[derive(Clone, Copy, Debug, PartialEq)] pub enum Procedure { /// Interpolate { progress: f64 }, /// Add, /// Accumulate { count: u64 }, } /// The context needed to provide an animated value from a computed value. pub struct Context<'a> { /// The computed style we're taking the value from. pub style: &'a ComputedValues, } /// Conversion between computed values and intermediate values for animations. /// /// Notably, colors are represented as four floats during animations. /// /// This trait is derivable with `#[derive(ToAnimatedValue)]`. pub trait ToAnimatedValue { /// The type of the animated value. type AnimatedValue; /// Converts this value to an animated value. fn to_animated_value(self, context: &Context) -> Self::AnimatedValue; /// Converts back an animated value into a computed value. fn from_animated_value(animated: Self::AnimatedValue) -> Self; } /// Returns a value similar to `self` that represents zero. /// /// This trait is derivable with `#[derive(ToAnimatedValue)]`. If a field is /// annotated with `#[animation(constant)]`, a clone of its value will be used /// instead of calling `ToAnimatedZero::to_animated_zero` on it. /// /// If a variant is annotated with `#[animation(error)]`, the corresponding /// `match` arm is not generated. /// /// Trait bounds for type parameter `Foo` can be opted out of with /// `#[animation(no_bound(Foo))]` on the type definition. pub trait ToAnimatedZero: Sized { /// Returns a value that, when added with an underlying value, will produce the underlying /// value. This is used for SMIL animation's "by-animation" where SMIL first interpolates from /// the zero value to the 'by' value, and then adds the result to the underlying value. /// /// This is not the necessarily the same as the initial value of a property. For example, the /// initial value of 'stroke-width' is 1, but the zero value is 0, since adding 1 to the /// underlying value will not produce the underlying value. fn to_animated_zero(&self) -> Result; } impl Procedure { /// Returns this procedure as a pair of weights. /// /// This is useful for animations that don't animate differently /// depending on the used procedure. #[inline] pub fn weights(self) -> (f64, f64) { match self { Procedure::Interpolate { progress } => (1. - progress, progress), Procedure::Add => (1., 1.), Procedure::Accumulate { count } => (count as f64, 1.), } } } /// impl Animate for i32 { #[inline] fn animate(&self, other: &Self, procedure: Procedure) -> Result { Ok(((*self as f64).animate(&(*other as f64), procedure)? + 0.5).floor() as i32) } } /// impl Animate for f32 { #[inline] fn animate(&self, other: &Self, procedure: Procedure) -> Result { let ret = (*self as f64).animate(&(*other as f64), procedure)?; Ok(ret.min(f32::MAX as f64).max(f32::MIN as f64) as f32) } } /// impl Animate for f64 { #[inline] fn animate(&self, other: &Self, procedure: Procedure) -> Result { let (self_weight, other_weight) = procedure.weights(); let ret = *self * self_weight + *other * other_weight; Ok(ret.min(f64::MAX).max(f64::MIN)) } } impl Animate for Option where T: Animate, { #[inline] fn animate(&self, other: &Self, procedure: Procedure) -> Result { match (self.as_ref(), other.as_ref()) { (Some(ref this), Some(ref other)) => Ok(Some(this.animate(other, procedure)?)), (None, None) => Ok(None), _ => Err(()), } } } impl ToAnimatedValue for Au { type AnimatedValue = Length; #[inline] fn to_animated_value(self, context: &Context) -> Self::AnimatedValue { Length::new(self.to_f32_px()).to_animated_value(context) } #[inline] fn from_animated_value(animated: Self::AnimatedValue) -> Self { Au::from_f32_px(Length::from_animated_value(animated).px()) } } impl Animate for Box { #[inline] fn animate(&self, other: &Self, procedure: Procedure) -> Result { Ok(Box::new((**self).animate(&other, procedure)?)) } } impl ToAnimatedValue for Option where T: ToAnimatedValue, { type AnimatedValue = Option<::AnimatedValue>; #[inline] fn to_animated_value(self, context: &Context) -> Self::AnimatedValue { self.map(|v| T::to_animated_value(v, context)) } #[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, context: &Context) -> Self::AnimatedValue { self.into_iter().map(|v| v.to_animated_value(context)).collect() } #[inline] fn from_animated_value(animated: Self::AnimatedValue) -> Self { animated.into_iter().map(T::from_animated_value).collect() } } impl ToAnimatedValue for thin_vec::ThinVec where T: ToAnimatedValue, { type AnimatedValue = thin_vec::ThinVec<::AnimatedValue>; #[inline] fn to_animated_value(self, context: &Context) -> Self::AnimatedValue { self.into_iter().map(|v| v.to_animated_value(context)).collect() } #[inline] fn from_animated_value(animated: Self::AnimatedValue) -> Self { animated.into_iter().map(T::from_animated_value).collect() } } impl ToAnimatedValue for Box where T: ToAnimatedValue, { type AnimatedValue = Box<::AnimatedValue>; #[inline] fn to_animated_value(self, context: &Context) -> Self::AnimatedValue { Box::new((*self).to_animated_value(context)) } #[inline] fn from_animated_value(animated: Self::AnimatedValue) -> Self { Box::new(T::from_animated_value(*animated)) } } impl ToAnimatedValue for Box<[T]> where T: ToAnimatedValue, { type AnimatedValue = Box<[::AnimatedValue]>; #[inline] fn to_animated_value(self, context: &Context) -> Self::AnimatedValue { self.into_vec() .into_iter() .map(|v| v.to_animated_value(context)) .collect() } #[inline] fn from_animated_value(animated: Self::AnimatedValue) -> Self { animated .into_vec() .into_iter() .map(T::from_animated_value) .collect() } } impl ToAnimatedValue for crate::OwnedSlice where T: ToAnimatedValue, { type AnimatedValue = crate::OwnedSlice<::AnimatedValue>; #[inline] fn to_animated_value(self, context: &Context) -> Self::AnimatedValue { self.into_box().to_animated_value(context).into() } #[inline] fn from_animated_value(animated: Self::AnimatedValue) -> Self { Self::from(Box::from_animated_value(animated.into_box())) } } impl ToAnimatedValue for SmallVec<[T; 1]> where T: ToAnimatedValue, { type AnimatedValue = SmallVec<[T::AnimatedValue; 1]>; #[inline] fn to_animated_value(self, context: &Context) -> Self::AnimatedValue { self.into_iter().map(|v| v.to_animated_value(context)).collect() } #[inline] fn from_animated_value(animated: Self::AnimatedValue) -> Self { animated.into_iter().map(T::from_animated_value).collect() } } macro_rules! trivial_to_animated_value { ($ty:ty) => { impl $crate::values::animated::ToAnimatedValue for $ty { type AnimatedValue = Self; #[inline] fn to_animated_value(self, _: &Context) -> Self { self } #[inline] fn from_animated_value(animated: Self::AnimatedValue) -> Self { animated } } }; } trivial_to_animated_value!(crate::Atom); trivial_to_animated_value!(Angle); trivial_to_animated_value!(ComputedUrl); trivial_to_animated_value!(bool); trivial_to_animated_value!(f32); trivial_to_animated_value!(i32); trivial_to_animated_value!(u32); trivial_to_animated_value!(usize); trivial_to_animated_value!(AbsoluteColor); trivial_to_animated_value!(crate::values::generics::color::ColorMixFlags); // Note: This implementation is for ToAnimatedValue of ShapeSource. // // SVGPathData uses Box<[T]>. If we want to derive ToAnimatedValue for all the // types, we have to do "impl ToAnimatedValue for Box<[T]>" first. // However, the general version of "impl ToAnimatedValue for Box<[T]>" needs to // clone |T| and convert it into |T::AnimatedValue|. However, for SVGPathData // that is unnecessary--moving |T| is sufficient. So here, we implement this // trait manually. trivial_to_animated_value!(SVGPathData); // FIXME: Bug 1514342, Image is not animatable, but we still need to implement // this to avoid adding this derive to generic::Image and all its arms. We can // drop this after landing Bug 1514342. trivial_to_animated_value!(Image); impl ToAnimatedZero for Au { #[inline] fn to_animated_zero(&self) -> Result { Ok(Au(0)) } } impl ToAnimatedZero for f32 { #[inline] fn to_animated_zero(&self) -> Result { Ok(0.) } } impl ToAnimatedZero for f64 { #[inline] fn to_animated_zero(&self) -> Result { Ok(0.) } } impl ToAnimatedZero for i32 { #[inline] fn to_animated_zero(&self) -> Result { Ok(0) } } impl ToAnimatedZero for Box where T: ToAnimatedZero, { #[inline] fn to_animated_zero(&self) -> Result { Ok(Box::new((**self).to_animated_zero()?)) } } impl ToAnimatedZero for Option where T: ToAnimatedZero, { #[inline] fn to_animated_zero(&self) -> Result { match *self { Some(ref value) => Ok(Some(value.to_animated_zero()?)), None => Ok(None), } } } impl ToAnimatedZero for Vec where T: ToAnimatedZero, { #[inline] fn to_animated_zero(&self) -> Result { self.iter().map(|v| v.to_animated_zero()).collect() } } impl ToAnimatedZero for thin_vec::ThinVec where T: ToAnimatedZero, { #[inline] fn to_animated_zero(&self) -> Result { self.iter().map(|v| v.to_animated_zero()).collect() } } impl ToAnimatedZero for Box<[T]> where T: ToAnimatedZero, { #[inline] fn to_animated_zero(&self) -> Result { self.iter().map(|v| v.to_animated_zero()).collect() } } impl ToAnimatedZero for crate::OwnedSlice where T: ToAnimatedZero, { #[inline] fn to_animated_zero(&self) -> Result { self.iter().map(|v| v.to_animated_zero()).collect() } } impl ToAnimatedZero for crate::ArcSlice where T: ToAnimatedZero, { #[inline] fn to_animated_zero(&self) -> Result { let v = self .iter() .map(|v| v.to_animated_zero()) .collect::, _>>()?; Ok(crate::ArcSlice::from_iter(v.into_iter())) } }