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