Skip to main content

style/values/animated/
mod.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Animated values.
6//!
7//! Some values, notably colors, cannot be interpolated directly with their
8//! computed values and need yet another intermediate representation. This
9//! module's raison d'ĂȘtre is to ultimately contain all these types.
10
11use crate::color::AbsoluteColor;
12use crate::properties::{ComputedValues, PropertyId};
13use crate::values::computed::url::ComputedUrl;
14use crate::values::computed::{Angle, Image, Length};
15use crate::values::generics::{ClampToNonNegative, NonNegative};
16use crate::values::specified::SVGPathData;
17use crate::values::CSSFloat;
18use app_units::Au;
19use smallvec::SmallVec;
20use std::cmp;
21
22pub mod color;
23pub mod effects;
24mod font;
25mod grid;
26pub mod lists;
27mod svg;
28pub mod text;
29pub mod transform;
30
31/// The category a property falls into for ordering purposes.
32///
33/// https://drafts.csswg.org/web-animations/#calculating-computed-keyframes
34#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
35enum PropertyCategory {
36    Custom,
37    PhysicalLonghand,
38    LogicalLonghand,
39    Shorthand,
40}
41
42impl PropertyCategory {
43    fn of(id: &PropertyId) -> Self {
44        match *id {
45            PropertyId::NonCustom(id) => match id.longhand_or_shorthand() {
46                Ok(id) => {
47                    if id.is_logical() {
48                        PropertyCategory::LogicalLonghand
49                    } else {
50                        PropertyCategory::PhysicalLonghand
51                    }
52                },
53                Err(..) => PropertyCategory::Shorthand,
54            },
55            PropertyId::Custom(..) => PropertyCategory::Custom,
56        }
57    }
58}
59
60/// A comparator to sort PropertyIds such that physical longhands are sorted
61/// before logical longhands and shorthands, shorthands with fewer components
62/// are sorted before shorthands with more components, and otherwise shorthands
63/// are sorted by IDL name as defined by [Web Animations][property-order].
64///
65/// Using this allows us to prioritize values specified by longhands (or smaller
66/// shorthand subsets) when longhands and shorthands are both specified on the
67/// one keyframe.
68///
69/// [property-order] https://drafts.csswg.org/web-animations/#calculating-computed-keyframes
70pub fn compare_property_priority(a: &PropertyId, b: &PropertyId) -> cmp::Ordering {
71    let a_category = PropertyCategory::of(a);
72    let b_category = PropertyCategory::of(b);
73
74    if a_category != b_category {
75        return a_category.cmp(&b_category);
76    }
77
78    if a_category != PropertyCategory::Shorthand {
79        return cmp::Ordering::Equal;
80    }
81
82    let a = a.as_shorthand().unwrap();
83    let b = b.as_shorthand().unwrap();
84    // Within shorthands, sort by the number of subproperties, then by IDL
85    // name.
86    let subprop_count_a = a.longhands().count();
87    let subprop_count_b = b.longhands().count();
88    subprop_count_a
89        .cmp(&subprop_count_b)
90        .then_with(|| a.idl_name_sort_order().cmp(&b.idl_name_sort_order()))
91}
92
93/// A helper function to animate two multiplicative factor.
94pub fn animate_multiplicative_factor(
95    this: CSSFloat,
96    other: CSSFloat,
97    procedure: Procedure,
98) -> Result<CSSFloat, ()> {
99    Ok((this - 1.).animate(&(other - 1.), procedure)? + 1.)
100}
101
102/// Animate from one value to another.
103///
104/// This trait is derivable with `#[derive(Animate)]`. The derived
105/// implementation uses a `match` expression with identical patterns for both
106/// `self` and `other`, calling `Animate::animate` on each fields of the values.
107/// If a field is annotated with `#[animation(constant)]`, the two values should
108/// be equal or an error is returned.
109///
110/// If a variant is annotated with `#[animation(error)]`, the corresponding
111/// `match` arm returns an error.
112///
113/// Trait bounds for type parameter `Foo` can be opted out of with
114/// `#[animation(no_bound(Foo))]` on the type definition, trait bounds for
115/// fields can be opted into with `#[animation(field_bound)]` on the field.
116pub trait Animate: Sized {
117    /// Animate a value towards another one, given an animation procedure.
118    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>;
119}
120
121/// An animation procedure.
122///
123/// <https://drafts.csswg.org/web-animations/#procedures-for-animating-properties>
124#[allow(missing_docs)]
125#[derive(Clone, Copy, Debug, PartialEq)]
126pub enum Procedure {
127    /// <https://drafts.csswg.org/web-animations/#animation-interpolation>
128    Interpolate { progress: f64 },
129    /// <https://drafts.csswg.org/web-animations/#animation-addition>
130    Add,
131    /// <https://drafts.csswg.org/web-animations/#animation-accumulation>
132    Accumulate { count: u64 },
133}
134
135/// The context needed to provide an animated value from a computed value.
136pub struct Context<'a> {
137    /// The computed style we're taking the value from.
138    pub style: &'a ComputedValues,
139}
140
141/// Conversion between computed values and intermediate values for animations.
142///
143/// Notably, colors are represented as four floats during animations.
144///
145/// This trait is derivable with `#[derive(ToAnimatedValue)]`.
146pub trait ToAnimatedValue {
147    /// The type of the animated value.
148    type AnimatedValue;
149
150    /// Converts this value to an animated value.
151    fn to_animated_value(self, context: &Context) -> Self::AnimatedValue;
152
153    /// Converts back an animated value into a computed value.
154    fn from_animated_value(animated: Self::AnimatedValue) -> Self;
155}
156
157/// Returns a value similar to `self` that represents zero.
158///
159/// This trait is derivable with `#[derive(ToAnimatedValue)]`. If a field is
160/// annotated with `#[animation(constant)]`, a clone of its value will be used
161/// instead of calling `ToAnimatedZero::to_animated_zero` on it.
162///
163/// If a variant is annotated with `#[animation(error)]`, the corresponding
164/// `match` arm is not generated.
165///
166/// Trait bounds for type parameter `Foo` can be opted out of with
167/// `#[animation(no_bound(Foo))]` on the type definition.
168pub trait ToAnimatedZero: Sized {
169    /// Returns a value that, when added with an underlying value, will produce the underlying
170    /// value. This is used for SMIL animation's "by-animation" where SMIL first interpolates from
171    /// the zero value to the 'by' value, and then adds the result to the underlying value.
172    ///
173    /// This is not the necessarily the same as the initial value of a property. For example, the
174    /// initial value of 'stroke-width' is 1, but the zero value is 0, since adding 1 to the
175    /// underlying value will not produce the underlying value.
176    fn to_animated_zero(&self) -> Result<Self, ()>;
177}
178
179impl Procedure {
180    /// Returns this procedure as a pair of weights.
181    ///
182    /// This is useful for animations that don't animate differently
183    /// depending on the used procedure.
184    #[inline]
185    pub fn weights(self) -> (f64, f64) {
186        match self {
187            Procedure::Interpolate { progress } => (1. - progress, progress),
188            Procedure::Add => (1., 1.),
189            Procedure::Accumulate { count } => (count as f64, 1.),
190        }
191    }
192}
193
194/// <https://drafts.csswg.org/css-transitions/#animtype-number>
195impl Animate for i32 {
196    #[inline]
197    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
198        Ok(((*self as f64).animate(&(*other as f64), procedure)? + 0.5).floor() as i32)
199    }
200}
201
202/// <https://drafts.csswg.org/css-transitions/#animtype-number>
203impl Animate for f32 {
204    #[inline]
205    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
206        let ret = (*self as f64).animate(&(*other as f64), procedure)?;
207        Ok(ret.min(f32::MAX as f64).max(f32::MIN as f64) as f32)
208    }
209}
210
211/// <https://drafts.csswg.org/css-transitions/#animtype-number>
212impl Animate for f64 {
213    #[inline]
214    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
215        let (self_weight, other_weight) = procedure.weights();
216
217        let ret = *self * self_weight + *other * other_weight;
218        Ok(ret.min(f64::MAX).max(f64::MIN))
219    }
220}
221
222impl<T> Animate for Option<T>
223where
224    T: Animate,
225{
226    #[inline]
227    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
228        match (self.as_ref(), other.as_ref()) {
229            (Some(ref this), Some(ref other)) => Ok(Some(this.animate(other, procedure)?)),
230            (None, None) => Ok(None),
231            _ => Err(()),
232        }
233    }
234}
235
236impl<T: ToAnimatedValue + ClampToNonNegative> ToAnimatedValue for NonNegative<T> {
237    type AnimatedValue = NonNegative<<T as ToAnimatedValue>::AnimatedValue>;
238
239    #[inline]
240    fn to_animated_value(self, cx: &crate::values::animated::Context) -> Self::AnimatedValue {
241        NonNegative(self.0.to_animated_value(cx))
242    }
243
244    #[inline]
245    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
246        Self(<T as ToAnimatedValue>::from_animated_value(animated.0).clamp_to_non_negative())
247    }
248}
249
250impl ToAnimatedValue for Au {
251    type AnimatedValue = Length;
252
253    #[inline]
254    fn to_animated_value(self, context: &Context) -> Self::AnimatedValue {
255        Length::new(self.to_f32_px()).to_animated_value(context)
256    }
257
258    #[inline]
259    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
260        Au::from_f32_px(Length::from_animated_value(animated).px())
261    }
262}
263
264impl<T: Animate> Animate for Box<T> {
265    #[inline]
266    fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
267        Ok(Box::new((**self).animate(&other, procedure)?))
268    }
269}
270
271impl<T> ToAnimatedValue for Option<T>
272where
273    T: ToAnimatedValue,
274{
275    type AnimatedValue = Option<<T as ToAnimatedValue>::AnimatedValue>;
276
277    #[inline]
278    fn to_animated_value(self, context: &Context) -> Self::AnimatedValue {
279        self.map(|v| T::to_animated_value(v, context))
280    }
281
282    #[inline]
283    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
284        animated.map(T::from_animated_value)
285    }
286}
287
288impl<T> ToAnimatedValue for Vec<T>
289where
290    T: ToAnimatedValue,
291{
292    type AnimatedValue = Vec<<T as ToAnimatedValue>::AnimatedValue>;
293
294    #[inline]
295    fn to_animated_value(self, context: &Context) -> Self::AnimatedValue {
296        self.into_iter()
297            .map(|v| v.to_animated_value(context))
298            .collect()
299    }
300
301    #[inline]
302    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
303        animated.into_iter().map(T::from_animated_value).collect()
304    }
305}
306
307impl<T> ToAnimatedValue for thin_vec::ThinVec<T>
308where
309    T: ToAnimatedValue,
310{
311    type AnimatedValue = thin_vec::ThinVec<<T as ToAnimatedValue>::AnimatedValue>;
312
313    #[inline]
314    fn to_animated_value(self, context: &Context) -> Self::AnimatedValue {
315        self.into_iter()
316            .map(|v| v.to_animated_value(context))
317            .collect()
318    }
319
320    #[inline]
321    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
322        animated.into_iter().map(T::from_animated_value).collect()
323    }
324}
325
326impl<T> ToAnimatedValue for Box<T>
327where
328    T: ToAnimatedValue,
329{
330    type AnimatedValue = Box<<T as ToAnimatedValue>::AnimatedValue>;
331
332    #[inline]
333    fn to_animated_value(self, context: &Context) -> Self::AnimatedValue {
334        Box::new((*self).to_animated_value(context))
335    }
336
337    #[inline]
338    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
339        Box::new(T::from_animated_value(*animated))
340    }
341}
342
343impl<T> ToAnimatedValue for Box<[T]>
344where
345    T: ToAnimatedValue,
346{
347    type AnimatedValue = Box<[<T as ToAnimatedValue>::AnimatedValue]>;
348
349    #[inline]
350    fn to_animated_value(self, context: &Context) -> Self::AnimatedValue {
351        self.into_vec()
352            .into_iter()
353            .map(|v| v.to_animated_value(context))
354            .collect()
355    }
356
357    #[inline]
358    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
359        animated
360            .into_vec()
361            .into_iter()
362            .map(T::from_animated_value)
363            .collect()
364    }
365}
366
367impl<T> ToAnimatedValue for crate::OwnedSlice<T>
368where
369    T: ToAnimatedValue,
370{
371    type AnimatedValue = crate::OwnedSlice<<T as ToAnimatedValue>::AnimatedValue>;
372
373    #[inline]
374    fn to_animated_value(self, context: &Context) -> Self::AnimatedValue {
375        self.into_box().to_animated_value(context).into()
376    }
377
378    #[inline]
379    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
380        Self::from(Box::from_animated_value(animated.into_box()))
381    }
382}
383
384impl<T> ToAnimatedValue for SmallVec<[T; 1]>
385where
386    T: ToAnimatedValue,
387{
388    type AnimatedValue = SmallVec<[T::AnimatedValue; 1]>;
389
390    #[inline]
391    fn to_animated_value(self, context: &Context) -> Self::AnimatedValue {
392        self.into_iter()
393            .map(|v| v.to_animated_value(context))
394            .collect()
395    }
396
397    #[inline]
398    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
399        animated.into_iter().map(T::from_animated_value).collect()
400    }
401}
402
403macro_rules! trivial_to_animated_value {
404    ($ty:ty) => {
405        impl $crate::values::animated::ToAnimatedValue for $ty {
406            type AnimatedValue = Self;
407
408            #[inline]
409            fn to_animated_value(self, _: &Context) -> Self {
410                self
411            }
412
413            #[inline]
414            fn from_animated_value(animated: Self::AnimatedValue) -> Self {
415                animated
416            }
417        }
418    };
419}
420
421trivial_to_animated_value!(crate::Atom);
422trivial_to_animated_value!(Angle);
423trivial_to_animated_value!(ComputedUrl);
424trivial_to_animated_value!(bool);
425trivial_to_animated_value!(f32);
426trivial_to_animated_value!(i32);
427trivial_to_animated_value!(u8);
428trivial_to_animated_value!(u32);
429trivial_to_animated_value!(usize);
430trivial_to_animated_value!(AbsoluteColor);
431trivial_to_animated_value!(crate::values::generics::color::ColorMixFlags);
432// Note: This implementation is for ToAnimatedValue of ShapeSource.
433//
434// SVGPathData uses Box<[T]>. If we want to derive ToAnimatedValue for all the
435// types, we have to do "impl ToAnimatedValue for Box<[T]>" first.
436// However, the general version of "impl ToAnimatedValue for Box<[T]>" needs to
437// clone |T| and convert it into |T::AnimatedValue|. However, for SVGPathData
438// that is unnecessary--moving |T| is sufficient. So here, we implement this
439// trait manually.
440trivial_to_animated_value!(SVGPathData);
441// FIXME: Bug 1514342, Image is not animatable, but we still need to implement
442// this to avoid adding this derive to generic::Image and all its arms. We can
443// drop this after landing Bug 1514342.
444trivial_to_animated_value!(Image);
445
446impl ToAnimatedZero for Au {
447    #[inline]
448    fn to_animated_zero(&self) -> Result<Self, ()> {
449        Ok(Au(0))
450    }
451}
452
453impl ToAnimatedZero for f32 {
454    #[inline]
455    fn to_animated_zero(&self) -> Result<Self, ()> {
456        Ok(0.)
457    }
458}
459
460impl ToAnimatedZero for f64 {
461    #[inline]
462    fn to_animated_zero(&self) -> Result<Self, ()> {
463        Ok(0.)
464    }
465}
466
467impl ToAnimatedZero for i32 {
468    #[inline]
469    fn to_animated_zero(&self) -> Result<Self, ()> {
470        Ok(0)
471    }
472}
473
474impl<T> ToAnimatedZero for Box<T>
475where
476    T: ToAnimatedZero,
477{
478    #[inline]
479    fn to_animated_zero(&self) -> Result<Self, ()> {
480        Ok(Box::new((**self).to_animated_zero()?))
481    }
482}
483
484impl<T> ToAnimatedZero for Option<T>
485where
486    T: ToAnimatedZero,
487{
488    #[inline]
489    fn to_animated_zero(&self) -> Result<Self, ()> {
490        match *self {
491            Some(ref value) => Ok(Some(value.to_animated_zero()?)),
492            None => Ok(None),
493        }
494    }
495}
496
497impl<T> ToAnimatedZero for Vec<T>
498where
499    T: ToAnimatedZero,
500{
501    #[inline]
502    fn to_animated_zero(&self) -> Result<Self, ()> {
503        self.iter().map(|v| v.to_animated_zero()).collect()
504    }
505}
506
507impl<T> ToAnimatedZero for thin_vec::ThinVec<T>
508where
509    T: ToAnimatedZero,
510{
511    #[inline]
512    fn to_animated_zero(&self) -> Result<Self, ()> {
513        self.iter().map(|v| v.to_animated_zero()).collect()
514    }
515}
516
517impl<T> ToAnimatedZero for Box<[T]>
518where
519    T: ToAnimatedZero,
520{
521    #[inline]
522    fn to_animated_zero(&self) -> Result<Self, ()> {
523        self.iter().map(|v| v.to_animated_zero()).collect()
524    }
525}
526
527impl<T> ToAnimatedZero for crate::OwnedSlice<T>
528where
529    T: ToAnimatedZero,
530{
531    #[inline]
532    fn to_animated_zero(&self) -> Result<Self, ()> {
533        self.iter().map(|v| v.to_animated_zero()).collect()
534    }
535}
536
537impl<T> ToAnimatedZero for crate::ArcSlice<T>
538where
539    T: ToAnimatedZero,
540{
541    #[inline]
542    fn to_animated_zero(&self) -> Result<Self, ()> {
543        let v = self
544            .iter()
545            .map(|v| v.to_animated_zero())
546            .collect::<Result<Vec<_>, _>>()?;
547        Ok(crate::ArcSlice::from_iter(v.into_iter()))
548    }
549}