aboutsummaryrefslogtreecommitdiffstats
path: root/components/layout_2020/flex.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/layout_2020/flex.rs')
-rw-r--r--components/layout_2020/flex.rs1128
1 files changed, 1128 insertions, 0 deletions
diff --git a/components/layout_2020/flex.rs b/components/layout_2020/flex.rs
new file mode 100644
index 00000000000..e6aa2f7b2c8
--- /dev/null
+++ b/components/layout_2020/flex.rs
@@ -0,0 +1,1128 @@
+/* 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/. */
+
+//! Layout for elements with a CSS `display` property of `flex`.
+
+use crate::block::{AbsoluteAssignBSizesTraversal, BlockFlow, MarginsMayCollapseFlag};
+use crate::context::LayoutContext;
+use crate::display_list::{
+ BorderPaintingMode, DisplayListBuildState, StackingContextCollectionState,
+};
+use crate::floats::FloatKind;
+use crate::flow::{Flow, FlowClass, FlowFlags, GetBaseFlow, ImmutableFlowUtils, OpaqueFlow};
+use crate::fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
+use crate::layout_debug;
+use crate::model::{self, AdjoiningMargins, CollapsibleMargins};
+use crate::model::{IntrinsicISizes, MaybeAuto, SizeConstraint};
+use crate::traversal::PreorderFlowTraversal;
+use app_units::{Au, MAX_AU};
+use euclid::Point2D;
+use std::cmp::{max, min};
+use std::ops::Range;
+use style::computed_values::align_content::T as AlignContent;
+use style::computed_values::align_self::T as AlignSelf;
+use style::computed_values::flex_direction::T as FlexDirection;
+use style::computed_values::flex_wrap::T as FlexWrap;
+use style::computed_values::justify_content::T as JustifyContent;
+use style::logical_geometry::{Direction, LogicalSize};
+use style::properties::ComputedValues;
+use style::servo::restyle_damage::ServoRestyleDamage;
+use style::values::computed::flex::FlexBasis;
+use style::values::computed::{MaxSize, Size};
+
+/// The size of an axis. May be a specified size, a min/max
+/// constraint, or an unlimited size
+#[derive(Debug, Serialize)]
+enum AxisSize {
+ Definite(Au),
+ MinMax(SizeConstraint),
+ Infinite,
+}
+
+impl AxisSize {
+ /// Generate a new available cross or main axis size from the specified size of the container,
+ /// containing block size, min constraint, and max constraint
+ pub fn new(size: Size, content_size: Option<Au>, min: Size, max: MaxSize) -> AxisSize {
+ match size {
+ Size::Auto => AxisSize::MinMax(SizeConstraint::new(content_size, min, max, None)),
+ Size::LengthPercentage(ref lp) => match lp.maybe_to_used_value(content_size) {
+ Some(length) => AxisSize::Definite(length),
+ None => AxisSize::Infinite,
+ },
+ }
+ }
+}
+
+/// This function accepts the flex-basis and the size property in main direction from style,
+/// and the container size, then return the used value of flex basis. it can be used to help
+/// determining the flex base size and to indicate whether the main size of the item
+/// is definite after flex size resolving.
+fn from_flex_basis(flex_basis: FlexBasis, main_length: Size, containing_length: Au) -> MaybeAuto {
+ let width = match flex_basis {
+ FlexBasis::Content => return MaybeAuto::Auto,
+ FlexBasis::Size(width) => width,
+ };
+
+ let width = match width {
+ Size::Auto => main_length,
+ _ => width,
+ };
+
+ match width {
+ Size::Auto => MaybeAuto::Auto,
+ Size::LengthPercentage(ref lp) => MaybeAuto::Specified(lp.to_used_value(containing_length)),
+ }
+}
+
+/// Represents a child in a flex container. Most fields here are used in
+/// flex size resolving, and items are sorted by the 'order' property.
+#[derive(Debug, Serialize)]
+struct FlexItem {
+ /// Main size of a flex item, used to store results of flexible length calcuation.
+ pub main_size: Au,
+ /// Used flex base size.
+ pub base_size: Au,
+ /// The minimal size in main direction.
+ pub min_size: Au,
+ /// The maximal main size. If this property is not actually set by style
+ /// It will be the largest size available for code reuse.
+ pub max_size: Au,
+ /// The index of the actual flow in our child list.
+ pub index: usize,
+ /// The 'flex-grow' property of this item.
+ pub flex_grow: f32,
+ /// The 'flex-shrink' property of this item.
+ pub flex_shrink: f32,
+ /// The 'order' property of this item.
+ pub order: i32,
+ /// Whether the main size has met its constraint.
+ pub is_frozen: bool,
+ /// True if this flow has property 'visibility::collapse'.
+ pub is_strut: bool,
+}
+
+impl FlexItem {
+ pub fn new(index: usize, flow: &dyn Flow) -> FlexItem {
+ let style = &flow.as_block().fragment.style;
+ let flex_grow = style.get_position().flex_grow;
+ let flex_shrink = style.get_position().flex_shrink;
+ let order = style.get_position().order;
+ // TODO(stshine): for item with 'visibility:collapse', set is_strut to true.
+
+ FlexItem {
+ main_size: Au(0),
+ base_size: Au(0),
+ min_size: Au(0),
+ max_size: MAX_AU,
+ index: index,
+ flex_grow: flex_grow.into(),
+ flex_shrink: flex_shrink.into(),
+ order: order,
+ is_frozen: false,
+ is_strut: false,
+ }
+ }
+
+ /// Initialize the used flex base size, minimal main size and maximal main size.
+ /// For block mode container this method should be called in assign_block_size()
+ /// pass so that the item has already been layouted.
+ pub fn init_sizes(&mut self, flow: &mut dyn Flow, containing_length: Au, direction: Direction) {
+ let block = flow.as_mut_block();
+ match direction {
+ // TODO(stshine): the definition of min-{width, height} in style component
+ // should change to LengthPercentageOrAuto for automatic implied minimal size.
+ // https://drafts.csswg.org/css-flexbox-1/#min-size-auto
+ Direction::Inline => {
+ let basis = from_flex_basis(
+ block.fragment.style.get_position().flex_basis,
+ block.fragment.style.content_inline_size(),
+ containing_length,
+ );
+
+ // These methods compute auto margins to zero length, which is exactly what we want.
+ block.fragment.compute_border_and_padding(containing_length);
+ block
+ .fragment
+ .compute_inline_direction_margins(containing_length);
+ block
+ .fragment
+ .compute_block_direction_margins(containing_length);
+
+ let (border_padding, margin) = block.fragment.surrounding_intrinsic_inline_size();
+ let content_size = block.base.intrinsic_inline_sizes.preferred_inline_size -
+ border_padding -
+ margin +
+ block.fragment.box_sizing_boundary(direction);
+ self.base_size = basis.specified_or_default(content_size);
+ self.max_size = block
+ .fragment
+ .style
+ .max_inline_size()
+ .to_used_value(containing_length)
+ .unwrap_or(MAX_AU);
+ self.min_size = block
+ .fragment
+ .style
+ .min_inline_size()
+ .to_used_value(containing_length)
+ .unwrap_or(Au(0));
+ },
+ Direction::Block => {
+ let basis = from_flex_basis(
+ block.fragment.style.get_position().flex_basis,
+ block.fragment.style.content_block_size(),
+ containing_length,
+ );
+ let content_size = block.fragment.border_box.size.block -
+ block.fragment.border_padding.block_start_end() +
+ block.fragment.box_sizing_boundary(direction);
+ self.base_size = basis.specified_or_default(content_size);
+ self.max_size = block
+ .fragment
+ .style
+ .max_block_size()
+ .to_used_value(containing_length)
+ .unwrap_or(MAX_AU);
+ self.min_size = block
+ .fragment
+ .style
+ .min_block_size()
+ .to_used_value(containing_length)
+ .unwrap_or(Au(0));
+ },
+ }
+ }
+
+ /// Returns the outer main size of the item, including paddings and margins,
+ /// clamped by max and min size.
+ pub fn outer_main_size(&self, flow: &dyn Flow, direction: Direction) -> Au {
+ let ref fragment = flow.as_block().fragment;
+ let outer_width = match direction {
+ Direction::Inline => {
+ fragment.border_padding.inline_start_end() + fragment.margin.inline_start_end()
+ },
+ Direction::Block => {
+ fragment.border_padding.block_start_end() + fragment.margin.block_start_end()
+ },
+ };
+ max(self.min_size, min(self.base_size, self.max_size)) -
+ fragment.box_sizing_boundary(direction) +
+ outer_width
+ }
+
+ /// Returns the number of auto margins in given direction.
+ pub fn auto_margin_count(&self, flow: &dyn Flow, direction: Direction) -> i32 {
+ let margin = flow.as_block().fragment.style.logical_margin();
+ let mut margin_count = 0;
+ match direction {
+ Direction::Inline => {
+ if margin.inline_start.is_auto() {
+ margin_count += 1;
+ }
+ if margin.inline_end.is_auto() {
+ margin_count += 1;
+ }
+ },
+ Direction::Block => {
+ if margin.block_start.is_auto() {
+ margin_count += 1;
+ }
+ if margin.block_end.is_auto() {
+ margin_count += 1;
+ }
+ },
+ }
+ margin_count
+ }
+}
+
+/// A line in a flex container.
+// TODO(stshine): More fields are required to handle collapsed items and baseline alignment.
+#[derive(Debug, Serialize)]
+struct FlexLine {
+ /// Range of items belong to this line in 'self.items'.
+ pub range: Range<usize>,
+ /// Remaining free space of this line, items will grow or shrink based on it being positive or negative.
+ pub free_space: Au,
+ /// The number of auto margins of items.
+ pub auto_margin_count: i32,
+ /// Line size in the block direction.
+ pub cross_size: Au,
+}
+
+impl FlexLine {
+ pub fn new(range: Range<usize>, free_space: Au, auto_margin_count: i32) -> FlexLine {
+ FlexLine {
+ range: range,
+ auto_margin_count: auto_margin_count,
+ free_space: free_space,
+ cross_size: Au(0),
+ }
+ }
+
+ /// This method implements the flexible lengths resolving algorithm.
+ /// The 'collapse' parameter is used to indicate whether items with 'visibility: collapse'
+ /// is included in length resolving. The result main size is stored in 'item.main_size'.
+ /// <https://drafts.csswg.org/css-flexbox/#resolve-flexible-lengths>
+ pub fn flex_resolve(&mut self, items: &mut [FlexItem], collapse: bool) {
+ let mut total_grow = 0.0;
+ let mut total_shrink = 0.0;
+ let mut total_scaled = 0.0;
+ let mut active_count = 0;
+ // Iterate through items, collect total factors and freeze those that have already met
+ // their constraints or won't grow/shrink in corresponding scenario.
+ // https://drafts.csswg.org/css-flexbox/#resolve-flexible-lengths
+ for item in items.iter_mut().filter(|i| !(i.is_strut && collapse)) {
+ item.main_size = max(item.min_size, min(item.base_size, item.max_size));
+ if (self.free_space > Au(0) &&
+ (item.flex_grow == 0.0 || item.base_size >= item.max_size)) ||
+ (self.free_space < Au(0) &&
+ (item.flex_shrink == 0.0 || item.base_size <= item.min_size))
+ {
+ item.is_frozen = true;
+ } else {
+ item.is_frozen = false;
+ total_grow += item.flex_grow;
+ total_shrink += item.flex_shrink;
+ // The scaled factor is used to calculate flex shrink
+ total_scaled += item.flex_shrink * item.base_size.0 as f32;
+ active_count += 1;
+ }
+ }
+
+ let initial_free_space = self.free_space;
+ let mut total_variation = Au(1);
+ // If there is no remaining free space or all items are frozen, stop loop.
+ while total_variation != Au(0) && self.free_space != Au(0) && active_count > 0 {
+ self.free_space =
+ // https://drafts.csswg.org/css-flexbox/#remaining-free-space
+ if self.free_space > Au(0) {
+ min(initial_free_space.scale_by(total_grow), self.free_space)
+ } else {
+ max(initial_free_space.scale_by(total_shrink), self.free_space)
+ };
+
+ total_variation = Au(0);
+ for item in items
+ .iter_mut()
+ .filter(|i| !i.is_frozen)
+ .filter(|i| !(i.is_strut && collapse))
+ {
+ // Use this and the 'abs()' below to make the code work in both grow and shrink scenarios.
+ let (factor, end_size) = if self.free_space > Au(0) {
+ (item.flex_grow / total_grow, item.max_size)
+ } else {
+ (
+ item.flex_shrink * item.base_size.0 as f32 / total_scaled,
+ item.min_size,
+ )
+ };
+ let variation = self.free_space.scale_by(factor);
+ if variation.0.abs() >= (end_size - item.main_size).0.abs() {
+ // Use constraint as the target main size, and freeze item.
+ total_variation += end_size - item.main_size;
+ item.main_size = end_size;
+ item.is_frozen = true;
+ active_count -= 1;
+ total_shrink -= item.flex_shrink;
+ total_grow -= item.flex_grow;
+ total_scaled -= item.flex_shrink * item.base_size.0 as f32;
+ } else {
+ total_variation += variation;
+ item.main_size += variation;
+ }
+ }
+ self.free_space -= total_variation;
+ }
+ }
+}
+
+#[allow(unsafe_code)]
+unsafe impl crate::flow::HasBaseFlow for FlexFlow {}
+
+/// A block with the CSS `display` property equal to `flex`.
+#[derive(Debug, Serialize)]
+#[repr(C)]
+pub struct FlexFlow {
+ /// Data common to all block flows.
+ block_flow: BlockFlow,
+ /// The logical axis which the main axis will be parallel with.
+ /// The cross axis will be parallel with the opposite logical axis.
+ main_mode: Direction,
+ /// The available main axis size
+ available_main_size: AxisSize,
+ /// The available cross axis size
+ available_cross_size: AxisSize,
+ /// List of flex lines in the container.
+ lines: Vec<FlexLine>,
+ /// List of flex-items that belong to this flex-container
+ items: Vec<FlexItem>,
+ /// True if the flex-direction is *-reversed
+ main_reverse: bool,
+ /// True if this flex container can be multiline.
+ is_wrappable: bool,
+ /// True if the cross direction is reversed.
+ cross_reverse: bool,
+}
+
+impl FlexFlow {
+ pub fn from_fragment(fragment: Fragment, flotation: Option<FloatKind>) -> FlexFlow {
+ let main_mode;
+ let main_reverse;
+ let is_wrappable;
+ let cross_reverse;
+ {
+ let style = fragment.style();
+ let (mode, reverse) = match style.get_position().flex_direction {
+ FlexDirection::Row => (Direction::Inline, false),
+ FlexDirection::RowReverse => (Direction::Inline, true),
+ FlexDirection::Column => (Direction::Block, false),
+ FlexDirection::ColumnReverse => (Direction::Block, true),
+ };
+ main_mode = mode;
+ main_reverse = reverse == style.writing_mode.is_bidi_ltr();
+ let (wrappable, reverse) = match fragment.style.get_position().flex_wrap {
+ FlexWrap::Nowrap => (false, false),
+ FlexWrap::Wrap => (true, false),
+ FlexWrap::WrapReverse => (true, true),
+ };
+ is_wrappable = wrappable;
+ // TODO(stshine): Handle vertical writing mode.
+ cross_reverse = reverse;
+ }
+
+ FlexFlow {
+ block_flow: BlockFlow::from_fragment_and_float_kind(fragment, flotation),
+ main_mode: main_mode,
+ available_main_size: AxisSize::Infinite,
+ available_cross_size: AxisSize::Infinite,
+ lines: Vec::new(),
+ items: Vec::new(),
+ main_reverse: main_reverse,
+ is_wrappable: is_wrappable,
+ cross_reverse: cross_reverse,
+ }
+ }
+
+ pub fn main_mode(&self) -> Direction {
+ self.main_mode
+ }
+
+ /// Returns a line start after the last item that is already in a line.
+ /// Note that when the container main size is infinite(i.e. A column flexbox with auto height),
+ /// we do not need to do flex resolving and this can be considered as a fast-path, so the
+ /// 'container_size' param does not need to be 'None'. A line has to contain at least one item;
+ /// (except this) if the container can be multi-line the sum of outer main size of items should
+ /// be less than the container size; a line should be filled by items as much as possible.
+ /// After been collected in a line a item should have its main sizes initialized.
+ fn get_flex_line(&mut self, container_size: Au) -> Option<FlexLine> {
+ let start = self.lines.last().map(|line| line.range.end).unwrap_or(0);
+ if start == self.items.len() {
+ return None;
+ }
+ let mut end = start;
+ let mut total_line_size = Au(0);
+ let mut margin_count = 0;
+
+ let items = &mut self.items[start..];
+ let mut children = self.block_flow.base.children.random_access_mut();
+ for item in items {
+ let kid = children.get(item.index);
+ item.init_sizes(kid, container_size, self.main_mode);
+ let outer_main_size = item.outer_main_size(kid, self.main_mode);
+ if total_line_size + outer_main_size > container_size &&
+ end != start &&
+ self.is_wrappable
+ {
+ break;
+ }
+ margin_count += item.auto_margin_count(kid, self.main_mode);
+ total_line_size += outer_main_size;
+ end += 1;
+ }
+
+ let line = FlexLine::new(start..end, container_size - total_line_size, margin_count);
+ Some(line)
+ }
+
+ // TODO(zentner): This function should use flex-basis.
+ // Currently, this is the core of BlockFlow::bubble_inline_sizes() with all float logic
+ // stripped out, and max replaced with union_nonbreaking_inline.
+ fn inline_mode_bubble_inline_sizes(&mut self) {
+ // FIXME(emilio): This doesn't handle at all writing-modes.
+ let fixed_width =
+ !model::style_length(self.block_flow.fragment.style().get_position().width, None)
+ .is_auto();
+
+ let mut computation = self.block_flow.fragment.compute_intrinsic_inline_sizes();
+ if !fixed_width {
+ for kid in self.block_flow.base.children.iter_mut() {
+ let base = kid.mut_base();
+ let is_absolutely_positioned =
+ base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED);
+ if !is_absolutely_positioned {
+ let flex_item_inline_sizes = IntrinsicISizes {
+ minimum_inline_size: base.intrinsic_inline_sizes.minimum_inline_size,
+ preferred_inline_size: base.intrinsic_inline_sizes.preferred_inline_size,
+ };
+ computation.union_nonbreaking_inline(&flex_item_inline_sizes);
+ }
+ }
+ }
+ self.block_flow.base.intrinsic_inline_sizes = computation.finish();
+ }
+
+ // TODO(zentner): This function should use flex-basis.
+ // Currently, this is the core of BlockFlow::bubble_inline_sizes() with all float logic
+ // stripped out.
+ fn block_mode_bubble_inline_sizes(&mut self) {
+ let fixed_width =
+ !model::style_length(self.block_flow.fragment.style().get_position().width, None)
+ .is_auto();
+
+ let mut computation = self.block_flow.fragment.compute_intrinsic_inline_sizes();
+ if !fixed_width {
+ for kid in self.block_flow.base.children.iter_mut() {
+ let base = kid.mut_base();
+ let is_absolutely_positioned =
+ base.flags.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED);
+ if !is_absolutely_positioned {
+ computation.content_intrinsic_sizes.minimum_inline_size = max(
+ computation.content_intrinsic_sizes.minimum_inline_size,
+ base.intrinsic_inline_sizes.minimum_inline_size,
+ );
+
+ computation.content_intrinsic_sizes.preferred_inline_size = max(
+ computation.content_intrinsic_sizes.preferred_inline_size,
+ base.intrinsic_inline_sizes.preferred_inline_size,
+ );
+ }
+ }
+ }
+ self.block_flow.base.intrinsic_inline_sizes = computation.finish();
+ }
+
+ // TODO(zentner): This function needs to be radically different for multi-line flexbox.
+ // Currently, this is the core of BlockFlow::propagate_assigned_inline_size_to_children() with
+ // all float and table logic stripped out.
+ fn block_mode_assign_inline_sizes(
+ &mut self,
+ _layout_context: &LayoutContext,
+ inline_start_content_edge: Au,
+ inline_end_content_edge: Au,
+ content_inline_size: Au,
+ ) {
+ let _scope = layout_debug_scope!("flex::block_mode_assign_inline_sizes");
+ debug!("flex::block_mode_assign_inline_sizes");
+
+ // FIXME (mbrubeck): Get correct mode for absolute containing block
+ let containing_block_mode = self.block_flow.base.writing_mode;
+
+ let container_block_size = match self.available_main_size {
+ AxisSize::Definite(length) => Some(length),
+ _ => None,
+ };
+ let container_inline_size = match self.available_cross_size {
+ AxisSize::Definite(length) => length,
+ AxisSize::MinMax(ref constraint) => constraint.clamp(content_inline_size),
+ AxisSize::Infinite => content_inline_size,
+ };
+
+ let mut children = self.block_flow.base.children.random_access_mut();
+ for kid in &mut self.items {
+ let kid_base = children.get(kid.index).mut_base();
+ kid_base.block_container_explicit_block_size = container_block_size;
+ if kid_base
+ .flags
+ .contains(FlowFlags::INLINE_POSITION_IS_STATIC)
+ {
+ // The inline-start margin edge of the child flow is at our inline-start content
+ // edge, and its inline-size is our content inline-size.
+ kid_base.position.start.i =
+ if kid_base.writing_mode.is_bidi_ltr() == containing_block_mode.is_bidi_ltr() {
+ inline_start_content_edge
+ } else {
+ // The kid's inline 'start' is at the parent's 'end'
+ inline_end_content_edge
+ };
+ }
+ kid_base.block_container_inline_size = container_inline_size;
+ kid_base.block_container_writing_mode = containing_block_mode;
+ kid_base.position.start.i = inline_start_content_edge;
+ }
+ }
+
+ fn inline_mode_assign_inline_sizes(
+ &mut self,
+ layout_context: &LayoutContext,
+ inline_start_content_edge: Au,
+ _inline_end_content_edge: Au,
+ content_inline_size: Au,
+ ) {
+ let _scope = layout_debug_scope!("flex::inline_mode_assign_inline_sizes");
+ debug!("inline_mode_assign_inline_sizes");
+
+ debug!("content_inline_size = {:?}", content_inline_size);
+
+ let child_count = ImmutableFlowUtils::child_count(self as &dyn Flow) as i32;
+ debug!("child_count = {:?}", child_count);
+ if child_count == 0 {
+ return;
+ }
+
+ let inline_size = match self.available_main_size {
+ AxisSize::Definite(length) => length,
+ AxisSize::MinMax(ref constraint) => constraint.clamp(content_inline_size),
+ AxisSize::Infinite => content_inline_size,
+ };
+
+ let container_mode = self.block_flow.base.block_container_writing_mode;
+ self.block_flow.base.position.size.inline = inline_size;
+
+ // Calculate non-auto block size to pass to children.
+ let box_border = self
+ .block_flow
+ .fragment
+ .box_sizing_boundary(Direction::Block);
+
+ let parent_container_size = self
+ .block_flow
+ .explicit_block_containing_size(layout_context.shared_context());
+ // https://drafts.csswg.org/css-ui-3/#box-sizing
+ let explicit_content_size = self
+ .block_flow
+ .explicit_block_size(parent_container_size)
+ .map(|x| max(x - box_border, Au(0)));
+ let containing_block_text_align = self
+ .block_flow
+ .fragment
+ .style()
+ .get_inherited_text()
+ .text_align;
+
+ while let Some(mut line) = self.get_flex_line(inline_size) {
+ let items = &mut self.items[line.range.clone()];
+ line.flex_resolve(items, false);
+ // TODO(stshine): if this flex line contain children that have
+ // property visibility:collapse, exclude them and resolve again.
+
+ let item_count = items.len() as i32;
+ let mut cur_i = inline_start_content_edge;
+ let item_interval = if line.free_space >= Au(0) && line.auto_margin_count == 0 {
+ match self
+ .block_flow
+ .fragment
+ .style()
+ .get_position()
+ .justify_content
+ {
+ JustifyContent::SpaceBetween => {
+ if item_count == 1 {
+ Au(0)
+ } else {
+ line.free_space / (item_count - 1)
+ }
+ },
+ JustifyContent::SpaceAround => line.free_space / item_count,
+ _ => Au(0),
+ }
+ } else {
+ Au(0)
+ };
+
+ match self
+ .block_flow
+ .fragment
+ .style()
+ .get_position()
+ .justify_content
+ {
+ // Overflow equally in both ends of line.
+ JustifyContent::Center | JustifyContent::SpaceAround => {
+ cur_i += (line.free_space - item_interval * (item_count - 1)) / 2;
+ },
+ JustifyContent::FlexEnd => {
+ cur_i += line.free_space;
+ },
+ _ => {},
+ }
+
+ let mut children = self.block_flow.base.children.random_access_mut();
+ for item in items.iter_mut() {
+ let block = children.get(item.index).as_mut_block();
+
+ block.base.block_container_writing_mode = container_mode;
+ block.base.block_container_inline_size = inline_size;
+ block.base.block_container_explicit_block_size = explicit_content_size;
+ // Per CSS 2.1 § 16.3.1, text alignment propagates to all children in flow.
+ //
+ // TODO(#2265, pcwalton): Do this in the cascade instead.
+ block.base.flags.set_text_align(containing_block_text_align);
+
+ let margin = block.fragment.style().logical_margin();
+ let auto_len = if line.auto_margin_count == 0 || line.free_space <= Au(0) {
+ Au(0)
+ } else {
+ line.free_space / line.auto_margin_count
+ };
+ let margin_inline_start = MaybeAuto::from_style(margin.inline_start, inline_size)
+ .specified_or_default(auto_len);
+ let margin_inline_end = MaybeAuto::from_style(margin.inline_end, inline_size)
+ .specified_or_default(auto_len);
+ let item_inline_size = item.main_size -
+ block.fragment.box_sizing_boundary(self.main_mode) +
+ block.fragment.border_padding.inline_start_end();
+ let item_outer_size = item_inline_size + block.fragment.margin.inline_start_end();
+
+ block.fragment.margin.inline_start = margin_inline_start;
+ block.fragment.margin.inline_end = margin_inline_end;
+ block.fragment.border_box.start.i = margin_inline_start;
+ block.fragment.border_box.size.inline = item_inline_size;
+ block.base.position.start.i = if !self.main_reverse {
+ cur_i
+ } else {
+ inline_start_content_edge * 2 + content_inline_size - cur_i - item_outer_size
+ };
+ block.base.position.size.inline = item_outer_size;
+ cur_i += item_outer_size + item_interval;
+ }
+ self.lines.push(line);
+ }
+ }
+
+ // TODO(zentner): This function should actually flex elements!
+ fn block_mode_assign_block_size(&mut self) {
+ let mut cur_b = if !self.main_reverse {
+ self.block_flow.fragment.border_padding.block_start
+ } else {
+ self.block_flow.fragment.border_box.size.block
+ };
+
+ let mut children = self.block_flow.base.children.random_access_mut();
+ for item in &mut self.items {
+ let base = children.get(item.index).mut_base();
+ if !self.main_reverse {
+ base.position.start.b = cur_b;
+ cur_b = cur_b + base.position.size.block;
+ } else {
+ cur_b = cur_b - base.position.size.block;
+ base.position.start.b = cur_b;
+ }
+ }
+ }
+
+ fn inline_mode_assign_block_size(&mut self, layout_context: &LayoutContext) {
+ let _scope = layout_debug_scope!("flex::inline_mode_assign_block_size");
+
+ let line_count = self.lines.len() as i32;
+ let line_align = self
+ .block_flow
+ .fragment
+ .style()
+ .get_position()
+ .align_content;
+ let mut cur_b = self.block_flow.fragment.border_padding.block_start;
+ let mut total_cross_size = Au(0);
+ let mut line_interval = Au(0);
+
+ {
+ let mut children = self.block_flow.base.children.random_access_mut();
+ for line in self.lines.iter_mut() {
+ for item in &self.items[line.range.clone()] {
+ let fragment = &children.get(item.index).as_block().fragment;
+ line.cross_size = max(
+ line.cross_size,
+ fragment.border_box.size.block + fragment.margin.block_start_end(),
+ );
+ }
+ total_cross_size += line.cross_size;
+ }
+ }
+
+ let box_border = self
+ .block_flow
+ .fragment
+ .box_sizing_boundary(Direction::Block);
+ let parent_container_size = self
+ .block_flow
+ .explicit_block_containing_size(layout_context.shared_context());
+ // https://drafts.csswg.org/css-ui-3/#box-sizing
+ let explicit_content_size = self
+ .block_flow
+ .explicit_block_size(parent_container_size)
+ .map(|x| max(x - box_border, Au(0)));
+
+ if let Some(container_block_size) = explicit_content_size {
+ let free_space = container_block_size - total_cross_size;
+ total_cross_size = container_block_size;
+
+ if line_align == AlignContent::Stretch && free_space > Au(0) {
+ for line in self.lines.iter_mut() {
+ line.cross_size += free_space / line_count;
+ }
+ }
+
+ line_interval = match line_align {
+ AlignContent::SpaceBetween => {
+ if line_count <= 1 {
+ Au(0)
+ } else {
+ free_space / (line_count - 1)
+ }
+ },
+ AlignContent::SpaceAround => {
+ if line_count == 0 {
+ Au(0)
+ } else {
+ free_space / line_count
+ }
+ },
+ _ => Au(0),
+ };
+
+ match line_align {
+ AlignContent::Center | AlignContent::SpaceAround => {
+ cur_b += (free_space - line_interval * (line_count - 1)) / 2;
+ },
+ AlignContent::FlexEnd => {
+ cur_b += free_space;
+ },
+ _ => {},
+ }
+ }
+
+ let mut children = self.block_flow.base.children.random_access_mut();
+ for line in &self.lines {
+ for item in self.items[line.range.clone()].iter_mut() {
+ let block = children.get(item.index).as_mut_block();
+ let auto_margin_count = item.auto_margin_count(block, Direction::Block);
+ let margin = block.fragment.style().logical_margin();
+
+ let mut margin_block_start = block.fragment.margin.block_start;
+ let mut margin_block_end = block.fragment.margin.block_end;
+ let mut free_space = line.cross_size -
+ block.base.position.size.block -
+ block.fragment.margin.block_start_end();
+
+ // The spec is a little vague here, but if I understand it correctly, the outer
+ // cross size of item should equal to the line size if any auto margin exists.
+ // https://drafts.csswg.org/css-flexbox/#algo-cross-margins
+ if auto_margin_count > 0 {
+ if margin.block_start.is_auto() {
+ margin_block_start = if free_space < Au(0) {
+ Au(0)
+ } else {
+ free_space / auto_margin_count
+ };
+ }
+ margin_block_end =
+ line.cross_size - margin_block_start - block.base.position.size.block;
+ free_space = Au(0);
+ }
+
+ let self_align = block.fragment.style().get_position().align_self;
+ if self_align == AlignSelf::Stretch &&
+ block.fragment.style().content_block_size().is_auto()
+ {
+ free_space = Au(0);
+ block.base.block_container_explicit_block_size = Some(line.cross_size);
+ block.base.position.size.block =
+ line.cross_size - margin_block_start - margin_block_end;
+ block.fragment.border_box.size.block = block.base.position.size.block;
+ // FIXME(stshine): item with 'align-self: stretch' and auto cross size should act
+ // as if it has a fixed cross size, all child blocks should resolve against it.
+ // block.assign_block_size(layout_context);
+ }
+ block.base.position.start.b = margin_block_start +
+ if !self.cross_reverse {
+ cur_b
+ } else {
+ self.block_flow.fragment.border_padding.block_start * 2 + total_cross_size -
+ cur_b -
+ line.cross_size
+ };
+ // TODO(stshine): support baseline alignment.
+ if free_space != Au(0) {
+ let flex_cross = match self_align {
+ AlignSelf::FlexEnd => free_space,
+ AlignSelf::Center => free_space / 2,
+ _ => Au(0),
+ };
+ block.base.position.start.b += if !self.cross_reverse {
+ flex_cross
+ } else {
+ free_space - flex_cross
+ };
+ }
+ }
+ cur_b += line_interval + line.cross_size;
+ }
+ let total_block_size =
+ total_cross_size + self.block_flow.fragment.border_padding.block_start_end();
+ self.block_flow.fragment.border_box.size.block = total_block_size;
+ self.block_flow.base.position.size.block = total_block_size;
+ }
+}
+
+impl Flow for FlexFlow {
+ fn class(&self) -> FlowClass {
+ FlowClass::Flex
+ }
+
+ fn as_flex(&self) -> &FlexFlow {
+ self
+ }
+
+ fn as_block(&self) -> &BlockFlow {
+ &self.block_flow
+ }
+
+ fn as_mut_block(&mut self) -> &mut BlockFlow {
+ &mut self.block_flow
+ }
+
+ fn mark_as_root(&mut self) {
+ self.block_flow.mark_as_root();
+ }
+
+ fn bubble_inline_sizes(&mut self) {
+ let _scope = layout_debug_scope!(
+ "flex::bubble_inline_sizes {:x}",
+ self.block_flow.base.debug_id()
+ );
+
+ // Flexbox Section 9.0: Generate anonymous flex items:
+ // This part was handled in the flow constructor.
+
+ // Flexbox Section 9.1: Re-order flex items according to their order.
+ // FIXME(stshine): This should be done during flow construction.
+ let mut items: Vec<FlexItem> = self
+ .block_flow
+ .base
+ .children
+ .iter()
+ .enumerate()
+ .filter(|&(_, flow)| {
+ !flow
+ .as_block()
+ .base
+ .flags
+ .contains(FlowFlags::IS_ABSOLUTELY_POSITIONED)
+ })
+ .map(|(index, flow)| FlexItem::new(index, flow))
+ .collect();
+
+ items.sort_by_key(|item| item.order);
+ self.items = items;
+
+ match self.main_mode {
+ Direction::Inline => self.inline_mode_bubble_inline_sizes(),
+ Direction::Block => self.block_mode_bubble_inline_sizes(),
+ }
+ }
+
+ fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
+ let _scope = layout_debug_scope!(
+ "flex::assign_inline_sizes {:x}",
+ self.block_flow.base.debug_id()
+ );
+ debug!("assign_inline_sizes");
+
+ if !self
+ .block_flow
+ .base
+ .restyle_damage
+ .intersects(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW)
+ {
+ return;
+ }
+
+ self.block_flow
+ .initialize_container_size_for_root(layout_context.shared_context());
+
+ // Our inline-size was set to the inline-size of the containing block by the flow's parent.
+ // Now compute the real value.
+ let containing_block_inline_size = self.block_flow.base.block_container_inline_size;
+ self.block_flow.compute_used_inline_size(
+ layout_context.shared_context(),
+ containing_block_inline_size,
+ );
+ if self.block_flow.base.flags.is_float() {
+ self.block_flow
+ .float
+ .as_mut()
+ .unwrap()
+ .containing_inline_size = containing_block_inline_size
+ }
+
+ let (available_block_size, available_inline_size) = {
+ let style = &self.block_flow.fragment.style;
+ let (specified_block_size, specified_inline_size) = if style.writing_mode.is_vertical()
+ {
+ (style.get_position().width, style.get_position().height)
+ } else {
+ (style.get_position().height, style.get_position().width)
+ };
+
+ let available_inline_size = AxisSize::new(
+ specified_inline_size,
+ Some(self.block_flow.base.block_container_inline_size),
+ style.min_inline_size(),
+ style.max_inline_size(),
+ );
+
+ let available_block_size = AxisSize::new(
+ specified_block_size,
+ self.block_flow.base.block_container_explicit_block_size,
+ style.min_block_size(),
+ style.max_block_size(),
+ );
+ (available_block_size, available_inline_size)
+ };
+
+ // Move in from the inline-start border edge.
+ let inline_start_content_edge = self.block_flow.fragment.border_box.start.i +
+ self.block_flow.fragment.border_padding.inline_start;
+
+ debug!(
+ "inline_start_content_edge = {:?}",
+ inline_start_content_edge
+ );
+
+ let padding_and_borders = self.block_flow.fragment.border_padding.inline_start_end();
+
+ // Distance from the inline-end margin edge to the inline-end content edge.
+ let inline_end_content_edge = self.block_flow.fragment.margin.inline_end +
+ self.block_flow.fragment.border_padding.inline_end;
+
+ debug!("padding_and_borders = {:?}", padding_and_borders);
+ debug!(
+ "self.block_flow.fragment.border_box.size.inline = {:?}",
+ self.block_flow.fragment.border_box.size.inline
+ );
+ let content_inline_size =
+ self.block_flow.fragment.border_box.size.inline - padding_and_borders;
+
+ match self.main_mode {
+ Direction::Inline => {
+ self.available_main_size = available_inline_size;
+ self.available_cross_size = available_block_size;
+ self.inline_mode_assign_inline_sizes(
+ layout_context,
+ inline_start_content_edge,
+ inline_end_content_edge,
+ content_inline_size,
+ )
+ },
+ Direction::Block => {
+ self.available_main_size = available_block_size;
+ self.available_cross_size = available_inline_size;
+ self.block_mode_assign_inline_sizes(
+ layout_context,
+ inline_start_content_edge,
+ inline_end_content_edge,
+ content_inline_size,
+ )
+ },
+ }
+ }
+
+ fn assign_block_size(&mut self, layout_context: &LayoutContext) {
+ match self.main_mode {
+ Direction::Inline => {
+ self.inline_mode_assign_block_size(layout_context);
+ let block_start =
+ AdjoiningMargins::from_margin(self.block_flow.fragment.margin.block_start);
+ let block_end =
+ AdjoiningMargins::from_margin(self.block_flow.fragment.margin.block_end);
+ self.block_flow.base.collapsible_margins =
+ CollapsibleMargins::Collapse(block_start, block_end);
+
+ // TODO(stshine): assign proper static position for absolute descendants.
+ if (&*self as &dyn Flow).contains_roots_of_absolute_flow_tree() {
+ // Assign block-sizes for all flows in this absolute flow tree.
+ // This is preorder because the block-size of an absolute flow may depend on
+ // the block-size of its containing block, which may also be an absolute flow.
+ let assign_abs_b_sizes =
+ AbsoluteAssignBSizesTraversal(layout_context.shared_context());
+ assign_abs_b_sizes.traverse_absolute_flows(&mut *self);
+ }
+ },
+ Direction::Block => {
+ self.block_flow.assign_block_size_block_base(
+ layout_context,
+ None,
+ MarginsMayCollapseFlag::MarginsMayNotCollapse,
+ );
+ self.block_mode_assign_block_size();
+ },
+ }
+ }
+
+ fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
+ self.block_flow
+ .compute_stacking_relative_position(layout_context)
+ }
+
+ fn place_float_if_applicable<'a>(&mut self) {
+ self.block_flow.place_float_if_applicable()
+ }
+
+ fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
+ self.block_flow
+ .update_late_computed_inline_position_if_necessary(inline_position)
+ }
+
+ fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
+ self.block_flow
+ .update_late_computed_block_position_if_necessary(block_position)
+ }
+
+ fn build_display_list(&mut self, state: &mut DisplayListBuildState) {
+ // Draw the rest of the block.
+ self.as_mut_block()
+ .build_display_list_for_block(state, BorderPaintingMode::Separate)
+ }
+
+ fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
+ self.block_flow.collect_stacking_contexts(state);
+ }
+
+ fn repair_style(&mut self, new_style: &crate::ServoArc<ComputedValues>) {
+ self.block_flow.repair_style(new_style)
+ }
+
+ fn compute_overflow(&self) -> Overflow {
+ self.block_flow.compute_overflow()
+ }
+
+ fn contains_roots_of_absolute_flow_tree(&self) -> bool {
+ self.block_flow.contains_roots_of_absolute_flow_tree()
+ }
+
+ fn is_absolute_containing_block(&self) -> bool {
+ self.block_flow.is_absolute_containing_block()
+ }
+
+ fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
+ self.block_flow.generated_containing_block_size(flow)
+ }
+
+ fn iterate_through_fragment_border_boxes(
+ &self,
+ iterator: &mut dyn FragmentBorderBoxIterator,
+ level: i32,
+ stacking_context_position: &Point2D<Au>,
+ ) {
+ self.block_flow.iterate_through_fragment_border_boxes(
+ iterator,
+ level,
+ stacking_context_position,
+ );
+ }
+
+ fn mutate_fragments(&mut self, mutator: &mut dyn FnMut(&mut Fragment)) {
+ self.block_flow.mutate_fragments(mutator);
+ }
+}