servo/components/layout/flex.rs
Martin Robinson e7019f2721 Flatten display list structure
Instead of producing a tree of stacking contexts, display list
generation now produces a flat list of display items and a tree of
stacking contexts. This will eventually allow display list construction
to produce and modify WebRender vertex buffers directly, removing the
overhead of display list conversion.  This change also moves
layerization of the display list to the paint thread, since it isn't
currently useful for WebRender.

To accomplish this, display list generation now takes three passes of
the flow tree:

        1. Calculation of absolute positions.
        2. Collection of a tree of stacking contexts.
        3. Creation of a list of display items.

After collection of display items, they are sorted based upon the index
of their parent stacking contexts and their position in CSS 2.1
Appendeix E stacking order.

This is a big change, but it actually simplifies display list generation.
2016-03-01 14:50:07 -08:00

460 lines
20 KiB
Rust

/* 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::ImmutableFlowUtils;
use flow::{Flow, FlowClass, OpaqueFlow};
use flow::{HAS_LEFT_FLOATED_DESCENDANTS, HAS_RIGHT_FLOATED_DESCENDANTS};
use fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
use gfx::display_list::{StackingContext, StackingContextId};
use incremental::{REFLOW, REFLOW_OUT_OF_FLOW};
use layout_debug;
use model::MaybeAuto;
use model::{IntrinsicISizes};
use std::cmp::max;
use std::sync::Arc;
use style::computed_values::{flex_direction, float};
use style::logical_geometry::LogicalSize;
use style::properties::ComputedValues;
use style::properties::style_structs;
use style::values::computed::LengthOrPercentageOrAuto;
use util::opts;
// 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,
}
fn flex_style(fragment: &Fragment) -> &style_structs::Flex {
fragment.style.get_flex()
}
// 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<FloatKind>)
-> FlexFlow {
let main_mode = match flex_style(&fragment).flex_direction {
flex_direction::T::row_reverse => Mode::Inline,
flex_direction::T::row => Mode::Inline,
flex_direction::T::column_reverse => Mode::Block,
flex_direction::T::column => Mode::Block
};
FlexFlow {
block_flow: BlockFlow::from_fragment(fragment, flotation),
main_mode: main_mode
}
}
// 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");
// Calculate non-auto block size to pass to children.
let content_block_size = self.block_flow.fragment.style().content_block_size();
let explicit_content_size =
match (content_block_size, self.block_flow.base.block_container_explicit_block_size) {
(LengthOrPercentageOrAuto::Percentage(percent), Some(container_size)) => {
Some(container_size.scale_by(percent))
}
(LengthOrPercentageOrAuto::Percentage(_), None) |
(LengthOrPercentageOrAuto::Auto, _) => None,
(LengthOrPercentageOrAuto::Calc(_), _) => None,
(LengthOrPercentageOrAuto::Length(length), _) => Some(length),
};
// 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 = explicit_content_size;
}
// 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 = 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 even_content_inline_size = content_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.
// `flex item`s (our children) cannot be floated. Furthermore, they all establish BFC's.
// Therefore, we do not have to handle any floats here.
let mut flags = self.block_flow.base.flags;
flags.remove(HAS_LEFT_FLOATED_DESCENDANTS);
flags.remove(HAS_RIGHT_FLOATED_DESCENDANTS);
match self.main_mode {
Mode::Inline => self.inline_mode_bubble_inline_sizes(),
Mode::Block => self.block_mode_bubble_inline_sizes()
}
// Although our children can't be floated, we can.
match self.block_flow.fragment.style().get_box().float {
float::T::none => {}
float::T::left => flags.insert(HAS_LEFT_FLOATED_DESCENDANTS),
float::T::right => flags.insert(HAS_RIGHT_FLOATED_DESCENDANTS),
}
self.block_flow.base.flags = flags
}
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
}
// 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.inline_mode_assign_inline_sizes(layout_context,
inline_start_content_edge,
inline_end_content_edge,
content_inline_size),
Mode::Block =>
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);
if opts::get().validate_display_list_geometry {
self.block_flow.base.validate_display_list_geometry();
}
}
fn collect_stacking_contexts(&mut self,
parent_id: StackingContextId,
contexts: &mut Vec<StackingContext>)
-> StackingContextId {
self.block_flow.collect_stacking_contexts(parent_id, contexts)
}
fn repair_style(&mut self, new_style: &Arc<ComputedValues>) {
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<Au> {
self.block_flow.generated_containing_block_size(flow)
}
fn iterate_through_fragment_border_boxes(&self,
iterator: &mut 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 FnMut(&mut Fragment)) {
self.block_flow.mutate_fragments(mutator);
}
}