diff --git a/components/style/gecko/conversions.rs b/components/style/gecko/conversions.rs index 5f4865179ab1..42c146f7125f 100644 --- a/components/style/gecko/conversions.rs +++ b/components/style/gecko/conversions.rs @@ -273,8 +273,8 @@ impl nsStyleImage { }, } unsafe { - (*gecko_gradient).mBgPosX.set(position.horizontal); - (*gecko_gradient).mBgPosY.set(position.vertical); + (*gecko_gradient).mBgPosX.set(position.horizontal.0); + (*gecko_gradient).mBgPosY.set(position.vertical.0); } gecko_gradient @@ -330,6 +330,7 @@ pub mod basic_shape { use values::computed::position; use values::generics::BorderRadiusSize as GenericBorderRadiusSize; use values::generics::basic_shape::FillRule; + use values::generics::position::{HorizontalPosition, VerticalPosition}; // using Borrow so that we can have a non-moving .into() impl> From for BasicShape { @@ -440,8 +441,8 @@ pub mod basic_shape { impl From for structs::Position { fn from(other: position::Position) -> Self { structs::Position { - mXPosition: other.horizontal.into(), - mYPosition: other.vertical.into() + mXPosition: other.horizontal.0.into(), + mYPosition: other.vertical.0.into() } } } @@ -458,8 +459,8 @@ pub mod basic_shape { fn from(other: T) -> Self { let other = other.borrow(); position::Position { - horizontal: other.mXPosition.into(), - vertical: other.mYPosition.into(), + horizontal: HorizontalPosition(other.mXPosition.into()), + vertical: VerticalPosition(other.mYPosition.into()), } } } diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index e74d147915f4..863d9438a3ed 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -376,17 +376,17 @@ fn color_to_nscolor_zero_currentcolor(color: Color) -> structs::nscolor { <%def name="impl_position(ident, gecko_ffi_name, need_clone=False)"> #[allow(non_snake_case)] pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) { - ${set_gecko_property("%s.mXPosition" % gecko_ffi_name, "v.horizontal.into()")} - ${set_gecko_property("%s.mYPosition" % gecko_ffi_name, "v.vertical.into()")} + ${set_gecko_property("%s.mXPosition" % gecko_ffi_name, "v.horizontal.0.into()")} + ${set_gecko_property("%s.mYPosition" % gecko_ffi_name, "v.vertical.0.into()")} } <%call expr="impl_simple_copy(ident, gecko_ffi_name)"> % if need_clone: #[allow(non_snake_case)] pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T { - use values::computed::Position; + use values::generics::position::{HorizontalPosition, Position, VerticalPosition}; Position { - horizontal: self.gecko.${gecko_ffi_name}.mXPosition.into(), - vertical: self.gecko.${gecko_ffi_name}.mYPosition.into(), + horizontal: HorizontalPosition(self.gecko.${gecko_ffi_name}.mXPosition.into()), + vertical: VerticalPosition(self.gecko.${gecko_ffi_name}.mYPosition.into()), } } % endif @@ -1947,8 +1947,8 @@ fn static_assert() { for (gecko, servo) in self.gecko.mScrollSnapCoordinate .iter_mut() .zip(v.0.iter()) { - gecko.mXPosition = servo.horizontal.into(); - gecko.mYPosition = servo.vertical.into(); + gecko.mXPosition = servo.horizontal.0.into(); + gecko.mYPosition = servo.vertical.0.into(); } } @@ -2600,7 +2600,7 @@ fn static_assert() { pub fn clone_${shorthand}_position_${orientation[0]}(&self) -> longhands::${shorthand}_position_${orientation[0]}::computed_value::T { - use values::computed::position::${orientation[1]}Position; + use values::generics::position::${orientation[1]}Position; longhands::${shorthand}_position_${orientation[0]}::computed_value::T( self.gecko.${image_layers_field}.mLayers.iter() .take(self.gecko.${image_layers_field}.mPosition${orientation[0].upper()}Count as usize) diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs index 29516f7c6fda..8783fc4a44f7 100644 --- a/components/style/properties/helpers/animated_properties.mako.rs +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -36,9 +36,9 @@ use values::computed::{Angle, LengthOrPercentageOrAuto, LengthOrPercentageOrNone use values::computed::{BorderRadiusSize, ClipRect, LengthOrNone}; use values::computed::{CalcLengthOrPercentage, Context, LengthOrPercentage}; use values::computed::{MaxLength, MinLength}; -use values::computed::position::{HorizontalPosition, Position, VerticalPosition}; +use values::computed::position::{HorizontalPosition, VerticalPosition}; use values::computed::ToComputedValue; - +use values::generics::position as generic_position; /// A given transition property, that is either `All`, or an animatable @@ -957,23 +957,24 @@ impl Interpolate for FontWeight { } /// https://drafts.csswg.org/css-transitions/#animtype-simple-list -impl Interpolate for Position { +impl Interpolate for generic_position::Position { #[inline] fn interpolate(&self, other: &Self, progress: f64) -> Result { - Ok(Position { + Ok(generic_position::Position { horizontal: try!(self.horizontal.interpolate(&other.horizontal, progress)), vertical: try!(self.vertical.interpolate(&other.vertical, progress)), }) } } -impl RepeatableListInterpolate for Position {} +impl RepeatableListInterpolate for generic_position::Position + where H: RepeatableListInterpolate, V: RepeatableListInterpolate {} /// https://drafts.csswg.org/css-transitions/#animtype-simple-list impl Interpolate for HorizontalPosition { #[inline] fn interpolate(&self, other: &Self, progress: f64) -> Result { - Ok(HorizontalPosition(try!(self.0.interpolate(&other.0, progress)))) + self.0.interpolate(&other.0, progress).map(generic_position::HorizontalPosition) } } @@ -983,7 +984,7 @@ impl RepeatableListInterpolate for HorizontalPosition {} impl Interpolate for VerticalPosition { #[inline] fn interpolate(&self, other: &Self, progress: f64) -> Result { - Ok(VerticalPosition(try!(self.0.interpolate(&other.0, progress)))) + self.0.interpolate(&other.0, progress).map(generic_position::VerticalPosition) } } @@ -2607,7 +2608,9 @@ impl ComputeDistance for FontWeight { } } -impl ComputeDistance for Position { +impl ComputeDistance for generic_position::Position + where H: ComputeDistance, V: ComputeDistance +{ #[inline] fn compute_distance(&self, other: &Self) -> Result { self.compute_squared_distance(other).map(|sd| sd.sqrt()) diff --git a/components/style/properties/longhand/background.mako.rs b/components/style/properties/longhand/background.mako.rs index 96119dd2c02f..0f4a5e663042 100644 --- a/components/style/properties/longhand/background.mako.rs +++ b/components/style/properties/longhand/background.mako.rs @@ -111,26 +111,27 @@ ${helpers.predefined_type("background-color", "CSSColor", #[inline] #[allow(missing_docs)] pub fn get_initial_value() -> computed_value::T { - use values::computed::position::HorizontalPosition; + use values::generics::position::HorizontalPosition; HorizontalPosition(computed::LengthOrPercentage::Percentage(0.0)) } #[inline] #[allow(missing_docs)] pub fn get_initial_specified_value() -> SpecifiedValue { - use values::specified::position::Keyword; - HorizontalPosition { + use values::generics::position::{HorizontalPosition, Keyword, PositionValue}; + HorizontalPosition(PositionValue { keyword: Some(Keyword::Left), position: None, - } + }) } #[inline] #[allow(missing_docs)] pub fn get_initial_position_value() -> SpecifiedValue { + use values::generics::position::{HorizontalPosition, PositionValue}; use values::specified::{LengthOrPercentage, Percentage}; - HorizontalPosition { + HorizontalPosition(PositionValue { keyword: None, position: Some(LengthOrPercentage::Percentage(Percentage(0.0))), - } + }) } #[allow(missing_docs)] @@ -162,26 +163,27 @@ ${helpers.predefined_type("background-color", "CSSColor", #[inline] #[allow(missing_docs)] pub fn get_initial_value() -> computed_value::T { - use values::computed::position::VerticalPosition; + use values::generics::position::VerticalPosition; VerticalPosition(computed::LengthOrPercentage::Percentage(0.0)) } #[inline] #[allow(missing_docs)] pub fn get_initial_specified_value() -> SpecifiedValue { - use values::specified::position::Keyword; - VerticalPosition { + use values::generics::position::{Keyword, PositionValue, VerticalPosition}; + VerticalPosition(PositionValue { keyword: Some(Keyword::Top), position: None, - } + }) } #[inline] #[allow(missing_docs)] pub fn get_initial_position_value() -> SpecifiedValue { + use values::generics::position::{PositionValue, VerticalPosition}; use values::specified::{LengthOrPercentage, Percentage}; - VerticalPosition { + VerticalPosition(PositionValue { keyword: None, position: Some(LengthOrPercentage::Percentage(Percentage(0.0))), - } + }) } #[inline] diff --git a/components/style/properties/longhand/box.mako.rs b/components/style/properties/longhand/box.mako.rs index 73adf4aa3351..57a798e5a51e 100644 --- a/components/style/properties/longhand/box.mako.rs +++ b/components/style/properties/longhand/box.mako.rs @@ -1113,7 +1113,6 @@ ${helpers.predefined_type("scroll-snap-coordinate", delegate_animate=True)} - <%helpers:longhand name="transform" extra_prefixes="webkit" animation_value_type="ComputedValue" flags="CREATES_STACKING_CONTEXT FIXPOS_CB" @@ -2096,83 +2095,13 @@ ${helpers.predefined_type("perspective", flags="CREATES_STACKING_CONTEXT FIXPOS_CB", animation_value_type="ComputedValue")} -<%helpers:longhand name="perspective-origin" boxed="True" - animation_value_type="ComputedValue" - extra_prefixes="moz webkit" - spec="https://drafts.csswg.org/css-transforms/#perspective-origin-property"> - use std::fmt; - use style_traits::ToCss; - use values::HasViewportPercentage; - use values::specified::{LengthOrPercentage, Percentage}; - - pub mod computed_value { - use properties::animated_properties::Interpolate; - use values::computed::LengthOrPercentage; - use values::computed::Position; - - pub type T = Position; - } - - impl HasViewportPercentage for SpecifiedValue { - fn has_viewport_percentage(&self) -> bool { - self.horizontal.has_viewport_percentage() || self.vertical.has_viewport_percentage() - } - } - - #[derive(Clone, Debug, PartialEq)] - #[cfg_attr(feature = "servo", derive(HeapSizeOf))] - pub struct SpecifiedValue { - horizontal: LengthOrPercentage, - vertical: LengthOrPercentage, - } - - impl ToCss for SpecifiedValue { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - try!(self.horizontal.to_css(dest)); - try!(dest.write_str(" ")); - self.vertical.to_css(dest) - } - } - - #[inline] - pub fn get_initial_value() -> computed_value::T { - computed_value::T { - horizontal: computed::LengthOrPercentage::Percentage(0.5), - vertical: computed::LengthOrPercentage::Percentage(0.5), - } - } - - pub fn parse(context: &ParserContext, input: &mut Parser) -> Result { - let result = try!(super::parse_origin(context, input)); - match result.depth { - Some(_) => Err(()), - None => Ok(SpecifiedValue { - horizontal: result.horizontal.unwrap_or(LengthOrPercentage::Percentage(Percentage(0.5))), - vertical: result.vertical.unwrap_or(LengthOrPercentage::Percentage(Percentage(0.5))), - }) - } - } - - impl ToComputedValue for SpecifiedValue { - type ComputedValue = computed_value::T; - - #[inline] - fn to_computed_value(&self, context: &Context) -> computed_value::T { - computed_value::T { - horizontal: self.horizontal.to_computed_value(context), - vertical: self.vertical.to_computed_value(context), - } - } - - #[inline] - fn from_computed_value(computed: &computed_value::T) -> Self { - SpecifiedValue { - horizontal: ToComputedValue::from_computed_value(&computed.horizontal), - vertical: ToComputedValue::from_computed_value(&computed.vertical), - } - } - } - +${helpers.predefined_type("perspective-origin", + "position::OriginPosition", + "computed::position::OriginPosition::center()", + boxed="True", + extra_prefixes="moz webkit", + spec="https://drafts.csswg.org/css-transforms/#perspective-origin-property", + animation_value_type="ComputedValue")} ${helpers.single_keyword("backface-visibility", "visible hidden", diff --git a/components/style/values/computed/position.rs b/components/style/values/computed/position.rs index d2cc4b1e48fc..eed9c0971986 100644 --- a/components/style/values/computed/position.rs +++ b/components/style/values/computed/position.rs @@ -3,59 +3,63 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ //! CSS handling for the computed value of -//! [`position`][position]s +//! [`position`][position] values. //! //! [position]: https://drafts.csswg.org/css-backgrounds-3/#position use std::fmt; use style_traits::ToCss; use values::computed::LengthOrPercentage; +use values::generics::position::{Position as GenericPosition, PositionWithKeyword}; +use values::generics::position::HorizontalPosition as GenericHorizontalPosition; +use values::generics::position::VerticalPosition as GenericVerticalPosition; -#[derive(Debug, Clone, PartialEq, Copy)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub struct Position { - pub horizontal: LengthOrPercentage, - pub vertical: LengthOrPercentage, +/// The computed value of a CSS `` +pub type Position = PositionWithKeyword; + +impl Copy for Position {} + +/// The computed value for `` values without a keyword. +pub type OriginPosition = GenericPosition; + +impl Copy for OriginPosition {} + +impl OriginPosition { + #[inline] + /// The initial value for `perspective-origin` + pub fn center() -> OriginPosition { + GenericPosition { + horizontal: LengthOrPercentage::Percentage(0.5), + vertical: LengthOrPercentage::Percentage(0.5), + } + } } impl Position { + #[inline] /// Construct a position at (0, 0) pub fn zero() -> Self { Position { - horizontal: LengthOrPercentage::zero(), - vertical: LengthOrPercentage::zero(), + horizontal: GenericHorizontalPosition(LengthOrPercentage::zero()), + vertical: GenericVerticalPosition(LengthOrPercentage::zero()), } } } impl ToCss for Position { fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - try!(self.horizontal.to_css(dest)); - try!(dest.write_str(" ")); - try!(self.vertical.to_css(dest)); - Ok(()) + self.horizontal.to_css(dest)?; + dest.write_str(" ")?; + self.vertical.to_css(dest) } } -#[derive(Debug, Clone, PartialEq, Copy)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub struct HorizontalPosition(pub LengthOrPercentage); +/// The computed value of a horizontal `` +pub type HorizontalPosition = GenericHorizontalPosition; -impl ToCss for HorizontalPosition { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - self.0.to_css(dest) - } -} +impl Copy for HorizontalPosition {} -#[derive(Debug, Clone, PartialEq, Copy)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub struct VerticalPosition(pub LengthOrPercentage); +/// The computed value of a vertical `` +pub type VerticalPosition = GenericVerticalPosition; -impl ToCss for VerticalPosition { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - self.0.to_css(dest) - } -} +impl Copy for VerticalPosition {} diff --git a/components/style/values/generics/position.rs b/components/style/values/generics/position.rs index 7ecfafdf3563..d15198b638ea 100644 --- a/components/style/values/generics/position.rs +++ b/components/style/values/generics/position.rs @@ -25,6 +25,52 @@ define_css_keyword_enum!{ Keyword: "y-end" => YEnd } +add_impls_for_keyword_enum!(Keyword); + +impl Keyword { + #[inline] + /// The defaults for position keywords are `left` and `top` (`x-start` and `y-start` for logical). + /// This method checks whether this keyword indicates their opposite sides. See the + /// `ToComputedValue` impl on `HorizontalPosition` and `VerticalPosition` for its use case. + pub fn is_other_side(&self) -> bool { + if self.is_horizontal() || self.is_logical_x() { + matches!(*self, Keyword::Right | Keyword::XEnd) + } else { + matches!(*self, Keyword::Bottom | Keyword::YEnd) + } + } + + #[inline] + /// Check whether this is a keyword for horizontal position. + pub fn is_horizontal(&self) -> bool { + matches!(*self, Keyword::Left | Keyword::Right) + } + + #[inline] + /// Check whether this is a keyword for vertical position. + pub fn is_vertical(&self) -> bool { + matches!(*self, Keyword::Top | Keyword::Bottom) + } + + #[inline] + /// Check whether this is a horizontal logical keyword. + pub fn is_logical_x(&self) -> bool { + matches!(*self, Keyword::XStart | Keyword::XEnd) + } + + #[inline] + /// Check whether this is a vertical logical keyword. + pub fn is_logical_y(&self) -> bool { + matches!(*self, Keyword::YStart | Keyword::YEnd) + } + + #[inline] + /// Check whether this is a logical keyword. + pub fn is_logical(&self) -> bool { + self.is_logical_x() || self.is_logical_y() + } +} + impl From for LengthOrPercentage { fn from(val: Keyword) -> LengthOrPercentage { match val { diff --git a/components/style/values/specified/basic_shape.rs b/components/style/values/specified/basic_shape.rs index 7c75b1a1d129..989f5ce1a3c5 100644 --- a/components/style/values/specified/basic_shape.rs +++ b/components/style/values/specified/basic_shape.rs @@ -338,13 +338,11 @@ fn serialize_basicshape_position(position: &Position, dest: &mut W) -> fmt::R replace_with_percent(y).to_css(dest) } - match (position.horizontal.keyword, position.horizontal.position.clone(), - position.vertical.keyword, position.vertical.position.clone()) { + match (position.horizontal.0.keyword, position.horizontal.0.position.clone(), + position.vertical.0.keyword, position.vertical.0.position.clone()) { (Some(hk), None, Some(vk), None) => { // two keywords: serialize as two lengths - serialize_position_pair(hk.to_length_or_percentage(), - vk.to_length_or_percentage(), - dest) + serialize_position_pair(hk.into(), vk.into(), dest) } (None, Some(hp), None, Some(vp)) => { // two lengths: just serialize regularly diff --git a/components/style/values/specified/position.rs b/components/style/values/specified/position.rs index edbe8c8472d7..2289df4871b4 100644 --- a/components/style/values/specified/position.rs +++ b/components/style/values/specified/position.rs @@ -8,616 +8,233 @@ //! [position]: https://drafts.csswg.org/css-backgrounds-3/#position use app_units::Au; -use cssparser::{Parser, Token}; +use cssparser::Parser; use parser::{Parse, ParserContext}; -use std::fmt; -use style_traits::ToCss; -use values::HasViewportPercentage; -use values::computed::{self, CalcLengthOrPercentage, Context}; +use properties::longhands::parse_origin; +use std::mem; +use values::Either; +use values::computed::{CalcLengthOrPercentage, Context}; use values::computed::{LengthOrPercentage as ComputedLengthOrPercentage, ToComputedValue}; use values::computed::position as computed_position; +use values::generics::position::{Position as GenericPosition, PositionValue, PositionWithKeyword}; +use values::generics::position::HorizontalPosition as GenericHorizontalPosition; +use values::generics::position::VerticalPosition as GenericVerticalPosition; use values::specified::{LengthOrPercentage, Percentage}; -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -/// A [position][pos]. -/// -/// [pos]: https://drafts.csswg.org/css-values/#position -pub struct Position { - /// The horizontal component. - pub horizontal: HorizontalPosition, - /// The vertical component. - pub vertical: VerticalPosition, -} - -impl ToCss for Position { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - macro_rules! to_css_with_keyword { - ($pos:expr, $default_keyword:expr) => { - $pos.keyword.unwrap_or($default_keyword).to_css(dest)?; - - if let Some(ref position) = $pos.position { - dest.write_str(" ")?; - position.to_css(dest)?; - }; - }; - } +pub use values::generics::position::Keyword; - let mut is_keyword_needed = false; - let position_x = &self.horizontal; - let position_y = &self.vertical; +/// The specified value of a CSS `` +pub type Position = PositionWithKeyword>; - if (position_x.keyword.is_some() && position_x.position.is_some()) || - (position_y.keyword.is_some() && position_y.position.is_some()) { - is_keyword_needed = true; - } +/// The specified value for `` values without a keyword. +pub type OriginPosition = GenericPosition; - if is_keyword_needed { - to_css_with_keyword!(position_x, Keyword::Left); - dest.write_str(" ")?; - to_css_with_keyword!(position_y, Keyword::Top); - } else { - position_x.to_css(dest)?; - dest.write_str(" ")?; - position_y.to_css(dest)?; +impl Parse for OriginPosition { + fn parse(context: &ParserContext, input: &mut Parser) -> Result { + let result = parse_origin(context, input)?; + match result.depth { + Some(_) => Err(()), + None => Ok(GenericPosition { + horizontal: result.horizontal.unwrap_or(LengthOrPercentage::Percentage(Percentage(0.5))), + vertical: result.vertical.unwrap_or(LengthOrPercentage::Percentage(Percentage(0.5))), + }) } - - Ok(()) } } -impl HasViewportPercentage for Position { - fn has_viewport_percentage(&self) -> bool { - self.horizontal.has_viewport_percentage() || self.vertical.has_viewport_percentage() - } -} +type PositionComponent = Either; impl Position { - /// Create a new position value. - pub fn new(mut first_position: Option, - mut second_position: Option, - first_keyword: Option, - second_keyword: Option) - -> Result { + /// Create a new position value from either a length or a keyword. + pub fn from_components(mut first_position: Option, + mut second_position: Option, + first_keyword: Option, + second_keyword: Option) -> Result { // Unwrap for checking if values are at right place. - let first_key = first_keyword.clone().unwrap_or(PositionComponent::Keyword(Keyword::Left)); - let second_key = second_keyword.clone().unwrap_or(PositionComponent::Keyword(Keyword::Top)); - - // Check if position specified after center keyword. - if let PositionCategory::OtherKeyword = category(&first_key) { - if let Some(_) = first_position { - return Err(()); - }; - }; - if let PositionCategory::OtherKeyword = category(&second_key) { - if let Some(_) = second_position { - return Err(()); - }; - }; + let first_key = first_keyword.clone().unwrap_or(Either::Second(Keyword::Left)); + let second_key = second_keyword.clone().unwrap_or(Either::Second(Keyword::Top)); - // Check first and second keywords for both 2 and 4 value positions. - let (horiz_keyword, vert_keyword) = match (category(&first_key), category(&second_key)) { - // Don't allow two vertical keywords or two horizontal keywords. - // also don't allow length/percentage values in the wrong position - (PositionCategory::HorizontalKeyword, PositionCategory::HorizontalKeyword) | - (PositionCategory::VerticalKeyword, PositionCategory::VerticalKeyword) | - (PositionCategory::LengthOrPercentage, PositionCategory::HorizontalKeyword) | - (PositionCategory::VerticalKeyword, PositionCategory::LengthOrPercentage) => return Err(()), + let (horiz_keyword, vert_keyword) = match (&first_key, &second_key) { + // Check if a position is specified after center keyword. + (&Either::Second(Keyword::Center), _) if first_position.is_some() => return Err(()), + (_, &Either::Second(Keyword::Center)) if second_position.is_some() => return Err(()), + + // Check first and second keywords for both 2 and 4 value positions. // FIXME(canaltinova): Allow logical keywords for Position. They are not in current spec yet. - (PositionCategory::HorizontalLogicalKeyword, _) | - (PositionCategory::VerticalLogicalKeyword, _) | - (_, PositionCategory::HorizontalLogicalKeyword) | - (_, PositionCategory::VerticalLogicalKeyword) => return Err(()), + (&Either::Second(k), _) if k.is_logical() => return Err(()), + (_, &Either::Second(k)) if k.is_logical() => return Err(()), - // Swap if both are keywords and vertical precedes horizontal. - (PositionCategory::VerticalKeyword, PositionCategory::HorizontalKeyword) | - (PositionCategory::VerticalKeyword, PositionCategory::OtherKeyword) | - (PositionCategory::OtherKeyword, PositionCategory::HorizontalKeyword) => { - let tmp = first_position; - first_position = second_position; - second_position = tmp; + // Don't allow two vertical keywords or two horizontal keywords. + (&Either::Second(k1), &Either::Second(k2)) + if (k1.is_horizontal() && k2.is_horizontal()) || (k1.is_vertical() && k2.is_vertical()) => + return Err(()), + + // Also don't allow values in the wrong position + (&Either::First(_), &Either::Second(k)) if k.is_horizontal() => return Err(()), + (&Either::Second(k), &Either::First(_)) if k.is_vertical() => return Err(()), + // Swap if both are keywords and vertical precedes horizontal. + (&Either::Second(k1), &Either::Second(k2)) + if (k1.is_vertical() && k2.is_horizontal()) || (k1.is_vertical() && k2 == Keyword::Center) || + (k1 == Keyword::Center && k2.is_horizontal()) => { + mem::swap(&mut first_position, &mut second_position); (second_keyword, first_keyword) }, + // By default, horizontal is first. _ => (first_keyword, second_keyword), }; - // Unwrap positions from PositionComponent and wrap with Option - let (first_position, second_position) = if let Some(PositionComponent::Length(horiz_pos)) = first_position { - if let Some(PositionComponent::Length(vert_pos)) = second_position { - (Some(horiz_pos), Some(vert_pos)) - } else { - (Some(horiz_pos), None) - } - } else { - if let Some(PositionComponent::Length(vert_pos)) = second_position { - (None, Some(vert_pos)) - } else { - (None, None) - } - }; + let (mut h_pos, mut h_key, mut v_pos, mut v_key) = (None, None, None, None); + if let Some(Either::First(l)) = first_position { + h_pos = Some(l); + } - // Unwrap keywords from PositionComponent and wrap with Option. - let (horizontal_keyword, vertical_keyword) = if let Some(PositionComponent::Keyword(horiz_key)) = - horiz_keyword { - if let Some(PositionComponent::Keyword(vert_key)) = vert_keyword { - (Some(horiz_key), Some(vert_key)) - } else { - (Some(horiz_key), None) - } - } else { - if let Some(PositionComponent::Keyword(vert_key)) = vert_keyword { - (None, Some(vert_key)) - } else { - (None, None) - } - }; + if let Some(Either::First(l)) = second_position { + v_pos = Some(l); + } + + if let Some(Either::Second(k)) = horiz_keyword { + h_key = Some(k); + } + + if let Some(Either::Second(k)) = vert_keyword { + v_key = Some(k); + } Ok(Position { - horizontal: HorizontalPosition { - keyword: horizontal_keyword, - position: first_position, - }, - vertical: VerticalPosition { - keyword: vertical_keyword, - position: second_position, - }, + horizontal: GenericHorizontalPosition(PositionValue { + keyword: h_key, + position: h_pos, + }), + vertical: GenericVerticalPosition(PositionValue { + keyword: v_key, + position: v_pos, + }), }) } /// Returns a "centered" position, as in "center center". pub fn center() -> Position { Position { - horizontal: HorizontalPosition { + horizontal: GenericHorizontalPosition(PositionValue { keyword: Some(Keyword::Center), position: None, - }, - vertical: VerticalPosition { + }), + vertical: GenericVerticalPosition(PositionValue { keyword: Some(Keyword::Center), position: None, - }, + }), } } } impl Parse for Position { fn parse(context: &ParserContext, input: &mut Parser) -> Result { - let first = try!(PositionComponent::parse(context, input)); + let first = input.try(|i| PositionComponent::parse(context, i))?; let second = input.try(|i| PositionComponent::parse(context, i)) - .unwrap_or(PositionComponent::Keyword(Keyword::Center)); + .unwrap_or(Either::Second(Keyword::Center)); - // Try to parse third and fourth values if let Ok(third) = input.try(|i| PositionComponent::parse(context, i)) { + // There's a 3rd value. if let Ok(fourth) = input.try(|i| PositionComponent::parse(context, i)) { - // Handle 4 value background position - Position::new(Some(second), Some(fourth), Some(first), Some(third)) + // There's a 4th value. + Position::from_components(Some(second), Some(fourth), Some(first), Some(third)) } else { - // Handle 3 value background position there are several options: - if let PositionCategory::LengthOrPercentage = category(&first) { - Err(()) - } else { - if let PositionCategory::LengthOrPercentage = category(&second) { - if let PositionCategory::LengthOrPercentage = category(&third) { - Err(()) - } else { - // "keyword length keyword" - Position::new(Some(second), None, Some(first), Some(third)) - } - } else { - // "keyword keyword length" - Position::new(None, Some(third), Some(first), Some(second)) - } + // For 3 value background position, there are several options. + if let Either::First(_) = first { + return Err(()) // must be preceded by + } + + // only 3 values. + match (&second, &third) { + (&Either::First(_), &Either::First(_)) => Err(()), + // "keyword length keyword" + (&Either::First(_), _) => Position::from_components(Some(second), None, + Some(first), Some(third)), + // "keyword keyword length" + _ => Position::from_components(None, Some(third), Some(first), Some(second)), } } } else { - // Handle 2 value background position. - if let PositionCategory::LengthOrPercentage = category(&first) { - if let PositionCategory::LengthOrPercentage = category(&second) { - Position::new(Some(first), Some(second), None, None) - } else { - Position::new(Some(first), None, None, Some(second)) - } - } else { - if let PositionCategory::LengthOrPercentage = category(&second) { - Position::new(None, Some(second), Some(first), None) - } else { - Position::new(None, None, Some(first), Some(second)) - } + // only 2 values. + match (&first, &second) { + (&Either::First(_), &Either::First(_)) => + Position::from_components(Some(first), Some(second), None, None), + (&Either::First(_), &Either::Second(_)) => + Position::from_components(Some(first), None, None, Some(second)), + (&Either::Second(_), &Either::First(_)) => + Position::from_components(None, Some(second), Some(first), None), + (&Either::Second(_), &Either::Second(_)) => + Position::from_components(None, None, Some(first), Some(second)), } } } } -impl ToComputedValue for Position { - type ComputedValue = computed_position::Position; - - #[inline] - fn to_computed_value(&self, context: &Context) -> computed_position::Position { - computed_position::Position { - horizontal: self.horizontal.to_computed_value(context).0, - vertical: self.vertical.to_computed_value(context).0, - } - } - - #[inline] - fn from_computed_value(computed: &computed_position::Position) -> Position { - Position { - horizontal: HorizontalPosition { - keyword: None, - position: Some(ToComputedValue::from_computed_value(&computed.horizontal)), - }, - vertical: VerticalPosition { - keyword: None, - position: Some(ToComputedValue::from_computed_value(&computed.vertical)), - }, - } - } -} - -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub struct HorizontalPosition { - pub keyword: Option, - pub position: Option, -} - -impl HasViewportPercentage for HorizontalPosition { - fn has_viewport_percentage(&self) -> bool { - self.position.as_ref().map_or(false, |pos| pos.has_viewport_percentage()) - } -} - -impl ToCss for HorizontalPosition { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - let mut keyword_present = false; - if let Some(keyword) = self.keyword { - try!(keyword.to_css(dest)); - keyword_present = true; - }; - - if let Some(ref position) = self.position { - if keyword_present { - try!(dest.write_str(" ")); - } - try!(position.to_css(dest)); - }; - Ok(()) - } -} - -impl Parse for HorizontalPosition { - #[inline] - fn parse(context: &ParserContext, input: &mut Parser) -> Result { - let first = try!(PositionComponent::parse(context, input)); - let second = input.try(|i| PositionComponent::parse(context, i)).ok(); - - let (keyword, position) = if let PositionCategory::LengthOrPercentage = category(&first) { - // "length keyword?" - (second, Some(first)) - } else { - // "keyword length?" - (Some(first), second) - }; - - // Unwrapping and checking keyword. - let keyword = match keyword { - Some(PositionComponent::Keyword(key)) => { - match category(keyword.as_ref().unwrap()) { - PositionCategory::VerticalKeyword | - PositionCategory::VerticalLogicalKeyword => return Err(()), - _ => Some(key), - } - }, - Some(_) => return Err(()), - None => None, - }; - - // Unwrapping and checking position. - let position = match position { - Some(PositionComponent::Length(pos)) => { - // "center " is not allowed - if let Some(Keyword::Center) = keyword { - return Err(()); - } - Some(pos) - }, - Some(_) => return Err(()), - None => None, - }; - - Ok(HorizontalPosition { - keyword: keyword, - position: position, - }) - } -} - -impl ToComputedValue for HorizontalPosition { - type ComputedValue = computed_position::HorizontalPosition; - - #[inline] - fn to_computed_value(&self, context: &Context) -> computed_position::HorizontalPosition { - let keyword = self.keyword.unwrap_or(Keyword::Left); - - // Construct horizontal computed LengthOrPercentage - let horizontal = match keyword { - // FIXME(canaltinova): Support logical keywords. - Keyword::Right | Keyword::XEnd => { - if let Some(ref x) = self.position { +impl PositionValue { + /// Generic function for the computed value of a position. + fn computed_value(&self, context: &Context) -> ComputedLengthOrPercentage { + match self.keyword { + Some(Keyword::Center) => ComputedLengthOrPercentage::Percentage(0.5), + Some(k) if k.is_other_side() => match self.position { + Some(ref x) => { let (length, percentage) = match *x { LengthOrPercentage::Percentage(Percentage(y)) => (Au(0), Some(1.0 - y)), LengthOrPercentage::Length(ref y) => (-y.to_computed_value(context), Some(1.0)), _ => (Au(0), None), }; + ComputedLengthOrPercentage::Calc(CalcLengthOrPercentage { length: length, percentage: percentage }) - } else { - ComputedLengthOrPercentage::Percentage(1.0) - } - }, - Keyword::Center => { - keyword.to_length_or_percentage().to_computed_value(context) - }, - _ => { - let zero = LengthOrPercentage::Percentage(Percentage(0.0)); - let horiz = self.position.as_ref().unwrap_or(&zero); - horiz.to_computed_value(context) + }, + None => ComputedLengthOrPercentage::Percentage(1.0), }, - }; - - computed_position::HorizontalPosition(horizontal) - } - - #[inline] - fn from_computed_value(computed: &computed_position::HorizontalPosition) -> HorizontalPosition { - HorizontalPosition { - keyword: None, - position: Some(ToComputedValue::from_computed_value(&computed.0)), + _ => self.position.as_ref().map(|l| l.to_computed_value(context)) + .unwrap_or(ComputedLengthOrPercentage::Percentage(0.0)), } } } -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr(feature = "servo", derive(HeapSizeOf))] -#[allow(missing_docs)] -pub struct VerticalPosition { - pub keyword: Option, - pub position: Option, -} - -impl HasViewportPercentage for VerticalPosition { - fn has_viewport_percentage(&self) -> bool { - self.position.as_ref().map_or(false, |pos| pos.has_viewport_percentage()) - } -} +/// The specified value of horizontal `` +pub type HorizontalPosition = GenericHorizontalPosition>; -impl ToCss for VerticalPosition { - fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { - let mut keyword_present = false; - if let Some(keyword) = self.keyword { - try!(keyword.to_css(dest)); - keyword_present = true; - }; +impl ToComputedValue for HorizontalPosition { + type ComputedValue = computed_position::HorizontalPosition; - if let Some(ref position) = self.position { - if keyword_present { - try!(dest.write_str(" ")); - } - try!(position.to_css(dest)); - }; - Ok(()) + #[inline] + fn to_computed_value(&self, context: &Context) -> computed_position::HorizontalPosition { + GenericHorizontalPosition(self.0.computed_value(context)) } -} - -impl Parse for VerticalPosition { #[inline] - fn parse(context: &ParserContext, input: &mut Parser) -> Result { - let first = try!(PositionComponent::parse(context, input)); - let second = input.try(|i| PositionComponent::parse(context, i)).ok(); - - let (keyword, position) = if let PositionCategory::LengthOrPercentage = category(&first) { - // "length keyword?" - (second, Some(first)) - } else { - // "keyword length?" - (Some(first), second) - }; - - // Unwrapping and checking keyword. - let keyword = match keyword { - Some(PositionComponent::Keyword(key)) => { - match category(keyword.as_ref().unwrap()) { - PositionCategory::HorizontalKeyword | - PositionCategory::HorizontalLogicalKeyword => return Err(()), - _ => Some(key), - } - }, - Some(_) => return Err(()), - None => None, - }; - - // Unwrapping and checking position. - let position = match position { - Some(PositionComponent::Length(pos)) => { - // "center " is not allowed - if let Some(Keyword::Center) = keyword { - return Err(()); - } - Some(pos) - }, - Some(_) => return Err(()), - None => None, - }; - - Ok(VerticalPosition { - keyword: keyword, - position: position, + fn from_computed_value(computed: &computed_position::HorizontalPosition) -> HorizontalPosition { + GenericHorizontalPosition(PositionValue { + keyword: None, + position: Some(ToComputedValue::from_computed_value(&computed.0)), }) } } +/// The specified value of vertical `` +pub type VerticalPosition = GenericVerticalPosition>; + impl ToComputedValue for VerticalPosition { type ComputedValue = computed_position::VerticalPosition; #[inline] fn to_computed_value(&self, context: &Context) -> computed_position::VerticalPosition { - let keyword = self.keyword.unwrap_or(Keyword::Left); - - // Construct vertical computed LengthOrPercentage - let vertical = match keyword { - // FIXME(canaltinova): Support logical keywords. - Keyword::Bottom | Keyword::YEnd => { - if let Some(ref x) = self.position { - let (length, percentage) = match *x { - LengthOrPercentage::Percentage(Percentage(y)) => (Au(0), Some(1.0 - y)), - LengthOrPercentage::Length(ref y) => (-y.to_computed_value(context), Some(1.0)), - _ => (Au(0), None), - }; - ComputedLengthOrPercentage::Calc(CalcLengthOrPercentage { - length: length, - percentage: percentage - }) - } else { - ComputedLengthOrPercentage::Percentage(1.0) - } - }, - Keyword::Center => { - keyword.to_length_or_percentage().to_computed_value(context) - }, - _ => { - let zero = LengthOrPercentage::Percentage(Percentage(0.0)); - let vert = self.position.as_ref().unwrap_or(&zero); - vert.to_computed_value(context) - }, - }; - - computed_position::VerticalPosition(vertical) + GenericVerticalPosition(self.0.computed_value(context)) } #[inline] fn from_computed_value(computed: &computed_position::VerticalPosition) -> VerticalPosition { - VerticalPosition { + GenericVerticalPosition(PositionValue { keyword: None, position: Some(ToComputedValue::from_computed_value(&computed.0)), - } - } -} - -define_css_keyword_enum!(Keyword: - "center" => Center, - "left" => Left, - "right" => Right, - "top" => Top, - "bottom" => Bottom, - "x-start" => XStart, - "x-end" => XEnd, - "y-start" => YStart, - "y-end" => YEnd); - -impl Keyword { - /// Convert the given keyword to a length or a percentage. - pub fn to_length_or_percentage(self) -> LengthOrPercentage { - match self { - Keyword::Center => LengthOrPercentage::Percentage(Percentage(0.5)), - Keyword::Left | Keyword::Top => LengthOrPercentage::Percentage(Percentage(0.0)), - Keyword::Right | Keyword::Bottom => LengthOrPercentage::Percentage(Percentage(1.0)), - // FIXME(canaltinova): Support logical keywords - Keyword::XStart | Keyword::YStart => LengthOrPercentage::Percentage(Percentage(0.0)), - Keyword::XEnd | Keyword::YEnd => LengthOrPercentage::Percentage(Percentage(1.0)), - } - } -} - -// Collapse `Position` into a few categories to simplify the above `match` expression. -enum PositionCategory { - HorizontalKeyword, - VerticalKeyword, - HorizontalLogicalKeyword, - VerticalLogicalKeyword, - OtherKeyword, - LengthOrPercentage, -} - -/// A position component. -/// -/// http://dev.w3.org/csswg/css2/colors.html#propdef-background-position -#[derive(Clone, PartialEq)] -pub enum PositionComponent { - /// A `` - Length(LengthOrPercentage), - /// A position keyword. - Keyword(Keyword), -} - -fn category(p: &PositionComponent) -> PositionCategory { - if let PositionComponent::Keyword(keyword) = *p { - match keyword { - Keyword::Left | Keyword::Right => - PositionCategory::HorizontalKeyword, - Keyword::Top | Keyword::Bottom => - PositionCategory::VerticalKeyword, - Keyword::XStart | Keyword::XEnd => - PositionCategory::HorizontalLogicalKeyword, - Keyword::YStart | Keyword::YEnd => - PositionCategory::VerticalLogicalKeyword, - Keyword::Center => - PositionCategory::OtherKeyword, - } - } else { - PositionCategory::LengthOrPercentage - } -} - -impl HasViewportPercentage for PositionComponent { - fn has_viewport_percentage(&self) -> bool { - match *self { - PositionComponent::Length(ref length) => length.has_viewport_percentage(), - _ => false - } - } -} - -impl PositionComponent { - /// Convert the given position component to a length or a percentage. - #[inline] - pub fn to_length_or_percentage_computed(&self, cx: &Context) -> computed::LengthOrPercentage { - match *self { - PositionComponent::Length(ref value) => value.to_computed_value(cx), - PositionComponent::Keyword(keyword) => { - keyword.to_length_or_percentage().to_computed_value(cx) - } - } - } -} - -impl Parse for PositionComponent { - fn parse(context: &ParserContext, input: &mut Parser) -> Result { - input.try(|i| LengthOrPercentage::parse(context, i)) - .map(PositionComponent::Length) - .or_else(|()| { - match try!(input.next()) { - Token::Ident(value) => { - match_ignore_ascii_case! { &value, - "center" => Ok(PositionComponent::Keyword(Keyword::Center)), - "left" => Ok(PositionComponent::Keyword(Keyword::Left)), - "right" => Ok(PositionComponent::Keyword(Keyword::Right)), - "top" => Ok(PositionComponent::Keyword(Keyword::Top)), - "bottom" => Ok(PositionComponent::Keyword(Keyword::Bottom)), - "x-start" => Ok(PositionComponent::Keyword(Keyword::XStart)), - "x-end" => Ok(PositionComponent::Keyword(Keyword::XEnd)), - "y-start" => Ok(PositionComponent::Keyword(Keyword::YStart)), - "y-end" => Ok(PositionComponent::Keyword(Keyword::YEnd)), - _ => Err(()) - } - }, - _ => Err(()) - } - }) + }) } } diff --git a/tests/unit/style/properties/serialization.rs b/tests/unit/style/properties/serialization.rs index 7e862e512048..95c3ea638cf4 100644 --- a/tests/unit/style/properties/serialization.rs +++ b/tests/unit/style/properties/serialization.rs @@ -799,8 +799,8 @@ mod shorthand_serialization { use style::properties::longhands::mask_position_y as position_y; use style::properties::longhands::mask_repeat as repeat; use style::properties::longhands::mask_size as size; + use style::values::generics::position::{HorizontalPosition, Keyword, PositionValue, VerticalPosition}; use style::values::specified::Image; - use style::values::specified::position::{HorizontalPosition, VerticalPosition, Keyword}; use super::*; macro_rules! single_vec_value_typedef { @@ -836,16 +836,16 @@ mod shorthand_serialization { let mode = single_vec_keyword_value!(mode, luminance); let position_x = single_vec_value_typedef!(position_x, - HorizontalPosition { + HorizontalPosition(PositionValue { keyword: None, position: Some(LengthOrPercentage::Length(NoCalcLength::from_px(7f32))), - } + }) ); let position_y = single_vec_value_typedef!(position_y, - VerticalPosition { + VerticalPosition(PositionValue { keyword: Some(Keyword::Bottom), position: Some(LengthOrPercentage::Length(NoCalcLength::from_px(4f32))), - } + }) ); let size = single_vec_variant_value!(size, @@ -891,17 +891,17 @@ mod shorthand_serialization { let mode = single_vec_keyword_value!(mode, luminance); let position_x = single_vec_value_typedef!(position_x, - HorizontalPosition { + HorizontalPosition(PositionValue { keyword: None, position: Some(LengthOrPercentage::Length(NoCalcLength::from_px(7f32))), - } + }) ); let position_y = single_vec_value_typedef!(position_y, - VerticalPosition { + VerticalPosition(PositionValue { keyword: None, position: Some(LengthOrPercentage::Length(NoCalcLength::from_px(4f32))), - } + }) ); let size = single_vec_variant_value!(size,