diff --git a/src/JavaTranspiler.js b/src/JavaTranspiler.js index 56ce90fd..a1bef154 100644 --- a/src/JavaTranspiler.js +++ b/src/JavaTranspiler.js @@ -103,6 +103,7 @@ var JavaTranspiler = { .replace('node.style.measure', 'node.measure') .replace(/\.children\.length/g, '.getChildCount()') .replace(/node.children\[i\]/g, 'node.getChildAt(i)') + .replace(/node.children\[ii\]/g, 'node.getChildAt(ii)') .replace(/fmaxf/g, 'Math.max') .replace(/\/\*\([^\/]+\*\/\n/g, '') // remove comments for other languages .replace(/var\/\*([^\/]+)\*\//g, '$1') diff --git a/src/Layout.c b/src/Layout.c index cbbb4c32..72b89936 100644 --- a/src/Layout.c +++ b/src/Layout.c @@ -35,6 +35,7 @@ static bool eq(float a, float b) { void init_css_node(css_node_t *node) { node->style.align_items = CSS_ALIGN_STRETCH; + node->style.align_content = CSS_ALIGN_STRETCH; // Some of the fields default to undefined and not 0 node->style.dimensions[CSS_WIDTH] = CSS_UNDEFINED; @@ -271,6 +272,10 @@ static css_justify_t getJustifyContent(css_node_t *node) { return node->style.justify_content; } +static css_align_t getAlignContent(css_node_t *node) { + return node->style.align_content; +} + static css_align_t getAlignItem(css_node_t *node, css_node_t *child) { if (child->style.align_self != CSS_ALIGN_AUTO) { return child->style.align_self; @@ -492,6 +497,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { // We aggregate the total dimensions of the container in those two variables float linesCrossDim = 0; float linesMainDim = 0; + int linesCount = 0; while (endLine < node->children_count) { // Layout non flexible children and count children by type @@ -676,6 +682,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { for (i = startLine; i < endLine; ++i) { child = node->get_child(node->context, i); + child->line_index = linesCount; if (getPositionType(child) == CSS_POSITION_ABSOLUTE && isPosDefined(child, leading[mainAxis])) { @@ -770,9 +777,112 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) { linesCrossDim += crossDim; linesMainDim = fmaxf(linesMainDim, mainDim); + linesCount += 1; startLine = endLine; } + // + // + // PIERRE: More than one line, we need to layout the crossAxis according to + // alignContent. + // + // Note that we could probably remove and handle the one line case + // here too, but for the moment this is safer since it won't interfere with + // previously working code. + // + // See specs: + // http://www.w3.org/TR/2012/CR-css3-flexbox-20120918/#layout-algorithm + // section 9.4 + // + if (linesCount > 1 && + (!isUndefined(node->layout.dimensions[dim[crossAxis]]))) + { + float nodeCrossAxisInnerSize = node->layout.dimensions[dim[crossAxis]] - + getPaddingAndBorderAxis(node, crossAxis); + float remainingCrossDim = nodeCrossAxisInnerSize - linesCrossDim; + + float crossDimAdd = 0; + float currentLead = getPaddingAndBorder(node, leading[crossAxis]); + + css_align_t alignContent = getAlignContent(node); + if (alignContent == CSS_ALIGN_FLEX_END) { + currentLead += remainingCrossDim; + } + else if (alignContent == CSS_ALIGN_CENTER) { + currentLead += remainingCrossDim / 2; + } + else if (alignContent == CSS_ALIGN_STRETCH) { + if (nodeCrossAxisInnerSize > linesCrossDim) { + crossDimAdd = (remainingCrossDim / linesCount); + } + } + + // find the first node on the first line + for (i = 0; i < node->children_count; ) { + int startIndex = i; + int lineIndex = -1; + + // get the first child on the current line + { + child = node->get_child(node->context, i); + if (getPositionType(child) != CSS_POSITION_RELATIVE) { + ++i; + continue; + } + lineIndex = child->line_index; + } + + // compute the line's height and find the endIndex + float lineHeight = 0; + for (ii = startIndex; ii < node->children_count; ++ii) { + child = node->get_child(node->context, ii); + if (getPositionType(child) != CSS_POSITION_RELATIVE) { + continue; + } + if (child->line_index != lineIndex) { + break; + } + if (!isUndefined(child->layout.dimensions[dim[crossAxis]])) { + lineHeight = fmaxf(lineHeight,child->layout.dimensions[dim[crossAxis]] + + getMarginAxis(child,crossAxis)); + } + } + int endIndex = ii; + lineHeight += crossDimAdd; + + for (ii = startIndex; ii < endIndex; ++ii) { + child = node->get_child(node->context, ii); + if (getPositionType(child) != CSS_POSITION_RELATIVE) { + continue; + } + + css_align_t alignItem = getAlignItem(node, child); + float crossPosition = child->layout.position[pos[crossAxis]]; // preserve current position if someting goes wrong with alignItem? + if (alignItem == CSS_ALIGN_FLEX_START) { + crossPosition = currentLead + getMargin(child,leading[crossAxis]); + } + else if (alignItem == CSS_ALIGN_FLEX_END) { + crossPosition = currentLead + lineHeight - + getMargin(child,trailing[crossAxis]) - + child->layout.dimensions[dim[crossAxis]]; + } + else if (alignItem == CSS_ALIGN_CENTER) { + float childHeight = child->layout.dimensions[dim[crossAxis]]; + crossPosition = currentLead + ((lineHeight - childHeight)/2); + } + else if (alignItem == CSS_ALIGN_STRETCH) { + crossPosition = currentLead + getMargin(child,leading[crossAxis]); + // TODO: Correctly set the height of items with undefined (auto) + // crossAxis dimension. + } + child->layout.position[pos[crossAxis]] = crossPosition; + } + + currentLead += lineHeight; + i = endIndex; + } + } + // 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]])) { diff --git a/src/Layout.h b/src/Layout.h index a75232d6..7ad7d4c0 100644 --- a/src/Layout.h +++ b/src/Layout.h @@ -82,6 +82,7 @@ typedef struct { float last_parent_max_width; float last_dimensions[2]; float last_position[2]; + } css_layout_t; typedef struct { @@ -91,6 +92,7 @@ typedef struct { typedef struct { css_flex_direction_t flex_direction; css_justify_t justify_content; + css_align_t align_content; css_align_t align_items; css_align_t align_self; css_position_type_t position_type; @@ -119,6 +121,7 @@ typedef struct css_node { css_style_t style; css_layout_t layout; int children_count; + int line_index; css_dim_t (*measure)(void *context, float width); void (*print)(void *context); diff --git a/src/Layout.js b/src/Layout.js index fd39b4fb..b44cd4c0 100755 --- a/src/Layout.js +++ b/src/Layout.js @@ -144,6 +144,13 @@ var computeLayout = (function() { return 'flex-start'; } + function getAlignContent(node) { + if ('alignContent' in node.style) { + return node.style.alignContent; + } + return 'stretch'; + } + function getAlignItem(node, child) { if ('alignSelf' in child.style) { return child.style.alignSelf; @@ -376,6 +383,7 @@ var computeLayout = (function() { // We aggregate the total dimensions of the container in those two variables var/*float*/ linesCrossDim = 0; var/*float*/ linesMainDim = 0; + var/*int*/ linesCount = 0; while (endLine < node.children.length) { // Layout non flexible children and count children by type @@ -560,6 +568,7 @@ var computeLayout = (function() { for (i = startLine; i < endLine; ++i) { child = node.children[i]; + child.lineIndex = linesCount; if (getPositionType(child) === CSS_POSITION_ABSOLUTE && isPosDefined(child, leading[mainAxis])) { @@ -654,9 +663,112 @@ var computeLayout = (function() { linesCrossDim += crossDim; linesMainDim = fmaxf(linesMainDim, mainDim); + linesCount += 1; startLine = endLine; } + // + // + // PIERRE: More than one line, we need to layout the crossAxis according to + // alignContent. + // + // Note that we could probably remove and handle the one line case + // here too, but for the moment this is safer since it won't interfere with + // previously working code. + // + // See specs: + // http://www.w3.org/TR/2012/CR-css3-flexbox-20120918/#layout-algorithm + // section 9.4 + // + if (linesCount > 1 && + (!isUndefined(node.layout[dim[crossAxis]]))) + { + var/*float*/ nodeCrossAxisInnerSize = node.layout[dim[crossAxis]] - + getPaddingAndBorderAxis(node, crossAxis); + var/*float*/ remainingCrossDim = nodeCrossAxisInnerSize - linesCrossDim; + + var/*float*/ crossDimAdd = 0; + var/*float*/ currentLead = getPaddingAndBorder(node, leading[crossAxis]); + + var/*css_align_t*/ alignContent = getAlignContent(node); + if (alignContent == CSS_ALIGN_FLEX_END) { + currentLead += remainingCrossDim; + } + else if (alignContent == CSS_ALIGN_CENTER) { + currentLead += remainingCrossDim / 2; + } + else if (alignContent == CSS_ALIGN_STRETCH) { + if (nodeCrossAxisInnerSize > linesCrossDim) { + crossDimAdd = (remainingCrossDim / linesCount); + } + } + + // find the first node on the first line + for (i = 0; i < node.children.length; ) { + var/*int*/ startIndex = i; + var/*int*/ lineIndex = -1; + + // get the first child on the current line + { + child = node.children[i]; + if (getPositionType(child) != CSS_POSITION_RELATIVE) { + ++i; + continue; + } + lineIndex = child.lineIndex; + } + + // compute the line's height and find the endIndex + var/*float*/ lineHeight = 0; + for (ii = startIndex; ii < node.children.length; ++ii) { + child = node.children[ii]; + if (getPositionType(child) != CSS_POSITION_RELATIVE) { + continue; + } + if (child.lineIndex != lineIndex) { + break; + } + if (!isUndefined(child.layout[dim[crossAxis]])) { + lineHeight = fmaxf(lineHeight,child.layout[dim[crossAxis]] + + getMarginAxis(child,crossAxis)); + } + } + var/*int*/ endIndex = ii; + lineHeight += crossDimAdd; + + for (ii = startIndex; ii < endIndex; ++ii) { + child = node.children[ii]; + if (getPositionType(child) != CSS_POSITION_RELATIVE) { + continue; + } + + var/*css_align_t*/ alignItem = getAlignItem(node, child); + var/*float*/ crossPosition = child.layout[pos[crossAxis]]; // preserve current position if someting goes wrong with alignItem? + if (alignItem == CSS_ALIGN_FLEX_START) { + crossPosition = currentLead + getMargin(child,leading[crossAxis]); + } + else if (alignItem == CSS_ALIGN_FLEX_END) { + crossPosition = currentLead + lineHeight - + getMargin(child,trailing[crossAxis]) - + child.layout[dim[crossAxis]]; + } + else if (alignItem == CSS_ALIGN_CENTER) { + var/*float*/ childHeight = child.layout[dim[crossAxis]]; + crossPosition = currentLead + ((lineHeight - childHeight)/2); + } + else if (alignItem == CSS_ALIGN_STRETCH) { + crossPosition = currentLead + getMargin(child,leading[crossAxis]); + // TODO: Correctly set the height of items with undefined (auto) + // crossAxis dimension. + } + child.layout[pos[crossAxis]] = crossPosition; + } + + currentLead += lineHeight; + i = endIndex; + } + } + // 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]])) { diff --git a/src/java/src/com/facebook/csslayout/CSSNode.java b/src/java/src/com/facebook/csslayout/CSSNode.java index df8c3c63..0dfa6b54 100644 --- a/src/java/src/com/facebook/csslayout/CSSNode.java +++ b/src/java/src/com/facebook/csslayout/CSSNode.java @@ -58,6 +58,8 @@ public class CSSNode { /*package*/ final CSSLayout layout = new CSSLayout(); /*package*/ final CachedCSSLayout lastLayout = new CachedCSSLayout(); + public int lineIndex = 0; + // 4 is kinda arbitrary, but the default of 10 seems really high for an average View. private final ArrayList mChildren = new ArrayList(4); diff --git a/src/java/src/com/facebook/csslayout/CSSStyle.java b/src/java/src/com/facebook/csslayout/CSSStyle.java index 602de3ed..64c67fc2 100644 --- a/src/java/src/com/facebook/csslayout/CSSStyle.java +++ b/src/java/src/com/facebook/csslayout/CSSStyle.java @@ -15,6 +15,7 @@ public class CSSStyle { public CSSFlexDirection flexDirection = CSSFlexDirection.COLUMN; public CSSJustify justifyContent = CSSJustify.FLEX_START; + public CSSAlign alignContent = CSSAlign.STRETCH; public CSSAlign alignItems = CSSAlign.STRETCH; public CSSAlign alignSelf = CSSAlign.AUTO; public CSSPositionType positionType = CSSPositionType.RELATIVE; diff --git a/src/java/src/com/facebook/csslayout/LayoutEngine.java b/src/java/src/com/facebook/csslayout/LayoutEngine.java index 01ee44d0..28d32c02 100644 --- a/src/java/src/com/facebook/csslayout/LayoutEngine.java +++ b/src/java/src/com/facebook/csslayout/LayoutEngine.java @@ -260,6 +260,10 @@ public class LayoutEngine { return node.style.alignItems; } + private static CSSAlign getAlignContent(CSSNode node) { + return node.style.alignContent; + } + private static CSSJustify getJustifyContent(CSSNode node) { return node.style.justifyContent; } @@ -430,6 +434,7 @@ public class LayoutEngine { // We aggregate the total dimensions of the container in those two variables float linesCrossDim = 0; float linesMainDim = 0; + int linesCount = 0; while (endLine < node.getChildCount()) { // Layout non flexible children and count children by type @@ -614,6 +619,7 @@ public class LayoutEngine { for (i = startLine; i < endLine; ++i) { child = node.getChildAt(i); + child.lineIndex = linesCount; if (getPositionType(child) == CSSPositionType.ABSOLUTE && isPosDefined(child, getLeading(mainAxis))) { @@ -708,9 +714,112 @@ public class LayoutEngine { linesCrossDim = linesCrossDim + crossDim; linesMainDim = Math.max(linesMainDim, mainDim); + linesCount = linesCount + 1; startLine = endLine; } + // + // + // PIERRE: More than one line, we need to layout the crossAxis according to + // alignContent. + // + // Note that we could probably remove and handle the one line case + // here too, but for the moment this is safer since it won't interfere with + // previously working code. + // + // See specs: + // http://www.w3.org/TR/2012/CR-css3-flexbox-20120918/#layout-algorithm + // section 9.4 + // + if (linesCount > 1 && + (!CSSConstants.isUndefined(getLayoutDimension(node, getDim(crossAxis))))) + { + float nodeCrossAxisInnerSize = getLayoutDimension(node, getDim(crossAxis)) - + getPaddingAndBorderAxis(node, crossAxis); + float remainingCrossDim = nodeCrossAxisInnerSize - linesCrossDim; + + float crossDimAdd = 0; + float currentLead = getPaddingAndBorder(node, getLeading(crossAxis)); + + CSSAlign alignContent = getAlignContent(node); + if (alignContent == CSSAlign.FLEX_END) { + currentLead = currentLead + remainingCrossDim; + } + else if (alignContent == CSSAlign.CENTER) { + currentLead = currentLead + remainingCrossDim / 2; + } + else if (alignContent == CSSAlign.STRETCH) { + if (nodeCrossAxisInnerSize > linesCrossDim) { + crossDimAdd = (remainingCrossDim / linesCount); + } + } + + // find the first node on the first line + for (i = 0; i < node.getChildCount(); ) { + int startIndex = i; + int lineIndex = -1; + + // get the first child on the current line + { + child = node.getChildAt(i); + if (getPositionType(child) != CSSPositionType.RELATIVE) { + ++i; + continue; + } + lineIndex = child.lineIndex; + } + + // compute the line's height and find the endIndex + float lineHeight = 0; + for (ii = startIndex; ii < node.getChildCount(); ++ii) { + child = node.getChildAt(ii); + if (getPositionType(child) != CSSPositionType.RELATIVE) { + continue; + } + if (child.lineIndex != lineIndex) { + break; + } + if (!CSSConstants.isUndefined(getLayoutDimension(child, getDim(crossAxis)))) { + lineHeight = Math.max(lineHeight,getLayoutDimension(child, getDim(crossAxis)) + + getMarginAxis(child,crossAxis)); + } + } + int endIndex = ii; + lineHeight = lineHeight + crossDimAdd; + + for (ii = startIndex; ii < endIndex; ++ii) { + child = node.getChildAt(ii); + if (getPositionType(child) != CSSPositionType.RELATIVE) { + continue; + } + + CSSAlign alignItem = getAlignItem(node, child); + float crossPosition = getLayoutPosition(child, getPos(crossAxis)); // preserve current position if someting goes wrong with alignItem? + if (alignItem == CSSAlign.FLEX_START) { + crossPosition = currentLead + getMargin(child,getLeading(crossAxis)); + } + else if (alignItem == CSSAlign.FLEX_END) { + crossPosition = currentLead + lineHeight - + getMargin(child,getTrailing(crossAxis)) - + getLayoutDimension(child, getDim(crossAxis)); + } + else if (alignItem == CSSAlign.CENTER) { + float childHeight = getLayoutDimension(child, getDim(crossAxis)); + crossPosition = currentLead + ((lineHeight - childHeight)/2); + } + else if (alignItem == CSSAlign.STRETCH) { + crossPosition = currentLead + getMargin(child,getLeading(crossAxis)); + // TODO: Correctly set the height of items with undefined (auto) + // crossAxis dimension. + } + setLayoutPosition(child, getPos(crossAxis), crossPosition); + } + + currentLead = currentLead + lineHeight; + i = endIndex; + } + } + // 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)))) { diff --git a/src/transpile.js b/src/transpile.js index 193631dc..b12c07e8 100644 --- a/src/transpile.js +++ b/src/transpile.js @@ -122,6 +122,12 @@ function printLayout(test) { 'space-between': 'CSS_JUSTIFY_SPACE_BETWEEN', 'space-around': 'CSS_JUSTIFY_SPACE_AROUND' }); + addEnum(node, 'alignContent', 'align_content', { + 'flex-start': 'CSS_ALIGN_FLEX_START', + 'center': 'CSS_ALIGN_CENTER', + 'flex-end': 'CSS_ALIGN_FLEX_END', + 'stretch': 'CSS_ALIGN_STRETCH' + }); addEnum(node, 'alignItems', 'align_items', { 'flex-start': 'CSS_ALIGN_FLEX_START', 'center': 'CSS_ALIGN_CENTER', @@ -231,11 +237,13 @@ function transpileAnnotatedJStoC(jsCode) { .replace(/\.maxHeight/g, '.maxDimensions[CSS_HEIGHT]') .replace(/\.minWidth/g, '.minDimensions[CSS_WIDTH]') .replace(/\.minHeight/g, '.minDimensions[CSS_HEIGHT]') + .replace(/\.lineIndex/g, '.line_index') .replace(/layout\[dim/g, 'layout.dimensions[dim') .replace(/layout\[pos/g, 'layout.position[pos') .replace(/layout\[leading/g, 'layout.position[leading') .replace(/style\[dim/g, 'style.dimensions[dim') .replace(/node.children\[i\]/g, 'node->get_child(node->context, i)') + .replace(/node.children\[ii\]/g, 'node->get_child(node->context, ii)') .replace(/node\./g, 'node->') .replace(/child\./g, 'child->') .replace(/parent\./g, 'parent->')