Bug 1643201 - servo: Remove AnimatedProperty.

This removes an extra layer of abstraction and allows Servo to share
more code with Gecko. In addition, we will need to handle raw
`AnimationValue` structs soon in order to fully implement "faster
reversing of interrupted transitions."

Depends on D78192

Differential Revision: https://phabricator.services.mozilla.com/D78193
This commit is contained in:
Martin Robinson 2020-06-04 00:34:34 +00:00
Родитель 67b71f76f3
Коммит fb83423da6
2 изменённых файлов: 89 добавлений и 222 удалений

Просмотреть файл

@ -11,7 +11,7 @@ use crate::bezier::Bezier;
use crate::context::SharedStyleContext;
use crate::dom::{OpaqueNode, TElement, TNode};
use crate::font_metrics::FontMetricsProvider;
use crate::properties::animated_properties::AnimatedProperty;
use crate::properties::animated_properties::AnimationValue;
use crate::properties::longhands::animation_direction::computed_value::single_value::T as AnimationDirection;
use crate::properties::longhands::animation_play_state::computed_value::single_value::T as AnimationPlayState;
use crate::properties::{self, CascadeMode, ComputedValues, LonghandId};
@ -19,6 +19,7 @@ use crate::properties::{self, CascadeMode, ComputedValues, LonghandId};
use crate::properties::LonghandIdSet;
use crate::stylesheets::keyframes_rule::{KeyframesAnimation, KeyframesStep, KeyframesStepValue};
use crate::stylesheets::Origin;
use crate::values::animated::{Animate, Procedure};
use crate::values::computed::Time;
use crate::values::computed::TimingFunction;
use crate::values::generics::box_::AnimationIterationCount;
@ -30,8 +31,11 @@ use std::fmt;
/// Represents an animation for a given property.
#[derive(Clone, Debug, MallocSizeOf)]
pub struct PropertyAnimation {
/// An `AnimatedProperty` that this `PropertyAnimation` corresponds to.
property: AnimatedProperty,
/// The value we are animating from.
from: AnimationValue,
/// The value we are animating to.
to: AnimationValue,
/// The timing function of this `PropertyAnimation`.
timing_function: TimingFunction,
@ -43,12 +47,8 @@ pub struct PropertyAnimation {
impl PropertyAnimation {
/// Returns the given property longhand id.
pub fn property_id(&self) -> LonghandId {
self.property.id()
}
/// Returns the given property name.
pub fn property_name(&self) -> &'static str {
self.property.name()
debug_assert_eq!(self.from.id(), self.to.id());
self.from.id()
}
fn from_longhand(
@ -58,30 +58,33 @@ impl PropertyAnimation {
old_style: &ComputedValues,
new_style: &ComputedValues,
) -> Option<PropertyAnimation> {
let animated_property = AnimatedProperty::from_longhand(longhand, old_style, new_style)?;
// FIXME(emilio): Handle the case where old_style and new_style's writing mode differ.
let longhand = longhand.to_physical(new_style.writing_mode);
let from = AnimationValue::from_computed_values(longhand, old_style)?;
let to = AnimationValue::from_computed_values(longhand, new_style)?;
let duration = duration.seconds() as f64;
let property_animation = PropertyAnimation {
property: animated_property,
timing_function,
duration: duration.seconds() as f64,
};
if property_animation.does_animate() {
Some(property_animation)
} else {
None
if from == to || duration == 0.0 {
return None;
}
Some(PropertyAnimation {
from,
to,
timing_function,
duration,
})
}
/// Update the given animation at a given point of progress.
pub fn update(&self, style: &mut ComputedValues, time: f64) {
/// The output of the timing function given the progress ration of this animation.
fn timing_function_output(&self, progress: f64) -> f64 {
let epsilon = 1. / (200. * self.duration);
let progress = match self.timing_function {
match self.timing_function {
GenericTimingFunction::CubicBezier { x1, y1, x2, y2 } => {
Bezier::new(x1, y1, x2, y2).solve(time, epsilon)
Bezier::new(x1, y1, x2, y2).solve(progress, epsilon)
},
GenericTimingFunction::Steps(steps, pos) => {
let mut current_step = (time * (steps as f64)).floor() as i32;
let mut current_step = (progress * (steps as f64)).floor() as i32;
if pos == StepPosition::Start ||
pos == StepPosition::JumpStart ||
@ -96,7 +99,7 @@ impl PropertyAnimation {
// (i.e. Treat before_flag is unset,)
// https://drafts.csswg.org/css-easing/#step-timing-function-algo
if time >= 0.0 && current_step < 0 {
if progress >= 0.0 && current_step < 0 {
current_step = 0;
}
@ -109,7 +112,7 @@ impl PropertyAnimation {
StepPosition::End => steps,
};
if time <= 1.0 && current_step > jumps {
if progress <= 1.0 && current_step > jumps {
current_step = jumps;
}
@ -117,22 +120,19 @@ impl PropertyAnimation {
},
GenericTimingFunction::Keyword(keyword) => {
let (x1, x2, y1, y2) = keyword.to_bezier();
Bezier::new(x1, x2, y1, y2).solve(time, epsilon)
Bezier::new(x1, x2, y1, y2).solve(progress, epsilon)
},
}
}
/// Update the given animation at a given point of progress.
fn update(&self, style: &mut ComputedValues, progress: f64) {
let procedure = Procedure::Interpolate {
progress: self.timing_function_output(progress),
};
self.property.update(style, progress);
}
#[inline]
fn does_animate(&self) -> bool {
self.property.does_animate() && self.duration != 0.0
}
/// Whether this animation has the same end value as another one.
#[inline]
pub fn has_the_same_end_value_as(&self, other: &Self) -> bool {
self.property.has_the_same_end_value_as(&other.property)
if let Ok(new_value) = self.from.animate(&self.to, procedure) {
new_value.set_in_style_for_servo(style);
}
}
}
@ -487,42 +487,23 @@ impl Animation {
);
let mut new_style = (*style).clone();
let mut update_style_for_longhand = |longhand| {
let from = AnimationValue::from_computed_values(longhand, &from_style)?;
let to = AnimationValue::from_computed_values(longhand, &target_style)?;
PropertyAnimation {
from,
to,
timing_function,
duration: relative_duration as f64,
}
.update(&mut new_style, relative_progress);
None::<()>
};
for property in self.keyframes_animation.properties_changed.iter() {
debug!(
"Animation::update_style: scanning prop {:?} for animation \"{}\"",
property, self.name
);
let animation = PropertyAnimation::from_longhand(
property,
timing_function,
Time::from_seconds(relative_duration as f32),
&from_style,
&target_style,
);
match animation {
Some(property_animation) => {
debug!(
"Animation::update_style: got property animation for prop {:?}",
property
);
debug!("Animation::update_style: {:?}", property_animation);
property_animation.update(&mut new_style, relative_progress);
},
None => {
debug!(
"Animation::update_style: property animation {:?} not animating",
property
);
},
}
update_style_for_longhand(property);
}
debug!(
"Animation::update_style: got style change in animation \"{}\"",
self.name
);
*style = new_style;
}
}
@ -574,12 +555,9 @@ impl Transition {
/// Whether this animation has the same end value as another one.
#[inline]
fn has_same_end_value(&self, other_animation: &PropertyAnimation) -> bool {
if self.state == AnimationState::Canceled {
return false;
}
self.property_animation
.has_the_same_end_value_as(other_animation)
fn progress(&self, now: f64) -> f64 {
let progress = (now - self.start_time) / (self.property_animation.duration);
progress.min(1.0)
}
/// Update a style to the value specified by this `Transition` given a `SharedStyleContext`.
@ -589,9 +567,7 @@ impl Transition {
return;
}
let now = context.current_time_for_animations;
let progress = (now - self.start_time) / (self.property_animation.duration);
let progress = progress.min(1.0);
let progress = self.progress(context.current_time_for_animations);
if progress >= 0.0 {
self.property_animation.update(style, progress);
}
@ -783,7 +759,8 @@ pub fn start_transitions_if_applicable(
) -> LonghandIdSet {
// If the style of this element is display:none, then we don't start any transitions
// and we cancel any currently running transitions by returning an empty LonghandIdSet.
if new_style.get_box().clone_display().is_none() {
let box_style = new_style.get_box();
if box_style.clone_display().is_none() {
return LonghandIdSet::new();
}
@ -798,12 +775,8 @@ pub fn start_transitions_if_applicable(
let property_animation = match PropertyAnimation::from_longhand(
transition.longhand_id,
new_style
.get_box()
.transition_timing_function_mod(transition.index),
new_style
.get_box()
.transition_duration_mod(transition.index),
box_style.transition_timing_function_mod(transition.index),
box_style.transition_duration_mod(transition.index),
old_style,
new_style,
) {
@ -813,12 +786,13 @@ pub fn start_transitions_if_applicable(
// Per [1], don't trigger a new transition if the end state for that
// transition is the same as that of a transition that's running or
// completed.
// completed. We don't take into account any canceled animations.
// [1]: https://drafts.csswg.org/css-transitions/#starting
if animation_state
.transitions
.iter()
.any(|transition| transition.has_same_end_value(&property_animation))
.filter(|transition| transition.state != AnimationState::Canceled)
.any(|transition| transition.property_animation.to == property_animation.to)
{
continue;
}

Просмотреть файл

@ -57,132 +57,6 @@ impl From<nsCSSPropertyID> for TransitionProperty {
}
}
/// An animated property interpolation between two computed values for that
/// property.
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
pub enum AnimatedProperty {
% for prop in data.longhands:
% if prop.animatable and not prop.logical:
<%
value_type = "longhands::{}::computed_value::T".format(prop.ident)
if not prop.is_animatable_with_computed_value:
value_type = "<{} as ToAnimatedValue>::AnimatedValue".format(value_type)
%>
/// ${prop.name}
${prop.camel_case}(${value_type}, ${value_type}),
% endif
% endfor
}
impl AnimatedProperty {
/// Get the id of the property we're animating.
pub fn id(&self) -> LonghandId {
match *self {
% for prop in data.longhands:
% if prop.animatable and not prop.logical:
AnimatedProperty::${prop.camel_case}(..) => LonghandId::${prop.camel_case},
% endif
% endfor
}
}
/// Get the name of this property.
pub fn name(&self) -> &'static str {
self.id().name()
}
/// Whether this interpolation does animate, that is, whether the start and
/// end values are different.
pub fn does_animate(&self) -> bool {
match *self {
% for prop in data.longhands:
% if prop.animatable and not prop.logical:
AnimatedProperty::${prop.camel_case}(ref from, ref to) => from != to,
% endif
% endfor
}
}
/// Whether an animated property has the same end value as another.
pub fn has_the_same_end_value_as(&self, other: &Self) -> bool {
match (self, other) {
% for prop in data.longhands:
% if prop.animatable and not prop.logical:
(&AnimatedProperty::${prop.camel_case}(_, ref this_end_value),
&AnimatedProperty::${prop.camel_case}(_, ref other_end_value)) => {
this_end_value == other_end_value
}
% endif
% endfor
_ => false,
}
}
/// Update `style` with the proper computed style corresponding to this
/// animation at `progress`.
#[cfg_attr(feature = "gecko", allow(unused))]
pub fn update(&self, style: &mut ComputedValues, progress: f64) {
#[cfg(feature = "servo")]
{
match *self {
% for prop in data.longhands:
% if prop.animatable and not prop.logical:
AnimatedProperty::${prop.camel_case}(ref from, ref to) => {
// https://drafts.csswg.org/web-animations/#discrete-animation-type
% if prop.animation_value_type == "discrete":
let value = if progress < 0.5 { from.clone() } else { to.clone() };
% else:
let value = match from.animate(to, Procedure::Interpolate { progress }) {
Ok(value) => value,
Err(()) => return,
};
% endif
% if not prop.is_animatable_with_computed_value:
let value: longhands::${prop.ident}::computed_value::T =
ToAnimatedValue::from_animated_value(value);
% endif
style.mutate_${prop.style_struct.name_lower}().set_${prop.ident}(value);
}
% endif
% endfor
}
}
}
/// Get an animatable value from a transition-property, an old style, and a
/// new style.
pub fn from_longhand(
property: LonghandId,
old_style: &ComputedValues,
new_style: &ComputedValues,
) -> Option<AnimatedProperty> {
// FIXME(emilio): Handle the case where old_style and new_style's
// writing mode differ.
let property = property.to_physical(new_style.writing_mode);
Some(match property {
% for prop in data.longhands:
% if prop.animatable and not prop.logical:
LonghandId::${prop.camel_case} => {
let old_computed = old_style.clone_${prop.ident}();
let new_computed = new_style.clone_${prop.ident}();
AnimatedProperty::${prop.camel_case}(
% if prop.is_animatable_with_computed_value:
old_computed,
new_computed,
% else:
old_computed.to_animated_value(),
new_computed.to_animated_value(),
% endif
)
}
% endif
% endfor
_ => return None,
})
}
}
/// A collection of AnimationValue that were composed on an element.
/// This HashMap stores the values that are the last AnimationValue to be
/// composed for each TransitionProperty.
@ -192,11 +66,6 @@ pub type AnimationValueMap = FxHashMap<LonghandId, AnimationValue>;
/// property in order to be interpolated with another one. When interpolating,
/// both values need to belong to the same property.
///
/// This is different to AnimatedProperty in the sense that AnimatedProperty
/// also knows the final value to be used during the animation.
///
/// This is to be used in Gecko integration code.
///
/// FIXME: We need to add a path for custom properties, but that's trivial after
/// this (is a similar path to that of PropertyDeclaration).
#[cfg_attr(feature = "servo", derive(MallocSizeOf))]
@ -546,6 +415,30 @@ impl AnimationValue {
_ => return None,
})
}
/// Update `style` with the value of this `AnimationValue`.
///
/// SERVO ONLY: This doesn't properly handle things like updating 'em' units
/// when animated font-size.
pub fn set_in_style_for_servo(&self, style: &mut ComputedValues) {
match self {
% for prop in data.longhands:
% if prop.animatable and not prop.logical:
AnimationValue::${prop.camel_case}(ref value) => {
% if not prop.is_animatable_with_computed_value:
let value: longhands::${prop.ident}::computed_value::T =
ToAnimatedValue::from_animated_value(value.clone());
style.mutate_${prop.style_struct.name_lower}().set_${prop.ident}(value);
% else:
style.mutate_${prop.style_struct.name_lower}().set_${prop.ident}(value.clone());
% endif
}
% else:
AnimationValue::${prop.camel_case}(..) => unreachable!(),
% endif
% endfor
}
}
}
fn animate_discrete<T: Clone>(this: &T, other: &T, procedure: Procedure) -> Result<T, ()> {