mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
This mostly just moves code around, to minimize potential behavior changes. There are some cleanups that we should try to do long term (this "have an array with n different counts" is pretty weird). But for now this should unblock people. The destination struct (nsStyleUIReset) was chosen mainly because it's small and non-inherited, and it doesn't seem like a worse place than nsStyleDisplay. Differential Revision: https://phabricator.services.mozilla.com/D144183
851 lines
32 KiB
Rust
851 lines
32 KiB
Rust
/* 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 https://mozilla.org/MPL/2.0/. */
|
|
|
|
<%namespace name="helpers" file="/helpers.mako.rs" />
|
|
|
|
<%
|
|
from data import to_idl_name, SYSTEM_FONT_LONGHANDS, to_camel_case
|
|
from itertools import groupby
|
|
%>
|
|
|
|
#[cfg(feature = "gecko")] use crate::gecko_bindings::structs::nsCSSPropertyID;
|
|
use itertools::{EitherOrBoth, Itertools};
|
|
use crate::properties::{CSSWideKeyword, PropertyDeclaration, NonCustomPropertyIterator};
|
|
use crate::properties::longhands;
|
|
use crate::properties::longhands::visibility::computed_value::T as Visibility;
|
|
use crate::properties::LonghandId;
|
|
use servo_arc::Arc;
|
|
use smallvec::SmallVec;
|
|
use std::ptr;
|
|
use std::mem;
|
|
use fxhash::FxHashMap;
|
|
use super::ComputedValues;
|
|
use crate::values::animated::{Animate, Procedure, ToAnimatedValue, ToAnimatedZero};
|
|
use crate::values::animated::effects::AnimatedFilter;
|
|
#[cfg(feature = "gecko")] use crate::values::computed::TransitionProperty;
|
|
use crate::values::computed::{ClipRect, Context};
|
|
use crate::values::computed::ToComputedValue;
|
|
use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
|
|
use crate::values::generics::effects::Filter;
|
|
use void::{self, Void};
|
|
|
|
/// Convert nsCSSPropertyID to TransitionProperty
|
|
#[cfg(feature = "gecko")]
|
|
#[allow(non_upper_case_globals)]
|
|
impl From<nsCSSPropertyID> for TransitionProperty {
|
|
fn from(property: nsCSSPropertyID) -> TransitionProperty {
|
|
use crate::properties::ShorthandId;
|
|
match property {
|
|
% for prop in data.longhands:
|
|
${prop.nscsspropertyid()} => {
|
|
TransitionProperty::Longhand(LonghandId::${prop.camel_case})
|
|
}
|
|
% endfor
|
|
% for prop in data.shorthands_except_all():
|
|
${prop.nscsspropertyid()} => {
|
|
TransitionProperty::Shorthand(ShorthandId::${prop.camel_case})
|
|
}
|
|
% endfor
|
|
nsCSSPropertyID::eCSSPropertyExtra_all_properties => {
|
|
TransitionProperty::Shorthand(ShorthandId::All)
|
|
}
|
|
_ => {
|
|
panic!("non-convertible nsCSSPropertyID")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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.
|
|
pub type AnimationValueMap = FxHashMap<LonghandId, AnimationValue>;
|
|
|
|
/// An enum to represent a single computed value belonging to an animated
|
|
/// property in order to be interpolated with another one. When interpolating,
|
|
/// both values need to belong to the same property.
|
|
///
|
|
/// FIXME: We need to add a path for custom properties, but that's trivial after
|
|
/// this (is a similar path to that of PropertyDeclaration).
|
|
#[derive(Debug, MallocSizeOf)]
|
|
#[repr(u16)]
|
|
pub enum AnimationValue {
|
|
% for prop in data.longhands:
|
|
/// `${prop.name}`
|
|
% if prop.animatable and not prop.logical:
|
|
${prop.camel_case}(${prop.animated_type()}),
|
|
% else:
|
|
${prop.camel_case}(Void),
|
|
% endif
|
|
% endfor
|
|
}
|
|
|
|
<%
|
|
animated = []
|
|
unanimated = []
|
|
animated_with_logical = []
|
|
for prop in data.longhands:
|
|
if prop.animatable:
|
|
animated_with_logical.append(prop)
|
|
if prop.animatable and not prop.logical:
|
|
animated.append(prop)
|
|
else:
|
|
unanimated.append(prop)
|
|
%>
|
|
|
|
#[repr(C)]
|
|
struct AnimationValueVariantRepr<T> {
|
|
tag: u16,
|
|
value: T
|
|
}
|
|
|
|
impl Clone for AnimationValue {
|
|
#[inline]
|
|
fn clone(&self) -> Self {
|
|
use self::AnimationValue::*;
|
|
|
|
<%
|
|
[copy, others] = [list(g) for _, g in groupby(animated, key=lambda x: not x.specified_is_copy())]
|
|
%>
|
|
|
|
let self_tag = unsafe { *(self as *const _ as *const u16) };
|
|
if self_tag <= LonghandId::${copy[-1].camel_case} as u16 {
|
|
#[derive(Clone, Copy)]
|
|
#[repr(u16)]
|
|
enum CopyVariants {
|
|
% for prop in copy:
|
|
_${prop.camel_case}(${prop.animated_type()}),
|
|
% endfor
|
|
}
|
|
|
|
unsafe {
|
|
let mut out = mem::MaybeUninit::uninit();
|
|
ptr::write(
|
|
out.as_mut_ptr() as *mut CopyVariants,
|
|
*(self as *const _ as *const CopyVariants),
|
|
);
|
|
return out.assume_init();
|
|
}
|
|
}
|
|
|
|
match *self {
|
|
% for ty, props in groupby(others, key=lambda x: x.animated_type()):
|
|
<% props = list(props) %>
|
|
${" |\n".join("{}(ref value)".format(prop.camel_case) for prop in props)} => {
|
|
% if len(props) == 1:
|
|
${props[0].camel_case}(value.clone())
|
|
% else:
|
|
unsafe {
|
|
let mut out = mem::MaybeUninit::uninit();
|
|
ptr::write(
|
|
out.as_mut_ptr() as *mut AnimationValueVariantRepr<${ty}>,
|
|
AnimationValueVariantRepr {
|
|
tag: *(self as *const _ as *const u16),
|
|
value: value.clone(),
|
|
},
|
|
);
|
|
out.assume_init()
|
|
}
|
|
% endif
|
|
}
|
|
% endfor
|
|
_ => unsafe { debug_unreachable!() }
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PartialEq for AnimationValue {
|
|
#[inline]
|
|
fn eq(&self, other: &Self) -> bool {
|
|
use self::AnimationValue::*;
|
|
|
|
unsafe {
|
|
let this_tag = *(self as *const _ as *const u16);
|
|
let other_tag = *(other as *const _ as *const u16);
|
|
if this_tag != other_tag {
|
|
return false;
|
|
}
|
|
|
|
match *self {
|
|
% for ty, props in groupby(animated, key=lambda x: x.animated_type()):
|
|
${" |\n".join("{}(ref this)".format(prop.camel_case) for prop in props)} => {
|
|
let other_repr =
|
|
&*(other as *const _ as *const AnimationValueVariantRepr<${ty}>);
|
|
*this == other_repr.value
|
|
}
|
|
% endfor
|
|
${" |\n".join("{}(void)".format(prop.camel_case) for prop in unanimated)} => {
|
|
void::unreachable(void)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl AnimationValue {
|
|
/// Returns the longhand id this animated value corresponds to.
|
|
#[inline]
|
|
pub fn id(&self) -> LonghandId {
|
|
let id = unsafe { *(self as *const _ as *const LonghandId) };
|
|
debug_assert_eq!(id, match *self {
|
|
% for prop in data.longhands:
|
|
% if prop.animatable and not prop.logical:
|
|
AnimationValue::${prop.camel_case}(..) => LonghandId::${prop.camel_case},
|
|
% else:
|
|
AnimationValue::${prop.camel_case}(void) => void::unreachable(void),
|
|
% endif
|
|
% endfor
|
|
});
|
|
id
|
|
}
|
|
|
|
/// "Uncompute" this animation value in order to be used inside the CSS
|
|
/// cascade.
|
|
pub fn uncompute(&self) -> PropertyDeclaration {
|
|
use crate::properties::longhands;
|
|
use self::AnimationValue::*;
|
|
|
|
use super::PropertyDeclarationVariantRepr;
|
|
|
|
match *self {
|
|
<% keyfunc = lambda x: (x.base_type(), x.specified_type(), x.boxed, x.is_animatable_with_computed_value) %>
|
|
% for (ty, specified, boxed, computed), props in groupby(animated, key=keyfunc):
|
|
<% props = list(props) %>
|
|
${" |\n".join("{}(ref value)".format(prop.camel_case) for prop in props)} => {
|
|
% if not computed:
|
|
let ref value = ToAnimatedValue::from_animated_value(value.clone());
|
|
% endif
|
|
let value = ${ty}::from_computed_value(&value);
|
|
% if boxed:
|
|
let value = Box::new(value);
|
|
% endif
|
|
% if len(props) == 1:
|
|
PropertyDeclaration::${props[0].camel_case}(value)
|
|
% else:
|
|
unsafe {
|
|
let mut out = mem::MaybeUninit::uninit();
|
|
ptr::write(
|
|
out.as_mut_ptr() as *mut PropertyDeclarationVariantRepr<${specified}>,
|
|
PropertyDeclarationVariantRepr {
|
|
tag: *(self as *const _ as *const u16),
|
|
value,
|
|
},
|
|
);
|
|
out.assume_init()
|
|
}
|
|
% endif
|
|
}
|
|
% endfor
|
|
${" |\n".join("{}(void)".format(prop.camel_case) for prop in unanimated)} => {
|
|
void::unreachable(void)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Construct an AnimationValue from a property declaration.
|
|
pub fn from_declaration(
|
|
decl: &PropertyDeclaration,
|
|
context: &mut Context,
|
|
extra_custom_properties: Option<<&Arc<crate::custom_properties::CustomPropertiesMap>>,
|
|
initial: &ComputedValues,
|
|
) -> Option<Self> {
|
|
use super::PropertyDeclarationVariantRepr;
|
|
|
|
<%
|
|
keyfunc = lambda x: (
|
|
x.specified_type(),
|
|
x.animated_type(),
|
|
x.boxed,
|
|
not x.is_animatable_with_computed_value,
|
|
x.style_struct.inherited,
|
|
x.ident in SYSTEM_FONT_LONGHANDS and engine == "gecko",
|
|
)
|
|
%>
|
|
|
|
let animatable = match *decl {
|
|
% for (specified_ty, ty, boxed, to_animated, inherit, system), props in groupby(animated_with_logical, key=keyfunc):
|
|
${" |\n".join("PropertyDeclaration::{}(ref value)".format(prop.camel_case) for prop in props)} => {
|
|
let decl_repr = unsafe {
|
|
&*(decl as *const _ as *const PropertyDeclarationVariantRepr<${specified_ty}>)
|
|
};
|
|
let longhand_id = unsafe {
|
|
*(&decl_repr.tag as *const u16 as *const LonghandId)
|
|
};
|
|
% if inherit:
|
|
context.for_non_inherited_property = None;
|
|
% else:
|
|
context.for_non_inherited_property = Some(longhand_id);
|
|
% endif
|
|
% if system:
|
|
if let Some(sf) = value.get_system() {
|
|
longhands::system_font::resolve_system_font(sf, context)
|
|
}
|
|
% endif
|
|
% if boxed:
|
|
let value = (**value).to_computed_value(context);
|
|
% else:
|
|
let value = value.to_computed_value(context);
|
|
% endif
|
|
% if to_animated:
|
|
let value = value.to_animated_value();
|
|
% endif
|
|
|
|
unsafe {
|
|
let mut out = mem::MaybeUninit::uninit();
|
|
ptr::write(
|
|
out.as_mut_ptr() as *mut AnimationValueVariantRepr<${ty}>,
|
|
AnimationValueVariantRepr {
|
|
tag: longhand_id.to_physical(context.builder.writing_mode) as u16,
|
|
value,
|
|
},
|
|
);
|
|
out.assume_init()
|
|
}
|
|
}
|
|
% endfor
|
|
PropertyDeclaration::CSSWideKeyword(ref declaration) => {
|
|
match declaration.id {
|
|
// We put all the animatable properties first in the hopes
|
|
// that it might increase match locality.
|
|
% for prop in data.longhands:
|
|
% if prop.animatable:
|
|
LonghandId::${prop.camel_case} => {
|
|
// FIXME(emilio, bug 1533327): I think revert (and
|
|
// revert-layer) handling is not fine here, but what to
|
|
// do instead?
|
|
//
|
|
// Seems we'd need the computed value as if it was
|
|
// revert, somehow. Treating it as `unset` seems fine
|
|
// for now...
|
|
let style_struct = match declaration.keyword {
|
|
% if not prop.style_struct.inherited:
|
|
CSSWideKeyword::Revert |
|
|
CSSWideKeyword::RevertLayer |
|
|
CSSWideKeyword::Unset |
|
|
% endif
|
|
CSSWideKeyword::Initial => {
|
|
initial.get_${prop.style_struct.name_lower}()
|
|
},
|
|
% if prop.style_struct.inherited:
|
|
CSSWideKeyword::Revert |
|
|
CSSWideKeyword::RevertLayer |
|
|
CSSWideKeyword::Unset |
|
|
% endif
|
|
CSSWideKeyword::Inherit => {
|
|
context.builder
|
|
.get_parent_${prop.style_struct.name_lower}()
|
|
},
|
|
};
|
|
let computed = style_struct
|
|
% if prop.logical:
|
|
.clone_${prop.ident}(context.builder.writing_mode);
|
|
% else:
|
|
.clone_${prop.ident}();
|
|
% endif
|
|
|
|
% if not prop.is_animatable_with_computed_value:
|
|
let computed = computed.to_animated_value();
|
|
% endif
|
|
|
|
% if prop.logical:
|
|
let wm = context.builder.writing_mode;
|
|
<%helpers:logical_setter_helper name="${prop.name}">
|
|
<%def name="inner(physical_ident)">
|
|
AnimationValue::${to_camel_case(physical_ident)}(computed)
|
|
</%def>
|
|
</%helpers:logical_setter_helper>
|
|
% else:
|
|
AnimationValue::${prop.camel_case}(computed)
|
|
% endif
|
|
},
|
|
% endif
|
|
% endfor
|
|
% for prop in data.longhands:
|
|
% if not prop.animatable:
|
|
LonghandId::${prop.camel_case} => return None,
|
|
% endif
|
|
% endfor
|
|
}
|
|
},
|
|
PropertyDeclaration::WithVariables(ref declaration) => {
|
|
let mut cache = Default::default();
|
|
let substituted = {
|
|
let custom_properties =
|
|
extra_custom_properties.or_else(|| context.style().custom_properties());
|
|
|
|
declaration.value.substitute_variables(
|
|
declaration.id,
|
|
context.builder.writing_mode,
|
|
custom_properties,
|
|
context.quirks_mode,
|
|
context.device(),
|
|
&mut cache,
|
|
)
|
|
};
|
|
return AnimationValue::from_declaration(
|
|
&substituted,
|
|
context,
|
|
extra_custom_properties,
|
|
initial,
|
|
)
|
|
},
|
|
_ => return None // non animatable properties will get included because of shorthands. ignore.
|
|
};
|
|
Some(animatable)
|
|
}
|
|
|
|
/// Get an AnimationValue for an AnimatableLonghand from a given computed values.
|
|
pub fn from_computed_values(
|
|
property: LonghandId,
|
|
style: &ComputedValues,
|
|
) -> Option<Self> {
|
|
let property = property.to_physical(style.writing_mode);
|
|
Some(match property {
|
|
% for prop in data.longhands:
|
|
% if prop.animatable and not prop.logical:
|
|
LonghandId::${prop.camel_case} => {
|
|
let computed = style.clone_${prop.ident}();
|
|
AnimationValue::${prop.camel_case}(
|
|
% if prop.is_animatable_with_computed_value:
|
|
computed
|
|
% else:
|
|
computed.to_animated_value()
|
|
% endif
|
|
)
|
|
}
|
|
% endif
|
|
% endfor
|
|
_ => 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.
|
|
#[cfg(feature = "servo")]
|
|
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
|
|
}
|
|
}
|
|
|
|
/// As above, but a stub for Gecko.
|
|
#[cfg(feature = "gecko")]
|
|
pub fn set_in_style_for_servo(&self, _: &mut ComputedValues) {
|
|
}
|
|
}
|
|
|
|
fn animate_discrete<T: Clone>(this: &T, other: &T, procedure: Procedure) -> Result<T, ()> {
|
|
if let Procedure::Interpolate { progress } = procedure {
|
|
Ok(if progress < 0.5 { this.clone() } else { other.clone() })
|
|
} else {
|
|
Err(())
|
|
}
|
|
}
|
|
|
|
impl Animate for AnimationValue {
|
|
fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
|
|
Ok(unsafe {
|
|
use self::AnimationValue::*;
|
|
|
|
let this_tag = *(self as *const _ as *const u16);
|
|
let other_tag = *(other as *const _ as *const u16);
|
|
if this_tag != other_tag {
|
|
panic!("Unexpected AnimationValue::animate call");
|
|
}
|
|
|
|
match *self {
|
|
<% keyfunc = lambda x: (x.animated_type(), x.animation_value_type == "discrete") %>
|
|
% for (ty, discrete), props in groupby(animated, key=keyfunc):
|
|
${" |\n".join("{}(ref this)".format(prop.camel_case) for prop in props)} => {
|
|
let other_repr =
|
|
&*(other as *const _ as *const AnimationValueVariantRepr<${ty}>);
|
|
% if discrete:
|
|
let value = animate_discrete(this, &other_repr.value, procedure)?;
|
|
% else:
|
|
let value = this.animate(&other_repr.value, procedure)?;
|
|
% endif
|
|
|
|
let mut out = mem::MaybeUninit::uninit();
|
|
ptr::write(
|
|
out.as_mut_ptr() as *mut AnimationValueVariantRepr<${ty}>,
|
|
AnimationValueVariantRepr {
|
|
tag: this_tag,
|
|
value,
|
|
},
|
|
);
|
|
out.assume_init()
|
|
}
|
|
% endfor
|
|
${" |\n".join("{}(void)".format(prop.camel_case) for prop in unanimated)} => {
|
|
void::unreachable(void)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
<%
|
|
nondiscrete = []
|
|
for prop in animated:
|
|
if prop.animation_value_type != "discrete":
|
|
nondiscrete.append(prop)
|
|
%>
|
|
|
|
impl ComputeSquaredDistance for AnimationValue {
|
|
fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
|
|
unsafe {
|
|
use self::AnimationValue::*;
|
|
|
|
let this_tag = *(self as *const _ as *const u16);
|
|
let other_tag = *(other as *const _ as *const u16);
|
|
if this_tag != other_tag {
|
|
panic!("Unexpected AnimationValue::compute_squared_distance call");
|
|
}
|
|
|
|
match *self {
|
|
% for ty, props in groupby(nondiscrete, key=lambda x: x.animated_type()):
|
|
${" |\n".join("{}(ref this)".format(prop.camel_case) for prop in props)} => {
|
|
let other_repr =
|
|
&*(other as *const _ as *const AnimationValueVariantRepr<${ty}>);
|
|
|
|
this.compute_squared_distance(&other_repr.value)
|
|
}
|
|
% endfor
|
|
_ => Err(()),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ToAnimatedZero for AnimationValue {
|
|
#[inline]
|
|
fn to_animated_zero(&self) -> Result<Self, ()> {
|
|
match *self {
|
|
% for prop in data.longhands:
|
|
% if prop.animatable and not prop.logical and prop.animation_value_type != "discrete":
|
|
AnimationValue::${prop.camel_case}(ref base) => {
|
|
Ok(AnimationValue::${prop.camel_case}(base.to_animated_zero()?))
|
|
},
|
|
% endif
|
|
% endfor
|
|
_ => Err(()),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A trait to abstract away the different kind of animations over a list that
|
|
/// there may be.
|
|
pub trait ListAnimation<T> : Sized {
|
|
/// <https://drafts.csswg.org/css-transitions/#animtype-repeatable-list>
|
|
fn animate_repeatable_list(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>
|
|
where
|
|
T: Animate;
|
|
|
|
/// <https://drafts.csswg.org/css-transitions/#animtype-repeatable-list>
|
|
fn squared_distance_repeatable_list(&self, other: &Self) -> Result<SquaredDistance, ()>
|
|
where
|
|
T: ComputeSquaredDistance;
|
|
|
|
/// This is the animation used for some of the types like shadows and
|
|
/// filters, where the interpolation happens with the zero value if one of
|
|
/// the sides is not present.
|
|
fn animate_with_zero(&self, other: &Self, procedure: Procedure) -> Result<Self, ()>
|
|
where
|
|
T: Animate + Clone + ToAnimatedZero;
|
|
|
|
/// This is the animation used for some of the types like shadows and
|
|
/// filters, where the interpolation happens with the zero value if one of
|
|
/// the sides is not present.
|
|
fn squared_distance_with_zero(&self, other: &Self) -> Result<SquaredDistance, ()>
|
|
where
|
|
T: ToAnimatedZero + ComputeSquaredDistance;
|
|
}
|
|
|
|
macro_rules! animated_list_impl {
|
|
(<$t:ident> for $ty:ty) => {
|
|
impl<$t> ListAnimation<$t> for $ty {
|
|
fn animate_repeatable_list(
|
|
&self,
|
|
other: &Self,
|
|
procedure: Procedure,
|
|
) -> Result<Self, ()>
|
|
where
|
|
T: Animate,
|
|
{
|
|
// If the length of either list is zero, the least common multiple is undefined.
|
|
if self.is_empty() || other.is_empty() {
|
|
return Err(());
|
|
}
|
|
use num_integer::lcm;
|
|
let len = lcm(self.len(), other.len());
|
|
self.iter().cycle().zip(other.iter().cycle()).take(len).map(|(this, other)| {
|
|
this.animate(other, procedure)
|
|
}).collect()
|
|
}
|
|
|
|
fn squared_distance_repeatable_list(
|
|
&self,
|
|
other: &Self,
|
|
) -> Result<SquaredDistance, ()>
|
|
where
|
|
T: ComputeSquaredDistance,
|
|
{
|
|
if self.is_empty() || other.is_empty() {
|
|
return Err(());
|
|
}
|
|
use num_integer::lcm;
|
|
let len = lcm(self.len(), other.len());
|
|
self.iter().cycle().zip(other.iter().cycle()).take(len).map(|(this, other)| {
|
|
this.compute_squared_distance(other)
|
|
}).sum()
|
|
}
|
|
|
|
fn animate_with_zero(
|
|
&self,
|
|
other: &Self,
|
|
procedure: Procedure,
|
|
) -> Result<Self, ()>
|
|
where
|
|
T: Animate + Clone + ToAnimatedZero
|
|
{
|
|
if procedure == Procedure::Add {
|
|
return Ok(
|
|
self.iter().chain(other.iter()).cloned().collect()
|
|
);
|
|
}
|
|
self.iter().zip_longest(other.iter()).map(|it| {
|
|
match it {
|
|
EitherOrBoth::Both(this, other) => {
|
|
this.animate(other, procedure)
|
|
},
|
|
EitherOrBoth::Left(this) => {
|
|
this.animate(&this.to_animated_zero()?, procedure)
|
|
},
|
|
EitherOrBoth::Right(other) => {
|
|
other.to_animated_zero()?.animate(other, procedure)
|
|
}
|
|
}
|
|
}).collect()
|
|
}
|
|
|
|
fn squared_distance_with_zero(
|
|
&self,
|
|
other: &Self,
|
|
) -> Result<SquaredDistance, ()>
|
|
where
|
|
T: ToAnimatedZero + ComputeSquaredDistance
|
|
{
|
|
self.iter().zip_longest(other.iter()).map(|it| {
|
|
match it {
|
|
EitherOrBoth::Both(this, other) => {
|
|
this.compute_squared_distance(other)
|
|
},
|
|
EitherOrBoth::Left(list) | EitherOrBoth::Right(list) => {
|
|
list.to_animated_zero()?.compute_squared_distance(list)
|
|
},
|
|
}
|
|
}).sum()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
animated_list_impl!(<T> for crate::OwnedSlice<T>);
|
|
animated_list_impl!(<T> for SmallVec<[T; 1]>);
|
|
animated_list_impl!(<T> for Vec<T>);
|
|
|
|
/// <https://drafts.csswg.org/web-animations-1/#animating-visibility>
|
|
impl Animate for Visibility {
|
|
#[inline]
|
|
fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
|
|
match procedure {
|
|
Procedure::Interpolate { .. } => {
|
|
let (this_weight, other_weight) = procedure.weights();
|
|
match (*self, *other) {
|
|
(Visibility::Visible, _) => {
|
|
Ok(if this_weight > 0.0 { *self } else { *other })
|
|
},
|
|
(_, Visibility::Visible) => {
|
|
Ok(if other_weight > 0.0 { *other } else { *self })
|
|
},
|
|
_ => Err(()),
|
|
}
|
|
},
|
|
_ => Err(()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ComputeSquaredDistance for Visibility {
|
|
#[inline]
|
|
fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> {
|
|
Ok(SquaredDistance::from_sqrt(if *self == *other { 0. } else { 1. }))
|
|
}
|
|
}
|
|
|
|
impl ToAnimatedZero for Visibility {
|
|
#[inline]
|
|
fn to_animated_zero(&self) -> Result<Self, ()> {
|
|
Err(())
|
|
}
|
|
}
|
|
|
|
/// <https://drafts.csswg.org/css-transitions/#animtype-rect>
|
|
impl Animate for ClipRect {
|
|
#[inline]
|
|
fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
|
|
use crate::values::computed::LengthOrAuto;
|
|
let animate_component = |this: &LengthOrAuto, other: &LengthOrAuto| {
|
|
let result = this.animate(other, procedure)?;
|
|
if let Procedure::Interpolate { .. } = procedure {
|
|
return Ok(result);
|
|
}
|
|
if result.is_auto() {
|
|
// FIXME(emilio): Why? A couple SMIL tests fail without this,
|
|
// but it seems extremely fishy.
|
|
return Err(());
|
|
}
|
|
Ok(result)
|
|
};
|
|
|
|
Ok(ClipRect {
|
|
top: animate_component(&self.top, &other.top)?,
|
|
right: animate_component(&self.right, &other.right)?,
|
|
bottom: animate_component(&self.bottom, &other.bottom)?,
|
|
left: animate_component(&self.left, &other.left)?,
|
|
})
|
|
}
|
|
}
|
|
|
|
<%
|
|
FILTER_FUNCTIONS = [ 'Blur', 'Brightness', 'Contrast', 'Grayscale',
|
|
'HueRotate', 'Invert', 'Opacity', 'Saturate',
|
|
'Sepia' ]
|
|
%>
|
|
|
|
/// <https://drafts.fxtf.org/filters/#animation-of-filters>
|
|
impl Animate for AnimatedFilter {
|
|
fn animate(
|
|
&self,
|
|
other: &Self,
|
|
procedure: Procedure,
|
|
) -> Result<Self, ()> {
|
|
use crate::values::animated::animate_multiplicative_factor;
|
|
match (self, other) {
|
|
% for func in ['Blur', 'Grayscale', 'HueRotate', 'Invert', 'Sepia']:
|
|
(&Filter::${func}(ref this), &Filter::${func}(ref other)) => {
|
|
Ok(Filter::${func}(this.animate(other, procedure)?))
|
|
},
|
|
% endfor
|
|
% for func in ['Brightness', 'Contrast', 'Opacity', 'Saturate']:
|
|
(&Filter::${func}(this), &Filter::${func}(other)) => {
|
|
Ok(Filter::${func}(animate_multiplicative_factor(this, other, procedure)?))
|
|
},
|
|
% endfor
|
|
% if engine == "gecko":
|
|
(&Filter::DropShadow(ref this), &Filter::DropShadow(ref other)) => {
|
|
Ok(Filter::DropShadow(this.animate(other, procedure)?))
|
|
},
|
|
% endif
|
|
_ => Err(()),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <http://dev.w3.org/csswg/css-transforms/#none-transform-animation>
|
|
impl ToAnimatedZero for AnimatedFilter {
|
|
fn to_animated_zero(&self) -> Result<Self, ()> {
|
|
match *self {
|
|
% for func in ['Blur', 'Grayscale', 'HueRotate', 'Invert', 'Sepia']:
|
|
Filter::${func}(ref this) => Ok(Filter::${func}(this.to_animated_zero()?)),
|
|
% endfor
|
|
% for func in ['Brightness', 'Contrast', 'Opacity', 'Saturate']:
|
|
Filter::${func}(_) => Ok(Filter::${func}(1.)),
|
|
% endfor
|
|
% if engine == "gecko":
|
|
Filter::DropShadow(ref this) => Ok(Filter::DropShadow(this.to_animated_zero()?)),
|
|
% endif
|
|
_ => Err(()),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An iterator over all the properties that transition on a given style.
|
|
pub struct TransitionPropertyIterator<'a> {
|
|
style: &'a ComputedValues,
|
|
index_range: core::ops::Range<usize>,
|
|
longhand_iterator: Option<NonCustomPropertyIterator<LonghandId>>,
|
|
}
|
|
|
|
impl<'a> TransitionPropertyIterator<'a> {
|
|
/// Create a `TransitionPropertyIterator` for the given style.
|
|
pub fn from_style(style: &'a ComputedValues) -> Self {
|
|
Self {
|
|
style,
|
|
index_range: 0..style.get_ui().transition_property_count(),
|
|
longhand_iterator: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A single iteration of the TransitionPropertyIterator.
|
|
pub struct TransitionPropertyIteration {
|
|
/// The id of the longhand for this property.
|
|
pub longhand_id: LonghandId,
|
|
|
|
/// The index of this property in the list of transition properties for this
|
|
/// iterator's style.
|
|
pub index: usize,
|
|
}
|
|
|
|
impl<'a> Iterator for TransitionPropertyIterator<'a> {
|
|
type Item = TransitionPropertyIteration;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
use crate::values::computed::TransitionProperty;
|
|
loop {
|
|
if let Some(ref mut longhand_iterator) = self.longhand_iterator {
|
|
if let Some(longhand_id) = longhand_iterator.next() {
|
|
return Some(TransitionPropertyIteration {
|
|
longhand_id,
|
|
index: self.index_range.start - 1,
|
|
});
|
|
}
|
|
self.longhand_iterator = None;
|
|
}
|
|
|
|
let index = self.index_range.next()?;
|
|
match self.style.get_ui().transition_property_at(index) {
|
|
TransitionProperty::Longhand(longhand_id) => {
|
|
return Some(TransitionPropertyIteration {
|
|
longhand_id,
|
|
index,
|
|
})
|
|
}
|
|
// In the other cases, we set up our state so that we are ready to
|
|
// compute the next value of the iterator and then loop (equivalent
|
|
// to calling self.next()).
|
|
TransitionProperty::Shorthand(ref shorthand_id) =>
|
|
self.longhand_iterator = Some(shorthand_id.longhands()),
|
|
TransitionProperty::Custom(..) | TransitionProperty::Unsupported(..) => {}
|
|
}
|
|
}
|
|
}
|
|
}
|