diff --git a/React/Layout/Layout.c b/React/Layout/Layout.c index c8f086b0e..d94535b30 100644 --- a/React/Layout/Layout.c +++ b/React/Layout/Layout.c @@ -19,6 +19,7 @@ * of patent rights can be found in the PATENTS file in the same directory. */ + #include #include #include #include @@ -41,6 +42,13 @@ #endif #endif + #define POSITIVE_FLEX_IS_AUTO 0 + + int gCurrentGenerationCount = 0; + + bool layoutNodeInternal(css_node_t* node, float availableWidth, float availableHeight, css_direction_t parentDirection, + css_measure_mode_t widthMeasureMode, css_measure_mode_t heightMeasureMode, bool performLayout, char* reason); + bool isUndefined(float value) { return isnan(value); } @@ -52,13 +60,15 @@ return fabs(a - b) < 0.0001; } - void init_css_node(css_node_t *node) { + void init_css_node(css_node_t* node) { node->style.align_items = CSS_ALIGN_STRETCH; node->style.align_content = CSS_ALIGN_FLEX_START; node->style.direction = CSS_DIRECTION_INHERIT; node->style.flex_direction = CSS_FLEX_DIRECTION_COLUMN; + node->style.overflow = CSS_OVERFLOW_VISIBLE; + // Some of the fields default to undefined and not 0 node->style.dimensions[CSS_WIDTH] = CSS_UNDEFINED; node->style.dimensions[CSS_HEIGHT] = CSS_UNDEFINED; @@ -85,21 +95,23 @@ node->layout.dimensions[CSS_HEIGHT] = CSS_UNDEFINED; // Such that the comparison is always going to be false - node->layout.last_requested_dimensions[CSS_WIDTH] = -1; - node->layout.last_requested_dimensions[CSS_HEIGHT] = -1; - node->layout.last_parent_max_width = -1; - node->layout.last_parent_max_height = -1; - node->layout.last_direction = (css_direction_t)-1; + node->layout.last_parent_direction = (css_direction_t)-1; node->layout.should_update = true; + node->layout.next_cached_measurements_index = 0; + + node->layout.measured_dimensions[CSS_WIDTH] = CSS_UNDEFINED; + node->layout.measured_dimensions[CSS_HEIGHT] = CSS_UNDEFINED; + node->layout.cached_layout.width_measure_mode = (css_measure_mode_t)-1; + node->layout.cached_layout.height_measure_mode = (css_measure_mode_t)-1; } - css_node_t *new_css_node() { - css_node_t *node = (css_node_t *)calloc(1, sizeof(*node)); + css_node_t* new_css_node() { + css_node_t* node = (css_node_t*)calloc(1, sizeof(*node)); init_css_node(node); return node; } - void free_css_node(css_node_t *node) { + void free_css_node(css_node_t* node) { free(node); } @@ -109,13 +121,13 @@ } } - static void print_number_0(const char *str, float number) { + static void print_number_0(const char* str, float number) { if (!eq(number, 0)) { printf("%s: %g, ", str, number); } } - static void print_number_nan(const char *str, float number) { + static void print_number_nan(const char* str, float number) { if (!isnan(number)) { printf("%s: %g, ", str, number); } @@ -130,7 +142,7 @@ static void print_css_node_rec( - css_node_t *node, + css_node_t* node, css_print_options_t options, int level ) { @@ -154,11 +166,11 @@ if (node->style.flex_direction == CSS_FLEX_DIRECTION_COLUMN) { printf("flexDirection: 'column', "); } else if (node->style.flex_direction == CSS_FLEX_DIRECTION_COLUMN_REVERSE) { - printf("flexDirection: 'columnReverse', "); + printf("flexDirection: 'column-reverse', "); } else if (node->style.flex_direction == CSS_FLEX_DIRECTION_ROW) { printf("flexDirection: 'row', "); } else if (node->style.flex_direction == CSS_FLEX_DIRECTION_ROW_REVERSE) { - printf("flexDirection: 'rowReverse', "); + printf("flexDirection: 'row-reverse', "); } if (node->style.justify_content == CSS_JUSTIFY_CENTER) { @@ -199,6 +211,12 @@ print_number_nan("flex", node->style.flex); + if (node->style.overflow == CSS_OVERFLOW_HIDDEN) { + printf("overflow: 'hidden', "); + } else if (node->style.overflow == CSS_OVERFLOW_VISIBLE) { + printf("overflow: 'visible', "); + } + if (four_equal(node->style.margin)) { print_number_0("margin", node->style.margin[CSS_LEFT]); } else { @@ -211,7 +229,7 @@ } if (four_equal(node->style.padding)) { - print_number_0("padding", node->style.margin[CSS_LEFT]); + print_number_0("padding", node->style.padding[CSS_LEFT]); } else { print_number_0("paddingLeft", node->style.padding[CSS_LEFT]); print_number_0("paddingRight", node->style.padding[CSS_RIGHT]); @@ -234,6 +252,10 @@ print_number_nan("width", node->style.dimensions[CSS_WIDTH]); print_number_nan("height", node->style.dimensions[CSS_HEIGHT]); + print_number_nan("maxWidth", node->style.maxDimensions[CSS_WIDTH]); + print_number_nan("maxHeight", node->style.maxDimensions[CSS_HEIGHT]); + print_number_nan("minWidth", node->style.minDimensions[CSS_WIDTH]); + print_number_nan("minHeight", node->style.minDimensions[CSS_HEIGHT]); if (node->style.position_type == CSS_POSITION_ABSOLUTE) { printf("position: 'absolute', "); @@ -257,11 +279,10 @@ } } - void print_css_node(css_node_t *node, css_print_options_t options) { + void print_css_node(css_node_t* node, css_print_options_t options) { print_css_node_rec(node, options, 0); } - static css_position_t leading[4] = { /* CSS_FLEX_DIRECTION_COLUMN = */ CSS_TOP, /* CSS_FLEX_DIRECTION_COLUMN_REVERSE = */ CSS_BOTTOM, @@ -297,7 +318,41 @@ flex_direction == CSS_FLEX_DIRECTION_COLUMN_REVERSE; } - static float getLeadingMargin(css_node_t *node, css_flex_direction_t axis) { + static bool isFlexBasisAuto(css_node_t* node) { + #if POSITIVE_FLEX_IS_AUTO + // All flex values are auto. + (void) node; + return true; + #else + // A flex value > 0 implies a basis of zero. + return node->style.flex <= 0; + #endif + } + + static float getFlexGrowFactor(css_node_t* node) { + // Flex grow is implied by positive values for flex. + if (node->style.flex > 0) { + return node->style.flex; + } + return 0; + } + + static float getFlexShrinkFactor(css_node_t* node) { + #if POSITIVE_FLEX_IS_AUTO + // A flex shrink factor of 1 is implied by non-zero values for flex. + if (node->style.flex != 0) { + return 1; + } + #else + // A flex shrink factor of 1 is implied by negative values for flex. + if (node->style.flex < 0) { + return 1; + } + #endif + return 0; + } + + static float getLeadingMargin(css_node_t* node, css_flex_direction_t axis) { if (isRowDirection(axis) && !isUndefined(node->style.margin[CSS_START])) { return node->style.margin[CSS_START]; } @@ -305,7 +360,7 @@ return node->style.margin[leading[axis]]; } - static float getTrailingMargin(css_node_t *node, css_flex_direction_t axis) { + static float getTrailingMargin(css_node_t* node, css_flex_direction_t axis) { if (isRowDirection(axis) && !isUndefined(node->style.margin[CSS_END])) { return node->style.margin[CSS_END]; } @@ -313,7 +368,7 @@ return node->style.margin[trailing[axis]]; } - static float getLeadingPadding(css_node_t *node, css_flex_direction_t axis) { + static float getLeadingPadding(css_node_t* node, css_flex_direction_t axis) { if (isRowDirection(axis) && !isUndefined(node->style.padding[CSS_START]) && node->style.padding[CSS_START] >= 0) { @@ -327,7 +382,7 @@ return 0; } - static float getTrailingPadding(css_node_t *node, css_flex_direction_t axis) { + static float getTrailingPadding(css_node_t* node, css_flex_direction_t axis) { if (isRowDirection(axis) && !isUndefined(node->style.padding[CSS_END]) && node->style.padding[CSS_END] >= 0) { @@ -341,7 +396,7 @@ return 0; } - static float getLeadingBorder(css_node_t *node, css_flex_direction_t axis) { + static float getLeadingBorder(css_node_t* node, css_flex_direction_t axis) { if (isRowDirection(axis) && !isUndefined(node->style.border[CSS_START]) && node->style.border[CSS_START] >= 0) { @@ -355,7 +410,7 @@ return 0; } - static float getTrailingBorder(css_node_t *node, css_flex_direction_t axis) { + static float getTrailingBorder(css_node_t* node, css_flex_direction_t axis) { if (isRowDirection(axis) && !isUndefined(node->style.border[CSS_END]) && node->style.border[CSS_END] >= 0) { @@ -369,34 +424,30 @@ return 0; } - static float getLeadingPaddingAndBorder(css_node_t *node, css_flex_direction_t axis) { + static float getLeadingPaddingAndBorder(css_node_t* node, css_flex_direction_t axis) { return getLeadingPadding(node, axis) + getLeadingBorder(node, axis); } - static float getTrailingPaddingAndBorder(css_node_t *node, css_flex_direction_t axis) { + static float getTrailingPaddingAndBorder(css_node_t* node, css_flex_direction_t axis) { return getTrailingPadding(node, axis) + getTrailingBorder(node, axis); } - static float getBorderAxis(css_node_t *node, css_flex_direction_t axis) { - return getLeadingBorder(node, axis) + getTrailingBorder(node, axis); - } - - static float getMarginAxis(css_node_t *node, css_flex_direction_t axis) { + static float getMarginAxis(css_node_t* node, css_flex_direction_t axis) { return getLeadingMargin(node, axis) + getTrailingMargin(node, axis); } - static float getPaddingAndBorderAxis(css_node_t *node, css_flex_direction_t axis) { + static float getPaddingAndBorderAxis(css_node_t* node, css_flex_direction_t axis) { return getLeadingPaddingAndBorder(node, axis) + getTrailingPaddingAndBorder(node, axis); } - static css_align_t getAlignItem(css_node_t *node, css_node_t *child) { + 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; } return node->style.align_items; } - static css_direction_t resolveDirection(css_node_t *node, css_direction_t parentDirection) { + static css_direction_t resolveDirection(css_node_t* node, css_direction_t parentDirection) { css_direction_t direction = node->style.direction; if (direction == CSS_DIRECTION_INHERIT) { @@ -406,7 +457,7 @@ return direction; } - static css_flex_direction_t getFlexDirection(css_node_t *node) { + static css_flex_direction_t getFlexDirection(css_node_t* node) { return node->style.flex_direction; } @@ -430,46 +481,46 @@ } } - static float getFlex(css_node_t *node) { + static float getFlex(css_node_t* node) { return node->style.flex; } - static bool isFlex(css_node_t *node) { + static bool isFlex(css_node_t* node) { return ( node->style.position_type == CSS_POSITION_RELATIVE && - getFlex(node) > 0 + getFlex(node) != 0 ); } - static bool isFlexWrap(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]] + + static float getDimWithMargin(css_node_t* node, css_flex_direction_t axis) { + return node->layout.measured_dimensions[dim[axis]] + getLeadingMargin(node, axis) + getTrailingMargin(node, axis); } - static bool isStyleDimDefined(css_node_t *node, css_flex_direction_t axis) { + static bool isStyleDimDefined(css_node_t* node, css_flex_direction_t axis) { float value = node->style.dimensions[dim[axis]]; return !isUndefined(value) && value >= 0.0; } - static bool isLayoutDimDefined(css_node_t *node, css_flex_direction_t axis) { - float value = node->layout.dimensions[dim[axis]]; + static bool isLayoutDimDefined(css_node_t* node, css_flex_direction_t axis) { + float value = node->layout.measured_dimensions[dim[axis]]; return !isUndefined(value) && value >= 0.0; } - static bool isPosDefined(css_node_t *node, css_position_t position) { + static bool isPosDefined(css_node_t* node, css_position_t position) { return !isUndefined(node->style.position[position]); } - static bool isMeasureDefined(css_node_t *node) { + static bool isMeasureDefined(css_node_t* node) { return node->measure; } - static float getPosition(css_node_t *node, css_position_t position) { + static float getPosition(css_node_t* node, css_position_t position) { float result = node->style.position[position]; if (!isUndefined(result)) { return result; @@ -477,7 +528,7 @@ return 0; } - static float boundAxis(css_node_t *node, css_flex_direction_t axis, float value) { + static float boundAxisWithinMinAndMax(css_node_t* node, css_flex_direction_t axis, float value) { float min = CSS_UNDEFINED; float max = CSS_UNDEFINED; @@ -501,32 +552,22 @@ return boundValue; } - // When the user specifically sets a value for width or height - static void setDimensionFromStyle(css_node_t *node, css_flex_direction_t axis) { - // The parent already computed us a width or height. We just skip it - if (isLayoutDimDefined(node, axis)) { - return; - } - // We only run if there's a width or height defined - if (!isStyleDimDefined(node, axis)) { - return; - } - - // The dimensions can never be smaller than the padding and border - node->layout.dimensions[dim[axis]] = fmaxf( - boundAxis(node, axis, node->style.dimensions[dim[axis]]), - getPaddingAndBorderAxis(node, axis) - ); + // Like boundAxisWithinMinAndMax but also ensures that the value doesn't go below the + // padding and border amount. + static float boundAxis(css_node_t* node, css_flex_direction_t axis, float value) { + return fmaxf(boundAxisWithinMinAndMax(node, axis, value), getPaddingAndBorderAxis(node, axis)); } - static void setTrailingPosition(css_node_t *node, css_node_t *child, css_flex_direction_t axis) { - child->layout.position[trailing[axis]] = node->layout.dimensions[dim[axis]] - - child->layout.dimensions[dim[axis]] - child->layout.position[pos[axis]]; - } + static void setTrailingPosition(css_node_t* node, css_node_t* child, css_flex_direction_t axis) { + float size = child->style.position_type == CSS_POSITION_ABSOLUTE ? + 0 : + child->layout.measured_dimensions[dim[axis]]; + child->layout.position[trailing[axis]] = node->layout.measured_dimensions[dim[axis]] - size - child->layout.position[pos[axis]]; + } // If both left and right are defined, then use left. Otherwise return // +left or -right depending on which is defined. - static float getRelativePosition(css_node_t *node, css_flex_direction_t axis) { + static float getRelativePosition(css_node_t* node, css_flex_direction_t axis) { float lead = node->style.position[leading[axis]]; if (!isUndefined(lead)) { return lead; @@ -534,352 +575,388 @@ return -getPosition(node, trailing[axis]); } - static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, float parentMaxHeight, css_direction_t parentDirection) { - /** START_GENERATED **/ - css_direction_t direction = resolveDirection(node, parentDirection); + static void setPosition(css_node_t* node, css_direction_t direction) { css_flex_direction_t mainAxis = resolveAxis(getFlexDirection(node), direction); css_flex_direction_t crossAxis = getCrossFlexDirection(mainAxis, direction); - css_flex_direction_t resolvedRowAxis = resolveAxis(CSS_FLEX_DIRECTION_ROW, direction); - // Handle width and height style attributes - setDimensionFromStyle(node, mainAxis); - setDimensionFromStyle(node, crossAxis); + node->layout.position[leading[mainAxis]] = getLeadingMargin(node, mainAxis) + + getRelativePosition(node, mainAxis); + node->layout.position[trailing[mainAxis]] = getTrailingMargin(node, mainAxis) + + getRelativePosition(node, mainAxis); + node->layout.position[leading[crossAxis]] = getLeadingMargin(node, crossAxis) + + getRelativePosition(node, crossAxis); + node->layout.position[trailing[crossAxis]] = getTrailingMargin(node, crossAxis) + + getRelativePosition(node, crossAxis); + } - // Set the resolved resolution in the node's layout + // + // This is the main routine that implements a subset of the flexbox layout algorithm + // described in the W3C CSS documentation: https://www.w3.org/TR/css3-flexbox/. + // + // Limitations of this algorithm, compared to the full standard: + // * Display property is always assumed to be 'flex' except for Text nodes, which + // are assumed to be 'inline-flex'. + // * The 'zIndex' property (or any form of z ordering) is not supported. Nodes are + // stacked in document order. + // * The 'order' property is not supported. The order of flex items is always defined + // by document order. + // * The 'visibility' property is always assumed to be 'visible'. Values of 'collapse' + // and 'hidden' are not supported. + // * The 'wrap' property supports only 'nowrap' (which is the default) or 'wrap'. The + // rarely-used 'wrap-reverse' is not supported. + // * Rather than allowing arbitrary combinations of flexGrow, flexShrink and + // flexBasis, this algorithm supports only the three most common combinations: + // flex: 0 is equiavlent to flex: 0 0 auto + // flex: n (where n is a positive value) is equivalent to flex: n 1 auto + // If POSITIVE_FLEX_IS_AUTO is 0, then it is equivalent to flex: n 0 0 + // This is faster because the content doesn't need to be measured, but it's + // less flexible because the basis is always 0 and can't be overriden with + // the width/height attributes. + // flex: -1 (or any negative value) is equivalent to flex: 0 1 auto + // * Margins cannot be specified as 'auto'. They must be specified in terms of pixel + // values, and the default value is 0. + // * The 'baseline' value is not supported for alignItems and alignSelf properties. + // * Values of width, maxWidth, minWidth, height, maxHeight and minHeight must be + // specified as pixel values, not as percentages. + // * There is no support for calculation of dimensions based on intrinsic aspect ratios + // (e.g. images). + // * There is no support for forced breaks. + // * It does not support vertical inline directions (top-to-bottom or bottom-to-top text). + // + // Deviations from standard: + // * Section 4.5 of the spec indicates that all flex items have a default minimum + // main size. For text blocks, for example, this is the width of the widest word. + // Calculating the minimum width is expensive, so we forego it and assume a default + // minimum main size of 0. + // * Min/Max sizes in the main axis are not honored when resolving flexible lengths. + // * The spec indicates that the default value for 'flexDirection' is 'row', but + // the algorithm below assumes a default of 'column'. + // + // Input parameters: + // - node: current node to be sized and layed out + // - availableWidth & availableHeight: available size to be used for sizing the node + // or CSS_UNDEFINED if the size is not available; interpretation depends on layout + // flags + // - parentDirection: the inline (text) direction within the parent (left-to-right or + // right-to-left) + // - widthMeasureMode: indicates the sizing rules for the width (see below for explanation) + // - heightMeasureMode: indicates the sizing rules for the height (see below for explanation) + // - performLayout: specifies whether the caller is interested in just the dimensions + // of the node or it requires the entire node and its subtree to be layed out + // (with final positions) + // + // Details: + // This routine is called recursively to lay out subtrees of flexbox elements. It uses the + // information in node.style, which is treated as a read-only input. It is responsible for + // setting the layout.direction and layout.measured_dimensions fields for the input node as well + // as the layout.position and layout.line_index fields for its child nodes. The + // layout.measured_dimensions field includes any border or padding for the node but does + // not include margins. + // + // The spec describes four different layout modes: "fill available", "max content", "min content", + // and "fit content". Of these, we don't use "min content" because we don't support default + // minimum main sizes (see above for details). Each of our measure modes maps to a layout mode + // from the spec (https://www.w3.org/TR/css3-sizing/#terms): + // - CSS_MEASURE_MODE_UNDEFINED: max content + // - CSS_MEASURE_MODE_EXACTLY: fill available + // - CSS_MEASURE_MODE_AT_MOST: fit content + // + // When calling layoutNodeImpl and layoutNodeInternal, if the caller passes an available size of + // undefined then it must also pass a measure mode of CSS_MEASURE_MODE_UNDEFINED in that dimension. + // + static void layoutNodeImpl(css_node_t* node, float availableWidth, float availableHeight, + css_direction_t parentDirection, css_measure_mode_t widthMeasureMode, css_measure_mode_t heightMeasureMode, bool performLayout) { + /** START_GENERATED **/ + + assert(isUndefined(availableWidth) ? widthMeasureMode == CSS_MEASURE_MODE_UNDEFINED : true); // availableWidth is indefinite so widthMeasureMode must be CSS_MEASURE_MODE_UNDEFINED + assert(isUndefined(availableHeight) ? heightMeasureMode == CSS_MEASURE_MODE_UNDEFINED : true); // availableHeight is indefinite so heightMeasureMode must be CSS_MEASURE_MODE_UNDEFINED + + float paddingAndBorderAxisRow = getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW); + float paddingAndBorderAxisColumn = getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_COLUMN); + float marginAxisRow = getMarginAxis(node, CSS_FLEX_DIRECTION_ROW); + float marginAxisColumn = getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN); + + // Set the resolved resolution in the node's layout. + css_direction_t direction = resolveDirection(node, parentDirection); node->layout.direction = direction; - // The position is set by the parent, but we need to complete it with a - // delta composed of the margin and left/top/right/bottom - node->layout.position[leading[mainAxis]] += getLeadingMargin(node, mainAxis) + - getRelativePosition(node, mainAxis); - node->layout.position[trailing[mainAxis]] += getTrailingMargin(node, mainAxis) + - getRelativePosition(node, mainAxis); - node->layout.position[leading[crossAxis]] += getLeadingMargin(node, crossAxis) + - getRelativePosition(node, crossAxis); - node->layout.position[trailing[crossAxis]] += getTrailingMargin(node, crossAxis) + - getRelativePosition(node, crossAxis); - - // Inline immutable values from the target node to avoid excessive method - // invocations during the layout calculation. - int childCount = node->children_count; - float paddingAndBorderAxisResolvedRow = getPaddingAndBorderAxis(node, resolvedRowAxis); - float paddingAndBorderAxisColumn = getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_COLUMN); - + // For content (text) nodes, determine the dimensions based on the text contents. if (isMeasureDefined(node)) { - bool isResolvedRowDimDefined = isLayoutDimDefined(node, resolvedRowAxis); + float innerWidth = availableWidth - marginAxisRow - paddingAndBorderAxisRow; + float innerHeight = availableHeight - marginAxisColumn - paddingAndBorderAxisColumn; - float width = CSS_UNDEFINED; - css_measure_mode_t widthMode = CSS_MEASURE_MODE_UNDEFINED; - if (isStyleDimDefined(node, resolvedRowAxis)) { - width = node->style.dimensions[CSS_WIDTH]; - widthMode = CSS_MEASURE_MODE_EXACTLY; - } else if (isResolvedRowDimDefined) { - width = node->layout.dimensions[dim[resolvedRowAxis]]; - widthMode = CSS_MEASURE_MODE_EXACTLY; + if (widthMeasureMode == CSS_MEASURE_MODE_EXACTLY && heightMeasureMode == CSS_MEASURE_MODE_EXACTLY) { + + // Don't bother sizing the text if both dimensions are already defined. + node->layout.measured_dimensions[CSS_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, availableWidth - marginAxisRow); + node->layout.measured_dimensions[CSS_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, availableHeight - marginAxisColumn); + } else if (innerWidth <= 0 || innerHeight <= 0) { + + // Don't bother sizing the text if there's no horizontal or vertical space. + node->layout.measured_dimensions[CSS_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, 0); + node->layout.measured_dimensions[CSS_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, 0); } else { - width = parentMaxWidth - - getMarginAxis(node, resolvedRowAxis); - widthMode = CSS_MEASURE_MODE_AT_MOST; - } - width -= paddingAndBorderAxisResolvedRow; - if (isUndefined(width)) { - widthMode = CSS_MEASURE_MODE_UNDEFINED; - } - float height = CSS_UNDEFINED; - css_measure_mode_t heightMode = CSS_MEASURE_MODE_UNDEFINED; - if (isStyleDimDefined(node, CSS_FLEX_DIRECTION_COLUMN)) { - height = node->style.dimensions[CSS_HEIGHT]; - heightMode = CSS_MEASURE_MODE_EXACTLY; - } else if (isLayoutDimDefined(node, CSS_FLEX_DIRECTION_COLUMN)) { - height = node->layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]; - heightMode = CSS_MEASURE_MODE_EXACTLY; - } else { - height = parentMaxHeight - - getMarginAxis(node, resolvedRowAxis); - heightMode = CSS_MEASURE_MODE_AT_MOST; - } - height -= getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_COLUMN); - if (isUndefined(height)) { - heightMode = CSS_MEASURE_MODE_UNDEFINED; - } - - // We only need to give a dimension for the text if we haven't got any - // for it computed yet. It can either be from the style attribute or because - // the element is flexible. - bool isRowUndefined = !isStyleDimDefined(node, resolvedRowAxis) && !isResolvedRowDimDefined; - bool isColumnUndefined = !isStyleDimDefined(node, CSS_FLEX_DIRECTION_COLUMN) && - isUndefined(node->layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]); - - // Let's not measure the text if we already know both dimensions - if (isRowUndefined || isColumnUndefined) { + // Measure the text under the current constraints. css_dim_t measureDim = node->measure( node->context, - width, - widthMode, - height, - heightMode + innerWidth, + widthMeasureMode, + innerHeight, + heightMeasureMode ); - if (isRowUndefined) { - node->layout.dimensions[CSS_WIDTH] = measureDim.dimensions[CSS_WIDTH] + - paddingAndBorderAxisResolvedRow; - } - if (isColumnUndefined) { - node->layout.dimensions[CSS_HEIGHT] = measureDim.dimensions[CSS_HEIGHT] + - paddingAndBorderAxisColumn; - } + + node->layout.measured_dimensions[CSS_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, + (widthMeasureMode == CSS_MEASURE_MODE_UNDEFINED || widthMeasureMode == CSS_MEASURE_MODE_AT_MOST) ? + measureDim.dimensions[CSS_WIDTH] + paddingAndBorderAxisRow : + availableWidth - marginAxisRow); + node->layout.measured_dimensions[CSS_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, + (heightMeasureMode == CSS_MEASURE_MODE_UNDEFINED || heightMeasureMode == CSS_MEASURE_MODE_AT_MOST) ? + measureDim.dimensions[CSS_HEIGHT] + paddingAndBorderAxisColumn : + availableHeight - marginAxisColumn); } - if (childCount == 0) { + + return; + } + + // For nodes with no children, use the available values if they were provided, or + // the minimum size as indicated by the padding and border sizes. + int childCount = node->children_count; + if (childCount == 0) { + node->layout.measured_dimensions[CSS_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, + (widthMeasureMode == CSS_MEASURE_MODE_UNDEFINED || widthMeasureMode == CSS_MEASURE_MODE_AT_MOST) ? + paddingAndBorderAxisRow : + availableWidth - marginAxisRow); + node->layout.measured_dimensions[CSS_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, + (heightMeasureMode == CSS_MEASURE_MODE_UNDEFINED || heightMeasureMode == CSS_MEASURE_MODE_AT_MOST) ? + paddingAndBorderAxisColumn : + availableHeight - marginAxisColumn); + return; + } + + // If we're not being asked to perform a full layout, we can handle a number of common + // cases here without incurring the cost of the remaining function. + if (!performLayout) { + // If we're being asked to size the content with an at most constraint but there is no available width, + // the measurement will always be zero. + if (widthMeasureMode == CSS_MEASURE_MODE_AT_MOST && availableWidth <= 0 && + heightMeasureMode == CSS_MEASURE_MODE_AT_MOST && availableHeight <= 0) { + node->layout.measured_dimensions[CSS_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, 0); + node->layout.measured_dimensions[CSS_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, 0); + return; + } + + if (widthMeasureMode == CSS_MEASURE_MODE_AT_MOST && availableWidth <= 0) { + node->layout.measured_dimensions[CSS_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, 0); + node->layout.measured_dimensions[CSS_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, isUndefined(availableHeight) ? 0 : (availableHeight - marginAxisColumn)); + return; + } + + if (heightMeasureMode == CSS_MEASURE_MODE_AT_MOST && availableHeight <= 0) { + node->layout.measured_dimensions[CSS_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, isUndefined(availableWidth) ? 0 : (availableWidth - marginAxisRow)); + node->layout.measured_dimensions[CSS_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, 0); + return; + } + + // If we're being asked to use an exact width/height, there's no need to measure the children. + if (widthMeasureMode == CSS_MEASURE_MODE_EXACTLY && heightMeasureMode == CSS_MEASURE_MODE_EXACTLY) { + node->layout.measured_dimensions[CSS_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, availableWidth - marginAxisRow); + node->layout.measured_dimensions[CSS_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, availableHeight - marginAxisColumn); return; } } - bool isNodeFlexWrap = isFlexWrap(node); - + // STEP 1: CALCULATE VALUES FOR REMAINDER OF ALGORITHM + css_flex_direction_t mainAxis = resolveAxis(getFlexDirection(node), direction); + css_flex_direction_t crossAxis = getCrossFlexDirection(mainAxis, direction); + bool isMainAxisRow = isRowDirection(mainAxis); css_justify_t justifyContent = node->style.justify_content; - - float leadingPaddingAndBorderMain = getLeadingPaddingAndBorder(node, mainAxis); - float leadingPaddingAndBorderCross = getLeadingPaddingAndBorder(node, crossAxis); - float paddingAndBorderAxisMain = getPaddingAndBorderAxis(node, mainAxis); - float paddingAndBorderAxisCross = getPaddingAndBorderAxis(node, crossAxis); - - bool isMainDimDefined = isLayoutDimDefined(node, mainAxis); - bool isCrossDimDefined = isLayoutDimDefined(node, crossAxis); - bool isMainRowDirection = isRowDirection(mainAxis); - - int i; - int ii; - css_node_t* child; - css_flex_direction_t axis; + bool isNodeFlexWrap = isFlexWrap(node); css_node_t* firstAbsoluteChild = NULL; css_node_t* currentAbsoluteChild = NULL; - float definedMainDim = CSS_UNDEFINED; - if (isMainDimDefined) { - definedMainDim = node->layout.dimensions[dim[mainAxis]] - paddingAndBorderAxisMain; - } + float leadingPaddingAndBorderMain = getLeadingPaddingAndBorder(node, mainAxis); + float trailingPaddingAndBorderMain = getTrailingPaddingAndBorder(node, mainAxis); + float leadingPaddingAndBorderCross = getLeadingPaddingAndBorder(node, crossAxis); + float paddingAndBorderAxisMain = getPaddingAndBorderAxis(node, mainAxis); + float paddingAndBorderAxisCross = getPaddingAndBorderAxis(node, crossAxis); - // We want to execute the next two loops one per line with flex-wrap - int startLine = 0; - int endLine = 0; - // int nextOffset = 0; - int alreadyComputedNextLayout = 0; - // We aggregate the total dimensions of the container in those two variables - float linesCrossDim = 0; - float linesMainDim = 0; - int linesCount = 0; - while (endLine < childCount) { - // Layout non flexible children and count children by type + css_measure_mode_t measureModeMainDim = isMainAxisRow ? widthMeasureMode : heightMeasureMode; + css_measure_mode_t measureModeCrossDim = isMainAxisRow ? heightMeasureMode : widthMeasureMode; - // 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; + // STEP 2: DETERMINE AVAILABLE SIZE IN MAIN AND CROSS DIRECTIONS + float availableInnerWidth = availableWidth - marginAxisRow - paddingAndBorderAxisRow; + float availableInnerHeight = availableHeight - marginAxisColumn - paddingAndBorderAxisColumn; + float availableInnerMainDim = isMainAxisRow ? availableInnerWidth : availableInnerHeight; + float availableInnerCrossDim = isMainAxisRow ? availableInnerHeight : availableInnerWidth; - // 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; + // STEP 3: DETERMINE FLEX BASIS FOR EACH ITEM + css_node_t* child; + int i; + float childWidth; + float childHeight; + css_measure_mode_t childWidthMeasureMode; + css_measure_mode_t childHeightMeasureMode; + for (i = 0; i < childCount; i++) { + child = node->get_child(node->context, i); - // Use the line loop to position children in the main axis for as long - // as they are using a simple stacking behaviour. Children that are - // immediately stacked in the initial loop will not be touched again - // in . - bool isSimpleStackMain = - (isMainDimDefined && justifyContent == CSS_JUSTIFY_FLEX_START) || - (!isMainDimDefined && justifyContent != CSS_JUSTIFY_CENTER); - int firstComplexMain = (isSimpleStackMain ? childCount : startLine); - - // Use the initial line loop to position children in the cross axis for - // as long as they are relatively positioned with alignment STRETCH or - // FLEX_START. Children that are immediately stacked in the initial loop - // will not be touched again in . - bool isSimpleStackCross = true; - int firstComplexCross = childCount; - - css_node_t* firstFlexChild = NULL; - css_node_t* currentFlexChild = NULL; - - float mainDim = leadingPaddingAndBorderMain; - float crossDim = 0; - - float maxWidth = CSS_UNDEFINED; - float maxHeight = CSS_UNDEFINED; - for (i = startLine; i < childCount; ++i) { - child = node->get_child(node->context, i); - child->line_index = linesCount; - - child->next_absolute_child = NULL; - child->next_flex_child = NULL; - - css_align_t alignItem = getAlignItem(node, child); - - // Pre-fill cross axis dimensions when the child is using stretch before - // we call the recursive layout pass - if (alignItem == CSS_ALIGN_STRETCH && - child->style.position_type == CSS_POSITION_RELATIVE && - isCrossDimDefined && - !isStyleDimDefined(child, crossAxis)) { - child->layout.dimensions[dim[crossAxis]] = fmaxf( - boundAxis(child, crossAxis, node->layout.dimensions[dim[crossAxis]] - - paddingAndBorderAxisCross - getMarginAxis(child, crossAxis)), - // You never want to go smaller than padding - getPaddingAndBorderAxis(child, crossAxis) - ); - } else if (child->style.position_type == CSS_POSITION_ABSOLUTE) { - // Store a private linked list of absolutely positioned children - // so that we can efficiently traverse them later. - if (firstAbsoluteChild == NULL) { - firstAbsoluteChild = child; - } - if (currentAbsoluteChild != NULL) { - currentAbsoluteChild->next_absolute_child = child; - } - currentAbsoluteChild = child; - - // 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 (ii = 0; ii < 2; ii++) { - axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; - if (isLayoutDimDefined(node, axis) && - !isStyleDimDefined(child, axis) && - isPosDefined(child, leading[axis]) && - isPosDefined(child, trailing[axis])) { - child->layout.dimensions[dim[axis]] = fmaxf( - boundAxis(child, axis, node->layout.dimensions[dim[axis]] - - getPaddingAndBorderAxis(node, axis) - - getMarginAxis(child, axis) - - getPosition(child, leading[axis]) - - getPosition(child, trailing[axis])), - // You never want to go smaller than padding - getPaddingAndBorderAxis(child, axis) - ); - } - } - } - - float nextContentDim = 0; - - // It only makes sense to consider a child flexible if we have a computed - // dimension for the node-> - if (isMainDimDefined && isFlex(child)) { - flexibleChildrenCount++; - totalFlexible += child->style.flex; - - // Store a private linked list of flexible children so that we can - // efficiently traverse them later. - if (firstFlexChild == NULL) { - firstFlexChild = child; - } - if (currentFlexChild != NULL) { - currentFlexChild->next_flex_child = child; - } - currentFlexChild = 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, which represents - // the smallest possible size for the child, to compute the remaining - // available space. - nextContentDim = getPaddingAndBorderAxis(child, mainAxis) + - getMarginAxis(child, mainAxis); - - } else { - maxWidth = CSS_UNDEFINED; - maxHeight = CSS_UNDEFINED; - - if (!isMainRowDirection) { - if (isLayoutDimDefined(node, resolvedRowAxis)) { - maxWidth = node->layout.dimensions[dim[resolvedRowAxis]] - - paddingAndBorderAxisResolvedRow; - } else { - maxWidth = parentMaxWidth - - getMarginAxis(node, resolvedRowAxis) - - paddingAndBorderAxisResolvedRow; - } - } else { - if (isLayoutDimDefined(node, CSS_FLEX_DIRECTION_COLUMN)) { - maxHeight = node->layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] - - paddingAndBorderAxisColumn; - } else { - maxHeight = parentMaxHeight - - getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN) - - paddingAndBorderAxisColumn; - } - } - - // This is the main recursive call. We layout non flexible children. - if (alreadyComputedNextLayout == 0) { - layoutNode(child, maxWidth, maxHeight, direction); - } - - // Absolute positioned elements do not take part of the layout, so we - // don't use them to compute mainContentDim - if (child->style.position_type == 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 (isNodeFlexWrap && - isMainDimDefined && - mainContentDim + nextContentDim > definedMainDim && - // If there's only one element, then it's bigger than the content - // and needs its own line - i != startLine) { - nonFlexibleChildrenCount--; - alreadyComputedNextLayout = 1; - break; - } - - // Disable simple stacking in the main axis for the current line as - // we found a non-trivial child-> The remaining children will be laid out - // in . - if (isSimpleStackMain && - (child->style.position_type != CSS_POSITION_RELATIVE || isFlex(child))) { - isSimpleStackMain = false; - firstComplexMain = i; - } - - // Disable simple stacking in the cross axis for the current line as - // we found a non-trivial child-> The remaining children will be laid out - // in . - if (isSimpleStackCross && - (child->style.position_type != CSS_POSITION_RELATIVE || - (alignItem != CSS_ALIGN_STRETCH && alignItem != CSS_ALIGN_FLEX_START) || - (alignItem == CSS_ALIGN_STRETCH && !isCrossDimDefined))) { - isSimpleStackCross = false; - firstComplexCross = i; - } - - if (isSimpleStackMain) { - child->layout.position[pos[mainAxis]] += mainDim; - if (isMainDimDefined) { - setTrailingPosition(node, child, mainAxis); - } - - mainDim += getDimWithMargin(child, mainAxis); - crossDim = fmaxf(crossDim, boundAxis(child, crossAxis, getDimWithMargin(child, crossAxis))); - } - - if (isSimpleStackCross) { - child->layout.position[pos[crossAxis]] += linesCrossDim + leadingPaddingAndBorderCross; - if (isCrossDimDefined) { - setTrailingPosition(node, child, crossAxis); - } - } - - alreadyComputedNextLayout = 0; - mainContentDim += nextContentDim; - endLine = i + 1; + if (performLayout) { + // Set the initial position (relative to the parent). + css_direction_t childDirection = resolveDirection(child, direction); + setPosition(child, childDirection); } - // Layout flexible children and allocate empty space + // Absolute-positioned children don't participate in flex layout. Add them + // to a list that we can process later. + if (child->style.position_type == CSS_POSITION_ABSOLUTE) { + + // Store a private linked list of absolutely positioned children + // so that we can efficiently traverse them later. + if (firstAbsoluteChild == NULL) { + firstAbsoluteChild = child; + } + if (currentAbsoluteChild != NULL) { + currentAbsoluteChild->next_child = child; + } + currentAbsoluteChild = child; + child->next_child = NULL; + } else { + + if (isMainAxisRow && isStyleDimDefined(child, CSS_FLEX_DIRECTION_ROW)) { + + // The width is definite, so use that as the flex basis. + child->layout.flex_basis = fmaxf(child->style.dimensions[CSS_WIDTH], getPaddingAndBorderAxis(child, CSS_FLEX_DIRECTION_ROW)); + } else if (!isMainAxisRow && isStyleDimDefined(child, CSS_FLEX_DIRECTION_COLUMN)) { + + // The height is definite, so use that as the flex basis. + child->layout.flex_basis = fmaxf(child->style.dimensions[CSS_HEIGHT], getPaddingAndBorderAxis(child, CSS_FLEX_DIRECTION_COLUMN)); + } else if (!isFlexBasisAuto(child) && !isUndefined(availableInnerMainDim)) { + + // If the basis isn't 'auto', it is assumed to be zero. + child->layout.flex_basis = fmaxf(0, getPaddingAndBorderAxis(child, mainAxis)); + } else { + + // Compute the flex basis and hypothetical main size (i.e. the clamped flex basis). + childWidth = CSS_UNDEFINED; + childHeight = CSS_UNDEFINED; + childWidthMeasureMode = CSS_MEASURE_MODE_UNDEFINED; + childHeightMeasureMode = CSS_MEASURE_MODE_UNDEFINED; + + if (isStyleDimDefined(child, CSS_FLEX_DIRECTION_ROW)) { + childWidth = child->style.dimensions[CSS_WIDTH] + getMarginAxis(child, CSS_FLEX_DIRECTION_ROW); + childWidthMeasureMode = CSS_MEASURE_MODE_EXACTLY; + } + if (isStyleDimDefined(child, CSS_FLEX_DIRECTION_COLUMN)) { + childHeight = child->style.dimensions[CSS_HEIGHT] + getMarginAxis(child, CSS_FLEX_DIRECTION_COLUMN); + childHeightMeasureMode = CSS_MEASURE_MODE_EXACTLY; + } + + // According to the spec, if the main size is not definite and the + // child's inline axis is parallel to the main axis (i.e. it's + // horizontal), the child should be sized using "UNDEFINED" in + // the main size. Otherwise use "AT_MOST" in the cross axis. + if (!isMainAxisRow && isUndefined(childWidth) && !isUndefined(availableInnerWidth)) { + childWidth = availableInnerWidth; + childWidthMeasureMode = CSS_MEASURE_MODE_AT_MOST; + } + + // The W3C spec doesn't say anything about the 'overflow' property, + // but all major browsers appear to implement the following logic. + if (node->style.overflow == CSS_OVERFLOW_HIDDEN) { + if (isMainAxisRow && isUndefined(childHeight) && !isUndefined(availableInnerHeight)) { + childHeight = availableInnerHeight; + childHeightMeasureMode = CSS_MEASURE_MODE_AT_MOST; + } + } + + // Measure the child + layoutNodeInternal(child, childWidth, childHeight, direction, childWidthMeasureMode, childHeightMeasureMode, false, "measure"); + + child->layout.flex_basis = fmaxf(isMainAxisRow ? child->layout.measured_dimensions[CSS_WIDTH] : child->layout.measured_dimensions[CSS_HEIGHT], getPaddingAndBorderAxis(child, mainAxis)); + } + } + } + + // STEP 4: COLLECT FLEX ITEMS INTO FLEX LINES + + // Indexes of children that represent the first and last items in the line. + int startOfLineIndex = 0; + int endOfLineIndex = 0; + + // Number of lines. + int lineCount = 0; + + // Accumulated cross dimensions of all lines so far. + float totalLineCrossDim = 0; + + // Max main dimension of all the lines. + float maxLineMainDim = 0; + + while (endOfLineIndex < childCount) { + + // Number of items on the currently line. May be different than the difference + // between start and end indicates because we skip over absolute-positioned items. + int itemsOnLine = 0; + + // sizeConsumedOnCurrentLine is accumulation of the dimensions and margin + // of all the children on the current line. 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 sizeConsumedOnCurrentLine = 0; + + float totalFlexGrowFactors = 0; + float totalFlexShrinkScaledFactors = 0; + + i = startOfLineIndex; + + // Maintain a linked list of the child nodes that can shrink and/or grow. + css_node_t* firstRelativeChild = NULL; + css_node_t* currentRelativeChild = NULL; + + // Add items to the current line until it's full or we run out of items. + while (i < childCount) { + child = node->get_child(node->context, i); + child->line_index = lineCount; + + if (child->style.position_type != CSS_POSITION_ABSOLUTE) { + float outerFlexBasis = child->layout.flex_basis + getMarginAxis(child, mainAxis); + + // If this is a multi-line flow and this item pushes us over the available size, we've + // hit the end of the current line. Break out of the loop and lay out the current line. + if (sizeConsumedOnCurrentLine + outerFlexBasis > availableInnerMainDim && isNodeFlexWrap && itemsOnLine > 0) { + break; + } + + sizeConsumedOnCurrentLine += outerFlexBasis; + itemsOnLine++; + + if (isFlex(child)) { + totalFlexGrowFactors += getFlexGrowFactor(child); + + // Unlike the grow factor, the shrink factor is scaled relative to the child + // dimension. + totalFlexShrinkScaledFactors += getFlexShrinkFactor(child) * child->layout.flex_basis; + } + + // Store a private linked list of children that need to be layed out. + if (firstRelativeChild == NULL) { + firstRelativeChild = child; + } + if (currentRelativeChild != NULL) { + currentRelativeChild->next_child = child; + } + currentRelativeChild = child; + child->next_child = NULL; + } + + i++; + endOfLineIndex++; + } + + // If we don't need to measure the cross axis, we can skip the entire flex step. + bool canSkipFlex = !performLayout && measureModeCrossDim == CSS_MEASURE_MODE_EXACTLY; // In order to position the elements in the main axis, we have two // controls. The space between the beginning and the first element @@ -887,212 +964,300 @@ float leadingMainDim = 0; float betweenMainDim = 0; - // The remaining available space that needs to be allocated - float remainingMainDim = 0; - if (isMainDimDefined) { - remainingMainDim = definedMainDim - mainContentDim; - } else { - remainingMainDim = fmaxf(mainContentDim, 0) - mainContentDim; + // STEP 5: RESOLVING FLEXIBLE LENGTHS ON MAIN AXIS + // Calculate the remaining available space that needs to be allocated. + // If the main dimension size isn't known, it is computed based on + // the line length, so there's no more space left to distribute. + float remainingFreeSpace = 0; + if (!isUndefined(availableInnerMainDim)) { + remainingFreeSpace = availableInnerMainDim - sizeConsumedOnCurrentLine; + } else if (sizeConsumedOnCurrentLine < 0) { + // availableInnerMainDim is indefinite which means the node is being sized based on its content. + // sizeConsumedOnCurrentLine is negative which means the node will allocate 0 pixels for + // its content. Consequently, remainingFreeSpace is 0 - sizeConsumedOnCurrentLine. + remainingFreeSpace = -sizeConsumedOnCurrentLine; } - // If there are flexible children in the mix, they are going to fill the - // remaining space - if (flexibleChildrenCount != 0) { - float flexibleMainDim = remainingMainDim / totalFlexible; - float baseMainDim; - float boundMainDim; + float originalRemainingFreeSpace = remainingFreeSpace; + float deltaFreeSpace = 0; - // If the flex share of remaining space doesn't meet min/max bounds, - // remove this child from flex calculations. - currentFlexChild = firstFlexChild; - while (currentFlexChild != NULL) { - baseMainDim = flexibleMainDim * currentFlexChild->style.flex + - getPaddingAndBorderAxis(currentFlexChild, mainAxis); - boundMainDim = boundAxis(currentFlexChild, mainAxis, baseMainDim); + if (!canSkipFlex) { + float childFlexBasis; + float flexShrinkScaledFactor; + float flexGrowFactor; + float baseMainSize; + float boundMainSize; - if (baseMainDim != boundMainDim) { - remainingMainDim -= boundMainDim; - totalFlexible -= currentFlexChild->style.flex; + // Do two passes over the flex items to figure out how to distribute the remaining space. + // The first pass finds the items whose min/max constraints trigger, freezes them at those + // sizes, and excludes those sizes from the remaining space. The second pass sets the size + // of each flexible item. It distributes the remaining space amongst the items whose min/max + // constraints didn't trigger in pass 1. For the other items, it sets their sizes by forcing + // their min/max constraints to trigger again. + // + // This two pass approach for resolving min/max constraints deviates from the spec. The + // spec (https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths) describes a process + // that needs to be repeated a variable number of times. The algorithm implemented here + // won't handle all cases but it was simpler to implement and it mitigates performance + // concerns because we know exactly how many passes it'll do. + + // First pass: detect the flex items whose min/max constraints trigger + float deltaFlexShrinkScaledFactors = 0; + float deltaFlexGrowFactors = 0; + currentRelativeChild = firstRelativeChild; + while (currentRelativeChild != NULL) { + childFlexBasis = currentRelativeChild->layout.flex_basis; + + if (remainingFreeSpace < 0) { + flexShrinkScaledFactor = getFlexShrinkFactor(currentRelativeChild) * childFlexBasis; + + // Is this child able to shrink? + if (flexShrinkScaledFactor != 0) { + baseMainSize = childFlexBasis + + remainingFreeSpace / totalFlexShrinkScaledFactors * flexShrinkScaledFactor; + boundMainSize = boundAxis(currentRelativeChild, mainAxis, baseMainSize); + if (baseMainSize != boundMainSize) { + // By excluding this item's size and flex factor from remaining, this item's + // min/max constraints should also trigger in the second pass resulting in the + // item's size calculation being identical in the first and second passes. + deltaFreeSpace -= boundMainSize - childFlexBasis; + deltaFlexShrinkScaledFactors -= flexShrinkScaledFactor; + } + } + } else if (remainingFreeSpace > 0) { + flexGrowFactor = getFlexGrowFactor(currentRelativeChild); + + // Is this child able to grow? + if (flexGrowFactor != 0) { + baseMainSize = childFlexBasis + + remainingFreeSpace / totalFlexGrowFactors * flexGrowFactor; + boundMainSize = boundAxis(currentRelativeChild, mainAxis, baseMainSize); + if (baseMainSize != boundMainSize) { + // By excluding this item's size and flex factor from remaining, this item's + // min/max constraints should also trigger in the second pass resulting in the + // item's size calculation being identical in the first and second passes. + deltaFreeSpace -= boundMainSize - childFlexBasis; + deltaFlexGrowFactors -= flexGrowFactor; + } + } } - currentFlexChild = currentFlexChild->next_flex_child; - } - 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; + currentRelativeChild = currentRelativeChild->next_child; } - currentFlexChild = firstFlexChild; - while (currentFlexChild != NULL) { - // At this point we know the final size of the element in the main - // dimension - currentFlexChild->layout.dimensions[dim[mainAxis]] = boundAxis(currentFlexChild, mainAxis, - flexibleMainDim * currentFlexChild->style.flex + - getPaddingAndBorderAxis(currentFlexChild, mainAxis) - ); + totalFlexShrinkScaledFactors += deltaFlexShrinkScaledFactors; + totalFlexGrowFactors += deltaFlexGrowFactors; + remainingFreeSpace += deltaFreeSpace; - maxWidth = CSS_UNDEFINED; - if (isLayoutDimDefined(node, resolvedRowAxis)) { - maxWidth = node->layout.dimensions[dim[resolvedRowAxis]] - - paddingAndBorderAxisResolvedRow; - } else if (!isMainRowDirection) { - maxWidth = parentMaxWidth - - getMarginAxis(node, resolvedRowAxis) - - paddingAndBorderAxisResolvedRow; - } - maxHeight = CSS_UNDEFINED; - if (isLayoutDimDefined(node, CSS_FLEX_DIRECTION_COLUMN)) { - maxHeight = node->layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] - - paddingAndBorderAxisColumn; - } else if (isMainRowDirection) { - maxHeight = parentMaxHeight - - getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN) - - paddingAndBorderAxisColumn; + // Second pass: resolve the sizes of the flexible items + deltaFreeSpace = 0; + currentRelativeChild = firstRelativeChild; + while (currentRelativeChild != NULL) { + childFlexBasis = currentRelativeChild->layout.flex_basis; + float updatedMainSize = childFlexBasis; + + if (remainingFreeSpace < 0) { + flexShrinkScaledFactor = getFlexShrinkFactor(currentRelativeChild) * childFlexBasis; + + // Is this child able to shrink? + if (flexShrinkScaledFactor != 0) { + updatedMainSize = boundAxis(currentRelativeChild, mainAxis, childFlexBasis + + remainingFreeSpace / totalFlexShrinkScaledFactors * flexShrinkScaledFactor); + } + } else if (remainingFreeSpace > 0) { + flexGrowFactor = getFlexGrowFactor(currentRelativeChild); + + // Is this child able to grow? + if (flexGrowFactor != 0) { + updatedMainSize = boundAxis(currentRelativeChild, mainAxis, childFlexBasis + + remainingFreeSpace / totalFlexGrowFactors * flexGrowFactor); + } } - // And we recursively call the layout algorithm for this child - layoutNode(currentFlexChild, maxWidth, maxHeight, direction); + deltaFreeSpace -= updatedMainSize - childFlexBasis; - child = currentFlexChild; - currentFlexChild = currentFlexChild->next_flex_child; - child->next_flex_child = NULL; + if (isMainAxisRow) { + childWidth = updatedMainSize + getMarginAxis(currentRelativeChild, CSS_FLEX_DIRECTION_ROW); + childWidthMeasureMode = CSS_MEASURE_MODE_EXACTLY; + + if (!isStyleDimDefined(currentRelativeChild, CSS_FLEX_DIRECTION_COLUMN)) { + childHeight = availableInnerCrossDim; + childHeightMeasureMode = isUndefined(childHeight) ? CSS_MEASURE_MODE_UNDEFINED : CSS_MEASURE_MODE_AT_MOST; + } else { + childHeight = currentRelativeChild->style.dimensions[CSS_HEIGHT] + getMarginAxis(currentRelativeChild, CSS_FLEX_DIRECTION_COLUMN); + childHeightMeasureMode = CSS_MEASURE_MODE_EXACTLY; + } + } else { + childHeight = updatedMainSize + getMarginAxis(currentRelativeChild, CSS_FLEX_DIRECTION_COLUMN); + childHeightMeasureMode = CSS_MEASURE_MODE_EXACTLY; + + if (!isStyleDimDefined(currentRelativeChild, CSS_FLEX_DIRECTION_ROW)) { + childWidth = availableInnerCrossDim; + childWidthMeasureMode = isUndefined(childWidth) ? CSS_MEASURE_MODE_UNDEFINED : CSS_MEASURE_MODE_AT_MOST; + } else { + childWidth = currentRelativeChild->style.dimensions[CSS_WIDTH] + getMarginAxis(currentRelativeChild, CSS_FLEX_DIRECTION_ROW); + childWidthMeasureMode = CSS_MEASURE_MODE_EXACTLY; + } + } + + bool requiresStretchLayout = !isStyleDimDefined(currentRelativeChild, crossAxis) && + getAlignItem(node, currentRelativeChild) == CSS_ALIGN_STRETCH; + + // Recursively call the layout algorithm for this child with the updated main size. + layoutNodeInternal(currentRelativeChild, childWidth, childHeight, direction, childWidthMeasureMode, childHeightMeasureMode, performLayout && !requiresStretchLayout, "flex"); + + currentRelativeChild = currentRelativeChild->next_child; } + } - // We use justifyContent to figure out how to allocate the remaining - // space available - } else if (justifyContent != CSS_JUSTIFY_FLEX_START) { + remainingFreeSpace = originalRemainingFreeSpace + deltaFreeSpace; + + // STEP 6: MAIN-AXIS JUSTIFICATION & CROSS-AXIS SIZE DETERMINATION + + // At this point, all the children have their dimensions set in the main axis. + // Their dimensions are also set in the cross axis with the exception of items + // that are aligned "stretch". We need to compute these stretch values and + // set the final positions. + + // If we are using "at most" rules in the main axis, we won't distribute + // any remaining space at this point. + if (measureModeMainDim == CSS_MEASURE_MODE_AT_MOST) { + remainingFreeSpace = 0; + } + + // Use justifyContent to figure out how to allocate the remaining space + // available in the main axis. + if (justifyContent != CSS_JUSTIFY_FLEX_START) { if (justifyContent == CSS_JUSTIFY_CENTER) { - leadingMainDim = remainingMainDim / 2; + leadingMainDim = remainingFreeSpace / 2; } else if (justifyContent == CSS_JUSTIFY_FLEX_END) { - leadingMainDim = remainingMainDim; + leadingMainDim = remainingFreeSpace; } else if (justifyContent == CSS_JUSTIFY_SPACE_BETWEEN) { - remainingMainDim = fmaxf(remainingMainDim, 0); - if (flexibleChildrenCount + nonFlexibleChildrenCount - 1 != 0) { - betweenMainDim = remainingMainDim / - (flexibleChildrenCount + nonFlexibleChildrenCount - 1); + remainingFreeSpace = fmaxf(remainingFreeSpace, 0); + if (itemsOnLine > 1) { + betweenMainDim = remainingFreeSpace / (itemsOnLine - 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); + betweenMainDim = remainingFreeSpace / itemsOnLine; leadingMainDim = betweenMainDim / 2; } } - // Position elements in the main axis and compute dimensions + float mainDim = leadingPaddingAndBorderMain + leadingMainDim; + float crossDim = 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! - mainDim += leadingMainDim; - - for (i = firstComplexMain; i < endLine; ++i) { + for (i = startOfLineIndex; i < endOfLineIndex; ++i) { child = node->get_child(node->context, i); if (child->style.position_type == 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]) + - getLeadingBorder(node, mainAxis) + - getLeadingMargin(child, mainAxis); + if (performLayout) { + // 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]) + + getLeadingBorder(node, mainAxis) + + getLeadingMargin(child, 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; - - // Define the trailing position accordingly. - if (isMainDimDefined) { - setTrailingPosition(node, child, mainAxis); + if (performLayout) { + // 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 + // Now that we placed the element, we need to update the variables. + // We need to do that only for relative elements. Absolute elements // do not take part in that phase. if (child->style.position_type == 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, boundAxis(child, crossAxis, getDimWithMargin(child, crossAxis))); + if (canSkipFlex) { + // If we skipped the flex step, then we can't rely on the measuredDims because + // they weren't computed. This means we can't call getDimWithMargin. + mainDim += betweenMainDim + getMarginAxis(child, mainAxis) + child->layout.flex_basis; + crossDim = availableInnerCrossDim; + } else { + // 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 containerCrossAxis = node->layout.dimensions[dim[crossAxis]]; - if (!isCrossDimDefined) { - 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 - boundAxis(node, crossAxis, crossDim + paddingAndBorderAxisCross), - paddingAndBorderAxisCross - ); + mainDim += trailingPaddingAndBorderMain; + + float containerCrossAxis = availableInnerCrossDim; + if (measureModeCrossDim == CSS_MEASURE_MODE_UNDEFINED || measureModeCrossDim == CSS_MEASURE_MODE_AT_MOST) { + // Compute the cross axis from the max cross dimension of the children. + containerCrossAxis = boundAxis(node, crossAxis, crossDim + paddingAndBorderAxisCross) - paddingAndBorderAxisCross; + + if (measureModeCrossDim == CSS_MEASURE_MODE_AT_MOST) { + containerCrossAxis = fminf(containerCrossAxis, availableInnerCrossDim); + } } - // Position elements in the cross axis - for (i = firstComplexCross; i < endLine; ++i) { - child = node->get_child(node->context, i); + // If there's no flex wrap, the cross dimension is defined by the container. + if (!isNodeFlexWrap && measureModeCrossDim == CSS_MEASURE_MODE_EXACTLY) { + crossDim = availableInnerCrossDim; + } - if (child->style.position_type == 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]) + - getLeadingBorder(node, crossAxis) + - getLeadingMargin(child, crossAxis); + // Clamp to the min/max size specified on the container. + crossDim = boundAxis(node, crossAxis, crossDim + paddingAndBorderAxisCross) - paddingAndBorderAxisCross; - } else { - float leadingCrossDim = leadingPaddingAndBorderCross; + // STEP 7: CROSS-AXIS ALIGNMENT + // We can skip child alignment if we're just measuring the container. + if (performLayout) { + for (i = startOfLineIndex; i < endOfLineIndex; ++i) { + child = node->get_child(node->context, i); - // For a relative children, we're either using alignItems (parent) or - // alignSelf (child) in order to determine the position in the cross axis - if (child->style.position_type == CSS_POSITION_RELATIVE) { - /*eslint-disable */ - // This variable is intentionally re-defined as the code is transpiled to a block scope language + if (child->style.position_type == CSS_POSITION_ABSOLUTE) { + // If the child is absolutely positioned and has a top/left/bottom/right + // set, override all the previously computed positions to set it correctly. + if (isPosDefined(child, leading[crossAxis])) { + child->layout.position[pos[crossAxis]] = getPosition(child, leading[crossAxis]) + + getLeadingBorder(node, crossAxis) + + getLeadingMargin(child, crossAxis); + } else { + child->layout.position[pos[crossAxis]] = leadingPaddingAndBorderCross + + getLeadingMargin(child, crossAxis); + } + } else { + float leadingCrossDim = leadingPaddingAndBorderCross; + + // For a relative children, we're either using alignItems (parent) or + // alignSelf (child) in order to determine the position in the cross axis css_align_t alignItem = getAlignItem(node, child); - /*eslint-enable */ + + // If the child uses align stretch, we need to lay it out one more time, this time + // forcing the cross-axis size to be the computed cross size for the current line. if (alignItem == CSS_ALIGN_STRETCH) { - // You can only stretch if the dimension has not already been defined - // previously. - if (!isStyleDimDefined(child, crossAxis)) { - float dimCrossAxis = child->layout.dimensions[dim[crossAxis]]; - child->layout.dimensions[dim[crossAxis]] = fmaxf( - boundAxis(child, crossAxis, containerCrossAxis - - paddingAndBorderAxisCross - getMarginAxis(child, crossAxis)), - // You never want to go smaller than padding - getPaddingAndBorderAxis(child, crossAxis) - ); + childWidth = child->layout.measured_dimensions[CSS_WIDTH] + getMarginAxis(child, CSS_FLEX_DIRECTION_ROW); + childHeight = child->layout.measured_dimensions[CSS_HEIGHT] + getMarginAxis(child, CSS_FLEX_DIRECTION_COLUMN); + bool isCrossSizeDefinite = false; - // If the size has changed, and this child has children we need to re-layout this child - if (dimCrossAxis != child->layout.dimensions[dim[crossAxis]] && child->children_count > 0) { - // Reset child margins before re-layout as they are added back in layoutNode and would be doubled - child->layout.position[leading[mainAxis]] -= getLeadingMargin(child, mainAxis) + - getRelativePosition(child, mainAxis); - child->layout.position[trailing[mainAxis]] -= getTrailingMargin(child, mainAxis) + - getRelativePosition(child, mainAxis); - child->layout.position[leading[crossAxis]] -= getLeadingMargin(child, crossAxis) + - getRelativePosition(child, crossAxis); - child->layout.position[trailing[crossAxis]] -= getTrailingMargin(child, crossAxis) + - getRelativePosition(child, crossAxis); + if (isMainAxisRow) { + isCrossSizeDefinite = isStyleDimDefined(child, CSS_FLEX_DIRECTION_COLUMN); + childHeight = crossDim; + } else { + isCrossSizeDefinite = isStyleDimDefined(child, CSS_FLEX_DIRECTION_ROW); + childWidth = crossDim; + } - layoutNode(child, maxWidth, maxHeight, direction); - } + // If the child defines a definite size for its cross axis, there's no need to stretch. + if (!isCrossSizeDefinite) { + childWidthMeasureMode = isUndefined(childWidth) ? CSS_MEASURE_MODE_UNDEFINED : CSS_MEASURE_MODE_EXACTLY; + childHeightMeasureMode = isUndefined(childHeight) ? CSS_MEASURE_MODE_UNDEFINED : CSS_MEASURE_MODE_EXACTLY; + layoutNodeInternal(child, childWidth, childHeight, direction, childWidthMeasureMode, childHeightMeasureMode, true, "stretch"); } } else if (alignItem != CSS_ALIGN_FLEX_START) { - // The remaining space between the parent dimensions+padding and child - // dimensions+margin. - float remainingCrossDim = containerCrossAxis - - paddingAndBorderAxisCross - getDimWithMargin(child, crossAxis); + float remainingCrossDim = containerCrossAxis - getDimWithMargin(child, crossAxis); if (alignItem == CSS_ALIGN_CENTER) { leadingCrossDim += remainingCrossDim / 2; @@ -1100,41 +1265,25 @@ leadingCrossDim += remainingCrossDim; } } - } - // And we apply the position - child->layout.position[pos[crossAxis]] += linesCrossDim + leadingCrossDim; - - // Define the trailing position accordingly. - if (isCrossDimDefined) { - setTrailingPosition(node, child, crossAxis); + // And we apply the position + child->layout.position[pos[crossAxis]] += totalLineCrossDim + leadingCrossDim; } } } - linesCrossDim += crossDim; - linesMainDim = fmaxf(linesMainDim, mainDim); - linesCount += 1; - startLine = endLine; + totalLineCrossDim += crossDim; + maxLineMainDim = fmaxf(maxLineMainDim, mainDim); + + // Reset variables for new line. + lineCount++; + startOfLineIndex = endOfLineIndex; + endOfLineIndex = startOfLineIndex; } - // - // - // Note(prenaux): 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 && isCrossDimDefined) { - float nodeCrossAxisInnerSize = node->layout.dimensions[dim[crossAxis]] - - paddingAndBorderAxisCross; - float remainingAlignContentDim = nodeCrossAxisInnerSize - linesCrossDim; + // STEP 8: MULTI-LINE CONTENT ALIGNMENT + if (lineCount > 1 && performLayout && !isUndefined(availableInnerCrossDim)) { + float remainingAlignContentDim = availableInnerCrossDim - totalLineCrossDim; float crossDimLead = 0; float currentLead = leadingPaddingAndBorderCross; @@ -1145,19 +1294,20 @@ } else if (alignContent == CSS_ALIGN_CENTER) { currentLead += remainingAlignContentDim / 2; } else if (alignContent == CSS_ALIGN_STRETCH) { - if (nodeCrossAxisInnerSize > linesCrossDim) { - crossDimLead = (remainingAlignContentDim / linesCount); + if (availableInnerCrossDim > totalLineCrossDim) { + crossDimLead = (remainingAlignContentDim / lineCount); } } int endIndex = 0; - for (i = 0; i < linesCount; ++i) { + for (i = 0; i < lineCount; ++i) { int startIndex = endIndex; + int j; // compute the line's height and find the endIndex float lineHeight = 0; - for (ii = startIndex; ii < childCount; ++ii) { - child = node->get_child(node->context, ii); + for (j = startIndex; j < childCount; ++j) { + child = node->get_child(node->context, j); if (child->style.position_type != CSS_POSITION_RELATIVE) { continue; } @@ -1165,33 +1315,33 @@ break; } if (isLayoutDimDefined(child, crossAxis)) { - lineHeight = fmaxf( - lineHeight, - child->layout.dimensions[dim[crossAxis]] + getMarginAxis(child, crossAxis) - ); + lineHeight = fmaxf(lineHeight, + child->layout.measured_dimensions[dim[crossAxis]] + getMarginAxis(child, crossAxis)); } } - endIndex = ii; + endIndex = j; lineHeight += crossDimLead; - for (ii = startIndex; ii < endIndex; ++ii) { - child = node->get_child(node->context, ii); - if (child->style.position_type != CSS_POSITION_RELATIVE) { - continue; - } + if (performLayout) { + for (j = startIndex; j < endIndex; ++j) { + child = node->get_child(node->context, j); + if (child->style.position_type != CSS_POSITION_RELATIVE) { + continue; + } - css_align_t alignContentAlignItem = getAlignItem(node, child); - if (alignContentAlignItem == CSS_ALIGN_FLEX_START) { - child->layout.position[pos[crossAxis]] = currentLead + getLeadingMargin(child, crossAxis); - } else if (alignContentAlignItem == CSS_ALIGN_FLEX_END) { - child->layout.position[pos[crossAxis]] = currentLead + lineHeight - getTrailingMargin(child, crossAxis) - child->layout.dimensions[dim[crossAxis]]; - } else if (alignContentAlignItem == CSS_ALIGN_CENTER) { - float childHeight = child->layout.dimensions[dim[crossAxis]]; - child->layout.position[pos[crossAxis]] = currentLead + (lineHeight - childHeight) / 2; - } else if (alignContentAlignItem == CSS_ALIGN_STRETCH) { - child->layout.position[pos[crossAxis]] = currentLead + getLeadingMargin(child, crossAxis); - // TODO(prenaux): Correctly set the height of items with undefined - // (auto) crossAxis dimension. + css_align_t alignContentAlignItem = getAlignItem(node, child); + if (alignContentAlignItem == CSS_ALIGN_FLEX_START) { + child->layout.position[pos[crossAxis]] = currentLead + getLeadingMargin(child, crossAxis); + } else if (alignContentAlignItem == CSS_ALIGN_FLEX_END) { + child->layout.position[pos[crossAxis]] = currentLead + lineHeight - getTrailingMargin(child, crossAxis) - child->layout.measured_dimensions[dim[crossAxis]]; + } else if (alignContentAlignItem == CSS_ALIGN_CENTER) { + childHeight = child->layout.measured_dimensions[dim[crossAxis]]; + child->layout.position[pos[crossAxis]] = currentLead + (lineHeight - childHeight) / 2; + } else if (alignContentAlignItem == CSS_ALIGN_STRETCH) { + child->layout.position[pos[crossAxis]] = currentLead + getLeadingMargin(child, crossAxis); + // TODO(prenaux): Correctly set the height of items with indefinite + // (auto) crossAxis dimension. + } } } @@ -1199,137 +1349,394 @@ } } - bool needsMainTrailingPos = false; - bool needsCrossTrailingPos = false; + // STEP 9: COMPUTING FINAL DIMENSIONS + node->layout.measured_dimensions[CSS_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, availableWidth - marginAxisRow); + node->layout.measured_dimensions[CSS_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, availableHeight - marginAxisColumn); - // 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 (!isMainDimDefined) { - node->layout.dimensions[dim[mainAxis]] = fmaxf( - // We're missing the last padding at this point to get the final - // dimension - boundAxis(node, mainAxis, linesMainDim + getTrailingPaddingAndBorder(node, mainAxis)), - // We can never assign a width smaller than the padding and borders - paddingAndBorderAxisMain - ); + // If the user didn't specify a width or height for the node, set the + // dimensions based on the children. + if (measureModeMainDim == CSS_MEASURE_MODE_UNDEFINED) { + // Clamp the size to the min/max size, if specified, and make sure it + // doesn't go below the padding and border amount. + node->layout.measured_dimensions[dim[mainAxis]] = boundAxis(node, mainAxis, maxLineMainDim); + } else if (measureModeMainDim == CSS_MEASURE_MODE_AT_MOST) { + node->layout.measured_dimensions[dim[mainAxis]] = fmaxf( + fminf(availableInnerMainDim + paddingAndBorderAxisMain, + boundAxisWithinMinAndMax(node, mainAxis, maxLineMainDim)), + paddingAndBorderAxisMain); + } + + if (measureModeCrossDim == CSS_MEASURE_MODE_UNDEFINED) { + // Clamp the size to the min/max size, if specified, and make sure it + // doesn't go below the padding and border amount. + node->layout.measured_dimensions[dim[crossAxis]] = boundAxis(node, crossAxis, totalLineCrossDim + paddingAndBorderAxisCross); + } else if (measureModeCrossDim == CSS_MEASURE_MODE_AT_MOST) { + node->layout.measured_dimensions[dim[crossAxis]] = fmaxf( + fminf(availableInnerCrossDim + paddingAndBorderAxisCross, + boundAxisWithinMinAndMax(node, crossAxis, totalLineCrossDim + paddingAndBorderAxisCross)), + paddingAndBorderAxisCross); + } + + // STEP 10: SETTING TRAILING POSITIONS FOR CHILDREN + if (performLayout) { + bool needsMainTrailingPos = false; + bool needsCrossTrailingPos = false; if (mainAxis == CSS_FLEX_DIRECTION_ROW_REVERSE || mainAxis == CSS_FLEX_DIRECTION_COLUMN_REVERSE) { needsMainTrailingPos = true; } - } - - if (!isCrossDimDefined) { - node->layout.dimensions[dim[crossAxis]] = 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 - boundAxis(node, crossAxis, linesCrossDim + paddingAndBorderAxisCross), - paddingAndBorderAxisCross - ); if (crossAxis == CSS_FLEX_DIRECTION_ROW_REVERSE || crossAxis == CSS_FLEX_DIRECTION_COLUMN_REVERSE) { needsCrossTrailingPos = true; } - } - // Set trailing position if necessary - if (needsMainTrailingPos || needsCrossTrailingPos) { - for (i = 0; i < childCount; ++i) { - child = node->get_child(node->context, i); + // Set trailing position if necessary. + if (needsMainTrailingPos || needsCrossTrailingPos) { + for (i = 0; i < childCount; ++i) { + child = node->get_child(node->context, i); - if (needsMainTrailingPos) { - setTrailingPosition(node, child, mainAxis); - } + if (needsMainTrailingPos) { + setTrailingPosition(node, child, mainAxis); + } - if (needsCrossTrailingPos) { - setTrailingPosition(node, child, crossAxis); + if (needsCrossTrailingPos) { + setTrailingPosition(node, child, crossAxis); + } } } } - // Calculate dimensions for absolutely positioned elements + // STEP 11: SIZING AND POSITIONING ABSOLUTE CHILDREN currentAbsoluteChild = firstAbsoluteChild; while (currentAbsoluteChild != NULL) { - // 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 (ii = 0; ii < 2; ii++) { - axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; + // Now that we know the bounds of the container, perform layout again on the + // absolutely-positioned children. + if (performLayout) { - if (isLayoutDimDefined(node, axis) && - !isStyleDimDefined(currentAbsoluteChild, axis) && - isPosDefined(currentAbsoluteChild, leading[axis]) && - isPosDefined(currentAbsoluteChild, trailing[axis])) { - currentAbsoluteChild->layout.dimensions[dim[axis]] = fmaxf( - boundAxis(currentAbsoluteChild, axis, node->layout.dimensions[dim[axis]] - - getBorderAxis(node, axis) - - getMarginAxis(currentAbsoluteChild, axis) - - getPosition(currentAbsoluteChild, leading[axis]) - - getPosition(currentAbsoluteChild, trailing[axis]) - ), - // You never want to go smaller than padding - getPaddingAndBorderAxis(currentAbsoluteChild, axis) - ); + childWidth = CSS_UNDEFINED; + childHeight = CSS_UNDEFINED; + + if (isStyleDimDefined(currentAbsoluteChild, CSS_FLEX_DIRECTION_ROW)) { + childWidth = currentAbsoluteChild->style.dimensions[CSS_WIDTH] + getMarginAxis(currentAbsoluteChild, CSS_FLEX_DIRECTION_ROW); + } else { + // If the child doesn't have a specified width, compute the width based on the left/right offsets if they're defined. + if (isPosDefined(currentAbsoluteChild, CSS_LEFT) && isPosDefined(currentAbsoluteChild, CSS_RIGHT)) { + childWidth = node->layout.measured_dimensions[CSS_WIDTH] - + (getLeadingBorder(node, CSS_FLEX_DIRECTION_ROW) + getTrailingBorder(node, CSS_FLEX_DIRECTION_ROW)) - + (currentAbsoluteChild->style.position[CSS_LEFT] + currentAbsoluteChild->style.position[CSS_RIGHT]); + childWidth = boundAxis(currentAbsoluteChild, CSS_FLEX_DIRECTION_ROW, childWidth); + } } - if (isPosDefined(currentAbsoluteChild, trailing[axis]) && - !isPosDefined(currentAbsoluteChild, leading[axis])) { - currentAbsoluteChild->layout.position[leading[axis]] = - node->layout.dimensions[dim[axis]] - - currentAbsoluteChild->layout.dimensions[dim[axis]] - - getPosition(currentAbsoluteChild, trailing[axis]); + if (isStyleDimDefined(currentAbsoluteChild, CSS_FLEX_DIRECTION_COLUMN)) { + childHeight = currentAbsoluteChild->style.dimensions[CSS_HEIGHT] + getMarginAxis(currentAbsoluteChild, CSS_FLEX_DIRECTION_COLUMN); + } else { + // If the child doesn't have a specified height, compute the height based on the top/bottom offsets if they're defined. + if (isPosDefined(currentAbsoluteChild, CSS_TOP) && isPosDefined(currentAbsoluteChild, CSS_BOTTOM)) { + childHeight = node->layout.measured_dimensions[CSS_HEIGHT] - + (getLeadingBorder(node, CSS_FLEX_DIRECTION_COLUMN) + getTrailingBorder(node, CSS_FLEX_DIRECTION_COLUMN)) - + (currentAbsoluteChild->style.position[CSS_TOP] + currentAbsoluteChild->style.position[CSS_BOTTOM]); + childHeight = boundAxis(currentAbsoluteChild, CSS_FLEX_DIRECTION_COLUMN, childHeight); + } + } + + // If we're still missing one or the other dimension, measure the content. + if (isUndefined(childWidth) || isUndefined(childHeight)) { + childWidthMeasureMode = isUndefined(childWidth) ? CSS_MEASURE_MODE_UNDEFINED : CSS_MEASURE_MODE_EXACTLY; + childHeightMeasureMode = isUndefined(childHeight) ? CSS_MEASURE_MODE_UNDEFINED : CSS_MEASURE_MODE_EXACTLY; + + // According to the spec, if the main size is not definite and the + // child's inline axis is parallel to the main axis (i.e. it's + // horizontal), the child should be sized using "UNDEFINED" in + // the main size. Otherwise use "AT_MOST" in the cross axis. + if (!isMainAxisRow && isUndefined(childWidth) && !isUndefined(availableInnerWidth)) { + childWidth = availableInnerWidth; + childWidthMeasureMode = CSS_MEASURE_MODE_AT_MOST; + } + + // The W3C spec doesn't say anything about the 'overflow' property, + // but all major browsers appear to implement the following logic. + if (node->style.overflow == CSS_OVERFLOW_HIDDEN) { + if (isMainAxisRow && isUndefined(childHeight) && !isUndefined(availableInnerHeight)) { + childHeight = availableInnerHeight; + childHeightMeasureMode = CSS_MEASURE_MODE_AT_MOST; + } + } + + layoutNodeInternal(currentAbsoluteChild, childWidth, childHeight, direction, childWidthMeasureMode, childHeightMeasureMode, false, "abs-measure"); + childWidth = currentAbsoluteChild->layout.measured_dimensions[CSS_WIDTH] + getMarginAxis(currentAbsoluteChild, CSS_FLEX_DIRECTION_ROW); + childHeight = currentAbsoluteChild->layout.measured_dimensions[CSS_HEIGHT] + getMarginAxis(currentAbsoluteChild, CSS_FLEX_DIRECTION_COLUMN); + } + + layoutNodeInternal(currentAbsoluteChild, childWidth, childHeight, direction, CSS_MEASURE_MODE_EXACTLY, CSS_MEASURE_MODE_EXACTLY, true, "abs-layout"); + + if (isPosDefined(currentAbsoluteChild, trailing[CSS_FLEX_DIRECTION_ROW]) && + !isPosDefined(currentAbsoluteChild, leading[CSS_FLEX_DIRECTION_ROW])) { + currentAbsoluteChild->layout.position[leading[CSS_FLEX_DIRECTION_ROW]] = + node->layout.measured_dimensions[dim[CSS_FLEX_DIRECTION_ROW]] - + currentAbsoluteChild->layout.measured_dimensions[dim[CSS_FLEX_DIRECTION_ROW]] - + getPosition(currentAbsoluteChild, trailing[CSS_FLEX_DIRECTION_ROW]); + } + + if (isPosDefined(currentAbsoluteChild, trailing[CSS_FLEX_DIRECTION_COLUMN]) && + !isPosDefined(currentAbsoluteChild, leading[CSS_FLEX_DIRECTION_COLUMN])) { + currentAbsoluteChild->layout.position[leading[CSS_FLEX_DIRECTION_COLUMN]] = + node->layout.measured_dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] - + currentAbsoluteChild->layout.measured_dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] - + getPosition(currentAbsoluteChild, trailing[CSS_FLEX_DIRECTION_COLUMN]); } } - child = currentAbsoluteChild; - currentAbsoluteChild = currentAbsoluteChild->next_absolute_child; - child->next_absolute_child = NULL; + currentAbsoluteChild = currentAbsoluteChild->next_child; } /** END_GENERATED **/ } - void layoutNode(css_node_t *node, float parentMaxWidth, float parentMaxHeight, css_direction_t parentDirection) { - css_layout_t *layout = &node->layout; - css_direction_t direction = node->style.direction; - layout->should_update = true; + int gDepth = 0; + bool gPrintTree = false; + bool gPrintChanges = false; + bool gPrintSkips = false; - bool skipLayout = - !node->is_dirty(node->context) && - eq(layout->last_requested_dimensions[CSS_WIDTH], layout->dimensions[CSS_WIDTH]) && - eq(layout->last_requested_dimensions[CSS_HEIGHT], layout->dimensions[CSS_HEIGHT]) && - eq(layout->last_parent_max_width, parentMaxWidth) && - eq(layout->last_parent_max_height, parentMaxHeight) && - eq(layout->last_direction, direction); + static const char* spacer = " "; - if (skipLayout) { - layout->dimensions[CSS_WIDTH] = layout->last_dimensions[CSS_WIDTH]; - layout->dimensions[CSS_HEIGHT] = layout->last_dimensions[CSS_HEIGHT]; - layout->position[CSS_TOP] = layout->last_position[CSS_TOP]; - layout->position[CSS_LEFT] = layout->last_position[CSS_LEFT]; + static const char* getSpacer(unsigned long level) { + unsigned long spacerLen = strlen(spacer); + if (level > spacerLen) { + level = spacerLen; + } + return &spacer[spacerLen - level]; + } + + static const char* getModeName(css_measure_mode_t mode, bool performLayout) { + const char* kMeasureModeNames[CSS_MEASURE_MODE_COUNT] = { + "UNDEFINED", + "EXACTLY", + "AT_MOST" + }; + const char* kLayoutModeNames[CSS_MEASURE_MODE_COUNT] = { + "LAY_UNDEFINED", + "LAY_EXACTLY", + "LAY_AT_MOST" + }; + + if (mode >= CSS_MEASURE_MODE_COUNT) { + return ""; + } + + return performLayout? kLayoutModeNames[mode] : kMeasureModeNames[mode]; + } + + static bool canUseCachedMeasurement(float availableWidth, float availableHeight, + float marginRow, float marginColumn, + css_measure_mode_t widthMeasureMode, css_measure_mode_t heightMeasureMode, + css_cached_measurement_t cachedLayout) { + + // Is it an exact match? + if (eq(cachedLayout.available_width, availableWidth) && + eq(cachedLayout.available_height, availableHeight) && + cachedLayout.width_measure_mode == widthMeasureMode && + cachedLayout.height_measure_mode == heightMeasureMode) { + return true; + } + + // If the width is an exact match, try a fuzzy match on the height. + if (cachedLayout.width_measure_mode == widthMeasureMode && + eq(cachedLayout.available_width, availableWidth) && + heightMeasureMode == CSS_MEASURE_MODE_EXACTLY && + eq(availableHeight - marginColumn, cachedLayout.computed_height)) { + return true; + } + + // If the height is an exact match, try a fuzzy match on the width. + if (cachedLayout.height_measure_mode == heightMeasureMode && + eq(cachedLayout.available_height, availableHeight) && + widthMeasureMode == CSS_MEASURE_MODE_EXACTLY && + eq(availableWidth - marginRow, cachedLayout.computed_width)) { + return true; + } + + return false; + } + + // + // This is a wrapper around the layoutNodeImpl function. It determines + // whether the layout request is redundant and can be skipped. + // + // Parameters: + // Input parameters are the same as layoutNodeImpl (see above) + // Return parameter is true if layout was performed, false if skipped + // + bool layoutNodeInternal(css_node_t* node, float availableWidth, float availableHeight, + css_direction_t parentDirection, css_measure_mode_t widthMeasureMode, css_measure_mode_t heightMeasureMode, bool performLayout, char* reason) { + css_layout_t* layout = &node->layout; + + gDepth++; + + bool needToVisitNode = (node->is_dirty(node->context) && layout->generation_count != gCurrentGenerationCount) || + layout->last_parent_direction != parentDirection; + + if (needToVisitNode) { + // Invalidate the cached results. + layout->next_cached_measurements_index = 0; + layout->cached_layout.width_measure_mode = (css_measure_mode_t)-1; + layout->cached_layout.height_measure_mode = (css_measure_mode_t)-1; + } + + css_cached_measurement_t* cachedResults = NULL; + + // Determine whether the results are already cached. We maintain a separate + // cache for layouts and measurements. A layout operation modifies the positions + // and dimensions for nodes in the subtree. The algorithm assumes that each node + // gets layed out a maximum of one time per tree layout, but multiple measurements + // may be required to resolve all of the flex dimensions. + // We handle nodes with measure functions specially here because they are the most + // expensive to measure, so it's worth avoiding redundant measurements if at all possible. + if (isMeasureDefined(node)) { + float marginAxisRow = getMarginAxis(node, CSS_FLEX_DIRECTION_ROW); + float marginAxisColumn = getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN); + + // First, try to use the layout cache. + if (canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn, + widthMeasureMode, heightMeasureMode, layout->cached_layout)) { + cachedResults = &layout->cached_layout; + } else { + // Try to use the measurement cache. + for (int i = 0; i < layout->next_cached_measurements_index; i++) { + if (canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn, + widthMeasureMode, heightMeasureMode, layout->cached_measurements[i])) { + cachedResults = &layout->cached_measurements[i]; + break; + } + } + } + } else if (performLayout) { + if (eq(layout->cached_layout.available_width, availableWidth) && + eq(layout->cached_layout.available_height, availableHeight) && + layout->cached_layout.width_measure_mode == widthMeasureMode && + layout->cached_layout.height_measure_mode == heightMeasureMode) { + + cachedResults = &layout->cached_layout; + } } else { - layout->last_requested_dimensions[CSS_WIDTH] = layout->dimensions[CSS_WIDTH]; - layout->last_requested_dimensions[CSS_HEIGHT] = layout->dimensions[CSS_HEIGHT]; - layout->last_parent_max_width = parentMaxWidth; - layout->last_parent_max_height = parentMaxHeight; - layout->last_direction = direction; + for (int i = 0; i < layout->next_cached_measurements_index; i++) { + if (eq(layout->cached_measurements[i].available_width, availableWidth) && + eq(layout->cached_measurements[i].available_height, availableHeight) && + layout->cached_measurements[i].width_measure_mode == widthMeasureMode && + layout->cached_measurements[i].height_measure_mode == heightMeasureMode) { - for (int i = 0, childCount = node->children_count; i < childCount; i++) { - resetNodeLayout(node->get_child(node->context, i)); + cachedResults = &layout->cached_measurements[i]; + break; + } + } + } + + if (!needToVisitNode && cachedResults != NULL) { + layout->measured_dimensions[CSS_WIDTH] = cachedResults->computed_width; + layout->measured_dimensions[CSS_HEIGHT] = cachedResults->computed_height; + + if (gPrintChanges && gPrintSkips) { + printf("%s%d.{[skipped] ", getSpacer(gDepth), gDepth); + if (node->print) { + node->print(node->context); + } + printf("wm: %s, hm: %s, aw: %f ah: %f => d: (%f, %f) %s\n", + getModeName(widthMeasureMode, performLayout), + getModeName(heightMeasureMode, performLayout), + availableWidth, availableHeight, + cachedResults->computed_width, cachedResults->computed_height, reason); + } + } else { + + if (gPrintChanges) { + printf("%s%d.{%s", getSpacer(gDepth), gDepth, needToVisitNode ? "*" : ""); + if (node->print) { + node->print(node->context); + } + printf("wm: %s, hm: %s, aw: %f ah: %f %s\n", + getModeName(widthMeasureMode, performLayout), + getModeName(heightMeasureMode, performLayout), + availableWidth, availableHeight, reason); } - layoutNodeImpl(node, parentMaxWidth, parentMaxHeight, parentDirection); + layoutNodeImpl(node, availableWidth, availableHeight, parentDirection, widthMeasureMode, heightMeasureMode, performLayout); - layout->last_dimensions[CSS_WIDTH] = layout->dimensions[CSS_WIDTH]; - layout->last_dimensions[CSS_HEIGHT] = layout->dimensions[CSS_HEIGHT]; - layout->last_position[CSS_TOP] = layout->position[CSS_TOP]; - layout->last_position[CSS_LEFT] = layout->position[CSS_LEFT]; + if (gPrintChanges) { + printf("%s%d.}%s", getSpacer(gDepth), gDepth, needToVisitNode ? "*" : ""); + if (node->print) { + node->print(node->context); + } + printf("wm: %s, hm: %s, d: (%f, %f) %s\n", + getModeName(widthMeasureMode, performLayout), + getModeName(heightMeasureMode, performLayout), + layout->measured_dimensions[CSS_WIDTH], layout->measured_dimensions[CSS_HEIGHT], reason); + } + + layout->last_parent_direction = parentDirection; + + if (cachedResults == NULL) { + if (layout->next_cached_measurements_index == CSS_MAX_CACHED_RESULT_COUNT) { + if (gPrintChanges) { + printf("Out of cache entries!\n"); + } + layout->next_cached_measurements_index = 0; + } + + css_cached_measurement_t* newCacheEntry; + if (performLayout) { + // Use the single layout cache entry. + newCacheEntry = &layout->cached_layout; + } else { + // Allocate a new measurement cache entry. + newCacheEntry = &layout->cached_measurements[layout->next_cached_measurements_index]; + layout->next_cached_measurements_index++; + } + + newCacheEntry->available_width = availableWidth; + newCacheEntry->available_height = availableHeight; + newCacheEntry->width_measure_mode = widthMeasureMode; + newCacheEntry->height_measure_mode = heightMeasureMode; + newCacheEntry->computed_width = layout->measured_dimensions[CSS_WIDTH]; + newCacheEntry->computed_height = layout->measured_dimensions[CSS_HEIGHT]; + } + } + + if (performLayout) { + node->layout.dimensions[CSS_WIDTH] = node->layout.measured_dimensions[CSS_WIDTH]; + node->layout.dimensions[CSS_HEIGHT] = node->layout.measured_dimensions[CSS_HEIGHT]; + layout->should_update = true; + } + + gDepth--; + layout->generation_count = gCurrentGenerationCount; + return (needToVisitNode || cachedResults == NULL); + } + + void layoutNode(css_node_t* node, float availableWidth, float availableHeight, css_direction_t parentDirection) { + // Increment the generation count. This will force the recursive routine to visit + // all dirty nodes at least once. Subsequent visits will be skipped if the input + // parameters don't change. + gCurrentGenerationCount++; + + // If the caller didn't specify a height/width, use the dimensions + // specified in the style. + if (isUndefined(availableWidth) && isStyleDimDefined(node, CSS_FLEX_DIRECTION_ROW)) { + availableWidth = node->style.dimensions[CSS_WIDTH] + getMarginAxis(node, CSS_FLEX_DIRECTION_ROW); + } + if (isUndefined(availableHeight) && isStyleDimDefined(node, CSS_FLEX_DIRECTION_COLUMN)) { + availableHeight = node->style.dimensions[CSS_HEIGHT] + getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN); + } + + css_measure_mode_t widthMeasureMode = isUndefined(availableWidth) ? CSS_MEASURE_MODE_UNDEFINED : CSS_MEASURE_MODE_EXACTLY; + css_measure_mode_t heightMeasureMode = isUndefined(availableHeight) ? CSS_MEASURE_MODE_UNDEFINED : CSS_MEASURE_MODE_EXACTLY; + + if (layoutNodeInternal(node, availableWidth, availableHeight, parentDirection, widthMeasureMode, heightMeasureMode, true, "initial")) { + + setPosition(node, node->layout.direction); + + if (gPrintTree) { + print_css_node(node, CSS_PRINT_LAYOUT | CSS_PRINT_CHILDREN | CSS_PRINT_STYLE); + } } } - - void resetNodeLayout(css_node_t *node) { - node->layout.dimensions[CSS_WIDTH] = CSS_UNDEFINED; - node->layout.dimensions[CSS_HEIGHT] = CSS_UNDEFINED; - node->layout.position[CSS_LEFT] = 0; - node->layout.position[CSS_TOP] = 0; - } diff --git a/React/Layout/Layout.h b/React/Layout/Layout.h index b2de9cea7..59d702e0d 100644 --- a/React/Layout/Layout.h +++ b/React/Layout/Layout.h @@ -56,6 +56,11 @@ CSS_JUSTIFY_SPACE_AROUND } css_justify_t; + typedef enum { + CSS_OVERFLOW_VISIBLE = 0, + CSS_OVERFLOW_HIDDEN + } css_overflow_t; + // Note: auto is only a valid value for alignSelf. It is NOT a valid value for // alignItems. typedef enum { @@ -91,7 +96,8 @@ typedef enum { CSS_MEASURE_MODE_UNDEFINED = 0, CSS_MEASURE_MODE_EXACTLY, - CSS_MEASURE_MODE_AT_MOST + CSS_MEASURE_MODE_AT_MOST, + CSS_MEASURE_MODE_COUNT } css_measure_mode_t; typedef enum { @@ -99,20 +105,40 @@ CSS_HEIGHT } css_dimension_t; + typedef struct { + float available_width; + float available_height; + css_measure_mode_t width_measure_mode; + css_measure_mode_t height_measure_mode; + + float computed_width; + float computed_height; + } css_cached_measurement_t; + + enum { + // This value was chosen based on empiracle data. Even the most complicated + // layouts should not require more than 16 entries to fit within the cache. + CSS_MAX_CACHED_RESULT_COUNT = 16 + }; + typedef struct { float position[4]; float dimensions[2]; css_direction_t direction; + float flex_basis; + // Instead of recomputing the entire layout every single time, we // cache some information to break early when nothing changed bool should_update; - float last_requested_dimensions[2]; - float last_parent_max_width; - float last_parent_max_height; - float last_dimensions[2]; - float last_position[2]; - css_direction_t last_direction; + int generation_count; + css_direction_t last_parent_direction; + + int next_cached_measurements_index; + css_cached_measurement_t cached_measurements[CSS_MAX_CACHED_RESULT_COUNT]; + float measured_dimensions[2]; + + css_cached_measurement_t cached_layout; } css_layout_t; typedef struct { @@ -128,6 +154,7 @@ css_align_t align_self; css_position_type_t position_type; css_wrap_type_t flex_wrap; + css_overflow_t overflow; float flex; float margin[6]; float position[4]; @@ -155,8 +182,7 @@ int children_count; int line_index; - css_node_t *next_absolute_child; - css_node_t *next_flex_child; + css_node_t* next_child; css_dim_t (*measure)(void *context, float width, css_measure_mode_t widthMode, float height, css_measure_mode_t heightMode); void (*print)(void *context); @@ -178,12 +204,8 @@ } css_print_options_t; void print_css_node(css_node_t *node, css_print_options_t options); + // Function that computes the layout! + void layoutNode(css_node_t *node, float availableWidth, float availableHeight, css_direction_t parentDirection); bool isUndefined(float value); - // Function that computes the layout! - void layoutNode(css_node_t *node, float maxWidth, float maxHeight, css_direction_t parentDirection); - - // Reset the calculated layout values for a given node. You should call this before `layoutNode`. - void resetNodeLayout(css_node_t *node); - #endif diff --git a/React/Views/RCTRootShadowView.m b/React/Views/RCTRootShadowView.m index 70f62be17..b23914d25 100644 --- a/React/Views/RCTRootShadowView.m +++ b/React/Views/RCTRootShadowView.m @@ -34,7 +34,6 @@ [self applySizeConstraints]; [self fillCSSNode:self.cssNode]; - resetNodeLayout(self.cssNode); layoutNode(self.cssNode, CSS_UNDEFINED, CSS_UNDEFINED, CSS_DIRECTION_INHERIT); NSMutableSet *viewsWithNewFrame = [NSMutableSet set]; diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSCachedMeasurement.java b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSCachedMeasurement.java new file mode 100644 index 000000000..11b188293 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSCachedMeasurement.java @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2014-present, 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. + */ + +// NOTE: this file is auto-copied from https://github.com/facebook/css-layout +// @generated SignedSource<> + +package com.facebook.csslayout; + +public class CSSCachedMeasurement { + public float availableWidth; + public float availableHeight; + public CSSMeasureMode widthMeasureMode = null; + public CSSMeasureMode heightMeasureMode = null; + + public float computedWidth; + public float computedHeight; +} diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSLayout.java b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSLayout.java index 165c5f96d..60f33fe5b 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSLayout.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSLayout.java @@ -7,7 +7,7 @@ */ // NOTE: this file is auto-copied from https://github.com/facebook/css-layout -// @generated SignedSource<<1e547b3af02a275fe73089e5a0a172c5>> +// @generated SignedSource<<4ed6ae4c11cdd41f5db380e3b9a06f23>> package com.facebook.csslayout; @@ -17,6 +17,10 @@ import java.util.Arrays; * Where the output of {@link LayoutEngine#layoutNode(CSSNode, float)} will go in the CSSNode. */ public class CSSLayout { + // This value was chosen based on empiracle data. Even the most complicated + // layouts should not require more than 16 entries to fit within the cache. + public static final int MAX_CACHED_RESULT_COUNT = 16; + public static final int POSITION_LEFT = 0; public static final int POSITION_TOP = 1; public static final int POSITION_RIGHT = 2; @@ -28,24 +32,38 @@ public class CSSLayout { public float[] position = new float[4]; public float[] dimensions = new float[2]; public CSSDirection direction = CSSDirection.LTR; - - /** - * This should always get called before calling {@link LayoutEngine#layoutNode(CSSNode, float)} - */ + + public float flexBasis; + + public int generationCount; + public CSSDirection lastParentDirection; + + public int nextCachedMeasurementsIndex; + public CSSCachedMeasurement[] cachedMeasurements = new CSSCachedMeasurement[MAX_CACHED_RESULT_COUNT]; + public float[] measuredDimensions = new float[2]; + + public CSSCachedMeasurement cachedLayout = new CSSCachedMeasurement(); + + CSSLayout() { + resetResult(); + } + public void resetResult() { Arrays.fill(position, 0); Arrays.fill(dimensions, CSSConstants.UNDEFINED); direction = CSSDirection.LTR; - } - - public void copy(CSSLayout layout) { - position[POSITION_LEFT] = layout.position[POSITION_LEFT]; - position[POSITION_TOP] = layout.position[POSITION_TOP]; - position[POSITION_RIGHT] = layout.position[POSITION_RIGHT]; - position[POSITION_BOTTOM] = layout.position[POSITION_BOTTOM]; - dimensions[DIMENSION_WIDTH] = layout.dimensions[DIMENSION_WIDTH]; - dimensions[DIMENSION_HEIGHT] = layout.dimensions[DIMENSION_HEIGHT]; - direction = layout.direction; + + flexBasis = 0; + + generationCount = 0; + lastParentDirection = null; + + nextCachedMeasurementsIndex = 0; + measuredDimensions[DIMENSION_WIDTH] = CSSConstants.UNDEFINED; + measuredDimensions[DIMENSION_HEIGHT] = CSSConstants.UNDEFINED; + + cachedLayout.widthMeasureMode = null; + cachedLayout.heightMeasureMode = null; } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSLayoutContext.java b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSLayoutContext.java index 574c0bdf5..a6562ff2b 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSLayoutContext.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSLayoutContext.java @@ -7,7 +7,7 @@ */ // NOTE: this file is auto-copied from https://github.com/facebook/css-layout -// @generated SignedSource<<9d48f3d4330e7b6cba0fff7d8f1e8b0c>> +// @generated SignedSource<> package com.facebook.csslayout; @@ -20,4 +20,5 @@ package com.facebook.csslayout; */ public class CSSLayoutContext { /*package*/ final MeasureOutput measureOutput = new MeasureOutput(); + int currentGenerationCount; } diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSNode.java b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSNode.java index 348b695e0..6145219f7 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSNode.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSNode.java @@ -7,7 +7,7 @@ */ // NOTE: this file is auto-copied from https://github.com/facebook/css-layout -// @generated SignedSource<> +// @generated SignedSource<<67fbba6df7c2472877c7b04327fb1863>> package com.facebook.csslayout; @@ -66,9 +66,8 @@ public class CSSNode { public int lineIndex = 0; - /*package*/ CSSNode nextAbsoluteChild; - /*package*/ CSSNode nextFlexChild; - + /*package*/ CSSNode nextChild; + private @Nullable ArrayList mChildren; private @Nullable CSSNode mParent; private @Nullable MeasureFunction mMeasureFunction = null; @@ -142,7 +141,6 @@ public class CSSNode { * Performs the actual layout and saves the results in {@link #layout} */ public void calculateLayout(CSSLayoutContext layoutContext) { - layout.resetResult(); LayoutEngine.layoutNode(layoutContext, this, CSSConstants.UNDEFINED, CSSConstants.UNDEFINED, null); } diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSOverflow.java b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSOverflow.java new file mode 100644 index 000000000..74e2efcb4 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSOverflow.java @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2014-present, 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. + */ + +// NOTE: this file is auto-copied from https://github.com/facebook/css-layout +// @generated SignedSource<<3bbf86ec0e75cbdbc9c741e0b3922679>> + +package com.facebook.csslayout; + +public enum CSSOverflow { + VISIBLE, + HIDDEN, +} diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSStyle.java b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSStyle.java index 6170720ce..119761fb9 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSStyle.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSStyle.java @@ -7,7 +7,7 @@ */ // NOTE: this file is auto-copied from https://github.com/facebook/css-layout -// @generated SignedSource<<4c7c75ffd4800aee843a5f5828f3e3ab>> +// @generated SignedSource<> package com.facebook.csslayout; @@ -26,6 +26,7 @@ public class CSSStyle { public CSSAlign alignSelf; public CSSPositionType positionType; public CSSWrap flexWrap; + public CSSOverflow overflow; public float flex; public Spacing margin = new Spacing(); @@ -54,6 +55,7 @@ public class CSSStyle { alignSelf = CSSAlign.AUTO; positionType = CSSPositionType.RELATIVE; flexWrap = CSSWrap.NOWRAP; + overflow = CSSOverflow.VISIBLE; flex = 0f; margin.reset();; diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/LayoutEngine.java b/ReactAndroid/src/main/java/com/facebook/csslayout/LayoutEngine.java index 5b9cc60f1..c8ef50114 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/LayoutEngine.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/LayoutEngine.java @@ -7,10 +7,12 @@ */ // NOTE: this file is auto-copied from https://github.com/facebook/css-layout -// @generated SignedSource<<9224a1489ee541a447ede3657538f5bc>> +// @generated SignedSource<<9165e7546c964b47cb01be9ff8070aa8>> package com.facebook.csslayout; +import com.facebook.infer.annotation.Assertions; + import static com.facebook.csslayout.CSSLayout.DIMENSION_HEIGHT; import static com.facebook.csslayout.CSSLayout.DIMENSION_WIDTH; import static com.facebook.csslayout.CSSLayout.POSITION_BOTTOM; @@ -22,7 +24,9 @@ import static com.facebook.csslayout.CSSLayout.POSITION_TOP; * Calculates layouts based on CSS style. See {@link #layoutNode(CSSNode, float, float)}. */ public class LayoutEngine { - + + private static final boolean POSITIVE_FLEX_IS_AUTO = false; + private static final int CSS_FLEX_DIRECTION_COLUMN = CSSFlexDirection.COLUMN.ordinal(); private static final int CSS_FLEX_DIRECTION_COLUMN_REVERSE = @@ -76,8 +80,42 @@ public class LayoutEngine { Spacing.END, Spacing.END }; + + private static boolean isFlexBasisAuto(CSSNode node) { + if (POSITIVE_FLEX_IS_AUTO) { + // All flex values are auto. + return true; + } else { + // A flex value > 0 implies a basis of zero. + return node.style.flex <= 0; + } + } + + private static float getFlexGrowFactor(CSSNode node) { + // Flex grow is implied by positive values for flex. + if (node.style.flex > 0) { + return node.style.flex; + } + return 0; + } + + private static float getFlexShrinkFactor(CSSNode node) { + if (POSITIVE_FLEX_IS_AUTO) { + // A flex shrink factor of 1 is implied by non-zero values for flex. + if (node.style.flex != 0) { + return 1; + } + } else { + // A flex shrink factor of 1 is implied by negative values for flex. + if (node.style.flex < 0) { + return 1; + } + } + return 0; + } - private static float boundAxis(CSSNode node, int axis, float value) { + + private static float boundAxisWithinMinAndMax(CSSNode node, int axis, float value) { float min = CSSConstants.UNDEFINED; float max = CSSConstants.UNDEFINED; @@ -102,26 +140,14 @@ public class LayoutEngine { return boundValue; } - - private static void setDimensionFromStyle(CSSNode node, int axis) { - // The parent already computed us a width or height. We just skip it - if (!Float.isNaN(node.layout.dimensions[dim[axis]])) { - return; - } - // We only run if there's a width or height defined - if (Float.isNaN(node.style.dimensions[dim[axis]]) || - node.style.dimensions[dim[axis]] <= 0.0) { - return; - } - - // The dimensions can never be smaller than the padding and border - float maxLayoutDimension = Math.max( - boundAxis(node, axis, node.style.dimensions[dim[axis]]), - node.style.padding.getWithFallback(leadingSpacing[axis], leading[axis]) + - node.style.padding.getWithFallback(trailingSpacing[axis], trailing[axis]) + - node.style.border.getWithFallback(leadingSpacing[axis], leading[axis]) + - node.style.border.getWithFallback(trailingSpacing[axis], trailing[axis])); - node.layout.dimensions[dim[axis]] = maxLayoutDimension; + + private static float boundAxis(CSSNode node, int axis, float value) { + float paddingAndBorderAxis = + node.style.padding.getWithFallback(leadingSpacing[axis], leading[axis]) + + node.style.border.getWithFallback(leadingSpacing[axis], leading[axis]) + + node.style.padding.getWithFallback(trailingSpacing[axis], trailing[axis]) + + node.style.border.getWithFallback(trailingSpacing[axis], trailing[axis]); + return Math.max(boundAxisWithinMinAndMax(node, axis, value), paddingAndBorderAxis); } private static float getRelativePosition(CSSNode node, int axis) { @@ -133,6 +159,20 @@ public class LayoutEngine { float trailingPos = node.style.position[trailing[axis]]; return Float.isNaN(trailingPos) ? 0 : -trailingPos; } + + private static void setPosition(CSSNode node, CSSDirection direction) { + int mainAxis = resolveAxis(getFlexDirection(node), direction); + int crossAxis = getCrossFlexDirection(mainAxis, direction); + + node.layout.position[leading[mainAxis]] = node.style.margin.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + + getRelativePosition(node, mainAxis); + node.layout.position[trailing[mainAxis]] = node.style.margin.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + + getRelativePosition(node, mainAxis); + node.layout.position[leading[crossAxis]] = node.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + + getRelativePosition(node, crossAxis); + node.layout.position[trailing[crossAxis]] = node.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]) + + getRelativePosition(node, crossAxis); + } private static int resolveAxis( int axis, @@ -183,395 +223,571 @@ public class LayoutEngine { return node.isMeasureDefined(); } - static boolean needsRelayout(CSSNode node, float parentMaxWidth, float parentMaxHeight) { - return node.isDirty() || - !FloatUtil.floatsEqual( - node.lastLayout.requestedHeight, - node.layout.dimensions[DIMENSION_HEIGHT]) || - !FloatUtil.floatsEqual( - node.lastLayout.requestedWidth, - node.layout.dimensions[DIMENSION_WIDTH]) || - !FloatUtil.floatsEqual(node.lastLayout.parentMaxWidth, parentMaxWidth) || - !FloatUtil.floatsEqual(node.lastLayout.parentMaxHeight, parentMaxHeight); - } - /*package*/ static void layoutNode( CSSLayoutContext layoutContext, CSSNode node, - float parentMaxWidth, - float parentMaxHeight, + float availableWidth, + float availableHeight, CSSDirection parentDirection) { - if (needsRelayout(node, parentMaxWidth, parentMaxHeight)) { - node.lastLayout.requestedWidth = node.layout.dimensions[DIMENSION_WIDTH]; - node.lastLayout.requestedHeight = node.layout.dimensions[DIMENSION_HEIGHT]; - node.lastLayout.parentMaxWidth = parentMaxWidth; - node.lastLayout.parentMaxHeight = parentMaxHeight; - - for (int i = 0, childCount = node.getChildCount(); i < childCount; i++) { - node.getChildAt(i).layout.resetResult(); - } - - layoutNodeImpl(layoutContext, node, parentMaxWidth, parentMaxHeight, parentDirection); - node.lastLayout.copy(node.layout); - } else { - node.layout.copy(node.lastLayout); + // Increment the generation count. This will force the recursive routine to visit + // all dirty nodes at least once. Subsequent visits will be skipped if the input + // parameters don't change. + layoutContext.currentGenerationCount++; + + // If the caller didn't specify a height/width, use the dimensions + // specified in the style. + if (Float.isNaN(availableWidth) && node.style.dimensions[DIMENSION_WIDTH] >= 0.0) { + float marginAxisRow = (node.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW]) + node.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW])); + availableWidth = node.style.dimensions[DIMENSION_WIDTH] + marginAxisRow; + } + if (Float.isNaN(availableHeight) && node.style.dimensions[DIMENSION_HEIGHT] >= 0.0) { + float marginAxisColumn = (node.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + node.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])); + availableHeight = node.style.dimensions[DIMENSION_HEIGHT] + marginAxisColumn; + } + + CSSMeasureMode widthMeasureMode = Float.isNaN(availableWidth) ? CSSMeasureMode.UNDEFINED : CSSMeasureMode.EXACTLY; + CSSMeasureMode heightMeasureMode = Float.isNaN(availableHeight) ? CSSMeasureMode.UNDEFINED : CSSMeasureMode.EXACTLY; + + if (layoutNodeInternal(layoutContext, node, availableWidth, availableHeight, parentDirection, widthMeasureMode, heightMeasureMode, true, "initial")) { + setPosition(node, node.layout.direction); } - - node.markHasNewLayout(); } + + /*package*/ static boolean canUseCachedMeasurement(float availableWidth, float availableHeight, + float marginRow, float marginColumn, + CSSMeasureMode widthMeasureMode, CSSMeasureMode heightMeasureMode, + CSSCachedMeasurement cachedLayout) { + // Is it an exact match? + if (FloatUtil.floatsEqual(cachedLayout.availableWidth, availableWidth) && + FloatUtil.floatsEqual(cachedLayout.availableHeight, availableHeight) && + cachedLayout.widthMeasureMode == widthMeasureMode && + cachedLayout.heightMeasureMode == heightMeasureMode) { + return true; + } + + // If the width is an exact match, try a fuzzy match on the height. + if (FloatUtil.floatsEqual(cachedLayout.availableWidth, availableWidth) && + cachedLayout.widthMeasureMode == widthMeasureMode && + heightMeasureMode == CSSMeasureMode.EXACTLY && + FloatUtil.floatsEqual(availableHeight - marginColumn, cachedLayout.computedHeight)) { + return true; + } + + // If the height is an exact match, try a fuzzy match on the width. + if (FloatUtil.floatsEqual(cachedLayout.availableHeight, availableHeight) && + cachedLayout.heightMeasureMode == heightMeasureMode && + widthMeasureMode == CSSMeasureMode.EXACTLY && + FloatUtil.floatsEqual(availableWidth - marginRow, cachedLayout.computedWidth)) { + return true; + } + + return false; + } + + // + // This is a wrapper around the layoutNodeImpl function. It determines + // whether the layout request is redundant and can be skipped. + // + // Parameters: + // Input parameters are the same as layoutNodeImpl (see below) + // Return parameter is true if layout was performed, false if skipped + // + private static boolean layoutNodeInternal( + CSSLayoutContext layoutContext, + CSSNode node, + float availableWidth, + float availableHeight, + CSSDirection parentDirection, + CSSMeasureMode widthMeasureMode, + CSSMeasureMode heightMeasureMode, + boolean performLayout, + String reason) { + CSSLayout layout = node.layout; + + boolean needToVisitNode = (node.isDirty() && layout.generationCount != layoutContext.currentGenerationCount) || + layout.lastParentDirection != parentDirection; + + if (needToVisitNode) { + // Invalidate the cached results. + layout.nextCachedMeasurementsIndex = 0; + layout.cachedLayout.widthMeasureMode = null; + layout.cachedLayout.heightMeasureMode = null; + } + + CSSCachedMeasurement cachedResults = null; + + // Determine whether the results are already cached. We maintain a separate + // cache for layouts and measurements. A layout operation modifies the positions + // and dimensions for nodes in the subtree. The algorithm assumes that each node + // gets layed out a maximum of one time per tree layout, but multiple measurements + // may be required to resolve all of the flex dimensions. + // We handle nodes with measure functions specially here because they are the most + // expensive to measure, so it's worth avoiding redundant measurements if at all possible. + if (isMeasureDefined(node)) { + float marginAxisRow = + node.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW]) + + node.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW]); + float marginAxisColumn = + node.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + + node.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN]); + + // First, try to use the layout cache. + if (canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn, + widthMeasureMode, heightMeasureMode, layout.cachedLayout)) { + cachedResults = layout.cachedLayout; + } else { + // Try to use the measurement cache. + for (int i = 0; i < layout.nextCachedMeasurementsIndex; i++) { + if (canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn, + widthMeasureMode, heightMeasureMode, layout.cachedMeasurements[i])) { + cachedResults = layout.cachedMeasurements[i]; + break; + } + } + } + } else if (performLayout) { + if (FloatUtil.floatsEqual(layout.cachedLayout.availableWidth, availableWidth) && + FloatUtil.floatsEqual(layout.cachedLayout.availableHeight, availableHeight) && + layout.cachedLayout.widthMeasureMode == widthMeasureMode && + layout.cachedLayout.heightMeasureMode == heightMeasureMode) { + + cachedResults = layout.cachedLayout; + } + } else { + for (int i = 0; i < layout.nextCachedMeasurementsIndex; i++) { + if (FloatUtil.floatsEqual(layout.cachedMeasurements[i].availableWidth, availableWidth) && + FloatUtil.floatsEqual(layout.cachedMeasurements[i].availableHeight, availableHeight) && + layout.cachedMeasurements[i].widthMeasureMode == widthMeasureMode && + layout.cachedMeasurements[i].heightMeasureMode == heightMeasureMode) { + cachedResults = layout.cachedMeasurements[i]; + break; + } + } + } + + if (!needToVisitNode && cachedResults != null) { + layout.measuredDimensions[DIMENSION_WIDTH] = cachedResults.computedWidth; + layout.measuredDimensions[DIMENSION_HEIGHT] = cachedResults.computedHeight; + } else { + layoutNodeImpl(layoutContext, node, availableWidth, availableHeight, parentDirection, widthMeasureMode, heightMeasureMode, performLayout); + + layout.lastParentDirection = parentDirection; + + if (cachedResults == null) { + if (layout.nextCachedMeasurementsIndex == CSSLayout.MAX_CACHED_RESULT_COUNT) { + layout.nextCachedMeasurementsIndex = 0; + } + + CSSCachedMeasurement newCacheEntry = null; + if (performLayout) { + // Use the single layout cache entry. + newCacheEntry = layout.cachedLayout; + } else { + // Allocate a new measurement cache entry. + newCacheEntry = layout.cachedMeasurements[layout.nextCachedMeasurementsIndex]; + if (newCacheEntry == null) { + newCacheEntry = new CSSCachedMeasurement(); + layout.cachedMeasurements[layout.nextCachedMeasurementsIndex] = newCacheEntry; + } + layout.nextCachedMeasurementsIndex++; + } + + newCacheEntry.availableWidth = availableWidth; + newCacheEntry.availableHeight = availableHeight; + newCacheEntry.widthMeasureMode = widthMeasureMode; + newCacheEntry.heightMeasureMode = heightMeasureMode; + newCacheEntry.computedWidth = layout.measuredDimensions[DIMENSION_WIDTH]; + newCacheEntry.computedHeight = layout.measuredDimensions[DIMENSION_HEIGHT]; + } + } + + if (performLayout) { + node.layout.dimensions[DIMENSION_WIDTH] = node.layout.measuredDimensions[DIMENSION_WIDTH]; + node.layout.dimensions[DIMENSION_HEIGHT] = node.layout.measuredDimensions[DIMENSION_HEIGHT]; + node.markHasNewLayout(); + } + + layout.generationCount = layoutContext.currentGenerationCount; + return (needToVisitNode || cachedResults == null); + } + + + // + // This is the main routine that implements a subset of the flexbox layout algorithm + // described in the W3C CSS documentation: https://www.w3.org/TR/css3-flexbox/. + // + // Limitations of this algorithm, compared to the full standard: + // * Display property is always assumed to be 'flex' except for Text nodes, which + // are assumed to be 'inline-flex'. + // * The 'zIndex' property (or any form of z ordering) is not supported. Nodes are + // stacked in document order. + // * The 'order' property is not supported. The order of flex items is always defined + // by document order. + // * The 'visibility' property is always assumed to be 'visible'. Values of 'collapse' + // and 'hidden' are not supported. + // * The 'wrap' property supports only 'nowrap' (which is the default) or 'wrap'. The + // rarely-used 'wrap-reverse' is not supported. + // * Rather than allowing arbitrary combinations of flexGrow, flexShrink and + // flexBasis, this algorithm supports only the three most common combinations: + // flex: 0 is equiavlent to flex: 0 0 auto + // flex: n (where n is a positive value) is equivalent to flex: n 1 auto + // If POSITIVE_FLEX_IS_AUTO is 0, then it is equivalent to flex: n 0 0 + // This is faster because the content doesn't need to be measured, but it's + // less flexible because the basis is always 0 and can't be overriden with + // the width/height attributes. + // flex: -1 (or any negative value) is equivalent to flex: 0 1 auto + // * Margins cannot be specified as 'auto'. They must be specified in terms of pixel + // values, and the default value is 0. + // * The 'baseline' value is not supported for alignItems and alignSelf properties. + // * Values of width, maxWidth, minWidth, height, maxHeight and minHeight must be + // specified as pixel values, not as percentages. + // * There is no support for calculation of dimensions based on intrinsic aspect ratios + // (e.g. images). + // * There is no support for forced breaks. + // * It does not support vertical inline directions (top-to-bottom or bottom-to-top text). + // + // Deviations from standard: + // * Section 4.5 of the spec indicates that all flex items have a default minimum + // main size. For text blocks, for example, this is the width of the widest word. + // Calculating the minimum width is expensive, so we forego it and assume a default + // minimum main size of 0. + // * Min/Max sizes in the main axis are not honored when resolving flexible lengths. + // * The spec indicates that the default value for 'flexDirection' is 'row', but + // the algorithm below assumes a default of 'column'. + // + // Input parameters: + // - node: current node to be sized and layed out + // - availableWidth & availableHeight: available size to be used for sizing the node + // or CSS_UNDEFINED if the size is not available; interpretation depends on layout + // flags + // - parentDirection: the inline (text) direction within the parent (left-to-right or + // right-to-left) + // - widthMeasureMode: indicates the sizing rules for the width (see below for explanation) + // - heightMeasureMode: indicates the sizing rules for the height (see below for explanation) + // - performLayout: specifies whether the caller is interested in just the dimensions + // of the node or it requires the entire node and its subtree to be layed out + // (with final positions) + // + // Details: + // This routine is called recursively to lay out subtrees of flexbox elements. It uses the + // information in node.style, which is treated as a read-only input. It is responsible for + // setting the layout.direction and layout.measured_dimensions fields for the input node as well + // as the layout.position and layout.line_index fields for its child nodes. The + // layout.measured_dimensions field includes any border or padding for the node but does + // not include margins. + // + // The spec describes four different layout modes: "fill available", "max content", "min content", + // and "fit content". Of these, we don't use "min content" because we don't support default + // minimum main sizes (see above for details). Each of our measure modes maps to a layout mode + // from the spec (https://www.w3.org/TR/css3-sizing/#terms): + // - CSS_MEASURE_MODE_UNDEFINED: max content + // - CSS_MEASURE_MODE_EXACTLY: fill available + // - CSS_MEASURE_MODE_AT_MOST: fit content + // + // When calling layoutNodeImpl and layoutNodeInternal, if the caller passes an available size of + // undefined then it must also pass a measure mode of CSS_MEASURE_MODE_UNDEFINED in that dimension. + // private static void layoutNodeImpl( CSSLayoutContext layoutContext, CSSNode node, - float parentMaxWidth, - float parentMaxHeight, - CSSDirection parentDirection) { + float availableWidth, + float availableHeight, + CSSDirection parentDirection, + CSSMeasureMode widthMeasureMode, + CSSMeasureMode heightMeasureMode, + boolean performLayout) { /** START_GENERATED **/ + Assertions.assertCondition(Float.isNaN(availableWidth) ? widthMeasureMode == CSSMeasureMode.UNDEFINED : true, "availableWidth is indefinite so widthMeasureMode must be CSSMeasureMode.UNDEFINED"); + Assertions.assertCondition(Float.isNaN(availableHeight) ? heightMeasureMode == CSSMeasureMode.UNDEFINED : true, "availableHeight is indefinite so heightMeasureMode must be CSSMeasureMode.UNDEFINED"); + + float paddingAndBorderAxisRow = ((node.style.padding.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW]) + node.style.border.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW])) + (node.style.padding.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW]) + node.style.border.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW]))); + float paddingAndBorderAxisColumn = ((node.style.padding.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + node.style.border.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN])) + (node.style.padding.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN]) + node.style.border.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN]))); + float marginAxisRow = (node.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW]) + node.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW])); + float marginAxisColumn = (node.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + node.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])); + + // Set the resolved resolution in the node's layout. CSSDirection direction = resolveDirection(node, parentDirection); - int mainAxis = resolveAxis(getFlexDirection(node), direction); - int crossAxis = getCrossFlexDirection(mainAxis, direction); - int resolvedRowAxis = resolveAxis(CSS_FLEX_DIRECTION_ROW, direction); - - // Handle width and height style attributes - setDimensionFromStyle(node, mainAxis); - setDimensionFromStyle(node, crossAxis); - - // Set the resolved resolution in the node's layout node.layout.direction = direction; - // The position is set by the parent, but we need to complete it with a - // delta composed of the margin and left/top/right/bottom - node.layout.position[leading[mainAxis]] += node.style.margin.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + - getRelativePosition(node, mainAxis); - node.layout.position[trailing[mainAxis]] += node.style.margin.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + - getRelativePosition(node, mainAxis); - node.layout.position[leading[crossAxis]] += node.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + - getRelativePosition(node, crossAxis); - node.layout.position[trailing[crossAxis]] += node.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]) + - getRelativePosition(node, crossAxis); - - // Inline immutable values from the target node to avoid excessive method - // invocations during the layout calculation. - int childCount = node.getChildCount(); - float paddingAndBorderAxisResolvedRow = ((node.style.padding.getWithFallback(leadingSpacing[resolvedRowAxis], leading[resolvedRowAxis]) + node.style.border.getWithFallback(leadingSpacing[resolvedRowAxis], leading[resolvedRowAxis])) + (node.style.padding.getWithFallback(trailingSpacing[resolvedRowAxis], trailing[resolvedRowAxis]) + node.style.border.getWithFallback(trailingSpacing[resolvedRowAxis], trailing[resolvedRowAxis]))); - float paddingAndBorderAxisColumn = ((node.style.padding.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + node.style.border.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN])) + (node.style.padding.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN]) + node.style.border.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN]))); - + // For content (text) nodes, determine the dimensions based on the text contents. if (isMeasureDefined(node)) { - boolean isResolvedRowDimDefined = (!Float.isNaN(node.layout.dimensions[dim[resolvedRowAxis]]) && node.layout.dimensions[dim[resolvedRowAxis]] >= 0.0); + float innerWidth = availableWidth - marginAxisRow - paddingAndBorderAxisRow; + float innerHeight = availableHeight - marginAxisColumn - paddingAndBorderAxisColumn; + + if (widthMeasureMode == CSSMeasureMode.EXACTLY && heightMeasureMode == CSSMeasureMode.EXACTLY) { - float width = CSSConstants.UNDEFINED; - CSSMeasureMode widthMode = CSSMeasureMode.UNDEFINED; - if ((!Float.isNaN(node.style.dimensions[dim[resolvedRowAxis]]) && node.style.dimensions[dim[resolvedRowAxis]] >= 0.0)) { - width = node.style.dimensions[DIMENSION_WIDTH]; - widthMode = CSSMeasureMode.EXACTLY; - } else if (isResolvedRowDimDefined) { - width = node.layout.dimensions[dim[resolvedRowAxis]]; - widthMode = CSSMeasureMode.EXACTLY; + // Don't bother sizing the text if both dimensions are already defined. + node.layout.measuredDimensions[DIMENSION_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, availableWidth - marginAxisRow); + node.layout.measuredDimensions[DIMENSION_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, availableHeight - marginAxisColumn); + } else if (innerWidth <= 0 || innerHeight <= 0) { + + // Don't bother sizing the text if there's no horizontal or vertical space. + node.layout.measuredDimensions[DIMENSION_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, 0); + node.layout.measuredDimensions[DIMENSION_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, 0); } else { - width = parentMaxWidth - - (node.style.margin.getWithFallback(leadingSpacing[resolvedRowAxis], leading[resolvedRowAxis]) + node.style.margin.getWithFallback(trailingSpacing[resolvedRowAxis], trailing[resolvedRowAxis])); - widthMode = CSSMeasureMode.AT_MOST; - } - width -= paddingAndBorderAxisResolvedRow; - if (Float.isNaN(width)) { - widthMode = CSSMeasureMode.UNDEFINED; - } - float height = CSSConstants.UNDEFINED; - CSSMeasureMode heightMode = CSSMeasureMode.UNDEFINED; - if ((!Float.isNaN(node.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]) && node.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0)) { - height = node.style.dimensions[DIMENSION_HEIGHT]; - heightMode = CSSMeasureMode.EXACTLY; - } else if ((!Float.isNaN(node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]) && node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0)) { - height = node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]; - heightMode = CSSMeasureMode.EXACTLY; - } else { - height = parentMaxHeight - - (node.style.margin.getWithFallback(leadingSpacing[resolvedRowAxis], leading[resolvedRowAxis]) + node.style.margin.getWithFallback(trailingSpacing[resolvedRowAxis], trailing[resolvedRowAxis])); - heightMode = CSSMeasureMode.AT_MOST; - } - height -= ((node.style.padding.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + node.style.border.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN])) + (node.style.padding.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN]) + node.style.border.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN]))); - if (Float.isNaN(height)) { - heightMode = CSSMeasureMode.UNDEFINED; - } - - // We only need to give a dimension for the text if we haven't got any - // for it computed yet. It can either be from the style attribute or because - // the element is flexible. - boolean isRowUndefined = !(!Float.isNaN(node.style.dimensions[dim[resolvedRowAxis]]) && node.style.dimensions[dim[resolvedRowAxis]] >= 0.0) && !isResolvedRowDimDefined; - boolean isColumnUndefined = !(!Float.isNaN(node.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]) && node.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0) && - Float.isNaN(node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]); - - // Let's not measure the text if we already know both dimensions - if (isRowUndefined || isColumnUndefined) { + // Measure the text under the current constraints. MeasureOutput measureDim = node.measure( layoutContext.measureOutput, - width, - widthMode, - height, - heightMode + innerWidth, + widthMeasureMode, + innerHeight, + heightMeasureMode ); - if (isRowUndefined) { - node.layout.dimensions[DIMENSION_WIDTH] = measureDim.width + - paddingAndBorderAxisResolvedRow; - } - if (isColumnUndefined) { - node.layout.dimensions[DIMENSION_HEIGHT] = measureDim.height + - paddingAndBorderAxisColumn; - } + + node.layout.measuredDimensions[DIMENSION_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, + (widthMeasureMode == CSSMeasureMode.UNDEFINED || widthMeasureMode == CSSMeasureMode.AT_MOST) ? + measureDim.width + paddingAndBorderAxisRow : + availableWidth - marginAxisRow); + node.layout.measuredDimensions[DIMENSION_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, + (heightMeasureMode == CSSMeasureMode.UNDEFINED || heightMeasureMode == CSSMeasureMode.AT_MOST) ? + measureDim.height + paddingAndBorderAxisColumn : + availableHeight - marginAxisColumn); } - if (childCount == 0) { + + return; + } + + // For nodes with no children, use the available values if they were provided, or + // the minimum size as indicated by the padding and border sizes. + int childCount = node.getChildCount(); + if (childCount == 0) { + node.layout.measuredDimensions[DIMENSION_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, + (widthMeasureMode == CSSMeasureMode.UNDEFINED || widthMeasureMode == CSSMeasureMode.AT_MOST) ? + paddingAndBorderAxisRow : + availableWidth - marginAxisRow); + node.layout.measuredDimensions[DIMENSION_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, + (heightMeasureMode == CSSMeasureMode.UNDEFINED || heightMeasureMode == CSSMeasureMode.AT_MOST) ? + paddingAndBorderAxisColumn : + availableHeight - marginAxisColumn); + return; + } + + // If we're not being asked to perform a full layout, we can handle a number of common + // cases here without incurring the cost of the remaining function. + if (!performLayout) { + // If we're being asked to size the content with an at most constraint but there is no available width, + // the measurement will always be zero. + if (widthMeasureMode == CSSMeasureMode.AT_MOST && availableWidth <= 0 && + heightMeasureMode == CSSMeasureMode.AT_MOST && availableHeight <= 0) { + node.layout.measuredDimensions[DIMENSION_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, 0); + node.layout.measuredDimensions[DIMENSION_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, 0); + return; + } + + if (widthMeasureMode == CSSMeasureMode.AT_MOST && availableWidth <= 0) { + node.layout.measuredDimensions[DIMENSION_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, 0); + node.layout.measuredDimensions[DIMENSION_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, Float.isNaN(availableHeight) ? 0 : (availableHeight - marginAxisColumn)); + return; + } + + if (heightMeasureMode == CSSMeasureMode.AT_MOST && availableHeight <= 0) { + node.layout.measuredDimensions[DIMENSION_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, Float.isNaN(availableWidth) ? 0 : (availableWidth - marginAxisRow)); + node.layout.measuredDimensions[DIMENSION_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, 0); + return; + } + + // If we're being asked to use an exact width/height, there's no need to measure the children. + if (widthMeasureMode == CSSMeasureMode.EXACTLY && heightMeasureMode == CSSMeasureMode.EXACTLY) { + node.layout.measuredDimensions[DIMENSION_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, availableWidth - marginAxisRow); + node.layout.measuredDimensions[DIMENSION_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, availableHeight - marginAxisColumn); return; } } - boolean isNodeFlexWrap = (node.style.flexWrap == CSSWrap.WRAP); - + // STEP 1: CALCULATE VALUES FOR REMAINDER OF ALGORITHM + int mainAxis = resolveAxis(getFlexDirection(node), direction); + int crossAxis = getCrossFlexDirection(mainAxis, direction); + boolean isMainAxisRow = (mainAxis == CSS_FLEX_DIRECTION_ROW || mainAxis == CSS_FLEX_DIRECTION_ROW_REVERSE); CSSJustify justifyContent = node.style.justifyContent; - - float leadingPaddingAndBorderMain = (node.style.padding.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + node.style.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis])); - float leadingPaddingAndBorderCross = (node.style.padding.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + node.style.border.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis])); - float paddingAndBorderAxisMain = ((node.style.padding.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + node.style.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis])) + (node.style.padding.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + node.style.border.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]))); - float paddingAndBorderAxisCross = ((node.style.padding.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + node.style.border.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis])) + (node.style.padding.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]) + node.style.border.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]))); - - boolean isMainDimDefined = (!Float.isNaN(node.layout.dimensions[dim[mainAxis]]) && node.layout.dimensions[dim[mainAxis]] >= 0.0); - boolean isCrossDimDefined = (!Float.isNaN(node.layout.dimensions[dim[crossAxis]]) && node.layout.dimensions[dim[crossAxis]] >= 0.0); - boolean isMainRowDirection = (mainAxis == CSS_FLEX_DIRECTION_ROW || mainAxis == CSS_FLEX_DIRECTION_ROW_REVERSE); - - int i; - int ii; - CSSNode child; - int axis; + boolean isNodeFlexWrap = (node.style.flexWrap == CSSWrap.WRAP); CSSNode firstAbsoluteChild = null; CSSNode currentAbsoluteChild = null; - float definedMainDim = CSSConstants.UNDEFINED; - if (isMainDimDefined) { - definedMainDim = node.layout.dimensions[dim[mainAxis]] - paddingAndBorderAxisMain; + float leadingPaddingAndBorderMain = (node.style.padding.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + node.style.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis])); + float trailingPaddingAndBorderMain = (node.style.padding.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + node.style.border.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis])); + float leadingPaddingAndBorderCross = (node.style.padding.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + node.style.border.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis])); + float paddingAndBorderAxisMain = ((node.style.padding.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + node.style.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis])) + (node.style.padding.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + node.style.border.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]))); + float paddingAndBorderAxisCross = ((node.style.padding.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + node.style.border.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis])) + (node.style.padding.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]) + node.style.border.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]))); + + CSSMeasureMode measureModeMainDim = isMainAxisRow ? widthMeasureMode : heightMeasureMode; + CSSMeasureMode measureModeCrossDim = isMainAxisRow ? heightMeasureMode : widthMeasureMode; + + // STEP 2: DETERMINE AVAILABLE SIZE IN MAIN AND CROSS DIRECTIONS + float availableInnerWidth = availableWidth - marginAxisRow - paddingAndBorderAxisRow; + float availableInnerHeight = availableHeight - marginAxisColumn - paddingAndBorderAxisColumn; + float availableInnerMainDim = isMainAxisRow ? availableInnerWidth : availableInnerHeight; + float availableInnerCrossDim = isMainAxisRow ? availableInnerHeight : availableInnerWidth; + + // STEP 3: DETERMINE FLEX BASIS FOR EACH ITEM + CSSNode child; + int i; + float childWidth; + float childHeight; + CSSMeasureMode childWidthMeasureMode; + CSSMeasureMode childHeightMeasureMode; + for (i = 0; i < childCount; i++) { + child = node.getChildAt(i); + + if (performLayout) { + // Set the initial position (relative to the parent). + CSSDirection childDirection = resolveDirection(child, direction); + setPosition(child, childDirection); + } + + // Absolute-positioned children don't participate in flex layout. Add them + // to a list that we can process later. + if (child.style.positionType == CSSPositionType.ABSOLUTE) { + + // Store a private linked list of absolutely positioned children + // so that we can efficiently traverse them later. + if (firstAbsoluteChild == null) { + firstAbsoluteChild = child; + } + if (currentAbsoluteChild != null) { + currentAbsoluteChild.nextChild = child; + } + currentAbsoluteChild = child; + child.nextChild = null; + } else { + + if (isMainAxisRow && (child.style.dimensions[dim[CSS_FLEX_DIRECTION_ROW]] >= 0.0)) { + + // The width is definite, so use that as the flex basis. + child.layout.flexBasis = Math.max(child.style.dimensions[DIMENSION_WIDTH], ((child.style.padding.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW]) + child.style.border.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW])) + (child.style.padding.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW]) + child.style.border.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW])))); + } else if (!isMainAxisRow && (child.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0)) { + + // The height is definite, so use that as the flex basis. + child.layout.flexBasis = Math.max(child.style.dimensions[DIMENSION_HEIGHT], ((child.style.padding.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + child.style.border.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN])) + (child.style.padding.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN]) + child.style.border.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])))); + } else if (!isFlexBasisAuto(child) && !Float.isNaN(availableInnerMainDim)) { + + // If the basis isn't 'auto', it is assumed to be zero. + child.layout.flexBasis = Math.max(0, ((child.style.padding.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + child.style.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis])) + (child.style.padding.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + child.style.border.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis])))); + } else { + + // Compute the flex basis and hypothetical main size (i.e. the clamped flex basis). + childWidth = CSSConstants.UNDEFINED; + childHeight = CSSConstants.UNDEFINED; + childWidthMeasureMode = CSSMeasureMode.UNDEFINED; + childHeightMeasureMode = CSSMeasureMode.UNDEFINED; + + if ((child.style.dimensions[dim[CSS_FLEX_DIRECTION_ROW]] >= 0.0)) { + childWidth = child.style.dimensions[DIMENSION_WIDTH] + (child.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW]) + child.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW])); + childWidthMeasureMode = CSSMeasureMode.EXACTLY; + } + if ((child.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0)) { + childHeight = child.style.dimensions[DIMENSION_HEIGHT] + (child.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + child.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])); + childHeightMeasureMode = CSSMeasureMode.EXACTLY; + } + + // According to the spec, if the main size is not definite and the + // child's inline axis is parallel to the main axis (i.e. it's + // horizontal), the child should be sized using "UNDEFINED" in + // the main size. Otherwise use "AT_MOST" in the cross axis. + if (!isMainAxisRow && Float.isNaN(childWidth) && !Float.isNaN(availableInnerWidth)) { + childWidth = availableInnerWidth; + childWidthMeasureMode = CSSMeasureMode.AT_MOST; + } + + // The W3C spec doesn't say anything about the 'overflow' property, + // but all major browsers appear to implement the following logic. + if (node.style.overflow == CSSOverflow.HIDDEN) { + if (isMainAxisRow && Float.isNaN(childHeight) && !Float.isNaN(availableInnerHeight)) { + childHeight = availableInnerHeight; + childHeightMeasureMode = CSSMeasureMode.AT_MOST; + } + } + + // Measure the child + layoutNodeInternal(layoutContext, child, childWidth, childHeight, direction, childWidthMeasureMode, childHeightMeasureMode, false, "measure"); + + child.layout.flexBasis = Math.max(isMainAxisRow ? child.layout.measuredDimensions[DIMENSION_WIDTH] : child.layout.measuredDimensions[DIMENSION_HEIGHT], ((child.style.padding.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + child.style.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis])) + (child.style.padding.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + child.style.border.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis])))); + } + } } - // We want to execute the next two loops one per line with flex-wrap - int startLine = 0; - int endLine = 0; - // int nextOffset = 0; - int alreadyComputedNextLayout = 0; - // We aggregate the total dimensions of the container in those two variables - float linesCrossDim = 0; - float linesMainDim = 0; - int linesCount = 0; - while (endLine < childCount) { - // Layout non flexible children and count children by type + // STEP 4: COLLECT FLEX ITEMS INTO FLEX LINES + + // Indexes of children that represent the first and last items in the line. + int startOfLineIndex = 0; + int endOfLineIndex = 0; + + // Number of lines. + int lineCount = 0; + + // Accumulated cross dimensions of all lines so far. + float totalLineCrossDim = 0; - // 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; + // Max main dimension of all the lines. + float maxLineMainDim = 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; + while (endOfLineIndex < childCount) { + + // Number of items on the currently line. May be different than the difference + // between start and end indicates because we skip over absolute-positioned items. + int itemsOnLine = 0; - // Use the line loop to position children in the main axis for as long - // as they are using a simple stacking behaviour. Children that are - // immediately stacked in the initial loop will not be touched again - // in . - boolean isSimpleStackMain = - (isMainDimDefined && justifyContent == CSSJustify.FLEX_START) || - (!isMainDimDefined && justifyContent != CSSJustify.CENTER); - int firstComplexMain = (isSimpleStackMain ? childCount : startLine); + // sizeConsumedOnCurrentLine is accumulation of the dimensions and margin + // of all the children on the current line. 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 sizeConsumedOnCurrentLine = 0; - // Use the initial line loop to position children in the cross axis for - // as long as they are relatively positioned with alignment STRETCH or - // FLEX_START. Children that are immediately stacked in the initial loop - // will not be touched again in . - boolean isSimpleStackCross = true; - int firstComplexCross = childCount; + float totalFlexGrowFactors = 0; + float totalFlexShrinkScaledFactors = 0; - CSSNode firstFlexChild = null; - CSSNode currentFlexChild = null; + i = startOfLineIndex; - float mainDim = leadingPaddingAndBorderMain; - float crossDim = 0; + // Maintain a linked list of the child nodes that can shrink and/or grow. + CSSNode firstRelativeChild = null; + CSSNode currentRelativeChild = null; - float maxWidth = CSSConstants.UNDEFINED; - float maxHeight = CSSConstants.UNDEFINED; - for (i = startLine; i < childCount; ++i) { + // Add items to the current line until it's full or we run out of items. + while (i < childCount) { child = node.getChildAt(i); - child.lineIndex = linesCount; + child.lineIndex = lineCount; - child.nextAbsoluteChild = null; - child.nextFlexChild = null; - - CSSAlign alignItem = getAlignItem(node, child); - - // Pre-fill cross axis dimensions when the child is using stretch before - // we call the recursive layout pass - if (alignItem == CSSAlign.STRETCH && - child.style.positionType == CSSPositionType.RELATIVE && - isCrossDimDefined && - !(!Float.isNaN(child.style.dimensions[dim[crossAxis]]) && child.style.dimensions[dim[crossAxis]] >= 0.0)) { - child.layout.dimensions[dim[crossAxis]] = Math.max( - boundAxis(child, crossAxis, node.layout.dimensions[dim[crossAxis]] - - paddingAndBorderAxisCross - (child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]))), - // You never want to go smaller than padding - ((child.style.padding.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.style.border.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis])) + (child.style.padding.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]) + child.style.border.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]))) - ); - } else if (child.style.positionType == CSSPositionType.ABSOLUTE) { - // Store a private linked list of absolutely positioned children - // so that we can efficiently traverse them later. - if (firstAbsoluteChild == null) { - firstAbsoluteChild = child; + if (child.style.positionType != CSSPositionType.ABSOLUTE) { + float outerFlexBasis = child.layout.flexBasis + (child.style.margin.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + child.style.margin.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis])); + + // If this is a multi-line flow and this item pushes us over the available size, we've + // hit the end of the current line. Break out of the loop and lay out the current line. + if (sizeConsumedOnCurrentLine + outerFlexBasis > availableInnerMainDim && isNodeFlexWrap && itemsOnLine > 0) { + break; } - if (currentAbsoluteChild != null) { - currentAbsoluteChild.nextAbsoluteChild = child; - } - currentAbsoluteChild = child; - // 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 (ii = 0; ii < 2; ii++) { - axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; - if ((!Float.isNaN(node.layout.dimensions[dim[axis]]) && node.layout.dimensions[dim[axis]] >= 0.0) && - !(!Float.isNaN(child.style.dimensions[dim[axis]]) && child.style.dimensions[dim[axis]] >= 0.0) && - !Float.isNaN(child.style.position[leading[axis]]) && - !Float.isNaN(child.style.position[trailing[axis]])) { - child.layout.dimensions[dim[axis]] = Math.max( - boundAxis(child, axis, node.layout.dimensions[dim[axis]] - - ((node.style.padding.getWithFallback(leadingSpacing[axis], leading[axis]) + node.style.border.getWithFallback(leadingSpacing[axis], leading[axis])) + (node.style.padding.getWithFallback(trailingSpacing[axis], trailing[axis]) + node.style.border.getWithFallback(trailingSpacing[axis], trailing[axis]))) - - (child.style.margin.getWithFallback(leadingSpacing[axis], leading[axis]) + child.style.margin.getWithFallback(trailingSpacing[axis], trailing[axis])) - - (Float.isNaN(child.style.position[leading[axis]]) ? 0 : child.style.position[leading[axis]]) - - (Float.isNaN(child.style.position[trailing[axis]]) ? 0 : child.style.position[trailing[axis]])), - // You never want to go smaller than padding - ((child.style.padding.getWithFallback(leadingSpacing[axis], leading[axis]) + child.style.border.getWithFallback(leadingSpacing[axis], leading[axis])) + (child.style.padding.getWithFallback(trailingSpacing[axis], trailing[axis]) + child.style.border.getWithFallback(trailingSpacing[axis], trailing[axis]))) - ); - } + sizeConsumedOnCurrentLine += outerFlexBasis; + itemsOnLine++; + + if ((child.style.positionType == CSSPositionType.RELATIVE && child.style.flex != 0)) { + totalFlexGrowFactors += getFlexGrowFactor(child); + + // Unlike the grow factor, the shrink factor is scaled relative to the child + // dimension. + totalFlexShrinkScaledFactors += getFlexShrinkFactor(child) * child.layout.flexBasis; } + + // Store a private linked list of children that need to be layed out. + if (firstRelativeChild == null) { + firstRelativeChild = child; + } + if (currentRelativeChild != null) { + currentRelativeChild.nextChild = child; + } + currentRelativeChild = child; + child.nextChild = null; } - - float nextContentDim = 0; - - // It only makes sense to consider a child flexible if we have a computed - // dimension for the node. - if (isMainDimDefined && (child.style.positionType == CSSPositionType.RELATIVE && child.style.flex > 0)) { - flexibleChildrenCount++; - totalFlexible += child.style.flex; - - // Store a private linked list of flexible children so that we can - // efficiently traverse them later. - if (firstFlexChild == null) { - firstFlexChild = child; - } - if (currentFlexChild != null) { - currentFlexChild.nextFlexChild = child; - } - currentFlexChild = 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, which represents - // the smallest possible size for the child, to compute the remaining - // available space. - nextContentDim = ((child.style.padding.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + child.style.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis])) + (child.style.padding.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + child.style.border.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]))) + - (child.style.margin.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + child.style.margin.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis])); - - } else { - maxWidth = CSSConstants.UNDEFINED; - maxHeight = CSSConstants.UNDEFINED; - - if (!isMainRowDirection) { - if ((!Float.isNaN(node.layout.dimensions[dim[resolvedRowAxis]]) && node.layout.dimensions[dim[resolvedRowAxis]] >= 0.0)) { - maxWidth = node.layout.dimensions[dim[resolvedRowAxis]] - - paddingAndBorderAxisResolvedRow; - } else { - maxWidth = parentMaxWidth - - (node.style.margin.getWithFallback(leadingSpacing[resolvedRowAxis], leading[resolvedRowAxis]) + node.style.margin.getWithFallback(trailingSpacing[resolvedRowAxis], trailing[resolvedRowAxis])) - - paddingAndBorderAxisResolvedRow; - } - } else { - if ((!Float.isNaN(node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]) && node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0)) { - maxHeight = node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] - - paddingAndBorderAxisColumn; - } else { - maxHeight = parentMaxHeight - - (node.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + node.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])) - - paddingAndBorderAxisColumn; - } - } - - // This is the main recursive call. We layout non flexible children. - if (alreadyComputedNextLayout == 0) { - layoutNode(layoutContext, child, maxWidth, maxHeight, direction); - } - - // Absolute positioned elements do not take part of the layout, so we - // don't use them to compute mainContentDim - if (child.style.positionType == CSSPositionType.RELATIVE) { - nonFlexibleChildrenCount++; - // At this point we know the final size and margin of the element. - nextContentDim = (child.layout.dimensions[dim[mainAxis]] + child.style.margin.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + child.style.margin.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis])); - } - } - - // The element we are about to add would make us go to the next line - if (isNodeFlexWrap && - isMainDimDefined && - mainContentDim + nextContentDim > definedMainDim && - // If there's only one element, then it's bigger than the content - // and needs its own line - i != startLine) { - nonFlexibleChildrenCount--; - alreadyComputedNextLayout = 1; - break; - } - - // Disable simple stacking in the main axis for the current line as - // we found a non-trivial child. The remaining children will be laid out - // in . - if (isSimpleStackMain && - (child.style.positionType != CSSPositionType.RELATIVE || (child.style.positionType == CSSPositionType.RELATIVE && child.style.flex > 0))) { - isSimpleStackMain = false; - firstComplexMain = i; - } - - // Disable simple stacking in the cross axis for the current line as - // we found a non-trivial child. The remaining children will be laid out - // in . - if (isSimpleStackCross && - (child.style.positionType != CSSPositionType.RELATIVE || - (alignItem != CSSAlign.STRETCH && alignItem != CSSAlign.FLEX_START) || - (alignItem == CSSAlign.STRETCH && !isCrossDimDefined))) { - isSimpleStackCross = false; - firstComplexCross = i; - } - - if (isSimpleStackMain) { - child.layout.position[pos[mainAxis]] += mainDim; - if (isMainDimDefined) { - child.layout.position[trailing[mainAxis]] = node.layout.dimensions[dim[mainAxis]] - child.layout.dimensions[dim[mainAxis]] - child.layout.position[pos[mainAxis]]; - } - - mainDim += (child.layout.dimensions[dim[mainAxis]] + child.style.margin.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + child.style.margin.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis])); - crossDim = Math.max(crossDim, boundAxis(child, crossAxis, (child.layout.dimensions[dim[crossAxis]] + child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis])))); - } - - if (isSimpleStackCross) { - child.layout.position[pos[crossAxis]] += linesCrossDim + leadingPaddingAndBorderCross; - if (isCrossDimDefined) { - child.layout.position[trailing[crossAxis]] = node.layout.dimensions[dim[crossAxis]] - child.layout.dimensions[dim[crossAxis]] - child.layout.position[pos[crossAxis]]; - } - } - - alreadyComputedNextLayout = 0; - mainContentDim += nextContentDim; - endLine = i + 1; + + i++; + endOfLineIndex++; } - - // Layout flexible children and allocate empty space + + // If we don't need to measure the cross axis, we can skip the entire flex step. + boolean canSkipFlex = !performLayout && measureModeCrossDim == CSSMeasureMode.EXACTLY; // In order to position the elements in the main axis, we have two // controls. The space between the beginning and the first element @@ -579,212 +795,300 @@ public class LayoutEngine { float leadingMainDim = 0; float betweenMainDim = 0; - // The remaining available space that needs to be allocated - float remainingMainDim = 0; - if (isMainDimDefined) { - remainingMainDim = definedMainDim - mainContentDim; - } else { - remainingMainDim = Math.max(mainContentDim, 0) - mainContentDim; + // STEP 5: RESOLVING FLEXIBLE LENGTHS ON MAIN AXIS + // Calculate the remaining available space that needs to be allocated. + // If the main dimension size isn't known, it is computed based on + // the line length, so there's no more space left to distribute. + float remainingFreeSpace = 0; + if (!Float.isNaN(availableInnerMainDim)) { + remainingFreeSpace = availableInnerMainDim - sizeConsumedOnCurrentLine; + } else if (sizeConsumedOnCurrentLine < 0) { + // availableInnerMainDim is indefinite which means the node is being sized based on its content. + // sizeConsumedOnCurrentLine is negative which means the node will allocate 0 pixels for + // its content. Consequently, remainingFreeSpace is 0 - sizeConsumedOnCurrentLine. + remainingFreeSpace = -sizeConsumedOnCurrentLine; + } + + float originalRemainingFreeSpace = remainingFreeSpace; + float deltaFreeSpace = 0; + + if (!canSkipFlex) { + float childFlexBasis; + float flexShrinkScaledFactor; + float flexGrowFactor; + float baseMainSize; + float boundMainSize; + + // Do two passes over the flex items to figure out how to distribute the remaining space. + // The first pass finds the items whose min/max constraints trigger, freezes them at those + // sizes, and excludes those sizes from the remaining space. The second pass sets the size + // of each flexible item. It distributes the remaining space amongst the items whose min/max + // constraints didn't trigger in pass 1. For the other items, it sets their sizes by forcing + // their min/max constraints to trigger again. + // + // This two pass approach for resolving min/max constraints deviates from the spec. The + // spec (https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths) describes a process + // that needs to be repeated a variable number of times. The algorithm implemented here + // won't handle all cases but it was simpler to implement and it mitigates performance + // concerns because we know exactly how many passes it'll do. + + // First pass: detect the flex items whose min/max constraints trigger + float deltaFlexShrinkScaledFactors = 0; + float deltaFlexGrowFactors = 0; + currentRelativeChild = firstRelativeChild; + while (currentRelativeChild != null) { + childFlexBasis = currentRelativeChild.layout.flexBasis; + + if (remainingFreeSpace < 0) { + flexShrinkScaledFactor = getFlexShrinkFactor(currentRelativeChild) * childFlexBasis; + + // Is this child able to shrink? + if (flexShrinkScaledFactor != 0) { + baseMainSize = childFlexBasis + + remainingFreeSpace / totalFlexShrinkScaledFactors * flexShrinkScaledFactor; + boundMainSize = boundAxis(currentRelativeChild, mainAxis, baseMainSize); + if (baseMainSize != boundMainSize) { + // By excluding this item's size and flex factor from remaining, this item's + // min/max constraints should also trigger in the second pass resulting in the + // item's size calculation being identical in the first and second passes. + deltaFreeSpace -= boundMainSize - childFlexBasis; + deltaFlexShrinkScaledFactors -= flexShrinkScaledFactor; + } + } + } else if (remainingFreeSpace > 0) { + flexGrowFactor = getFlexGrowFactor(currentRelativeChild); + + // Is this child able to grow? + if (flexGrowFactor != 0) { + baseMainSize = childFlexBasis + + remainingFreeSpace / totalFlexGrowFactors * flexGrowFactor; + boundMainSize = boundAxis(currentRelativeChild, mainAxis, baseMainSize); + if (baseMainSize != boundMainSize) { + // By excluding this item's size and flex factor from remaining, this item's + // min/max constraints should also trigger in the second pass resulting in the + // item's size calculation being identical in the first and second passes. + deltaFreeSpace -= boundMainSize - childFlexBasis; + deltaFlexGrowFactors -= flexGrowFactor; + } + } + } + + currentRelativeChild = currentRelativeChild.nextChild; + } + + totalFlexShrinkScaledFactors += deltaFlexShrinkScaledFactors; + totalFlexGrowFactors += deltaFlexGrowFactors; + remainingFreeSpace += deltaFreeSpace; + + // Second pass: resolve the sizes of the flexible items + deltaFreeSpace = 0; + currentRelativeChild = firstRelativeChild; + while (currentRelativeChild != null) { + childFlexBasis = currentRelativeChild.layout.flexBasis; + float updatedMainSize = childFlexBasis; + + if (remainingFreeSpace < 0) { + flexShrinkScaledFactor = getFlexShrinkFactor(currentRelativeChild) * childFlexBasis; + + // Is this child able to shrink? + if (flexShrinkScaledFactor != 0) { + updatedMainSize = boundAxis(currentRelativeChild, mainAxis, childFlexBasis + + remainingFreeSpace / totalFlexShrinkScaledFactors * flexShrinkScaledFactor); + } + } else if (remainingFreeSpace > 0) { + flexGrowFactor = getFlexGrowFactor(currentRelativeChild); + + // Is this child able to grow? + if (flexGrowFactor != 0) { + updatedMainSize = boundAxis(currentRelativeChild, mainAxis, childFlexBasis + + remainingFreeSpace / totalFlexGrowFactors * flexGrowFactor); + } + } + + deltaFreeSpace -= updatedMainSize - childFlexBasis; + + if (isMainAxisRow) { + childWidth = updatedMainSize + (currentRelativeChild.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW]) + currentRelativeChild.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW])); + childWidthMeasureMode = CSSMeasureMode.EXACTLY; + + if (!(currentRelativeChild.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0)) { + childHeight = availableInnerCrossDim; + childHeightMeasureMode = Float.isNaN(childHeight) ? CSSMeasureMode.UNDEFINED : CSSMeasureMode.AT_MOST; + } else { + childHeight = currentRelativeChild.style.dimensions[DIMENSION_HEIGHT] + (currentRelativeChild.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + currentRelativeChild.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])); + childHeightMeasureMode = CSSMeasureMode.EXACTLY; + } + } else { + childHeight = updatedMainSize + (currentRelativeChild.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + currentRelativeChild.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])); + childHeightMeasureMode = CSSMeasureMode.EXACTLY; + + if (!(currentRelativeChild.style.dimensions[dim[CSS_FLEX_DIRECTION_ROW]] >= 0.0)) { + childWidth = availableInnerCrossDim; + childWidthMeasureMode = Float.isNaN(childWidth) ? CSSMeasureMode.UNDEFINED : CSSMeasureMode.AT_MOST; + } else { + childWidth = currentRelativeChild.style.dimensions[DIMENSION_WIDTH] + (currentRelativeChild.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW]) + currentRelativeChild.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW])); + childWidthMeasureMode = CSSMeasureMode.EXACTLY; + } + } + + boolean requiresStretchLayout = !(currentRelativeChild.style.dimensions[dim[crossAxis]] >= 0.0) && + getAlignItem(node, currentRelativeChild) == CSSAlign.STRETCH; + + // Recursively call the layout algorithm for this child with the updated main size. + layoutNodeInternal(layoutContext, currentRelativeChild, childWidth, childHeight, direction, childWidthMeasureMode, childHeightMeasureMode, performLayout && !requiresStretchLayout, "flex"); + + currentRelativeChild = currentRelativeChild.nextChild; + } + } + + remainingFreeSpace = originalRemainingFreeSpace + deltaFreeSpace; + + // STEP 6: MAIN-AXIS JUSTIFICATION & CROSS-AXIS SIZE DETERMINATION + + // At this point, all the children have their dimensions set in the main axis. + // Their dimensions are also set in the cross axis with the exception of items + // that are aligned "stretch". We need to compute these stretch values and + // set the final positions. + + // If we are using "at most" rules in the main axis, we won't distribute + // any remaining space at this point. + if (measureModeMainDim == CSSMeasureMode.AT_MOST) { + remainingFreeSpace = 0; } - // If there are flexible children in the mix, they are going to fill the - // remaining space - if (flexibleChildrenCount != 0) { - float flexibleMainDim = remainingMainDim / totalFlexible; - float baseMainDim; - float boundMainDim; - - // If the flex share of remaining space doesn't meet min/max bounds, - // remove this child from flex calculations. - currentFlexChild = firstFlexChild; - while (currentFlexChild != null) { - baseMainDim = flexibleMainDim * currentFlexChild.style.flex + - ((currentFlexChild.style.padding.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + currentFlexChild.style.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis])) + (currentFlexChild.style.padding.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + currentFlexChild.style.border.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]))); - boundMainDim = boundAxis(currentFlexChild, mainAxis, baseMainDim); - - if (baseMainDim != boundMainDim) { - remainingMainDim -= boundMainDim; - totalFlexible -= currentFlexChild.style.flex; - } - - currentFlexChild = currentFlexChild.nextFlexChild; - } - 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; - } - - currentFlexChild = firstFlexChild; - while (currentFlexChild != null) { - // At this point we know the final size of the element in the main - // dimension - currentFlexChild.layout.dimensions[dim[mainAxis]] = boundAxis(currentFlexChild, mainAxis, - flexibleMainDim * currentFlexChild.style.flex + - ((currentFlexChild.style.padding.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + currentFlexChild.style.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis])) + (currentFlexChild.style.padding.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + currentFlexChild.style.border.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]))) - ); - - maxWidth = CSSConstants.UNDEFINED; - if ((!Float.isNaN(node.layout.dimensions[dim[resolvedRowAxis]]) && node.layout.dimensions[dim[resolvedRowAxis]] >= 0.0)) { - maxWidth = node.layout.dimensions[dim[resolvedRowAxis]] - - paddingAndBorderAxisResolvedRow; - } else if (!isMainRowDirection) { - maxWidth = parentMaxWidth - - (node.style.margin.getWithFallback(leadingSpacing[resolvedRowAxis], leading[resolvedRowAxis]) + node.style.margin.getWithFallback(trailingSpacing[resolvedRowAxis], trailing[resolvedRowAxis])) - - paddingAndBorderAxisResolvedRow; - } - maxHeight = CSSConstants.UNDEFINED; - if ((!Float.isNaN(node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]]) && node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0)) { - maxHeight = node.layout.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] - - paddingAndBorderAxisColumn; - } else if (isMainRowDirection) { - maxHeight = parentMaxHeight - - (node.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + node.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])) - - paddingAndBorderAxisColumn; - } - - // And we recursively call the layout algorithm for this child - layoutNode(layoutContext, currentFlexChild, maxWidth, maxHeight, direction); - - child = currentFlexChild; - currentFlexChild = currentFlexChild.nextFlexChild; - child.nextFlexChild = null; - } - - // We use justifyContent to figure out how to allocate the remaining - // space available - } else if (justifyContent != CSSJustify.FLEX_START) { + // Use justifyContent to figure out how to allocate the remaining space + // available in the main axis. + if (justifyContent != CSSJustify.FLEX_START) { if (justifyContent == CSSJustify.CENTER) { - leadingMainDim = remainingMainDim / 2; + leadingMainDim = remainingFreeSpace / 2; } else if (justifyContent == CSSJustify.FLEX_END) { - leadingMainDim = remainingMainDim; + leadingMainDim = remainingFreeSpace; } else if (justifyContent == CSSJustify.SPACE_BETWEEN) { - remainingMainDim = Math.max(remainingMainDim, 0); - if (flexibleChildrenCount + nonFlexibleChildrenCount - 1 != 0) { - betweenMainDim = remainingMainDim / - (flexibleChildrenCount + nonFlexibleChildrenCount - 1); + remainingFreeSpace = Math.max(remainingFreeSpace, 0); + if (itemsOnLine > 1) { + betweenMainDim = remainingFreeSpace / (itemsOnLine - 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); + betweenMainDim = remainingFreeSpace / itemsOnLine; leadingMainDim = betweenMainDim / 2; } } - // Position elements in the main axis and compute dimensions + float mainDim = leadingPaddingAndBorderMain + leadingMainDim; + float crossDim = 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! - mainDim += leadingMainDim; - - for (i = firstComplexMain; i < endLine; ++i) { + for (i = startOfLineIndex; i < endOfLineIndex; ++i) { child = node.getChildAt(i); if (child.style.positionType == CSSPositionType.ABSOLUTE && !Float.isNaN(child.style.position[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]] = (Float.isNaN(child.style.position[leading[mainAxis]]) ? 0 : child.style.position[leading[mainAxis]]) + - node.style.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + - child.style.margin.getWithFallback(leadingSpacing[mainAxis], 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; - - // Define the trailing position accordingly. - if (isMainDimDefined) { - child.layout.position[trailing[mainAxis]] = node.layout.dimensions[dim[mainAxis]] - child.layout.dimensions[dim[mainAxis]] - child.layout.position[pos[mainAxis]]; + if (performLayout) { + // 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]] = (Float.isNaN(child.style.position[leading[mainAxis]]) ? 0 : child.style.position[leading[mainAxis]]) + + node.style.border.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + + child.style.margin.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]); } - - // Now that we placed the element, we need to update the variables - // We only need to do that for relative elements. Absolute elements + } else { + if (performLayout) { + // 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 need to do that only for relative elements. Absolute elements // do not take part in that phase. if (child.style.positionType == CSSPositionType.RELATIVE) { - // The main dimension is the sum of all the elements dimension plus - // the spacing. - mainDim += betweenMainDim + (child.layout.dimensions[dim[mainAxis]] + child.style.margin.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + child.style.margin.getWithFallback(trailingSpacing[mainAxis], trailing[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, boundAxis(child, crossAxis, (child.layout.dimensions[dim[crossAxis]] + child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis])))); + if (canSkipFlex) { + // If we skipped the flex step, then we can't rely on the measuredDims because + // they weren't computed. This means we can't call getDimWithMargin. + mainDim += betweenMainDim + (child.style.margin.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + child.style.margin.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis])) + child.layout.flexBasis; + crossDim = availableInnerCrossDim; + } else { + // The main dimension is the sum of all the elements dimension plus + // the spacing. + mainDim += betweenMainDim + (child.layout.measuredDimensions[dim[mainAxis]] + child.style.margin.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + child.style.margin.getWithFallback(trailingSpacing[mainAxis], trailing[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, (child.layout.measuredDimensions[dim[crossAxis]] + child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]))); + } } } } - float containerCrossAxis = node.layout.dimensions[dim[crossAxis]]; - if (!isCrossDimDefined) { - 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 - boundAxis(node, crossAxis, crossDim + paddingAndBorderAxisCross), - paddingAndBorderAxisCross - ); + mainDim += trailingPaddingAndBorderMain; + + float containerCrossAxis = availableInnerCrossDim; + if (measureModeCrossDim == CSSMeasureMode.UNDEFINED || measureModeCrossDim == CSSMeasureMode.AT_MOST) { + // Compute the cross axis from the max cross dimension of the children. + containerCrossAxis = boundAxis(node, crossAxis, crossDim + paddingAndBorderAxisCross) - paddingAndBorderAxisCross; + + if (measureModeCrossDim == CSSMeasureMode.AT_MOST) { + containerCrossAxis = Math.min(containerCrossAxis, availableInnerCrossDim); + } } - // Position elements in the cross axis - for (i = firstComplexCross; i < endLine; ++i) { - child = node.getChildAt(i); + // If there's no flex wrap, the cross dimension is defined by the container. + if (!isNodeFlexWrap && measureModeCrossDim == CSSMeasureMode.EXACTLY) { + crossDim = availableInnerCrossDim; + } - if (child.style.positionType == CSSPositionType.ABSOLUTE && - !Float.isNaN(child.style.position[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]] = (Float.isNaN(child.style.position[leading[crossAxis]]) ? 0 : child.style.position[leading[crossAxis]]) + - node.style.border.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + - child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]); + // Clamp to the min/max size specified on the container. + crossDim = boundAxis(node, crossAxis, crossDim + paddingAndBorderAxisCross) - paddingAndBorderAxisCross; - } else { - float leadingCrossDim = leadingPaddingAndBorderCross; + // STEP 7: CROSS-AXIS ALIGNMENT + // We can skip child alignment if we're just measuring the container. + if (performLayout) { + for (i = startOfLineIndex; i < endOfLineIndex; ++i) { + child = node.getChildAt(i); - // For a relative children, we're either using alignItems (parent) or - // alignSelf (child) in order to determine the position in the cross axis - if (child.style.positionType == CSSPositionType.RELATIVE) { - /*eslint-disable */ - // This variable is intentionally re-defined as the code is transpiled to a block scope language + if (child.style.positionType == CSSPositionType.ABSOLUTE) { + // If the child is absolutely positioned and has a top/left/bottom/right + // set, override all the previously computed positions to set it correctly. + if (!Float.isNaN(child.style.position[leading[crossAxis]])) { + child.layout.position[pos[crossAxis]] = (Float.isNaN(child.style.position[leading[crossAxis]]) ? 0 : child.style.position[leading[crossAxis]]) + + node.style.border.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + + child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]); + } else { + child.layout.position[pos[crossAxis]] = leadingPaddingAndBorderCross + + child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]); + } + } else { + float leadingCrossDim = leadingPaddingAndBorderCross; + + // For a relative children, we're either using alignItems (parent) or + // alignSelf (child) in order to determine the position in the cross axis CSSAlign alignItem = getAlignItem(node, child); - /*eslint-enable */ + + // If the child uses align stretch, we need to lay it out one more time, this time + // forcing the cross-axis size to be the computed cross size for the current line. if (alignItem == CSSAlign.STRETCH) { - // You can only stretch if the dimension has not already been defined - // previously. - if (!(!Float.isNaN(child.style.dimensions[dim[crossAxis]]) && child.style.dimensions[dim[crossAxis]] >= 0.0)) { - float dimCrossAxis = child.layout.dimensions[dim[crossAxis]]; - child.layout.dimensions[dim[crossAxis]] = Math.max( - boundAxis(child, crossAxis, containerCrossAxis - - paddingAndBorderAxisCross - (child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]))), - // You never want to go smaller than padding - ((child.style.padding.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.style.border.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis])) + (child.style.padding.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]) + child.style.border.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]))) - ); - - // If the size has changed, and this child has children we need to re-layout this child - if (dimCrossAxis != child.layout.dimensions[dim[crossAxis]] && child.getChildCount() > 0) { - // Reset child margins before re-layout as they are added back in layoutNode and would be doubled - child.layout.position[leading[mainAxis]] -= child.style.margin.getWithFallback(leadingSpacing[mainAxis], leading[mainAxis]) + - getRelativePosition(child, mainAxis); - child.layout.position[trailing[mainAxis]] -= child.style.margin.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + - getRelativePosition(child, mainAxis); - child.layout.position[leading[crossAxis]] -= child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + - getRelativePosition(child, crossAxis); - child.layout.position[trailing[crossAxis]] -= child.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]) + - getRelativePosition(child, crossAxis); - - layoutNode(layoutContext, child, maxWidth, maxHeight, direction); - } + childWidth = child.layout.measuredDimensions[DIMENSION_WIDTH] + (child.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW]) + child.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW])); + childHeight = child.layout.measuredDimensions[DIMENSION_HEIGHT] + (child.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + child.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])); + boolean isCrossSizeDefinite = false; + + if (isMainAxisRow) { + isCrossSizeDefinite = (child.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0); + childHeight = crossDim; + } else { + isCrossSizeDefinite = (child.style.dimensions[dim[CSS_FLEX_DIRECTION_ROW]] >= 0.0); + childWidth = crossDim; + } + + // If the child defines a definite size for its cross axis, there's no need to stretch. + if (!isCrossSizeDefinite) { + childWidthMeasureMode = Float.isNaN(childWidth) ? CSSMeasureMode.UNDEFINED : CSSMeasureMode.EXACTLY; + childHeightMeasureMode = Float.isNaN(childHeight) ? CSSMeasureMode.UNDEFINED : CSSMeasureMode.EXACTLY; + layoutNodeInternal(layoutContext, child, childWidth, childHeight, direction, childWidthMeasureMode, childHeightMeasureMode, true, "stretch"); } } else if (alignItem != CSSAlign.FLEX_START) { - // The remaining space between the parent dimensions+padding and child - // dimensions+margin. - float remainingCrossDim = containerCrossAxis - - paddingAndBorderAxisCross - (child.layout.dimensions[dim[crossAxis]] + child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis])); + float remainingCrossDim = containerCrossAxis - (child.layout.measuredDimensions[dim[crossAxis]] + child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis])); if (alignItem == CSSAlign.CENTER) { leadingCrossDim += remainingCrossDim / 2; @@ -792,41 +1096,25 @@ public class LayoutEngine { leadingCrossDim += remainingCrossDim; } } - } - // And we apply the position - child.layout.position[pos[crossAxis]] += linesCrossDim + leadingCrossDim; - - // Define the trailing position accordingly. - if (isCrossDimDefined) { - child.layout.position[trailing[crossAxis]] = node.layout.dimensions[dim[crossAxis]] - child.layout.dimensions[dim[crossAxis]] - child.layout.position[pos[crossAxis]]; + // And we apply the position + child.layout.position[pos[crossAxis]] += totalLineCrossDim + leadingCrossDim; } } } - linesCrossDim += crossDim; - linesMainDim = Math.max(linesMainDim, mainDim); - linesCount += 1; - startLine = endLine; + totalLineCrossDim += crossDim; + maxLineMainDim = Math.max(maxLineMainDim, mainDim); + + // Reset variables for new line. + lineCount++; + startOfLineIndex = endOfLineIndex; + endOfLineIndex = startOfLineIndex; } - // - // - // Note(prenaux): 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 && isCrossDimDefined) { - float nodeCrossAxisInnerSize = node.layout.dimensions[dim[crossAxis]] - - paddingAndBorderAxisCross; - float remainingAlignContentDim = nodeCrossAxisInnerSize - linesCrossDim; + // STEP 8: MULTI-LINE CONTENT ALIGNMENT + if (lineCount > 1 && performLayout && !Float.isNaN(availableInnerCrossDim)) { + float remainingAlignContentDim = availableInnerCrossDim - totalLineCrossDim; float crossDimLead = 0; float currentLead = leadingPaddingAndBorderCross; @@ -837,53 +1125,54 @@ public class LayoutEngine { } else if (alignContent == CSSAlign.CENTER) { currentLead += remainingAlignContentDim / 2; } else if (alignContent == CSSAlign.STRETCH) { - if (nodeCrossAxisInnerSize > linesCrossDim) { - crossDimLead = (remainingAlignContentDim / linesCount); + if (availableInnerCrossDim > totalLineCrossDim) { + crossDimLead = (remainingAlignContentDim / lineCount); } } int endIndex = 0; - for (i = 0; i < linesCount; ++i) { + for (i = 0; i < lineCount; ++i) { int startIndex = endIndex; + int j; // compute the line's height and find the endIndex float lineHeight = 0; - for (ii = startIndex; ii < childCount; ++ii) { - child = node.getChildAt(ii); + for (j = startIndex; j < childCount; ++j) { + child = node.getChildAt(j); if (child.style.positionType != CSSPositionType.RELATIVE) { continue; } if (child.lineIndex != i) { break; } - if ((!Float.isNaN(child.layout.dimensions[dim[crossAxis]]) && child.layout.dimensions[dim[crossAxis]] >= 0.0)) { - lineHeight = Math.max( - lineHeight, - child.layout.dimensions[dim[crossAxis]] + (child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis])) - ); + if ((child.layout.measuredDimensions[dim[crossAxis]] >= 0.0)) { + lineHeight = Math.max(lineHeight, + child.layout.measuredDimensions[dim[crossAxis]] + (child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]) + child.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]))); } } - endIndex = ii; + endIndex = j; lineHeight += crossDimLead; - for (ii = startIndex; ii < endIndex; ++ii) { - child = node.getChildAt(ii); - if (child.style.positionType != CSSPositionType.RELATIVE) { - continue; - } + if (performLayout) { + for (j = startIndex; j < endIndex; ++j) { + child = node.getChildAt(j); + if (child.style.positionType != CSSPositionType.RELATIVE) { + continue; + } - CSSAlign alignContentAlignItem = getAlignItem(node, child); - if (alignContentAlignItem == CSSAlign.FLEX_START) { - child.layout.position[pos[crossAxis]] = currentLead + child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]); - } else if (alignContentAlignItem == CSSAlign.FLEX_END) { - child.layout.position[pos[crossAxis]] = currentLead + lineHeight - child.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]) - child.layout.dimensions[dim[crossAxis]]; - } else if (alignContentAlignItem == CSSAlign.CENTER) { - float childHeight = child.layout.dimensions[dim[crossAxis]]; - child.layout.position[pos[crossAxis]] = currentLead + (lineHeight - childHeight) / 2; - } else if (alignContentAlignItem == CSSAlign.STRETCH) { - child.layout.position[pos[crossAxis]] = currentLead + child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]); - // TODO(prenaux): Correctly set the height of items with undefined - // (auto) crossAxis dimension. + CSSAlign alignContentAlignItem = getAlignItem(node, child); + if (alignContentAlignItem == CSSAlign.FLEX_START) { + child.layout.position[pos[crossAxis]] = currentLead + child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]); + } else if (alignContentAlignItem == CSSAlign.FLEX_END) { + child.layout.position[pos[crossAxis]] = currentLead + lineHeight - child.style.margin.getWithFallback(trailingSpacing[crossAxis], trailing[crossAxis]) - child.layout.measuredDimensions[dim[crossAxis]]; + } else if (alignContentAlignItem == CSSAlign.CENTER) { + childHeight = child.layout.measuredDimensions[dim[crossAxis]]; + child.layout.position[pos[crossAxis]] = currentLead + (lineHeight - childHeight) / 2; + } else if (alignContentAlignItem == CSSAlign.STRETCH) { + child.layout.position[pos[crossAxis]] = currentLead + child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]); + // TODO(prenaux): Correctly set the height of items with indefinite + // (auto) crossAxis dimension. + } } } @@ -891,93 +1180,148 @@ public class LayoutEngine { } } - boolean needsMainTrailingPos = false; - boolean needsCrossTrailingPos = false; + // STEP 9: COMPUTING FINAL DIMENSIONS + node.layout.measuredDimensions[DIMENSION_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, availableWidth - marginAxisRow); + node.layout.measuredDimensions[DIMENSION_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, availableHeight - marginAxisColumn); - // 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 (!isMainDimDefined) { - node.layout.dimensions[dim[mainAxis]] = Math.max( - // We're missing the last padding at this point to get the final - // dimension - boundAxis(node, mainAxis, linesMainDim + (node.style.padding.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]) + node.style.border.getWithFallback(trailingSpacing[mainAxis], trailing[mainAxis]))), - // We can never assign a width smaller than the padding and borders - paddingAndBorderAxisMain - ); + // If the user didn't specify a width or height for the node, set the + // dimensions based on the children. + if (measureModeMainDim == CSSMeasureMode.UNDEFINED) { + // Clamp the size to the min/max size, if specified, and make sure it + // doesn't go below the padding and border amount. + node.layout.measuredDimensions[dim[mainAxis]] = boundAxis(node, mainAxis, maxLineMainDim); + } else if (measureModeMainDim == CSSMeasureMode.AT_MOST) { + node.layout.measuredDimensions[dim[mainAxis]] = Math.max( + Math.min(availableInnerMainDim + paddingAndBorderAxisMain, + boundAxisWithinMinAndMax(node, mainAxis, maxLineMainDim)), + paddingAndBorderAxisMain); + } + + if (measureModeCrossDim == CSSMeasureMode.UNDEFINED) { + // Clamp the size to the min/max size, if specified, and make sure it + // doesn't go below the padding and border amount. + node.layout.measuredDimensions[dim[crossAxis]] = boundAxis(node, crossAxis, totalLineCrossDim + paddingAndBorderAxisCross); + } else if (measureModeCrossDim == CSSMeasureMode.AT_MOST) { + node.layout.measuredDimensions[dim[crossAxis]] = Math.max( + Math.min(availableInnerCrossDim + paddingAndBorderAxisCross, + boundAxisWithinMinAndMax(node, crossAxis, totalLineCrossDim + paddingAndBorderAxisCross)), + paddingAndBorderAxisCross); + } + + // STEP 10: SETTING TRAILING POSITIONS FOR CHILDREN + if (performLayout) { + boolean needsMainTrailingPos = false; + boolean needsCrossTrailingPos = false; if (mainAxis == CSS_FLEX_DIRECTION_ROW_REVERSE || mainAxis == CSS_FLEX_DIRECTION_COLUMN_REVERSE) { needsMainTrailingPos = true; } - } - - if (!isCrossDimDefined) { - node.layout.dimensions[dim[crossAxis]] = 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 - boundAxis(node, crossAxis, linesCrossDim + paddingAndBorderAxisCross), - paddingAndBorderAxisCross - ); if (crossAxis == CSS_FLEX_DIRECTION_ROW_REVERSE || crossAxis == CSS_FLEX_DIRECTION_COLUMN_REVERSE) { needsCrossTrailingPos = true; } - } - // Set trailing position if necessary - if (needsMainTrailingPos || needsCrossTrailingPos) { - for (i = 0; i < childCount; ++i) { - child = node.getChildAt(i); + // Set trailing position if necessary. + if (needsMainTrailingPos || needsCrossTrailingPos) { + for (i = 0; i < childCount; ++i) { + child = node.getChildAt(i); - if (needsMainTrailingPos) { - child.layout.position[trailing[mainAxis]] = node.layout.dimensions[dim[mainAxis]] - child.layout.dimensions[dim[mainAxis]] - child.layout.position[pos[mainAxis]]; - } + if (needsMainTrailingPos) { + child.layout.position[trailing[mainAxis]] = node.layout.measuredDimensions[dim[mainAxis]] - (child.style.positionType == CSSPositionType.ABSOLUTE ? 0 : child.layout.measuredDimensions[dim[mainAxis]]) - child.layout.position[pos[mainAxis]]; + } - if (needsCrossTrailingPos) { - child.layout.position[trailing[crossAxis]] = node.layout.dimensions[dim[crossAxis]] - child.layout.dimensions[dim[crossAxis]] - child.layout.position[pos[crossAxis]]; + if (needsCrossTrailingPos) { + child.layout.position[trailing[crossAxis]] = node.layout.measuredDimensions[dim[crossAxis]] - (child.style.positionType == CSSPositionType.ABSOLUTE ? 0 : child.layout.measuredDimensions[dim[crossAxis]]) - child.layout.position[pos[crossAxis]]; + } } } } - - // Calculate dimensions for absolutely positioned elements + + // STEP 11: SIZING AND POSITIONING ABSOLUTE CHILDREN currentAbsoluteChild = firstAbsoluteChild; while (currentAbsoluteChild != null) { - // 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 (ii = 0; ii < 2; ii++) { - axis = (ii != 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN; + // Now that we know the bounds of the container, perform layout again on the + // absolutely-positioned children. + if (performLayout) { - if ((!Float.isNaN(node.layout.dimensions[dim[axis]]) && node.layout.dimensions[dim[axis]] >= 0.0) && - !(!Float.isNaN(currentAbsoluteChild.style.dimensions[dim[axis]]) && currentAbsoluteChild.style.dimensions[dim[axis]] >= 0.0) && - !Float.isNaN(currentAbsoluteChild.style.position[leading[axis]]) && - !Float.isNaN(currentAbsoluteChild.style.position[trailing[axis]])) { - currentAbsoluteChild.layout.dimensions[dim[axis]] = Math.max( - boundAxis(currentAbsoluteChild, axis, node.layout.dimensions[dim[axis]] - - (node.style.border.getWithFallback(leadingSpacing[axis], leading[axis]) + node.style.border.getWithFallback(trailingSpacing[axis], trailing[axis])) - - (currentAbsoluteChild.style.margin.getWithFallback(leadingSpacing[axis], leading[axis]) + currentAbsoluteChild.style.margin.getWithFallback(trailingSpacing[axis], trailing[axis])) - - (Float.isNaN(currentAbsoluteChild.style.position[leading[axis]]) ? 0 : currentAbsoluteChild.style.position[leading[axis]]) - - (Float.isNaN(currentAbsoluteChild.style.position[trailing[axis]]) ? 0 : currentAbsoluteChild.style.position[trailing[axis]]) - ), - // You never want to go smaller than padding - ((currentAbsoluteChild.style.padding.getWithFallback(leadingSpacing[axis], leading[axis]) + currentAbsoluteChild.style.border.getWithFallback(leadingSpacing[axis], leading[axis])) + (currentAbsoluteChild.style.padding.getWithFallback(trailingSpacing[axis], trailing[axis]) + currentAbsoluteChild.style.border.getWithFallback(trailingSpacing[axis], trailing[axis]))) - ); + childWidth = CSSConstants.UNDEFINED; + childHeight = CSSConstants.UNDEFINED; + + if ((currentAbsoluteChild.style.dimensions[dim[CSS_FLEX_DIRECTION_ROW]] >= 0.0)) { + childWidth = currentAbsoluteChild.style.dimensions[DIMENSION_WIDTH] + (currentAbsoluteChild.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW]) + currentAbsoluteChild.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW])); + } else { + // If the child doesn't have a specified width, compute the width based on the left/right offsets if they're defined. + if (!Float.isNaN(currentAbsoluteChild.style.position[POSITION_LEFT]) && !Float.isNaN(currentAbsoluteChild.style.position[POSITION_RIGHT])) { + childWidth = node.layout.measuredDimensions[DIMENSION_WIDTH] - + (node.style.border.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW]) + node.style.border.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW])) - + (currentAbsoluteChild.style.position[POSITION_LEFT] + currentAbsoluteChild.style.position[POSITION_RIGHT]); + childWidth = boundAxis(currentAbsoluteChild, CSS_FLEX_DIRECTION_ROW, childWidth); + } + } + + if ((currentAbsoluteChild.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0)) { + childHeight = currentAbsoluteChild.style.dimensions[DIMENSION_HEIGHT] + (currentAbsoluteChild.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + currentAbsoluteChild.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])); + } else { + // If the child doesn't have a specified height, compute the height based on the top/bottom offsets if they're defined. + if (!Float.isNaN(currentAbsoluteChild.style.position[POSITION_TOP]) && !Float.isNaN(currentAbsoluteChild.style.position[POSITION_BOTTOM])) { + childHeight = node.layout.measuredDimensions[DIMENSION_HEIGHT] - + (node.style.border.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + node.style.border.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])) - + (currentAbsoluteChild.style.position[POSITION_TOP] + currentAbsoluteChild.style.position[POSITION_BOTTOM]); + childHeight = boundAxis(currentAbsoluteChild, CSS_FLEX_DIRECTION_COLUMN, childHeight); + } } - if (!Float.isNaN(currentAbsoluteChild.style.position[trailing[axis]]) && - !!Float.isNaN(currentAbsoluteChild.style.position[leading[axis]])) { - currentAbsoluteChild.layout.position[leading[axis]] = - node.layout.dimensions[dim[axis]] - - currentAbsoluteChild.layout.dimensions[dim[axis]] - - (Float.isNaN(currentAbsoluteChild.style.position[trailing[axis]]) ? 0 : currentAbsoluteChild.style.position[trailing[axis]]); + // If we're still missing one or the other dimension, measure the content. + if (Float.isNaN(childWidth) || Float.isNaN(childHeight)) { + childWidthMeasureMode = Float.isNaN(childWidth) ? CSSMeasureMode.UNDEFINED : CSSMeasureMode.EXACTLY; + childHeightMeasureMode = Float.isNaN(childHeight) ? CSSMeasureMode.UNDEFINED : CSSMeasureMode.EXACTLY; + + // According to the spec, if the main size is not definite and the + // child's inline axis is parallel to the main axis (i.e. it's + // horizontal), the child should be sized using "UNDEFINED" in + // the main size. Otherwise use "AT_MOST" in the cross axis. + if (!isMainAxisRow && Float.isNaN(childWidth) && !Float.isNaN(availableInnerWidth)) { + childWidth = availableInnerWidth; + childWidthMeasureMode = CSSMeasureMode.AT_MOST; + } + + // The W3C spec doesn't say anything about the 'overflow' property, + // but all major browsers appear to implement the following logic. + if (node.style.overflow == CSSOverflow.HIDDEN) { + if (isMainAxisRow && Float.isNaN(childHeight) && !Float.isNaN(availableInnerHeight)) { + childHeight = availableInnerHeight; + childHeightMeasureMode = CSSMeasureMode.AT_MOST; + } + } + + layoutNodeInternal(layoutContext, currentAbsoluteChild, childWidth, childHeight, direction, childWidthMeasureMode, childHeightMeasureMode, false, "abs-measure"); + childWidth = currentAbsoluteChild.layout.measuredDimensions[DIMENSION_WIDTH] + (currentAbsoluteChild.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW]) + currentAbsoluteChild.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW])); + childHeight = currentAbsoluteChild.layout.measuredDimensions[DIMENSION_HEIGHT] + (currentAbsoluteChild.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) + currentAbsoluteChild.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN])); + } + + layoutNodeInternal(layoutContext, currentAbsoluteChild, childWidth, childHeight, direction, CSSMeasureMode.EXACTLY, CSSMeasureMode.EXACTLY, true, "abs-layout"); + + if (!Float.isNaN(currentAbsoluteChild.style.position[trailing[CSS_FLEX_DIRECTION_ROW]]) && + !!Float.isNaN(currentAbsoluteChild.style.position[leading[CSS_FLEX_DIRECTION_ROW]])) { + currentAbsoluteChild.layout.position[leading[CSS_FLEX_DIRECTION_ROW]] = + node.layout.measuredDimensions[dim[CSS_FLEX_DIRECTION_ROW]] - + currentAbsoluteChild.layout.measuredDimensions[dim[CSS_FLEX_DIRECTION_ROW]] - + (Float.isNaN(currentAbsoluteChild.style.position[trailing[CSS_FLEX_DIRECTION_ROW]]) ? 0 : currentAbsoluteChild.style.position[trailing[CSS_FLEX_DIRECTION_ROW]]); + } + + if (!Float.isNaN(currentAbsoluteChild.style.position[trailing[CSS_FLEX_DIRECTION_COLUMN]]) && + !!Float.isNaN(currentAbsoluteChild.style.position[leading[CSS_FLEX_DIRECTION_COLUMN]])) { + currentAbsoluteChild.layout.position[leading[CSS_FLEX_DIRECTION_COLUMN]] = + node.layout.measuredDimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] - + currentAbsoluteChild.layout.measuredDimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] - + (Float.isNaN(currentAbsoluteChild.style.position[trailing[CSS_FLEX_DIRECTION_COLUMN]]) ? 0 : currentAbsoluteChild.style.position[trailing[CSS_FLEX_DIRECTION_COLUMN]]); } } - child = currentAbsoluteChild; - currentAbsoluteChild = currentAbsoluteChild.nextAbsoluteChild; - child.nextAbsoluteChild = null; + currentAbsoluteChild = currentAbsoluteChild.nextChild; } - } /** END_GENERATED **/ + } } diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/README b/ReactAndroid/src/main/java/com/facebook/csslayout/README index 92aafb8d0..34a003a1b 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/README +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/README @@ -1,7 +1,7 @@ The source of truth for css-layout is: https://github.com/facebook/css-layout The code here should be kept in sync with GitHub. -HEAD at the time this code was synced: https://github.com/facebook/css-layout/commit/b0d00ad33850d83450139d994bded89d20ddac32 +HEAD at the time this code was synced: https://github.com/facebook/css-layout/commit/16f43dac87ace8b60e0e4c07a798a558c22bd21b There is generated code in: - README (this file) diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/README.facebook b/ReactAndroid/src/main/java/com/facebook/csslayout/README.facebook index 637f6609e..b4d26531d 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/README.facebook +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/README.facebook @@ -1,7 +1,7 @@ The source of truth for css-layout is: https://github.com/facebook/css-layout The code here should be kept in sync with GitHub. -HEAD at the time this code was synced: https://github.com/facebook/css-layout/commit/b0d00ad33850d83450139d994bded89d20ddac32 +HEAD at the time this code was synced: https://github.com/facebook/css-layout/commit/16f43dac87ace8b60e0e4c07a798a558c22bd21b There is generated code in: - README.facebook (this file)