/* 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 http://mozilla.org/MPL/2.0/. */ //! Layout for elements with a CSS `display` property of `flex`. #![deny(unsafe_code)] use app_units::Au; use block::BlockFlow; use context::LayoutContext; use display_list_builder::{DisplayListBuildState, FlexFlowDisplayListBuilding}; use euclid::Point2D; use floats::FloatKind; use flow; use flow::INLINE_POSITION_IS_STATIC; use flow::IS_ABSOLUTELY_POSITIONED; use flow::{Flow, FlowClass, ImmutableFlowUtils, OpaqueFlow}; use fragment::{Fragment, FragmentBorderBoxIterator, Overflow}; use gfx::display_list::{StackingContext, StackingContextId}; use incremental::{REFLOW, REFLOW_OUT_OF_FLOW}; use layout_debug; use model::{IntrinsicISizes, MaybeAuto, MinMaxConstraint}; use std::cmp::max; use std::sync::Arc; use style::computed_values::flex_direction; use style::logical_geometry::LogicalSize; use style::properties::{ComputedValues, ServoComputedValues}; use style::values::computed::{LengthOrPercentage, LengthOrPercentageOrAuto, LengthOrPercentageOrNone}; /// The size of an axis. May be a specified size, a min/max /// constraint, or an unlimited size #[derive(Debug)] enum AxisSize { Definite(Au), MinMax(MinMaxConstraint), 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: LengthOrPercentageOrAuto, content_size: Option, min: LengthOrPercentage, max: LengthOrPercentageOrNone) -> AxisSize { match size { LengthOrPercentageOrAuto::Length(length) => AxisSize::Definite(length), LengthOrPercentageOrAuto::Percentage(percent) => { match content_size { Some(size) => AxisSize::Definite(size.scale_by(percent)), None => AxisSize::Infinite } }, LengthOrPercentageOrAuto::Calc(calc) => { match content_size { Some(size) => AxisSize::Definite(size.scale_by(calc.percentage())), None => AxisSize::Infinite } }, LengthOrPercentageOrAuto::Auto => { AxisSize::MinMax(MinMaxConstraint::new(content_size, min, max)) } } } } // A mode describes which logical axis a flex axis is parallel with. // The logical axises are inline and block, the flex axises are main and cross. // When the flex container has flex-direction: column or flex-direction: column-reverse, the main axis // should be block. Otherwise, it should be inline. #[derive(Debug)] enum Mode { Inline, Block } /// A block with the CSS `display` property equal to `flex`. #[derive(Debug)] 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: Mode, /// The available main axis size available_main_size: AxisSize, /// The available cross axis size available_cross_size: AxisSize } // TODO(zentner): This function should use flex-basis. fn flex_item_inline_sizes(flow: &mut Flow) -> IntrinsicISizes { let _scope = layout_debug_scope!("flex::flex_item_inline_sizes"); debug!("flex_item_inline_sizes"); let base = flow::mut_base(flow); debug!("FlexItem intrinsic inline sizes: {:?}, {:?}", base.intrinsic_inline_sizes.minimum_inline_size, base.intrinsic_inline_sizes.preferred_inline_size); IntrinsicISizes { minimum_inline_size: base.intrinsic_inline_sizes.minimum_inline_size, preferred_inline_size: base.intrinsic_inline_sizes.preferred_inline_size, } } impl FlexFlow { pub fn from_fragment(fragment: Fragment, flotation: Option) -> FlexFlow { let main_mode = match fragment.style.get_position().flex_direction { flex_direction::T::row_reverse | flex_direction::T::row => Mode::Inline, flex_direction::T::column_reverse | flex_direction::T::column => Mode::Block }; FlexFlow { block_flow: BlockFlow::from_fragment(fragment, flotation), main_mode: main_mode, available_main_size: AxisSize::Infinite, available_cross_size: AxisSize::Infinite } } // 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) { let fixed_width = match self.block_flow.fragment.style().get_box().width { LengthOrPercentageOrAuto::Length(_) => true, _ => false, }; let mut computation = self.block_flow.fragment.compute_intrinsic_inline_sizes(); if !fixed_width { for kid in self.block_flow.base.child_iter() { let is_absolutely_positioned = flow::base(kid).flags.contains(IS_ABSOLUTELY_POSITIONED); if !is_absolutely_positioned { computation.union_nonbreaking_inline(&flex_item_inline_sizes(kid)); } } } 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 = match self.block_flow.fragment.style().get_box().width { LengthOrPercentageOrAuto::Length(_) => true, _ => false, }; let mut computation = self.block_flow.fragment.compute_intrinsic_inline_sizes(); if !fixed_width { for kid in self.block_flow.base.child_iter() { let is_absolutely_positioned = flow::base(kid).flags.contains(IS_ABSOLUTELY_POSITIONED); let child_base = flow::mut_base(kid); if !is_absolutely_positioned { computation.content_intrinsic_sizes.minimum_inline_size = max(computation.content_intrinsic_sizes.minimum_inline_size, child_base.intrinsic_inline_sizes.minimum_inline_size); computation.content_intrinsic_sizes.preferred_inline_size = max(computation.content_intrinsic_sizes.preferred_inline_size, child_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!("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 mut iterator = self.block_flow.base.child_iter().enumerate().peekable(); while let Some((_, kid)) = iterator.next() { { let kid_base = flow::mut_base(kid); kid_base.block_container_explicit_block_size = match self.available_main_size { AxisSize::Definite(length) => Some(length), _ => None } } // 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. let kid_mode = flow::base(kid).writing_mode; { let kid_base = flow::mut_base(kid); if kid_base.flags.contains(INLINE_POSITION_IS_STATIC) { kid_base.position.start.i = if kid_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 = match self.available_main_size { AxisSize::Definite(length) => length, AxisSize::MinMax(ref constraint) => constraint.clamp(content_inline_size), AxisSize::Infinite => content_inline_size, }; kid_base.block_container_writing_mode = containing_block_mode; } } } // TODO(zentner): This function should actually flex elements! // Currently, this is the core of InlineFlow::propagate_assigned_inline_size_to_children() with // fragment logic stripped out. 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 &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 even_content_inline_size = inline_size / child_count; let inline_size = self.block_flow.base.block_container_inline_size; let container_mode = self.block_flow.base.block_container_writing_mode; self.block_flow.base.position.size.inline = inline_size; let block_container_explicit_block_size = self.block_flow.base.block_container_explicit_block_size; let mut inline_child_start = inline_start_content_edge; for kid in self.block_flow.base.child_iter() { let kid_base = flow::mut_base(kid); kid_base.block_container_inline_size = even_content_inline_size; kid_base.block_container_writing_mode = container_mode; kid_base.block_container_explicit_block_size = block_container_explicit_block_size; kid_base.position.start.i = inline_child_start; inline_child_start = inline_child_start + even_content_inline_size; } } // TODO(zentner): This function should actually flex elements! fn block_mode_assign_block_size<'a>(&mut self, layout_context: &'a LayoutContext<'a>) { self.block_flow.assign_block_size(layout_context) } // TODO(zentner): This function should actually flex elements! // Currently, this is the core of TableRowFlow::assign_block_size() with // float related logic stripped out. fn inline_mode_assign_block_size<'a>(&mut self, layout_context: &'a LayoutContext<'a>) { let _scope = layout_debug_scope!("flex::inline_mode_assign_block_size"); let mut max_block_size = Au(0); let thread_id = self.block_flow.base.thread_id; for kid in self.block_flow.base.child_iter() { kid.assign_block_size_for_inorder_child_if_necessary(layout_context, thread_id); { let child_fragment = &mut kid.as_mut_block().fragment; // TODO: Percentage block-size let child_specified_block_size = MaybeAuto::from_style(child_fragment.style().content_block_size(), Au(0)).specified_or_zero(); max_block_size = max(max_block_size, child_specified_block_size + child_fragment.border_padding.block_start_end()); } let child_node = flow::mut_base(kid); child_node.position.start.b = Au(0); max_block_size = max(max_block_size, child_node.position.size.block); } let mut block_size = max_block_size; // TODO: Percentage block-size block_size = match MaybeAuto::from_style(self.block_flow .fragment .style() .content_block_size(), Au(0)) { MaybeAuto::Auto => block_size, MaybeAuto::Specified(value) => max(value, block_size), }; // Assign the block-size of own fragment let mut position = self.block_flow.fragment.border_box; position.size.block = block_size; self.block_flow.fragment.border_box = position; self.block_flow.base.position.size.block = block_size; // Assign the block-size of kid fragments, which is the same value as own block-size. for kid in self.block_flow.base.child_iter() { { let kid_fragment = &mut kid.as_mut_block().fragment; let mut position = kid_fragment.border_box; position.size.block = block_size; kid_fragment.border_box = position; } // Assign the child's block size. flow::mut_base(kid).position.size.block = block_size } } } impl Flow for FlexFlow { fn class(&self) -> FlowClass { FlowClass::Flex } 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 the flex items (and any absolutely positioned flex // container children) according to their order. // TODO(zentner): We need to re-order the items at some point. However, all the operations // here ignore order, so we can afford to do it later, if necessary. match self.main_mode { Mode::Inline => self.inline_mode_bubble_inline_sizes(), Mode::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(REFLOW_OUT_OF_FLOW | REFLOW) { return } // 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, 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_box().width, style.get_box().height) } else { (style.get_box().height, style.get_box().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 { Mode::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) }, Mode::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<'a>(&mut self, layout_context: &'a LayoutContext<'a>) { self.block_flow.assign_block_size(layout_context); match self.main_mode { Mode::Inline => self.inline_mode_assign_block_size(layout_context), Mode::Block => self.block_mode_assign_block_size(layout_context) } } fn compute_absolute_position(&mut self, layout_context: &LayoutContext) { self.block_flow.compute_absolute_position(layout_context) } fn place_float_if_applicable<'a>(&mut self, layout_context: &'a LayoutContext<'a>) { self.block_flow.place_float_if_applicable(layout_context) } 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) { self.build_display_list_for_flex(state); } fn collect_stacking_contexts(&mut self, parent_id: StackingContextId, contexts: &mut Vec>) -> StackingContextId { self.block_flow.collect_stacking_contexts(parent_id, contexts) } fn repair_style(&mut self, new_style: &Arc) { self.block_flow.repair_style(new_style) } fn compute_overflow(&self) -> Overflow { self.block_flow.compute_overflow() } fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize { self.block_flow.generated_containing_block_size(flow) } fn iterate_through_fragment_border_boxes(&self, iterator: &mut FragmentBorderBoxIterator, level: i32, stacking_context_position: &Point2D) { self.block_flow.iterate_through_fragment_border_boxes(iterator, level, stacking_context_position); } fn mutate_fragments(&mut self, mutator: &mut FnMut(&mut Fragment)) { self.block_flow.mutate_fragments(mutator); } }