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::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()
281            .map(|v| v.to_animated_value(context))
282            .collect()
283    }
284
285    #[inline]
286    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
287        animated.into_iter().map(T::from_animated_value).collect()
288    }
289}
290
291impl<T> ToAnimatedValue for thin_vec::ThinVec<T>
292where
293    T: ToAnimatedValue,
294{
295    type AnimatedValue = thin_vec::ThinVec<<T as ToAnimatedValue>::AnimatedValue>;
296
297    #[inline]
298    fn to_animated_value(self, context: &Context) -> Self::AnimatedValue {
299        self.into_iter()
300            .map(|v| v.to_animated_value(context))
301            .collect()
302    }
303
304    #[inline]
305    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
306        animated.into_iter().map(T::from_animated_value).collect()
307    }
308}
309
310impl<T> ToAnimatedValue for Box<T>
311where
312    T: ToAnimatedValue,
313{
314    type AnimatedValue = Box<<T as ToAnimatedValue>::AnimatedValue>;
315
316    #[inline]
317    fn to_animated_value(self, context: &Context) -> Self::AnimatedValue {
318        Box::new((*self).to_animated_value(context))
319    }
320
321    #[inline]
322    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
323        Box::new(T::from_animated_value(*animated))
324    }
325}
326
327impl<T> ToAnimatedValue for Box<[T]>
328where
329    T: ToAnimatedValue,
330{
331    type AnimatedValue = Box<[<T as ToAnimatedValue>::AnimatedValue]>;
332
333    #[inline]
334    fn to_animated_value(self, context: &Context) -> Self::AnimatedValue {
335        self.into_vec()
336            .into_iter()
337            .map(|v| v.to_animated_value(context))
338            .collect()
339    }
340
341    #[inline]
342    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
343        animated
344            .into_vec()
345            .into_iter()
346            .map(T::from_animated_value)
347            .collect()
348    }
349}
350
351impl<T> ToAnimatedValue for crate::OwnedSlice<T>
352where
353    T: ToAnimatedValue,
354{
355    type AnimatedValue = crate::OwnedSlice<<T as ToAnimatedValue>::AnimatedValue>;
356
357    #[inline]
358    fn to_animated_value(self, context: &Context) -> Self::AnimatedValue {
359        self.into_box().to_animated_value(context).into()
360    }
361
362    #[inline]
363    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
364        Self::from(Box::from_animated_value(animated.into_box()))
365    }
366}
367
368impl<T> ToAnimatedValue for SmallVec<[T; 1]>
369where
370    T: ToAnimatedValue,
371{
372    type AnimatedValue = SmallVec<[T::AnimatedValue; 1]>;
373
374    #[inline]
375    fn to_animated_value(self, context: &Context) -> Self::AnimatedValue {
376        self.into_iter()
377            .map(|v| v.to_animated_value(context))
378            .collect()
379    }
380
381    #[inline]
382    fn from_animated_value(animated: Self::AnimatedValue) -> Self {
383        animated.into_iter().map(T::from_animated_value).collect()
384    }
385}
386
387macro_rules! trivial_to_animated_value {
388    ($ty:ty) => {
389        impl $crate::values::animated::ToAnimatedValue for $ty {
390            type AnimatedValue = Self;
391
392            #[inline]
393            fn to_animated_value(self, _: &Context) -> Self {
394                self
395            }
396
397            #[inline]
398            fn from_animated_value(animated: Self::AnimatedValue) -> Self {
399                animated
400            }
401        }
402    };
403}
404
405trivial_to_animated_value!(crate::Atom);
406trivial_to_animated_value!(Angle);
407trivial_to_animated_value!(ComputedUrl);
408trivial_to_animated_value!(bool);
409trivial_to_animated_value!(f32);
410trivial_to_animated_value!(i32);
411trivial_to_animated_value!(u32);
412trivial_to_animated_value!(usize);
413trivial_to_animated_value!(AbsoluteColor);
414trivial_to_animated_value!(crate::values::generics::color::ColorMixFlags);
415// Note: This implementation is for ToAnimatedValue of ShapeSource.
416//
417// SVGPathData uses Box<[T]>. If we want to derive ToAnimatedValue for all the
418// types, we have to do "impl ToAnimatedValue for Box<[T]>" first.
419// However, the general version of "impl ToAnimatedValue for Box<[T]>" needs to
420// clone |T| and convert it into |T::AnimatedValue|. However, for SVGPathData
421// that is unnecessary--moving |T| is sufficient. So here, we implement this
422// trait manually.
423trivial_to_animated_value!(SVGPathData);
424// FIXME: Bug 1514342, Image is not animatable, but we still need to implement
425// this to avoid adding this derive to generic::Image and all its arms. We can
426// drop this after landing Bug 1514342.
427trivial_to_animated_value!(Image);
428
429impl ToAnimatedZero for Au {
430    #[inline]
431    fn to_animated_zero(&self) -> Result<Self, ()> {
432        Ok(Au(0))
433    }
434}
435
436impl ToAnimatedZero for f32 {
437    #[inline]
438    fn to_animated_zero(&self) -> Result<Self, ()> {
439        Ok(0.)
440    }
441}
442
443impl ToAnimatedZero for f64 {
444    #[inline]
445    fn to_animated_zero(&self) -> Result<Self, ()> {
446        Ok(0.)
447    }
448}
449
450impl ToAnimatedZero for i32 {
451    #[inline]
452    fn to_animated_zero(&self) -> Result<Self, ()> {
453        Ok(0)
454    }
455}
456
457impl<T> ToAnimatedZero for Box<T>
458where
459    T: ToAnimatedZero,
460{
461    #[inline]
462    fn to_animated_zero(&self) -> Result<Self, ()> {
463        Ok(Box::new((**self).to_animated_zero()?))
464    }
465}
466
467impl<T> ToAnimatedZero for Option<T>
468where
469    T: ToAnimatedZero,
470{
471    #[inline]
472    fn to_animated_zero(&self) -> Result<Self, ()> {
473        match *self {
474            Some(ref value) => Ok(Some(value.to_animated_zero()?)),
475            None => Ok(None),
476        }
477    }
478}
479
480impl<T> ToAnimatedZero for Vec<T>
481where
482    T: ToAnimatedZero,
483{
484    #[inline]
485    fn to_animated_zero(&self) -> Result<Self, ()> {
486        self.iter().map(|v| v.to_animated_zero()).collect()
487    }
488}
489
490impl<T> ToAnimatedZero for thin_vec::ThinVec<T>
491where
492    T: ToAnimatedZero,
493{
494    #[inline]
495    fn to_animated_zero(&self) -> Result<Self, ()> {
496        self.iter().map(|v| v.to_animated_zero()).collect()
497    }
498}
499
500impl<T> ToAnimatedZero for Box<[T]>
501where
502    T: ToAnimatedZero,
503{
504    #[inline]
505    fn to_animated_zero(&self) -> Result<Self, ()> {
506        self.iter().map(|v| v.to_animated_zero()).collect()
507    }
508}
509
510impl<T> ToAnimatedZero for crate::OwnedSlice<T>
511where
512    T: ToAnimatedZero,
513{
514    #[inline]
515    fn to_animated_zero(&self) -> Result<Self, ()> {
516        self.iter().map(|v| v.to_animated_zero()).collect()
517    }
518}
519
520impl<T> ToAnimatedZero for crate::ArcSlice<T>
521where
522    T: ToAnimatedZero,
523{
524    #[inline]
525    fn to_animated_zero(&self) -> Result<Self, ()> {
526        let v = self
527            .iter()
528            .map(|v| v.to_animated_zero())
529            .collect::<Result<Vec<_>, _>>()?;
530        Ok(crate::ArcSlice::from_iter(v.into_iter()))
531    }
532}