/* 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 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; /// 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 { 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>, initial: &ComputedValues, ) -> Option { 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) % 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 { 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(this: &T, other: &T, procedure: Procedure) -> Result { 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 { 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 { 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 { 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 : Sized { /// fn animate_repeatable_list(&self, other: &Self, procedure: Procedure) -> Result where T: Animate; /// fn squared_distance_repeatable_list(&self, other: &Self) -> Result 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 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 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 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 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 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 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!( for crate::OwnedSlice); animated_list_impl!( for SmallVec<[T; 1]>); animated_list_impl!( for Vec); /// impl Animate for Visibility { #[inline] fn animate(&self, other: &Self, procedure: Procedure) -> Result { 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 { Ok(SquaredDistance::from_sqrt(if *self == *other { 0. } else { 1. })) } } impl ToAnimatedZero for Visibility { #[inline] fn to_animated_zero(&self) -> Result { Err(()) } } /// impl Animate for ClipRect { #[inline] fn animate(&self, other: &Self, procedure: Procedure) -> Result { 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' ] %> /// impl Animate for AnimatedFilter { fn animate( &self, other: &Self, procedure: Procedure, ) -> Result { 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(()), } } } /// impl ToAnimatedZero for AnimatedFilter { fn to_animated_zero(&self) -> Result { 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, longhand_iterator: Option>, } 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 { 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(..) => {} } } } }