servo: Merge #10178 - Implement flexbox reordering (from danlrobertson:flex-order); r=SimonSapin
authorDaniel Robertson <dan.robertson@anidata.org>
Tue, 12 Apr 2016 02:48:20 +0500
changeset 476648 993f348880a66120f717a4cca374614f58a1a625
parent 476647 3f3e57e89169f4fb7fa4766d2462f711fc096b57
child 476649 cc4bbd467e8dcf96cf48ac297e29a14fd3ea2b45
push id44079
push userbmo:gps@mozilla.com
push dateSat, 04 Feb 2017 00:14:49 +0000
reviewersSimonSapin
servo: Merge #10178 - Implement flexbox reordering (from danlrobertson:flex-order); r=SimonSapin Add style property for `order` and implement reordering by this property in flex flow. Based on previous work by @zentner-kyle. Fixes: #9957 Source-Repo: https://github.com/servo/servo Source-Revision: df21bb47d414a1ff068623b55e9da644d7239731
servo/components/layout/flex.rs
servo/components/layout/flow_list.rs
servo/components/script/dom/webidls/CSSStyleDeclaration.webidl
servo/components/style/properties.mako.rs
--- a/servo/components/layout/flex.rs
+++ b/servo/components/layout/flex.rs
@@ -8,19 +8,19 @@
 
 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 flow::{INLINE_POSITION_IS_STATIC, IS_ABSOLUTELY_POSITIONED};
+use flow_ref::{self, FlowRef};
 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;
@@ -68,80 +68,84 @@ impl AxisSize {
 // 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
 }
 
+#[derive(Debug)]
+struct FlexItem {
+    pub flow: FlowRef,
+}
+
+impl FlexItem {
+    fn new(flow: FlowRef) -> FlexItem {
+        FlexItem {
+            flow: flow
+        }
+    }
+}
+
 /// 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,
-    }
+    available_cross_size: AxisSize,
+    /// List of flex-items that belong to this flex-container
+    items: Vec<FlexItem>
 }
 
 impl FlexFlow {
     pub fn from_fragment(fragment: Fragment,
                          flotation: Option<FloatKind>)
                          -> 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
+            available_cross_size: AxisSize::Infinite,
+            items: Vec::new()
         }
     }
 
     // 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_mut() {
-                let is_absolutely_positioned =
-                    flow::base(kid).flags.contains(IS_ABSOLUTELY_POSITIONED);
+            for kid in &mut self.items {
+                let base = flow::mut_base(flow_ref::deref_mut(&mut kid.flow));
+                let is_absolutely_positioned = base.flags.contains(IS_ABSOLUTELY_POSITIONED);
                 if !is_absolutely_positioned {
-                    computation.union_nonbreaking_inline(&flex_item_inline_sizes(kid));
+                    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
@@ -149,28 +153,27 @@ impl FlexFlow {
     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_mut() {
-                let is_absolutely_positioned =
-                    flow::base(kid).flags.contains(IS_ABSOLUTELY_POSITIONED);
-                let child_base = flow::mut_base(kid);
+            for kid in &mut self.items {
+                let base = flow::mut_base(flow_ref::deref_mut(&mut kid.flow));
+                let is_absolutely_positioned = base.flags.contains(IS_ABSOLUTELY_POSITIONED);
                 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);
+                            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);
+                            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
@@ -190,39 +193,34 @@ impl FlexFlow {
             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 iterator = self.block_flow.base.child_iter_mut().enumerate().peekable();
-        while let Some((_, kid)) = iterator.next() {
+        for kid in &mut self.items {
             {
-                let kid_base = flow::mut_base(kid);
+                let kid_base = flow::mut_base(flow_ref::deref_mut(&mut kid.flow));
                 kid_base.block_container_explicit_block_size = container_block_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) {
+                    // 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_mode.is_bidi_ltr() == containing_block_mode.is_bidi_ltr() {
+                        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;
             }
         }
     }
 
     // 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,
@@ -249,29 +247,35 @@ impl FlexFlow {
 
         let even_content_inline_size = inline_size / child_count;
 
         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_mut() {
-            let kid_base = flow::mut_base(kid);
+        for kid in &mut self.items {
+            let base = flow::mut_base(flow_ref::deref_mut(&mut kid.flow));
 
-            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;
+            base.block_container_inline_size = even_content_inline_size;
+            base.block_container_writing_mode = container_mode;
+            base.block_container_explicit_block_size = block_container_explicit_block_size;
+            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>) {
+        let mut cur_b = self.block_flow.fragment.border_padding.block_start;
+        for kid in &mut self.items {
+            let base = flow::mut_base(flow_ref::deref_mut(&mut kid.flow));
+            base.position.start.b = cur_b;
+            cur_b = cur_b + base.position.size.block;
+        }
         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");
@@ -330,16 +334,20 @@ impl FlexFlow {
     }
 }
 
 impl Flow for FlexFlow {
     fn class(&self) -> FlowClass {
         FlowClass::Flex
     }
 
+    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();
     }
 
@@ -347,18 +355,28 @@ impl Flow for FlexFlow {
         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.
+
+        let mut items = self.block_flow.base.children.iter_flow_ref_mut().map(|flow| {
+            FlexItem::new(flow.clone())
+        }).collect::<Vec<FlexItem>>();
+
+        items.sort_by(|item1, item2| {
+            item1.flow.as_block().fragment.style.get_position().order.cmp(
+                &item2.flow.as_block().fragment.style.get_position().order
+                )
+        });
+
+        self.items = items;
 
         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) {
--- a/servo/components/layout/flow_list.rs
+++ b/servo/components/layout/flow_list.rs
@@ -59,16 +59,22 @@ impl FlowList {
     /// Provide a forward iterator with mutable references
     #[inline]
     pub fn iter_mut(&mut self) -> MutFlowListIterator {
         MutFlowListIterator {
             it: self.flows.iter_mut(),
         }
     }
 
+    /// Provide a forward iterator with FlowRef items
+    #[inline]
+    pub fn iter_flow_ref_mut<'a>(&'a mut self) -> linked_list::IterMut<'a, FlowRef> {
+        self.flows.iter_mut()
+    }
+
     /// O(1)
     #[inline]
     pub fn is_empty(&self) -> bool {
         self.flows.is_empty()
     }
 
     /// O(1)
     #[inline]
--- a/servo/components/script/dom/webidls/CSSStyleDeclaration.webidl
+++ b/servo/components/script/dom/webidls/CSSStyleDeclaration.webidl
@@ -305,9 +305,10 @@ partial interface CSSStyleDeclaration {
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString transition-timing-function;
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString transitionProperty;
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString transition-property;
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString transitionDelay;
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString transition-delay;
 
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString flexDirection;
   [SetterThrows, TreatNullAs=EmptyString] attribute DOMString flex-direction;
+  [SetterThrows, TreatNullAs=EmptyString] attribute DOMString order;
 };
--- a/servo/components/style/properties.mako.rs
+++ b/servo/components/style/properties.mako.rs
@@ -4901,16 +4901,38 @@ pub mod longhands {
 
     // CSS Flexible Box Layout Module Level 1
     // http://www.w3.org/TR/css3-flexbox/
 
     ${switch_to_style_struct("Position")}
 
     // Flex container properties
     ${single_keyword("flex-direction", "row row-reverse column column-reverse", experimental=True)}
+
+    // https://drafts.csswg.org/css-flexbox/#propdef-order
+    <%self:longhand name="order">
+        use values::computed::ComputedValueAsSpecified;
+
+        impl ComputedValueAsSpecified for SpecifiedValue {}
+
+        pub type SpecifiedValue = computed_value::T;
+
+        pub mod computed_value {
+            pub type T = i32;
+        }
+
+        #[inline]
+        pub fn get_initial_value() -> computed_value::T {
+            0
+        }
+
+        fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+            specified::parse_integer(input)
+        }
+    </%self:longhand>
 }
 
 
 pub mod shorthands {
     use cssparser::Parser;
     use parser::ParserContext;
     use values::specified;