/* 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 crate::properties::{ longhands::{ self, content_visibility::computed_value::T as ContentVisibility, visibility::computed_value::T as Visibility, }, CSSWideKeyword, NonCustomPropertyId, LonghandId, NonCustomPropertyIterator, PropertyDeclaration, PropertyDeclarationId, }; use std::ptr; use std::mem; use fxhash::FxHashMap; use super::ComputedValues; use crate::properties::OwnedPropertyDeclarationId; 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}; use crate::properties_and_values::value::CustomAnimatedValue; /// Convert nsCSSPropertyID to TransitionProperty #[cfg(feature = "gecko")] #[allow(non_upper_case_globals)] impl From for TransitionProperty { fn from(property: nsCSSPropertyID) -> TransitionProperty { TransitionProperty::NonCustom(NonCustomPropertyId::from_nscsspropertyid(property).unwrap()) } } /// 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. #[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 /// A custom property. Custom(CustomAnimatedValue), } <% 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 Custom(ref animated_value) => Custom(animated_value.clone()), _ => 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) }, AnimationValue::Custom(ref this) => { let other_repr = &*(other as *const _ as *const AnimationValueVariantRepr); *this == other_repr.value }, } } } } impl AnimationValue { /// Returns the longhand id this animated value corresponds to. #[inline] pub fn id(&self) -> PropertyDeclarationId { if let AnimationValue::Custom(animated_value) = self { return PropertyDeclarationId::Custom(&animated_value.name); } 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 AnimationValue::Custom(..) => unsafe { debug_unreachable!() }, }); PropertyDeclarationId::Longhand(id) } /// Returns whether this value is interpolable with another one. pub fn interpolable_with(&self, other: &Self) -> bool { self.animate(other, Procedure::Interpolate { progress: 0.5 }).is_ok() } /// "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) }, Custom(ref animated_value) => animated_value.to_declaration(), } } /// Construct an AnimationValue from a property declaration. pub fn from_declaration( decl: &PropertyDeclaration, context: &mut Context, 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) }; context.for_non_inherited_property = ${"false" if inherit else "true"}; % 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.to_physical(context.builder.writing_mode) { // We put all the animatable properties first in the hopes // that it might increase match locality. % for prop in data.longhands: % if prop.animatable and not prop.logical: 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 AnimationValue::${prop.camel_case}(computed) }, % endif % endfor % for prop in data.longhands: % if not prop.animatable or prop.logical: LonghandId::${prop.camel_case} => return None, % endif % endfor } }, PropertyDeclaration::WithVariables(ref declaration) => { let mut cache = Default::default(); let substituted = { let custom_properties = &context.style().custom_properties(); debug_assert!( context.builder.stylist.is_some(), "Need a Stylist to substitute variables!" ); declaration.value.substitute_variables( declaration.id, custom_properties, context.builder.stylist.unwrap(), context, &mut cache, ) }; return AnimationValue::from_declaration( &substituted, context, initial, ) }, PropertyDeclaration::Custom(ref declaration) => { AnimationValue::Custom(CustomAnimatedValue::from_declaration( declaration, context, initial, )?) }, _ => return None // non animatable properties will get included because of shorthands. ignore. }; Some(animatable) } /// Get an AnimationValue for an declaration id from a given computed values. pub fn from_computed_values( property: PropertyDeclarationId, style: &ComputedValues, ) -> Option { let property = match property { PropertyDeclarationId::Longhand(id) => id, PropertyDeclarationId::Custom(ref name) => { // FIXME(bug 1869476): This should use a stylist to determine whether the name // corresponds to an inherited custom property and then choose the // inherited/non_inherited map accordingly. let p = &style.custom_properties(); let value = p.inherited.get(*name).or_else(|| p.non_inherited.get(*name))?; return Some(AnimationValue::Custom(CustomAnimatedValue::from_computed(name, value))) } }; 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 AnimationValue::Custom(..) => unreachable!(), } } /// 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) }, Custom(ref self_value) => { let Custom(ref other_value) = *other else { unreachable!() }; Custom(self_value.animate(other_value, procedure)?) }, } }) } } <% 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 AnimationValue::Custom(..) => { // TODO(bug 1869185): For some non-universal registered custom properties, it may make sense to implement this. Err(()) }, _ => Err(()), } } } /// 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 ContentVisibility { #[inline] fn animate(&self, other: &Self, procedure: Procedure) -> Result { match procedure { Procedure::Interpolate { .. } => { let (this_weight, other_weight) = procedure.weights(); match (*self, *other) { (ContentVisibility::Hidden, _) => { Ok(if other_weight > 0.0 { *other } else { *self }) }, (_, ContentVisibility::Hidden) => { Ok(if this_weight > 0.0 { *self } else { *other }) }, _ => Err(()), } }, _ => Err(()), } } } impl ComputeSquaredDistance for ContentVisibility { #[inline] fn compute_squared_distance(&self, other: &Self) -> Result { Ok(SquaredDistance::from_sqrt(if *self == *other { 0. } else { 1. })) } } impl ToAnimatedZero for ContentVisibility { #[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::NonCustom(id) => { match id.longhand_or_shorthand() { Ok(longhand_id) => { return Some(TransitionPropertyIteration { longhand_id, index, }); }, Err(shorthand_id) => { // 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()). self.longhand_iterator = Some(shorthand_id.longhands()); }, } } TransitionProperty::Custom(..) | TransitionProperty::Unsupported(..) => {} } } } }