From 6f58c665899c23fe8b3965ec687cecfd2ee67aa0 Mon Sep 17 00:00:00 2001 From: Mats Palmgren Date: Tue, 28 Apr 2020 01:18:44 +0000 Subject: [PATCH] style: Implement style system support for Masonry layout. This implements support for this CSS Masonry layout proposal: https://github.com/w3c/csswg-drafts/issues/4650 I've intentionally left out a shorthand (place-tracks?) for now until we have a draft CSS spec for this. Differential Revision: https://phabricator.services.mozilla.com/D67061 --- components/style/properties/data.py | 1 + components/style/properties/gecko.mako.rs | 4 +- .../properties/longhands/position.mako.rs | 32 ++++ components/style/values/computed/align.rs | 2 +- components/style/values/computed/mod.rs | 4 +- components/style/values/computed/position.rs | 2 +- components/style/values/generics/grid.rs | 3 + components/style/values/specified/align.rs | 87 ++++++--- components/style/values/specified/grid.rs | 18 +- components/style/values/specified/mod.rs | 6 +- components/style/values/specified/position.rs | 166 ++++++++++++++++++ 11 files changed, 295 insertions(+), 30 deletions(-) diff --git a/components/style/properties/data.py b/components/style/properties/data.py index 472d1eb34dd5..56ac30dd4586 100644 --- a/components/style/properties/data.py +++ b/components/style/properties/data.py @@ -356,6 +356,7 @@ def specified_is_copy(self): "JustifyItems", "JustifySelf", "LineBreak", + "MasonryAutoFlow", "MozForceBrokenImageIcon", "MozListReversed", "MozScriptLevel", diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index f3db987d8f98..545e8cc77b54 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -802,7 +802,8 @@ fn static_assert() { <% skip_position_longhands = " ".join(x.ident for x in SIDES) %> <%self:impl_trait style_struct_name="Position" - skip_longhands="${skip_position_longhands}"> + skip_longhands="${skip_position_longhands} + masonry-auto-flow"> % for side in SIDES: <% impl_split_style_coord(side.ident, "mOffset", side.index) %> % endfor @@ -811,6 +812,7 @@ fn static_assert() { self.gecko.mJustifyItems.computed = v; } + ${impl_simple_type_with_conversion("masonry_auto_flow", "mMasonryAutoFlow")} <% skip_outline_longhands = " ".join("outline-style outline-width".split() + diff --git a/components/style/properties/longhands/position.mako.rs b/components/style/properties/longhands/position.mako.rs index d9403be5864a..04d735201d4f 100644 --- a/components/style/properties/longhands/position.mako.rs +++ b/components/style/properties/longhands/position.mako.rs @@ -114,6 +114,17 @@ ${helpers.single_keyword( animation_value_type="discrete", servo_restyle_damage="reflow", )} + + ${helpers.predefined_type( + "justify-tracks", + "JustifyTracks", + "specified::JustifyTracks::default()", + engines="gecko", + gecko_pref="layout.css.grid-template-masonry-value.enabled", + animation_value_type="discrete", + servo_restyle_damage="reflow", + spec="https://github.com/w3c/csswg-drafts/issues/4650", + )} % endif % if engine in ["servo-2013", "servo-2020"]: @@ -151,6 +162,17 @@ ${helpers.single_keyword( servo_restyle_damage="reflow", )} + ${helpers.predefined_type( + "align-tracks", + "AlignTracks", + "specified::AlignTracks::default()", + engines="gecko", + gecko_pref="layout.css.grid-template-masonry-value.enabled", + animation_value_type="discrete", + servo_restyle_damage="reflow", + spec="https://github.com/w3c/csswg-drafts/issues/4650", + )} + ${helpers.predefined_type( "align-items", "AlignItems", @@ -372,6 +394,16 @@ ${helpers.predefined_type( % endfor +${helpers.predefined_type( + "masonry-auto-flow", + "MasonryAutoFlow", + "computed::MasonryAutoFlow::initial()", + engines="gecko", + gecko_pref="layout.css.grid-template-masonry-value.enabled", + animation_value_type="discrete", + spec="https://github.com/w3c/csswg-drafts/issues/4650", +)} + ${helpers.predefined_type( "grid-auto-flow", "GridAutoFlow", diff --git a/components/style/values/computed/align.rs b/components/style/values/computed/align.rs index 45d6cfac323d..893f6fcce51b 100644 --- a/components/style/values/computed/align.rs +++ b/components/style/values/computed/align.rs @@ -10,7 +10,7 @@ use crate::values::computed::{Context, ToComputedValue}; use crate::values::specified; pub use super::specified::{ - AlignContent, AlignItems, ContentDistribution, JustifyContent, SelfAlignment, + AlignContent, AlignItems, AlignTracks, ContentDistribution, JustifyContent, JustifyTracks, SelfAlignment, }; pub use super::specified::{AlignSelf, JustifySelf}; diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index 841cc3df22a6..468deac4592d 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -29,7 +29,7 @@ use std::cmp; use std::f32; #[cfg(feature = "gecko")] -pub use self::align::{AlignContent, AlignItems, JustifyContent, JustifyItems, SelfAlignment}; +pub use self::align::{AlignContent, AlignItems, AlignTracks, JustifyContent, JustifyItems, JustifyTracks, SelfAlignment}; #[cfg(feature = "gecko")] pub use self::align::{AlignSelf, JustifySelf}; pub use self::angle::Angle; @@ -68,7 +68,7 @@ pub use self::list::Quotes; pub use self::motion::{OffsetPath, OffsetRotate}; pub use self::outline::OutlineStyle; pub use self::percentage::{NonNegativePercentage, Percentage}; -pub use self::position::{GridAutoFlow, GridTemplateAreas, Position, PositionOrAuto, ZIndex}; +pub use self::position::{GridAutoFlow, GridTemplateAreas, MasonryAutoFlow, Position, PositionOrAuto, ZIndex}; pub use self::rect::NonNegativeLengthOrNumberRect; pub use self::resolution::Resolution; pub use self::svg::MozContextProperties; diff --git a/components/style/values/computed/position.rs b/components/style/values/computed/position.rs index 3eff231de885..554c36295c7b 100644 --- a/components/style/values/computed/position.rs +++ b/components/style/values/computed/position.rs @@ -12,7 +12,7 @@ use crate::values::generics::position::Position as GenericPosition; use crate::values::generics::position::PositionComponent as GenericPositionComponent; use crate::values::generics::position::PositionOrAuto as GenericPositionOrAuto; use crate::values::generics::position::ZIndex as GenericZIndex; -pub use crate::values::specified::position::{GridAutoFlow, GridTemplateAreas}; +pub use crate::values::specified::position::{GridAutoFlow, GridTemplateAreas, MasonryAutoFlow}; use crate::Zero; use std::fmt::{self, Write}; use style_traits::{CssWriter, ToCss}; diff --git a/components/style/values/generics/grid.rs b/components/style/values/generics/grid.rs index 6f0f155912d7..0fbeebea1fb2 100644 --- a/components/style/values/generics/grid.rs +++ b/components/style/values/generics/grid.rs @@ -786,6 +786,9 @@ pub enum GenericGridTemplateComponent { /// TODO: Support animations for this after subgrid is addressed in [grid-2] spec. #[animation(error)] Subgrid(Box), + /// `masonry` value. + /// https://github.com/w3c/csswg-drafts/issues/4650 + Masonry, } pub use self::GenericGridTemplateComponent as GridTemplateComponent; diff --git a/components/style/values/specified/align.rs b/components/style/values/specified/align.rs index 10f7f3efbfc4..4b483962f2fe 100644 --- a/components/style/values/specified/align.rs +++ b/components/style/values/specified/align.rs @@ -171,16 +171,6 @@ impl ContentDistribution { Self { primary } } - fn from_bits(bits: u16) -> Self { - Self { - primary: AlignFlags::from_bits_truncate(bits as u8), - } - } - - fn as_bits(&self) -> u16 { - self.primary.bits() as u16 - } - /// Returns whether this value is a . pub fn is_baseline_position(&self) -> bool { matches!( @@ -292,6 +282,41 @@ impl SpecifiedValueInfo for AlignContent { } } +/// Value for the `align-tracks` property. +/// +/// +#[derive( + Clone, + Debug, + Default, + Eq, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(transparent)] +#[css(comma)] +pub struct AlignTracks( + #[css(iterable, if_empty = "normal")] + pub crate::OwnedSlice +); + +impl Parse for AlignTracks { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + let values = input.parse_comma_separated(|input| { + AlignContent::parse(context, input) + })?; + Ok(AlignTracks(values.into())) + } +} + /// Value for the `justify-content` property. /// /// @@ -329,18 +354,38 @@ impl SpecifiedValueInfo for JustifyContent { ContentDistribution::list_keywords(f, AxisDirection::Inline); } } +/// Value for the `justify-tracks` property. +/// +/// +#[derive( + Clone, + Debug, + Default, + Eq, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +#[repr(transparent)] +#[css(comma)] +pub struct JustifyTracks( + #[css(iterable, if_empty = "normal")] + pub crate::OwnedSlice +); -#[cfg(feature = "gecko")] -impl From for JustifyContent { - fn from(bits: u16) -> Self { - JustifyContent(ContentDistribution::from_bits(bits)) - } -} - -#[cfg(feature = "gecko")] -impl From for u16 { - fn from(v: JustifyContent) -> u16 { - v.0.as_bits() +impl Parse for JustifyTracks { + fn parse<'i, 't>( + context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + let values = input.parse_comma_separated(|input| { + JustifyContent::parse(context, input) + })?; + Ok(JustifyTracks(values.into())) } } diff --git a/components/style/values/specified/grid.rs b/components/style/values/specified/grid.rs index 54d31d5826dc..9c6bf385d31c 100644 --- a/components/style/values/specified/grid.rs +++ b/components/style/values/specified/grid.rs @@ -295,6 +295,18 @@ fn allow_grid_template_subgrids() -> bool { false } +#[cfg(feature = "gecko")] +#[inline] +fn allow_grid_template_masonry() -> bool { + static_prefs::pref!("layout.css.grid-template-masonry-value.enabled") +} + +#[cfg(feature = "servo")] +#[inline] +fn allow_grid_template_masonry() -> bool { + false +} + impl Parse for GridTemplateComponent { fn parse<'i, 't>( context: &ParserContext, @@ -319,7 +331,11 @@ impl GridTemplateComponent { return Ok(GridTemplateComponent::Subgrid(Box::new(t))); } } - + if allow_grid_template_masonry() { + if input.try(|i| i.expect_ident_matching("masonry")).is_ok() { + return Ok(GridTemplateComponent::Masonry); + } + } let track_list = TrackList::parse(context, input)?; Ok(GridTemplateComponent::TrackList(Box::new(track_list))) } diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index 63af30153860..c1c11d5f8164 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -28,9 +28,9 @@ use style_traits::values::specified::AllowedNumericType; use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss}; #[cfg(feature = "gecko")] -pub use self::align::{AlignContent, AlignItems, AlignSelf, ContentDistribution}; +pub use self::align::{AlignContent, AlignItems, AlignSelf, AlignTracks, ContentDistribution}; #[cfg(feature = "gecko")] -pub use self::align::{JustifyContent, JustifyItems, JustifySelf, SelfAlignment}; +pub use self::align::{JustifyContent, JustifyItems, JustifySelf, JustifyTracks, SelfAlignment}; pub use self::angle::{AllowUnitlessZeroAngle, Angle}; pub use self::background::{BackgroundRepeat, BackgroundSize}; pub use self::basic_shape::FillRule; @@ -72,7 +72,7 @@ pub use self::list::Quotes; pub use self::motion::{OffsetPath, OffsetRotate}; pub use self::outline::OutlineStyle; pub use self::percentage::Percentage; -pub use self::position::{GridAutoFlow, GridTemplateAreas, Position, PositionOrAuto}; +pub use self::position::{GridAutoFlow, GridTemplateAreas, MasonryAutoFlow, Position, PositionOrAuto}; pub use self::position::{PositionComponent, ZIndex}; pub use self::rect::NonNegativeLengthOrNumberRect; pub use self::resolution::Resolution; diff --git a/components/style/values/specified/position.rs b/components/style/values/specified/position.rs index b843de29a413..33b9ddcf5b50 100644 --- a/components/style/values/specified/position.rs +++ b/components/style/values/specified/position.rs @@ -383,6 +383,172 @@ bitflags! { } } +#[derive( + Clone, + Copy, + Debug, + Eq, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +/// Masonry auto-placement algorithm packing. +pub enum MasonryPlacement { + /// Place the item in the track(s) with the smallest extent so far. + Pack, + /// Place the item after the last item, from start to end. + Next, +} + +#[derive( + Clone, + Copy, + Debug, + Eq, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +/// Masonry auto-placement algorithm item sorting option. +pub enum MasonryItemOrder { + /// Place all items with a definite placement before auto-placed items. + DefiniteFirst, + /// Place items in `order-modified document order`. + Ordered, +} + +#[derive( + Clone, + Copy, + Debug, + Eq, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, +)] +/// Controls how the Masonry layout algorithm works +/// specifying exactly how auto-placed items get flowed in the masonry axis. +pub struct MasonryAutoFlow { + /// Specify how to pick a auto-placement track. + #[css(contextual_skip_if = "is_pack_with_non_default_order")] + pub placement: MasonryPlacement, + /// Specify how to pick an item to place. + #[css(skip_if = "is_item_order_definite_first")] + pub order: MasonryItemOrder, +} + +#[inline] +fn is_pack_with_non_default_order(placement: &MasonryPlacement, order: &MasonryItemOrder) -> bool { + *placement == MasonryPlacement::Pack && + *order != MasonryItemOrder::DefiniteFirst +} + +#[inline] +fn is_item_order_definite_first(order: &MasonryItemOrder) -> bool { + *order == MasonryItemOrder::DefiniteFirst +} + +impl MasonryAutoFlow { + #[inline] + /// Get initial `masonry-auto-flow` value. + pub fn initial() -> MasonryAutoFlow { + MasonryAutoFlow { + placement: MasonryPlacement::Pack, + order: MasonryItemOrder::DefiniteFirst, + } + } +} + +impl Parse for MasonryAutoFlow { + /// [ definite-first | ordered ] || [ pack | next ] + fn parse<'i, 't>( + _context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + let mut value = MasonryAutoFlow::initial(); + let mut got_placement = false; + let mut got_order = false; + while !input.is_exhausted() { + let location = input.current_source_location(); + let ident = input.expect_ident()?; + let success = match_ignore_ascii_case! { &ident, + "pack" if !got_placement => { + got_placement = true; + true + }, + "next" if !got_placement => { + value.placement = MasonryPlacement::Next; + got_placement = true; + true + }, + "definite-first" if !got_order => { + got_order = true; + true + }, + "ordered" if !got_order => { + value.order = MasonryItemOrder::Ordered; + got_order = true; + true + }, + _ => false + }; + if !success { + return Err(location + .new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone()))); + } + } + + if got_placement || got_order { + Ok(value) + } else { + Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } + } +} + +#[cfg(feature = "gecko")] +impl From for MasonryAutoFlow { + fn from(bits: u8) -> MasonryAutoFlow { + use crate::gecko_bindings::structs; + let mut value = MasonryAutoFlow::initial(); + if bits & structs::NS_STYLE_MASONRY_PLACEMENT_PACK as u8 == 0 { + value.placement = MasonryPlacement::Next; + } + if bits & structs::NS_STYLE_MASONRY_ORDER_DEFINITE_FIRST as u8 == 0 { + value.order = MasonryItemOrder::Ordered; + } + value + } +} + +#[cfg(feature = "gecko")] +impl From for u8 { + fn from(v: MasonryAutoFlow) -> u8 { + use crate::gecko_bindings::structs; + + let mut result: u8 = 0; + if v.placement == MasonryPlacement::Pack { + result |= structs::NS_STYLE_MASONRY_PLACEMENT_PACK as u8; + } + if v.order == MasonryItemOrder::DefiniteFirst { + result |= structs::NS_STYLE_MASONRY_ORDER_DEFINITE_FIRST as u8; + } + result + } +} + impl Parse for GridAutoFlow { /// [ row | column ] || dense fn parse<'i, 't>(