diff options
-rw-r--r-- | components/style/animation.rs | 873 | ||||
-rw-r--r-- | components/style/keyframes.rs | 65 | ||||
-rw-r--r-- | components/style/matching.rs | 5 | ||||
-rw-r--r-- | components/style/properties/data.py | 53 | ||||
-rw-r--r-- | components/style/properties/helpers/animated_properties.mako.rs | 728 | ||||
-rw-r--r-- | components/style/properties/longhand/box.mako.rs | 259 | ||||
-rw-r--r-- | components/style/properties/properties.mako.rs | 26 | ||||
-rw-r--r-- | components/style/properties/shorthand/box.mako.rs | 2 | ||||
-rw-r--r-- | components/style/selector_matching.rs | 17 | ||||
-rw-r--r-- | components/style/values.rs | 24 | ||||
-rw-r--r-- | components/style/viewport.rs | 1 |
11 files changed, 929 insertions, 1124 deletions
diff --git a/components/style/animation.rs b/components/style/animation.rs index 3cd994af327..e6e8a3ac89e 100644 --- a/components/style/animation.rs +++ b/components/style/animation.rs @@ -6,36 +6,17 @@ use app_units::Au; use bezier::Bezier; -use cssparser::{Color, RGBA}; -use dom::{OpaqueNode, TRestyleDamage}; use euclid::point::Point2D; -use properties::longhands::background_position::computed_value::T as BackgroundPosition; -use properties::longhands::border_spacing::computed_value::T as BorderSpacing; -use properties::longhands::clip::computed_value::ClipRect; -use properties::longhands::font_weight::computed_value::T as FontWeight; -use properties::longhands::line_height::computed_value::T as LineHeight; -use properties::longhands::text_shadow::computed_value::T as TextShadowList; -use properties::longhands::text_shadow::computed_value::TextShadow; -use properties::longhands::transform::computed_value::ComputedMatrix; -use properties::longhands::transform::computed_value::ComputedOperation as TransformOperation; -use properties::longhands::transform::computed_value::T as TransformList; -use properties::longhands::transition_property; -use properties::longhands::transition_property::computed_value::TransitionProperty; +use dom::{OpaqueNode, TRestyleDamage}; +use properties::animated_properties::{AnimatedProperty, TransitionProperty}; use properties::longhands::transition_timing_function::computed_value::StartEnd; use properties::longhands::transition_timing_function::computed_value::TransitionTimingFunction; -use properties::longhands::vertical_align::computed_value::T as VerticalAlign; -use properties::longhands::visibility::computed_value::T as Visibility; -use properties::longhands::z_index::computed_value::T as ZIndex; use properties::style_struct_traits::Box; use properties::{ComputedValues, ServoComputedValues}; -use std::cmp::Ordering; -use std::iter::repeat; use std::sync::mpsc::Sender; use std::sync::{Arc, Mutex}; use time; -use values::CSSFloat; -use values::computed::{Angle, LengthOrPercentageOrAuto, LengthOrPercentageOrNone}; -use values::computed::{CalcLengthOrPercentage, Length, LengthOrPercentage, Time}; +use values::computed::Time; /// State relating to an animation. #[derive(Clone)] @@ -74,13 +55,18 @@ impl PropertyAnimation { old_style: &ServoComputedValues, new_style: &mut ServoComputedValues) -> Vec<PropertyAnimation> { - let mut result = Vec::new(); - let transition_property = - new_style.as_servo().get_box().transition_property.0[transition_index]; + let mut result = vec![]; + let box_style = new_style.as_servo().get_box(); + let transition_property = box_style.transition_property.0[transition_index]; + let timing_function = *box_style.transition_timing_function.0.get_mod(transition_index); + let duration = *box_style.transition_duration.0.get_mod(transition_index); + + if transition_property != TransitionProperty::All { if let Some(property_animation) = PropertyAnimation::from_transition_property(transition_property, - transition_index, + timing_function, + duration, old_style, new_style) { result.push(property_animation) @@ -88,114 +74,40 @@ impl PropertyAnimation { return result } - for transition_property in - transition_property::computed_value::ALL_TRANSITION_PROPERTIES.iter() { + TransitionProperty::each(|transition_property| { if let Some(property_animation) = - PropertyAnimation::from_transition_property(*transition_property, - transition_index, + PropertyAnimation::from_transition_property(transition_property, + timing_function, + duration, old_style, new_style) { result.push(property_animation) } - } + }); result } fn from_transition_property(transition_property: TransitionProperty, - transition_index: usize, + timing_function: TransitionTimingFunction, + duration: Time, old_style: &ServoComputedValues, - new_style: &mut ServoComputedValues) + new_style: &ServoComputedValues) -> Option<PropertyAnimation> { - let box_style = new_style.get_box(); - macro_rules! match_transition { - ( $( [$name:ident; $structname:ident; $field:ident] ),* ) => { - match transition_property { - TransitionProperty::All => { - panic!("Don't use `TransitionProperty::All` with \ - `PropertyAnimation::from_transition_property`!") - } - $( - TransitionProperty::$name => { - AnimatedProperty::$name(old_style.$structname().$field, - new_style.$structname().$field) - } - )* - TransitionProperty::Clip => { - AnimatedProperty::Clip(old_style.get_effects().clip.0, - new_style.get_effects().clip.0) - } - TransitionProperty::LetterSpacing => { - AnimatedProperty::LetterSpacing(old_style.get_inheritedtext().letter_spacing.0, - new_style.get_inheritedtext().letter_spacing.0) - } - TransitionProperty::TextShadow => { - AnimatedProperty::TextShadow(old_style.get_inheritedtext().text_shadow.clone(), - new_style.get_inheritedtext().text_shadow.clone()) - } - TransitionProperty::Transform => { - AnimatedProperty::Transform(old_style.get_effects().transform.clone(), - new_style.get_effects().transform.clone()) - } - TransitionProperty::WordSpacing => { - AnimatedProperty::WordSpacing(old_style.get_inheritedtext().word_spacing.0, - new_style.get_inheritedtext().word_spacing.0) - } - } - } - } - let animated_property = match_transition!( - [BackgroundColor; get_background; background_color], - [BackgroundPosition; get_background; background_position], - [BorderBottomColor; get_border; border_bottom_color], - [BorderBottomWidth; get_border; border_bottom_width], - [BorderLeftColor; get_border; border_left_color], - [BorderLeftWidth; get_border; border_left_width], - [BorderRightColor; get_border; border_right_color], - [BorderRightWidth; get_border; border_right_width], - [BorderSpacing; get_inheritedtable; border_spacing], - [BorderTopColor; get_border; border_top_color], - [BorderTopWidth; get_border; border_top_width], - [Bottom; get_position; bottom], - [Color; get_color; color], - [FontSize; get_font; font_size], - [FontWeight; get_font; font_weight], - [Height; get_position; height], - [Left; get_position; left], - [LineHeight; get_inheritedtext; line_height], - [MarginBottom; get_margin; margin_bottom], - [MarginLeft; get_margin; margin_left], - [MarginRight; get_margin; margin_right], - [MarginTop; get_margin; margin_top], - [MaxHeight; get_position; max_height], - [MaxWidth; get_position; max_width], - [MinHeight; get_position; min_height], - [MinWidth; get_position; min_width], - [Opacity; get_effects; opacity], - [OutlineColor; get_outline; outline_color], - [OutlineWidth; get_outline; outline_width], - [PaddingBottom; get_padding; padding_bottom], - [PaddingLeft; get_padding; padding_left], - [PaddingRight; get_padding; padding_right], - [PaddingTop; get_padding; padding_top], - [Right; get_position; right], - [TextIndent; get_inheritedtext; text_indent], - [Top; get_position; top], - [VerticalAlign; get_box; vertical_align], - [Visibility; get_inheritedbox; visibility], - [Width; get_position; width], - [ZIndex; get_position; z_index]); + let animated_property = AnimatedProperty::from_transition_property(&transition_property, + old_style, + new_style); let property_animation = PropertyAnimation { property: animated_property, - timing_function: - *box_style.transition_timing_function.0.get_mod(transition_index), - duration: *box_style.transition_duration.0.get_mod(transition_index), + timing_function: timing_function, + duration: duration, }; - if property_animation.does_not_animate() { - None - } else { + + if property_animation.does_animate() { Some(property_animation) + } else { + None } } @@ -215,725 +127,12 @@ impl PropertyAnimation { } }; - macro_rules! match_property( - ( $( [$name:ident; $structname:ident; $field:ident] ),* ) => { - match self.property { - $( - AnimatedProperty::$name(ref start, ref end) => { - if let Some(value) = start.interpolate(end, progress) { - style.$structname().$field = value - } - } - )* - AnimatedProperty::Clip(ref start, ref end) => { - if let Some(value) = start.interpolate(end, progress) { - style.mutate_effects().clip.0 = value - } - } - AnimatedProperty::LetterSpacing(ref start, ref end) => { - if let Some(value) = start.interpolate(end, progress) { - style.mutate_inheritedtext().letter_spacing.0 = value - } - } - AnimatedProperty::WordSpacing(ref start, ref end) => { - if let Some(value) = start.interpolate(end, progress) { - style.mutate_inheritedtext().word_spacing.0 = value - } - } - } - }); - match_property!( - [BackgroundColor; mutate_background; background_color], - [BackgroundPosition; mutate_background; background_position], - [BorderBottomColor; mutate_border; border_bottom_color], - [BorderBottomWidth; mutate_border; border_bottom_width], - [BorderLeftColor; mutate_border; border_left_color], - [BorderLeftWidth; mutate_border; border_left_width], - [BorderRightColor; mutate_border; border_right_color], - [BorderRightWidth; mutate_border; border_right_width], - [BorderSpacing; mutate_inheritedtable; border_spacing], - [BorderTopColor; mutate_border; border_top_color], - [BorderTopWidth; mutate_border; border_top_width], - [Bottom; mutate_position; bottom], - [Color; mutate_color; color], - [FontSize; mutate_font; font_size], - [FontWeight; mutate_font; font_weight], - [Height; mutate_position; height], - [Left; mutate_position; left], - [LineHeight; mutate_inheritedtext; line_height], - [MarginBottom; mutate_margin; margin_bottom], - [MarginLeft; mutate_margin; margin_left], - [MarginRight; mutate_margin; margin_right], - [MarginTop; mutate_margin; margin_top], - [MaxHeight; mutate_position; max_height], - [MaxWidth; mutate_position; max_width], - [MinHeight; mutate_position; min_height], - [MinWidth; mutate_position; min_width], - [Opacity; mutate_effects; opacity], - [OutlineColor; mutate_outline; outline_color], - [OutlineWidth; mutate_outline; outline_width], - [PaddingBottom; mutate_padding; padding_bottom], - [PaddingLeft; mutate_padding; padding_left], - [PaddingRight; mutate_padding; padding_right], - [PaddingTop; mutate_padding; padding_top], - [Right; mutate_position; right], - [TextIndent; mutate_inheritedtext; text_indent], - [TextShadow; mutate_inheritedtext; text_shadow], - [Top; mutate_position; top], - [Transform; mutate_effects; transform], - [VerticalAlign; mutate_box; vertical_align], - [Visibility; mutate_inheritedbox; visibility], - [Width; mutate_position; width], - [ZIndex; mutate_position; z_index]); - } - - #[inline] - fn does_not_animate(&self) -> bool { - self.property.does_not_animate() || self.duration == Time(0.0) - } -} - -#[derive(Clone, Debug)] -enum AnimatedProperty { - BackgroundColor(Color, Color), - BackgroundPosition(BackgroundPosition, BackgroundPosition), - BorderBottomColor(Color, Color), - BorderBottomWidth(Length, Length), - BorderLeftColor(Color, Color), - BorderLeftWidth(Length, Length), - BorderRightColor(Color, Color), - BorderRightWidth(Length, Length), - BorderSpacing(BorderSpacing, BorderSpacing), - BorderTopColor(Color, Color), - BorderTopWidth(Length, Length), - Bottom(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - Color(RGBA, RGBA), - Clip(Option<ClipRect>, Option<ClipRect>), - FontSize(Length, Length), - FontWeight(FontWeight, FontWeight), - Height(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - Left(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - LetterSpacing(Option<Au>, Option<Au>), - LineHeight(LineHeight, LineHeight), - MarginBottom(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - MarginLeft(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - MarginRight(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - MarginTop(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - MaxHeight(LengthOrPercentageOrNone, LengthOrPercentageOrNone), - MaxWidth(LengthOrPercentageOrNone, LengthOrPercentageOrNone), - MinHeight(LengthOrPercentage, LengthOrPercentage), - MinWidth(LengthOrPercentage, LengthOrPercentage), - Opacity(CSSFloat, CSSFloat), - OutlineColor(Color, Color), - OutlineWidth(Length, Length), - PaddingBottom(LengthOrPercentage, LengthOrPercentage), - PaddingLeft(LengthOrPercentage, LengthOrPercentage), - PaddingRight(LengthOrPercentage, LengthOrPercentage), - PaddingTop(LengthOrPercentage, LengthOrPercentage), - Right(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - TextIndent(LengthOrPercentage, LengthOrPercentage), - TextShadow(TextShadowList, TextShadowList), - Top(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - Transform(TransformList, TransformList), - VerticalAlign(VerticalAlign, VerticalAlign), - Visibility(Visibility, Visibility), - Width(LengthOrPercentageOrAuto, LengthOrPercentageOrAuto), - WordSpacing(Option<Au>, Option<Au>), - ZIndex(ZIndex, ZIndex), -} - -impl AnimatedProperty { - #[inline] - fn does_not_animate(&self) -> bool { - match *self { - AnimatedProperty::Top(ref a, ref b) | - AnimatedProperty::Right(ref a, ref b) | - AnimatedProperty::Bottom(ref a, ref b) | - AnimatedProperty::Left(ref a, ref b) | - AnimatedProperty::MarginTop(ref a, ref b) | - AnimatedProperty::MarginRight(ref a, ref b) | - AnimatedProperty::MarginBottom(ref a, ref b) | - AnimatedProperty::MarginLeft(ref a, ref b) | - AnimatedProperty::Width(ref a, ref b) | - AnimatedProperty::Height(ref a, ref b) => a == b, - AnimatedProperty::MaxWidth(ref a, ref b) | - AnimatedProperty::MaxHeight(ref a, ref b) => a == b, - AnimatedProperty::MinWidth(ref a, ref b) | - AnimatedProperty::MinHeight(ref a, ref b) | - AnimatedProperty::TextIndent(ref a, ref b) => a == b, - AnimatedProperty::FontSize(ref a, ref b) | - AnimatedProperty::BorderTopWidth(ref a, ref b) | - AnimatedProperty::BorderRightWidth(ref a, ref b) | - AnimatedProperty::BorderBottomWidth(ref a, ref b) | - AnimatedProperty::BorderLeftWidth(ref a, ref b) => a == b, - AnimatedProperty::BorderTopColor(ref a, ref b) | - AnimatedProperty::BorderRightColor(ref a, ref b) | - AnimatedProperty::BorderBottomColor(ref a, ref b) | - AnimatedProperty::BorderLeftColor(ref a, ref b) | - AnimatedProperty::OutlineColor(ref a, ref b) | - AnimatedProperty::BackgroundColor(ref a, ref b) => a == b, - AnimatedProperty::PaddingTop(ref a, ref b) | - AnimatedProperty::PaddingRight(ref a, ref b) | - AnimatedProperty::PaddingBottom(ref a, ref b) | - AnimatedProperty::PaddingLeft(ref a, ref b) => a == b, - AnimatedProperty::LineHeight(ref a, ref b) => a == b, - AnimatedProperty::LetterSpacing(ref a, ref b) => a == b, - AnimatedProperty::BackgroundPosition(ref a, ref b) => a == b, - AnimatedProperty::BorderSpacing(ref a, ref b) => a == b, - AnimatedProperty::Clip(ref a, ref b) => a == b, - AnimatedProperty::Color(ref a, ref b) => a == b, - AnimatedProperty::FontWeight(ref a, ref b) => a == b, - AnimatedProperty::Opacity(ref a, ref b) => a == b, - AnimatedProperty::OutlineWidth(ref a, ref b) => a == b, - AnimatedProperty::TextShadow(ref a, ref b) => a == b, - AnimatedProperty::VerticalAlign(ref a, ref b) => a == b, - AnimatedProperty::Visibility(ref a, ref b) => a == b, - AnimatedProperty::WordSpacing(ref a, ref b) => a == b, - AnimatedProperty::ZIndex(ref a, ref b) => a == b, - AnimatedProperty::Transform(ref a, ref b) => a == b, - } - } -} - -/// A trait used to implement [interpolation][interpolated-types]. -/// -/// [interpolated-types]: https://drafts.csswg.org/css-transitions/#interpolated-types -trait Interpolate: Sized { - fn interpolate(&self, other: &Self, time: f64) -> Option<Self>; -} - -impl Interpolate for Au { - #[inline] - fn interpolate(&self, other: &Au, time: f64) -> Option<Au> { - Some(Au((self.0 as f64 + (other.0 as f64 - self.0 as f64) * time).round() as i32)) - } -} - -impl <T> Interpolate for Option<T> where T: Interpolate { - #[inline] - fn interpolate(&self, other: &Option<T>, time: f64) -> Option<Option<T>> { - match (self, other) { - (&Some(ref this), &Some(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(Some(value)) - }) - } - (_, _) => None - } - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-number -impl Interpolate for f32 { - #[inline] - fn interpolate(&self, other: &f32, time: f64) -> Option<f32> { - Some(((*self as f64) + ((*other as f64) - (*self as f64)) * time) as f32) - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-number -impl Interpolate for f64 { - #[inline] - fn interpolate(&self, other: &f64, time: f64) -> Option<f64> { - Some(*self + (*other - *self) * time) - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-integer -impl Interpolate for i32 { - #[inline] - fn interpolate(&self, other: &i32, time: f64) -> Option<i32> { - let a = *self as f64; - let b = *other as f64; - Some((a + (b - a) * time).round() as i32) - } -} - -impl Interpolate for Angle { - #[inline] - fn interpolate(&self, other: &Angle, time: f64) -> Option<Angle> { - self.radians().interpolate(&other.radians(), time).map(Angle) - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-visibility -impl Interpolate for Visibility { - #[inline] - fn interpolate(&self, other: &Visibility, time: f64) - -> Option<Visibility> { - match (*self, *other) { - (Visibility::visible, _) | (_, Visibility::visible) => { - if time >= 0.0 && time <= 1.0 { - Some(Visibility::visible) - } else if time < 0.0 { - Some(*self) - } else { - Some(*other) - } - } - (_, _) => None, - } - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-integer -impl Interpolate for ZIndex { - #[inline] - fn interpolate(&self, other: &ZIndex, time: f64) - -> Option<ZIndex> { - match (*self, *other) { - (ZIndex::Number(ref this), - ZIndex::Number(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(ZIndex::Number(value)) - }) - } - (_, _) => None, - } - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-length -impl Interpolate for VerticalAlign { - #[inline] - fn interpolate(&self, other: &VerticalAlign, time: f64) - -> Option<VerticalAlign> { - match (*self, *other) { - (VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(ref this)), - VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(ref other))) => { - this.interpolate(other, time).and_then(|value| { - Some(VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(value))) - }) - } - (_, _) => None, - } - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-simple-list -impl Interpolate for BorderSpacing { - #[inline] - fn interpolate(&self, other: &BorderSpacing, time: f64) - -> Option<BorderSpacing> { - self.horizontal.interpolate(&other.horizontal, time).and_then(|horizontal| { - self.vertical.interpolate(&other.vertical, time).and_then(|vertical| { - Some(BorderSpacing { horizontal: horizontal, vertical: vertical }) - }) - }) - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-color -impl Interpolate for RGBA { - #[inline] - fn interpolate(&self, other: &RGBA, time: f64) -> Option<RGBA> { - match (self.red.interpolate(&other.red, time), - self.green.interpolate(&other.green, time), - self.blue.interpolate(&other.blue, time), - self.alpha.interpolate(&other.alpha, time)) { - (Some(red), Some(green), Some(blue), Some(alpha)) => { - Some(RGBA { red: red, green: green, blue: blue, alpha: alpha }) - } - (_, _, _, _) => None - } - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-color -impl Interpolate for Color { - #[inline] - fn interpolate(&self, other: &Color, time: f64) -> Option<Color> { - match (*self, *other) { - (Color::RGBA(ref this), Color::RGBA(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(Color::RGBA(value)) - }) - } - (_, _) => None, - } - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc -impl Interpolate for CalcLengthOrPercentage { - #[inline] - fn interpolate(&self, other: &CalcLengthOrPercentage, time: f64) - -> Option<CalcLengthOrPercentage> { - Some(CalcLengthOrPercentage { - length: self.length().interpolate(&other.length(), time), - percentage: self.percentage().interpolate(&other.percentage(), time), - }) - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc -impl Interpolate for LengthOrPercentage { - #[inline] - fn interpolate(&self, other: &LengthOrPercentage, time: f64) - -> Option<LengthOrPercentage> { - match (*self, *other) { - (LengthOrPercentage::Length(ref this), - LengthOrPercentage::Length(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(LengthOrPercentage::Length(value)) - }) - } - (LengthOrPercentage::Percentage(ref this), - LengthOrPercentage::Percentage(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(LengthOrPercentage::Percentage(value)) - }) - } - (this, other) => { - let this: CalcLengthOrPercentage = From::from(this); - let other: CalcLengthOrPercentage = From::from(other); - this.interpolate(&other, time).and_then(|value| { - Some(LengthOrPercentage::Calc(value)) - }) - } - } - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc -impl Interpolate for LengthOrPercentageOrAuto { - #[inline] - fn interpolate(&self, other: &LengthOrPercentageOrAuto, time: f64) - -> Option<LengthOrPercentageOrAuto> { - match (*self, *other) { - (LengthOrPercentageOrAuto::Length(ref this), - LengthOrPercentageOrAuto::Length(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(LengthOrPercentageOrAuto::Length(value)) - }) - } - (LengthOrPercentageOrAuto::Percentage(ref this), - LengthOrPercentageOrAuto::Percentage(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(LengthOrPercentageOrAuto::Percentage(value)) - }) - } - (LengthOrPercentageOrAuto::Auto, LengthOrPercentageOrAuto::Auto) => { - Some(LengthOrPercentageOrAuto::Auto) - } - (this, other) => { - let this: Option<CalcLengthOrPercentage> = From::from(this); - let other: Option<CalcLengthOrPercentage> = From::from(other); - this.interpolate(&other, time).unwrap_or(None).and_then(|value| { - Some(LengthOrPercentageOrAuto::Calc(value)) - }) - } - } - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc -impl Interpolate for LengthOrPercentageOrNone { - #[inline] - fn interpolate(&self, other: &LengthOrPercentageOrNone, time: f64) - -> Option<LengthOrPercentageOrNone> { - match (*self, *other) { - (LengthOrPercentageOrNone::Length(ref this), - LengthOrPercentageOrNone::Length(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(LengthOrPercentageOrNone::Length(value)) - }) - } - (LengthOrPercentageOrNone::Percentage(ref this), - LengthOrPercentageOrNone::Percentage(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(LengthOrPercentageOrNone::Percentage(value)) - }) - } - (LengthOrPercentageOrNone::None, LengthOrPercentageOrNone::None) => { - Some(LengthOrPercentageOrNone::None) - } - (_, _) => None, - } - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-number -/// https://drafts.csswg.org/css-transitions/#animtype-length -impl Interpolate for LineHeight { - #[inline] - fn interpolate(&self, other: &LineHeight, time: f64) - -> Option<LineHeight> { - match (*self, *other) { - (LineHeight::Length(ref this), - LineHeight::Length(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(LineHeight::Length(value)) - }) - } - (LineHeight::Number(ref this), - LineHeight::Number(ref other)) => { - this.interpolate(other, time).and_then(|value| { - Some(LineHeight::Number(value)) - }) - } - (LineHeight::Normal, LineHeight::Normal) => { - Some(LineHeight::Normal) - } - (_, _) => None, - } - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-font-weight -impl Interpolate for FontWeight { - #[inline] - fn interpolate(&self, other: &FontWeight, time: f64) - -> Option<FontWeight> { - let a = (*self as u32) as f64; - let b = (*other as u32) as f64; - let weight = a + (b - a) * time; - Some(if weight < 150. { - FontWeight::Weight100 - } else if weight < 250. { - FontWeight::Weight200 - } else if weight < 350. { - FontWeight::Weight300 - } else if weight < 450. { - FontWeight::Weight400 - } else if weight < 550. { - FontWeight::Weight500 - } else if weight < 650. { - FontWeight::Weight600 - } else if weight < 750. { - FontWeight::Weight700 - } else if weight < 850. { - FontWeight::Weight800 - } else { - FontWeight::Weight900 - }) - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-rect -impl Interpolate for ClipRect { - #[inline] - fn interpolate(&self, other: &ClipRect, time: f64) - -> Option<ClipRect> { - match (self.top.interpolate(&other.top, time), - self.right.interpolate(&other.right, time), - self.bottom.interpolate(&other.bottom, time), - self.left.interpolate(&other.left, time)) { - (Some(top), Some(right), Some(bottom), Some(left)) => { - Some(ClipRect { top: top, right: right, bottom: bottom, left: left }) - }, - (_, _, _, _) => None, - } - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-simple-list -impl Interpolate for BackgroundPosition { - #[inline] - fn interpolate(&self, other: &BackgroundPosition, time: f64) - -> Option<BackgroundPosition> { - match (self.horizontal.interpolate(&other.horizontal, time), - self.vertical.interpolate(&other.vertical, time)) { - (Some(horizontal), Some(vertical)) => { - Some(BackgroundPosition { horizontal: horizontal, vertical: vertical }) - }, - (_, _) => None, - } - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-shadow-list -impl Interpolate for TextShadow { - #[inline] - fn interpolate(&self, other: &TextShadow, time: f64) - -> Option<TextShadow> { - match (self.offset_x.interpolate(&other.offset_x, time), - self.offset_y.interpolate(&other.offset_y, time), - self.blur_radius.interpolate(&other.blur_radius, time), - self.color.interpolate(&other.color, time)) { - (Some(offset_x), Some(offset_y), Some(blur_radius), Some(color)) => { - Some(TextShadow { offset_x: offset_x, offset_y: offset_y, blur_radius: blur_radius, color: color }) - }, - (_, _, _, _) => None, - } - } -} - -/// https://drafts.csswg.org/css-transitions/#animtype-shadow-list -impl Interpolate for TextShadowList { - #[inline] - fn interpolate(&self, other: &TextShadowList, time: f64) - -> Option<TextShadowList> { - let zero = TextShadow { - offset_x: Au(0), - offset_y: Au(0), - blur_radius: Au(0), - color: Color::RGBA(RGBA { - red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0 - }) - }; - - let interpolate_each = |(a, b): (&TextShadow, &TextShadow)| { - a.interpolate(b, time).unwrap() - }; - - Some(TextShadowList(match self.0.len().cmp(&other.0.len()) { - Ordering::Less => other.0.iter().chain(repeat(&zero)).zip(other.0.iter()).map(interpolate_each).collect(), - _ => self.0.iter().zip(other.0.iter().chain(repeat(&zero))).map(interpolate_each).collect(), - })) - } -} - -/// Check if it's possible to do a direct numerical interpolation -/// between these two transform lists. -/// http://dev.w3.org/csswg/css-transforms/#transform-transform-animation -fn can_interpolate_list(from_list: &[TransformOperation], - to_list: &[TransformOperation]) -> bool { - // Lists must be equal length - if from_list.len() != to_list.len() { - return false; - } - - // Each transform operation must match primitive type in other list - for (from, to) in from_list.iter().zip(to_list) { - match (from, to) { - (&TransformOperation::Matrix(..), &TransformOperation::Matrix(..)) | - (&TransformOperation::Skew(..), &TransformOperation::Skew(..)) | - (&TransformOperation::Translate(..), &TransformOperation::Translate(..)) | - (&TransformOperation::Scale(..), &TransformOperation::Scale(..)) | - (&TransformOperation::Rotate(..), &TransformOperation::Rotate(..)) | - (&TransformOperation::Perspective(..), &TransformOperation::Perspective(..)) => {} - _ => { - return false; - } - } - } - - true -} - -/// Interpolate two transform lists. -/// https://drafts.csswg.org/css-transforms/#interpolation-of-transforms -fn interpolate_transform_list(from_list: &[TransformOperation], - to_list: &[TransformOperation], - time: f64) -> TransformList { - let mut result = vec![]; - - if can_interpolate_list(from_list, to_list) { - for (from, to) in from_list.iter().zip(to_list) { - match (from, to) { - (&TransformOperation::Matrix(from), - &TransformOperation::Matrix(_to)) => { - // TODO(gw): Implement matrix decomposition and interpolation - result.push(TransformOperation::Matrix(from)); - } - (&TransformOperation::Skew(fx, fy), - &TransformOperation::Skew(tx, ty)) => { - let ix = fx.interpolate(&tx, time).unwrap(); - let iy = fy.interpolate(&ty, time).unwrap(); - result.push(TransformOperation::Skew(ix, iy)); - } - (&TransformOperation::Translate(fx, fy, fz), - &TransformOperation::Translate(tx, ty, tz)) => { - let ix = fx.interpolate(&tx, time).unwrap(); - let iy = fy.interpolate(&ty, time).unwrap(); - let iz = fz.interpolate(&tz, time).unwrap(); - result.push(TransformOperation::Translate(ix, iy, iz)); - } - (&TransformOperation::Scale(fx, fy, fz), - &TransformOperation::Scale(tx, ty, tz)) => { - let ix = fx.interpolate(&tx, time).unwrap(); - let iy = fy.interpolate(&ty, time).unwrap(); - let iz = fz.interpolate(&tz, time).unwrap(); - result.push(TransformOperation::Scale(ix, iy, iz)); - } - (&TransformOperation::Rotate(fx, fy, fz, fa), - &TransformOperation::Rotate(_tx, _ty, _tz, _ta)) => { - // TODO(gw): Implement matrix decomposition and interpolation - result.push(TransformOperation::Rotate(fx, fy, fz, fa)); - } - (&TransformOperation::Perspective(fd), - &TransformOperation::Perspective(_td)) => { - // TODO(gw): Implement matrix decomposition and interpolation - result.push(TransformOperation::Perspective(fd)); - } - _ => { - // This should be unreachable due to the can_interpolate_list() call. - unreachable!(); - } - } - } - } else { - // TODO(gw): Implement matrix decomposition and interpolation - result.extend_from_slice(from_list); + self.property.update(style, progress); } - TransformList(Some(result)) -} - -/// Build an equivalent 'identity transform function list' based -/// on an existing transform list. -/// https://drafts.csswg.org/css-transforms/#none-transform-animation -fn build_identity_transform_list(list: &[TransformOperation]) -> Vec<TransformOperation> { - let mut result = vec!(); - - for operation in list { - match *operation { - TransformOperation::Matrix(..) => { - let identity = ComputedMatrix::identity(); - result.push(TransformOperation::Matrix(identity)); - } - TransformOperation::Skew(..) => { - result.push(TransformOperation::Skew(Angle(0.0), Angle(0.0))); - } - TransformOperation::Translate(..) => { - result.push(TransformOperation::Translate(LengthOrPercentage::zero(), - LengthOrPercentage::zero(), - Au(0))); - } - TransformOperation::Scale(..) => { - result.push(TransformOperation::Scale(1.0, 1.0, 1.0)); - } - TransformOperation::Rotate(..) => { - result.push(TransformOperation::Rotate(0.0, 0.0, 1.0, Angle(0.0))); - } - TransformOperation::Perspective(..) => { - // http://dev.w3.org/csswg/css-transforms/#identity-transform-function - let identity = ComputedMatrix::identity(); - result.push(TransformOperation::Matrix(identity)); - } - } - } - - result -} - -/// https://drafts.csswg.org/css-transforms/#interpolation-of-transforms -impl Interpolate for TransformList { #[inline] - fn interpolate(&self, other: &TransformList, time: f64) -> Option<TransformList> { - let result = match (&self.0, &other.0) { - (&Some(ref from_list), &Some(ref to_list)) => { - // https://drafts.csswg.org/css-transforms/#transform-transform-animation - interpolate_transform_list(from_list, &to_list, time) - } - (&Some(ref from_list), &None) => { - // https://drafts.csswg.org/css-transforms/#none-transform-animation - let to_list = build_identity_transform_list(from_list); - interpolate_transform_list(from_list, &to_list, time) - } - (&None, &Some(ref to_list)) => { - // https://drafts.csswg.org/css-transforms/#none-transform-animation - let from_list = build_identity_transform_list(to_list); - interpolate_transform_list(&from_list, to_list, time) - } - _ => { - // https://drafts.csswg.org/css-transforms/#none-none-animation - TransformList(None) - } - }; - - Some(result) + fn does_animate(&self) -> bool { + self.property.does_animate() && self.duration != Time(0.0) } } @@ -948,6 +147,7 @@ pub trait GetMod { impl<T> GetMod for Vec<T> { type Item = T; + #[inline] fn get_mod(&self, i: usize) -> &T { &(*self)[i % self.len()] } @@ -991,10 +191,9 @@ pub fn start_transitions_if_applicable<C: ComputedValues>(new_animations_sender: /// Updates a single animation and associated style based on the current time. If `damage` is /// provided, inserts the appropriate restyle damage. -pub fn update_style_for_animation<C: ComputedValues, - Damage: TRestyleDamage<ConcreteComputedValues=C>>(animation: &Animation, - style: &mut Arc<C>, - damage: Option<&mut Damage>) { +pub fn update_style_for_animation<Damage: TRestyleDamage>(animation: &Animation, + style: &mut Arc<Damage::ConcreteComputedValues>, + damage: Option<&mut Damage>) { let now = time::precise_time_s(); let mut progress = (now - animation.start_time) / animation.duration(); if progress > 1.0 { diff --git a/components/style/keyframes.rs b/components/style/keyframes.rs index 5f0cf6858be..a558020a748 100644 --- a/components/style/keyframes.rs +++ b/components/style/keyframes.rs @@ -4,7 +4,8 @@ use cssparser::{Parser, Delimiter}; use parser::ParserContext; -use properties::{PropertyDeclarationBlock, parse_property_declaration_list}; +use properties::{ComputedValues, PropertyDeclarationBlock, parse_property_declaration_list}; +use properties::animated_properties::AnimatedProperty; /// Parses a keyframes list, like: /// 0%, 50% { @@ -19,6 +20,11 @@ pub fn parse_keyframe_list(context: &ParserContext, input: &mut Parser) -> Resul while !input.is_exhausted() { keyframes.push(try!(Keyframe::parse(context, input))); } + + if keyframes.len() < 2 { + return Err(()) + } + Ok(keyframes) } @@ -89,3 +95,60 @@ impl Keyframe { }) } } + +/// A single step from a keyframe animation. +#[derive(Debug, Clone, PartialEq, HeapSizeOf)] +pub struct ComputedKeyframesStep<C: ComputedValues> { + /// The percentage of the animation duration that should be taken for this + /// step. + duration_percentage: KeyframePercentage, + // XXX: Can we optimise this? Probably not such a show-stopper... Probably + // storing declared values could work/should be the thing to do? + /// The computed values at the beginning of the step. + begin: C, + /// The computed values at the end of the step. + end: C, +} + +/// This structure represents a list of animation steps computed from the list +/// of keyframes, in order. +/// +/// It only takes into account animable properties. +#[derive(Debug, Clone, PartialEq, HeapSizeOf)] +pub struct ComputedKeyframesAnimation<C: ComputedValues> { + steps: Vec<ComputedKeyframesStep<C>>, + /// The properties that change in this animation. + properties_changed: Vec<AnimatedProperty>, +} + +fn get_animated_properties(keyframe: &Keyframe) -> Vec<AnimatedProperty> { + // TODO + vec![] +} + +impl<C: ComputedValues> ComputedKeyframesAnimation<C> { + pub fn from_keyframes(keyframes: &[Keyframe]) -> Option<ComputedKeyframesAnimation<C>> { + debug_assert!(keyframes.len() > 1); + let mut steps = vec![]; + + // NB: we do two passes, first storing the steps in the order of + // appeareance, then sorting them, then updating with the real + // "duration_percentage". + let mut animated_properties = get_animated_properties(&keyframes[0]); + + if animated_properties.is_empty() { + return None; + } + + for keyframe in keyframes { + for step in keyframe.selector.0.iter() { + + } + } + + Some(ComputedKeyframesAnimation { + steps: steps, + properties_changed: animated_properties, + }) + } +} diff --git a/components/style/matching.rs b/components/style/matching.rs index 2e4b7922f7e..e17af2a2fcb 100644 --- a/components/style/matching.rs +++ b/components/style/matching.rs @@ -400,7 +400,6 @@ trait PrivateMatchMethods: TNode shareable, Some(&***parent_style), cached_computed_values, - animations, context.error_reporter.clone()); cacheable = cacheable && is_cacheable; this_style = the_style @@ -411,7 +410,6 @@ trait PrivateMatchMethods: TNode shareable, None, None, - animations, context.error_reporter.clone()); cacheable = cacheable && is_cacheable; this_style = the_style @@ -482,8 +480,7 @@ trait PrivateMatchMethods: TNode if had_running_animations { let mut all_running_animations = context.running_animations.write().unwrap(); for running_animation in all_running_animations.get(&this_opaque).unwrap() { - animation::update_style_for_animation::<Self::ConcreteComputedValues, - Self::ConcreteRestyleDamage>(running_animation, style, None); + animation::update_style_for_animation::<Self::ConcreteRestyleDamage>(running_animation, style, None); } all_running_animations.remove(&this_opaque); } diff --git a/components/style/properties/data.py b/components/style/properties/data.py index 4b2cc717407..eb16f986319 100644 --- a/components/style/properties/data.py +++ b/components/style/properties/data.py @@ -16,6 +16,53 @@ def to_camel_case(ident): return re.sub("(^|_|-)([a-z])", lambda m: m.group(2).upper(), ident.strip("_").strip("-")) +# https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties +def is_known_animatable_property(name): + return name in [ + "-moz-outline-radius", "-moz-outline-radius-bottomleft", + "-moz-outline-radius-bottomright", "-moz-outline-radius-topleft", + "-moz-outline-radius-topright", "-webkit-text-fill-color", + "-webkit-text-stroke", "-webkit-text-stroke-color", + "-webkit-touch-callout", "all", "backdrop-filter", "background", + "background-color", "background-position", "background-size", "border", + "border-bottom", "border-bottom-color", "border-bottom-left-radius", + "border-bottom-right-radius", "border-bottom-width", "border-color", + "border-left", "border-left-color", "border-left-width", "border-radius", + "border-right", "border-right-color", "border-right-width", "border-top", + "border-top-color", "border-top-left-radius", "border-top-right-radius", + "border-top-width", "border-width", "bottom", "box-shadow", "clip", + "clip-path", "color", "column-count", "column-gap", "column-rule", + "column-rule-color", "column-rule-width", "column-width", "columns", + "filter", "flex", "flex-basis", "flex-grow", "flex-shrink", "font", + "font-size", "font-size-adjust", "font-stretch", "font-weight", + "grid-column-gap", "grid-gap", "grid-row-gap", "height", "left", + "letter-spacing", "line-height", "margin", "margin-bottom", + "margin-left", "margin-right", "margin-top", "mask", "mask-position", + "mask-size", "max-height", "max-width", "min-height", "min-width", + "motion-offset", "motion-rotation", "object-position", "opacity", + "order", "outline", "outline-color", "outline-offset", "outline-width", + "padding", "padding-bottom", "padding-left", "padding-right", + "padding-top", "perspective", "perspective-origin", "right", + "scroll-snap-coordinate", "scroll-snap-destination", + "shape-image-threshold", "shape-margin", "shape-outside", + "text-decoration", "text-decoration-color", "text-emphasis", + "text-emphasis-color", "text-indent", "text-shadow", "top", "transform", + "transform-origin", "vertical-align", "visibility", "width", + "word-spacing", "z-index" + ] + + +# FIXME: Servo doesn't support some animatable properties yet,those are in the +# following list, and can be implemented removing it from the list and +# implementing the Interpolate trait in helpers/animated_properties.mako.rs +def is_not_supported_animatable_property(name): + return name in [ + "flex-basis", "column-width", "column-height", "column-count", + "column-gap", "box-shadow", "clip", "filter", "transform-origin", + "perspective-origin", "font-stretch", "letter-spacing", "word-spacing", + "text-decoration" ] + + class Keyword(object): def __init__(self, name, values, gecko_constant_prefix=None, extra_gecko_values=None, extra_servo_values=None): @@ -47,7 +94,7 @@ class Keyword(object): class Longhand(object): def __init__(self, style_struct, name, derived_from=None, keyword=None, predefined_type=None, custom_cascade=False, experimental=False, internal=False, - need_clone=False, gecko_ffi_name=None): + need_clone=False, gecko_ffi_name=None, animatable=None): self.name = name self.keyword = keyword self.predefined_type = predefined_type @@ -60,6 +107,10 @@ class Longhand(object): self.need_clone = need_clone self.gecko_ffi_name = gecko_ffi_name or "m" + self.camel_case self.derived_from = (derived_from or "").split() + if animatable is not None: + self.animatable = animatable + else: + self.animatable = is_known_animatable_property(name) and not is_not_supported_animatable_property(name) class Shorthand(object): diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs new file mode 100644 index 00000000000..6a637948972 --- /dev/null +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -0,0 +1,728 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use app_units::Au; +use bezier::Bezier; +use cssparser::{Color as CSSParserColor, Parser, RGBA, ToCss}; +use dom::{OpaqueNode, TRestyleDamage}; +use euclid::{Point2D, Size2D}; +use properties::longhands; +use properties::longhands::background_position::computed_value::T as BackgroundPosition; +use properties::longhands::background_size::computed_value::T as BackgroundSize; +use properties::longhands::border_spacing::computed_value::T as BorderSpacing; +use properties::longhands::clip::computed_value::ClipRect; +use properties::longhands::font_weight::computed_value::T as FontWeight; +use properties::longhands::line_height::computed_value::T as LineHeight; +use properties::longhands::text_shadow::computed_value::T as TextShadowList; +use properties::longhands::text_shadow::computed_value::TextShadow; +use properties::longhands::transform::computed_value::ComputedMatrix; +use properties::longhands::transform::computed_value::ComputedOperation as TransformOperation; +use properties::longhands::transform::computed_value::T as TransformList; +use properties::longhands::transition_property; +use properties::longhands::transition_timing_function::computed_value::StartEnd; +use properties::longhands::transition_timing_function::computed_value::TransitionTimingFunction; +use properties::longhands::vertical_align::computed_value::T as VerticalAlign; +use properties::longhands::visibility::computed_value::T as Visibility; +use properties::longhands::z_index::computed_value::T as ZIndex; +use properties::style_struct_traits::*; +use std::cmp::Ordering; +use std::fmt; +use std::iter::repeat; +use super::ComputedValues; +use values::computed::{Angle, LengthOrPercentageOrAuto, LengthOrPercentageOrNone}; +use values::computed::{BorderRadiusSize, LengthOrNone}; +use values::computed::{CalcLengthOrPercentage, Length, LengthOrPercentage, Time}; + +// NB: This needs to be here because it needs all the longhands generated +// beforehand. +#[derive(Copy, Clone, Debug, PartialEq, HeapSizeOf)] +pub enum TransitionProperty { + All, + % for prop in data.longhands: + % if prop.animatable: + ${prop.camel_case}, + % endif + % endfor +} + +impl TransitionProperty { + /// Iterates over each property that is not `All`. + pub fn each<F: FnMut(TransitionProperty) -> ()>(mut cb: F) { + % for prop in data.longhands: + % if prop.animatable: + cb(TransitionProperty::${prop.camel_case}); + % endif + % endfor + } + + pub fn parse(input: &mut Parser) -> Result<Self, ()> { + match_ignore_ascii_case! { try!(input.expect_ident()), + "all" => Ok(TransitionProperty::All), + % for prop in data.longhands: + % if prop.animatable: + "${prop.name}" => Ok(TransitionProperty::${prop.camel_case}), + % endif + % endfor + _ => Err(()) + } + } + +} + +impl ToCss for TransitionProperty { + fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + match *self { + TransitionProperty::All => dest.write_str("all"), + % for prop in data.longhands: + % if prop.animatable: + TransitionProperty::${prop.camel_case} => dest.write_str("${prop.name}"), + % endif + % endfor + } + } +} + +#[derive(Clone, Debug, PartialEq, HeapSizeOf)] +pub enum AnimatedProperty { + % for prop in data.longhands: + % if prop.animatable: + ${prop.camel_case}(longhands::${prop.ident}::computed_value::T, + longhands::${prop.ident}::computed_value::T), + % endif + % endfor +} + +impl AnimatedProperty { + pub fn does_animate(&self) -> bool { + match *self { + % for prop in data.longhands: + % if prop.animatable: + AnimatedProperty::${prop.camel_case}(ref from, ref to) => from != to, + % endif + % endfor + } + } + + pub fn update<C: ComputedValues>(&self, style: &mut C, progress: f64) { + match *self { + % for prop in data.longhands: + % if prop.animatable: + AnimatedProperty::${prop.camel_case}(ref from, ref to) => { + if let Some(value) = from.interpolate(to, progress) { + style.mutate_${prop.style_struct.ident.strip("_")}().set_${prop.ident}(value); + } + } + % endif + % endfor + } + } + + // NB: Transition properties need clone + pub fn from_transition_property<C: ComputedValues>(transition_property: &TransitionProperty, + old_style: &C, + new_style: &C) -> AnimatedProperty { + // TODO: Generalise this for GeckoLib, adding clone_xxx to the + // appropiate longhands. + let old_style = old_style.as_servo(); + let new_style = new_style.as_servo(); + match *transition_property { + TransitionProperty::All => panic!("Can't use TransitionProperty::All here."), + % for prop in data.longhands: + % if prop.animatable: + TransitionProperty::${prop.camel_case} => { + AnimatedProperty::${prop.camel_case}(old_style.get_${prop.style_struct.ident.strip("_")}().${prop.ident}.clone(), + new_style.get_${prop.style_struct.ident.strip("_")}().${prop.ident}.clone()) + } + % endif + % endfor + } + } +} + +pub trait Interpolate: Sized { + fn interpolate(&self, other: &Self, time: f64) -> Option<Self>; +} + +impl Interpolate for Au { + #[inline] + fn interpolate(&self, other: &Au, time: f64) -> Option<Au> { + Some(Au((self.0 as f64 + (other.0 as f64 - self.0 as f64) * time).round() as i32)) + } +} + +impl <T> Interpolate for Option<T> where T: Interpolate { + #[inline] + fn interpolate(&self, other: &Option<T>, time: f64) -> Option<Option<T>> { + match (self, other) { + (&Some(ref this), &Some(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(Some(value)) + }) + } + (_, _) => None + } + } +} + +impl Interpolate for f32 { + #[inline] + fn interpolate(&self, other: &f32, time: f64) -> Option<f32> { + Some(((*self as f64) + ((*other as f64) - (*self as f64)) * time) as f32) + } +} + +impl Interpolate for f64 { + #[inline] + fn interpolate(&self, other: &f64, time: f64) -> Option<f64> { + Some(*self + (*other - *self) * time) + } +} + +impl Interpolate for i32 { + #[inline] + fn interpolate(&self, other: &i32, time: f64) -> Option<i32> { + let a = *self as f64; + let b = *other as f64; + Some((a + (b - a) * time).round() as i32) + } +} + +impl Interpolate for Angle { + #[inline] + fn interpolate(&self, other: &Angle, time: f64) -> Option<Angle> { + self.radians().interpolate(&other.radians(), time).map(Angle) + } +} + +impl Interpolate for Visibility { + #[inline] + fn interpolate(&self, other: &Visibility, time: f64) + -> Option<Visibility> { + match (*self, *other) { + (Visibility::visible, _) | (_, Visibility::visible) => { + if time >= 0.0 && time <= 1.0 { + Some(Visibility::visible) + } else if time < 0.0 { + Some(*self) + } else { + Some(*other) + } + } + (_, _) => None, + } + } +} + +impl Interpolate for ZIndex { + #[inline] + fn interpolate(&self, other: &ZIndex, time: f64) + -> Option<ZIndex> { + match (*self, *other) { + (ZIndex::Number(ref this), + ZIndex::Number(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(ZIndex::Number(value)) + }) + } + (_, _) => None, + } + } +} + +impl<T: Interpolate + Clone> Interpolate for Size2D<T> { + #[inline] + fn interpolate(&self, other: &Self, time: f64) -> Option<Self> { + let width = match self.width.interpolate(&other.width, time) { + Some(width) => width, + None => return None, + }; + + let height = match self.height.interpolate(&other.height, time) { + Some(height) => height, + None => return None, + }; + Some(Size2D::new(width, height)) + } +} + +impl<T: Interpolate + Clone> Interpolate for Point2D<T> { + #[inline] + fn interpolate(&self, other: &Self, time: f64) -> Option<Self> { + let x = match self.x.interpolate(&other.x, time) { + Some(x) => x, + None => return None, + }; + + let y = match self.y.interpolate(&other.y, time) { + Some(y) => y, + None => return None, + }; + + Some(Point2D::new(x, y)) + } +} + +impl Interpolate for BorderRadiusSize { + #[inline] + fn interpolate(&self, other: &Self, time: f64) -> Option<Self> { + self.0.interpolate(&other.0, time).map(BorderRadiusSize) + } +} + +impl Interpolate for VerticalAlign { + #[inline] + fn interpolate(&self, other: &VerticalAlign, time: f64) + -> Option<VerticalAlign> { + match (*self, *other) { + (VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(ref this)), + VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(ref other))) => { + this.interpolate(other, time).and_then(|value| { + Some(VerticalAlign::LengthOrPercentage(LengthOrPercentage::Length(value))) + }) + } + (_, _) => None, + } + } +} + +impl Interpolate for BorderSpacing { + #[inline] + fn interpolate(&self, other: &BorderSpacing, time: f64) + -> Option<BorderSpacing> { + self.horizontal.interpolate(&other.horizontal, time).and_then(|horizontal| { + self.vertical.interpolate(&other.vertical, time).and_then(|vertical| { + Some(BorderSpacing { horizontal: horizontal, vertical: vertical }) + }) + }) + } +} + +impl Interpolate for RGBA { + #[inline] + fn interpolate(&self, other: &RGBA, time: f64) -> Option<RGBA> { + match (self.red.interpolate(&other.red, time), + self.green.interpolate(&other.green, time), + self.blue.interpolate(&other.blue, time), + self.alpha.interpolate(&other.alpha, time)) { + (Some(red), Some(green), Some(blue), Some(alpha)) => { + Some(RGBA { red: red, green: green, blue: blue, alpha: alpha }) + } + (_, _, _, _) => None + } + } +} + +impl Interpolate for CSSParserColor { + #[inline] + fn interpolate(&self, other: &Self, time: f64) -> Option<Self> { + match (*self, *other) { + (CSSParserColor::RGBA(ref this), CSSParserColor::RGBA(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(CSSParserColor::RGBA(value)) + }) + } + (_, _) => None, + } + } +} + +impl Interpolate for CalcLengthOrPercentage { + #[inline] + fn interpolate(&self, other: &CalcLengthOrPercentage, time: f64) + -> Option<CalcLengthOrPercentage> { + Some(CalcLengthOrPercentage { + length: self.length().interpolate(&other.length(), time), + percentage: self.percentage().interpolate(&other.percentage(), time), + }) + } +} + +impl Interpolate for LengthOrPercentage { + #[inline] + fn interpolate(&self, other: &LengthOrPercentage, time: f64) + -> Option<LengthOrPercentage> { + match (*self, *other) { + (LengthOrPercentage::Length(ref this), + LengthOrPercentage::Length(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(LengthOrPercentage::Length(value)) + }) + } + (LengthOrPercentage::Percentage(ref this), + LengthOrPercentage::Percentage(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(LengthOrPercentage::Percentage(value)) + }) + } + (this, other) => { + let this: CalcLengthOrPercentage = From::from(this); + let other: CalcLengthOrPercentage = From::from(other); + this.interpolate(&other, time).and_then(|value| { + Some(LengthOrPercentage::Calc(value)) + }) + } + } + } +} + +impl Interpolate for LengthOrPercentageOrAuto { + #[inline] + fn interpolate(&self, other: &LengthOrPercentageOrAuto, time: f64) + -> Option<LengthOrPercentageOrAuto> { + match (*self, *other) { + (LengthOrPercentageOrAuto::Length(ref this), + LengthOrPercentageOrAuto::Length(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(LengthOrPercentageOrAuto::Length(value)) + }) + } + (LengthOrPercentageOrAuto::Percentage(ref this), + LengthOrPercentageOrAuto::Percentage(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(LengthOrPercentageOrAuto::Percentage(value)) + }) + } + (LengthOrPercentageOrAuto::Auto, LengthOrPercentageOrAuto::Auto) => { + Some(LengthOrPercentageOrAuto::Auto) + } + (this, other) => { + let this: Option<CalcLengthOrPercentage> = From::from(this); + let other: Option<CalcLengthOrPercentage> = From::from(other); + this.interpolate(&other, time).unwrap_or(None).and_then(|value| { + Some(LengthOrPercentageOrAuto::Calc(value)) + }) + } + } + } +} + +impl Interpolate for LengthOrPercentageOrNone { + #[inline] + fn interpolate(&self, other: &LengthOrPercentageOrNone, time: f64) + -> Option<LengthOrPercentageOrNone> { + match (*self, *other) { + (LengthOrPercentageOrNone::Length(ref this), + LengthOrPercentageOrNone::Length(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(LengthOrPercentageOrNone::Length(value)) + }) + } + (LengthOrPercentageOrNone::Percentage(ref this), + LengthOrPercentageOrNone::Percentage(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(LengthOrPercentageOrNone::Percentage(value)) + }) + } + (LengthOrPercentageOrNone::None, LengthOrPercentageOrNone::None) => { + Some(LengthOrPercentageOrNone::None) + } + (_, _) => None, + } + } +} + +impl Interpolate for LineHeight { + #[inline] + fn interpolate(&self, other: &LineHeight, time: f64) + -> Option<LineHeight> { + match (*self, *other) { + (LineHeight::Length(ref this), + LineHeight::Length(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(LineHeight::Length(value)) + }) + } + (LineHeight::Number(ref this), + LineHeight::Number(ref other)) => { + this.interpolate(other, time).and_then(|value| { + Some(LineHeight::Number(value)) + }) + } + (LineHeight::Normal, LineHeight::Normal) => { + Some(LineHeight::Normal) + } + (_, _) => None, + } + } +} + +/// http://dev.w3.org/csswg/css-transitions/#animtype-font-weight +impl Interpolate for FontWeight { + #[inline] + fn interpolate(&self, other: &FontWeight, time: f64) + -> Option<FontWeight> { + let a = (*self as u32) as f64; + let b = (*other as u32) as f64; + let weight = a + (b - a) * time; + Some(if weight < 150. { + FontWeight::Weight100 + } else if weight < 250. { + FontWeight::Weight200 + } else if weight < 350. { + FontWeight::Weight300 + } else if weight < 450. { + FontWeight::Weight400 + } else if weight < 550. { + FontWeight::Weight500 + } else if weight < 650. { + FontWeight::Weight600 + } else if weight < 750. { + FontWeight::Weight700 + } else if weight < 850. { + FontWeight::Weight800 + } else { + FontWeight::Weight900 + }) + } +} + +impl Interpolate for ClipRect { + #[inline] + fn interpolate(&self, other: &ClipRect, time: f64) + -> Option<ClipRect> { + match (self.top.interpolate(&other.top, time), + self.right.interpolate(&other.right, time), + self.bottom.interpolate(&other.bottom, time), + self.left.interpolate(&other.left, time)) { + (Some(top), Some(right), Some(bottom), Some(left)) => { + Some(ClipRect { top: top, right: right, bottom: bottom, left: left }) + }, + (_, _, _, _) => None, + } + } +} + +impl Interpolate for BackgroundPosition { + #[inline] + fn interpolate(&self, other: &BackgroundPosition, time: f64) + -> Option<BackgroundPosition> { + match (self.horizontal.interpolate(&other.horizontal, time), + self.vertical.interpolate(&other.vertical, time)) { + (Some(horizontal), Some(vertical)) => { + Some(BackgroundPosition { horizontal: horizontal, vertical: vertical }) + }, + (_, _) => None, + } + } +} + +impl Interpolate for BackgroundSize { + fn interpolate(&self, other: &Self, time: f64) -> Option<Self> { + use properties::longhands::background_size::computed_value::ExplicitSize; + match (self, other) { + (&BackgroundSize::Explicit(ref me), &BackgroundSize::Explicit(ref other)) + => match (me.width.interpolate(&other.width, time), + me.height.interpolate(&other.height, time)) { + (Some(width), Some(height)) + => Some(BackgroundSize::Explicit( + ExplicitSize { width: width, height: height })), + _ => None, + }, + _ => None + } + } +} + +impl Interpolate for TextShadow { + #[inline] + fn interpolate(&self, other: &TextShadow, time: f64) + -> Option<TextShadow> { + match (self.offset_x.interpolate(&other.offset_x, time), + self.offset_y.interpolate(&other.offset_y, time), + self.blur_radius.interpolate(&other.blur_radius, time), + self.color.interpolate(&other.color, time)) { + (Some(offset_x), Some(offset_y), Some(blur_radius), Some(color)) => { + Some(TextShadow { offset_x: offset_x, offset_y: offset_y, blur_radius: blur_radius, color: color }) + }, + (_, _, _, _) => None, + } + } +} + +impl Interpolate for TextShadowList { + #[inline] + fn interpolate(&self, other: &TextShadowList, time: f64) + -> Option<TextShadowList> { + let zero = TextShadow { + offset_x: Au(0), + offset_y: Au(0), + blur_radius: Au(0), + color: CSSParserColor::RGBA(RGBA { + red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0 + }) + }; + + let interpolate_each = |(a, b): (&TextShadow, &TextShadow)| { + a.interpolate(b, time).unwrap() + }; + + Some(TextShadowList(match self.0.len().cmp(&other.0.len()) { + Ordering::Less => other.0.iter().chain(repeat(&zero)).zip(other.0.iter()).map(interpolate_each).collect(), + _ => self.0.iter().zip(other.0.iter().chain(repeat(&zero))).map(interpolate_each).collect(), + })) + } +} + +/// Check if it's possible to do a direct numerical interpolation +/// between these two transform lists. +/// http://dev.w3.org/csswg/css-transforms/#transform-transform-animation +fn can_interpolate_list(from_list: &[TransformOperation], + to_list: &[TransformOperation]) -> bool { + // Lists must be equal length + if from_list.len() != to_list.len() { + return false; + } + + // Each transform operation must match primitive type in other list + for (from, to) in from_list.iter().zip(to_list) { + match (from, to) { + (&TransformOperation::Matrix(..), &TransformOperation::Matrix(..)) | + (&TransformOperation::Skew(..), &TransformOperation::Skew(..)) | + (&TransformOperation::Translate(..), &TransformOperation::Translate(..)) | + (&TransformOperation::Scale(..), &TransformOperation::Scale(..)) | + (&TransformOperation::Rotate(..), &TransformOperation::Rotate(..)) | + (&TransformOperation::Perspective(..), &TransformOperation::Perspective(..)) => {} + _ => { + return false; + } + } + } + + true +} + +/// Interpolate two transform lists. +/// http://dev.w3.org/csswg/css-transforms/#interpolation-of-transforms +fn interpolate_transform_list(from_list: &[TransformOperation], + to_list: &[TransformOperation], + time: f64) -> TransformList { + let mut result = vec![]; + + if can_interpolate_list(from_list, to_list) { + for (from, to) in from_list.iter().zip(to_list) { + match (from, to) { + (&TransformOperation::Matrix(from), + &TransformOperation::Matrix(_to)) => { + // TODO(gw): Implement matrix decomposition and interpolation + result.push(TransformOperation::Matrix(from)); + } + (&TransformOperation::Skew(fx, fy), + &TransformOperation::Skew(tx, ty)) => { + let ix = fx.interpolate(&tx, time).unwrap(); + let iy = fy.interpolate(&ty, time).unwrap(); + result.push(TransformOperation::Skew(ix, iy)); + } + (&TransformOperation::Translate(fx, fy, fz), + &TransformOperation::Translate(tx, ty, tz)) => { + let ix = fx.interpolate(&tx, time).unwrap(); + let iy = fy.interpolate(&ty, time).unwrap(); + let iz = fz.interpolate(&tz, time).unwrap(); + result.push(TransformOperation::Translate(ix, iy, iz)); + } + (&TransformOperation::Scale(fx, fy, fz), + &TransformOperation::Scale(tx, ty, tz)) => { + let ix = fx.interpolate(&tx, time).unwrap(); + let iy = fy.interpolate(&ty, time).unwrap(); + let iz = fz.interpolate(&tz, time).unwrap(); + result.push(TransformOperation::Scale(ix, iy, iz)); + } + (&TransformOperation::Rotate(fx, fy, fz, fa), + &TransformOperation::Rotate(_tx, _ty, _tz, _ta)) => { + // TODO(gw): Implement matrix decomposition and interpolation + result.push(TransformOperation::Rotate(fx, fy, fz, fa)); + } + (&TransformOperation::Perspective(fd), + &TransformOperation::Perspective(_td)) => { + // TODO(gw): Implement matrix decomposition and interpolation + result.push(TransformOperation::Perspective(fd)); + } + _ => { + // This should be unreachable due to the can_interpolate_list() call. + unreachable!(); + } + } + } + } else { + // TODO(gw): Implement matrix decomposition and interpolation + result.extend_from_slice(from_list); + } + + TransformList(Some(result)) +} + +/// Build an equivalent 'identity transform function list' based +/// on an existing transform list. +/// http://dev.w3.org/csswg/css-transforms/#none-transform-animation +fn build_identity_transform_list(list: &[TransformOperation]) -> Vec<TransformOperation> { + let mut result = vec!(); + + for operation in list { + match *operation { + TransformOperation::Matrix(..) => { + let identity = ComputedMatrix::identity(); + result.push(TransformOperation::Matrix(identity)); + } + TransformOperation::Skew(..) => { + result.push(TransformOperation::Skew(Angle(0.0), Angle(0.0))); + } + TransformOperation::Translate(..) => { + result.push(TransformOperation::Translate(LengthOrPercentage::zero(), + LengthOrPercentage::zero(), + Au(0))); + } + TransformOperation::Scale(..) => { + result.push(TransformOperation::Scale(1.0, 1.0, 1.0)); + } + TransformOperation::Rotate(..) => { + result.push(TransformOperation::Rotate(0.0, 0.0, 1.0, Angle(0.0))); + } + TransformOperation::Perspective(..) => { + // http://dev.w3.org/csswg/css-transforms/#identity-transform-function + let identity = ComputedMatrix::identity(); + result.push(TransformOperation::Matrix(identity)); + } + } + } + + result +} + +impl Interpolate for LengthOrNone { + fn interpolate(&self, other: &Self, time: f64) -> Option<Self> { + match (*self, *other) { + (LengthOrNone::Length(ref len), LengthOrNone::Length(ref other)) => + len.interpolate(&other, time).map(LengthOrNone::Length), + _ => None, + } + } +} + +impl Interpolate for TransformList { + #[inline] + fn interpolate(&self, other: &TransformList, time: f64) -> Option<TransformList> { + // http://dev.w3.org/csswg/css-transforms/#interpolation-of-transforms + let result = match (&self.0, &other.0) { + (&Some(ref from_list), &Some(ref to_list)) => { + // Two lists of transforms + interpolate_transform_list(from_list, &to_list, time) + } + (&Some(ref from_list), &None) => { + // http://dev.w3.org/csswg/css-transforms/#none-transform-animation + let to_list = build_identity_transform_list(from_list); + interpolate_transform_list(from_list, &to_list, time) + } + (&None, &Some(ref to_list)) => { + // http://dev.w3.org/csswg/css-transforms/#none-transform-animation + let from_list = build_identity_transform_list(to_list); + interpolate_transform_list(&from_list, to_list, time) + } + _ => { + // http://dev.w3.org/csswg/css-transforms/#none-none-animation + TransformList(None) + } + }; + + Some(result) + } +} diff --git a/components/style/properties/longhand/box.mako.rs b/components/style/properties/longhand/box.mako.rs index 26014ee77b1..b2eed2758cb 100644 --- a/components/style/properties/longhand/box.mako.rs +++ b/components/style/properties/longhand/box.mako.rs @@ -525,170 +525,17 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= } </%helpers:longhand> -// TODO(pcwalton): Lots more properties. <%helpers:longhand name="transition-property"> - use self::computed_value::TransitionProperty; - pub use self::computed_value::SingleComputedValue as SingleSpecifiedValue; pub use self::computed_value::T as SpecifiedValue; pub mod computed_value { use cssparser::ToCss; use std::fmt; - - pub use self::TransitionProperty as SingleComputedValue; - - #[derive(Copy, Clone, Debug, PartialEq)] - #[cfg_attr(feature = "servo", derive(HeapSizeOf))] - pub enum TransitionProperty { - All, - BackgroundColor, - BackgroundPosition, - BorderBottomColor, - BorderBottomWidth, - BorderLeftColor, - BorderLeftWidth, - BorderRightColor, - BorderRightWidth, - BorderSpacing, - BorderTopColor, - BorderTopWidth, - Bottom, - Color, - Clip, - FontSize, - FontWeight, - Height, - Left, - LetterSpacing, - LineHeight, - MarginBottom, - MarginLeft, - MarginRight, - MarginTop, - MaxHeight, - MaxWidth, - MinHeight, - MinWidth, - Opacity, - OutlineColor, - OutlineWidth, - PaddingBottom, - PaddingLeft, - PaddingRight, - PaddingTop, - Right, - TextIndent, - TextShadow, - Top, - Transform, - VerticalAlign, - Visibility, - Width, - WordSpacing, - ZIndex, - } - - pub static ALL_TRANSITION_PROPERTIES: [TransitionProperty; 45] = [ - TransitionProperty::BackgroundColor, - TransitionProperty::BackgroundPosition, - TransitionProperty::BorderBottomColor, - TransitionProperty::BorderBottomWidth, - TransitionProperty::BorderLeftColor, - TransitionProperty::BorderLeftWidth, - TransitionProperty::BorderRightColor, - TransitionProperty::BorderRightWidth, - TransitionProperty::BorderSpacing, - TransitionProperty::BorderTopColor, - TransitionProperty::BorderTopWidth, - TransitionProperty::Bottom, - TransitionProperty::Color, - TransitionProperty::Clip, - TransitionProperty::FontSize, - TransitionProperty::FontWeight, - TransitionProperty::Height, - TransitionProperty::Left, - TransitionProperty::LetterSpacing, - TransitionProperty::LineHeight, - TransitionProperty::MarginBottom, - TransitionProperty::MarginLeft, - TransitionProperty::MarginRight, - TransitionProperty::MarginTop, - TransitionProperty::MaxHeight, - TransitionProperty::MaxWidth, - TransitionProperty::MinHeight, - TransitionProperty::MinWidth, - TransitionProperty::Opacity, - TransitionProperty::OutlineColor, - TransitionProperty::OutlineWidth, - TransitionProperty::PaddingBottom, - TransitionProperty::PaddingLeft, - TransitionProperty::PaddingRight, - TransitionProperty::PaddingTop, - TransitionProperty::Right, - TransitionProperty::TextIndent, - TransitionProperty::TextShadow, - TransitionProperty::Top, - TransitionProperty::Transform, - TransitionProperty::VerticalAlign, - TransitionProperty::Visibility, - TransitionProperty::Width, - TransitionProperty::WordSpacing, - TransitionProperty::ZIndex, - ]; - - impl ToCss for TransitionProperty { - fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - match *self { - TransitionProperty::All => dest.write_str("all"), - TransitionProperty::BackgroundColor => dest.write_str("background-color"), - TransitionProperty::BackgroundPosition => dest.write_str("background-position"), - TransitionProperty::BorderBottomColor => dest.write_str("border-bottom-color"), - TransitionProperty::BorderBottomWidth => dest.write_str("border-bottom-width"), - TransitionProperty::BorderLeftColor => dest.write_str("border-left-color"), - TransitionProperty::BorderLeftWidth => dest.write_str("border-left-width"), - TransitionProperty::BorderRightColor => dest.write_str("border-right-color"), - TransitionProperty::BorderRightWidth => dest.write_str("border-right-width"), - TransitionProperty::BorderSpacing => dest.write_str("border-spacing"), - TransitionProperty::BorderTopColor => dest.write_str("border-top-color"), - TransitionProperty::BorderTopWidth => dest.write_str("border-top-width"), - TransitionProperty::Bottom => dest.write_str("bottom"), - TransitionProperty::Color => dest.write_str("color"), - TransitionProperty::Clip => dest.write_str("clip"), - TransitionProperty::FontSize => dest.write_str("font-size"), - TransitionProperty::FontWeight => dest.write_str("font-weight"), - TransitionProperty::Height => dest.write_str("height"), - TransitionProperty::Left => dest.write_str("left"), - TransitionProperty::LetterSpacing => dest.write_str("letter-spacing"), - TransitionProperty::LineHeight => dest.write_str("line-height"), - TransitionProperty::MarginBottom => dest.write_str("margin-bottom"), - TransitionProperty::MarginLeft => dest.write_str("margin-left"), - TransitionProperty::MarginRight => dest.write_str("margin-right"), - TransitionProperty::MarginTop => dest.write_str("margin-top"), - TransitionProperty::MaxHeight => dest.write_str("max-height"), - TransitionProperty::MaxWidth => dest.write_str("max-width"), - TransitionProperty::MinHeight => dest.write_str("min-height"), - TransitionProperty::MinWidth => dest.write_str("min-width"), - TransitionProperty::Opacity => dest.write_str("opacity"), - TransitionProperty::OutlineColor => dest.write_str("outline-color"), - TransitionProperty::OutlineWidth => dest.write_str("outline-width"), - TransitionProperty::PaddingBottom => dest.write_str("padding-bottom"), - TransitionProperty::PaddingLeft => dest.write_str("padding-left"), - TransitionProperty::PaddingRight => dest.write_str("padding-right"), - TransitionProperty::PaddingTop => dest.write_str("padding-top"), - TransitionProperty::Right => dest.write_str("right"), - TransitionProperty::TextIndent => dest.write_str("text-indent"), - TransitionProperty::TextShadow => dest.write_str("text-shadow"), - TransitionProperty::Top => dest.write_str("top"), - TransitionProperty::Transform => dest.write_str("transform"), - TransitionProperty::VerticalAlign => dest.write_str("vertical-align"), - TransitionProperty::Visibility => dest.write_str("visibility"), - TransitionProperty::Width => dest.write_str("width"), - TransitionProperty::WordSpacing => dest.write_str("word-spacing"), - TransitionProperty::ZIndex => dest.write_str("z-index"), - } - } - } + // NB: Can't generate the type here because it needs all the longhands + // generated beforehand. + pub use properties::animated_properties::TransitionProperty; + pub use properties::animated_properties::TransitionProperty as SingleComputedValue; #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] @@ -715,61 +562,8 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= computed_value::T(Vec::new()) } - pub fn parse_one(input: &mut Parser) -> Result<SingleSpecifiedValue,()> { - match_ignore_ascii_case! { - try!(input.expect_ident()), - "all" => Ok(TransitionProperty::All), - "background-color" => Ok(TransitionProperty::BackgroundColor), - "background-position" => Ok(TransitionProperty::BackgroundPosition), - "border-bottom-color" => Ok(TransitionProperty::BorderBottomColor), - "border-bottom-width" => Ok(TransitionProperty::BorderBottomWidth), - "border-left-color" => Ok(TransitionProperty::BorderLeftColor), - "border-left-width" => Ok(TransitionProperty::BorderLeftWidth), - "border-right-color" => Ok(TransitionProperty::BorderRightColor), - "border-right-width" => Ok(TransitionProperty::BorderRightWidth), - "border-spacing" => Ok(TransitionProperty::BorderSpacing), - "border-top-color" => Ok(TransitionProperty::BorderTopColor), - "border-top-width" => Ok(TransitionProperty::BorderTopWidth), - "bottom" => Ok(TransitionProperty::Bottom), - "color" => Ok(TransitionProperty::Color), - "clip" => Ok(TransitionProperty::Clip), - "font-size" => Ok(TransitionProperty::FontSize), - "font-weight" => Ok(TransitionProperty::FontWeight), - "height" => Ok(TransitionProperty::Height), - "left" => Ok(TransitionProperty::Left), - "letter-spacing" => Ok(TransitionProperty::LetterSpacing), - "line-height" => Ok(TransitionProperty::LineHeight), - "margin-bottom" => Ok(TransitionProperty::MarginBottom), - "margin-left" => Ok(TransitionProperty::MarginLeft), - "margin-right" => Ok(TransitionProperty::MarginRight), - "margin-top" => Ok(TransitionProperty::MarginTop), - "max-height" => Ok(TransitionProperty::MaxHeight), - "max-width" => Ok(TransitionProperty::MaxWidth), - "min-height" => Ok(TransitionProperty::MinHeight), - "min-width" => Ok(TransitionProperty::MinWidth), - "opacity" => Ok(TransitionProperty::Opacity), - "outline-color" => Ok(TransitionProperty::OutlineColor), - "outline-width" => Ok(TransitionProperty::OutlineWidth), - "padding-bottom" => Ok(TransitionProperty::PaddingBottom), - "padding-left" => Ok(TransitionProperty::PaddingLeft), - "padding-right" => Ok(TransitionProperty::PaddingRight), - "padding-top" => Ok(TransitionProperty::PaddingTop), - "right" => Ok(TransitionProperty::Right), - "text-indent" => Ok(TransitionProperty::TextIndent), - "text-shadow" => Ok(TransitionProperty::TextShadow), - "top" => Ok(TransitionProperty::Top), - "transform" => Ok(TransitionProperty::Transform), - "vertical-align" => Ok(TransitionProperty::VerticalAlign), - "visibility" => Ok(TransitionProperty::Visibility), - "width" => Ok(TransitionProperty::Width), - "word-spacing" => Ok(TransitionProperty::WordSpacing), - "z-index" => Ok(TransitionProperty::ZIndex), - _ => Err(()) - } - } - pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> { - Ok(SpecifiedValue(try!(input.parse_comma_separated(parse_one)))) + Ok(SpecifiedValue(try!(input.parse_comma_separated(SingleSpecifiedValue::parse)))) } impl ToComputedValue for SpecifiedValue { @@ -790,9 +584,7 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= </%helpers:longhand> <%helpers:longhand name="animation-name" experimental="True"> - use cssparser::ToCss; - use std::borrow::Cow; - use std::fmt; + use values::computed::ComputedValueAsSpecified; pub mod computed_value { use cssparser::ToCss; @@ -818,26 +610,7 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= } } - // TODO: Use Cows? Probably more codegen work would be needed, and this - // could not be that worth it (animations arent *that* used). - #[derive(Debug, Clone, PartialEq, HeapSizeOf)] - pub struct SpecifiedValue(Vec<String>); - - impl ToCss for SpecifiedValue { - fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - if self.0.is_empty() { - return dest.write_str("none") - } - - for (i, name) in self.0.iter().enumerate() { - if i != 0 { - try!(dest.write_str(", ")); - } - try!(dest.write_str(&name)); - } - Ok(()) - } - } + pub use self::computed_value::T as SpecifiedValue; #[inline] pub fn get_initial_value() -> computed_value::T { @@ -845,27 +618,13 @@ ${helpers.single_keyword("overflow-x", "visible hidden scroll auto", need_clone= } pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> { + use std::borrow::Cow; Ok(SpecifiedValue(try!(input.parse_comma_separated(|input| { input.expect_ident().map(Cow::into_owned) })))) } - impl ToComputedValue for SpecifiedValue { - type ComputedValue = computed_value::T; - - #[inline] - fn to_computed_value<Cx: TContext>(&self, context: &Cx) -> computed_value::T { - let mut ret = vec![]; - if let Some(animations) = context.animations() { - for name in self.0.iter() { - if animations.contains_key(&**name) { - ret.push(name.clone()); - } - } - } - computed_value::T(ret) - } - } + impl ComputedValueAsSpecified for SpecifiedValue {} </%helpers:longhand> <%helpers:longhand name="animation-duration" experimental="True"> diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index 3df108e0e6a..26105138bcb 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -133,6 +133,10 @@ pub mod shorthands { <%include file="/shorthand/text.mako.rs" /> } +pub mod animated_properties { + <%include file="/helpers/animated_properties.mako.rs" /> +} + // TODO(SimonSapin): Convert this to a syntax extension rather than a Mako template. // Maybe submit for inclusion in libstd? @@ -1045,6 +1049,23 @@ impl PropertyDeclaration { PropertyDeclaration::Custom(_, _) => &[] } } + + /// Returns true if this property is one of the animable properties, false + /// otherwise. + pub fn is_animatable(&self) -> bool { + match *self { + % for property in data.longhands: + PropertyDeclaration::${property.camel_case}(_) => { + % if property.animatable: + true + % else: + false + % endif + } + % endfor + PropertyDeclaration::Custom(..) => false, + } + } } pub mod style_struct_traits { @@ -1595,14 +1616,12 @@ fn cascade_with_cached_declarations<C: ComputedValues>( parent_style: &C, cached_style: &C, custom_properties: Option<Arc<::custom_properties::ComputedValuesMap>>, - animations: Option<<&HashMap<String, Vec<Keyframe>>>, mut error_reporter: StdBox<ParseErrorReporter + Send>) -> C { let mut context = computed::Context { is_root_element: false, viewport_size: viewport_size, inherited_style: parent_style, - animations: animations, style: C::new( custom_properties, shareable, @@ -1742,7 +1761,6 @@ pub fn cascade<C: ComputedValues>( shareable: bool, parent_style: Option<<&C>, cached_style: Option<<&C>, - animations: Option<<&HashMap<String, Vec<Keyframe>>>, mut error_reporter: StdBox<ParseErrorReporter + Send>) -> (C, bool) { use properties::style_struct_traits::{Border, Box, Font, Outline}; @@ -1778,7 +1796,6 @@ pub fn cascade<C: ComputedValues>( parent_style, cached_style, custom_properties, - animations, error_reporter); return (style, false) } @@ -1787,7 +1804,6 @@ pub fn cascade<C: ComputedValues>( is_root_element: is_root_element, viewport_size: viewport_size, inherited_style: inherited_style, - animations: animations, style: C::new( custom_properties, shareable, diff --git a/components/style/properties/shorthand/box.mako.rs b/components/style/properties/shorthand/box.mako.rs index 48b49ca4811..27e923ba1aa 100644 --- a/components/style/properties/shorthand/box.mako.rs +++ b/components/style/properties/shorthand/box.mako.rs @@ -32,7 +32,7 @@ let (mut timing_function, mut delay) = (None, None); loop { if property.is_none() { - if let Ok(value) = input.try(|input| transition_property::parse_one(input)) { + if let Ok(value) = input.try(transition_property::SingleSpecifiedValue::parse) { property = Some(value); continue } diff --git a/components/style/selector_matching.rs b/components/style/selector_matching.rs index a3fe67b1b75..5b2901d70ef 100644 --- a/components/style/selector_matching.rs +++ b/components/style/selector_matching.rs @@ -8,6 +8,7 @@ use dom::PresentationalHintsSynthetizer; use element_state::*; use error_reporting::StdoutErrorReporter; use keyframes::Keyframe; +use keyframes::ComputedKeyframesAnimation; use media_queries::{Device, MediaType}; use parser::ParserContextExtraData; use properties::{self, PropertyDeclaration, PropertyDeclarationBlock}; @@ -128,7 +129,7 @@ pub struct Stylist<Impl: SelectorImplExt> { BuildHasherDefault<::fnv::FnvHasher>>, /// A map with all the animations indexed by name. - animations: HashMap<String, Vec<Keyframe>>, + animations: HashMap<String, ComputedKeyframesAnimation<Impl::ComputedValues>>, /// Applicable declarations for a given non-eagerly cascaded pseudo-element. /// These are eagerly computed once, and then used to resolve the new @@ -252,10 +253,10 @@ impl<Impl: SelectorImplExt> Stylist<Impl> { self.rules_source_order = rules_source_order; } CSSRule::Keyframes(ref keyframes_rule) => { - // TODO: This *might* be optimised converting the - // Vec<Keyframe> into something like Arc<[Keyframe]>. - self.animations.insert(keyframes_rule.name.clone(), - keyframes_rule.keyframes.clone()); + if let Some(computed) = ComputedKeyframesAnimation::from_keyframes(&keyframes_rule.keyframes) { + self.animations.insert(keyframes_rule.name.clone(), + computed); + } } // We don't care about any other rule. _ => {} @@ -289,7 +290,7 @@ impl<Impl: SelectorImplExt> Stylist<Impl> { properties::cascade(self.device.au_viewport_size(), &declarations, false, parent.map(|p| &**p), - None, None, + None, Box::new(StdoutErrorReporter)); Some(Arc::new(computed)) } else { @@ -322,7 +323,7 @@ impl<Impl: SelectorImplExt> Stylist<Impl> { let (computed, _) = properties::cascade(self.device.au_viewport_size(), &declarations, false, - Some(&**parent), None, None, + Some(&**parent), None, Box::new(StdoutErrorReporter)); Some(Arc::new(computed)) } @@ -458,7 +459,7 @@ impl<Impl: SelectorImplExt> Stylist<Impl> { } #[inline] - pub fn animations(&self) -> &HashMap<String, Vec<Keyframe>> { + pub fn animations(&self) -> &HashMap<String, ComputedKeyframesAnimation<Impl::ComputedValues>> { &self.animations } } diff --git a/components/style/values.rs b/components/style/values.rs index 0a62485a185..5dea6f3cb7a 100644 --- a/components/style/values.rs +++ b/components/style/values.rs @@ -1391,16 +1391,13 @@ pub mod specified { pub fn parse_border_radius(input: &mut Parser) -> Result<BorderRadiusSize, ()> { input.try(BorderRadiusSize::parse).or_else(|()| { match_ignore_ascii_case! { try!(input.expect_ident()), - "thin" => - Ok(BorderRadiusSize::circle( - LengthOrPercentage::Length(Length::from_px(1.)))), - "medium" => - Ok(BorderRadiusSize::circle( - LengthOrPercentage::Length(Length::from_px(3.)))), - "thick" => - Ok(BorderRadiusSize::circle( - LengthOrPercentage::Length(Length::from_px(5.)))), - _ => Err(()) + "thin" => Ok(BorderRadiusSize::circle( + LengthOrPercentage::Length(Length::from_px(1.)))), + "medium" => Ok(BorderRadiusSize::circle( + LengthOrPercentage::Length(Length::from_px(3.)))), + "thick" => Ok(BorderRadiusSize::circle( + LengthOrPercentage::Length(Length::from_px(5.)))), + _ => Err(()) } }) } @@ -1561,10 +1558,8 @@ pub mod specified { pub mod computed { use app_units::Au; use euclid::size::Size2D; - use keyframes::Keyframe; use properties::ComputedValues; use properties::style_struct_traits::Font; - use std::collections::HashMap; use std::fmt; use super::LocalToCss; use super::specified::AngleOrCorner; @@ -1580,7 +1575,6 @@ pub mod computed { fn inherited_style(&self) -> &Self::ConcreteComputedValues; fn style(&self) -> &Self::ConcreteComputedValues; fn mutate_style(&mut self) -> &mut Self::ConcreteComputedValues; - fn animations(&self) -> Option<&HashMap<String, Vec<Keyframe>>>; } pub struct Context<'a, C: ComputedValues> { @@ -1588,7 +1582,6 @@ pub mod computed { pub viewport_size: Size2D<Au>, pub inherited_style: &'a C, - pub animations: Option<&'a HashMap<String, Vec<Keyframe>>>, /// Values access through this need to be in the properties "computed early": /// color, text-decoration, font-size, display, position, float, border-*-style, outline-style pub style: C, @@ -1601,7 +1594,6 @@ pub mod computed { fn inherited_style(&self) -> &C { &self.inherited_style } fn style(&self) -> &C { &self.style } fn mutate_style(&mut self) -> &mut C { &mut self.style } - fn animations(&self) -> Option<&HashMap<String, Vec<Keyframe>>> { self.animations } } pub trait ToComputedValue { @@ -1757,7 +1749,7 @@ pub mod computed { } - #[derive(PartialEq, Clone, Copy)] + #[derive(Debug, PartialEq, Clone, Copy)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct BorderRadiusSize(pub Size2D<LengthOrPercentage>); diff --git a/components/style/viewport.rs b/components/style/viewport.rs index 1707721a362..06411425922 100644 --- a/components/style/viewport.rs +++ b/components/style/viewport.rs @@ -648,7 +648,6 @@ impl MaybeNew for ViewportConstraints { viewport_size: initial_viewport, inherited_style: ServoComputedValues::initial_values(), style: ServoComputedValues::initial_values().clone(), - animations: None, }; // DEVICE-ADAPT § 9.3 Resolving 'extend-to-zoom' |