diff options
Diffstat (limited to '')
-rw-r--r-- | servo/components/style/properties/helpers.mako.rs | 1031 | ||||
-rw-r--r-- | servo/components/style/properties/helpers/animated_properties.mako.rs | 728 |
2 files changed, 1759 insertions, 0 deletions
diff --git a/servo/components/style/properties/helpers.mako.rs b/servo/components/style/properties/helpers.mako.rs new file mode 100644 index 0000000000..3fe92c967d --- /dev/null +++ b/servo/components/style/properties/helpers.mako.rs @@ -0,0 +1,1031 @@ +/* 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/. */ + +<%! + from data import Keyword, to_rust_ident, to_phys, to_camel_case, SYSTEM_FONT_LONGHANDS + from data import (LOGICAL_CORNERS, PHYSICAL_CORNERS, LOGICAL_SIDES, + PHYSICAL_SIDES, LOGICAL_SIZES, LOGICAL_AXES) +%> + +<%def name="predefined_type(name, type, initial_value, parse_method='parse', + vector=False, initial_specified_value=None, + allow_quirks='No', allow_empty=False, **kwargs)"> + <%def name="predefined_type_inner(name, type, initial_value, parse_method)"> + #[allow(unused_imports)] + use app_units::Au; + #[allow(unused_imports)] + use crate::values::specified::AllowQuirks; + #[allow(unused_imports)] + use crate::Zero; + #[allow(unused_imports)] + use smallvec::SmallVec; + pub use crate::values::specified::${type} as SpecifiedValue; + pub mod computed_value { + pub use crate::values::computed::${type} as T; + } + % if initial_value: + #[inline] pub fn get_initial_value() -> computed_value::T { ${initial_value} } + % endif + % if initial_specified_value: + #[inline] pub fn get_initial_specified_value() -> SpecifiedValue { ${initial_specified_value} } + % endif + #[allow(unused_variables)] + #[inline] + pub fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<SpecifiedValue, ParseError<'i>> { + % if allow_quirks != "No": + specified::${type}::${parse_method}_quirky(context, input, AllowQuirks::${allow_quirks}) + % elif parse_method != "parse": + specified::${type}::${parse_method}(context, input) + % else: + <specified::${type} as crate::parser::Parse>::parse(context, input) + % endif + } + </%def> + % if vector: + <%call + expr="vector_longhand(name, predefined_type=type, allow_empty=allow_empty or not initial_value, **kwargs)" + > + ${predefined_type_inner(name, type, initial_value, parse_method)} + % if caller: + ${caller.body()} + % endif + </%call> + % else: + <%call expr="longhand(name, predefined_type=type, **kwargs)"> + ${predefined_type_inner(name, type, initial_value, parse_method)} + % if caller: + ${caller.body()} + % endif + </%call> + % endif +</%def> + +// FIXME (Manishearth): Add computed_value_as_specified argument +// and handle the empty case correctly +<%doc> + To be used in cases where we have a grammar like "<thing> [ , <thing> ]*". + + Setting allow_empty to False allows for cases where the vector + is empty. The grammar for these is usually "none | <thing> [ , <thing> ]*". + We assume that the default/initial value is an empty vector for these. + `initial_value` need not be defined for these. +</%doc> + +// The setup here is roughly: +// +// * UnderlyingList is the list that is stored in the computed value. This may +// be a shared ArcSlice if the property is inherited. +// * UnderlyingOwnedList is the list that is used for animation. +// * Specified values always use OwnedSlice, since it's more compact. +// * computed_value::List is just a convenient alias that you can use for the +// computed value list, since this is in the computed_value module. +// +// If simple_vector_bindings is true, then we don't use the complex iterator +// machinery and set_foo_from, and just compute the value like any other +// longhand. +<%def name="vector_longhand(name, animation_value_type=None, + vector_animation_type=None, allow_empty=False, + simple_vector_bindings=False, + separator='Comma', + **kwargs)"> + <%call expr="longhand(name, animation_value_type=animation_value_type, vector=True, + simple_vector_bindings=simple_vector_bindings, **kwargs)"> + #[allow(unused_imports)] + use smallvec::SmallVec; + + pub mod single_value { + #[allow(unused_imports)] + use cssparser::{Parser, BasicParseError}; + #[allow(unused_imports)] + use crate::parser::{Parse, ParserContext}; + #[allow(unused_imports)] + use crate::properties::ShorthandId; + #[allow(unused_imports)] + use selectors::parser::SelectorParseErrorKind; + #[allow(unused_imports)] + use style_traits::{ParseError, StyleParseErrorKind}; + #[allow(unused_imports)] + use crate::values::computed::{Context, ToComputedValue}; + #[allow(unused_imports)] + use crate::values::{computed, specified}; + ${caller.body()} + } + + /// The definition of the computed value for ${name}. + pub mod computed_value { + #[allow(unused_imports)] + use crate::values::animated::ToAnimatedValue; + #[allow(unused_imports)] + use crate::values::resolved::ToResolvedValue; + pub use super::single_value::computed_value as single_value; + pub use self::single_value::T as SingleComputedValue; + % if not allow_empty or allow_empty == "NotInitial": + use smallvec::SmallVec; + % endif + use crate::values::computed::ComputedVecIter; + + <% + is_shared_list = allow_empty and allow_empty != "NotInitial" and \ + data.longhands_by_name[name].style_struct.inherited + %> + + // FIXME(emilio): Add an OwnedNonEmptySlice type, and figure out + // something for transition-name, which is the only remaining user + // of NotInitial. + pub type UnderlyingList<T> = + % if allow_empty and allow_empty != "NotInitial": + % if data.longhands_by_name[name].style_struct.inherited: + crate::ArcSlice<T>; + % else: + crate::OwnedSlice<T>; + % endif + % else: + SmallVec<[T; 1]>; + % endif + + pub type UnderlyingOwnedList<T> = + % if allow_empty and allow_empty != "NotInitial": + crate::OwnedSlice<T>; + % else: + SmallVec<[T; 1]>; + % endif + + + /// The generic type defining the animated and resolved values for + /// this property. + /// + /// Making this type generic allows the compiler to figure out the + /// animated value for us, instead of having to implement it + /// manually for every type we care about. + #[derive( + Clone, + Debug, + MallocSizeOf, + PartialEq, + ToAnimatedValue, + ToResolvedValue, + ToCss, + )] + % if separator == "Comma": + #[css(comma)] + % endif + pub struct OwnedList<T>( + % if not allow_empty: + #[css(iterable)] + % else: + #[css(if_empty = "none", iterable)] + % endif + pub UnderlyingOwnedList<T>, + ); + + /// The computed value for this property. + % if not is_shared_list: + pub type ComputedList = OwnedList<single_value::T>; + pub use self::OwnedList as List; + % else: + pub use self::ComputedList as List; + + #[derive( + Clone, + Debug, + MallocSizeOf, + PartialEq, + ToCss, + )] + % if separator == "Comma": + #[css(comma)] + % endif + pub struct ComputedList( + % if not allow_empty: + #[css(iterable)] + % else: + #[css(if_empty = "none", iterable)] + % endif + % if is_shared_list: + #[ignore_malloc_size_of = "Arc"] + % endif + pub UnderlyingList<single_value::T>, + ); + + type ResolvedList = OwnedList<<single_value::T as ToResolvedValue>::ResolvedValue>; + impl ToResolvedValue for ComputedList { + type ResolvedValue = ResolvedList; + + fn to_resolved_value(self, context: &crate::values::resolved::Context) -> Self::ResolvedValue { + OwnedList( + self.0 + .iter() + .cloned() + .map(|v| v.to_resolved_value(context)) + .collect() + ) + } + + fn from_resolved_value(resolved: Self::ResolvedValue) -> Self { + % if not is_shared_list: + use std::iter::FromIterator; + % endif + let iter = + resolved.0.into_iter().map(ToResolvedValue::from_resolved_value); + ComputedList(UnderlyingList::from_iter(iter)) + } + } + % endif + + % if simple_vector_bindings: + impl From<ComputedList> for UnderlyingList<single_value::T> { + #[inline] + fn from(l: ComputedList) -> Self { + l.0 + } + } + impl From<UnderlyingList<single_value::T>> for ComputedList { + #[inline] + fn from(l: UnderlyingList<single_value::T>) -> Self { + List(l) + } + } + % endif + + % if vector_animation_type: + % if not animation_value_type: + Sorry, this is stupid but needed for now. + % endif + + use crate::values::animated::{Animate, ToAnimatedZero, Procedure, lists}; + use crate::values::distance::{SquaredDistance, ComputeSquaredDistance}; + + // FIXME(emilio): For some reason rust thinks that this alias is + // unused, even though it's clearly used below? + #[allow(unused)] + type AnimatedList = OwnedList<<single_value::T as ToAnimatedValue>::AnimatedValue>; + + % if is_shared_list: + impl ToAnimatedValue for ComputedList { + type AnimatedValue = AnimatedList; + + fn to_animated_value(self) -> Self::AnimatedValue { + OwnedList( + self.0.iter().map(|v| v.clone().to_animated_value()).collect() + ) + } + + fn from_animated_value(animated: Self::AnimatedValue) -> Self { + let iter = + animated.0.into_iter().map(ToAnimatedValue::from_animated_value); + ComputedList(UnderlyingList::from_iter(iter)) + } + } + % endif + + impl ToAnimatedZero for AnimatedList { + fn to_animated_zero(&self) -> Result<Self, ()> { Err(()) } + } + + impl Animate for AnimatedList { + fn animate( + &self, + other: &Self, + procedure: Procedure, + ) -> Result<Self, ()> { + Ok(OwnedList( + lists::${vector_animation_type}::animate(&self.0, &other.0, procedure)? + )) + } + } + impl ComputeSquaredDistance for AnimatedList { + fn compute_squared_distance( + &self, + other: &Self, + ) -> Result<SquaredDistance, ()> { + lists::${vector_animation_type}::squared_distance(&self.0, &other.0) + } + } + % endif + + /// The computed value, effectively a list of single values. + pub use self::ComputedList as T; + + pub type Iter<'a, 'cx, 'cx_a> = ComputedVecIter<'a, 'cx, 'cx_a, super::single_value::SpecifiedValue>; + } + + /// The specified value of ${name}. + #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] + % if separator == "Comma": + #[css(comma)] + % endif + pub struct SpecifiedValue( + % if not allow_empty: + #[css(iterable)] + % else: + #[css(if_empty = "none", iterable)] + % endif + pub crate::OwnedSlice<single_value::SpecifiedValue>, + ); + + pub fn get_initial_value() -> computed_value::T { + % if allow_empty and allow_empty != "NotInitial": + computed_value::List(Default::default()) + % else: + let mut v = SmallVec::new(); + v.push(single_value::get_initial_value()); + computed_value::List(v) + % endif + } + + pub fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<SpecifiedValue, ParseError<'i>> { + use style_traits::Separator; + + % if allow_empty: + if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() { + return Ok(SpecifiedValue(Default::default())) + } + % endif + + let v = style_traits::${separator}::parse(input, |parser| { + single_value::parse(context, parser) + })?; + Ok(SpecifiedValue(v.into())) + } + + pub use self::single_value::SpecifiedValue as SingleSpecifiedValue; + + % if not simple_vector_bindings and engine == "gecko": + impl SpecifiedValue { + fn compute_iter<'a, 'cx, 'cx_a>( + &'a self, + context: &'cx Context<'cx_a>, + ) -> computed_value::Iter<'a, 'cx, 'cx_a> { + computed_value::Iter::new(context, &self.0) + } + } + % endif + + impl ToComputedValue for SpecifiedValue { + type ComputedValue = computed_value::T; + + #[inline] + fn to_computed_value(&self, context: &Context) -> computed_value::T { + % if not is_shared_list: + use std::iter::FromIterator; + % endif + computed_value::List(computed_value::UnderlyingList::from_iter( + self.0.iter().map(|i| i.to_computed_value(context)) + )) + } + + #[inline] + fn from_computed_value(computed: &computed_value::T) -> Self { + let iter = computed.0.iter().map(ToComputedValue::from_computed_value); + SpecifiedValue(iter.collect()) + } + } + </%call> +</%def> +<%def name="longhand(*args, **kwargs)"> + <% + property = data.declare_longhand(*args, **kwargs) + if property is None: + return "" + %> + /// ${property.spec} + pub mod ${property.ident} { + #[allow(unused_imports)] + use cssparser::{Parser, BasicParseError, Token}; + #[allow(unused_imports)] + use crate::parser::{Parse, ParserContext}; + #[allow(unused_imports)] + use crate::properties::{UnparsedValue, ShorthandId}; + #[allow(unused_imports)] + use crate::error_reporting::ParseErrorReporter; + #[allow(unused_imports)] + use crate::properties::longhands; + #[allow(unused_imports)] + use crate::properties::{LonghandId, LonghandIdSet}; + #[allow(unused_imports)] + use crate::properties::{CSSWideKeyword, ComputedValues, PropertyDeclaration}; + #[allow(unused_imports)] + use crate::properties::style_structs; + #[allow(unused_imports)] + use selectors::parser::SelectorParseErrorKind; + #[allow(unused_imports)] + use servo_arc::Arc; + #[allow(unused_imports)] + use style_traits::{ParseError, StyleParseErrorKind}; + #[allow(unused_imports)] + use crate::values::computed::{Context, ToComputedValue}; + #[allow(unused_imports)] + use crate::values::{computed, generics, specified}; + #[allow(unused_imports)] + use crate::Atom; + ${caller.body()} + #[allow(unused_variables)] + pub fn cascade_property( + declaration: &PropertyDeclaration, + context: &mut computed::Context, + ) { + context.for_non_inherited_property = + % if property.style_struct.inherited: + None; + % else: + Some(LonghandId::${property.camel_case}); + % endif + + let specified_value = match *declaration { + PropertyDeclaration::${property.camel_case}(ref value) => value, + PropertyDeclaration::CSSWideKeyword(ref declaration) => { + debug_assert_eq!(declaration.id, LonghandId::${property.camel_case}); + match declaration.keyword { + % if not property.style_struct.inherited: + CSSWideKeyword::Unset | + % endif + CSSWideKeyword::Initial => { + % if not property.style_struct.inherited: + debug_assert!(false, "Should be handled in apply_properties"); + % else: + context.builder.reset_${property.ident}(); + % endif + }, + % if property.style_struct.inherited: + CSSWideKeyword::Unset | + % endif + CSSWideKeyword::Inherit => { + % if property.style_struct.inherited: + debug_assert!(false, "Should be handled in apply_properties"); + % else: + context.rule_cache_conditions.borrow_mut().set_uncacheable(); + context.builder.inherit_${property.ident}(); + % endif + } + CSSWideKeyword::RevertLayer | + CSSWideKeyword::Revert => unreachable!("Should never get here"), + } + return; + } + PropertyDeclaration::WithVariables(..) => { + panic!("variables should already have been substituted") + } + _ => panic!("entered the wrong cascade_property() implementation"), + }; + + % if property.ident in SYSTEM_FONT_LONGHANDS and engine == "gecko": + if let Some(sf) = specified_value.get_system() { + longhands::system_font::resolve_system_font(sf, context); + } + % endif + + % if not property.style_struct.inherited and property.logical: + context.rule_cache_conditions.borrow_mut() + .set_writing_mode_dependency(context.builder.writing_mode); + % endif + + % if property.is_vector and not property.simple_vector_bindings and engine == "gecko": + // In the case of a vector property we want to pass down an + // iterator so that this can be computed without allocation. + // + // However, computing requires a context, but the style struct + // being mutated is on the context. We temporarily remove it, + // mutate it, and then put it back. Vector longhands cannot + // touch their own style struct whilst computing, else this will + // panic. + let mut s = + context.builder.take_${data.current_style_struct.name_lower}(); + { + let iter = specified_value.compute_iter(context); + s.set_${property.ident}(iter); + } + context.builder.put_${data.current_style_struct.name_lower}(s); + % else: + % if property.boxed: + let computed = (**specified_value).to_computed_value(context); + % else: + let computed = specified_value.to_computed_value(context); + % endif + context.builder.set_${property.ident}(computed) + % endif + } + + pub fn parse_declared<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<PropertyDeclaration, ParseError<'i>> { + % if property.allow_quirks != "No": + parse_quirky(context, input, specified::AllowQuirks::${property.allow_quirks}) + % else: + parse(context, input) + % endif + % if property.boxed: + .map(Box::new) + % endif + .map(PropertyDeclaration::${property.camel_case}) + } + } +</%def> + +<%def name="gecko_keyword_conversion(keyword, values=None, type='SpecifiedValue', cast_to=None)"> + <% + if not values: + values = keyword.values_for(engine) + maybe_cast = "as %s" % cast_to if cast_to else "" + const_type = cast_to if cast_to else "u32" + %> + #[cfg(feature = "gecko")] + impl ${type} { + /// Obtain a specified value from a Gecko keyword value + /// + /// Intended for use with presentation attributes, not style structs + pub fn from_gecko_keyword(kw: u32) -> Self { + use crate::gecko_bindings::structs; + % for value in values: + // We can't match on enum values if we're matching on a u32 + const ${to_rust_ident(value).upper()}: ${const_type} + = structs::${keyword.gecko_constant(value)} as ${const_type}; + % endfor + match kw ${maybe_cast} { + % for value in values: + ${to_rust_ident(value).upper()} => ${type}::${to_camel_case(value)}, + % endfor + _ => panic!("Found unexpected value in style struct for ${keyword.name} property"), + } + } + } +</%def> + +<%def name="gecko_bitflags_conversion(bit_map, gecko_bit_prefix, type, kw_type='u8')"> + #[cfg(feature = "gecko")] + impl ${type} { + /// Obtain a specified value from a Gecko keyword value + /// + /// Intended for use with presentation attributes, not style structs + pub fn from_gecko_keyword(kw: ${kw_type}) -> Self { + % for gecko_bit in bit_map.values(): + use crate::gecko_bindings::structs::${gecko_bit_prefix}${gecko_bit}; + % endfor + + let mut bits = ${type}::empty(); + % for servo_bit, gecko_bit in bit_map.items(): + if kw & (${gecko_bit_prefix}${gecko_bit} as ${kw_type}) != 0 { + bits |= ${servo_bit}; + } + % endfor + bits + } + + pub fn to_gecko_keyword(self) -> ${kw_type} { + % for gecko_bit in bit_map.values(): + use crate::gecko_bindings::structs::${gecko_bit_prefix}${gecko_bit}; + % endfor + + let mut bits: ${kw_type} = 0; + // FIXME: if we ensure that the Servo bitflags storage is the same + // as Gecko's one, we can just copy it. + % for servo_bit, gecko_bit in bit_map.items(): + if self.contains(${servo_bit}) { + bits |= ${gecko_bit_prefix}${gecko_bit} as ${kw_type}; + } + % endfor + bits + } + } +</%def> + +<%def name="single_keyword(name, values, vector=False, + needs_conversion=False, **kwargs)"> + <% + keyword_kwargs = {a: kwargs.pop(a, None) for a in [ + 'gecko_constant_prefix', + 'gecko_enum_prefix', + 'extra_gecko_values', + 'extra_servo_2013_values', + 'extra_servo_2020_values', + 'gecko_aliases', + 'servo_2013_aliases', + 'servo_2020_aliases', + 'custom_consts', + 'gecko_inexhaustive', + 'gecko_strip_moz_prefix', + ]} + %> + + <%def name="inner_body(keyword, needs_conversion=False)"> + pub use self::computed_value::T as SpecifiedValue; + pub mod computed_value { + #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] + #[derive(Clone, Copy, Debug, Eq, FromPrimitive, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToComputedValue, ToCss, ToResolvedValue, ToShmem)] + pub enum T { + % for variant in keyword.values_for(engine): + <% + aliases = [] + for alias, v in keyword.aliases_for(engine).items(): + if variant == v: + aliases.append(alias) + %> + % if aliases: + #[parse(aliases = "${','.join(sorted(aliases))}")] + % endif + ${to_camel_case(variant)}, + % endfor + } + } + #[inline] + pub fn get_initial_value() -> computed_value::T { + computed_value::T::${to_camel_case(values.split()[0])} + } + #[inline] + pub fn get_initial_specified_value() -> SpecifiedValue { + SpecifiedValue::${to_camel_case(values.split()[0])} + } + #[inline] + pub fn parse<'i, 't>(_context: &ParserContext, input: &mut Parser<'i, 't>) + -> Result<SpecifiedValue, ParseError<'i>> { + SpecifiedValue::parse(input) + } + + % if needs_conversion: + <% + conversion_values = keyword.values_for(engine) + list(keyword.aliases_for(engine).keys()) + %> + ${gecko_keyword_conversion(keyword, values=conversion_values)} + % endif + </%def> + % if vector: + <%call expr="vector_longhand(name, keyword=Keyword(name, values, **keyword_kwargs), **kwargs)"> + ${inner_body(Keyword(name, values, **keyword_kwargs))} + % if caller: + ${caller.body()} + % endif + </%call> + % else: + <%call expr="longhand(name, keyword=Keyword(name, values, **keyword_kwargs), **kwargs)"> + ${inner_body(Keyword(name, values, **keyword_kwargs), + needs_conversion=needs_conversion)} + % if caller: + ${caller.body()} + % endif + </%call> + % endif +</%def> + +<%def name="shorthand(name, sub_properties, derive_serialize=False, + derive_value_info=True, **kwargs)"> +<% + shorthand = data.declare_shorthand(name, sub_properties.split(), **kwargs) + # mako doesn't accept non-string value in parameters with <% %> form, so + # we have to workaround it this way. + if not isinstance(derive_value_info, bool): + derive_value_info = eval(derive_value_info) +%> + % if shorthand: + /// ${shorthand.spec} + pub mod ${shorthand.ident} { + use cssparser::Parser; + use crate::parser::ParserContext; + use crate::properties::{PropertyDeclaration, SourcePropertyDeclaration, MaybeBoxed, longhands}; + #[allow(unused_imports)] + use selectors::parser::SelectorParseErrorKind; + #[allow(unused_imports)] + use std::fmt::{self, Write}; + #[allow(unused_imports)] + use style_traits::{ParseError, StyleParseErrorKind}; + #[allow(unused_imports)] + use style_traits::{CssWriter, KeywordsCollectFn, SpecifiedValueInfo, ToCss}; + + % if derive_value_info: + #[derive(SpecifiedValueInfo)] + % endif + pub struct Longhands { + % for sub_property in shorthand.sub_properties: + pub ${sub_property.ident}: + % if sub_property.boxed: + Box< + % endif + longhands::${sub_property.ident}::SpecifiedValue + % if sub_property.boxed: + > + % endif + , + % endfor + } + + /// Represents a serializable set of all of the longhand properties that + /// correspond to a shorthand. + % if derive_serialize: + #[derive(ToCss)] + % endif + pub struct LonghandsToSerialize<'a> { + % for sub_property in shorthand.sub_properties: + pub ${sub_property.ident}: + % if sub_property.may_be_disabled_in(shorthand, engine): + Option< + % endif + &'a longhands::${sub_property.ident}::SpecifiedValue, + % if sub_property.may_be_disabled_in(shorthand, engine): + >, + % endif + % endfor + } + + impl<'a> LonghandsToSerialize<'a> { + /// Tries to get a serializable set of longhands given a set of + /// property declarations. + pub fn from_iter(iter: impl Iterator<Item = &'a PropertyDeclaration>) -> Result<Self, ()> { + // Define all of the expected variables that correspond to the shorthand + % for sub_property in shorthand.sub_properties: + let mut ${sub_property.ident} = + None::< &'a longhands::${sub_property.ident}::SpecifiedValue>; + % endfor + + // Attempt to assign the incoming declarations to the expected variables + for declaration in iter { + match *declaration { + % for sub_property in shorthand.sub_properties: + PropertyDeclaration::${sub_property.camel_case}(ref value) => { + ${sub_property.ident} = Some(value) + }, + % endfor + _ => {} + }; + } + + // If any of the expected variables are missing, return an error + match ( + % for sub_property in shorthand.sub_properties: + ${sub_property.ident}, + % endfor + ) { + + ( + % for sub_property in shorthand.sub_properties: + % if sub_property.may_be_disabled_in(shorthand, engine): + ${sub_property.ident}, + % else: + Some(${sub_property.ident}), + % endif + % endfor + ) => + Ok(LonghandsToSerialize { + % for sub_property in shorthand.sub_properties: + ${sub_property.ident}, + % endfor + }), + _ => Err(()) + } + } + } + + /// Parse the given shorthand and fill the result into the + /// `declarations` vector. + pub fn parse_into<'i, 't>( + declarations: &mut SourcePropertyDeclaration, + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<(), ParseError<'i>> { + #[allow(unused_imports)] + use crate::properties::{NonCustomPropertyId, LonghandId}; + input.parse_entirely(|input| parse_value(context, input)).map(|longhands| { + % for sub_property in shorthand.sub_properties: + % if sub_property.may_be_disabled_in(shorthand, engine): + if NonCustomPropertyId::from(LonghandId::${sub_property.camel_case}) + .allowed_in_ignoring_rule_type(context) { + % endif + declarations.push(PropertyDeclaration::${sub_property.camel_case}( + longhands.${sub_property.ident} + )); + % if sub_property.may_be_disabled_in(shorthand, engine): + } + % endif + % endfor + }) + } + + /// Try to serialize a given shorthand to a string. + pub fn to_css(declarations: &[&PropertyDeclaration], dest: &mut crate::str::CssStringWriter) -> fmt::Result { + match LonghandsToSerialize::from_iter(declarations.iter().cloned()) { + Ok(longhands) => longhands.to_css(&mut CssWriter::new(dest)), + Err(_) => Ok(()) + } + } + + ${caller.body()} + } + % endif +</%def> + +// A shorthand of kind `<property-1> <property-2>?` where both properties have +// the same type. +<%def name="two_properties_shorthand( + name, + first_property, + second_property, + parser_function='crate::parser::Parse::parse', + **kwargs +)"> +<%call expr="self.shorthand(name, sub_properties=' '.join([first_property, second_property]), **kwargs)"> + #[allow(unused_imports)] + use crate::parser::Parse; + #[allow(unused_imports)] + use crate::values::specified; + + fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + let parse_one = |c: &ParserContext, input: &mut Parser<'i, 't>| -> Result< + crate::properties::longhands::${to_rust_ident(first_property)}::SpecifiedValue, + ParseError<'i> + > { + ${parser_function}(c, input) + }; + + let first = parse_one(context, input)?; + let second = + input.try_parse(|input| parse_one(context, input)).unwrap_or_else(|_| first.clone()); + Ok(expanded! { + ${to_rust_ident(first_property)}: first, + ${to_rust_ident(second_property)}: second, + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + let first = &self.${to_rust_ident(first_property)}; + let second = &self.${to_rust_ident(second_property)}; + + first.to_css(dest)?; + if first != second { + dest.write_char(' ')?; + second.to_css(dest)?; + } + Ok(()) + } + } +</%call> +</%def> + +<%def name="four_sides_shorthand(name, sub_property_pattern, + parser_function='crate::parser::Parse::parse', + allow_quirks='No', **kwargs)"> + <% sub_properties=' '.join(sub_property_pattern % side for side in PHYSICAL_SIDES) %> + <%call expr="self.shorthand(name, sub_properties=sub_properties, **kwargs)"> + #[allow(unused_imports)] + use crate::parser::Parse; + use crate::values::generics::rect::Rect; + #[allow(unused_imports)] + use crate::values::specified; + + fn parse_value<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result<Longhands, ParseError<'i>> { + let rect = Rect::parse_with(context, input, |c, i| -> Result< + crate::properties::longhands::${to_rust_ident(sub_property_pattern % "top")}::SpecifiedValue, + ParseError<'i> + > { + % if allow_quirks != "No": + ${parser_function}_quirky(c, i, specified::AllowQuirks::${allow_quirks}) + % else: + ${parser_function}(c, i) + % endif + })?; + Ok(expanded! { + % for index, side in enumerate(["top", "right", "bottom", "left"]): + ${to_rust_ident(sub_property_pattern % side)}: rect.${index}, + % endfor + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + let rect = Rect::new( + % for side in ["top", "right", "bottom", "left"]: + &self.${to_rust_ident(sub_property_pattern % side)}, + % endfor + ); + rect.to_css(dest) + } + } + </%call> +</%def> + +<%def name="logical_setter_helper(name)"> + <% + side = None + size = None + corner = None + axis = None + maybe_side = [s for s in LOGICAL_SIDES if s in name] + maybe_size = [s for s in LOGICAL_SIZES if s in name] + maybe_corner = [s for s in LOGICAL_CORNERS if s in name] + maybe_axis = [s for s in LOGICAL_AXES if name.endswith(s)] + if len(maybe_side) == 1: + side = maybe_side[0] + elif len(maybe_size) == 1: + size = maybe_size[0] + elif len(maybe_corner) == 1: + corner = maybe_corner[0] + elif len(maybe_axis) == 1: + axis = maybe_axis[0] + def phys_ident(side, phy_side): + return to_rust_ident(to_phys(name, side, phy_side)) + %> + % if side is not None: + use crate::logical_geometry::PhysicalSide; + match wm.${to_rust_ident(side)}_physical_side() { + % for phy_side in PHYSICAL_SIDES: + PhysicalSide::${phy_side.title()} => { + ${caller.inner(physical_ident=phys_ident(side, phy_side))} + } + % endfor + } + % elif corner is not None: + use crate::logical_geometry::PhysicalCorner; + match wm.${to_rust_ident(corner)}_physical_corner() { + % for phy_corner in PHYSICAL_CORNERS: + PhysicalCorner::${to_camel_case(phy_corner)} => { + ${caller.inner(physical_ident=phys_ident(corner, phy_corner))} + } + % endfor + } + % elif size is not None: + <% + # (horizontal, vertical) + physical_size = ("height", "width") + if size == "inline-size": + physical_size = ("width", "height") + %> + if wm.is_vertical() { + ${caller.inner(physical_ident=phys_ident(size, physical_size[1]))} + } else { + ${caller.inner(physical_ident=phys_ident(size, physical_size[0]))} + } + % elif axis is not None: + <% + if axis == "inline": + me, other = "x", "y" + else: + assert(axis == "block") + me, other = "y", "x" + %> + if wm.is_vertical() { + ${caller.inner(physical_ident=phys_ident(axis, other))} + } else { + ${caller.inner(physical_ident=phys_ident(axis, me))} + } + % else: + <% raise Exception("Don't know what to do with logical property %s" % name) %> + % endif +</%def> + +<%def name="logical_setter(name)"> + /// Set the appropriate physical property for ${name} given a writing mode. + pub fn set_${to_rust_ident(name)}(&mut self, + v: longhands::${to_rust_ident(name)}::computed_value::T, + wm: WritingMode) { + <%self:logical_setter_helper name="${name}"> + <%def name="inner(physical_ident)"> + self.set_${physical_ident}(v) + </%def> + </%self:logical_setter_helper> + } + + /// Copy the appropriate physical property from another struct for ${name} + /// given a writing mode. + pub fn copy_${to_rust_ident(name)}_from(&mut self, + other: &Self, + wm: WritingMode) { + <%self:logical_setter_helper name="${name}"> + <%def name="inner(physical_ident)"> + self.copy_${physical_ident}_from(other) + </%def> + </%self:logical_setter_helper> + } + + /// Copy the appropriate physical property from another struct for ${name} + /// given a writing mode. + pub fn reset_${to_rust_ident(name)}(&mut self, + other: &Self, + wm: WritingMode) { + self.copy_${to_rust_ident(name)}_from(other, wm) + } + + /// Get the computed value for the appropriate physical property for + /// ${name} given a writing mode. + pub fn clone_${to_rust_ident(name)}(&self, wm: WritingMode) + -> longhands::${to_rust_ident(name)}::computed_value::T { + <%self:logical_setter_helper name="${name}"> + <%def name="inner(physical_ident)"> + self.clone_${physical_ident}() + </%def> + </%self:logical_setter_helper> + } +</%def> diff --git a/servo/components/style/properties/helpers/animated_properties.mako.rs b/servo/components/style/properties/helpers/animated_properties.mako.rs new file mode 100644 index 0000000000..f1159ac4ec --- /dev/null +++ b/servo/components/style/properties/helpers/animated_properties.mako.rs @@ -0,0 +1,728 @@ +/* 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::{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 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(()), + } + } +} + +/// <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(..) => {} + } + } + } +} |