diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs index 1285d5cef7a..4dc72f20301 100644 --- a/components/layout/display_list_builder.rs +++ b/components/layout/display_list_builder.rs @@ -1176,10 +1176,11 @@ impl FragmentDisplayListBuilding for Fragment { fn adjust_clip_for_style(&self, parent_clip: &mut ClippingRegion, stacking_relative_border_box: &Rect) { + use style::values::Either; // Account for `clip` per CSS 2.1 ยง 11.1.2. let style_clip_rect = match (self.style().get_box().position, - self.style().get_effects().clip.0) { - (position::T::absolute, Some(style_clip_rect)) => style_clip_rect, + self.style().get_effects().clip) { + (position::T::absolute, Either::First(style_clip_rect)) => style_clip_rect, _ => return, }; diff --git a/components/style/properties/helpers.mako.rs b/components/style/properties/helpers.mako.rs index 37b4bed744d..3d84c4a8190 100644 --- a/components/style/properties/helpers.mako.rs +++ b/components/style/properties/helpers.mako.rs @@ -66,7 +66,7 @@ We assume that the default/initial value is an empty vector for these. `initial_value` need not be defined for these. -<%def name="vector_longhand(name, gecko_only=False, allow_empty=False, **kwargs)"> +<%def name="vector_longhand(name, gecko_only=False, allow_empty=False, delegate_animate=False, **kwargs)"> <%call expr="longhand(name, **kwargs)"> % if not gecko_only: use std::fmt; @@ -97,6 +97,15 @@ #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "servo", derive(HeapSizeOf))] pub struct T(pub Vec); + + % if delegate_animate: + use properties::animated_properties::Interpolate; + impl Interpolate for T { + fn interpolate(&self, other: &Self, progress: f64) -> Result { + self.0.interpolate(&other.0, progress).map(T) + } + } + % endif } impl ToCss for computed_value::T { diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs index 35fb529c851..fae2c82e54d 100644 --- a/components/style/properties/helpers/animated_properties.mako.rs +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -10,8 +10,6 @@ use euclid::{Point2D, Size2D}; #[cfg(feature = "gecko")] use gecko_bindings::structs::nsCSSPropertyID; use properties::{DeclaredValue, PropertyDeclaration}; use properties::longhands; -use properties::longhands::background_position_x::computed_value::T as BackgroundPositionX; -use properties::longhands::background_position_y::computed_value::T as BackgroundPositionY; use properties::longhands::background_size::computed_value::T as BackgroundSize; use properties::longhands::font_weight::computed_value::T as FontWeight; use properties::longhands::line_height::computed_value::T as LineHeight; @@ -33,7 +31,7 @@ use super::ComputedValues; use values::CSSFloat; use values::Either; use values::computed::{Angle, LengthOrPercentageOrAuto, LengthOrPercentageOrNone}; -use values::computed::{BorderRadiusSize, LengthOrNone}; +use values::computed::{BorderRadiusSize, ClipRect, LengthOrNone}; use values::computed::{CalcLengthOrPercentage, Context, LengthOrPercentage}; use values::computed::position::{HorizontalPosition, Position, VerticalPosition}; use values::computed::ToComputedValue; @@ -724,17 +722,16 @@ impl Interpolate for VerticalPosition { impl RepeatableListInterpolate for VerticalPosition {} -impl Interpolate for BackgroundPositionX { +/// https://drafts.csswg.org/css-transitions/#animtype-rect +impl Interpolate for ClipRect { #[inline] - fn interpolate(&self, other: &Self, progress: f64) -> Result { - Ok(BackgroundPositionX(try!(self.0.interpolate(&other.0, progress)))) - } -} - -impl Interpolate for BackgroundPositionY { - #[inline] - fn interpolate(&self, other: &Self, progress: f64) -> Result { - Ok(BackgroundPositionY(try!(self.0.interpolate(&other.0, progress)))) + fn interpolate(&self, other: &Self, time: f64) -> Result { + Ok(ClipRect { + top: try!(self.top.interpolate(&other.top, time)), + right: try!(self.right.interpolate(&other.right, time)), + bottom: try!(self.bottom.interpolate(&other.bottom, time)), + left: try!(self.left.interpolate(&other.left, time)), + }) } } diff --git a/components/style/properties/longhand/background.mako.rs b/components/style/properties/longhand/background.mako.rs index 57c193bedeb..62429df1966 100644 --- a/components/style/properties/longhand/background.mako.rs +++ b/components/style/properties/longhand/background.mako.rs @@ -89,7 +89,8 @@ ${helpers.predefined_type("background-color", "CSSColor", <%helpers:vector_longhand name="background-position-x" animatable="True" - spec="https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-x"> + spec="https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-x" + delegate_animate="True"> use std::fmt; use style_traits::ToCss; use values::HasViewportPercentage; @@ -139,7 +140,8 @@ ${helpers.predefined_type("background-color", "CSSColor", <%helpers:vector_longhand name="background-position-y" animatable="True" - spec="https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-y"> + spec="https://drafts.csswg.org/css-backgrounds-4/#propdef-background-position-y" + delegate_animate="True"> use std::fmt; use style_traits::ToCss; use values::HasViewportPercentage; diff --git a/components/style/properties/longhand/box.mako.rs b/components/style/properties/longhand/box.mako.rs index fa6d9a10a41..416df48fd8d 100644 --- a/components/style/properties/longhand/box.mako.rs +++ b/components/style/properties/longhand/box.mako.rs @@ -1054,7 +1054,7 @@ ${helpers.predefined_type("scroll-snap-destination", "computed::Position::zero()", products="gecko", spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-destination)", - animatable=False)} + animatable=True)} ${helpers.predefined_type("scroll-snap-coordinate", "Position", @@ -1062,8 +1062,10 @@ ${helpers.predefined_type("scroll-snap-coordinate", vector=True, products="gecko", spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-destination)", - animatable=False, - allow_empty=True)} + animatable=True, + allow_empty=True, + delegate_animate=True)} + <%helpers:longhand name="transform" products="gecko servo" extra_prefixes="webkit" diff --git a/components/style/properties/longhand/effects.mako.rs b/components/style/properties/longhand/effects.mako.rs index b01874e8095..eb4879f7afb 100644 --- a/components/style/properties/longhand/effects.mako.rs +++ b/components/style/properties/longhand/effects.mako.rs @@ -77,217 +77,13 @@ ${helpers.predefined_type("opacity", // FIXME: This prop should be animatable -<%helpers:longhand name="clip" products="servo" animatable="False" boxed="True" - spec="https://drafts.fxtf.org/css-masking/#clip-property"> - use std::fmt; - use style_traits::ToCss; - use values::HasViewportPercentage; - - // NB: `top` and `left` are 0 if `auto` per CSS 2.1 11.1.2. - - pub mod computed_value { - use app_units::Au; - use properties::animated_properties::Interpolate; - - #[derive(Clone, PartialEq, Eq, Copy, Debug)] - #[cfg_attr(feature = "servo", derive(HeapSizeOf))] - pub struct ClipRect { - pub top: Au, - pub right: Option, - pub bottom: Option, - pub left: Au, - } - - #[derive(Debug, Clone, PartialEq)] - #[cfg_attr(feature = "servo", derive(HeapSizeOf))] - pub struct T(pub Option); - - - /// https://drafts.csswg.org/css-transitions/#animtype-rect - impl Interpolate for ClipRect { - #[inline] - fn interpolate(&self, other: &Self, time: f64) -> Result { - Ok(ClipRect { - top: try!(self.top.interpolate(&other.top, time)), - right: try!(self.right.interpolate(&other.right, time)), - bottom: try!(self.bottom.interpolate(&other.bottom, time)), - left: try!(self.left.interpolate(&other.left, time)), - }) - } - } - } - - impl ToCss for computed_value::T { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - match self.0 { - None => dest.write_str("auto"), - Some(rect) => { - try!(dest.write_str("rect(")); - try!(rect.top.to_css(dest)); - try!(dest.write_str(", ")); - if let Some(right) = rect.right { - try!(right.to_css(dest)); - try!(dest.write_str(", ")); - } else { - try!(dest.write_str("auto, ")); - } - - if let Some(bottom) = rect.bottom { - try!(bottom.to_css(dest)); - try!(dest.write_str(", ")); - } else { - try!(dest.write_str("auto, ")); - } - - try!(rect.left.to_css(dest)); - try!(dest.write_str(")")); - Ok(()) - } - } - } - } - - impl HasViewportPercentage for SpecifiedClipRect { - fn has_viewport_percentage(&self) -> bool { - self.top.has_viewport_percentage() || - self.right.as_ref().map_or(false, |x| x.has_viewport_percentage()) || - self.bottom.as_ref().map_or(false, |x| x.has_viewport_percentage()) || - self.left.has_viewport_percentage() - } - } - - #[derive(Clone, Debug, PartialEq)] - #[cfg_attr(feature = "servo", derive(HeapSizeOf))] - pub struct SpecifiedClipRect { - pub top: specified::Length, - pub right: Option, - pub bottom: Option, - pub left: specified::Length, - } - - impl HasViewportPercentage for SpecifiedValue { - fn has_viewport_percentage(&self) -> bool { - self.0.as_ref().map_or(false, |x| x.has_viewport_percentage()) - } - } - - #[derive(Clone, Debug, PartialEq)] - #[cfg_attr(feature = "servo", derive(HeapSizeOf))] - pub struct SpecifiedValue(Option); - - impl ToCss for SpecifiedClipRect { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - try!(dest.write_str("rect(")); - - try!(self.top.to_css(dest)); - try!(dest.write_str(", ")); - - if let Some(ref right) = self.right { - try!(right.to_css(dest)); - try!(dest.write_str(", ")); - } else { - try!(dest.write_str("auto, ")); - } - - if let Some(ref bottom) = self.bottom { - try!(bottom.to_css(dest)); - try!(dest.write_str(", ")); - } else { - try!(dest.write_str("auto, ")); - } - - try!(self.left.to_css(dest)); - - try!(dest.write_str(")")); - Ok(()) - } - } - - impl ToCss for SpecifiedValue { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - if let Some(ref rect) = self.0 { - rect.to_css(dest) - } else { - dest.write_str("auto") - } - } - } - - #[inline] - pub fn get_initial_value() -> computed_value::T { - computed_value::T(None) - } - - impl ToComputedValue for SpecifiedValue { - type ComputedValue = computed_value::T; - - #[inline] - fn to_computed_value(&self, context: &Context) -> computed_value::T { - computed_value::T(self.0.as_ref().map(|value| computed_value::ClipRect { - top: value.top.to_computed_value(context), - right: value.right.as_ref().map(|right| right.to_computed_value(context)), - bottom: value.bottom.as_ref().map(|bottom| bottom.to_computed_value(context)), - left: value.left.to_computed_value(context), - })) - } - - #[inline] - fn from_computed_value(computed: &computed_value::T) -> Self { - SpecifiedValue(computed.0.map(|value| SpecifiedClipRect { - top: ToComputedValue::from_computed_value(&value.top), - right: value.right.map(|right| ToComputedValue::from_computed_value(&right)), - bottom: value.bottom.map(|bottom| ToComputedValue::from_computed_value(&bottom)), - left: ToComputedValue::from_computed_value(&value.left), - })) - } - } - - pub fn parse(context: &ParserContext, input: &mut Parser) -> Result { - use app_units::Au; - use std::ascii::AsciiExt; - use values::specified::Length; - - fn parse_argument(context: &ParserContext, input: &mut Parser) -> Result, ()> { - if input.try(|input| input.expect_ident_matching("auto")).is_ok() { - Ok(None) - } else { - Length::parse(context, input).map(Some) - } - } - - if input.try(|input| input.expect_ident_matching("auto")).is_ok() { - return Ok(SpecifiedValue(None)) - } - if !try!(input.expect_function()).eq_ignore_ascii_case("rect") { - return Err(()) - } - - input.parse_nested_block(|input| { - let top = try!(parse_argument(context, input)); - let right; - let bottom; - let left; - - if input.try(|input| input.expect_comma()).is_ok() { - right = try!(parse_argument(context, input)); - try!(input.expect_comma()); - bottom = try!(parse_argument(context, input)); - try!(input.expect_comma()); - left = try!(parse_argument(context, input)); - } else { - right = try!(parse_argument(context, input)); - bottom = try!(parse_argument(context, input)); - left = try!(parse_argument(context, input)); - } - Ok(SpecifiedValue(Some(SpecifiedClipRect { - top: top.unwrap_or(Length::zero()), - right: right, - bottom: bottom, - left: left.unwrap_or(Length::zero()), - }))) - }) - } - +${helpers.predefined_type("clip", + "ClipRectOrAuto", + "computed::ClipRectOrAuto::auto()", + animatable=False, + products="servo", + boxed="True", + spec="https://drafts.fxtf.org/css-masking/#clip-property")} // FIXME: This prop should be animatable <%helpers:longhand name="filter" animatable="False" extra_prefixes="webkit" diff --git a/components/style/properties/longhand/position.mako.rs b/components/style/properties/longhand/position.mako.rs index 4ff10a91839..9c801358988 100644 --- a/components/style/properties/longhand/position.mako.rs +++ b/components/style/properties/longhand/position.mako.rs @@ -216,7 +216,7 @@ ${helpers.predefined_type("object-position", products="gecko", boxed="True", spec="https://drafts.csswg.org/css-images-3/#the-object-position", - animatable=False)} + animatable=True)} <% grid_longhands = ["grid-row-start", "grid-row-end", "grid-column-start", "grid-column-end"] %> diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index 3f014d3368f..02cbcbf1150 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -1589,7 +1589,7 @@ impl ComputedValues { // TODO(gw): Add clip-path, isolation, mask-image, mask-border-source when supported. if effects.opacity < 1.0 || !effects.filter.is_empty() || - effects.clip.0.is_some() { + !effects.clip.is_auto() { effects.mix_blend_mode != mix_blend_mode::T::normal || return transform_style::T::flat; } @@ -2290,10 +2290,10 @@ pub fn modify_style_for_text(style: &mut Arc) { /// doesn't clip its children. #[cfg(feature = "servo")] pub fn modify_style_for_inline_absolute_hypothetical_fragment(style: &mut Arc) { - if style.get_effects().clip.0.is_some() { + if !style.get_effects().clip.is_auto() { let mut style = Arc::make_mut(style); let effects_style = Arc::make_mut(&mut style.effects); - effects_style.clip.0 = None + effects_style.clip = Either::auto() } } diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index 034f8f6953a..36b3b2dc6ac 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -15,7 +15,7 @@ use super::{CSSFloat, specified}; pub use cssparser::Color as CSSColor; pub use self::image::{AngleOrCorner, EndingShape as GradientShape, Gradient, GradientKind, Image}; pub use self::image::{LengthOrKeyword, LengthOrPercentageOrKeyword}; -pub use super::{Either, None_}; +pub use super::{Auto, Either, None_}; pub use super::specified::{Angle, BorderStyle, GridLine, Time, UrlOrNone}; pub use super::specified::url::UrlExtraData; pub use self::length::{CalcLengthOrPercentage, Length, LengthOrNumber, LengthOrPercentage, LengthOrPercentageOrAuto}; @@ -177,3 +177,57 @@ pub type Number = CSSFloat; /// A type used for opacity. pub type Opacity = CSSFloat; + + +#[derive(Clone, PartialEq, Eq, Copy, Debug)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +#[allow(missing_docs)] +/// A computed cliprect for clip and image-region +pub struct ClipRect { + pub top: Au, + pub right: Option, + pub bottom: Option, + pub left: Au, +} + +impl ToCss for ClipRect { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + try!(dest.write_str("rect(")); + try!(self.top.to_css(dest)); + try!(dest.write_str(", ")); + if let Some(right) = self.right { + try!(right.to_css(dest)); + try!(dest.write_str(", ")); + } else { + try!(dest.write_str("auto, ")); + } + + if let Some(bottom) = self.bottom { + try!(bottom.to_css(dest)); + try!(dest.write_str(", ")); + } else { + try!(dest.write_str("auto, ")); + } + + try!(self.left.to_css(dest)); + dest.write_str(")") + } +} + +/// rect(...) | auto +pub type ClipRectOrAuto = Either; + +impl ClipRectOrAuto { + /// Return an auto (default for clip-rect and image-region) value + pub fn auto() -> Self { + Either::Second(Auto) + } + + /// Check if it is auto + pub fn is_auto(&self) -> bool { + match *self { + Either::Second(_) => true, + _ => false + } + } +} diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index c7dc71d1bec..8e3315f0ea6 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -16,7 +16,7 @@ use std::f32::consts::PI; use std::fmt; use std::ops::Mul; use style_traits::ToCss; -use super::{CSSFloat, HasViewportPercentage, Either, None_}; +use super::{Auto, CSSFloat, HasViewportPercentage, Either, None_}; use super::computed::{ComputedValueAsSpecified, Context, ToComputedValue}; use super::computed::Shadow as ComputedShadow; @@ -665,3 +665,127 @@ impl Shadow { }) } } + + +impl HasViewportPercentage for ClipRect { + fn has_viewport_percentage(&self) -> bool { + self.top.has_viewport_percentage() || + self.right.as_ref().map_or(false, |x| x.has_viewport_percentage()) || + self.bottom.as_ref().map_or(false, |x| x.has_viewport_percentage()) || + self.left.has_viewport_percentage() + } +} + +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "servo", derive(HeapSizeOf))] +/// rect(, , , ) used by clip and image-region +pub struct ClipRect { + /// ( | ). Auto maps to 0 + pub top: Length, + /// ( | ) + pub right: Option, + /// ( | ) + pub bottom: Option, + /// ( | ). Auto maps to 0 + pub left: Length, +} + + +impl ToCss for ClipRect { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + try!(dest.write_str("rect(")); + + try!(self.top.to_css(dest)); + try!(dest.write_str(", ")); + + if let Some(ref right) = self.right { + try!(right.to_css(dest)); + try!(dest.write_str(", ")); + } else { + try!(dest.write_str("auto, ")); + } + + if let Some(ref bottom) = self.bottom { + try!(bottom.to_css(dest)); + try!(dest.write_str(", ")); + } else { + try!(dest.write_str("auto, ")); + } + + try!(self.left.to_css(dest)); + + try!(dest.write_str(")")); + Ok(()) + } +} + +impl ToComputedValue for ClipRect { + type ComputedValue = super::computed::ClipRect; + + #[inline] + fn to_computed_value(&self, context: &Context) -> super::computed::ClipRect { + super::computed::ClipRect { + top: self.top.to_computed_value(context), + right: self.right.as_ref().map(|right| right.to_computed_value(context)), + bottom: self.bottom.as_ref().map(|bottom| bottom.to_computed_value(context)), + left: self.left.to_computed_value(context), + } + } + + #[inline] + fn from_computed_value(computed: &super::computed::ClipRect) -> Self { + ClipRect { + top: ToComputedValue::from_computed_value(&computed.top), + right: computed.right.map(|right| ToComputedValue::from_computed_value(&right)), + bottom: computed.bottom.map(|bottom| ToComputedValue::from_computed_value(&bottom)), + left: ToComputedValue::from_computed_value(&computed.left), + } + } +} + +impl Parse for ClipRect { + fn parse(context: &ParserContext, input: &mut Parser) -> Result { + use values::specified::Length; + + fn parse_argument(context: &ParserContext, input: &mut Parser) -> Result, ()> { + if input.try(|input| input.expect_ident_matching("auto")).is_ok() { + Ok(None) + } else { + Length::parse(context, input).map(Some) + } + } + + if !try!(input.expect_function()).eq_ignore_ascii_case("rect") { + return Err(()) + } + + // NB: `top` and `left` are 0 if `auto` per CSS 2.1 11.1.2. + input.parse_nested_block(|input| { + let top = try!(parse_argument(context, input)); + let right; + let bottom; + let left; + + if input.try(|input| input.expect_comma()).is_ok() { + right = try!(parse_argument(context, input)); + try!(input.expect_comma()); + bottom = try!(parse_argument(context, input)); + try!(input.expect_comma()); + left = try!(parse_argument(context, input)); + } else { + right = try!(parse_argument(context, input)); + bottom = try!(parse_argument(context, input)); + left = try!(parse_argument(context, input)); + } + Ok(ClipRect { + top: top.unwrap_or(Length::zero()), + right: right, + bottom: bottom, + left: left.unwrap_or(Length::zero()), + }) + }) + } +} + +/// rect(...) | auto +pub type ClipRectOrAuto = Either;