style: Refactor the @keyframes parsing and add adequate computation for it.

This commit is contained in:
Emilio Cobos Álvarez 2016-06-18 15:04:16 +02:00
parent 6a362ae8e8
commit 058bfb39ae
No known key found for this signature in database
GPG key ID: 056B727BB9C1027C
4 changed files with 103 additions and 43 deletions

View file

@ -3,9 +3,11 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use cssparser::{Parser, Delimiter};
use selectors::matching::DeclarationBlock;
use parser::ParserContext;
use properties::{ComputedValues, PropertyDeclarationBlock, parse_property_declaration_list};
use properties::animated_properties::AnimatedProperty;
use properties::{ComputedValues, PropertyDeclaration, PropertyDeclarationBlock, parse_property_declaration_list};
use properties::animated_properties::TransitionProperty;
use std::sync::Arc;
/// Parses a keyframes list, like:
/// 0%, 50% {
@ -30,9 +32,19 @@ pub fn parse_keyframe_list(context: &ParserContext, input: &mut Parser) -> Resul
/// A number from 1 to 100, indicating the percentage of the animation where
/// this keyframe should run.
#[derive(Debug, Copy, Clone, PartialEq, HeapSizeOf)]
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, HeapSizeOf)]
pub struct KeyframePercentage(f32);
impl ::std::cmp::Ord for KeyframePercentage {
#[inline]
fn cmp(&self, other: &Self) -> ::std::cmp::Ordering {
// We know we have a number from 0 to 1, so unwrap() here is safe.
self.0.partial_cmp(&other.0).unwrap()
}
}
impl ::std::cmp::Eq for KeyframePercentage { }
impl KeyframePercentage {
#[inline]
pub fn new(value: f32) -> KeyframePercentage {
@ -73,7 +85,7 @@ impl KeyframeSelector {
#[derive(Debug, Clone, PartialEq, HeapSizeOf)]
pub struct Keyframe {
pub selector: KeyframeSelector,
pub declarations: PropertyDeclarationBlock,
pub declarations: Arc<Vec<PropertyDeclaration>>,
}
impl Keyframe {
@ -89,25 +101,33 @@ impl Keyframe {
Ok(parse_property_declaration_list(context, input))
}).unwrap();
// NB: Other browsers seem to ignore important declarations in keyframe
// animations too.
Ok(Keyframe {
selector: selector,
declarations: declarations,
declarations: declarations.normal,
})
}
}
/// A single step from a keyframe animation.
#[derive(Debug, Clone, PartialEq, HeapSizeOf)]
pub struct ComputedKeyframesStep<C: ComputedValues> {
pub struct KeyframesStep {
/// 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,
/// Declarations that will determine the final style during the step.
declarations: Arc<Vec<PropertyDeclaration>>,
}
impl KeyframesStep {
fn new(percentage: KeyframePercentage,
declarations: Arc<Vec<PropertyDeclaration>>) -> Self {
KeyframesStep {
duration_percentage: percentage,
declarations: declarations,
}
}
}
/// This structure represents a list of animation steps computed from the list
@ -115,19 +135,32 @@ pub struct ComputedKeyframesStep<C: ComputedValues> {
///
/// It only takes into account animable properties.
#[derive(Debug, Clone, PartialEq, HeapSizeOf)]
pub struct ComputedKeyframesAnimation<C: ComputedValues> {
steps: Vec<ComputedKeyframesStep<C>>,
pub struct KeyframesAnimation {
steps: Vec<KeyframesStep>,
/// The properties that change in this animation.
properties_changed: Vec<AnimatedProperty>,
properties_changed: Vec<TransitionProperty>,
}
fn get_animated_properties(keyframe: &Keyframe) -> Vec<AnimatedProperty> {
// TODO
vec![]
/// Get all the animated properties in a keyframes animation. Note that it's not
/// defined what happens when a property is not on a keyframe, so we only peek
/// the props of the first one.
///
/// In practice, browsers seem to try to do their best job at it, so we might
/// want to go through all the actual keyframes and deduplicate properties.
fn get_animated_properties(keyframe: &Keyframe) -> Vec<TransitionProperty> {
let mut ret = vec![];
// NB: declarations are already deduplicated, so we don't have to check for
// it here.
for declaration in keyframe.declarations.iter() {
if let Some(property) = TransitionProperty::from_declaration(&declaration) {
ret.push(property);
}
}
ret
}
impl<C: ComputedValues> ComputedKeyframesAnimation<C> {
pub fn from_keyframes(keyframes: &[Keyframe]) -> Option<ComputedKeyframesAnimation<C>> {
impl KeyframesAnimation {
pub fn from_keyframes(keyframes: &[Keyframe]) -> Option<Self> {
debug_assert!(keyframes.len() > 1);
let mut steps = vec![];
@ -135,18 +168,37 @@ impl<C: ComputedValues> ComputedKeyframesAnimation<C> {
// 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() {
for percentage in keyframe.selector.0.iter() {
steps.push(KeyframesStep::new(*percentage,
keyframe.declarations.clone()));
}
}
Some(ComputedKeyframesAnimation {
steps.sort_by_key(|step| step.duration_percentage);
if steps[0].duration_percentage != KeyframePercentage(0.0) {
// TODO: we could just insert a step from 0 and without declarations
// so we won't animate at the beginning. Seems like what other
// engines do, but might be a bit tricky so I'd rather leave it as a
// follow-up.
return None;
}
let mut remaining = 1.0;
let mut last_step_end = 0.0;
debug_assert!(steps.len() > 1);
for current_step in &mut steps[1..] {
let new_duration_percentage = KeyframePercentage(current_step.duration_percentage.0 - last_step_end);
last_step_end = current_step.duration_percentage.0;
current_step.duration_percentage = new_duration_percentage;
}
Some(KeyframesAnimation {
steps: steps,
properties_changed: animated_properties,
})

View file

@ -7,6 +7,7 @@ use bezier::Bezier;
use cssparser::{Color as CSSParserColor, Parser, RGBA, ToCss};
use dom::{OpaqueNode, TRestyleDamage};
use euclid::{Point2D, Size2D};
use properties::PropertyDeclaration;
use properties::longhands;
use properties::longhands::background_position::computed_value::T as BackgroundPosition;
use properties::longhands::background_size::computed_value::T as BackgroundSize;
@ -68,6 +69,17 @@ impl TransitionProperty {
}
}
pub fn from_declaration(declaration: &PropertyDeclaration) -> Option<Self> {
match *declaration {
% for prop in data.longhands:
% if prop.animatable:
PropertyDeclaration::${prop.camel_case}(..)
=> Some(TransitionProperty::${prop.camel_case}),
% endif
% endfor
_ => None,
}
}
}
impl ToCss for TransitionProperty {

View file

@ -8,7 +8,7 @@ use dom::PresentationalHintsSynthetizer;
use element_state::*;
use error_reporting::StdoutErrorReporter;
use keyframes::Keyframe;
use keyframes::ComputedKeyframesAnimation;
use keyframes::KeyframesAnimation;
use media_queries::{Device, MediaType};
use parser::ParserContextExtraData;
use properties::{self, PropertyDeclaration, PropertyDeclarationBlock};
@ -129,7 +129,7 @@ pub struct Stylist<Impl: SelectorImplExt> {
BuildHasherDefault<::fnv::FnvHasher>>,
/// A map with all the animations indexed by name.
animations: HashMap<String, ComputedKeyframesAnimation<Impl::ComputedValues>>,
animations: HashMap<String, KeyframesAnimation>,
/// Applicable declarations for a given non-eagerly cascaded pseudo-element.
/// These are eagerly computed once, and then used to resolve the new
@ -253,9 +253,11 @@ impl<Impl: SelectorImplExt> Stylist<Impl> {
self.rules_source_order = rules_source_order;
}
CSSRule::Keyframes(ref keyframes_rule) => {
if let Some(computed) = ComputedKeyframesAnimation::from_keyframes(&keyframes_rule.keyframes) {
debug!("Found valid keyframes rule: {:?}", keyframes_rule);
if let Some(animation) = KeyframesAnimation::from_keyframes(&keyframes_rule.keyframes) {
debug!("Found valid keyframe animation: {:?}", animation);
self.animations.insert(keyframes_rule.name.clone(),
computed);
animation);
}
}
// We don't care about any other rule.
@ -459,7 +461,7 @@ impl<Impl: SelectorImplExt> Stylist<Impl> {
}
#[inline]
pub fn animations(&self) -> &HashMap<String, ComputedKeyframesAnimation<Impl::ComputedValues>> {
pub fn animations(&self) -> &HashMap<String, KeyframesAnimation> {
&self.animations
}
}

View file

@ -156,24 +156,18 @@ fn test_parse_stylesheet() {
Keyframe {
selector: KeyframeSelector::new_for_unit_testing(
vec![KeyframePercentage::new(0.)]),
declarations: PropertyDeclarationBlock {
normal: Arc::new(vec![
PropertyDeclaration::Width(DeclaredValue::Value(
LengthOrPercentageOrAuto::Percentage(Percentage(0.)))),
]),
important: Arc::new(vec![]),
}
declarations: Arc::new(vec![
PropertyDeclaration::Width(DeclaredValue::Value(
LengthOrPercentageOrAuto::Percentage(Percentage(0.)))),
]),
},
Keyframe {
selector: KeyframeSelector::new_for_unit_testing(
vec![KeyframePercentage::new(1.)]),
declarations: PropertyDeclarationBlock {
normal: Arc::new(vec![
PropertyDeclaration::Width(DeclaredValue::Value(
LengthOrPercentageOrAuto::Percentage(Percentage(1.)))),
]),
important: Arc::new(vec![]),
}
declarations: Arc::new(vec![
PropertyDeclaration::Width(DeclaredValue::Value(
LengthOrPercentageOrAuto::Percentage(Percentage(1.)))),
]),
},
]
})