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