diff options
Diffstat (limited to 'components/layout_2020/flex.rs')
-rw-r--r-- | components/layout_2020/flex.rs | 1128 |
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); + } +} |