diff --git a/src/JavaTranspiler.js b/src/JavaTranspiler.js index 669d64be..7ac3fdcc 100644 --- a/src/JavaTranspiler.js +++ b/src/JavaTranspiler.js @@ -14,6 +14,7 @@ function __transpileToJavaCommon(code) { .replace(/CSS_FLEX_DIRECTION_/g, 'CSSFlexDirection.') .replace(/css_align_t/g, 'CSSAlign') .replace(/CSS_ALIGN_/g, 'CSSAlign.') + .replace(/CSS_WRAP/g, 'CSSWrap.WRAP') .replace(/CSS_POSITION_/g, 'CSSPositionType.') .replace(/css_justify_t/g, 'CSSJustify') .replace(/CSS_JUSTIFY_/g, 'CSSJustify.') diff --git a/src/Layout.c b/src/Layout.c index 566f6930..fe773a98 100644 --- a/src/Layout.c +++ b/src/Layout.c @@ -276,6 +276,10 @@ static bool isFlex(css_node_t *node) { ); } +static bool isFlexWrap(css_node_t *node) { + return node->style.flex_wrap == CSS_WRAP; +} + static float getDimWithMargin(css_node_t *node, css_flex_direction_t axis) { return node->layout.dimensions[dim[axis]] + getMargin(node, leading[axis]) + @@ -425,97 +429,50 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { } } - // Layout non flexible children and count children by type - - // mainContentDim is accumulation of the dimensions and margin of all the - // non flexible children. This will be used in order to either set the - // dimensions of the node if none already exist, or to compute the - // remaining space left for the flexible children. - float mainContentDim = 0; - - // There are three kind of children, non flexible, flexible and absolute. - // We need to know how many there are in order to distribute the space. - int flexibleChildrenCount = 0; - float totalFlexible = 0; - int nonFlexibleChildrenCount = 0; - for (int i = 0; i < node->children_count; ++i) { - css_node_t* child = node->get_child(node->context, i); - - // It only makes sense to consider a child flexible if we have a computed - // dimension for the node-> - if (!isUndefined(node->layout.dimensions[dim[mainAxis]]) && isFlex(child)) { - flexibleChildrenCount++; - totalFlexible += getFlex(child); - - // Even if we don't know its exact size yet, we already know the padding, - // border and margin. We'll use this partial information to compute the - // remaining space. - mainContentDim += getPaddingAndBorderAxis(child, mainAxis) + - getMarginAxis(child, mainAxis); - - } else { - float maxWidth = CSS_UNDEFINED; - if (mainAxis == CSS_FLEX_DIRECTION_ROW) { - // do nothing - } else if (isDimDefined(node, CSS_FLEX_DIRECTION_ROW)) { - maxWidth = node->layout.dimensions[dim[CSS_FLEX_DIRECTION_ROW]] - - getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW); - } else { - maxWidth = parentMaxWidth - - getMarginAxis(node, CSS_FLEX_DIRECTION_ROW) - - getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW); - } - - // This is the main recursive call. We layout non flexible children. - layoutNode(child, maxWidth); - - // Absolute positioned elements do not take part of the layout, so we - // don't use them to compute mainContentDim - if (getPositionType(child) == CSS_POSITION_RELATIVE) { - nonFlexibleChildrenCount++; - // At this point we know the final size and margin of the element. - mainContentDim += getDimWithMargin(child, mainAxis); - } - } - } - - // Layout flexible children and allocate empty space - - // In order to position the elements in the main axis, we have two - // controls. The space between the beginning and the first element - // and the space between each two elements. - float leadingMainDim = 0; - float betweenMainDim = 0; - - float definedMainDim = fmaxf(mainContentDim, 0); + float definedMainDim = CSS_UNDEFINED; if (!isUndefined(node->layout.dimensions[dim[mainAxis]])) { definedMainDim = node->layout.dimensions[dim[mainAxis]] - - getPaddingAndBorderAxis(node, mainAxis); + getPaddingAndBorderAxis(node, mainAxis); } - // The remaining available space that needs to be allocated - float remainingMainDim = definedMainDim - mainContentDim; - // If there are flexible children in the mix, they are going to fill the - // remaining space - if (flexibleChildrenCount != 0) { - float flexibleMainDim = remainingMainDim / totalFlexible; + // We want to execute the next two loops one per line with flex-wrap + int startLine = 0; + int endLine = 0; + int nextLine = 0; + // We aggregate the total dimensions of the container in those two variables + float linesCrossDim = 0; + float linesMainDim = 0; + while (endLine != node->children_count) { + // Layout non flexible children and count children by type - // The non flexible children can overflow the container, in this case - // we should just assume that there is no space available. - if (flexibleMainDim < 0) { - flexibleMainDim = 0; - } - // We iterate over the full array and only apply the action on flexible - // children. This is faster than actually allocating a new array that - // contains only flexible children. - for (int i = 0; i < node->children_count; ++i) { + // mainContentDim is accumulation of the dimensions and margin of all the + // non flexible children. This will be used in order to either set the + // dimensions of the node if none already exist, or to compute the + // remaining space left for the flexible children. + float mainContentDim = 0; + + // There are three kind of children, non flexible, flexible and absolute. + // We need to know how many there are in order to distribute the space. + int flexibleChildrenCount = 0; + float totalFlexible = 0; + int nonFlexibleChildrenCount = 0; + for (int i = startLine; i < node->children_count; ++i) { css_node_t* child = node->get_child(node->context, i); - if (isFlex(child)) { - // At this point we know the final size of the element in the main - // dimension - child->layout.dimensions[dim[mainAxis]] = flexibleMainDim * getFlex(child) + - getPaddingAndBorderAxis(child, mainAxis); + float nextContentDim = 0; + // It only makes sense to consider a child flexible if we have a computed + // dimension for the node-> + if (!isUndefined(node->layout.dimensions[dim[mainAxis]]) && isFlex(child)) { + flexibleChildrenCount++; + totalFlexible += getFlex(child); + + // Even if we don't know its exact size yet, we already know the padding, + // border and margin. We'll use this partial information to compute the + // remaining space. + nextContentDim = getPaddingAndBorderAxis(child, mainAxis) + + getMarginAxis(child, mainAxis); + + } else { float maxWidth = CSS_UNDEFINED; if (mainAxis == CSS_FLEX_DIRECTION_ROW) { // do nothing @@ -528,74 +485,234 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW); } - // And we recursively call the layout algorithm for this child - layoutNode(child, maxWidth); + // This is the main recursive call. We layout non flexible children. + if (nextLine == 0) { + layoutNode(child, maxWidth); + } + + // Absolute positioned elements do not take part of the layout, so we + // don't use them to compute mainContentDim + if (getPositionType(child) == CSS_POSITION_RELATIVE) { + nonFlexibleChildrenCount++; + // At this point we know the final size and margin of the element. + nextContentDim = getDimWithMargin(child, mainAxis); + } } + + // The element we are about to add would make us go to the next line + if (isFlexWrap(node) && + !isUndefined(node->layout.dimensions[dim[mainAxis]]) && + mainContentDim + nextContentDim > definedMainDim) { + nextLine = i + 1; + break; + } + nextLine = 0; + mainContentDim += nextContentDim; + endLine = i + 1; } - // We use justifyContent to figure out how to allocate the remaining - // space available - } else { - css_justify_t justifyContent = getJustifyContent(node); - if (justifyContent == CSS_JUSTIFY_FLEX_START) { - // Do nothing - } else if (justifyContent == CSS_JUSTIFY_CENTER) { - leadingMainDim = remainingMainDim / 2; - } else if (justifyContent == CSS_JUSTIFY_FLEX_END) { - leadingMainDim = remainingMainDim; - } else if (justifyContent == CSS_JUSTIFY_SPACE_BETWEEN) { - remainingMainDim = fmaxf(remainingMainDim, 0); - if (flexibleChildrenCount + nonFlexibleChildrenCount - 1 != 0) { - betweenMainDim = remainingMainDim / - (flexibleChildrenCount + nonFlexibleChildrenCount - 1); - } else { - betweenMainDim = 0; - } - } else if (justifyContent == CSS_JUSTIFY_SPACE_AROUND) { - // Space on the edges is half of the space between elements - betweenMainDim = remainingMainDim / - (flexibleChildrenCount + nonFlexibleChildrenCount); - leadingMainDim = betweenMainDim / 2; - } - } + // Layout flexible children and allocate empty space - // Position elements in the main axis and compute dimensions + // In order to position the elements in the main axis, we have two + // controls. The space between the beginning and the first element + // and the space between each two elements. + float leadingMainDim = 0; + float betweenMainDim = 0; - // At this point, all the children have their dimensions set. We need to - // find their position. In order to do that, we accumulate data in - // variables that are also useful to compute the total dimensions of the - // container! - float crossDim = 0; - float mainDim = leadingMainDim + - getPaddingAndBorder(node, leading[mainAxis]); - for (int i = 0; i < node->children_count; ++i) { - css_node_t* child = node->get_child(node->context, i); - - if (getPositionType(child) == CSS_POSITION_ABSOLUTE && - isPosDefined(child, leading[mainAxis])) { - // In case the child is position absolute and has left/top being - // defined, we override the position to whatever the user said - // (and margin/border). - child->layout.position[pos[mainAxis]] = getPosition(child, leading[mainAxis]) + - getBorder(node, leading[mainAxis]) + - getMargin(child, leading[mainAxis]); + // The remaining available space that needs to be allocated + float remainingMainDim = 0; + if (!isUndefined(node->layout.dimensions[dim[mainAxis]])) { + remainingMainDim = definedMainDim - mainContentDim; } else { - // If the child is position absolute (without top/left) or relative, - // we put it at the current accumulated offset. - child->layout.position[pos[mainAxis]] += mainDim; + remainingMainDim = fmaxf(mainContentDim, 0) - mainContentDim; } - // Now that we placed the element, we need to update the variables - // We only need to do that for relative elements. Absolute elements - // do not take part in that phase. - if (getPositionType(child) == CSS_POSITION_RELATIVE) { - // The main dimension is the sum of all the elements dimension plus - // the spacing. - mainDim += betweenMainDim + getDimWithMargin(child, mainAxis); - // The cross dimension is the max of the elements dimension since there - // can only be one element in that cross dimension. - crossDim = fmaxf(crossDim, getDimWithMargin(child, crossAxis)); + // If there are flexible children in the mix, they are going to fill the + // remaining space + if (flexibleChildrenCount != 0) { + float flexibleMainDim = remainingMainDim / totalFlexible; + + // The non flexible children can overflow the container, in this case + // we should just assume that there is no space available. + if (flexibleMainDim < 0) { + flexibleMainDim = 0; + } + // We iterate over the full array and only apply the action on flexible + // children. This is faster than actually allocating a new array that + // contains only flexible children. + for (int i = startLine; i < endLine; ++i) { + css_node_t* child = node->get_child(node->context, i); + if (isFlex(child)) { + // At this point we know the final size of the element in the main + // dimension + child->layout.dimensions[dim[mainAxis]] = flexibleMainDim * getFlex(child) + + getPaddingAndBorderAxis(child, mainAxis); + + float maxWidth = CSS_UNDEFINED; + if (mainAxis == CSS_FLEX_DIRECTION_ROW) { + // do nothing + } else if (isDimDefined(node, CSS_FLEX_DIRECTION_ROW)) { + maxWidth = node->layout.dimensions[dim[CSS_FLEX_DIRECTION_ROW]] - + getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW); + } else { + maxWidth = parentMaxWidth - + getMarginAxis(node, CSS_FLEX_DIRECTION_ROW) - + getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW); + } + + // And we recursively call the layout algorithm for this child + layoutNode(child, maxWidth); + } + } + + // We use justifyContent to figure out how to allocate the remaining + // space available + } else { + css_justify_t justifyContent = getJustifyContent(node); + if (justifyContent == CSS_JUSTIFY_FLEX_START) { + // Do nothing + } else if (justifyContent == CSS_JUSTIFY_CENTER) { + leadingMainDim = remainingMainDim / 2; + } else if (justifyContent == CSS_JUSTIFY_FLEX_END) { + leadingMainDim = remainingMainDim; + } else if (justifyContent == CSS_JUSTIFY_SPACE_BETWEEN) { + remainingMainDim = fmaxf(remainingMainDim, 0); + if (flexibleChildrenCount + nonFlexibleChildrenCount - 1 != 0) { + betweenMainDim = remainingMainDim / + (flexibleChildrenCount + nonFlexibleChildrenCount - 1); + } else { + betweenMainDim = 0; + } + } else if (justifyContent == CSS_JUSTIFY_SPACE_AROUND) { + // Space on the edges is half of the space between elements + betweenMainDim = remainingMainDim / + (flexibleChildrenCount + nonFlexibleChildrenCount); + leadingMainDim = betweenMainDim / 2; + } } + + // Position elements in the main axis and compute dimensions + + // At this point, all the children have their dimensions set. We need to + // find their position. In order to do that, we accumulate data in + // variables that are also useful to compute the total dimensions of the + // container! + float crossDim = 0; + float mainDim = leadingMainDim + + getPaddingAndBorder(node, leading[mainAxis]); + + for (int i = startLine; i < endLine; ++i) { + css_node_t* child = node->get_child(node->context, i); + + if (getPositionType(child) == CSS_POSITION_ABSOLUTE && + isPosDefined(child, leading[mainAxis])) { + // In case the child is position absolute and has left/top being + // defined, we override the position to whatever the user said + // (and margin/border). + child->layout.position[pos[mainAxis]] = getPosition(child, leading[mainAxis]) + + getBorder(node, leading[mainAxis]) + + getMargin(child, leading[mainAxis]); + } else { + // If the child is position absolute (without top/left) or relative, + // we put it at the current accumulated offset. + child->layout.position[pos[mainAxis]] += mainDim; + } + + // Now that we placed the element, we need to update the variables + // We only need to do that for relative elements. Absolute elements + // do not take part in that phase. + if (getPositionType(child) == CSS_POSITION_RELATIVE) { + // The main dimension is the sum of all the elements dimension plus + // the spacing. + mainDim += betweenMainDim + getDimWithMargin(child, mainAxis); + // The cross dimension is the max of the elements dimension since there + // can only be one element in that cross dimension. + crossDim = fmaxf(crossDim, getDimWithMargin(child, crossAxis)); + } + } + + float containerMainAxis = node->layout.dimensions[dim[mainAxis]]; + // If the user didn't specify a width or height, and it has not been set + // by the container, then we set it via the children. + if (isUndefined(node->layout.dimensions[dim[mainAxis]])) { + containerMainAxis = fmaxf( + // We're missing the last padding at this point to get the final + // dimension + mainDim + getPaddingAndBorder(node, trailing[mainAxis]), + // We can never assign a width smaller than the padding and borders + getPaddingAndBorderAxis(node, mainAxis) + ); + } + + float containerCrossAxis = node->layout.dimensions[dim[crossAxis]]; + if (isUndefined(node->layout.dimensions[dim[crossAxis]])) { + containerCrossAxis = fmaxf( + // For the cross dim, we add both sides at the end because the value + // is aggregate via a max function. Intermediate negative values + // can mess this computation otherwise + crossDim + getPaddingAndBorderAxis(node, crossAxis), + getPaddingAndBorderAxis(node, crossAxis) + ); + } + + // Position elements in the cross axis + + for (int i = startLine; i < endLine; ++i) { + css_node_t* child = node->get_child(node->context, i); + + if (getPositionType(child) == CSS_POSITION_ABSOLUTE && + isPosDefined(child, leading[crossAxis])) { + // In case the child is absolutely positionned and has a + // top/left/bottom/right being set, we override all the previously + // computed positions to set it correctly. + child->layout.position[pos[crossAxis]] = getPosition(child, leading[crossAxis]) + + getBorder(node, leading[crossAxis]) + + getMargin(child, leading[crossAxis]); + + } else { + float leadingCrossDim = getPaddingAndBorder(node, leading[crossAxis]); + + // For a relative children, we're either using alignItems (parent) or + // alignSelf (child) in order to determine the position in the cross axis + if (getPositionType(child) == CSS_POSITION_RELATIVE) { + css_align_t alignItem = getAlignItem(node, child); + if (alignItem == CSS_ALIGN_FLEX_START) { + // Do nothing + } else if (alignItem == CSS_ALIGN_STRETCH) { + // You can only stretch if the dimension has not already been set + // previously. + if (!isDimDefined(child, crossAxis)) { + child->layout.dimensions[dim[crossAxis]] = fmaxf( + containerCrossAxis - + getPaddingAndBorderAxis(node, crossAxis) - + getMarginAxis(child, crossAxis), + // You never want to go smaller than padding + getPaddingAndBorderAxis(child, crossAxis) + ); + } + } else { + // The remaining space between the parent dimensions+padding and child + // dimensions+margin. + float remainingCrossDim = containerCrossAxis - + getPaddingAndBorderAxis(node, crossAxis) - + getDimWithMargin(child, crossAxis); + + if (alignItem == CSS_ALIGN_CENTER) { + leadingCrossDim += remainingCrossDim / 2; + } else { // CSS_ALIGN_FLEX_END + leadingCrossDim += remainingCrossDim; + } + } + } + + // And we apply the position + child->layout.position[pos[crossAxis]] += linesCrossDim + leadingCrossDim; + } + } + + linesCrossDim += crossDim; + linesMainDim = fmaxf(linesMainDim, mainDim); + startLine = endLine; } // If the user didn't specify a width or height, and it has not been set @@ -604,7 +721,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { node->layout.dimensions[dim[mainAxis]] = fmaxf( // We're missing the last padding at this point to get the final // dimension - mainDim + getPaddingAndBorder(node, trailing[mainAxis]), + linesMainDim + getPaddingAndBorder(node, trailing[mainAxis]), // We can never assign a width smaller than the padding and borders getPaddingAndBorderAxis(node, mainAxis) ); @@ -615,67 +732,11 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { // For the cross dim, we add both sides at the end because the value // is aggregate via a max function. Intermediate negative values // can mess this computation otherwise - crossDim + getPaddingAndBorderAxis(node, crossAxis), + linesCrossDim + getPaddingAndBorderAxis(node, crossAxis), getPaddingAndBorderAxis(node, crossAxis) ); } - - // Position elements in the cross axis - - for (int i = 0; i < node->children_count; ++i) { - css_node_t* child = node->get_child(node->context, i); - - if (getPositionType(child) == CSS_POSITION_ABSOLUTE && - isPosDefined(child, leading[crossAxis])) { - // In case the child is absolutely positionned and has a - // top/left/bottom/right being set, we override all the previously - // computed positions to set it correctly. - child->layout.position[pos[crossAxis]] = getPosition(child, leading[crossAxis]) + - getBorder(node, leading[crossAxis]) + - getMargin(child, leading[crossAxis]); - - } else { - float leadingCrossDim = getPaddingAndBorder(node, leading[crossAxis]); - - // For a relative children, we're either using alignItems (parent) or - // alignSelf (child) in order to determine the position in the cross axis - if (getPositionType(child) == CSS_POSITION_RELATIVE) { - css_align_t alignItem = getAlignItem(node, child); - if (alignItem == CSS_ALIGN_FLEX_START) { - // Do nothing - } else if (alignItem == CSS_ALIGN_STRETCH) { - // You can only stretch if the dimension has not already been set - // previously. - if (!isDimDefined(child, crossAxis)) { - child->layout.dimensions[dim[crossAxis]] = fmaxf( - node->layout.dimensions[dim[crossAxis]] - - getPaddingAndBorderAxis(node, crossAxis) - - getMarginAxis(child, crossAxis), - // You never want to go smaller than padding - getPaddingAndBorderAxis(child, crossAxis) - ); - } - } else { - // The remaining space between the parent dimensions+padding and child - // dimensions+margin. - float remainingCrossDim = node->layout.dimensions[dim[crossAxis]] - - getPaddingAndBorderAxis(node, crossAxis) - - getDimWithMargin(child, crossAxis); - - if (alignItem == CSS_ALIGN_CENTER) { - leadingCrossDim += remainingCrossDim / 2; - } else { // CSS_ALIGN_FLEX_END - leadingCrossDim += remainingCrossDim; - } - } - } - - // And we apply the position - child->layout.position[pos[crossAxis]] += leadingCrossDim; - } - } - // Calculate dimensions for absolutely positioned elements for (int i = 0; i < node->children_count; ++i) { diff --git a/src/Layout.h b/src/Layout.h index acb73b9f..d5a815ce 100644 --- a/src/Layout.h +++ b/src/Layout.h @@ -42,6 +42,11 @@ typedef enum { CSS_POSITION_ABSOLUTE } css_position_type_t; +typedef enum { + CSS_NOWRAP = 0, + CSS_WRAP +} css_wrap_type_t; + // Note: left and top are shared between position[2] and position[4], so // they have to be before right and bottom. typedef enum { @@ -80,6 +85,7 @@ typedef struct { css_align_t align_items; css_align_t align_self; css_position_type_t position_type; + css_wrap_type_t flex_wrap; float flex; float margin[4]; float position[4]; diff --git a/src/Layout.js b/src/Layout.js index 4c7dec56..6e3e7d11 100755 --- a/src/Layout.js +++ b/src/Layout.js @@ -111,6 +111,10 @@ var computeLayout = (function() { ); } + function isFlexWrap(node) { + return node.style.flexWrap === 'wrap'; + } + function getDimWithMargin(node, axis) { return node.layout[dim[axis]] + getMarginAxis(node, axis); } @@ -298,97 +302,50 @@ var computeLayout = (function() { } } - // Layout non flexible children and count children by type - - // mainContentDim is accumulation of the dimensions and margin of all the - // non flexible children. This will be used in order to either set the - // dimensions of the node if none already exist, or to compute the - // remaining space left for the flexible children. - var/*float*/ mainContentDim = 0; - - // There are three kind of children, non flexible, flexible and absolute. - // We need to know how many there are in order to distribute the space. - var/*int*/ flexibleChildrenCount = 0; - var/*float*/ totalFlexible = 0; - var/*int*/ nonFlexibleChildrenCount = 0; - for (var/*int*/ i = 0; i < node.children.length; ++i) { - var/*css_node_t**/ child = node.children[i]; - - // It only makes sense to consider a child flexible if we have a computed - // dimension for the node. - if (!isUndefined(node.layout[dim[mainAxis]]) && isFlex(child)) { - flexibleChildrenCount++; - totalFlexible += getFlex(child); - - // Even if we don't know its exact size yet, we already know the padding, - // border and margin. We'll use this partial information to compute the - // remaining space. - mainContentDim += getPaddingAndBorderAxis(child, mainAxis) + - getMarginAxis(child, mainAxis); - - } else { - var/*float*/ maxWidth = CSS_UNDEFINED; - if (mainAxis === CSS_FLEX_DIRECTION_ROW) { - // do nothing - } else if (isDimDefined(node, CSS_FLEX_DIRECTION_ROW)) { - maxWidth = node.layout[dim[CSS_FLEX_DIRECTION_ROW]] - - getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW); - } else { - maxWidth = parentMaxWidth - - getMarginAxis(node, CSS_FLEX_DIRECTION_ROW) - - getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW); - } - - // This is the main recursive call. We layout non flexible children. - layoutNode(child, maxWidth); - - // Absolute positioned elements do not take part of the layout, so we - // don't use them to compute mainContentDim - if (getPositionType(child) === CSS_POSITION_RELATIVE) { - nonFlexibleChildrenCount++; - // At this point we know the final size and margin of the element. - mainContentDim += getDimWithMargin(child, mainAxis); - } - } - } - - // Layout flexible children and allocate empty space - - // In order to position the elements in the main axis, we have two - // controls. The space between the beginning and the first element - // and the space between each two elements. - var/*float*/ leadingMainDim = 0; - var/*float*/ betweenMainDim = 0; - - var/*float*/ definedMainDim = fmaxf(mainContentDim, 0); + var/*float*/ definedMainDim = CSS_UNDEFINED; if (!isUndefined(node.layout[dim[mainAxis]])) { definedMainDim = node.layout[dim[mainAxis]] - - getPaddingAndBorderAxis(node, mainAxis); + getPaddingAndBorderAxis(node, mainAxis); } - // The remaining available space that needs to be allocated - var/*float*/ remainingMainDim = definedMainDim - mainContentDim; - // If there are flexible children in the mix, they are going to fill the - // remaining space - if (flexibleChildrenCount !== 0) { - var/*float*/ flexibleMainDim = remainingMainDim / totalFlexible; + // We want to execute the next two loops one per line with flex-wrap + var/*int*/ startLine = 0; + var/*int*/ endLine = 0; + var/*int*/ nextLine = 0; + // We aggregate the total dimensions of the container in those two variables + var/*float*/ linesCrossDim = 0; + var/*float*/ linesMainDim = 0; + while (endLine !== node.children.length) { + // Layout non flexible children and count children by type - // The non flexible children can overflow the container, in this case - // we should just assume that there is no space available. - if (flexibleMainDim < 0) { - flexibleMainDim = 0; - } - // We iterate over the full array and only apply the action on flexible - // children. This is faster than actually allocating a new array that - // contains only flexible children. - for (var/*int*/ i = 0; i < node.children.length; ++i) { + // mainContentDim is accumulation of the dimensions and margin of all the + // non flexible children. This will be used in order to either set the + // dimensions of the node if none already exist, or to compute the + // remaining space left for the flexible children. + var/*float*/ mainContentDim = 0; + + // There are three kind of children, non flexible, flexible and absolute. + // We need to know how many there are in order to distribute the space. + var/*int*/ flexibleChildrenCount = 0; + var/*float*/ totalFlexible = 0; + var/*int*/ nonFlexibleChildrenCount = 0; + for (var/*int*/ i = startLine; i < node.children.length; ++i) { var/*css_node_t**/ child = node.children[i]; - if (isFlex(child)) { - // At this point we know the final size of the element in the main - // dimension - child.layout[dim[mainAxis]] = flexibleMainDim * getFlex(child) + - getPaddingAndBorderAxis(child, mainAxis); + var/*float*/ nextContentDim = 0; + // It only makes sense to consider a child flexible if we have a computed + // dimension for the node. + if (!isUndefined(node.layout[dim[mainAxis]]) && isFlex(child)) { + flexibleChildrenCount++; + totalFlexible += getFlex(child); + + // Even if we don't know its exact size yet, we already know the padding, + // border and margin. We'll use this partial information to compute the + // remaining space. + nextContentDim = getPaddingAndBorderAxis(child, mainAxis) + + getMarginAxis(child, mainAxis); + + } else { var/*float*/ maxWidth = CSS_UNDEFINED; if (mainAxis === CSS_FLEX_DIRECTION_ROW) { // do nothing @@ -401,74 +358,234 @@ var computeLayout = (function() { getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW); } - // And we recursively call the layout algorithm for this child - layoutNode(child, maxWidth); + // This is the main recursive call. We layout non flexible children. + if (nextLine === 0) { + layoutNode(child, maxWidth); + } + + // Absolute positioned elements do not take part of the layout, so we + // don't use them to compute mainContentDim + if (getPositionType(child) === CSS_POSITION_RELATIVE) { + nonFlexibleChildrenCount++; + // At this point we know the final size and margin of the element. + nextContentDim = getDimWithMargin(child, mainAxis); + } } + + // The element we are about to add would make us go to the next line + if (isFlexWrap(node) && + !isUndefined(node.layout[dim[mainAxis]]) && + mainContentDim + nextContentDim > definedMainDim) { + nextLine = i + 1; + break; + } + nextLine = 0; + mainContentDim += nextContentDim; + endLine = i + 1; } - // We use justifyContent to figure out how to allocate the remaining - // space available - } else { - var/*css_justify_t*/ justifyContent = getJustifyContent(node); - if (justifyContent === CSS_JUSTIFY_FLEX_START) { - // Do nothing - } else if (justifyContent === CSS_JUSTIFY_CENTER) { - leadingMainDim = remainingMainDim / 2; - } else if (justifyContent === CSS_JUSTIFY_FLEX_END) { - leadingMainDim = remainingMainDim; - } else if (justifyContent === CSS_JUSTIFY_SPACE_BETWEEN) { - remainingMainDim = fmaxf(remainingMainDim, 0); - if (flexibleChildrenCount + nonFlexibleChildrenCount - 1 !== 0) { - betweenMainDim = remainingMainDim / - (flexibleChildrenCount + nonFlexibleChildrenCount - 1); - } else { - betweenMainDim = 0; - } - } else if (justifyContent === CSS_JUSTIFY_SPACE_AROUND) { - // Space on the edges is half of the space between elements - betweenMainDim = remainingMainDim / - (flexibleChildrenCount + nonFlexibleChildrenCount); - leadingMainDim = betweenMainDim / 2; - } - } + // Layout flexible children and allocate empty space - // Position elements in the main axis and compute dimensions + // In order to position the elements in the main axis, we have two + // controls. The space between the beginning and the first element + // and the space between each two elements. + var/*float*/ leadingMainDim = 0; + var/*float*/ betweenMainDim = 0; - // At this point, all the children have their dimensions set. We need to - // find their position. In order to do that, we accumulate data in - // variables that are also useful to compute the total dimensions of the - // container! - var/*float*/ crossDim = 0; - var/*float*/ mainDim = leadingMainDim + - getPaddingAndBorder(node, leading[mainAxis]); - for (var/*int*/ i = 0; i < node.children.length; ++i) { - var/*css_node_t**/ child = node.children[i]; - - if (getPositionType(child) === CSS_POSITION_ABSOLUTE && - isPosDefined(child, leading[mainAxis])) { - // In case the child is position absolute and has left/top being - // defined, we override the position to whatever the user said - // (and margin/border). - child.layout[pos[mainAxis]] = getPosition(child, leading[mainAxis]) + - getBorder(node, leading[mainAxis]) + - getMargin(child, leading[mainAxis]); + // The remaining available space that needs to be allocated + var/*float*/ remainingMainDim = 0; + if (!isUndefined(node.layout[dim[mainAxis]])) { + remainingMainDim = definedMainDim - mainContentDim; } else { - // If the child is position absolute (without top/left) or relative, - // we put it at the current accumulated offset. - child.layout[pos[mainAxis]] += mainDim; + remainingMainDim = fmaxf(mainContentDim, 0) - mainContentDim; } - // Now that we placed the element, we need to update the variables - // We only need to do that for relative elements. Absolute elements - // do not take part in that phase. - if (getPositionType(child) === CSS_POSITION_RELATIVE) { - // The main dimension is the sum of all the elements dimension plus - // the spacing. - mainDim += betweenMainDim + getDimWithMargin(child, mainAxis); - // The cross dimension is the max of the elements dimension since there - // can only be one element in that cross dimension. - crossDim = fmaxf(crossDim, getDimWithMargin(child, crossAxis)); + // If there are flexible children in the mix, they are going to fill the + // remaining space + if (flexibleChildrenCount !== 0) { + var/*float*/ flexibleMainDim = remainingMainDim / totalFlexible; + + // The non flexible children can overflow the container, in this case + // we should just assume that there is no space available. + if (flexibleMainDim < 0) { + flexibleMainDim = 0; + } + // We iterate over the full array and only apply the action on flexible + // children. This is faster than actually allocating a new array that + // contains only flexible children. + for (var/*int*/ i = startLine; i < endLine; ++i) { + var/*css_node_t**/ child = node.children[i]; + if (isFlex(child)) { + // At this point we know the final size of the element in the main + // dimension + child.layout[dim[mainAxis]] = flexibleMainDim * getFlex(child) + + getPaddingAndBorderAxis(child, mainAxis); + + var/*float*/ maxWidth = CSS_UNDEFINED; + if (mainAxis === CSS_FLEX_DIRECTION_ROW) { + // do nothing + } else if (isDimDefined(node, CSS_FLEX_DIRECTION_ROW)) { + maxWidth = node.layout[dim[CSS_FLEX_DIRECTION_ROW]] - + getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW); + } else { + maxWidth = parentMaxWidth - + getMarginAxis(node, CSS_FLEX_DIRECTION_ROW) - + getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW); + } + + // And we recursively call the layout algorithm for this child + layoutNode(child, maxWidth); + } + } + + // We use justifyContent to figure out how to allocate the remaining + // space available + } else { + var/*css_justify_t*/ justifyContent = getJustifyContent(node); + if (justifyContent === CSS_JUSTIFY_FLEX_START) { + // Do nothing + } else if (justifyContent === CSS_JUSTIFY_CENTER) { + leadingMainDim = remainingMainDim / 2; + } else if (justifyContent === CSS_JUSTIFY_FLEX_END) { + leadingMainDim = remainingMainDim; + } else if (justifyContent === CSS_JUSTIFY_SPACE_BETWEEN) { + remainingMainDim = fmaxf(remainingMainDim, 0); + if (flexibleChildrenCount + nonFlexibleChildrenCount - 1 !== 0) { + betweenMainDim = remainingMainDim / + (flexibleChildrenCount + nonFlexibleChildrenCount - 1); + } else { + betweenMainDim = 0; + } + } else if (justifyContent === CSS_JUSTIFY_SPACE_AROUND) { + // Space on the edges is half of the space between elements + betweenMainDim = remainingMainDim / + (flexibleChildrenCount + nonFlexibleChildrenCount); + leadingMainDim = betweenMainDim / 2; + } } + + // Position elements in the main axis and compute dimensions + + // At this point, all the children have their dimensions set. We need to + // find their position. In order to do that, we accumulate data in + // variables that are also useful to compute the total dimensions of the + // container! + var/*float*/ crossDim = 0; + var/*float*/ mainDim = leadingMainDim + + getPaddingAndBorder(node, leading[mainAxis]); + + for (var/*int*/ i = startLine; i < endLine; ++i) { + var/*css_node_t**/ child = node.children[i]; + + if (getPositionType(child) === CSS_POSITION_ABSOLUTE && + isPosDefined(child, leading[mainAxis])) { + // In case the child is position absolute and has left/top being + // defined, we override the position to whatever the user said + // (and margin/border). + child.layout[pos[mainAxis]] = getPosition(child, leading[mainAxis]) + + getBorder(node, leading[mainAxis]) + + getMargin(child, leading[mainAxis]); + } else { + // If the child is position absolute (without top/left) or relative, + // we put it at the current accumulated offset. + child.layout[pos[mainAxis]] += mainDim; + } + + // Now that we placed the element, we need to update the variables + // We only need to do that for relative elements. Absolute elements + // do not take part in that phase. + if (getPositionType(child) === CSS_POSITION_RELATIVE) { + // The main dimension is the sum of all the elements dimension plus + // the spacing. + mainDim += betweenMainDim + getDimWithMargin(child, mainAxis); + // The cross dimension is the max of the elements dimension since there + // can only be one element in that cross dimension. + crossDim = fmaxf(crossDim, getDimWithMargin(child, crossAxis)); + } + } + + var/*float*/ containerMainAxis = node.layout[dim[mainAxis]]; + // If the user didn't specify a width or height, and it has not been set + // by the container, then we set it via the children. + if (isUndefined(node.layout[dim[mainAxis]])) { + containerMainAxis = fmaxf( + // We're missing the last padding at this point to get the final + // dimension + mainDim + getPaddingAndBorder(node, trailing[mainAxis]), + // We can never assign a width smaller than the padding and borders + getPaddingAndBorderAxis(node, mainAxis) + ); + } + + var/*float*/ containerCrossAxis = node.layout[dim[crossAxis]]; + if (isUndefined(node.layout[dim[crossAxis]])) { + containerCrossAxis = fmaxf( + // For the cross dim, we add both sides at the end because the value + // is aggregate via a max function. Intermediate negative values + // can mess this computation otherwise + crossDim + getPaddingAndBorderAxis(node, crossAxis), + getPaddingAndBorderAxis(node, crossAxis) + ); + } + + // Position elements in the cross axis + + for (var/*int*/ i = startLine; i < endLine; ++i) { + var/*css_node_t**/ child = node.children[i]; + + if (getPositionType(child) === CSS_POSITION_ABSOLUTE && + isPosDefined(child, leading[crossAxis])) { + // In case the child is absolutely positionned and has a + // top/left/bottom/right being set, we override all the previously + // computed positions to set it correctly. + child.layout[pos[crossAxis]] = getPosition(child, leading[crossAxis]) + + getBorder(node, leading[crossAxis]) + + getMargin(child, leading[crossAxis]); + + } else { + var/*float*/ leadingCrossDim = getPaddingAndBorder(node, leading[crossAxis]); + + // For a relative children, we're either using alignItems (parent) or + // alignSelf (child) in order to determine the position in the cross axis + if (getPositionType(child) === CSS_POSITION_RELATIVE) { + var/*css_align_t*/ alignItem = getAlignItem(node, child); + if (alignItem === CSS_ALIGN_FLEX_START) { + // Do nothing + } else if (alignItem === CSS_ALIGN_STRETCH) { + // You can only stretch if the dimension has not already been set + // previously. + if (!isDimDefined(child, crossAxis)) { + child.layout[dim[crossAxis]] = fmaxf( + containerCrossAxis - + getPaddingAndBorderAxis(node, crossAxis) - + getMarginAxis(child, crossAxis), + // You never want to go smaller than padding + getPaddingAndBorderAxis(child, crossAxis) + ); + } + } else { + // The remaining space between the parent dimensions+padding and child + // dimensions+margin. + var/*float*/ remainingCrossDim = containerCrossAxis - + getPaddingAndBorderAxis(node, crossAxis) - + getDimWithMargin(child, crossAxis); + + if (alignItem === CSS_ALIGN_CENTER) { + leadingCrossDim += remainingCrossDim / 2; + } else { // CSS_ALIGN_FLEX_END + leadingCrossDim += remainingCrossDim; + } + } + } + + // And we apply the position + child.layout[pos[crossAxis]] += linesCrossDim + leadingCrossDim; + } + } + + linesCrossDim += crossDim; + linesMainDim = fmaxf(linesMainDim, mainDim); + startLine = endLine; } // If the user didn't specify a width or height, and it has not been set @@ -477,7 +594,7 @@ var computeLayout = (function() { node.layout[dim[mainAxis]] = fmaxf( // We're missing the last padding at this point to get the final // dimension - mainDim + getPaddingAndBorder(node, trailing[mainAxis]), + linesMainDim + getPaddingAndBorder(node, trailing[mainAxis]), // We can never assign a width smaller than the padding and borders getPaddingAndBorderAxis(node, mainAxis) ); @@ -488,67 +605,11 @@ var computeLayout = (function() { // For the cross dim, we add both sides at the end because the value // is aggregate via a max function. Intermediate negative values // can mess this computation otherwise - crossDim + getPaddingAndBorderAxis(node, crossAxis), + linesCrossDim + getPaddingAndBorderAxis(node, crossAxis), getPaddingAndBorderAxis(node, crossAxis) ); } - - // Position elements in the cross axis - - for (var/*int*/ i = 0; i < node.children.length; ++i) { - var/*css_node_t**/ child = node.children[i]; - - if (getPositionType(child) === CSS_POSITION_ABSOLUTE && - isPosDefined(child, leading[crossAxis])) { - // In case the child is absolutely positionned and has a - // top/left/bottom/right being set, we override all the previously - // computed positions to set it correctly. - child.layout[pos[crossAxis]] = getPosition(child, leading[crossAxis]) + - getBorder(node, leading[crossAxis]) + - getMargin(child, leading[crossAxis]); - - } else { - var/*float*/ leadingCrossDim = getPaddingAndBorder(node, leading[crossAxis]); - - // For a relative children, we're either using alignItems (parent) or - // alignSelf (child) in order to determine the position in the cross axis - if (getPositionType(child) === CSS_POSITION_RELATIVE) { - var/*css_align_t*/ alignItem = getAlignItem(node, child); - if (alignItem === CSS_ALIGN_FLEX_START) { - // Do nothing - } else if (alignItem === CSS_ALIGN_STRETCH) { - // You can only stretch if the dimension has not already been set - // previously. - if (!isDimDefined(child, crossAxis)) { - child.layout[dim[crossAxis]] = fmaxf( - node.layout[dim[crossAxis]] - - getPaddingAndBorderAxis(node, crossAxis) - - getMarginAxis(child, crossAxis), - // You never want to go smaller than padding - getPaddingAndBorderAxis(child, crossAxis) - ); - } - } else { - // The remaining space between the parent dimensions+padding and child - // dimensions+margin. - var/*float*/ remainingCrossDim = node.layout[dim[crossAxis]] - - getPaddingAndBorderAxis(node, crossAxis) - - getDimWithMargin(child, crossAxis); - - if (alignItem === CSS_ALIGN_CENTER) { - leadingCrossDim += remainingCrossDim / 2; - } else { // CSS_ALIGN_FLEX_END - leadingCrossDim += remainingCrossDim; - } - } - } - - // And we apply the position - child.layout[pos[crossAxis]] += leadingCrossDim; - } - } - // Calculate dimensions for absolutely positioned elements for (var/*int*/ i = 0; i < node.children.length; ++i) { @@ -557,7 +618,7 @@ var computeLayout = (function() { // Pre-fill dimensions when using absolute position and both offsets for the axis are defined (either both // left and right or top and bottom). for (var/*int*/ ii = 0; ii < 2; ii++) { - var/*css_flex_direction_t*/ axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; + var/*css_flex_direction_t*/ axis = (ii !== 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; if (!isUndefined(node.layout[dim[axis]]) && !isDimDefined(child, axis) && isPosDefined(child, leading[axis]) && @@ -574,7 +635,7 @@ var computeLayout = (function() { } } for (var/*int*/ ii = 0; ii < 2; ii++) { - var/*css_flex_direction_t*/ axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; + var/*css_flex_direction_t*/ axis = (ii !== 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; if (isPosDefined(child, trailing[axis]) && !isPosDefined(child, leading[axis])) { child.layout[leading[axis]] = diff --git a/src/__tests__/Layout-test.c b/src/__tests__/Layout-test.c index 38851473..3adc2ab4 100644 --- a/src/__tests__/Layout-test.c +++ b/src/__tests__/Layout-test.c @@ -3671,6 +3671,59 @@ int main() test("should layout with children of a contain with left", root_node, root_layout); } + + { + css_node_t *root_node = new_test_css_node(); + { + css_node_t *node_0 = root_node; + node_0->style.flex_direction = CSS_FLEX_DIRECTION_ROW; + node_0->style.flex_wrap = CSS_WRAP; + node_0->style.dimensions[CSS_WIDTH] = 100; + init_css_node_children(node_0, 3); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->style.dimensions[CSS_WIDTH] = 40; + node_1->style.dimensions[CSS_HEIGHT] = 10; + node_1 = node_0->get_child(node_0->context, 1); + node_1->style.dimensions[CSS_WIDTH] = 40; + node_1->style.dimensions[CSS_HEIGHT] = 10; + node_1 = node_0->get_child(node_0->context, 2); + node_1->style.dimensions[CSS_WIDTH] = 40; + node_1->style.dimensions[CSS_HEIGHT] = 10; + } + } + + css_node_t *root_layout = new_test_css_node(); + { + css_node_t *node_0 = root_layout; + node_0->layout.position[CSS_TOP] = 0; + node_0->layout.position[CSS_LEFT] = 0; + node_0->layout.dimensions[CSS_WIDTH] = 100; + node_0->layout.dimensions[CSS_HEIGHT] = 20; + init_css_node_children(node_0, 3); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 0; + node_1->layout.dimensions[CSS_WIDTH] = 40; + node_1->layout.dimensions[CSS_HEIGHT] = 10; + node_1 = node_0->get_child(node_0->context, 1); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 40; + node_1->layout.dimensions[CSS_WIDTH] = 40; + node_1->layout.dimensions[CSS_HEIGHT] = 10; + node_1 = node_0->get_child(node_0->context, 2); + node_1->layout.position[CSS_TOP] = 10; + node_1->layout.position[CSS_LEFT] = 0; + node_1->layout.dimensions[CSS_WIDTH] = 40; + node_1->layout.dimensions[CSS_HEIGHT] = 10; + } + } + + test("should layout flex-wrap", root_node, root_layout); + } /** END_GENERATED **/ return tests_finished(); } diff --git a/src/__tests__/Layout-test.js b/src/__tests__/Layout-test.js index 3f8c4533..b5509580 100755 --- a/src/__tests__/Layout-test.js +++ b/src/__tests__/Layout-test.js @@ -1144,12 +1144,11 @@ describe('Layout', function() { ); }); - xit('should layout flex-wrap', function() { + it('should layout flex-wrap', function() { testLayout( {style: {flexWrap: 'wrap', flexDirection: 'row', width: 100}, children: [ {style: {width: 40, height: 10}}, {style: {width: 40, height: 10}}, - {style: {flex: 1}}, {style: {width: 40, height: 10}}, ]}, {width: 100, height: 20, top: 0, left: 0, children: [ diff --git a/src/java/src/com/facebook/csslayout/CSSNode.java b/src/java/src/com/facebook/csslayout/CSSNode.java index f8b37a81..f431890d 100644 --- a/src/java/src/com/facebook/csslayout/CSSNode.java +++ b/src/java/src/com/facebook/csslayout/CSSNode.java @@ -287,6 +287,13 @@ public class CSSNode { } } + public void setWrap(CSSWrap flexWrap) { + if (!valuesEqual(style.flexWrap, flexWrap)) { + style.flexWrap = flexWrap; + dirty(); + } + } + public void setFlex(float flex) { if (!valuesEqual(style.flex, flex)) { style.flex = flex; diff --git a/src/java/src/com/facebook/csslayout/CSSStyle.java b/src/java/src/com/facebook/csslayout/CSSStyle.java index 62be1449..d43ea729 100644 --- a/src/java/src/com/facebook/csslayout/CSSStyle.java +++ b/src/java/src/com/facebook/csslayout/CSSStyle.java @@ -23,6 +23,7 @@ public class CSSStyle { public CSSAlign alignItems = CSSAlign.STRETCH; public CSSAlign alignSelf = CSSAlign.AUTO; public CSSPositionType positionType = CSSPositionType.RELATIVE; + public CSSWrap flexWrap = CSSWrap.NOWRAP; public float flex; public float[] margin = new float[4]; diff --git a/src/java/src/com/facebook/csslayout/CSSWrap.java b/src/java/src/com/facebook/csslayout/CSSWrap.java new file mode 100644 index 00000000..581481a4 --- /dev/null +++ b/src/java/src/com/facebook/csslayout/CSSWrap.java @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2014, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +package com.facebook.csslayout; + +public enum CSSWrap { + NOWRAP, + WRAP, +} diff --git a/src/java/src/com/facebook/csslayout/LayoutEngine.java b/src/java/src/com/facebook/csslayout/LayoutEngine.java index 746e4095..b291da55 100644 --- a/src/java/src/com/facebook/csslayout/LayoutEngine.java +++ b/src/java/src/com/facebook/csslayout/LayoutEngine.java @@ -235,6 +235,10 @@ public class LayoutEngine { return node.style.justifyContent; } + private static boolean isFlexWrap(CSSNode node) { + return node.style.flexWrap == CSSWrap.WRAP; + } + private static boolean isFlex(CSSNode node) { return getPositionType(node) == CSSPositionType.RELATIVE && getFlex(node) > 0; } @@ -371,97 +375,50 @@ public class LayoutEngine { } } - // Layout non flexible children and count children by type - - // mainContentDim is accumulation of the dimensions and margin of all the - // non flexible children. This will be used in order to either set the - // dimensions of the node if none already exist, or to compute the - // remaining space left for the flexible children. - float mainContentDim = 0; - - // There are three kind of children, non flexible, flexible and absolute. - // We need to know how many there are in order to distribute the space. - int flexibleChildrenCount = 0; - float totalFlexible = 0; - int nonFlexibleChildrenCount = 0; - for (int i = 0; i < node.getChildCount(); ++i) { - CSSNode child = node.getChildAt(i); - - // It only makes sense to consider a child flexible if we have a computed - // dimension for the node. - if (!CSSConstants.isUndefined(getLayoutDimension(node, getDim(mainAxis))) && isFlex(child)) { - flexibleChildrenCount++; - totalFlexible = totalFlexible + getFlex(child); - - // Even if we don't know its exact size yet, we already know the padding, - // border and margin. We'll use this partial information to compute the - // remaining space. - mainContentDim = mainContentDim + getPaddingAndBorderAxis(child, mainAxis) + - getMarginAxis(child, mainAxis); - - } else { - float maxWidth = CSSConstants.UNDEFINED; - if (mainAxis == CSSFlexDirection.ROW) { - // do nothing - } else if (isDimDefined(node, CSSFlexDirection.ROW)) { - maxWidth = getLayoutDimension(node, getDim(CSSFlexDirection.ROW)) - - getPaddingAndBorderAxis(node, CSSFlexDirection.ROW); - } else { - maxWidth = parentMaxWidth - - getMarginAxis(node, CSSFlexDirection.ROW) - - getPaddingAndBorderAxis(node, CSSFlexDirection.ROW); - } - - // This is the main recursive call. We layout non flexible children. - layoutNode(child, maxWidth); - - // Absolute positioned elements do not take part of the layout, so we - // don't use them to compute mainContentDim - if (getPositionType(child) == CSSPositionType.RELATIVE) { - nonFlexibleChildrenCount++; - // At this point we know the final size and margin of the element. - mainContentDim = mainContentDim + getDimWithMargin(child, mainAxis); - } - } - } - - // Layout flexible children and allocate empty space - - // In order to position the elements in the main axis, we have two - // controls. The space between the beginning and the first element - // and the space between each two elements. - float leadingMainDim = 0; - float betweenMainDim = 0; - - float definedMainDim = Math.max(mainContentDim, 0); + float definedMainDim = CSSConstants.UNDEFINED; if (!CSSConstants.isUndefined(getLayoutDimension(node, getDim(mainAxis)))) { definedMainDim = getLayoutDimension(node, getDim(mainAxis)) - - getPaddingAndBorderAxis(node, mainAxis); + getPaddingAndBorderAxis(node, mainAxis); } - // The remaining available space that needs to be allocated - float remainingMainDim = definedMainDim - mainContentDim; - // If there are flexible children in the mix, they are going to fill the - // remaining space - if (flexibleChildrenCount != 0) { - float flexibleMainDim = remainingMainDim / totalFlexible; + // We want to execute the next two loops one per line with flex-wrap + int startLine = 0; + int endLine = 0; + int nextLine = 0; + // We aggregate the total dimensions of the container in those two variables + float linesCrossDim = 0; + float linesMainDim = 0; + while (endLine != node.getChildCount()) { + // Layout non flexible children and count children by type - // The non flexible children can overflow the container, in this case - // we should just assume that there is no space available. - if (flexibleMainDim < 0) { - flexibleMainDim = 0; - } - // We iterate over the full array and only apply the action on flexible - // children. This is faster than actually allocating a new array that - // contains only flexible children. - for (int i = 0; i < node.getChildCount(); ++i) { + // mainContentDim is accumulation of the dimensions and margin of all the + // non flexible children. This will be used in order to either set the + // dimensions of the node if none already exist, or to compute the + // remaining space left for the flexible children. + float mainContentDim = 0; + + // There are three kind of children, non flexible, flexible and absolute. + // We need to know how many there are in order to distribute the space. + int flexibleChildrenCount = 0; + float totalFlexible = 0; + int nonFlexibleChildrenCount = 0; + for (int i = startLine; i < node.getChildCount(); ++i) { CSSNode child = node.getChildAt(i); - if (isFlex(child)) { - // At this point we know the final size of the element in the main - // dimension - setLayoutDimension(child, getDim(mainAxis), flexibleMainDim * getFlex(child) + - getPaddingAndBorderAxis(child, mainAxis)); + float nextContentDim = 0; + // It only makes sense to consider a child flexible if we have a computed + // dimension for the node. + if (!CSSConstants.isUndefined(getLayoutDimension(node, getDim(mainAxis))) && isFlex(child)) { + flexibleChildrenCount++; + totalFlexible = totalFlexible + getFlex(child); + + // Even if we don't know its exact size yet, we already know the padding, + // border and margin. We'll use this partial information to compute the + // remaining space. + nextContentDim = getPaddingAndBorderAxis(child, mainAxis) + + getMarginAxis(child, mainAxis); + + } else { float maxWidth = CSSConstants.UNDEFINED; if (mainAxis == CSSFlexDirection.ROW) { // do nothing @@ -474,74 +431,234 @@ public class LayoutEngine { getPaddingAndBorderAxis(node, CSSFlexDirection.ROW); } - // And we recursively call the layout algorithm for this child - layoutNode(child, maxWidth); + // This is the main recursive call. We layout non flexible children. + if (nextLine == 0) { + layoutNode(child, maxWidth); + } + + // Absolute positioned elements do not take part of the layout, so we + // don't use them to compute mainContentDim + if (getPositionType(child) == CSSPositionType.RELATIVE) { + nonFlexibleChildrenCount++; + // At this point we know the final size and margin of the element. + nextContentDim = getDimWithMargin(child, mainAxis); + } } + + // The element we are about to add would make us go to the next line + if (isFlexWrap(node) && + !CSSConstants.isUndefined(getLayoutDimension(node, getDim(mainAxis))) && + mainContentDim + nextContentDim > definedMainDim) { + nextLine = i + 1; + break; + } + nextLine = 0; + mainContentDim = mainContentDim + nextContentDim; + endLine = i + 1; } - // We use justifyContent to figure out how to allocate the remaining - // space available - } else { - CSSJustify justifyContent = getJustifyContent(node); - if (justifyContent == CSSJustify.FLEX_START) { - // Do nothing - } else if (justifyContent == CSSJustify.CENTER) { - leadingMainDim = remainingMainDim / 2; - } else if (justifyContent == CSSJustify.FLEX_END) { - leadingMainDim = remainingMainDim; - } else if (justifyContent == CSSJustify.SPACE_BETWEEN) { - remainingMainDim = Math.max(remainingMainDim, 0); - if (flexibleChildrenCount + nonFlexibleChildrenCount - 1 != 0) { - betweenMainDim = remainingMainDim / - (flexibleChildrenCount + nonFlexibleChildrenCount - 1); - } else { - betweenMainDim = 0; - } - } else if (justifyContent == CSSJustify.SPACE_AROUND) { - // Space on the edges is half of the space between elements - betweenMainDim = remainingMainDim / - (flexibleChildrenCount + nonFlexibleChildrenCount); - leadingMainDim = betweenMainDim / 2; - } - } + // Layout flexible children and allocate empty space - // Position elements in the main axis and compute dimensions + // In order to position the elements in the main axis, we have two + // controls. The space between the beginning and the first element + // and the space between each two elements. + float leadingMainDim = 0; + float betweenMainDim = 0; - // At this point, all the children have their dimensions set. We need to - // find their position. In order to do that, we accumulate data in - // variables that are also useful to compute the total dimensions of the - // container! - float crossDim = 0; - float mainDim = leadingMainDim + - getPaddingAndBorder(node, getLeading(mainAxis)); - for (int i = 0; i < node.getChildCount(); ++i) { - CSSNode child = node.getChildAt(i); - - if (getPositionType(child) == CSSPositionType.ABSOLUTE && - isPosDefined(child, getLeading(mainAxis))) { - // In case the child is position absolute and has left/top being - // defined, we override the position to whatever the user said - // (and margin/border). - setLayoutPosition(child, getPos(mainAxis), getPosition(child, getLeading(mainAxis)) + - getBorder(node, getLeading(mainAxis)) + - getMargin(child, getLeading(mainAxis))); + // The remaining available space that needs to be allocated + float remainingMainDim = 0; + if (!CSSConstants.isUndefined(getLayoutDimension(node, getDim(mainAxis)))) { + remainingMainDim = definedMainDim - mainContentDim; } else { - // If the child is position absolute (without top/left) or relative, - // we put it at the current accumulated offset. - setLayoutPosition(child, getPos(mainAxis), getLayoutPosition(child, getPos(mainAxis)) + mainDim); + remainingMainDim = Math.max(mainContentDim, 0) - mainContentDim; } - // Now that we placed the element, we need to update the variables - // We only need to do that for relative elements. Absolute elements - // do not take part in that phase. - if (getPositionType(child) == CSSPositionType.RELATIVE) { - // The main dimension is the sum of all the elements dimension plus - // the spacing. - mainDim = mainDim + betweenMainDim + getDimWithMargin(child, mainAxis); - // The cross dimension is the max of the elements dimension since there - // can only be one element in that cross dimension. - crossDim = Math.max(crossDim, getDimWithMargin(child, crossAxis)); + // If there are flexible children in the mix, they are going to fill the + // remaining space + if (flexibleChildrenCount != 0) { + float flexibleMainDim = remainingMainDim / totalFlexible; + + // The non flexible children can overflow the container, in this case + // we should just assume that there is no space available. + if (flexibleMainDim < 0) { + flexibleMainDim = 0; + } + // We iterate over the full array and only apply the action on flexible + // children. This is faster than actually allocating a new array that + // contains only flexible children. + for (int i = startLine; i < endLine; ++i) { + CSSNode child = node.getChildAt(i); + if (isFlex(child)) { + // At this point we know the final size of the element in the main + // dimension + setLayoutDimension(child, getDim(mainAxis), flexibleMainDim * getFlex(child) + + getPaddingAndBorderAxis(child, mainAxis)); + + float maxWidth = CSSConstants.UNDEFINED; + if (mainAxis == CSSFlexDirection.ROW) { + // do nothing + } else if (isDimDefined(node, CSSFlexDirection.ROW)) { + maxWidth = getLayoutDimension(node, getDim(CSSFlexDirection.ROW)) - + getPaddingAndBorderAxis(node, CSSFlexDirection.ROW); + } else { + maxWidth = parentMaxWidth - + getMarginAxis(node, CSSFlexDirection.ROW) - + getPaddingAndBorderAxis(node, CSSFlexDirection.ROW); + } + + // And we recursively call the layout algorithm for this child + layoutNode(child, maxWidth); + } + } + + // We use justifyContent to figure out how to allocate the remaining + // space available + } else { + CSSJustify justifyContent = getJustifyContent(node); + if (justifyContent == CSSJustify.FLEX_START) { + // Do nothing + } else if (justifyContent == CSSJustify.CENTER) { + leadingMainDim = remainingMainDim / 2; + } else if (justifyContent == CSSJustify.FLEX_END) { + leadingMainDim = remainingMainDim; + } else if (justifyContent == CSSJustify.SPACE_BETWEEN) { + remainingMainDim = Math.max(remainingMainDim, 0); + if (flexibleChildrenCount + nonFlexibleChildrenCount - 1 != 0) { + betweenMainDim = remainingMainDim / + (flexibleChildrenCount + nonFlexibleChildrenCount - 1); + } else { + betweenMainDim = 0; + } + } else if (justifyContent == CSSJustify.SPACE_AROUND) { + // Space on the edges is half of the space between elements + betweenMainDim = remainingMainDim / + (flexibleChildrenCount + nonFlexibleChildrenCount); + leadingMainDim = betweenMainDim / 2; + } } + + // Position elements in the main axis and compute dimensions + + // At this point, all the children have their dimensions set. We need to + // find their position. In order to do that, we accumulate data in + // variables that are also useful to compute the total dimensions of the + // container! + float crossDim = 0; + float mainDim = leadingMainDim + + getPaddingAndBorder(node, getLeading(mainAxis)); + + for (int i = startLine; i < endLine; ++i) { + CSSNode child = node.getChildAt(i); + + if (getPositionType(child) == CSSPositionType.ABSOLUTE && + isPosDefined(child, getLeading(mainAxis))) { + // In case the child is position absolute and has left/top being + // defined, we override the position to whatever the user said + // (and margin/border). + setLayoutPosition(child, getPos(mainAxis), getPosition(child, getLeading(mainAxis)) + + getBorder(node, getLeading(mainAxis)) + + getMargin(child, getLeading(mainAxis))); + } else { + // If the child is position absolute (without top/left) or relative, + // we put it at the current accumulated offset. + setLayoutPosition(child, getPos(mainAxis), getLayoutPosition(child, getPos(mainAxis)) + mainDim); + } + + // Now that we placed the element, we need to update the variables + // We only need to do that for relative elements. Absolute elements + // do not take part in that phase. + if (getPositionType(child) == CSSPositionType.RELATIVE) { + // The main dimension is the sum of all the elements dimension plus + // the spacing. + mainDim = mainDim + betweenMainDim + getDimWithMargin(child, mainAxis); + // The cross dimension is the max of the elements dimension since there + // can only be one element in that cross dimension. + crossDim = Math.max(crossDim, getDimWithMargin(child, crossAxis)); + } + } + + float containerMainAxis = getLayoutDimension(node, getDim(mainAxis)); + // If the user didn't specify a width or height, and it has not been set + // by the container, then we set it via the children. + if (CSSConstants.isUndefined(getLayoutDimension(node, getDim(mainAxis)))) { + containerMainAxis = Math.max( + // We're missing the last padding at this point to get the final + // dimension + mainDim + getPaddingAndBorder(node, getTrailing(mainAxis)), + // We can never assign a width smaller than the padding and borders + getPaddingAndBorderAxis(node, mainAxis) + ); + } + + float containerCrossAxis = getLayoutDimension(node, getDim(crossAxis)); + if (CSSConstants.isUndefined(getLayoutDimension(node, getDim(crossAxis)))) { + containerCrossAxis = Math.max( + // For the cross dim, we add both sides at the end because the value + // is aggregate via a max function. Intermediate negative values + // can mess this computation otherwise + crossDim + getPaddingAndBorderAxis(node, crossAxis), + getPaddingAndBorderAxis(node, crossAxis) + ); + } + + // Position elements in the cross axis + + for (int i = startLine; i < endLine; ++i) { + CSSNode child = node.getChildAt(i); + + if (getPositionType(child) == CSSPositionType.ABSOLUTE && + isPosDefined(child, getLeading(crossAxis))) { + // In case the child is absolutely positionned and has a + // top/left/bottom/right being set, we override all the previously + // computed positions to set it correctly. + setLayoutPosition(child, getPos(crossAxis), getPosition(child, getLeading(crossAxis)) + + getBorder(node, getLeading(crossAxis)) + + getMargin(child, getLeading(crossAxis))); + + } else { + float leadingCrossDim = getPaddingAndBorder(node, getLeading(crossAxis)); + + // For a relative children, we're either using alignItems (parent) or + // alignSelf (child) in order to determine the position in the cross axis + if (getPositionType(child) == CSSPositionType.RELATIVE) { + CSSAlign alignItem = getAlignItem(node, child); + if (alignItem == CSSAlign.FLEX_START) { + // Do nothing + } else if (alignItem == CSSAlign.STRETCH) { + // You can only stretch if the dimension has not already been set + // previously. + if (!isDimDefined(child, crossAxis)) { + setLayoutDimension(child, getDim(crossAxis), Math.max( + containerCrossAxis - + getPaddingAndBorderAxis(node, crossAxis) - + getMarginAxis(child, crossAxis), + // You never want to go smaller than padding + getPaddingAndBorderAxis(child, crossAxis) + )); + } + } else { + // The remaining space between the parent dimensions+padding and child + // dimensions+margin. + float remainingCrossDim = containerCrossAxis - + getPaddingAndBorderAxis(node, crossAxis) - + getDimWithMargin(child, crossAxis); + + if (alignItem == CSSAlign.CENTER) { + leadingCrossDim = leadingCrossDim + remainingCrossDim / 2; + } else { // CSSAlign.FLEX_END + leadingCrossDim = leadingCrossDim + remainingCrossDim; + } + } + } + + // And we apply the position + setLayoutPosition(child, getPos(crossAxis), getLayoutPosition(child, getPos(crossAxis)) + linesCrossDim + leadingCrossDim); + } + } + + linesCrossDim = linesCrossDim + crossDim; + linesMainDim = Math.max(linesMainDim, mainDim); + startLine = endLine; } // If the user didn't specify a width or height, and it has not been set @@ -550,7 +667,7 @@ public class LayoutEngine { setLayoutDimension(node, getDim(mainAxis), Math.max( // We're missing the last padding at this point to get the final // dimension - mainDim + getPaddingAndBorder(node, getTrailing(mainAxis)), + linesMainDim + getPaddingAndBorder(node, getTrailing(mainAxis)), // We can never assign a width smaller than the padding and borders getPaddingAndBorderAxis(node, mainAxis) )); @@ -561,67 +678,11 @@ public class LayoutEngine { // For the cross dim, we add both sides at the end because the value // is aggregate via a max function. Intermediate negative values // can mess this computation otherwise - crossDim + getPaddingAndBorderAxis(node, crossAxis), + linesCrossDim + getPaddingAndBorderAxis(node, crossAxis), getPaddingAndBorderAxis(node, crossAxis) )); } - - // Position elements in the cross axis - - for (int i = 0; i < node.getChildCount(); ++i) { - CSSNode child = node.getChildAt(i); - - if (getPositionType(child) == CSSPositionType.ABSOLUTE && - isPosDefined(child, getLeading(crossAxis))) { - // In case the child is absolutely positionned and has a - // top/left/bottom/right being set, we override all the previously - // computed positions to set it correctly. - setLayoutPosition(child, getPos(crossAxis), getPosition(child, getLeading(crossAxis)) + - getBorder(node, getLeading(crossAxis)) + - getMargin(child, getLeading(crossAxis))); - - } else { - float leadingCrossDim = getPaddingAndBorder(node, getLeading(crossAxis)); - - // For a relative children, we're either using alignItems (parent) or - // alignSelf (child) in order to determine the position in the cross axis - if (getPositionType(child) == CSSPositionType.RELATIVE) { - CSSAlign alignItem = getAlignItem(node, child); - if (alignItem == CSSAlign.FLEX_START) { - // Do nothing - } else if (alignItem == CSSAlign.STRETCH) { - // You can only stretch if the dimension has not already been set - // previously. - if (!isDimDefined(child, crossAxis)) { - setLayoutDimension(child, getDim(crossAxis), Math.max( - getLayoutDimension(node, getDim(crossAxis)) - - getPaddingAndBorderAxis(node, crossAxis) - - getMarginAxis(child, crossAxis), - // You never want to go smaller than padding - getPaddingAndBorderAxis(child, crossAxis) - )); - } - } else { - // The remaining space between the parent dimensions+padding and child - // dimensions+margin. - float remainingCrossDim = getLayoutDimension(node, getDim(crossAxis)) - - getPaddingAndBorderAxis(node, crossAxis) - - getDimWithMargin(child, crossAxis); - - if (alignItem == CSSAlign.CENTER) { - leadingCrossDim = leadingCrossDim + remainingCrossDim / 2; - } else { // CSSAlign.FLEX_END - leadingCrossDim = leadingCrossDim + remainingCrossDim; - } - } - } - - // And we apply the position - setLayoutPosition(child, getPos(crossAxis), getLayoutPosition(child, getPos(crossAxis)) + leadingCrossDim); - } - } - // Calculate dimensions for absolutely positioned elements for (int i = 0; i < node.getChildCount(); ++i) { diff --git a/src/java/tests/com/facebook/csslayout/LayoutEngineTest.java b/src/java/tests/com/facebook/csslayout/LayoutEngineTest.java index 80dd0302..3d8caf93 100644 --- a/src/java/tests/com/facebook/csslayout/LayoutEngineTest.java +++ b/src/java/tests/com/facebook/csslayout/LayoutEngineTest.java @@ -3931,5 +3931,60 @@ public class LayoutEngineTest { test("should layout with children of a contain with left", root_node, root_layout); } + + @Test + public void testCase93() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.flexDirection = CSSFlexDirection.ROW; + node_0.style.flexWrap = CSSWrap.WRAP; + node_0.style.width = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.width = 40; + node_1.style.height = 10; + node_1 = node_0.getChildAt(1); + node_1.style.width = 40; + node_1.style.height = 10; + node_1 = node_0.getChildAt(2); + node_1.style.width = 40; + node_1.style.height = 10; + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.y = 0; + node_0.layout.x = 0; + node_0.layout.width = 100; + node_0.layout.height = 20; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.y = 0; + node_1.layout.x = 0; + node_1.layout.width = 40; + node_1.layout.height = 10; + node_1 = node_0.getChildAt(1); + node_1.layout.y = 0; + node_1.layout.x = 40; + node_1.layout.width = 40; + node_1.layout.height = 10; + node_1 = node_0.getChildAt(2); + node_1.layout.y = 10; + node_1.layout.x = 0; + node_1.layout.width = 40; + node_1.layout.height = 10; + } + } + + test("should layout flex-wrap", root_node, root_layout); + } /** END_GENERATED **/ } diff --git a/src/transpile.js b/src/transpile.js index 9520eac8..093c1a2b 100644 --- a/src/transpile.js +++ b/src/transpile.js @@ -141,6 +141,10 @@ function printLayout(test) { 'relative': 'CSS_POSITION_RELATIVE', 'absolute': 'CSS_POSITION_ABSOLUTE' }); + addEnum(node, 'flexWrap', 'flex_wrap', { + 'nowrap': 'CSS_NOWRAP', + 'wrap': 'CSS_WRAP' + }); addFloat('positive', node, 'flex', 'flex'); addFloat('positive', node, 'width', 'dimensions[CSS_WIDTH]'); addFloat('positive', node, 'height', 'dimensions[CSS_HEIGHT]');