Rewrite interpolate() in terms of a more general add_weighted() function

Generalizing the procedure like this will allow us to re-use it for addition of
most types.
This commit is contained in:
Brian Birtles 2017-05-15 12:26:21 +09:00
parent 8366b4d4f9
commit 2f07b29296
7 changed files with 261 additions and 200 deletions

View file

@ -113,8 +113,9 @@
% if delegate_animate: % if delegate_animate:
use properties::animated_properties::Animatable; use properties::animated_properties::Animatable;
impl Animatable for T { impl Animatable for T {
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64)
self.0.interpolate(&other.0, progress).map(T) -> Result<Self, ()> {
self.0.add_weighted(&other.0, self_portion, other_portion).map(T)
} }
#[inline] #[inline]
@ -976,16 +977,17 @@
<%def name="impl_animatable_for_option_tuple(value_for_none)"> <%def name="impl_animatable_for_option_tuple(value_for_none)">
impl Animatable for T { impl Animatable for T {
#[inline] #[inline]
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64)
-> Result<Self, ()> {
match (self, other) { match (self, other) {
(&T(Some(ref this)), &T(Some(ref other))) => { (&T(Some(ref this)), &T(Some(ref other))) => {
Ok(T(this.interpolate(other, progress).ok())) Ok(T(this.add_weighted(other, self_portion, other_portion).ok()))
}, },
(&T(Some(ref this)), &T(None)) => { (&T(Some(ref this)), &T(None)) => {
Ok(T(this.interpolate(&${value_for_none}, progress).ok())) Ok(T(this.add_weighted(&${value_for_none}, self_portion, other_portion).ok()))
}, },
(&T(None), &T(Some(ref other))) => { (&T(None), &T(Some(ref other))) => {
Ok(T(${value_for_none}.interpolate(other, progress).ok())) Ok(T(${value_for_none}.add_weighted(other, self_portion, other_portion).ok()))
}, },
(&T(None), &T(None)) => { (&T(None), &T(None)) => {
Ok(T(None)) Ok(T(None))

View file

@ -581,27 +581,28 @@ impl AnimationValue {
} }
impl Animatable for AnimationValue { impl Animatable for AnimationValue {
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64)
-> Result<Self, ()> {
match (self, other) { match (self, other) {
% for prop in data.longhands: % for prop in data.longhands:
% if prop.animatable: % if prop.animatable:
(&AnimationValue::${prop.camel_case}(ref from), (&AnimationValue::${prop.camel_case}(ref from),
&AnimationValue::${prop.camel_case}(ref to)) => { &AnimationValue::${prop.camel_case}(ref to)) => {
// https://w3c.github.io/web-animations/#discrete-animation-type
% if prop.animation_value_type == "discrete": % if prop.animation_value_type == "discrete":
if progress < 0.5 { if self_portion > other_portion {
Ok(AnimationValue::${prop.camel_case}(*from)) Ok(AnimationValue::${prop.camel_case}(*from))
} else { } else {
Ok(AnimationValue::${prop.camel_case}(*to)) Ok(AnimationValue::${prop.camel_case}(*to))
} }
% else: % else:
from.interpolate(to, progress).map(AnimationValue::${prop.camel_case}) from.add_weighted(to, self_portion, other_portion)
.map(AnimationValue::${prop.camel_case})
% endif % endif
} }
% endif % endif
% endfor % endfor
_ => { _ => {
panic!("Expected interpolation of computed values of the same \ panic!("Expected weighted addition of computed values of the same \
property, got: {:?}, {:?}", self, other); property, got: {:?}, {:?}", self, other);
} }
} }
@ -635,10 +636,17 @@ impl Animatable for AnimationValue {
/// A trait used to implement various procedures used during animation. /// A trait used to implement various procedures used during animation.
pub trait Animatable: Sized { pub trait Animatable: Sized {
/// Performs a weighted sum of this value and |other|. This is used for
/// interpolation and addition of animation values.
fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64)
-> Result<Self, ()>;
/// [Interpolates][interpolation] a value with another for a given property. /// [Interpolates][interpolation] a value with another for a given property.
/// ///
/// [interpolation]: https://w3c.github.io/web-animations/#animation-interpolation /// [interpolation]: https://w3c.github.io/web-animations/#animation-interpolation
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()>; fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> {
self.add_weighted(other, 1.0 - progress, progress)
}
/// Compute distance between a value and another for a given property. /// Compute distance between a value and another for a given property.
fn compute_distance(&self, _other: &Self) -> Result<f64, ()> { Err(()) } fn compute_distance(&self, _other: &Self) -> Result<f64, ()> { Err(()) }
@ -658,11 +666,12 @@ impl RepeatableListAnimatable for LengthOrPercentage {}
impl RepeatableListAnimatable for Either<f32, LengthOrPercentage> {} impl RepeatableListAnimatable for Either<f32, LengthOrPercentage> {}
impl<T: RepeatableListAnimatable> Animatable for SmallVec<[T; 1]> { impl<T: RepeatableListAnimatable> Animatable for SmallVec<[T; 1]> {
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64)
-> Result<Self, ()> {
use num_integer::lcm; use num_integer::lcm;
let len = lcm(self.len(), other.len()); let len = lcm(self.len(), other.len());
self.iter().cycle().zip(other.iter().cycle()).take(len).map(|(me, you)| { self.iter().cycle().zip(other.iter().cycle()).take(len).map(|(me, you)| {
me.interpolate(you, progress) me.add_weighted(you, self_portion, other_portion)
}).collect() }).collect()
} }
@ -684,8 +693,8 @@ impl<T: RepeatableListAnimatable> Animatable for SmallVec<[T; 1]> {
/// https://drafts.csswg.org/css-transitions/#animtype-number /// https://drafts.csswg.org/css-transitions/#animtype-number
impl Animatable for Au { impl Animatable for Au {
#[inline] #[inline]
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
Ok(Au((self.0 as f64 + (other.0 as f64 - self.0 as f64) * progress).round() as i32)) Ok(Au((self.0 as f64 * self_portion + other.0 as f64 * other_portion).round() as i32))
} }
#[inline] #[inline]
@ -698,10 +707,10 @@ impl <T> Animatable for Option<T>
where T: Animatable, where T: Animatable,
{ {
#[inline] #[inline]
fn interpolate(&self, other: &Option<T>, progress: f64) -> Result<Option<T>, ()> { fn add_weighted(&self, other: &Option<T>, self_portion: f64, other_portion: f64) -> Result<Option<T>, ()> {
match (self, other) { match (self, other) {
(&Some(ref this), &Some(ref other)) => { (&Some(ref this), &Some(ref other)) => {
Ok(this.interpolate(other, progress).ok()) Ok(this.add_weighted(other, self_portion, other_portion).ok())
} }
_ => Err(()), _ => Err(()),
} }
@ -731,8 +740,8 @@ impl <T> Animatable for Option<T>
/// https://drafts.csswg.org/css-transitions/#animtype-number /// https://drafts.csswg.org/css-transitions/#animtype-number
impl Animatable for f32 { impl Animatable for f32 {
#[inline] #[inline]
fn interpolate(&self, other: &f32, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &f32, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
Ok(((*self as f64) + ((*other as f64) - (*self as f64)) * progress) as f32) Ok((*self as f64 * self_portion + *other as f64 * other_portion) as f32)
} }
#[inline] #[inline]
@ -744,8 +753,8 @@ impl Animatable for f32 {
/// https://drafts.csswg.org/css-transitions/#animtype-number /// https://drafts.csswg.org/css-transitions/#animtype-number
impl Animatable for f64 { impl Animatable for f64 {
#[inline] #[inline]
fn interpolate(&self, other: &f64, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &f64, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
Ok(*self + (*other - *self) * progress) Ok(*self * self_portion + *other * other_portion)
} }
#[inline] #[inline]
@ -757,10 +766,8 @@ impl Animatable for f64 {
/// https://drafts.csswg.org/css-transitions/#animtype-integer /// https://drafts.csswg.org/css-transitions/#animtype-integer
impl Animatable for i32 { impl Animatable for i32 {
#[inline] #[inline]
fn interpolate(&self, other: &i32, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &i32, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
let a = *self as f64; Ok((*self as f64 * self_portion + *other as f64 * other_portion).round() as i32)
let b = *other as f64;
Ok((a + (b - a) * progress).round() as i32)
} }
#[inline] #[inline]
@ -772,20 +779,23 @@ impl Animatable for i32 {
/// https://drafts.csswg.org/css-transitions/#animtype-number /// https://drafts.csswg.org/css-transitions/#animtype-number
impl Animatable for Angle { impl Animatable for Angle {
#[inline] #[inline]
fn interpolate(&self, other: &Angle, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Angle, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
self.radians().interpolate(&other.radians(), progress).map(Angle::from_radians) self.radians()
.add_weighted(&other.radians(), self_portion, other_portion)
.map(Angle::from_radians)
} }
} }
/// https://drafts.csswg.org/css-transitions/#animtype-visibility /// https://drafts.csswg.org/css-transitions/#animtype-visibility
impl Animatable for Visibility { impl Animatable for Visibility {
#[inline] #[inline]
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
match (*self, *other) { match (*self, *other) {
(Visibility::visible, _) | (_, Visibility::visible) => { (Visibility::visible, _) | (_, Visibility::visible) => {
Ok(if progress >= 0.0 && progress <= 1.0 { Ok(if self_portion >= 0.0 && self_portion <= 1.0 &&
other_portion >= 0.0 && other_portion <= 1.0 {
Visibility::visible Visibility::visible
} else if progress < 0.0 { } else if self_portion > other_portion {
*self *self
} else { } else {
*other *other
@ -807,9 +817,9 @@ impl Animatable for Visibility {
impl<T: Animatable + Copy> Animatable for Size2D<T> { impl<T: Animatable + Copy> Animatable for Size2D<T> {
#[inline] #[inline]
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
let width = try!(self.width.interpolate(&other.width, progress)); let width = try!(self.width.add_weighted(&other.width, self_portion, other_portion));
let height = try!(self.height.interpolate(&other.height, progress)); let height = try!(self.height.add_weighted(&other.height, self_portion, other_portion));
Ok(Size2D::new(width, height)) Ok(Size2D::new(width, height))
} }
@ -817,9 +827,9 @@ impl<T: Animatable + Copy> Animatable for Size2D<T> {
impl<T: Animatable + Copy> Animatable for Point2D<T> { impl<T: Animatable + Copy> Animatable for Point2D<T> {
#[inline] #[inline]
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
let x = try!(self.x.interpolate(&other.x, progress)); let x = try!(self.x.add_weighted(&other.x, self_portion, other_portion));
let y = try!(self.y.interpolate(&other.y, progress)); let y = try!(self.y.add_weighted(&other.y, self_portion, other_portion));
Ok(Point2D::new(x, y)) Ok(Point2D::new(x, y))
} }
@ -827,8 +837,8 @@ impl<T: Animatable + Copy> Animatable for Point2D<T> {
impl Animatable for BorderRadiusSize { impl Animatable for BorderRadiusSize {
#[inline] #[inline]
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
self.0.interpolate(&other.0, progress).map(generics::BorderRadiusSize) self.0.add_weighted(&other.0, self_portion, other_portion).map(generics::BorderRadiusSize)
} }
#[inline] #[inline]
@ -846,11 +856,11 @@ impl Animatable for BorderRadiusSize {
/// https://drafts.csswg.org/css-transitions/#animtype-length /// https://drafts.csswg.org/css-transitions/#animtype-length
impl Animatable for VerticalAlign { impl Animatable for VerticalAlign {
#[inline] #[inline]
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
match (*self, *other) { match (*self, *other) {
(VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(ref this)), (VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(ref this)),
VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(ref other))) => { VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(ref other))) => {
this.interpolate(other, progress).map(|value| { this.add_weighted(other, self_portion, other_portion).map(|value| {
VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(value)) VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(value))
}) })
} }
@ -872,8 +882,8 @@ impl Animatable for VerticalAlign {
impl Animatable for BackgroundSizeList { impl Animatable for BackgroundSizeList {
#[inline] #[inline]
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
self.0.interpolate(&other.0, progress).map(BackgroundSizeList) self.0.add_weighted(&other.0, self_portion, other_portion).map(BackgroundSizeList)
} }
#[inline] #[inline]
@ -890,24 +900,28 @@ impl Animatable for BackgroundSizeList {
/// https://drafts.csswg.org/css-transitions/#animtype-color /// https://drafts.csswg.org/css-transitions/#animtype-color
impl Animatable for RGBA { impl Animatable for RGBA {
#[inline] #[inline]
fn interpolate(&self, other: &RGBA, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &RGBA, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
fn clamp(val: f32) -> f32 { fn clamp(val: f32) -> f32 {
val.max(0.).min(1.) val.max(0.).min(1.)
} }
let alpha = clamp(try!(self.alpha_f32().interpolate(&other.alpha_f32(), progress))); let alpha = clamp(try!(self.alpha_f32().add_weighted(&other.alpha_f32(),
self_portion, other_portion)));
if alpha == 0. { if alpha == 0. {
Ok(RGBA::transparent()) Ok(RGBA::transparent())
} else { } else {
// NB: We rely on RGBA::from_floats clamping already. // NB: We rely on RGBA::from_floats clamping already.
let red = try!((self.red_f32() * self.alpha_f32()) let red = try!((self.red_f32() * self.alpha_f32())
.interpolate(&(other.red_f32() * other.alpha_f32()), progress)) .add_weighted(&(other.red_f32() * other.alpha_f32()),
self_portion, other_portion))
* 1. / alpha; * 1. / alpha;
let green = try!((self.green_f32() * self.alpha_f32()) let green = try!((self.green_f32() * self.alpha_f32())
.interpolate(&(other.green_f32() * other.alpha_f32()), progress)) .add_weighted(&(other.green_f32() * other.alpha_f32()),
self_portion, other_portion))
* 1. / alpha; * 1. / alpha;
let blue = try!((self.blue_f32() * self.alpha_f32()) let blue = try!((self.blue_f32() * self.alpha_f32())
.interpolate(&(other.blue_f32() * other.alpha_f32()), progress)) .add_weighted(&(other.blue_f32() * other.alpha_f32()),
self_portion, other_portion))
* 1. / alpha; * 1. / alpha;
Ok(RGBA::from_floats(red, green, blue, alpha)) Ok(RGBA::from_floats(red, green, blue, alpha))
} }
@ -948,10 +962,10 @@ impl Animatable for RGBA {
/// https://drafts.csswg.org/css-transitions/#animtype-color /// https://drafts.csswg.org/css-transitions/#animtype-color
impl Animatable for CSSParserColor { impl Animatable for CSSParserColor {
#[inline] #[inline]
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
match (*self, *other) { match (*self, *other) {
(CSSParserColor::RGBA(ref this), CSSParserColor::RGBA(ref other)) => { (CSSParserColor::RGBA(ref this), CSSParserColor::RGBA(ref other)) => {
this.interpolate(other, progress).map(CSSParserColor::RGBA) this.add_weighted(other, self_portion, other_portion).map(CSSParserColor::RGBA)
} }
_ => Err(()), _ => Err(()),
} }
@ -976,10 +990,11 @@ impl Animatable for CSSParserColor {
/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc /// https://drafts.csswg.org/css-transitions/#animtype-lpcalc
impl Animatable for CalcLengthOrPercentage { impl Animatable for CalcLengthOrPercentage {
#[inline] #[inline]
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
fn interpolate_half<T>(this: Option<T>, fn add_weighted_half<T>(this: Option<T>,
other: Option<T>, other: Option<T>,
progress: f64) self_portion: f64,
other_portion: f64)
-> Result<Option<T>, ()> -> Result<Option<T>, ()>
where T: Default + Animatable, where T: Default + Animatable,
{ {
@ -988,14 +1003,15 @@ impl Animatable for CalcLengthOrPercentage {
(this, other) => { (this, other) => {
let this = this.unwrap_or(T::default()); let this = this.unwrap_or(T::default());
let other = other.unwrap_or(T::default()); let other = other.unwrap_or(T::default());
this.interpolate(&other, progress).map(Some) this.add_weighted(&other, self_portion, other_portion).map(Some)
} }
} }
} }
Ok(CalcLengthOrPercentage { Ok(CalcLengthOrPercentage {
length: try!(self.length.interpolate(&other.length, progress)), length: try!(self.length.add_weighted(&other.length, self_portion, other_portion)),
percentage: try!(interpolate_half(self.percentage, other.percentage, progress)), percentage: try!(add_weighted_half(self.percentage, other.percentage,
self_portion, other_portion)),
}) })
} }
@ -1015,20 +1031,22 @@ impl Animatable for CalcLengthOrPercentage {
/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc /// https://drafts.csswg.org/css-transitions/#animtype-lpcalc
impl Animatable for LengthOrPercentage { impl Animatable for LengthOrPercentage {
#[inline] #[inline]
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
match (*self, *other) { match (*self, *other) {
(LengthOrPercentage::Length(ref this), (LengthOrPercentage::Length(ref this),
LengthOrPercentage::Length(ref other)) => { LengthOrPercentage::Length(ref other)) => {
this.interpolate(other, progress).map(LengthOrPercentage::Length) this.add_weighted(other, self_portion, other_portion)
.map(LengthOrPercentage::Length)
} }
(LengthOrPercentage::Percentage(ref this), (LengthOrPercentage::Percentage(ref this),
LengthOrPercentage::Percentage(ref other)) => { LengthOrPercentage::Percentage(ref other)) => {
this.interpolate(other, progress).map(LengthOrPercentage::Percentage) this.add_weighted(other, self_portion, other_portion)
.map(LengthOrPercentage::Percentage)
} }
(this, other) => { (this, other) => {
let this: CalcLengthOrPercentage = From::from(this); let this: CalcLengthOrPercentage = From::from(this);
let other: CalcLengthOrPercentage = From::from(other); let other: CalcLengthOrPercentage = From::from(other);
this.interpolate(&other, progress) this.add_weighted(&other, self_portion, other_portion)
.map(LengthOrPercentage::Calc) .map(LengthOrPercentage::Calc)
} }
} }
@ -1080,15 +1098,17 @@ impl Animatable for LengthOrPercentage {
/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc /// https://drafts.csswg.org/css-transitions/#animtype-lpcalc
impl Animatable for LengthOrPercentageOrAuto { impl Animatable for LengthOrPercentageOrAuto {
#[inline] #[inline]
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
match (*self, *other) { match (*self, *other) {
(LengthOrPercentageOrAuto::Length(ref this), (LengthOrPercentageOrAuto::Length(ref this),
LengthOrPercentageOrAuto::Length(ref other)) => { LengthOrPercentageOrAuto::Length(ref other)) => {
this.interpolate(other, progress).map(LengthOrPercentageOrAuto::Length) this.add_weighted(other, self_portion, other_portion)
.map(LengthOrPercentageOrAuto::Length)
} }
(LengthOrPercentageOrAuto::Percentage(ref this), (LengthOrPercentageOrAuto::Percentage(ref this),
LengthOrPercentageOrAuto::Percentage(ref other)) => { LengthOrPercentageOrAuto::Percentage(ref other)) => {
this.interpolate(other, progress).map(LengthOrPercentageOrAuto::Percentage) this.add_weighted(other, self_portion, other_portion)
.map(LengthOrPercentageOrAuto::Percentage)
} }
(LengthOrPercentageOrAuto::Auto, LengthOrPercentageOrAuto::Auto) => { (LengthOrPercentageOrAuto::Auto, LengthOrPercentageOrAuto::Auto) => {
Ok(LengthOrPercentageOrAuto::Auto) Ok(LengthOrPercentageOrAuto::Auto)
@ -1096,7 +1116,7 @@ impl Animatable for LengthOrPercentageOrAuto {
(this, other) => { (this, other) => {
let this: Option<CalcLengthOrPercentage> = From::from(this); let this: Option<CalcLengthOrPercentage> = From::from(this);
let other: Option<CalcLengthOrPercentage> = From::from(other); let other: Option<CalcLengthOrPercentage> = From::from(other);
match this.interpolate(&other, progress) { match this.add_weighted(&other, self_portion, other_portion) {
Ok(Some(result)) => Ok(LengthOrPercentageOrAuto::Calc(result)), Ok(Some(result)) => Ok(LengthOrPercentageOrAuto::Calc(result)),
_ => Err(()), _ => Err(()),
} }
@ -1155,15 +1175,17 @@ impl Animatable for LengthOrPercentageOrAuto {
/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc /// https://drafts.csswg.org/css-transitions/#animtype-lpcalc
impl Animatable for LengthOrPercentageOrNone { impl Animatable for LengthOrPercentageOrNone {
#[inline] #[inline]
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
match (*self, *other) { match (*self, *other) {
(LengthOrPercentageOrNone::Length(ref this), (LengthOrPercentageOrNone::Length(ref this),
LengthOrPercentageOrNone::Length(ref other)) => { LengthOrPercentageOrNone::Length(ref other)) => {
this.interpolate(other, progress).map(LengthOrPercentageOrNone::Length) this.add_weighted(other, self_portion, other_portion)
.map(LengthOrPercentageOrNone::Length)
} }
(LengthOrPercentageOrNone::Percentage(ref this), (LengthOrPercentageOrNone::Percentage(ref this),
LengthOrPercentageOrNone::Percentage(ref other)) => { LengthOrPercentageOrNone::Percentage(ref other)) => {
this.interpolate(other, progress).map(LengthOrPercentageOrNone::Percentage) this.add_weighted(other, self_portion, other_portion)
.map(LengthOrPercentageOrNone::Percentage)
} }
(LengthOrPercentageOrNone::None, LengthOrPercentageOrNone::None) => { (LengthOrPercentageOrNone::None, LengthOrPercentageOrNone::None) => {
Ok(LengthOrPercentageOrNone::None) Ok(LengthOrPercentageOrNone::None)
@ -1191,11 +1213,12 @@ impl Animatable for LengthOrPercentageOrNone {
/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc /// https://drafts.csswg.org/css-transitions/#animtype-lpcalc
impl Animatable for MinLength { impl Animatable for MinLength {
#[inline] #[inline]
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
match (*self, *other) { match (*self, *other) {
(MinLength::LengthOrPercentage(ref this), (MinLength::LengthOrPercentage(ref this),
MinLength::LengthOrPercentage(ref other)) => { MinLength::LengthOrPercentage(ref other)) => {
this.interpolate(other, progress).map(MinLength::LengthOrPercentage) this.add_weighted(other, self_portion, other_portion)
.map(MinLength::LengthOrPercentage)
} }
_ => Err(()), _ => Err(()),
} }
@ -1216,11 +1239,12 @@ impl Animatable for MinLength {
/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc /// https://drafts.csswg.org/css-transitions/#animtype-lpcalc
impl Animatable for MaxLength { impl Animatable for MaxLength {
#[inline] #[inline]
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
match (*self, *other) { match (*self, *other) {
(MaxLength::LengthOrPercentage(ref this), (MaxLength::LengthOrPercentage(ref this),
MaxLength::LengthOrPercentage(ref other)) => { MaxLength::LengthOrPercentage(ref other)) => {
this.interpolate(other, progress).map(MaxLength::LengthOrPercentage) this.add_weighted(other, self_portion, other_portion)
.map(MaxLength::LengthOrPercentage)
} }
_ => Err(()), _ => Err(()),
} }
@ -1242,15 +1266,15 @@ impl Animatable for MaxLength {
/// https://drafts.csswg.org/css-transitions/#animtype-length /// https://drafts.csswg.org/css-transitions/#animtype-length
impl Animatable for LineHeight { impl Animatable for LineHeight {
#[inline] #[inline]
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
match (*self, *other) { match (*self, *other) {
(LineHeight::Length(ref this), (LineHeight::Length(ref this),
LineHeight::Length(ref other)) => { LineHeight::Length(ref other)) => {
this.interpolate(other, progress).map(LineHeight::Length) this.add_weighted(other, self_portion, other_portion).map(LineHeight::Length)
} }
(LineHeight::Number(ref this), (LineHeight::Number(ref this),
LineHeight::Number(ref other)) => { LineHeight::Number(ref other)) => {
this.interpolate(other, progress).map(LineHeight::Number) this.add_weighted(other, self_portion, other_portion).map(LineHeight::Number)
} }
(LineHeight::Normal, LineHeight::Normal) => { (LineHeight::Normal, LineHeight::Normal) => {
Ok(LineHeight::Normal) Ok(LineHeight::Normal)
@ -1278,10 +1302,10 @@ impl Animatable for LineHeight {
/// http://dev.w3.org/csswg/css-transitions/#animtype-font-weight /// http://dev.w3.org/csswg/css-transitions/#animtype-font-weight
impl Animatable for FontWeight { impl Animatable for FontWeight {
#[inline] #[inline]
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
let a = (*self as u32) as f64; let a = (*self as u32) as f64;
let b = (*other as u32) as f64; let b = (*other as u32) as f64;
let weight = a + (b - a) * progress; let weight = a * self_portion + b * other_portion;
Ok(if weight < 150. { Ok(if weight < 150. {
FontWeight::Weight100 FontWeight::Weight100
} else if weight < 250. { } else if weight < 250. {
@ -1314,10 +1338,12 @@ impl Animatable for FontWeight {
/// https://drafts.csswg.org/css-transitions/#animtype-simple-list /// https://drafts.csswg.org/css-transitions/#animtype-simple-list
impl<H: Animatable, V: Animatable> Animatable for generic_position::Position<H, V> { impl<H: Animatable, V: Animatable> Animatable for generic_position::Position<H, V> {
#[inline] #[inline]
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
Ok(generic_position::Position { Ok(generic_position::Position {
horizontal: try!(self.horizontal.interpolate(&other.horizontal, progress)), horizontal: try!(self.horizontal.add_weighted(&other.horizontal,
vertical: try!(self.vertical.interpolate(&other.vertical, progress)), self_portion, other_portion)),
vertical: try!(self.vertical.add_weighted(&other.vertical,
self_portion, other_portion)),
}) })
} }
@ -1339,12 +1365,13 @@ impl<H, V> RepeatableListAnimatable for generic_position::Position<H, V>
/// https://drafts.csswg.org/css-transitions/#animtype-rect /// https://drafts.csswg.org/css-transitions/#animtype-rect
impl Animatable for ClipRect { impl Animatable for ClipRect {
#[inline] #[inline]
fn interpolate(&self, other: &Self, time: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64)
-> Result<Self, ()> {
Ok(ClipRect { Ok(ClipRect {
top: try!(self.top.interpolate(&other.top, time)), top: try!(self.top.add_weighted(&other.top, self_portion, other_portion)),
right: try!(self.right.interpolate(&other.right, time)), right: try!(self.right.add_weighted(&other.right, self_portion, other_portion)),
bottom: try!(self.bottom.interpolate(&other.bottom, time)), bottom: try!(self.bottom.add_weighted(&other.bottom, self_portion, other_portion)),
left: try!(self.left.interpolate(&other.left, time)), left: try!(self.left.add_weighted(&other.left, self_portion, other_portion)),
}) })
} }
@ -1366,7 +1393,7 @@ impl Animatable for ClipRect {
<%def name="impl_animatable_for_shadow(item, transparent_color)"> <%def name="impl_animatable_for_shadow(item, transparent_color)">
impl Animatable for ${item} { impl Animatable for ${item} {
#[inline] #[inline]
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
% if "Box" in item: % if "Box" in item:
// It can't be interpolated if inset does not match. // It can't be interpolated if inset does not match.
if self.inset != other.inset { if self.inset != other.inset {
@ -1374,12 +1401,14 @@ impl Animatable for ClipRect {
} }
% endif % endif
let x = try!(self.offset_x.interpolate(&other.offset_x, progress)); let x = try!(self.offset_x.add_weighted(&other.offset_x, self_portion, other_portion));
let y = try!(self.offset_y.interpolate(&other.offset_y, progress)); let y = try!(self.offset_y.add_weighted(&other.offset_y, self_portion, other_portion));
let color = try!(self.color.interpolate(&other.color, progress)); let color = try!(self.color.add_weighted(&other.color, self_portion, other_portion));
let blur = try!(self.blur_radius.interpolate(&other.blur_radius, progress)); let blur = try!(self.blur_radius.add_weighted(&other.blur_radius,
self_portion, other_portion));
% if "Box" in item: % if "Box" in item:
let spread = try!(self.spread_radius.interpolate(&other.spread_radius, progress)); let spread = try!(self.spread_radius.add_weighted(&other.spread_radius,
self_portion, other_portion));
% endif % endif
Ok(${item} { Ok(${item} {
@ -1421,7 +1450,7 @@ impl Animatable for ClipRect {
/// https://drafts.csswg.org/css-transitions/#animtype-shadow-list /// https://drafts.csswg.org/css-transitions/#animtype-shadow-list
impl Animatable for ${item}List { impl Animatable for ${item}List {
#[inline] #[inline]
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
// The inset value must change // The inset value must change
% if "Box" in item: % if "Box" in item:
let mut zero = ${item} { let mut zero = ${item} {
@ -1449,18 +1478,18 @@ impl Animatable for ClipRect {
for i in 0..max_len { for i in 0..max_len {
let shadow = match (self.0.get(i), other.0.get(i)) { let shadow = match (self.0.get(i), other.0.get(i)) {
(Some(shadow), Some(other)) (Some(shadow), Some(other))
=> try!(shadow.interpolate(other, progress)), => try!(shadow.add_weighted(other, self_portion, other_portion)),
(Some(shadow), None) => { (Some(shadow), None) => {
% if "Box" in item: % if "Box" in item:
zero.inset = shadow.inset; zero.inset = shadow.inset;
% endif % endif
shadow.interpolate(&zero, progress).unwrap() shadow.add_weighted(&zero, self_portion, other_portion).unwrap()
} }
(None, Some(shadow)) => { (None, Some(shadow)) => {
% if "Box" in item: % if "Box" in item:
zero.inset = shadow.inset; zero.inset = shadow.inset;
% endif % endif
zero.interpolate(&shadow, progress).unwrap() zero.add_weighted(&shadow, self_portion, other_portion).unwrap()
} }
(None, None) => unreachable!(), (None, None) => unreachable!(),
}; };
@ -1541,11 +1570,12 @@ fn build_identity_transform_list(list: &[TransformOperation]) -> Vec<TransformOp
result result
} }
/// Interpolate two transform lists. /// Add two transform lists.
/// http://dev.w3.org/csswg/css-transforms/#interpolation-of-transforms /// http://dev.w3.org/csswg/css-transforms/#interpolation-of-transforms
fn interpolate_transform_list(from_list: &[TransformOperation], fn add_weighted_transform_lists(from_list: &[TransformOperation],
to_list: &[TransformOperation], to_list: &[TransformOperation],
progress: f64) -> TransformList { self_portion: f64,
other_portion: f64) -> TransformList {
let mut result = vec![]; let mut result = vec![];
if can_interpolate_list(from_list, to_list) { if can_interpolate_list(from_list, to_list) {
@ -1553,33 +1583,33 @@ fn interpolate_transform_list(from_list: &[TransformOperation],
match (from, to) { match (from, to) {
(&TransformOperation::Matrix(from), (&TransformOperation::Matrix(from),
&TransformOperation::Matrix(_to)) => { &TransformOperation::Matrix(_to)) => {
let interpolated = from.interpolate(&_to, progress).unwrap(); let sum = from.add_weighted(&_to, self_portion, other_portion).unwrap();
result.push(TransformOperation::Matrix(interpolated)); result.push(TransformOperation::Matrix(sum));
} }
(&TransformOperation::MatrixWithPercents(_), (&TransformOperation::MatrixWithPercents(_),
&TransformOperation::MatrixWithPercents(_)) => { &TransformOperation::MatrixWithPercents(_)) => {
// We don't interpolate `-moz-transform` matrices yet. // We don't add_weighted `-moz-transform` matrices yet.
// They contain percentage values. // They contain percentage values.
{} {}
} }
(&TransformOperation::Skew(fx, fy), (&TransformOperation::Skew(fx, fy),
&TransformOperation::Skew(tx, ty)) => { &TransformOperation::Skew(tx, ty)) => {
let ix = fx.interpolate(&tx, progress).unwrap(); let ix = fx.add_weighted(&tx, self_portion, other_portion).unwrap();
let iy = fy.interpolate(&ty, progress).unwrap(); let iy = fy.add_weighted(&ty, self_portion, other_portion).unwrap();
result.push(TransformOperation::Skew(ix, iy)); result.push(TransformOperation::Skew(ix, iy));
} }
(&TransformOperation::Translate(fx, fy, fz), (&TransformOperation::Translate(fx, fy, fz),
&TransformOperation::Translate(tx, ty, tz)) => { &TransformOperation::Translate(tx, ty, tz)) => {
let ix = fx.interpolate(&tx, progress).unwrap(); let ix = fx.add_weighted(&tx, self_portion, other_portion).unwrap();
let iy = fy.interpolate(&ty, progress).unwrap(); let iy = fy.add_weighted(&ty, self_portion, other_portion).unwrap();
let iz = fz.interpolate(&tz, progress).unwrap(); let iz = fz.add_weighted(&tz, self_portion, other_portion).unwrap();
result.push(TransformOperation::Translate(ix, iy, iz)); result.push(TransformOperation::Translate(ix, iy, iz));
} }
(&TransformOperation::Scale(fx, fy, fz), (&TransformOperation::Scale(fx, fy, fz),
&TransformOperation::Scale(tx, ty, tz)) => { &TransformOperation::Scale(tx, ty, tz)) => {
let ix = fx.interpolate(&tx, progress).unwrap(); let ix = fx.add_weighted(&tx, self_portion, other_portion).unwrap();
let iy = fy.interpolate(&ty, progress).unwrap(); let iy = fy.add_weighted(&ty, self_portion, other_portion).unwrap();
let iz = fz.interpolate(&tz, progress).unwrap(); let iz = fz.add_weighted(&tz, self_portion, other_portion).unwrap();
result.push(TransformOperation::Scale(ix, iy, iz)); result.push(TransformOperation::Scale(ix, iy, iz));
} }
(&TransformOperation::Rotate(fx, fy, fz, fa), (&TransformOperation::Rotate(fx, fy, fz, fa),
@ -1589,14 +1619,15 @@ fn interpolate_transform_list(from_list: &[TransformOperation],
let (fx, fy, fz) = (fx / norm_f, fy / norm_f, fz / norm_f); let (fx, fy, fz) = (fx / norm_f, fy / norm_f, fz / norm_f);
let (tx, ty, tz) = (tx / norm_t, ty / norm_t, tz / norm_t); let (tx, ty, tz) = (tx / norm_t, ty / norm_t, tz / norm_t);
if fx == tx && fy == ty && fz == tz { if fx == tx && fy == ty && fz == tz {
let ia = fa.interpolate(&ta, progress).unwrap(); let ia = fa.add_weighted(&ta, self_portion, other_portion).unwrap();
result.push(TransformOperation::Rotate(fx, fy, fz, ia)); result.push(TransformOperation::Rotate(fx, fy, fz, ia));
} else { } else {
let matrix_f = rotate_to_matrix(fx, fy, fz, fa); let matrix_f = rotate_to_matrix(fx, fy, fz, fa);
let matrix_t = rotate_to_matrix(tx, ty, tz, ta); let matrix_t = rotate_to_matrix(tx, ty, tz, ta);
let interpolated = matrix_f.interpolate(&matrix_t, progress).unwrap(); let sum = matrix_f.add_weighted(&matrix_t, self_portion, other_portion)
.unwrap();
result.push(TransformOperation::Matrix(interpolated)); result.push(TransformOperation::Matrix(sum));
} }
} }
(&TransformOperation::Perspective(fd), (&TransformOperation::Perspective(fd),
@ -1605,8 +1636,9 @@ fn interpolate_transform_list(from_list: &[TransformOperation],
let mut td_matrix = ComputedMatrix::identity(); let mut td_matrix = ComputedMatrix::identity();
fd_matrix.m43 = -1. / fd.to_f32_px(); fd_matrix.m43 = -1. / fd.to_f32_px();
td_matrix.m43 = -1. / _td.to_f32_px(); td_matrix.m43 = -1. / _td.to_f32_px();
let interpolated = fd_matrix.interpolate(&td_matrix, progress).unwrap(); let sum = fd_matrix.add_weighted(&td_matrix, self_portion, other_portion)
result.push(TransformOperation::Matrix(interpolated)); .unwrap();
result.push(TransformOperation::Matrix(sum));
} }
_ => { _ => {
// This should be unreachable due to the can_interpolate_list() call. // This should be unreachable due to the can_interpolate_list() call.
@ -1685,37 +1717,37 @@ pub struct MatrixDecomposed2D {
} }
impl Animatable for InnerMatrix2D { impl Animatable for InnerMatrix2D {
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
Ok(InnerMatrix2D { Ok(InnerMatrix2D {
m11: try!(self.m11.interpolate(&other.m11, progress)), m11: try!(self.m11.add_weighted(&other.m11, self_portion, other_portion)),
m12: try!(self.m12.interpolate(&other.m12, progress)), m12: try!(self.m12.add_weighted(&other.m12, self_portion, other_portion)),
m21: try!(self.m21.interpolate(&other.m21, progress)), m21: try!(self.m21.add_weighted(&other.m21, self_portion, other_portion)),
m22: try!(self.m22.interpolate(&other.m22, progress)), m22: try!(self.m22.add_weighted(&other.m22, self_portion, other_portion)),
}) })
} }
} }
impl Animatable for Translate2D { impl Animatable for Translate2D {
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
Ok(Translate2D( Ok(Translate2D(
try!(self.0.interpolate(&other.0, progress)), try!(self.0.add_weighted(&other.0, self_portion, other_portion)),
try!(self.1.interpolate(&other.1, progress)) try!(self.1.add_weighted(&other.1, self_portion, other_portion))
)) ))
} }
} }
impl Animatable for Scale2D { impl Animatable for Scale2D {
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
Ok(Scale2D( Ok(Scale2D(
try!(self.0.interpolate(&other.0, progress)), try!(self.0.add_weighted(&other.0, self_portion, other_portion)),
try!(self.1.interpolate(&other.1, progress)) try!(self.1.add_weighted(&other.1, self_portion, other_portion))
)) ))
} }
} }
impl Animatable for MatrixDecomposed2D { impl Animatable for MatrixDecomposed2D {
/// https://drafts.csswg.org/css-transforms/#interpolation-of-decomposed-2d-matrix-values /// https://drafts.csswg.org/css-transforms/#interpolation-of-decomposed-2d-matrix-values
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
// If x-axis of one is flipped, and y-axis of the other, // If x-axis of one is flipped, and y-axis of the other,
// convert to an unflipped rotation. // convert to an unflipped rotation.
let mut scale = self.scale; let mut scale = self.scale;
@ -1745,10 +1777,11 @@ impl Animatable for MatrixDecomposed2D {
} }
// Interpolate all values. // Interpolate all values.
let translate = try!(self.translate.interpolate(&other.translate, progress)); let translate = try!(self.translate.add_weighted(&other.translate,
let scale = try!(scale.interpolate(&other.scale, progress)); self_portion, other_portion));
let angle = try!(angle.interpolate(&other_angle, progress)); let scale = try!(scale.add_weighted(&other.scale, self_portion, other_portion));
let matrix = try!(self.matrix.interpolate(&other.matrix, progress)); let angle = try!(angle.add_weighted(&other_angle, self_portion, other_portion));
let matrix = try!(self.matrix.add_weighted(&other.matrix, self_portion, other_portion));
Ok(MatrixDecomposed2D { Ok(MatrixDecomposed2D {
translate: translate, translate: translate,
@ -1760,25 +1793,26 @@ impl Animatable for MatrixDecomposed2D {
} }
impl Animatable for ComputedMatrix { impl Animatable for ComputedMatrix {
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
if self.is_3d() || other.is_3d() { if self.is_3d() || other.is_3d() {
let decomposed_from = decompose_3d_matrix(*self); let decomposed_from = decompose_3d_matrix(*self);
let decomposed_to = decompose_3d_matrix(*other); let decomposed_to = decompose_3d_matrix(*other);
match (decomposed_from, decomposed_to) { match (decomposed_from, decomposed_to) {
(Ok(from), Ok(to)) => { (Ok(from), Ok(to)) => {
let interpolated = try!(from.interpolate(&to, progress)); let sum = try!(from.add_weighted(&to, self_portion, other_portion));
Ok(ComputedMatrix::from(interpolated)) Ok(ComputedMatrix::from(sum))
}, },
_ => { _ => {
let interpolated = if progress < 0.5 {*self} else {*other}; let result = if self_portion > other_portion {*self} else {*other};
Ok(interpolated) Ok(result)
} }
} }
} else { } else {
let decomposed_from = MatrixDecomposed2D::from(*self); let decomposed_from = MatrixDecomposed2D::from(*self);
let decomposed_to = MatrixDecomposed2D::from(*other); let decomposed_to = MatrixDecomposed2D::from(*other);
let interpolated = try!(decomposed_from.interpolate(&decomposed_to, progress)); let sum = try!(decomposed_from.add_weighted(&decomposed_to,
Ok(ComputedMatrix::from(interpolated)) self_portion, other_portion));
Ok(ComputedMatrix::from(sum))
} }
} }
} }
@ -2094,58 +2128,64 @@ fn cross(row1: [f32; 3], row2: [f32; 3]) -> [f32; 3] {
} }
impl Animatable for Translate3D { impl Animatable for Translate3D {
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
Ok(Translate3D( Ok(Translate3D(
try!(self.0.interpolate(&other.0, progress)), try!(self.0.add_weighted(&other.0, self_portion, other_portion)),
try!(self.1.interpolate(&other.1, progress)), try!(self.1.add_weighted(&other.1, self_portion, other_portion)),
try!(self.2.interpolate(&other.2, progress)) try!(self.2.add_weighted(&other.2, self_portion, other_portion))
)) ))
} }
} }
impl Animatable for Scale3D { impl Animatable for Scale3D {
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
Ok(Scale3D( Ok(Scale3D(
try!(self.0.interpolate(&other.0, progress)), try!(self.0.add_weighted(&other.0, self_portion, other_portion)),
try!(self.1.interpolate(&other.1, progress)), try!(self.1.add_weighted(&other.1, self_portion, other_portion)),
try!(self.2.interpolate(&other.2, progress)) try!(self.2.add_weighted(&other.2, self_portion, other_portion))
)) ))
} }
} }
impl Animatable for Skew { impl Animatable for Skew {
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
Ok(Skew( Ok(Skew(
try!(self.0.interpolate(&other.0, progress)), try!(self.0.add_weighted(&other.0, self_portion, other_portion)),
try!(self.1.interpolate(&other.1, progress)), try!(self.1.add_weighted(&other.1, self_portion, other_portion)),
try!(self.2.interpolate(&other.2, progress)) try!(self.2.add_weighted(&other.2, self_portion, other_portion))
)) ))
} }
} }
impl Animatable for Perspective { impl Animatable for Perspective {
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
Ok(Perspective( Ok(Perspective(
try!(self.0.interpolate(&other.0, progress)), try!(self.0.add_weighted(&other.0, self_portion, other_portion)),
try!(self.1.interpolate(&other.1, progress)), try!(self.1.add_weighted(&other.1, self_portion, other_portion)),
try!(self.2.interpolate(&other.2, progress)), try!(self.2.add_weighted(&other.2, self_portion, other_portion)),
try!(self.3.interpolate(&other.3, progress)) try!(self.3.add_weighted(&other.3, self_portion, other_portion))
)) ))
} }
} }
impl Animatable for MatrixDecomposed3D { impl Animatable for MatrixDecomposed3D {
/// https://drafts.csswg.org/css-transforms/#interpolation-of-decomposed-3d-matrix-values /// https://drafts.csswg.org/css-transforms/#interpolation-of-decomposed-3d-matrix-values
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64)
let mut interpolated = *self; -> Result<Self, ()> {
assert!(self_portion + other_portion == 1.0f64,
"add_weighted should only be used for interpolating transforms");
// Interpolate translate, scale, skew and perspective components. let mut sum = *self;
interpolated.translate = try!(self.translate.interpolate(&other.translate, progress));
interpolated.scale = try!(self.scale.interpolate(&other.scale, progress));
interpolated.skew = try!(self.skew.interpolate(&other.skew, progress));
interpolated.perspective = try!(self.perspective.interpolate(&other.perspective, progress));
// Interpolate quaternions using spherical linear interpolation (Slerp). // Add translate, scale, skew and perspective components.
sum.translate = try!(self.translate.add_weighted(&other.translate,
self_portion, other_portion));
sum.scale = try!(self.scale.add_weighted(&other.scale, self_portion, other_portion));
sum.skew = try!(self.skew.add_weighted(&other.skew, self_portion, other_portion));
sum.perspective = try!(self.perspective.add_weighted(&other.perspective,
self_portion, other_portion));
// Add quaternions using spherical linear interpolation (Slerp).
let mut product = self.quaternion.0 * other.quaternion.0 + let mut product = self.quaternion.0 * other.quaternion.0 +
self.quaternion.1 * other.quaternion.1 + self.quaternion.1 * other.quaternion.1 +
self.quaternion.2 * other.quaternion.2 + self.quaternion.2 * other.quaternion.2 +
@ -2156,21 +2196,21 @@ impl Animatable for MatrixDecomposed3D {
product = product.max(-1.0); product = product.max(-1.0);
if product == 1.0 { if product == 1.0 {
return Ok(interpolated); return Ok(sum);
} }
let theta = product.acos(); let theta = product.acos();
let w = (progress as f32 * theta).sin() * 1.0 / (1.0 - product * product).sqrt(); let w = (other_portion as f32 * theta).sin() * 1.0 / (1.0 - product * product).sqrt();
let mut a = *self; let mut a = *self;
let mut b = *other; let mut b = *other;
% for i in range(4): % for i in range(4):
a.quaternion.${i} *= (progress as f32 * theta).cos() - product * w; a.quaternion.${i} *= (other_portion as f32 * theta).cos() - product * w;
b.quaternion.${i} *= w; b.quaternion.${i} *= w;
interpolated.quaternion.${i} = a.quaternion.${i} + b.quaternion.${i}; sum.quaternion.${i} = a.quaternion.${i} + b.quaternion.${i};
% endfor % endfor
Ok(interpolated) Ok(sum)
} }
} }
@ -2374,22 +2414,22 @@ impl ComputedMatrix {
/// https://drafts.csswg.org/css-transforms/#interpolation-of-transforms /// https://drafts.csswg.org/css-transforms/#interpolation-of-transforms
impl Animatable for TransformList { impl Animatable for TransformList {
#[inline] #[inline]
fn interpolate(&self, other: &TransformList, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &TransformList, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
// http://dev.w3.org/csswg/css-transforms/#interpolation-of-transforms // http://dev.w3.org/csswg/css-transforms/#interpolation-of-transforms
let result = match (&self.0, &other.0) { let result = match (&self.0, &other.0) {
(&Some(ref from_list), &Some(ref to_list)) => { (&Some(ref from_list), &Some(ref to_list)) => {
// Two lists of transforms // Two lists of transforms
interpolate_transform_list(from_list, &to_list, progress) add_weighted_transform_lists(from_list, &to_list, self_portion, other_portion)
} }
(&Some(ref from_list), &None) => { (&Some(ref from_list), &None) => {
// http://dev.w3.org/csswg/css-transforms/#none-transform-animation // http://dev.w3.org/csswg/css-transforms/#none-transform-animation
let to_list = build_identity_transform_list(from_list); let to_list = build_identity_transform_list(from_list);
interpolate_transform_list(from_list, &to_list, progress) add_weighted_transform_lists(from_list, &to_list, self_portion, other_portion)
} }
(&None, &Some(ref to_list)) => { (&None, &Some(ref to_list)) => {
// http://dev.w3.org/csswg/css-transforms/#none-transform-animation // http://dev.w3.org/csswg/css-transforms/#none-transform-animation
let from_list = build_identity_transform_list(to_list); let from_list = build_identity_transform_list(to_list);
interpolate_transform_list(&from_list, to_list, progress) add_weighted_transform_lists(&from_list, to_list, self_portion, other_portion)
} }
_ => { _ => {
// http://dev.w3.org/csswg/css-transforms/#none-none-animation // http://dev.w3.org/csswg/css-transforms/#none-none-animation
@ -2405,17 +2445,17 @@ impl<T, U> Animatable for Either<T, U>
where T: Animatable + Copy, U: Animatable + Copy, where T: Animatable + Copy, U: Animatable + Copy,
{ {
#[inline] #[inline]
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
match (*self, *other) { match (*self, *other) {
(Either::First(ref this), Either::First(ref other)) => { (Either::First(ref this), Either::First(ref other)) => {
this.interpolate(&other, progress).map(Either::First) this.add_weighted(&other, self_portion, other_portion).map(Either::First)
}, },
(Either::Second(ref this), Either::Second(ref other)) => { (Either::Second(ref this), Either::Second(ref other)) => {
this.interpolate(&other, progress).map(Either::Second) this.add_weighted(&other, self_portion, other_portion).map(Either::Second)
}, },
_ => { _ => {
let interpolated = if progress < 0.5 { *self } else { *other }; let result = if self_portion > other_portion {*self} else {*other};
Ok(interpolated) Ok(result)
} }
} }
} }
@ -2497,21 +2537,26 @@ impl IntermediateRGBA {
/// Unlike Animatable for RGBA we don't clamp any component values. /// Unlike Animatable for RGBA we don't clamp any component values.
impl Animatable for IntermediateRGBA { impl Animatable for IntermediateRGBA {
#[inline] #[inline]
fn interpolate(&self, other: &IntermediateRGBA, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &IntermediateRGBA, self_portion: f64, other_portion: f64)
let alpha = try!(self.alpha.interpolate(&other.alpha, progress)); -> Result<Self, ()> {
if alpha == 0. { let mut alpha = try!(self.alpha.add_weighted(&other.alpha, self_portion, other_portion));
if alpha <= 0. {
// Ideally we should return color value that only alpha component is // Ideally we should return color value that only alpha component is
// 0, but this is what current gecko does. // 0, but this is what current gecko does.
Ok(IntermediateRGBA::transparent()) Ok(IntermediateRGBA::transparent())
} else { } else {
alpha = alpha.min(1.);
let red = try!((self.red * self.alpha) let red = try!((self.red * self.alpha)
.interpolate(&(other.red * other.alpha), progress)) .add_weighted(&(other.red * other.alpha),
self_portion, other_portion))
* 1. / alpha; * 1. / alpha;
let green = try!((self.green * self.alpha) let green = try!((self.green * self.alpha)
.interpolate(&(other.green * other.alpha), progress)) .add_weighted(&(other.green * other.alpha),
self_portion, other_portion))
* 1. / alpha; * 1. / alpha;
let blue = try!((self.blue * self.alpha) let blue = try!((self.blue * self.alpha)
.interpolate(&(other.blue * other.alpha), progress)) .add_weighted(&(other.blue * other.alpha),
self_portion, other_portion))
* 1. / alpha; * 1. / alpha;
Ok(IntermediateRGBA::new(red, green, blue, alpha)) Ok(IntermediateRGBA::new(red, green, blue, alpha))
} }
@ -2588,10 +2633,12 @@ pub enum IntermediateColor {
impl Animatable for IntermediateColor { impl Animatable for IntermediateColor {
#[inline] #[inline]
fn interpolate(&self, other: &Self, progress: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64) -> Result<Self, ()> {
match (*self, *other) { match (*self, *other) {
(IntermediateColor::IntermediateRGBA(ref this), IntermediateColor::IntermediateRGBA(ref other)) => { (IntermediateColor::IntermediateRGBA(ref this),
this.interpolate(other, progress).map(IntermediateColor::IntermediateRGBA) IntermediateColor::IntermediateRGBA(ref other)) => {
this.add_weighted(other, self_portion, other_portion)
.map(IntermediateColor::IntermediateRGBA)
} }
// FIXME: Bug 1345709: Implement currentColor animations. // FIXME: Bug 1345709: Implement currentColor animations.
_ => Err(()), _ => Err(()),

View file

@ -190,13 +190,16 @@ ${helpers.single_keyword("background-origin",
impl RepeatableListAnimatable for T {} impl RepeatableListAnimatable for T {}
impl Animatable for T { impl Animatable for T {
fn interpolate(&self, other: &Self, time: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64)
-> Result<Self, ()> {
use properties::longhands::background_size::single_value::computed_value::ExplicitSize; use properties::longhands::background_size::single_value::computed_value::ExplicitSize;
match (self, other) { match (self, other) {
(&T::Explicit(ref me), &T::Explicit(ref other)) => { (&T::Explicit(ref me), &T::Explicit(ref other)) => {
Ok(T::Explicit(ExplicitSize { Ok(T::Explicit(ExplicitSize {
width: try!(me.width.interpolate(&other.width, time)), width: try!(me.width.add_weighted(&other.width,
height: try!(me.height.interpolate(&other.height, time)), self_portion, other_portion)),
height: try!(me.height.add_weighted(&other.height,
self_portion, other_portion)),
})) }))
} }
_ => Err(()), _ => Err(()),

View file

@ -2198,11 +2198,14 @@ ${helpers.single_keyword("transform-style",
impl Animatable for T { impl Animatable for T {
#[inline] #[inline]
fn interpolate(&self, other: &Self, time: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64)
-> Result<Self, ()> {
Ok(T { Ok(T {
horizontal: try!(self.horizontal.interpolate(&other.horizontal, time)), horizontal: try!(self.horizontal.add_weighted(&other.horizontal,
vertical: try!(self.vertical.interpolate(&other.vertical, time)), self_portion, other_portion)),
depth: try!(self.depth.interpolate(&other.depth, time)), vertical: try!(self.vertical.add_weighted(&other.vertical,
self_portion, other_portion)),
depth: try!(self.depth.add_weighted(&other.depth, self_portion, other_portion)),
}) })
} }

View file

@ -1073,10 +1073,12 @@ ${helpers.single_keyword_system("font-variant-caps",
} }
impl Animatable for T { impl Animatable for T {
fn interpolate(&self, other: &Self, time: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64)
-> Result<Self, ()> {
match (*self, *other) { match (*self, *other) {
(T::Number(ref number), T::Number(ref other)) => (T::Number(ref number), T::Number(ref other)) =>
Ok(T::Number(try!(number.interpolate(other, time)))), Ok(T::Number(try!(number.add_weighted(other,
self_portion, other_portion)))),
_ => Err(()), _ => Err(()),
} }
} }

View file

@ -42,10 +42,13 @@ ${helpers.single_keyword("caption-side", "top bottom",
/// https://drafts.csswg.org/css-transitions/#animtype-simple-list /// https://drafts.csswg.org/css-transitions/#animtype-simple-list
impl Animatable for T { impl Animatable for T {
#[inline] #[inline]
fn interpolate(&self, other: &Self, time: f64) -> Result<Self, ()> { fn add_weighted(&self, other: &Self, self_portion: f64, other_portion: f64)
-> Result<Self, ()> {
Ok(T { Ok(T {
horizontal: try!(self.horizontal.interpolate(&other.horizontal, time)), horizontal: try!(self.horizontal.add_weighted(&other.horizontal,
vertical: try!(self.vertical.interpolate(&other.vertical, time)), self_portion, other_portion)),
vertical: try!(self.vertical.add_weighted(&other.vertical,
self_portion, other_portion)),
}) })
} }

View file

@ -129,7 +129,8 @@ macro_rules! define_keyword_type {
impl Animatable for $name { impl Animatable for $name {
#[inline] #[inline]
fn interpolate(&self, _other: &Self, _progress: f64) -> Result<Self, ()> { fn add_weighted(&self, _other: &Self, _self_progress: f64, _other_progress: f64)
-> Result<Self, ()> {
Ok($name) Ok($name)
} }
} }