diff --git a/README.md b/README.md index e3817e7a..607295e8 100644 --- a/README.md +++ b/README.md @@ -92,10 +92,19 @@ borderWidth, borderLeftWidth, borderRightWidth, borderTopWidth, borderBottomWidt flexDirection | 'column', 'row' justifyContent | 'flex-start', 'center', 'flex-end', 'space-between', 'space-around' alignItems, alignSelf | 'flex-start', 'center', 'flex-end', 'stretch' -flex | positive number +flex | number flexWrap | 'wrap', 'nowrap' position | 'relative', 'absolute' +overflow | 'visible', 'hidden' +- Rather than allowing arbitrary combinations of `flex-grow`, `flex-shrink`, and `flex-basis` the implementation only supports a few common combinations expressed as a single number using the `flex` attribute: + + css-layout `flex` value | W3C `flex` short-hand equivalent + ---|--- + n (where n > 0) | n 0 0 + 0 | 0 0 auto + -1 | 0 1 auto + - `inherit` value is not implemented because it's a way to disambiguate between multiple colliding rules. This should be done in a pre-processing step, not in the actual layout algorithm. @@ -118,6 +127,7 @@ div, span { border: 0 solid black; margin: 0; padding: 0; + min-width: 0; } ``` diff --git a/TestResult.xml b/TestResult.xml deleted file mode 100644 index 77989325..00000000 --- a/TestResult.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/dist/css-layout.h b/dist/css-layout.h index 5cf50521..95eab64d 100644 --- a/dist/css-layout.h +++ b/dist/css-layout.h @@ -48,6 +48,11 @@ typedef enum { 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 { @@ -83,7 +88,8 @@ typedef enum { 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 { @@ -91,20 +97,40 @@ typedef enum { 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 { @@ -120,6 +146,7 @@ typedef struct { 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]; @@ -147,8 +174,7 @@ struct css_node { 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); @@ -170,13 +196,9 @@ typedef enum { } css_print_options_t; void print_css_node(css_node_t *node, css_print_options_t options); -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); +void layoutNode(css_node_t *node, float availableWidth, float availableHeight, css_direction_t parentDirection); +bool isUndefined(float value); #endif @@ -190,6 +212,7 @@ void resetNodeLayout(css_node_t *node); * of patent rights can be found in the PATENTS file in the same directory. */ +#include #include #include #include @@ -212,6 +235,13 @@ __forceinline const float fmaxf(const float a, const float b) { #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); } @@ -223,12 +253,14 @@ static bool eq(float a, float b) { 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; @@ -256,21 +288,23 @@ void init_css_node(css_node_t *node) { 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); } @@ -280,13 +314,13 @@ static void indent(int n) { } } -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); } @@ -301,7 +335,7 @@ static bool four_equal(float four[4]) { static void print_css_node_rec( - css_node_t *node, + css_node_t* node, css_print_options_t options, int level ) { @@ -325,11 +359,11 @@ static void print_css_node_rec( 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) { @@ -370,6 +404,12 @@ static void print_css_node_rec( 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 { @@ -382,7 +422,7 @@ static void print_css_node_rec( } 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]); @@ -405,6 +445,10 @@ static void print_css_node_rec( 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', "); @@ -428,11 +472,10 @@ static void print_css_node_rec( } } -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, @@ -468,7 +511,41 @@ static bool isColumnDirection(css_flex_direction_t flex_direction) { 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]; } @@ -476,7 +553,7 @@ static float getLeadingMargin(css_node_t *node, css_flex_direction_t axis) { 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]; } @@ -484,7 +561,7 @@ static float getTrailingMargin(css_node_t *node, css_flex_direction_t axis) { 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) { @@ -498,7 +575,7 @@ static float getLeadingPadding(css_node_t *node, css_flex_direction_t axis) { 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) { @@ -512,7 +589,7 @@ static float getTrailingPadding(css_node_t *node, css_flex_direction_t axis) { 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) { @@ -526,7 +603,7 @@ static float getLeadingBorder(css_node_t *node, css_flex_direction_t axis) { 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) { @@ -540,34 +617,30 @@ static float getTrailingBorder(css_node_t *node, css_flex_direction_t axis) { 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) { @@ -577,7 +650,7 @@ static css_direction_t resolveDirection(css_node_t *node, css_direction_t parent 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; } @@ -601,46 +674,46 @@ static css_flex_direction_t getCrossFlexDirection(css_flex_direction_t flex_dire } } -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; @@ -648,7 +721,7 @@ static float getPosition(css_node_t *node, css_position_t position) { 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; @@ -672,32 +745,22 @@ static float boundAxis(css_node_t *node, css_flex_direction_t axis, float value) 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; @@ -705,352 +768,388 @@ static float getRelativePosition(css_node_t *node, css_flex_direction_t axis) { 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); + + 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); +} - // Handle width and height style attributes - setDimensionFromStyle(node, mainAxis); - setDimensionFromStyle(node, crossAxis); +// +// 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 **/ - // Set the resolved resolution in the node's layout + 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; + + if (widthMeasureMode == CSS_MEASURE_MODE_EXACTLY && heightMeasureMode == CSS_MEASURE_MODE_EXACTLY) { - 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; + // 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) { + + // Don't bother sizing the text if there's no horizontal 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); + + css_measure_mode_t measureModeMainDim = isMainAxisRow ? widthMeasureMode : heightMeasureMode; + css_measure_mode_t 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 + 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); + + if (performLayout) { + // Set the initial position (relative to the parent). + css_direction_t 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.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)); + } + } } - // 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 . - bool isSimpleStackMain = - (isMainDimDefined && justifyContent == CSS_JUSTIFY_FLEX_START) || - (!isMainDimDefined && justifyContent != CSS_JUSTIFY_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 . - bool isSimpleStackCross = true; - int firstComplexCross = childCount; + float totalFlexGrowFactors = 0; + float totalFlexShrinkScaledFactors = 0; - css_node_t* firstFlexChild = NULL; - css_node_t* currentFlexChild = NULL; + i = startOfLineIndex; - float mainDim = leadingPaddingAndBorderMain; - float crossDim = 0; + // Maintain a linked list of the child nodes that can shrink and/or grow. + css_node_t* firstRelativeChild = NULL; + css_node_t* currentRelativeChild = NULL; - float maxWidth = CSS_UNDEFINED; - float maxHeight = CSS_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->get_child(node->context, i); - child->line_index = linesCount; + child->line_index = lineCount; - 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 (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; } - 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) - ); - } + 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; } - - 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; + + 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. + 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 @@ -1058,212 +1157,300 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, float parentM 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; + } + + float remainingFreeSpaceAfterFlex = remainingFreeSpace; + + 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 deltaFreeSpace = 0; + 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; + 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; + deltaFlexGrowFactors -= flexGrowFactor; + } + } + } + + currentRelativeChild = currentRelativeChild->next_child; + } + + totalFlexShrinkScaledFactors += deltaFlexShrinkScaledFactors; + totalFlexGrowFactors += deltaFlexGrowFactors; + remainingFreeSpace += deltaFreeSpace; + remainingFreeSpaceAfterFlex = remainingFreeSpace; + + // Second pass: resolve the sizes of the flexible items + 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); + } + } + + remainingFreeSpaceAfterFlex -= updatedMainSize - childFlexBasis; + + 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; + } + } + + remainingFreeSpace = remainingFreeSpaceAfterFlex; + + // 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; } - // 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 + - getPaddingAndBorderAxis(currentFlexChild, mainAxis); - boundMainDim = boundAxis(currentFlexChild, mainAxis, baseMainDim); - - if (baseMainDim != boundMainDim) { - remainingMainDim -= boundMainDim; - totalFlexible -= currentFlexChild->style.flex; - } - - 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; - } - - 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) - ); - - 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; - } - - // And we recursively call the layout algorithm for this child - layoutNode(currentFlexChild, maxWidth, maxHeight, direction); - - child = currentFlexChild; - currentFlexChild = currentFlexChild->next_flex_child; - child->next_flex_child = NULL; - } - - // We use justifyContent to figure out how to allocate the remaining - // space available - } else if (justifyContent != CSS_JUSTIFY_FLEX_START) { + // 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); - } 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) { + // 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); } - - // 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.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) - ); - - // 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); - - layoutNode(child, maxWidth, maxHeight, direction); - } + 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 (isMainAxisRow) { + isCrossSizeDefinite = isStyleDimDefined(child, CSS_FLEX_DIRECTION_COLUMN); + childHeight = crossDim; + } else { + isCrossSizeDefinite = isStyleDimDefined(child, CSS_FLEX_DIRECTION_ROW); + childWidth = crossDim; + } + + // 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; @@ -1271,41 +1458,25 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, float parentM 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; @@ -1316,19 +1487,20 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, float parentM } 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; } @@ -1336,33 +1508,33 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, float parentM 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. + } } } @@ -1370,139 +1542,345 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, float parentM } } - 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 (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 (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 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]; +} + + +// +// 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. + 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); + + 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_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]; + 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; -} - #endif // CSS_LAYOUT_IMPLEMENTATION \ No newline at end of file diff --git a/dist/css-layout.jar b/dist/css-layout.jar index 5ebc995f..21cef567 100644 Binary files a/dist/css-layout.jar and b/dist/css-layout.jar differ diff --git a/dist/css-layout.js b/dist/css-layout.js index d35506d1..c5bf0095 100644 --- a/dist/css-layout.js +++ b/dist/css-layout.js @@ -17,7 +17,7 @@ root.computeLayout = factory(); } }(this, function() { - /** + /** * Copyright (c) 2014, Facebook, Inc. * All rights reserved. * @@ -27,9 +27,18 @@ */ var computeLayout = (function() { - + + var POSITIVE_FLEX_IS_AUTO = false; + + var gCurrentGenerationCount = 0; + var CSS_UNDEFINED; - + + var CSS_LEFT = 'left'; + var CSS_TOP = 'top'; + var CSS_RIGHT = 'right'; + var CSS_BOTTOM = 'bottom'; + var CSS_DIRECTION_INHERIT = 'inherit'; var CSS_DIRECTION_LTR = 'ltr'; var CSS_DIRECTION_RTL = 'rtl'; @@ -52,7 +61,10 @@ var computeLayout = (function() { var CSS_POSITION_RELATIVE = 'relative'; var CSS_POSITION_ABSOLUTE = 'absolute'; - + + var CSS_OVERFLOW_VISIBLE = 'visible'; + var CSS_OVERFLOW_HIDDEN = 'hidden'; + var CSS_MEASURE_MODE_UNDEFINED = 'undefined'; var CSS_MEASURE_MODE_EXACTLY = 'exactly'; var CSS_MEASURE_MODE_AT_MOST = 'at-most'; @@ -81,6 +93,12 @@ var computeLayout = (function() { 'column': 'height', 'column-reverse': 'height' }; + var measuredDim = { + 'row': 'measuredWidth', + 'row-reverse': 'measuredWidth', + 'column': 'measuredHeight', + 'column-reverse': 'measuredHeight' + }; // When transpiled to Java / C the node type has layout, children and style // properties. For the JavaScript version this function adds these properties @@ -114,7 +132,7 @@ var computeLayout = (function() { } function isUndefined(value) { - return value === undefined || isNaN(value); + return value === undefined || Number.isNaN(value); } function isRowDirection(flexDirection) { @@ -126,6 +144,46 @@ var computeLayout = (function() { return flexDirection === CSS_FLEX_DIRECTION_COLUMN || flexDirection === CSS_FLEX_DIRECTION_COLUMN_REVERSE; } + + function getFlex(node) { + if (node.style.flex === undefined) { + return 0; + } + return node.style.flex; + } + + function isFlexBasisAuto(node) { + if (POSITIVE_FLEX_IS_AUTO) { + // All flex values are auto. + return true; + } else { + // A flex value > 0 implies a basis of zero. + return getFlex(node) <= 0; + } + } + + function getFlexGrowFactor(node) { + // Flex grow is implied by positive values for flex. + if (getFlex(node) > 0) { + return getFlex(node); + } + return 0; + } + + function getFlexShrinkFactor(node) { + if (POSITIVE_FLEX_IS_AUTO) { + // A flex shrink factor of 1 is implied by non-zero values for flex. + if (getFlex(node) !== 0) { + return 1; + } + } else { + // A flex shrink factor of 1 is implied by negative values for flex. + if (getFlex(node) < 0) { + return 1; + } + } + return 0; + } function getLeadingMargin(node, axis) { if (node.style.marginStart !== undefined && isRowDirection(axis)) { @@ -283,10 +341,6 @@ var computeLayout = (function() { return getTrailingPadding(node, axis) + getTrailingBorder(node, axis); } - function getBorderAxis(node, axis) { - return getLeadingBorder(node, axis) + getTrailingBorder(node, axis); - } - function getMarginAxis(node, axis) { return getLeadingMargin(node, axis) + getTrailingMargin(node, axis); } @@ -366,13 +420,20 @@ var computeLayout = (function() { if (node.style.position) { return node.style.position; } - return 'relative'; + return CSS_POSITION_RELATIVE; + } + + function getOverflow(node) { + if (node.style.overflow) { + return node.style.overflow; + } + return CSS_OVERFLOW_VISIBLE; } function isFlex(node) { return ( getPositionType(node) === CSS_POSITION_RELATIVE && - node.style.flex > 0 + node.style.flex !== undefined && node.style.flex !== 0 ); } @@ -381,15 +442,15 @@ var computeLayout = (function() { } function getDimWithMargin(node, axis) { - return node.layout[dim[axis]] + getMarginAxis(node, axis); + return node.layout[measuredDim[axis]] + getMarginAxis(node, axis); } - - function isStyleDimDefined(node, axis) { + + function isStyleDimDefined(node, axis) { return node.style[dim[axis]] !== undefined && node.style[dim[axis]] >= 0; } - - function isLayoutDimDefined(node, axis) { - return node.layout[dim[axis]] !== undefined && node.layout[dim[axis]] >= 0; + + function isLayoutDimDefined(node, axis) { + return node.layout[measuredDim[axis]] !== undefined && node.layout[measuredDim[axis]] >= 0; } function isPosDefined(node, pos) { @@ -406,8 +467,8 @@ var computeLayout = (function() { } return 0; } - - function boundAxis(node, axis, value) { + + function boundAxisWithinMinAndMax(node, axis, value) { var min = { 'row': node.style.minWidth, 'row-reverse': node.style.minWidth, @@ -431,6 +492,13 @@ var computeLayout = (function() { } return boundValue; } + + function fminf(a, b) { + if (a < b) { + return a; + } + return b; + } function fmaxf(a, b) { if (a > b) { @@ -438,28 +506,18 @@ var computeLayout = (function() { } return b; } - - // When the user specifically sets a value for width or height - function setDimensionFromStyle(node, 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[dim[axis]] = fmaxf( - boundAxis(node, axis, node.style[dim[axis]]), - getPaddingAndBorderAxis(node, axis) - ); + + // Like boundAxisWithinMinAndMax but also ensures that the value doesn't go below the + // padding and border amount. + function boundAxis(node, axis, value) { + return fmaxf(boundAxisWithinMinAndMax(node, axis, value), getPaddingAndBorderAxis(node, axis)); } function setTrailingPosition(node, child, axis) { - child.layout[trailing[axis]] = node.layout[dim[axis]] - - child.layout[dim[axis]] - child.layout[pos[axis]]; + var size = (getPositionType(child) === CSS_POSITION_ABSOLUTE) ? + 0 : + child.layout[measuredDim[axis]]; + child.layout[trailing[axis]] = node.layout[measuredDim[axis]] - size - child.layout[pos[axis]]; } // If both left and right are defined, then use left. Otherwise return @@ -470,352 +528,392 @@ var computeLayout = (function() { } return -getPosition(node, trailing[axis]); } + + function setPosition(node, direction) { + var mainAxis = resolveAxis(getFlexDirection(node), direction); + var crossAxis = getCrossFlexDirection(mainAxis, direction); + + node.layout[leading[mainAxis]] = getLeadingMargin(node, mainAxis) + + getRelativePosition(node, mainAxis); + node.layout[trailing[mainAxis]] = getTrailingMargin(node, mainAxis) + + getRelativePosition(node, mainAxis); + node.layout[leading[crossAxis]] = getLeadingMargin(node, crossAxis) + + getRelativePosition(node, crossAxis); + node.layout[trailing[crossAxis]] = getTrailingMargin(node, crossAxis) + + getRelativePosition(node, crossAxis); + } + + function assert(condition, message) { + if (!condition) { + throw new Error(message); + } + } + + // + // 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. + // + function layoutNodeImpl(node, availableWidth, availableHeight, /*css_direction_t*/parentDirection, widthMeasureMode, heightMeasureMode, performLayout) { + 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'); + + var/*float*/ paddingAndBorderAxisRow = getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW); + var/*float*/ paddingAndBorderAxisColumn = getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_COLUMN); + var/*float*/ marginAxisRow = getMarginAxis(node, CSS_FLEX_DIRECTION_ROW); + var/*float*/ marginAxisColumn = getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN); - function layoutNodeImpl(node, parentMaxWidth, parentMaxHeight, /*css_direction_t*/parentDirection) { + // Set the resolved resolution in the node's layout. var/*css_direction_t*/ direction = resolveDirection(node, parentDirection); - var/*(c)!css_flex_direction_t*//*(java)!int*/ mainAxis = resolveAxis(getFlexDirection(node), direction); - var/*(c)!css_flex_direction_t*//*(java)!int*/ crossAxis = getCrossFlexDirection(mainAxis, direction); - var/*(c)!css_flex_direction_t*//*(java)!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[leading[mainAxis]] += getLeadingMargin(node, mainAxis) + - getRelativePosition(node, mainAxis); - node.layout[trailing[mainAxis]] += getTrailingMargin(node, mainAxis) + - getRelativePosition(node, mainAxis); - node.layout[leading[crossAxis]] += getLeadingMargin(node, crossAxis) + - getRelativePosition(node, crossAxis); - node.layout[trailing[crossAxis]] += getTrailingMargin(node, crossAxis) + - getRelativePosition(node, crossAxis); - - // Inline immutable values from the target node to avoid excessive method - // invocations during the layout calculation. - var/*int*/ childCount = node.children.length; - var/*float*/ paddingAndBorderAxisResolvedRow = getPaddingAndBorderAxis(node, resolvedRowAxis); - var/*float*/ paddingAndBorderAxisColumn = getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_COLUMN); - + // For content (text) nodes, determine the dimensions based on the text contents. if (isMeasureDefined(node)) { - var/*bool*/ isResolvedRowDimDefined = isLayoutDimDefined(node, resolvedRowAxis); + var/*float*/ innerWidth = availableWidth - marginAxisRow - paddingAndBorderAxisRow; + var/*float*/ innerHeight = availableHeight - marginAxisColumn - paddingAndBorderAxisColumn; + + if (widthMeasureMode === CSS_MEASURE_MODE_EXACTLY && heightMeasureMode === CSS_MEASURE_MODE_EXACTLY) { - var/*float*/ width = CSS_UNDEFINED; - var/*css_measure_mode_t*/ widthMode = CSS_MEASURE_MODE_UNDEFINED; - if (isStyleDimDefined(node, resolvedRowAxis)) { - width = node.style.width; - widthMode = CSS_MEASURE_MODE_EXACTLY; - } else if (isResolvedRowDimDefined) { - width = node.layout[dim[resolvedRowAxis]]; - widthMode = CSS_MEASURE_MODE_EXACTLY; + // Don't bother sizing the text if both dimensions are already defined. + node.layout.measuredWidth = boundAxis(node, CSS_FLEX_DIRECTION_ROW, availableWidth - marginAxisRow); + node.layout.measuredHeight = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, availableHeight - marginAxisColumn); + } else if (innerWidth <= 0) { + + // Don't bother sizing the text if there's no horizontal space. + node.layout.measuredWidth = boundAxis(node, CSS_FLEX_DIRECTION_ROW, 0); + node.layout.measuredHeight = 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; - } - var/*float*/ height = CSS_UNDEFINED; - var/*css_measure_mode_t*/ heightMode = CSS_MEASURE_MODE_UNDEFINED; - if (isStyleDimDefined(node, CSS_FLEX_DIRECTION_COLUMN)) { - height = node.style.height; - heightMode = CSS_MEASURE_MODE_EXACTLY; - } else if (isLayoutDimDefined(node, CSS_FLEX_DIRECTION_COLUMN)) { - height = node.layout[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. - var/*bool*/ isRowUndefined = !isStyleDimDefined(node, resolvedRowAxis) && !isResolvedRowDimDefined; - var/*bool*/ isColumnUndefined = !isStyleDimDefined(node, CSS_FLEX_DIRECTION_COLUMN) && - isUndefined(node.layout[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. var/*css_dim_t*/ measureDim = node.style.measure( /*(c)!node->context,*/ /*(java)!layoutContext.measureOutput,*/ - width, - widthMode, - height, - heightMode + innerWidth, + widthMeasureMode, + innerHeight, + heightMeasureMode ); - if (isRowUndefined) { - node.layout.width = measureDim.width + - paddingAndBorderAxisResolvedRow; - } - if (isColumnUndefined) { - node.layout.height = measureDim.height + - paddingAndBorderAxisColumn; - } + + node.layout.measuredWidth = boundAxis(node, CSS_FLEX_DIRECTION_ROW, + (widthMeasureMode === CSS_MEASURE_MODE_UNDEFINED || widthMeasureMode === CSS_MEASURE_MODE_AT_MOST) ? + measureDim.width + paddingAndBorderAxisRow : + availableWidth - marginAxisRow); + node.layout.measuredHeight = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, + (heightMeasureMode === CSS_MEASURE_MODE_UNDEFINED || heightMeasureMode === CSS_MEASURE_MODE_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. + var/*int*/ childCount = node.children.length; + if (childCount === 0) { + node.layout.measuredWidth = boundAxis(node, CSS_FLEX_DIRECTION_ROW, + (widthMeasureMode === CSS_MEASURE_MODE_UNDEFINED || widthMeasureMode === CSS_MEASURE_MODE_AT_MOST) ? + paddingAndBorderAxisRow : + availableWidth - marginAxisRow); + node.layout.measuredHeight = 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.measuredWidth = boundAxis(node, CSS_FLEX_DIRECTION_ROW, 0); + node.layout.measuredHeight = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, 0); + return; + } + + if (widthMeasureMode === CSS_MEASURE_MODE_AT_MOST && availableWidth <= 0) { + node.layout.measuredWidth = boundAxis(node, CSS_FLEX_DIRECTION_ROW, 0); + node.layout.measuredHeight = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, isUndefined(availableHeight) ? 0 : (availableHeight - marginAxisColumn)); + return; + } + + if (heightMeasureMode === CSS_MEASURE_MODE_AT_MOST && availableHeight <= 0) { + node.layout.measuredWidth = boundAxis(node, CSS_FLEX_DIRECTION_ROW, isUndefined(availableWidth) ? 0 : (availableWidth - marginAxisRow)); + node.layout.measuredHeight = 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.measuredWidth = boundAxis(node, CSS_FLEX_DIRECTION_ROW, availableWidth - marginAxisRow); + node.layout.measuredHeight = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, availableHeight - marginAxisColumn); return; } } + // STEP 1: CALCULATE VALUES FOR REMAINDER OF ALGORITHM + var/*(c)!css_flex_direction_t*//*(java)!int*/ mainAxis = resolveAxis(getFlexDirection(node), direction); + var/*(c)!css_flex_direction_t*//*(java)!int*/ crossAxis = getCrossFlexDirection(mainAxis, direction); + var/*bool*/ isMainAxisRow = isRowDirection(mainAxis); + var/*css_justify_t*/ justifyContent = getJustifyContent(node); var/*bool*/ isNodeFlexWrap = isFlexWrap(node); - var/*css_justify_t*/ justifyContent = getJustifyContent(node); + var/*css_node_t**/ firstAbsoluteChild = undefined; + var/*css_node_t**/ currentAbsoluteChild = undefined; var/*float*/ leadingPaddingAndBorderMain = getLeadingPaddingAndBorder(node, mainAxis); + var/*float*/ trailingPaddingAndBorderMain = getTrailingPaddingAndBorder(node, mainAxis); var/*float*/ leadingPaddingAndBorderCross = getLeadingPaddingAndBorder(node, crossAxis); var/*float*/ paddingAndBorderAxisMain = getPaddingAndBorderAxis(node, mainAxis); var/*float*/ paddingAndBorderAxisCross = getPaddingAndBorderAxis(node, crossAxis); + + var/*css_measure_mode_t*/ measureModeMainDim = isMainAxisRow ? widthMeasureMode : heightMeasureMode; + var/*css_measure_mode_t*/ measureModeCrossDim = isMainAxisRow ? heightMeasureMode : widthMeasureMode; - var/*bool*/ isMainDimDefined = isLayoutDimDefined(node, mainAxis); - var/*bool*/ isCrossDimDefined = isLayoutDimDefined(node, crossAxis); - var/*bool*/ isMainRowDirection = isRowDirection(mainAxis); + // STEP 2: DETERMINE AVAILABLE SIZE IN MAIN AND CROSS DIRECTIONS + var/*float*/ availableInnerWidth = availableWidth - marginAxisRow - paddingAndBorderAxisRow; + var/*float*/ availableInnerHeight = availableHeight - marginAxisColumn - paddingAndBorderAxisColumn; + var/*float*/ availableInnerMainDim = isMainAxisRow ? availableInnerWidth : availableInnerHeight; + var/*float*/ availableInnerCrossDim = isMainAxisRow ? availableInnerHeight : availableInnerWidth; - var/*int*/ i; - var/*int*/ ii; + // STEP 3: DETERMINE FLEX BASIS FOR EACH ITEM var/*css_node_t**/ child; - var/*(c)!css_flex_direction_t*//*(java)!int*/ axis; + var/*int*/ i; + var/*float*/ childWidth; + var/*float*/ childHeight; + var/*css_measure_mode_t*/ childWidthMeasureMode; + var/*css_measure_mode_t*/ childHeightMeasureMode; + for (i = 0; i < childCount; i++) { + child = node.children[i]; - var/*css_node_t**/ firstAbsoluteChild = null; - var/*css_node_t**/ currentAbsoluteChild = null; + if (performLayout) { + // Set the initial position (relative to the parent). + var/*css_direction_t*/ 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 (getPositionType(child) === CSS_POSITION_ABSOLUTE) { - var/*float*/ definedMainDim = CSS_UNDEFINED; - if (isMainDimDefined) { - definedMainDim = node.layout[dim[mainAxis]] - paddingAndBorderAxisMain; + // Store a private linked list of absolutely positioned children + // so that we can efficiently traverse them later. + if (firstAbsoluteChild === undefined) { + firstAbsoluteChild = child; + } + if (currentAbsoluteChild !== undefined) { + currentAbsoluteChild.nextChild = child; + } + currentAbsoluteChild = child; + child.nextChild = undefined; + } else { + + if (isMainAxisRow && isStyleDimDefined(child, CSS_FLEX_DIRECTION_ROW)) { + + // The width is definite, so use that as the flex basis. + child.layout.flexBasis = fmaxf(child.style.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.flexBasis = fmaxf(child.style.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.flexBasis = 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.width + getMarginAxis(child, CSS_FLEX_DIRECTION_ROW); + childWidthMeasureMode = CSS_MEASURE_MODE_EXACTLY; + } + if (isStyleDimDefined(child, CSS_FLEX_DIRECTION_COLUMN)) { + childHeight = child.style.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 (getOverflow(node) === 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.flexBasis = fmaxf(isMainAxisRow ? child.layout.measuredWidth : child.layout.measuredHeight, getPaddingAndBorderAxis(child, mainAxis)); + } + } } - // We want to execute the next two loops one per line with flex-wrap - var/*int*/ startLine = 0; - var/*int*/ endLine = 0; - // var/*int*/ nextOffset = 0; - var/*int*/ alreadyComputedNextLayout = 0; - // We aggregate the total dimensions of the container in those two variables - var/*float*/ linesCrossDim = 0; - var/*float*/ linesMainDim = 0; - var/*int*/ linesCount = 0; - while (endLine < 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. + var/*int*/ startOfLineIndex = 0; + var/*int*/ endOfLineIndex = 0; + + // Number of lines. + var/*int*/ lineCount = 0; + + // Accumulated cross dimensions of all lines so far. + var/*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. - var/*float*/ mainContentDim = 0; + // Max main dimension of all the lines. + var/*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. - var/*int*/ flexibleChildrenCount = 0; - var/*float*/ totalFlexible = 0; - var/*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. + var/*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 . - var/*bool*/ isSimpleStackMain = - (isMainDimDefined && justifyContent === CSS_JUSTIFY_FLEX_START) || - (!isMainDimDefined && justifyContent !== CSS_JUSTIFY_CENTER); - var/*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. + var/*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 . - var/*bool*/ isSimpleStackCross = true; - var/*int*/ firstComplexCross = childCount; + var/*float*/ totalFlexGrowFactors = 0; + var/*float*/ totalFlexShrinkScaledFactors = 0; - var/*css_node_t**/ firstFlexChild = null; - var/*css_node_t**/ currentFlexChild = null; + i = startOfLineIndex; - var/*float*/ mainDim = leadingPaddingAndBorderMain; - var/*float*/ crossDim = 0; + // Maintain a linked list of the child nodes that can shrink and/or grow. + var/*css_node_t**/ firstRelativeChild = undefined; + var/*css_node_t**/ currentRelativeChild = undefined; - var/*float*/ maxWidth = CSS_UNDEFINED; - var/*float*/ maxHeight = CSS_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.children[i]; - child.lineIndex = linesCount; + child.lineIndex = lineCount; - child.nextAbsoluteChild = null; - child.nextFlexChild = null; - - var/*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 && - getPositionType(child) === CSS_POSITION_RELATIVE && - isCrossDimDefined && - !isStyleDimDefined(child, crossAxis)) { - child.layout[dim[crossAxis]] = fmaxf( - boundAxis(child, crossAxis, node.layout[dim[crossAxis]] - - paddingAndBorderAxisCross - getMarginAxis(child, crossAxis)), - // You never want to go smaller than padding - getPaddingAndBorderAxis(child, crossAxis) - ); - } else if (getPositionType(child) === 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 (getPositionType(child) !== CSS_POSITION_ABSOLUTE) { + var/*float*/ outerFlexBasis = child.layout.flexBasis + 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; } - 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 (isLayoutDimDefined(node, axis) && - !isStyleDimDefined(child, axis) && - isPosDefined(child, leading[axis]) && - isPosDefined(child, trailing[axis])) { - child.layout[dim[axis]] = fmaxf( - boundAxis(child, axis, node.layout[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) - ); - } + 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.flexBasis; } + + // Store a private linked list of children that need to be layed out. + if (firstRelativeChild === undefined) { + firstRelativeChild = child; + } + if (currentRelativeChild !== undefined) { + currentRelativeChild.nextChild = child; + } + currentRelativeChild = child; + child.nextChild = undefined; } - - var/*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.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 = getPaddingAndBorderAxis(child, mainAxis) + - getMarginAxis(child, mainAxis); - - } else { - maxWidth = CSS_UNDEFINED; - maxHeight = CSS_UNDEFINED; - - if (!isMainRowDirection) { - if (isLayoutDimDefined(node, resolvedRowAxis)) { - maxWidth = node.layout[dim[resolvedRowAxis]] - - paddingAndBorderAxisResolvedRow; - } else { - maxWidth = parentMaxWidth - - getMarginAxis(node, resolvedRowAxis) - - paddingAndBorderAxisResolvedRow; - } - } else { - if (isLayoutDimDefined(node, CSS_FLEX_DIRECTION_COLUMN)) { - maxHeight = node.layout[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(/*(java)!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 (getPositionType(child) === CSS_POSITION_RELATIVE) { - nonFlexibleChildrenCount++; - // At this point we know the final size and margin of the element. - nextContentDim = getDimWithMargin(child, mainAxis); - } - } - - // The element we are about to add would make us go to the next line - if (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 && - (getPositionType(child) !== 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 && - (getPositionType(child) !== 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[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[pos[crossAxis]] += linesCrossDim + leadingPaddingAndBorderCross; - if (isCrossDimDefined) { - setTrailingPosition(node, child, 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. + var/*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 @@ -823,212 +921,300 @@ var computeLayout = (function() { var/*float*/ leadingMainDim = 0; var/*float*/ betweenMainDim = 0; - // The remaining available space that needs to be allocated - var/*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. + var/*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; + } + + var/*float*/ remainingFreeSpaceAfterFlex = remainingFreeSpace; + + if (!canSkipFlex) { + var/*float*/ childFlexBasis; + var/*float*/ flexShrinkScaledFactor; + var/*float*/ flexGrowFactor; + var/*float*/ baseMainSize; + var/*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 + var/*float*/ deltaFreeSpace = 0; + var/*float*/ deltaFlexShrinkScaledFactors = 0; + var/*float*/ deltaFlexGrowFactors = 0; + currentRelativeChild = firstRelativeChild; + while (currentRelativeChild !== undefined) { + 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; + 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; + deltaFlexGrowFactors -= flexGrowFactor; + } + } + } + + currentRelativeChild = currentRelativeChild.nextChild; + } + + totalFlexShrinkScaledFactors += deltaFlexShrinkScaledFactors; + totalFlexGrowFactors += deltaFlexGrowFactors; + remainingFreeSpace += deltaFreeSpace; + remainingFreeSpaceAfterFlex = remainingFreeSpace; + + // Second pass: resolve the sizes of the flexible items + currentRelativeChild = firstRelativeChild; + while (currentRelativeChild !== undefined) { + childFlexBasis = currentRelativeChild.layout.flexBasis; + var/*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); + } + } + + remainingFreeSpaceAfterFlex -= updatedMainSize - childFlexBasis; + + 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.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.width + getMarginAxis(currentRelativeChild, CSS_FLEX_DIRECTION_ROW); + childWidthMeasureMode = CSS_MEASURE_MODE_EXACTLY; + } + } + + var/*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.nextChild; + } + } + + remainingFreeSpace = remainingFreeSpaceAfterFlex; + + // 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; } - // If there are flexible children in the mix, they are going to fill the - // remaining space - if (flexibleChildrenCount !== 0) { - var/*float*/ flexibleMainDim = remainingMainDim / totalFlexible; - var/*float*/ baseMainDim; - var/*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 + - getPaddingAndBorderAxis(currentFlexChild, 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[dim[mainAxis]] = boundAxis(currentFlexChild, mainAxis, - flexibleMainDim * currentFlexChild.style.flex + - getPaddingAndBorderAxis(currentFlexChild, mainAxis) - ); - - maxWidth = CSS_UNDEFINED; - if (isLayoutDimDefined(node, resolvedRowAxis)) { - maxWidth = node.layout[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[dim[CSS_FLEX_DIRECTION_COLUMN]] - - paddingAndBorderAxisColumn; - } else if (isMainRowDirection) { - maxHeight = parentMaxHeight - - getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN) - - paddingAndBorderAxisColumn; - } - - // And we recursively call the layout algorithm for this child - layoutNode(/*(java)!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 !== CSS_JUSTIFY_FLEX_START) { + // 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 + var/*float*/ mainDim = leadingPaddingAndBorderMain + leadingMainDim; + var/*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.children[i]; if (getPositionType(child) === CSS_POSITION_ABSOLUTE && isPosDefined(child, leading[mainAxis])) { - // In case the child is position absolute and has left/top being - // defined, we override the position to whatever the user said - // (and margin/border). - child.layout[pos[mainAxis]] = getPosition(child, leading[mainAxis]) + - 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[pos[mainAxis]] += mainDim; - - // Define the trailing position accordingly. - if (isMainDimDefined) { - setTrailingPosition(node, 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[pos[mainAxis]] = getPosition(child, leading[mainAxis]) + + getLeadingBorder(node, mainAxis) + + getLeadingMargin(child, 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[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 (getPositionType(child) === CSS_POSITION_RELATIVE) { - // The main dimension is the sum of all the elements dimension plus - // the spacing. - mainDim += betweenMainDim + getDimWithMargin(child, mainAxis); - // The cross dimension is the max of the elements dimension since there - // can only be one element in that cross dimension. - crossDim = fmaxf(crossDim, 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.flexBasis; + 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)); + } } } } - var/*float*/ containerCrossAxis = node.layout[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; + + var/*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.children[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 (getPositionType(child) === CSS_POSITION_ABSOLUTE && - isPosDefined(child, leading[crossAxis])) { - // In case the child is absolutely positionned and has a - // top/left/bottom/right being set, we override all the previously - // computed positions to set it correctly. - child.layout[pos[crossAxis]] = getPosition(child, leading[crossAxis]) + - getLeadingBorder(node, crossAxis) + - getLeadingMargin(child, crossAxis); + // Clamp to the min/max size specified on the container. + crossDim = boundAxis(node, crossAxis, crossDim + paddingAndBorderAxisCross) - paddingAndBorderAxisCross; - } else { - var/*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.children[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 (getPositionType(child) === CSS_POSITION_RELATIVE) { - /*eslint-disable */ - // This variable is intentionally re-defined as the code is transpiled to a block scope language + if (getPositionType(child) === 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[pos[crossAxis]] = getPosition(child, leading[crossAxis]) + + getLeadingBorder(node, crossAxis) + + getLeadingMargin(child, crossAxis); + } else { + child.layout[pos[crossAxis]] = leadingPaddingAndBorderCross + + getLeadingMargin(child, crossAxis); + } + } else { + var/*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 var/*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)) { - var/*float*/ dimCrossAxis = child.layout[dim[crossAxis]]; - child.layout[dim[crossAxis]] = fmaxf( - boundAxis(child, crossAxis, containerCrossAxis - - paddingAndBorderAxisCross - getMarginAxis(child, crossAxis)), - // You never want to go smaller than padding - getPaddingAndBorderAxis(child, crossAxis) - ); - - // If the size has changed, and this child has children we need to re-layout this child - if (dimCrossAxis != child.layout[dim[crossAxis]] && child.children.length > 0) { - // Reset child margins before re-layout as they are added back in layoutNode and would be doubled - child.layout[leading[mainAxis]] -= getLeadingMargin(child, mainAxis) + - getRelativePosition(child, mainAxis); - child.layout[trailing[mainAxis]] -= getTrailingMargin(child, mainAxis) + - getRelativePosition(child, mainAxis); - child.layout[leading[crossAxis]] -= getLeadingMargin(child, crossAxis) + - getRelativePosition(child, crossAxis); - child.layout[trailing[crossAxis]] -= getTrailingMargin(child, crossAxis) + - getRelativePosition(child, crossAxis); - - layoutNode(/*(java)!layoutContext, */child, maxWidth, maxHeight, direction); - } + childWidth = child.layout.measuredWidth + getMarginAxis(child, CSS_FLEX_DIRECTION_ROW); + childHeight = child.layout.measuredHeight + getMarginAxis(child, CSS_FLEX_DIRECTION_COLUMN); + var/*bool*/ isCrossSizeDefinite = false; + + if (isMainAxisRow) { + isCrossSizeDefinite = isStyleDimDefined(child, CSS_FLEX_DIRECTION_COLUMN); + childHeight = crossDim; + } else { + isCrossSizeDefinite = isStyleDimDefined(child, CSS_FLEX_DIRECTION_ROW); + childWidth = crossDim; + } + + // 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. - var/*float*/ remainingCrossDim = containerCrossAxis - - paddingAndBorderAxisCross - getDimWithMargin(child, crossAxis); + var/*float*/ remainingCrossDim = containerCrossAxis - getDimWithMargin(child, crossAxis); if (alignItem === CSS_ALIGN_CENTER) { leadingCrossDim += remainingCrossDim / 2; @@ -1036,41 +1222,25 @@ var computeLayout = (function() { leadingCrossDim += remainingCrossDim; } } - } - // And we apply the position - child.layout[pos[crossAxis]] += linesCrossDim + leadingCrossDim; - - // Define the trailing position accordingly. - if (isCrossDimDefined) { - setTrailingPosition(node, child, crossAxis); + // And we apply the position + child.layout[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) { - var/*float*/ nodeCrossAxisInnerSize = node.layout[dim[crossAxis]] - - paddingAndBorderAxisCross; - var/*float*/ remainingAlignContentDim = nodeCrossAxisInnerSize - linesCrossDim; + // STEP 8: MULTI-LINE CONTENT ALIGNMENT + if (lineCount > 1 && performLayout && !isUndefined(availableInnerCrossDim)) { + var/*float*/ remainingAlignContentDim = availableInnerCrossDim - totalLineCrossDim; var/*float*/ crossDimLead = 0; var/*float*/ currentLead = leadingPaddingAndBorderCross; @@ -1081,19 +1251,20 @@ var computeLayout = (function() { } 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); } } var/*int*/ endIndex = 0; - for (i = 0; i < linesCount; ++i) { + for (i = 0; i < lineCount; ++i) { var/*int*/ startIndex = endIndex; + var/*int*/ j; // compute the line's height and find the endIndex var/*float*/ lineHeight = 0; - for (ii = startIndex; ii < childCount; ++ii) { - child = node.children[ii]; + for (j = startIndex; j < childCount; ++j) { + child = node.children[j]; if (getPositionType(child) !== CSS_POSITION_RELATIVE) { continue; } @@ -1101,33 +1272,33 @@ var computeLayout = (function() { break; } if (isLayoutDimDefined(child, crossAxis)) { - lineHeight = fmaxf( - lineHeight, - child.layout[dim[crossAxis]] + getMarginAxis(child, crossAxis) - ); + lineHeight = fmaxf(lineHeight, + child.layout[measuredDim[crossAxis]] + getMarginAxis(child, crossAxis)); } } - endIndex = ii; + endIndex = j; lineHeight += crossDimLead; - for (ii = startIndex; ii < endIndex; ++ii) { - child = node.children[ii]; - if (getPositionType(child) !== CSS_POSITION_RELATIVE) { - continue; - } + if (performLayout) { + for (j = startIndex; j < endIndex; ++j) { + child = node.children[j]; + if (getPositionType(child) !== CSS_POSITION_RELATIVE) { + continue; + } - var/*css_align_t*/ alignContentAlignItem = getAlignItem(node, child); - if (alignContentAlignItem === CSS_ALIGN_FLEX_START) { - child.layout[pos[crossAxis]] = currentLead + getLeadingMargin(child, crossAxis); - } else if (alignContentAlignItem === CSS_ALIGN_FLEX_END) { - child.layout[pos[crossAxis]] = currentLead + lineHeight - getTrailingMargin(child, crossAxis) - child.layout[dim[crossAxis]]; - } else if (alignContentAlignItem === CSS_ALIGN_CENTER) { - var/*float*/ childHeight = child.layout[dim[crossAxis]]; - child.layout[pos[crossAxis]] = currentLead + (lineHeight - childHeight) / 2; - } else if (alignContentAlignItem === CSS_ALIGN_STRETCH) { - child.layout[pos[crossAxis]] = currentLead + getLeadingMargin(child, crossAxis); - // TODO(prenaux): Correctly set the height of items with undefined - // (auto) crossAxis dimension. + var/*css_align_t*/ alignContentAlignItem = getAlignItem(node, child); + if (alignContentAlignItem === CSS_ALIGN_FLEX_START) { + child.layout[pos[crossAxis]] = currentLead + getLeadingMargin(child, crossAxis); + } else if (alignContentAlignItem === CSS_ALIGN_FLEX_END) { + child.layout[pos[crossAxis]] = currentLead + lineHeight - getTrailingMargin(child, crossAxis) - child.layout[measuredDim[crossAxis]]; + } else if (alignContentAlignItem === CSS_ALIGN_CENTER) { + childHeight = child.layout[measuredDim[crossAxis]]; + child.layout[pos[crossAxis]] = currentLead + (lineHeight - childHeight) / 2; + } else if (alignContentAlignItem === CSS_ALIGN_STRETCH) { + child.layout[pos[crossAxis]] = currentLead + getLeadingMargin(child, crossAxis); + // TODO(prenaux): Correctly set the height of items with indefinite + // (auto) crossAxis dimension. + } } } @@ -1135,138 +1306,266 @@ var computeLayout = (function() { } } - var/*bool*/ needsMainTrailingPos = false; - var/*bool*/ needsCrossTrailingPos = false; + // STEP 9: COMPUTING FINAL DIMENSIONS + node.layout.measuredWidth = boundAxis(node, CSS_FLEX_DIRECTION_ROW, availableWidth - marginAxisRow); + node.layout.measuredHeight = 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[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[measuredDim[mainAxis]] = boundAxis(node, mainAxis, maxLineMainDim); + } else if (measureModeMainDim === CSS_MEASURE_MODE_AT_MOST) { + node.layout[measuredDim[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[measuredDim[crossAxis]] = boundAxis(node, crossAxis, totalLineCrossDim + paddingAndBorderAxisCross); + } else if (measureModeCrossDim === CSS_MEASURE_MODE_AT_MOST) { + node.layout[measuredDim[crossAxis]] = fmaxf( + fminf(availableInnerCrossDim + paddingAndBorderAxisCross, + boundAxisWithinMinAndMax(node, crossAxis, totalLineCrossDim + paddingAndBorderAxisCross)), + paddingAndBorderAxisCross); + } + + // STEP 10: SETTING TRAILING POSITIONS FOR CHILDREN + if (performLayout) { + var/*bool*/ needsMainTrailingPos = false; + var/*bool*/ needsCrossTrailingPos = false; if (mainAxis === CSS_FLEX_DIRECTION_ROW_REVERSE || mainAxis === CSS_FLEX_DIRECTION_COLUMN_REVERSE) { needsMainTrailingPos = true; } - } - - if (!isCrossDimDefined) { - node.layout[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.children[i]; + // Set trailing position if necessary. + if (needsMainTrailingPos || needsCrossTrailingPos) { + for (i = 0; i < childCount; ++i) { + child = node.children[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; + while (currentAbsoluteChild !== undefined) { + // 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[dim[axis]] = fmaxf( - boundAxis(currentAbsoluteChild, axis, node.layout[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.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.measuredWidth - + (getLeadingBorder(node, CSS_FLEX_DIRECTION_ROW) + getTrailingBorder(node, CSS_FLEX_DIRECTION_ROW)) - + (currentAbsoluteChild.style[CSS_LEFT] + currentAbsoluteChild.style[CSS_RIGHT]); + childWidth = boundAxis(currentAbsoluteChild, CSS_FLEX_DIRECTION_ROW, childWidth); + } + } + + if (isStyleDimDefined(currentAbsoluteChild, CSS_FLEX_DIRECTION_COLUMN)) { + childHeight = currentAbsoluteChild.style.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.measuredHeight - + (getLeadingBorder(node, CSS_FLEX_DIRECTION_COLUMN) + getTrailingBorder(node, CSS_FLEX_DIRECTION_COLUMN)) - + (currentAbsoluteChild.style[CSS_TOP] + currentAbsoluteChild.style[CSS_BOTTOM]); + childHeight = boundAxis(currentAbsoluteChild, CSS_FLEX_DIRECTION_COLUMN, childHeight); + } } - if (isPosDefined(currentAbsoluteChild, trailing[axis]) && - !isPosDefined(currentAbsoluteChild, leading[axis])) { - currentAbsoluteChild.layout[leading[axis]] = - node.layout[dim[axis]] - - currentAbsoluteChild.layout[dim[axis]] - - getPosition(currentAbsoluteChild, trailing[axis]); + // 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 (getOverflow(node) === 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.measuredWidth + getMarginAxis(currentAbsoluteChild, CSS_FLEX_DIRECTION_ROW); + childHeight = currentAbsoluteChild.layout.measuredHeight + 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[leading[CSS_FLEX_DIRECTION_ROW]] = + node.layout[measuredDim[CSS_FLEX_DIRECTION_ROW]] - + currentAbsoluteChild.layout[measuredDim[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[leading[CSS_FLEX_DIRECTION_COLUMN]] = + node.layout[measuredDim[CSS_FLEX_DIRECTION_COLUMN]] - + currentAbsoluteChild.layout[measuredDim[CSS_FLEX_DIRECTION_COLUMN]] - + getPosition(currentAbsoluteChild, trailing[CSS_FLEX_DIRECTION_COLUMN]); } } - child = currentAbsoluteChild; - currentAbsoluteChild = currentAbsoluteChild.nextAbsoluteChild; - child.nextAbsoluteChild = null; + currentAbsoluteChild = currentAbsoluteChild.nextChild; } } + + // + // 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 + // + function layoutNodeInternal(node, availableWidth, availableHeight, parentDirection, + widthMeasureMode, heightMeasureMode, performLayout, reason) { + var layout = node.layout; - function layoutNode(node, parentMaxWidth, parentMaxHeight, parentDirection) { - node.shouldUpdate = true; + var needToVisitNode = (node.isDirty && layout.generationCount !== gCurrentGenerationCount) || + layout.lastParentDirection !== parentDirection; - var direction = node.style.direction || CSS_DIRECTION_LTR; - var skipLayout = - !node.isDirty && - node.lastLayout && - node.lastLayout.requestedHeight === node.layout.height && - node.lastLayout.requestedWidth === node.layout.width && - node.lastLayout.parentMaxWidth === parentMaxWidth && - node.lastLayout.parentMaxHeight === parentMaxHeight && - node.lastLayout.direction === direction; - - if (skipLayout) { - node.layout.width = node.lastLayout.width; - node.layout.height = node.lastLayout.height; - node.layout.top = node.lastLayout.top; - node.layout.left = node.lastLayout.left; - } else { - if (!node.lastLayout) { - node.lastLayout = {}; + if (needToVisitNode) { + // Invalidate the cached results. + if (layout.cachedMeasurements !== undefined) { + layout.cachedMeasurements = []; } + if (layout.cachedLayout !== undefined) { + layout.cachedLayout.widthMeasureMode = undefined; + layout.cachedLayout.heightMeasureMode = undefined; + } + } - node.lastLayout.requestedWidth = node.layout.width; - node.lastLayout.requestedHeight = node.layout.height; - node.lastLayout.parentMaxWidth = parentMaxWidth; - node.lastLayout.parentMaxHeight = parentMaxHeight; - node.lastLayout.direction = direction; - - // Reset child layouts - node.children.forEach(function(child) { - child.layout.width = undefined; - child.layout.height = undefined; - child.layout.top = 0; - child.layout.left = 0; - }); - - layoutNodeImpl(node, parentMaxWidth, parentMaxHeight, parentDirection); - - node.lastLayout.width = node.layout.width; - node.lastLayout.height = node.layout.height; - node.lastLayout.top = node.layout.top; - node.lastLayout.left = node.layout.left; + var cachedResults; + + // 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. + if (performLayout) { + if (layout.cachedLayout && + layout.cachedLayout.availableWidth === availableWidth && + layout.cachedLayout.availableHeight === availableHeight && + layout.cachedLayout.widthMeasureMode === widthMeasureMode && + layout.cachedLayout.heightMeasureMode === heightMeasureMode) { + cachedResults = layout.cachedLayout; + } + } else if (layout.cachedMeasurements) { + for (var i = 0, len = layout.cachedMeasurements.length; i < len; i++) { + if (layout.cachedMeasurements[i].availableWidth === availableWidth && + layout.cachedMeasurements[i].availableHeight === availableHeight && + layout.cachedMeasurements[i].widthMeasureMode === widthMeasureMode && + layout.cachedMeasurements[i].heightMeasureMode === heightMeasureMode) { + cachedResults = layout.cachedMeasurements[i]; + break; + } + } + } + + if (!needToVisitNode && cachedResults !== undefined) { + layout.measureWidth = cachedResults.computedWidth; + layout.measureHeight = cachedResults.computedHeight; + } else { + layoutNodeImpl(node, availableWidth, availableHeight, parentDirection, widthMeasureMode, heightMeasureMode, performLayout); + layout.lastParentDirection = parentDirection; + + if (cachedResults === undefined) { + var newCacheEntry; + if (performLayout) { + // Use the single layout cache entry. + if (layout.cachedLayout === undefined) { + layout.cachedLayout = {}; + } + newCacheEntry = layout.cachedLayout; + } else { + // Allocate a new measurement cache entry. + if (layout.cachedMeasurements === undefined) { + layout.cachedMeasurements = []; + } + newCacheEntry = {}; + layout.cachedMeasurements.push(newCacheEntry); + } + + newCacheEntry.availableWidth = availableWidth; + newCacheEntry.availableHeight = availableHeight; + newCacheEntry.widthMeasureMode = widthMeasureMode; + newCacheEntry.heightMeasureMode = heightMeasureMode; + newCacheEntry.computedWidth = layout.measuredWidth; + newCacheEntry.computedHeight = layout.measuredHeight; + } + } + + if (performLayout) { + node.layout.width = node.layout.measuredWidth; + node.layout.height = node.layout.measuredHeight; + layout.shouldUpdate = true; + } + + layout.generationCount = gCurrentGenerationCount; + return (needToVisitNode || cachedResults === undefined); + } + + function layoutNode(node, availableWidth, availableHeight, 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.width + getMarginAxis(node, CSS_FLEX_DIRECTION_ROW); + } + if (isUndefined(availableHeight) && isStyleDimDefined(node, CSS_FLEX_DIRECTION_COLUMN)) { + availableHeight = node.style.height + getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN); + } + + var widthMeasureMode = isUndefined(availableWidth) ? CSS_MEASURE_MODE_UNDEFINED : CSS_MEASURE_MODE_EXACTLY; + var 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); } } diff --git a/dist/css-layout.min.js b/dist/css-layout.min.js index 7ea18191..86e0108f 100644 --- a/dist/css-layout.min.js +++ b/dist/css-layout.min.js @@ -1,2 +1,2 @@ -!function(a,b){"function"==typeof define&&define.amd?define([],b):"object"==typeof exports?module.exports=b():a.computeLayout=b()}(this,function(){var a=function(){function a(b){if((!b.layout||b.isDirty)&&(b.layout={width:void 0,height:void 0,top:0,left:0,right:0,bottom:0}),b.style||(b.style={}),b.children||(b.children=[]),b.style.measure&&b.children&&b.children.length)throw new Error("Using custom measure function is supported only for leaf nodes.");return b.children.forEach(a),b}function b(a){return void 0===a||isNaN(a)}function c(a){return a===Q||a===R}function d(a){return a===S||a===T}function e(a,b){if(void 0!==a.style.marginStart&&c(b))return a.style.marginStart;var d=null;switch(b){case"row":d=a.style.marginLeft;break;case"row-reverse":d=a.style.marginRight;break;case"column":d=a.style.marginTop;break;case"column-reverse":d=a.style.marginBottom}return void 0!==d?d:void 0!==a.style.margin?a.style.margin:0}function f(a,b){if(void 0!==a.style.marginEnd&&c(b))return a.style.marginEnd;var d=null;switch(b){case"row":d=a.style.marginRight;break;case"row-reverse":d=a.style.marginLeft;break;case"column":d=a.style.marginBottom;break;case"column-reverse":d=a.style.marginTop}return null!=d?d:void 0!==a.style.margin?a.style.margin:0}function g(a,b){if(void 0!==a.style.paddingStart&&a.style.paddingStart>=0&&c(b))return a.style.paddingStart;var d=null;switch(b){case"row":d=a.style.paddingLeft;break;case"row-reverse":d=a.style.paddingRight;break;case"column":d=a.style.paddingTop;break;case"column-reverse":d=a.style.paddingBottom}return null!=d&&d>=0?d:void 0!==a.style.padding&&a.style.padding>=0?a.style.padding:0}function h(a,b){if(void 0!==a.style.paddingEnd&&a.style.paddingEnd>=0&&c(b))return a.style.paddingEnd;var d=null;switch(b){case"row":d=a.style.paddingRight;break;case"row-reverse":d=a.style.paddingLeft;break;case"column":d=a.style.paddingBottom;break;case"column-reverse":d=a.style.paddingTop}return null!=d&&d>=0?d:void 0!==a.style.padding&&a.style.padding>=0?a.style.padding:0}function i(a,b){if(void 0!==a.style.borderStartWidth&&a.style.borderStartWidth>=0&&c(b))return a.style.borderStartWidth;var d=null;switch(b){case"row":d=a.style.borderLeftWidth;break;case"row-reverse":d=a.style.borderRightWidth;break;case"column":d=a.style.borderTopWidth;break;case"column-reverse":d=a.style.borderBottomWidth}return null!=d&&d>=0?d:void 0!==a.style.borderWidth&&a.style.borderWidth>=0?a.style.borderWidth:0}function j(a,b){if(void 0!==a.style.borderEndWidth&&a.style.borderEndWidth>=0&&c(b))return a.style.borderEndWidth;var d=null;switch(b){case"row":d=a.style.borderRightWidth;break;case"row-reverse":d=a.style.borderLeftWidth;break;case"column":d=a.style.borderBottomWidth;break;case"column-reverse":d=a.style.borderTopWidth}return null!=d&&d>=0?d:void 0!==a.style.borderWidth&&a.style.borderWidth>=0?a.style.borderWidth:0}function k(a,b){return g(a,b)+i(a,b)}function l(a,b){return h(a,b)+j(a,b)}function m(a,b){return i(a,b)+j(a,b)}function n(a,b){return e(a,b)+f(a,b)}function o(a,b){return k(a,b)+l(a,b)}function p(a){return a.style.justifyContent?a.style.justifyContent:"flex-start"}function q(a){return a.style.alignContent?a.style.alignContent:"flex-start"}function r(a,b){return b.style.alignSelf?b.style.alignSelf:a.style.alignItems?a.style.alignItems:"stretch"}function s(a,b){if(b===P){if(a===Q)return R;if(a===R)return Q}return a}function t(a,b){var c;return c=a.style.direction?a.style.direction:N,c===N&&(c=void 0===b?O:b),c}function u(a){return a.style.flexDirection?a.style.flexDirection:S}function v(a,b){return d(a)?s(Q,b):S}function w(a){return a.style.position?a.style.position:"relative"}function x(a){return w(a)===ba&&a.style.flex>0}function y(a){return"wrap"===a.style.flexWrap}function z(a,b){return a.layout[ja[b]]+n(a,b)}function A(a,b){return void 0!==a.style[ja[b]]&&a.style[ja[b]]>=0}function B(a,b){return void 0!==a.layout[ja[b]]&&a.layout[ja[b]]>=0}function C(a,b){return void 0!==a.style[b]}function D(a){return void 0!==a.style.measure}function E(a,b){return void 0!==a.style[b]?a.style[b]:0}function F(a,b,c){var d={row:a.style.minWidth,"row-reverse":a.style.minWidth,column:a.style.minHeight,"column-reverse":a.style.minHeight}[b],e={row:a.style.maxWidth,"row-reverse":a.style.maxWidth,column:a.style.maxHeight,"column-reverse":a.style.maxHeight}[b],f=c;return void 0!==e&&e>=0&&f>e&&(f=e),void 0!==d&&d>=0&&d>f&&(f=d),f}function G(a,b){return a>b?a:b}function H(a,b){B(a,b)||A(a,b)&&(a.layout[ja[b]]=G(F(a,b,a.style[ja[b]]),o(a,b)))}function I(a,b,c){b.layout[ha[c]]=a.layout[ja[c]]-b.layout[ja[c]]-b.layout[ia[c]]}function J(a,b){return void 0!==a.style[ga[b]]?E(a,ga[b]):-E(a,ha[b])}function K(a,d,g,h){var j=t(a,h),K=s(u(a),j),N=v(K,j),O=s(Q,j);H(a,K),H(a,N),a.layout.direction=j,a.layout[ga[K]]+=e(a,K)+J(a,K),a.layout[ha[K]]+=f(a,K)+J(a,K),a.layout[ga[N]]+=e(a,N)+J(a,N),a.layout[ha[N]]+=f(a,N)+J(a,N);var P=a.children.length,ka=o(a,O),la=o(a,S);if(D(a)){var ma=B(a,O),na=M,oa=da;A(a,O)?(na=a.style.width,oa=ea):ma?(na=a.layout[ja[O]],oa=ea):(na=d-n(a,O),oa=fa),na-=ka,b(na)&&(oa=da);var pa=M,qa=da;A(a,S)?(pa=a.style.height,qa=ea):B(a,S)?(pa=a.layout[ja[S]],qa=ea):(pa=g-n(a,O),qa=fa),pa-=o(a,S),b(pa)&&(qa=da);var ra=!A(a,O)&&!ma,sa=!A(a,S)&&b(a.layout[ja[S]]);if(ra||sa){var ta=a.style.measure(na,oa,pa,qa);ra&&(a.layout.width=ta.width+ka),sa&&(a.layout.height=ta.height+la)}if(0===P)return}var ua,va,wa,xa,ya=y(a),za=p(a),Aa=k(a,K),Ba=k(a,N),Ca=o(a,K),Da=o(a,N),Ea=B(a,K),Fa=B(a,N),Ga=c(K),Ha=null,Ia=null,Ja=M;Ea&&(Ja=a.layout[ja[K]]-Ca);for(var Ka=0,La=0,Ma=0,Na=0,Oa=0,Pa=0;P>La;){var Qa=0,Ra=0,Sa=0,Ta=0,Ua=Ea&&za===U||!Ea&&za!==V,Va=Ua?P:Ka,Wa=!0,Xa=P,Ya=null,Za=null,$a=Aa,_a=0,ab=M,bb=M;for(ua=Ka;P>ua;++ua){wa=a.children[ua],wa.lineIndex=Pa,wa.nextAbsoluteChild=null,wa.nextFlexChild=null;var cb=r(a,wa);if(cb===aa&&w(wa)===ba&&Fa&&!A(wa,N))wa.layout[ja[N]]=G(F(wa,N,a.layout[ja[N]]-Da-n(wa,N)),o(wa,N));else if(w(wa)===ca)for(null===Ha&&(Ha=wa),null!==Ia&&(Ia.nextAbsoluteChild=wa),Ia=wa,va=0;2>va;va++)xa=0!==va?Q:S,B(a,xa)&&!A(wa,xa)&&C(wa,ga[xa])&&C(wa,ha[xa])&&(wa.layout[ja[xa]]=G(F(wa,xa,a.layout[ja[xa]]-o(a,xa)-n(wa,xa)-E(wa,ga[xa])-E(wa,ha[xa])),o(wa,xa)));var db=0;if(Ea&&x(wa)?(Ra++,Sa+=wa.style.flex,null===Ya&&(Ya=wa),null!==Za&&(Za.nextFlexChild=wa),Za=wa,db=o(wa,K)+n(wa,K)):(ab=M,bb=M,Ga?bb=B(a,S)?a.layout[ja[S]]-la:g-n(a,S)-la:ab=B(a,O)?a.layout[ja[O]]-ka:d-n(a,O)-ka,0===Ma&&L(wa,ab,bb,j),w(wa)===ba&&(Ta++,db=z(wa,K))),ya&&Ea&&Qa+db>Ja&&ua!==Ka){Ta--,Ma=1;break}Ua&&(w(wa)!==ba||x(wa))&&(Ua=!1,Va=ua),Wa&&(w(wa)!==ba||cb!==aa&&cb!==Z||cb==aa&&!Fa)&&(Wa=!1,Xa=ua),Ua&&(wa.layout[ia[K]]+=$a,Ea&&I(a,wa,K),$a+=z(wa,K),_a=G(_a,F(wa,N,z(wa,N)))),Wa&&(wa.layout[ia[N]]+=Na+Ba,Fa&&I(a,wa,N)),Ma=0,Qa+=db,La=ua+1}var eb=0,fb=0,gb=0;if(gb=Ea?Ja-Qa:G(Qa,0)-Qa,0!==Ra){var hb,ib,jb=gb/Sa;for(Za=Ya;null!==Za;)hb=jb*Za.style.flex+o(Za,K),ib=F(Za,K,hb),hb!==ib&&(gb-=ib,Sa-=Za.style.flex),Za=Za.nextFlexChild;for(jb=gb/Sa,0>jb&&(jb=0),Za=Ya;null!==Za;)Za.layout[ja[K]]=F(Za,K,jb*Za.style.flex+o(Za,K)),ab=M,B(a,O)?ab=a.layout[ja[O]]-ka:Ga||(ab=d-n(a,O)-ka),bb=M,B(a,S)?bb=a.layout[ja[S]]-la:Ga&&(bb=g-n(a,S)-la),L(Za,ab,bb,j),wa=Za,Za=Za.nextFlexChild,wa.nextFlexChild=null}else za!==U&&(za===V?eb=gb/2:za===W?eb=gb:za===X?(gb=G(gb,0),fb=Ra+Ta-1!==0?gb/(Ra+Ta-1):0):za===Y&&(fb=gb/(Ra+Ta),eb=fb/2));for($a+=eb,ua=Va;La>ua;++ua)wa=a.children[ua],w(wa)===ca&&C(wa,ga[K])?wa.layout[ia[K]]=E(wa,ga[K])+i(a,K)+e(wa,K):(wa.layout[ia[K]]+=$a,Ea&&I(a,wa,K),w(wa)===ba&&($a+=fb+z(wa,K),_a=G(_a,F(wa,N,z(wa,N)))));var kb=a.layout[ja[N]];for(Fa||(kb=G(F(a,N,_a+Da),Da)),ua=Xa;La>ua;++ua)if(wa=a.children[ua],w(wa)===ca&&C(wa,ga[N]))wa.layout[ia[N]]=E(wa,ga[N])+i(a,N)+e(wa,N);else{var lb=Ba;if(w(wa)===ba){var cb=r(a,wa);if(cb===aa){if(!A(wa,N)){var mb=wa.layout[ja[N]];wa.layout[ja[N]]=G(F(wa,N,kb-Da-n(wa,N)),o(wa,N)),mb!=wa.layout[ja[N]]&&wa.children.length>0&&(wa.layout[ga[K]]-=e(wa,K)+J(wa,K),wa.layout[ha[K]]-=f(wa,K)+J(wa,K),wa.layout[ga[N]]-=e(wa,N)+J(wa,N),wa.layout[ha[N]]-=f(wa,N)+J(wa,N),L(wa,ab,bb,j))}}else if(cb!==Z){var nb=kb-Da-z(wa,N);lb+=cb===$?nb/2:nb}}wa.layout[ia[N]]+=Na+lb,Fa&&I(a,wa,N)}Na+=_a,Oa=G(Oa,$a),Pa+=1,Ka=La}if(Pa>1&&Fa){var ob=a.layout[ja[N]]-Da,pb=ob-Na,qb=0,rb=Ba,sb=q(a);sb===_?rb+=pb:sb===$?rb+=pb/2:sb===aa&&ob>Na&&(qb=pb/Pa);var tb=0;for(ua=0;Pa>ua;++ua){var ub=tb,vb=0;for(va=ub;P>va;++va)if(wa=a.children[va],w(wa)===ba){if(wa.lineIndex!==ua)break;B(wa,N)&&(vb=G(vb,wa.layout[ja[N]]+n(wa,N)))}for(tb=va,vb+=qb,va=ub;tb>va;++va)if(wa=a.children[va],w(wa)===ba){var wb=r(a,wa);if(wb===Z)wa.layout[ia[N]]=rb+e(wa,N);else if(wb===_)wa.layout[ia[N]]=rb+vb-f(wa,N)-wa.layout[ja[N]];else if(wb===$){var xb=wa.layout[ja[N]];wa.layout[ia[N]]=rb+(vb-xb)/2}else wb===aa&&(wa.layout[ia[N]]=rb+e(wa,N))}rb+=vb}}var yb=!1,zb=!1;if(Ea||(a.layout[ja[K]]=G(F(a,K,Oa+l(a,K)),Ca),(K===R||K===T)&&(yb=!0)),Fa||(a.layout[ja[N]]=G(F(a,N,Na+Da),Da),(N===R||N===T)&&(zb=!0)),yb||zb)for(ua=0;P>ua;++ua)wa=a.children[ua],yb&&I(a,wa,K),zb&&I(a,wa,N);for(Ia=Ha;null!==Ia;){for(va=0;2>va;va++)xa=0!==va?Q:S,B(a,xa)&&!A(Ia,xa)&&C(Ia,ga[xa])&&C(Ia,ha[xa])&&(Ia.layout[ja[xa]]=G(F(Ia,xa,a.layout[ja[xa]]-m(a,xa)-n(Ia,xa)-E(Ia,ga[xa])-E(Ia,ha[xa])),o(Ia,xa))),C(Ia,ha[xa])&&!C(Ia,ga[xa])&&(Ia.layout[ga[xa]]=a.layout[ja[xa]]-Ia.layout[ja[xa]]-E(Ia,ha[xa]));wa=Ia,Ia=Ia.nextAbsoluteChild,wa.nextAbsoluteChild=null}}function L(a,b,c,d){a.shouldUpdate=!0;var e=a.style.direction||O,f=!a.isDirty&&a.lastLayout&&a.lastLayout.requestedHeight===a.layout.height&&a.lastLayout.requestedWidth===a.layout.width&&a.lastLayout.parentMaxWidth===b&&a.lastLayout.parentMaxHeight===c&&a.lastLayout.direction===e;f?(a.layout.width=a.lastLayout.width,a.layout.height=a.lastLayout.height,a.layout.top=a.lastLayout.top,a.layout.left=a.lastLayout.left):(a.lastLayout||(a.lastLayout={}),a.lastLayout.requestedWidth=a.layout.width,a.lastLayout.requestedHeight=a.layout.height,a.lastLayout.parentMaxWidth=b,a.lastLayout.parentMaxHeight=c,a.lastLayout.direction=e,a.children.forEach(function(a){a.layout.width=void 0,a.layout.height=void 0,a.layout.top=0,a.layout.left=0}),K(a,b,c,d),a.lastLayout.width=a.layout.width,a.lastLayout.height=a.layout.height,a.lastLayout.top=a.layout.top,a.lastLayout.left=a.layout.left)}var M,N="inherit",O="ltr",P="rtl",Q="row",R="row-reverse",S="column",T="column-reverse",U="flex-start",V="center",W="flex-end",X="space-between",Y="space-around",Z="flex-start",$="center",_="flex-end",aa="stretch",ba="relative",ca="absolute",da="undefined",ea="exactly",fa="at-most",ga={row:"left","row-reverse":"right",column:"top","column-reverse":"bottom"},ha={row:"right","row-reverse":"left",column:"bottom","column-reverse":"top"},ia={row:"left","row-reverse":"right",column:"top","column-reverse":"bottom"},ja={row:"width","row-reverse":"width",column:"height","column-reverse":"height"};return{layoutNodeImpl:K,computeLayout:L,fillNodes:a}}();return"object"==typeof exports&&(module.exports=a),function(b){a.fillNodes(b),a.computeLayout(b)}}); +!function(a,b){"function"==typeof define&&define.amd?define([],b):"object"==typeof exports?module.exports=b():a.computeLayout=b()}(this,function(){var a=function(){function a(b){if(b.layout&&!b.isDirty||(b.layout={width:void 0,height:void 0,top:0,left:0,right:0,bottom:0}),b.style||(b.style={}),b.children||(b.children=[]),b.style.measure&&b.children&&b.children.length)throw new Error("Using custom measure function is supported only for leaf nodes.");return b.children.forEach(a),b}function b(a){return void 0===a||Number.isNaN(a)}function c(a){return a===ca||a===da}function d(a){return a===ea||a===fa}function e(a){return void 0===a.style.flex?0:a.style.flex}function f(a){return V?!0:e(a)<=0}function g(a){return e(a)>0?e(a):0}function h(a){if(V){if(0!==e(a))return 1}else if(e(a)<0)return 1;return 0}function i(a,b){if(void 0!==a.style.marginStart&&c(b))return a.style.marginStart;var d=null;switch(b){case"row":d=a.style.marginLeft;break;case"row-reverse":d=a.style.marginRight;break;case"column":d=a.style.marginTop;break;case"column-reverse":d=a.style.marginBottom}return void 0!==d?d:void 0!==a.style.margin?a.style.margin:0}function j(a,b){if(void 0!==a.style.marginEnd&&c(b))return a.style.marginEnd;var d=null;switch(b){case"row":d=a.style.marginRight;break;case"row-reverse":d=a.style.marginLeft;break;case"column":d=a.style.marginBottom;break;case"column-reverse":d=a.style.marginTop}return null!=d?d:void 0!==a.style.margin?a.style.margin:0}function k(a,b){if(void 0!==a.style.paddingStart&&a.style.paddingStart>=0&&c(b))return a.style.paddingStart;var d=null;switch(b){case"row":d=a.style.paddingLeft;break;case"row-reverse":d=a.style.paddingRight;break;case"column":d=a.style.paddingTop;break;case"column-reverse":d=a.style.paddingBottom}return null!=d&&d>=0?d:void 0!==a.style.padding&&a.style.padding>=0?a.style.padding:0}function l(a,b){if(void 0!==a.style.paddingEnd&&a.style.paddingEnd>=0&&c(b))return a.style.paddingEnd;var d=null;switch(b){case"row":d=a.style.paddingRight;break;case"row-reverse":d=a.style.paddingLeft;break;case"column":d=a.style.paddingBottom;break;case"column-reverse":d=a.style.paddingTop}return null!=d&&d>=0?d:void 0!==a.style.padding&&a.style.padding>=0?a.style.padding:0}function m(a,b){if(void 0!==a.style.borderStartWidth&&a.style.borderStartWidth>=0&&c(b))return a.style.borderStartWidth;var d=null;switch(b){case"row":d=a.style.borderLeftWidth;break;case"row-reverse":d=a.style.borderRightWidth;break;case"column":d=a.style.borderTopWidth;break;case"column-reverse":d=a.style.borderBottomWidth}return null!=d&&d>=0?d:void 0!==a.style.borderWidth&&a.style.borderWidth>=0?a.style.borderWidth:0}function n(a,b){if(void 0!==a.style.borderEndWidth&&a.style.borderEndWidth>=0&&c(b))return a.style.borderEndWidth;var d=null;switch(b){case"row":d=a.style.borderRightWidth;break;case"row-reverse":d=a.style.borderLeftWidth;break;case"column":d=a.style.borderBottomWidth;break;case"column-reverse":d=a.style.borderTopWidth}return null!=d&&d>=0?d:void 0!==a.style.borderWidth&&a.style.borderWidth>=0?a.style.borderWidth:0}function o(a,b){return k(a,b)+m(a,b)}function p(a,b){return l(a,b)+n(a,b)}function q(a,b){return i(a,b)+j(a,b)}function r(a,b){return o(a,b)+p(a,b)}function s(a){return a.style.justifyContent?a.style.justifyContent:"flex-start"}function t(a){return a.style.alignContent?a.style.alignContent:"flex-start"}function u(a,b){return b.style.alignSelf?b.style.alignSelf:a.style.alignItems?a.style.alignItems:"stretch"}function v(a,b){if(b===ba){if(a===ca)return da;if(a===da)return ca}return a}function w(a,b){var c;return c=a.style.direction?a.style.direction:_,c===_&&(c=void 0===b?aa:b),c}function x(a){return a.style.flexDirection?a.style.flexDirection:ea}function y(a,b){return d(a)?v(ca,b):ea}function z(a){return a.style.position?a.style.position:pa}function A(a){return a.style.overflow?a.style.overflow:ra}function B(a){return z(a)===pa&&void 0!==a.style.flex&&0!==a.style.flex}function C(a){return"wrap"===a.style.flexWrap}function D(a,b){return a.layout[Aa[b]]+q(a,b)}function E(a,b){return void 0!==a.style[za[b]]&&a.style[za[b]]>=0}function F(a,b){return void 0!==a.layout[Aa[b]]&&a.layout[Aa[b]]>=0}function G(a,b){return void 0!==a.style[b]}function H(a){return void 0!==a.style.measure}function I(a,b){return void 0!==a.style[b]?a.style[b]:0}function J(a,b,c){var d={row:a.style.minWidth,"row-reverse":a.style.minWidth,column:a.style.minHeight,"column-reverse":a.style.minHeight}[b],e={row:a.style.maxWidth,"row-reverse":a.style.maxWidth,column:a.style.maxHeight,"column-reverse":a.style.maxHeight}[b],f=c;return void 0!==e&&e>=0&&f>e&&(f=e),void 0!==d&&d>=0&&d>f&&(f=d),f}function K(a,b){return b>a?a:b}function L(a,b){return a>b?a:b}function M(a,b,c){return L(J(a,b,c),r(a,b))}function N(a,b,c){var d=z(b)===qa?0:b.layout[Aa[c]];b.layout[xa[c]]=a.layout[Aa[c]]-d-b.layout[ya[c]]}function O(a,b){return void 0!==a.style[wa[b]]?I(a,wa[b]):-I(a,xa[b])}function P(a,b){var c=v(x(a),b),d=y(c,b);a.layout[wa[c]]=i(a,c)+O(a,c),a.layout[xa[c]]=j(a,c)+O(a,c),a.layout[wa[d]]=i(a,d)+O(a,d),a.layout[xa[d]]=j(a,d)+O(a,d)}function Q(a,b){if(!a)throw new Error(b)}function R(a,d,e,k,l,O,R){Q(b(d)?l===ta:!0,"availableWidth is indefinite so widthMeasureMode must be CSS_MEASURE_MODE_UNDEFINED"),Q(b(e)?O===ta:!0,"availableHeight is indefinite so heightMeasureMode must be CSS_MEASURE_MODE_UNDEFINED");var T=r(a,ca),V=r(a,ea),W=q(a,ca),_=q(a,ea),aa=w(a,k);if(a.layout.direction=aa,H(a)){var ba=d-W-T,ra=e-_-V;if(l===ua&&O===ua)a.layout.measuredWidth=M(a,ca,d-W),a.layout.measuredHeight=M(a,ea,e-_);else if(0>=ba)a.layout.measuredWidth=M(a,ca,0),a.layout.measuredHeight=M(a,ea,0);else{var za=a.style.measure(ba,l,ra,O);a.layout.measuredWidth=M(a,ca,l===ta||l===va?za.width+T:d-W),a.layout.measuredHeight=M(a,ea,O===ta||O===va?za.height+V:e-_)}}else{var Ba=a.children.length;if(0===Ba)return a.layout.measuredWidth=M(a,ca,l===ta||l===va?T:d-W),void(a.layout.measuredHeight=M(a,ea,O===ta||O===va?V:e-_));if(!R){if(l===va&&0>=d&&O===va&&0>=e)return a.layout.measuredWidth=M(a,ca,0),void(a.layout.measuredHeight=M(a,ea,0));if(l===va&&0>=d)return a.layout.measuredWidth=M(a,ca,0),void(a.layout.measuredHeight=M(a,ea,b(e)?0:e-_));if(O===va&&0>=e)return a.layout.measuredWidth=M(a,ca,b(d)?0:d-W),void(a.layout.measuredHeight=M(a,ea,0));if(l===ua&&O===ua)return a.layout.measuredWidth=M(a,ca,d-W),void(a.layout.measuredHeight=M(a,ea,e-_))}var Ca,Da,Ea,Fa,Ga,Ha,Ia=v(x(a),aa),Ja=y(Ia,aa),Ka=c(Ia),La=s(a),Ma=C(a),Na=void 0,Oa=void 0,Pa=o(a,Ia),Qa=p(a,Ia),Ra=o(a,Ja),Sa=r(a,Ia),Ta=r(a,Ja),Ua=Ka?l:O,Va=Ka?O:l,Wa=d-W-T,Xa=e-_-V,Ya=Ka?Wa:Xa,Za=Ka?Xa:Wa;for(Da=0;Ba>Da;Da++){if(Ca=a.children[Da],R){var $a=w(Ca,aa);P(Ca,$a)}z(Ca)===qa?(void 0===Na&&(Na=Ca),void 0!==Oa&&(Oa.nextChild=Ca),Oa=Ca,Ca.nextChild=void 0):Ka&&E(Ca,ca)?Ca.layout.flexBasis=L(Ca.style.width,r(Ca,ca)):!Ka&&E(Ca,ea)?Ca.layout.flexBasis=L(Ca.style.height,r(Ca,ea)):f(Ca)||b(Ya)?(Ea=U,Fa=U,Ga=ta,Ha=ta,E(Ca,ca)&&(Ea=Ca.style.width+q(Ca,ca),Ga=ua),E(Ca,ea)&&(Fa=Ca.style.height+q(Ca,ea),Ha=ua),Ka||!b(Ea)||b(Wa)||(Ea=Wa,Ga=va),A(a)===sa&&Ka&&b(Fa)&&!b(Xa)&&(Fa=Xa,Ha=va),S(Ca,Ea,Fa,aa,Ga,Ha,!1,"measure"),Ca.layout.flexBasis=L(Ka?Ca.layout.measuredWidth:Ca.layout.measuredHeight,r(Ca,Ia))):Ca.layout.flexBasis=L(0,r(Ca,Ia))}for(var _a=0,ab=0,bb=0,cb=0,db=0;Ba>ab;){var eb=0,fb=0,gb=0,hb=0;Da=_a;for(var ib=void 0,jb=void 0;Ba>Da;){if(Ca=a.children[Da],Ca.lineIndex=bb,z(Ca)!==qa){var kb=Ca.layout.flexBasis+q(Ca,Ia);if(fb+kb>Ya&&Ma&&eb>0)break;fb+=kb,eb++,B(Ca)&&(gb+=g(Ca),hb+=h(Ca)*Ca.layout.flexBasis),void 0===ib&&(ib=Ca),void 0!==jb&&(jb.nextChild=Ca),jb=Ca,Ca.nextChild=void 0}Da++,ab++}var lb=!R&&Va===ua,mb=0,nb=0,ob=0;b(Ya)?0>fb&&(ob=-fb):ob=Ya-fb;var pb=ob;if(!lb){var qb,rb,sb,tb,ub,vb=0,wb=0,xb=0;for(jb=ib;void 0!==jb;)qb=jb.layout.flexBasis,0>ob?(rb=h(jb)*qb,0!==rb&&(tb=qb+ob/hb*rb,ub=M(jb,Ia,tb),tb!==ub&&(vb-=ub,wb-=rb))):ob>0&&(sb=g(jb),0!==sb&&(tb=qb+ob/gb*sb,ub=M(jb,Ia,tb),tb!==ub&&(vb-=ub,xb-=sb))),jb=jb.nextChild;for(hb+=wb,gb+=xb,ob+=vb,pb=ob,jb=ib;void 0!==jb;){qb=jb.layout.flexBasis;var yb=qb;0>ob?(rb=h(jb)*qb,0!==rb&&(yb=M(jb,Ia,qb+ob/hb*rb))):ob>0&&(sb=g(jb),0!==sb&&(yb=M(jb,Ia,qb+ob/gb*sb))),pb-=yb-qb,Ka?(Ea=yb+q(jb,ca),Ga=ua,E(jb,ea)?(Fa=jb.style.height+q(jb,ea),Ha=ua):(Fa=Za,Ha=b(Fa)?ta:va)):(Fa=yb+q(jb,ea),Ha=ua,E(jb,ca)?(Ea=jb.style.width+q(jb,ca),Ga=ua):(Ea=Za,Ga=b(Ea)?ta:va));var zb=!E(jb,Ja)&&u(a,jb)===oa;S(jb,Ea,Fa,aa,Ga,Ha,R&&!zb,"flex"),jb=jb.nextChild}}ob=pb,Ua===va&&(ob=0),La!==ga&&(La===ha?mb=ob/2:La===ia?mb=ob:La===ja?(ob=L(ob,0),nb=eb>1?ob/(eb-1):0):La===ka&&(nb=ob/eb,mb=nb/2));var Ab=Pa+mb,Bb=0;for(Da=_a;ab>Da;++Da)Ca=a.children[Da],z(Ca)===qa&&G(Ca,wa[Ia])?R&&(Ca.layout[ya[Ia]]=I(Ca,wa[Ia])+m(a,Ia)+i(Ca,Ia)):(R&&(Ca.layout[ya[Ia]]+=Ab),z(Ca)===pa&&(lb?(Ab+=nb+q(Ca,Ia)+Ca.layout.flexBasis,Bb=Za):(Ab+=nb+D(Ca,Ia),Bb=L(Bb,D(Ca,Ja)))));Ab+=Qa;var Cb=Za;if(Va!==ta&&Va!==va||(Cb=M(a,Ja,Bb+Ta)-Ta,Va===va&&(Cb=K(Cb,Za))),Ma||Va!==ua||(Bb=Za),Bb=M(a,Ja,Bb+Ta)-Ta,R)for(Da=_a;ab>Da;++Da)if(Ca=a.children[Da],z(Ca)===qa)G(Ca,wa[Ja])?Ca.layout[ya[Ja]]=I(Ca,wa[Ja])+m(a,Ja)+i(Ca,Ja):Ca.layout[ya[Ja]]=Ra+i(Ca,Ja);else{var Db=Ra,Eb=u(a,Ca);if(Eb===oa){Ea=Ca.layout.measuredWidth+q(Ca,ca),Fa=Ca.layout.measuredHeight+q(Ca,ea);var Fb=!1;Ka?(Fb=E(Ca,ea),Fa=Bb):(Fb=E(Ca,ca),Ea=Bb),Fb||(Ga=b(Ea)?ta:ua,Ha=b(Fa)?ta:ua,S(Ca,Ea,Fa,aa,Ga,Ha,!0,"stretch"))}else if(Eb!==la){var Gb=Cb-D(Ca,Ja);Db+=Eb===ma?Gb/2:Gb}Ca.layout[ya[Ja]]+=cb+Db}cb+=Bb,db=L(db,Ab),bb++,_a=ab,ab=_a}if(bb>1&&R&&!b(Za)){var Hb=Za-cb,Ib=0,Jb=Ra,Kb=t(a);Kb===na?Jb+=Hb:Kb===ma?Jb+=Hb/2:Kb===oa&&Za>cb&&(Ib=Hb/bb);var Lb=0;for(Da=0;bb>Da;++Da){var Mb,Nb=Lb,Ob=0;for(Mb=Nb;Ba>Mb;++Mb)if(Ca=a.children[Mb],z(Ca)===pa){if(Ca.lineIndex!==Da)break;F(Ca,Ja)&&(Ob=L(Ob,Ca.layout[Aa[Ja]]+q(Ca,Ja)))}if(Lb=Mb,Ob+=Ib,R)for(Mb=Nb;Lb>Mb;++Mb)if(Ca=a.children[Mb],z(Ca)===pa){var Pb=u(a,Ca);Pb===la?Ca.layout[ya[Ja]]=Jb+i(Ca,Ja):Pb===na?Ca.layout[ya[Ja]]=Jb+Ob-j(Ca,Ja)-Ca.layout[Aa[Ja]]:Pb===ma?(Fa=Ca.layout[Aa[Ja]],Ca.layout[ya[Ja]]=Jb+(Ob-Fa)/2):Pb===oa&&(Ca.layout[ya[Ja]]=Jb+i(Ca,Ja))}Jb+=Ob}}if(a.layout.measuredWidth=M(a,ca,d-W),a.layout.measuredHeight=M(a,ea,e-_),Ua===ta?a.layout[Aa[Ia]]=M(a,Ia,db):Ua===va&&(a.layout[Aa[Ia]]=L(K(Ya+Sa,J(a,Ia,db)),Sa)),Va===ta?a.layout[Aa[Ja]]=M(a,Ja,cb+Ta):Va===va&&(a.layout[Aa[Ja]]=L(K(Za+Ta,J(a,Ja,cb+Ta)),Ta)),R){var Qb=!1,Rb=!1;if(Ia!==da&&Ia!==fa||(Qb=!0),Ja!==da&&Ja!==fa||(Rb=!0),Qb||Rb)for(Da=0;Ba>Da;++Da)Ca=a.children[Da],Qb&&N(a,Ca,Ia),Rb&&N(a,Ca,Ja)}for(Oa=Na;void 0!==Oa;)R&&(Ea=U,Fa=U,E(Oa,ca)?Ea=Oa.style.width+q(Oa,ca):G(Oa,X)&&G(Oa,Z)&&(Ea=a.layout.measuredWidth-(m(a,ca)+n(a,ca))-(Oa.style[X]+Oa.style[Z]),Ea=M(Oa,ca,Ea)),E(Oa,ea)?Fa=Oa.style.height+q(Oa,ea):G(Oa,Y)&&G(Oa,$)&&(Fa=a.layout.measuredHeight-(m(a,ea)+n(a,ea))-(Oa.style[Y]+Oa.style[$]),Fa=M(Oa,ea,Fa)),(b(Ea)||b(Fa))&&(Ga=b(Ea)?ta:ua,Ha=b(Fa)?ta:ua,Ka||!b(Ea)||b(Wa)||(Ea=Wa,Ga=va),A(a)===sa&&Ka&&b(Fa)&&!b(Xa)&&(Fa=Xa,Ha=va),S(Oa,Ea,Fa,aa,Ga,Ha,!1,"abs-measure"),Ea=Oa.layout.measuredWidth+q(Oa,ca),Fa=Oa.layout.measuredHeight+q(Oa,ea)),S(Oa,Ea,Fa,aa,ua,ua,!0,"abs-layout"),G(Oa,xa[ca])&&!G(Oa,wa[ca])&&(Oa.layout[wa[ca]]=a.layout[Aa[ca]]-Oa.layout[Aa[ca]]-I(Oa,xa[ca])),G(Oa,xa[ea])&&!G(Oa,wa[ea])&&(Oa.layout[wa[ea]]=a.layout[Aa[ea]]-Oa.layout[Aa[ea]]-I(Oa,xa[ea]))),Oa=Oa.nextChild}}function S(a,b,c,d,e,f,g,h){var i=a.layout,j=a.isDirty&&i.generationCount!==W||i.lastParentDirection!==d;j&&(void 0!==i.cachedMeasurements&&(i.cachedMeasurements=[]),void 0!==i.cachedLayout&&(i.cachedLayout.widthMeasureMode=void 0,i.cachedLayout.heightMeasureMode=void 0));var k;if(g)i.cachedLayout&&i.cachedLayout.availableWidth===b&&i.cachedLayout.availableHeight===c&&i.cachedLayout.widthMeasureMode===e&&i.cachedLayout.heightMeasureMode===f&&(k=i.cachedLayout);else if(i.cachedMeasurements)for(var l=0,m=i.cachedMeasurements.length;m>l;l++)if(i.cachedMeasurements[l].availableWidth===b&&i.cachedMeasurements[l].availableHeight===c&&i.cachedMeasurements[l].widthMeasureMode===e&&i.cachedMeasurements[l].heightMeasureMode===f){k=i.cachedMeasurements[l];break}if(j||void 0===k){if(R(a,b,c,d,e,f,g),i.lastParentDirection=d,void 0===k){var n;g?(void 0===i.cachedLayout&&(i.cachedLayout={}),n=i.cachedLayout):(void 0===i.cachedMeasurements&&(i.cachedMeasurements=[]),n={},i.cachedMeasurements.push(n)),n.availableWidth=b,n.availableHeight=c,n.widthMeasureMode=e,n.heightMeasureMode=f,n.computedWidth=i.measuredWidth,n.computedHeight=i.measuredHeight}}else i.measureWidth=k.computedWidth,i.measureHeight=k.computedHeight;return g&&(a.layout.width=a.layout.measuredWidth,a.layout.height=a.layout.measuredHeight,i.shouldUpdate=!0),i.generationCount=W,j||void 0===k}function T(a,c,d,e){W++,b(c)&&E(a,ca)&&(c=a.style.width+q(a,ca)),b(d)&&E(a,ea)&&(d=a.style.height+q(a,ea));var f=b(c)?ta:ua,g=b(d)?ta:ua;S(a,c,d,e,f,g,!0,"initial")&&P(a,a.layout.direction)}var U,V=!1,W=0,X="left",Y="top",Z="right",$="bottom",_="inherit",aa="ltr",ba="rtl",ca="row",da="row-reverse",ea="column",fa="column-reverse",ga="flex-start",ha="center",ia="flex-end",ja="space-between",ka="space-around",la="flex-start",ma="center",na="flex-end",oa="stretch",pa="relative",qa="absolute",ra="visible",sa="hidden",ta="undefined",ua="exactly",va="at-most",wa={row:"left","row-reverse":"right",column:"top","column-reverse":"bottom"},xa={row:"right","row-reverse":"left",column:"bottom","column-reverse":"top"},ya={row:"left","row-reverse":"right",column:"top","column-reverse":"bottom"},za={row:"width","row-reverse":"width",column:"height","column-reverse":"height"},Aa={row:"measuredWidth","row-reverse":"measuredWidth",column:"measuredHeight","column-reverse":"measuredHeight"};return{layoutNodeImpl:R,computeLayout:T,fillNodes:a}}();return"object"==typeof exports&&(module.exports=a),function(b){a.fillNodes(b),a.computeLayout(b)}}); //# sourceMappingURL=css-layout.min.js.map \ No newline at end of file diff --git a/dist/css-layout.min.js.map b/dist/css-layout.min.js.map index 0d933772..58ab8daa 100644 --- a/dist/css-layout.min.js.map +++ b/dist/css-layout.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["css-layout.js"],"names":["root","factory","define","amd","exports","module","computeLayout","this","fillNodes","node","layout","isDirty","width","undefined","height","top","left","right","bottom","style","children","measure","length","Error","forEach","isUndefined","value","isNaN","isRowDirection","flexDirection","CSS_FLEX_DIRECTION_ROW","CSS_FLEX_DIRECTION_ROW_REVERSE","isColumnDirection","CSS_FLEX_DIRECTION_COLUMN","CSS_FLEX_DIRECTION_COLUMN_REVERSE","getLeadingMargin","axis","marginStart","marginLeft","marginRight","marginTop","marginBottom","margin","getTrailingMargin","marginEnd","getLeadingPadding","paddingStart","paddingLeft","paddingRight","paddingTop","paddingBottom","padding","getTrailingPadding","paddingEnd","getLeadingBorder","borderStartWidth","borderLeftWidth","borderRightWidth","borderTopWidth","borderBottomWidth","borderWidth","getTrailingBorder","borderEndWidth","getLeadingPaddingAndBorder","getTrailingPaddingAndBorder","getBorderAxis","getMarginAxis","getPaddingAndBorderAxis","getJustifyContent","justifyContent","getAlignContent","alignContent","getAlignItem","child","alignSelf","alignItems","resolveAxis","direction","CSS_DIRECTION_RTL","resolveDirection","parentDirection","CSS_DIRECTION_INHERIT","CSS_DIRECTION_LTR","getFlexDirection","getCrossFlexDirection","getPositionType","position","isFlex","CSS_POSITION_RELATIVE","flex","isFlexWrap","flexWrap","getDimWithMargin","dim","isStyleDimDefined","isLayoutDimDefined","isPosDefined","pos","isMeasureDefined","getPosition","boundAxis","min","row","minWidth","row-reverse","column","minHeight","column-reverse","max","maxWidth","maxHeight","boundValue","fmaxf","a","b","setDimensionFromStyle","setTrailingPosition","trailing","getRelativePosition","leading","layoutNodeImpl","parentMaxWidth","parentMaxHeight","mainAxis","crossAxis","resolvedRowAxis","childCount","paddingAndBorderAxisResolvedRow","paddingAndBorderAxisColumn","isResolvedRowDimDefined","CSS_UNDEFINED","widthMode","CSS_MEASURE_MODE_UNDEFINED","CSS_MEASURE_MODE_EXACTLY","CSS_MEASURE_MODE_AT_MOST","heightMode","isRowUndefined","isColumnUndefined","measureDim","i","ii","isNodeFlexWrap","leadingPaddingAndBorderMain","leadingPaddingAndBorderCross","paddingAndBorderAxisMain","paddingAndBorderAxisCross","isMainDimDefined","isCrossDimDefined","isMainRowDirection","firstAbsoluteChild","currentAbsoluteChild","definedMainDim","startLine","endLine","alreadyComputedNextLayout","linesCrossDim","linesMainDim","linesCount","mainContentDim","flexibleChildrenCount","totalFlexible","nonFlexibleChildrenCount","isSimpleStackMain","CSS_JUSTIFY_FLEX_START","CSS_JUSTIFY_CENTER","firstComplexMain","isSimpleStackCross","firstComplexCross","firstFlexChild","currentFlexChild","mainDim","crossDim","lineIndex","nextAbsoluteChild","nextFlexChild","alignItem","CSS_ALIGN_STRETCH","CSS_POSITION_ABSOLUTE","nextContentDim","layoutNode","CSS_ALIGN_FLEX_START","leadingMainDim","betweenMainDim","remainingMainDim","baseMainDim","boundMainDim","flexibleMainDim","CSS_JUSTIFY_FLEX_END","CSS_JUSTIFY_SPACE_BETWEEN","CSS_JUSTIFY_SPACE_AROUND","containerCrossAxis","leadingCrossDim","dimCrossAxis","remainingCrossDim","CSS_ALIGN_CENTER","nodeCrossAxisInnerSize","remainingAlignContentDim","crossDimLead","currentLead","CSS_ALIGN_FLEX_END","endIndex","startIndex","lineHeight","alignContentAlignItem","childHeight","needsMainTrailingPos","needsCrossTrailingPos","shouldUpdate","skipLayout","lastLayout","requestedHeight","requestedWidth"],"mappings":"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","file":"css-layout.min.js","sourcesContent":["// UMD (Universal Module Definition)\n// See https://github.com/umdjs/umd for reference\n//\n// This file uses the following specific UMD implementation:\n// https://github.com/umdjs/umd/blob/master/templates/returnExports.js\n(function(root, factory) {\n if (typeof define === 'function' && define.amd) {\n // AMD. Register as an anonymous module.\n define([], factory);\n } else if (typeof exports === 'object') {\n // Node. Does not work with strict CommonJS, but\n // only CommonJS-like environments that support module.exports,\n // like Node.\n module.exports = factory();\n } else {\n // Browser globals (root is window)\n root.computeLayout = factory();\n }\n}(this, function() {\n /**\n * Copyright (c) 2014, Facebook, Inc.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree. An additional grant\n * of patent rights can be found in the PATENTS file in the same directory.\n */\n\nvar computeLayout = (function() {\n\n var CSS_UNDEFINED;\n\n var CSS_DIRECTION_INHERIT = 'inherit';\n var CSS_DIRECTION_LTR = 'ltr';\n var CSS_DIRECTION_RTL = 'rtl';\n\n var CSS_FLEX_DIRECTION_ROW = 'row';\n var CSS_FLEX_DIRECTION_ROW_REVERSE = 'row-reverse';\n var CSS_FLEX_DIRECTION_COLUMN = 'column';\n var CSS_FLEX_DIRECTION_COLUMN_REVERSE = 'column-reverse';\n\n var CSS_JUSTIFY_FLEX_START = 'flex-start';\n var CSS_JUSTIFY_CENTER = 'center';\n var CSS_JUSTIFY_FLEX_END = 'flex-end';\n var CSS_JUSTIFY_SPACE_BETWEEN = 'space-between';\n var CSS_JUSTIFY_SPACE_AROUND = 'space-around';\n\n var CSS_ALIGN_FLEX_START = 'flex-start';\n var CSS_ALIGN_CENTER = 'center';\n var CSS_ALIGN_FLEX_END = 'flex-end';\n var CSS_ALIGN_STRETCH = 'stretch';\n\n var CSS_POSITION_RELATIVE = 'relative';\n var CSS_POSITION_ABSOLUTE = 'absolute';\n\n var CSS_MEASURE_MODE_UNDEFINED = 'undefined';\n var CSS_MEASURE_MODE_EXACTLY = 'exactly';\n var CSS_MEASURE_MODE_AT_MOST = 'at-most';\n\n var leading = {\n 'row': 'left',\n 'row-reverse': 'right',\n 'column': 'top',\n 'column-reverse': 'bottom'\n };\n var trailing = {\n 'row': 'right',\n 'row-reverse': 'left',\n 'column': 'bottom',\n 'column-reverse': 'top'\n };\n var pos = {\n 'row': 'left',\n 'row-reverse': 'right',\n 'column': 'top',\n 'column-reverse': 'bottom'\n };\n var dim = {\n 'row': 'width',\n 'row-reverse': 'width',\n 'column': 'height',\n 'column-reverse': 'height'\n };\n\n // When transpiled to Java / C the node type has layout, children and style\n // properties. For the JavaScript version this function adds these properties\n // if they don't already exist.\n function fillNodes(node) {\n if (!node.layout || node.isDirty) {\n node.layout = {\n width: undefined,\n height: undefined,\n top: 0,\n left: 0,\n right: 0,\n bottom: 0\n };\n }\n\n if (!node.style) {\n node.style = {};\n }\n\n if (!node.children) {\n node.children = [];\n }\n\n if (node.style.measure && node.children && node.children.length) {\n throw new Error('Using custom measure function is supported only for leaf nodes.');\n }\n\n node.children.forEach(fillNodes);\n return node;\n }\n\n function isUndefined(value) {\n return value === undefined || isNaN(value);\n }\n\n function isRowDirection(flexDirection) {\n return flexDirection === CSS_FLEX_DIRECTION_ROW ||\n flexDirection === CSS_FLEX_DIRECTION_ROW_REVERSE;\n }\n\n function isColumnDirection(flexDirection) {\n return flexDirection === CSS_FLEX_DIRECTION_COLUMN ||\n flexDirection === CSS_FLEX_DIRECTION_COLUMN_REVERSE;\n }\n\n function getLeadingMargin(node, axis) {\n if (node.style.marginStart !== undefined && isRowDirection(axis)) {\n return node.style.marginStart;\n }\n\n var value = null;\n switch (axis) {\n case 'row': value = node.style.marginLeft; break;\n case 'row-reverse': value = node.style.marginRight; break;\n case 'column': value = node.style.marginTop; break;\n case 'column-reverse': value = node.style.marginBottom; break;\n }\n\n if (value !== undefined) {\n return value;\n }\n\n if (node.style.margin !== undefined) {\n return node.style.margin;\n }\n\n return 0;\n }\n\n function getTrailingMargin(node, axis) {\n if (node.style.marginEnd !== undefined && isRowDirection(axis)) {\n return node.style.marginEnd;\n }\n\n var value = null;\n switch (axis) {\n case 'row': value = node.style.marginRight; break;\n case 'row-reverse': value = node.style.marginLeft; break;\n case 'column': value = node.style.marginBottom; break;\n case 'column-reverse': value = node.style.marginTop; break;\n }\n\n if (value != null) {\n return value;\n }\n\n if (node.style.margin !== undefined) {\n return node.style.margin;\n }\n\n return 0;\n }\n\n function getLeadingPadding(node, axis) {\n if (node.style.paddingStart !== undefined && node.style.paddingStart >= 0\n && isRowDirection(axis)) {\n return node.style.paddingStart;\n }\n\n var value = null;\n switch (axis) {\n case 'row': value = node.style.paddingLeft; break;\n case 'row-reverse': value = node.style.paddingRight; break;\n case 'column': value = node.style.paddingTop; break;\n case 'column-reverse': value = node.style.paddingBottom; break;\n }\n\n if (value != null && value >= 0) {\n return value;\n }\n\n if (node.style.padding !== undefined && node.style.padding >= 0) {\n return node.style.padding;\n }\n\n return 0;\n }\n\n function getTrailingPadding(node, axis) {\n if (node.style.paddingEnd !== undefined && node.style.paddingEnd >= 0\n && isRowDirection(axis)) {\n return node.style.paddingEnd;\n }\n\n var value = null;\n switch (axis) {\n case 'row': value = node.style.paddingRight; break;\n case 'row-reverse': value = node.style.paddingLeft; break;\n case 'column': value = node.style.paddingBottom; break;\n case 'column-reverse': value = node.style.paddingTop; break;\n }\n\n if (value != null && value >= 0) {\n return value;\n }\n\n if (node.style.padding !== undefined && node.style.padding >= 0) {\n return node.style.padding;\n }\n\n return 0;\n }\n\n function getLeadingBorder(node, axis) {\n if (node.style.borderStartWidth !== undefined && node.style.borderStartWidth >= 0\n && isRowDirection(axis)) {\n return node.style.borderStartWidth;\n }\n\n var value = null;\n switch (axis) {\n case 'row': value = node.style.borderLeftWidth; break;\n case 'row-reverse': value = node.style.borderRightWidth; break;\n case 'column': value = node.style.borderTopWidth; break;\n case 'column-reverse': value = node.style.borderBottomWidth; break;\n }\n\n if (value != null && value >= 0) {\n return value;\n }\n\n if (node.style.borderWidth !== undefined && node.style.borderWidth >= 0) {\n return node.style.borderWidth;\n }\n\n return 0;\n }\n\n function getTrailingBorder(node, axis) {\n if (node.style.borderEndWidth !== undefined && node.style.borderEndWidth >= 0\n && isRowDirection(axis)) {\n return node.style.borderEndWidth;\n }\n\n var value = null;\n switch (axis) {\n case 'row': value = node.style.borderRightWidth; break;\n case 'row-reverse': value = node.style.borderLeftWidth; break;\n case 'column': value = node.style.borderBottomWidth; break;\n case 'column-reverse': value = node.style.borderTopWidth; break;\n }\n\n if (value != null && value >= 0) {\n return value;\n }\n\n if (node.style.borderWidth !== undefined && node.style.borderWidth >= 0) {\n return node.style.borderWidth;\n }\n\n return 0;\n }\n\n function getLeadingPaddingAndBorder(node, axis) {\n return getLeadingPadding(node, axis) + getLeadingBorder(node, axis);\n }\n\n function getTrailingPaddingAndBorder(node, axis) {\n return getTrailingPadding(node, axis) + getTrailingBorder(node, axis);\n }\n\n function getBorderAxis(node, axis) {\n return getLeadingBorder(node, axis) + getTrailingBorder(node, axis);\n }\n\n function getMarginAxis(node, axis) {\n return getLeadingMargin(node, axis) + getTrailingMargin(node, axis);\n }\n\n function getPaddingAndBorderAxis(node, axis) {\n return getLeadingPaddingAndBorder(node, axis) +\n getTrailingPaddingAndBorder(node, axis);\n }\n\n function getJustifyContent(node) {\n if (node.style.justifyContent) {\n return node.style.justifyContent;\n }\n return 'flex-start';\n }\n\n function getAlignContent(node) {\n if (node.style.alignContent) {\n return node.style.alignContent;\n }\n return 'flex-start';\n }\n\n function getAlignItem(node, child) {\n if (child.style.alignSelf) {\n return child.style.alignSelf;\n }\n if (node.style.alignItems) {\n return node.style.alignItems;\n }\n return 'stretch';\n }\n\n function resolveAxis(axis, direction) {\n if (direction === CSS_DIRECTION_RTL) {\n if (axis === CSS_FLEX_DIRECTION_ROW) {\n return CSS_FLEX_DIRECTION_ROW_REVERSE;\n } else if (axis === CSS_FLEX_DIRECTION_ROW_REVERSE) {\n return CSS_FLEX_DIRECTION_ROW;\n }\n }\n\n return axis;\n }\n\n function resolveDirection(node, parentDirection) {\n var direction;\n if (node.style.direction) {\n direction = node.style.direction;\n } else {\n direction = CSS_DIRECTION_INHERIT;\n }\n\n if (direction === CSS_DIRECTION_INHERIT) {\n direction = (parentDirection === undefined ? CSS_DIRECTION_LTR : parentDirection);\n }\n\n return direction;\n }\n\n function getFlexDirection(node) {\n if (node.style.flexDirection) {\n return node.style.flexDirection;\n }\n return CSS_FLEX_DIRECTION_COLUMN;\n }\n\n function getCrossFlexDirection(flexDirection, direction) {\n if (isColumnDirection(flexDirection)) {\n return resolveAxis(CSS_FLEX_DIRECTION_ROW, direction);\n } else {\n return CSS_FLEX_DIRECTION_COLUMN;\n }\n }\n\n function getPositionType(node) {\n if (node.style.position) {\n return node.style.position;\n }\n return 'relative';\n }\n\n function isFlex(node) {\n return (\n getPositionType(node) === CSS_POSITION_RELATIVE &&\n node.style.flex > 0\n );\n }\n\n function isFlexWrap(node) {\n return node.style.flexWrap === 'wrap';\n }\n\n function getDimWithMargin(node, axis) {\n return node.layout[dim[axis]] + getMarginAxis(node, axis);\n }\n\n function isStyleDimDefined(node, axis) {\n return node.style[dim[axis]] !== undefined && node.style[dim[axis]] >= 0;\n }\n\n function isLayoutDimDefined(node, axis) {\n return node.layout[dim[axis]] !== undefined && node.layout[dim[axis]] >= 0;\n }\n\n function isPosDefined(node, pos) {\n return node.style[pos] !== undefined;\n }\n\n function isMeasureDefined(node) {\n return node.style.measure !== undefined;\n }\n\n function getPosition(node, pos) {\n if (node.style[pos] !== undefined) {\n return node.style[pos];\n }\n return 0;\n }\n\n function boundAxis(node, axis, value) {\n var min = {\n 'row': node.style.minWidth,\n 'row-reverse': node.style.minWidth,\n 'column': node.style.minHeight,\n 'column-reverse': node.style.minHeight\n }[axis];\n\n var max = {\n 'row': node.style.maxWidth,\n 'row-reverse': node.style.maxWidth,\n 'column': node.style.maxHeight,\n 'column-reverse': node.style.maxHeight\n }[axis];\n\n var boundValue = value;\n if (max !== undefined && max >= 0 && boundValue > max) {\n boundValue = max;\n }\n if (min !== undefined && min >= 0 && boundValue < min) {\n boundValue = min;\n }\n return boundValue;\n }\n\n function fmaxf(a, b) {\n if (a > b) {\n return a;\n }\n return b;\n }\n\n // When the user specifically sets a value for width or height\n function setDimensionFromStyle(node, axis) {\n // The parent already computed us a width or height. We just skip it\n if (isLayoutDimDefined(node, axis)) {\n return;\n }\n // We only run if there's a width or height defined\n if (!isStyleDimDefined(node, axis)) {\n return;\n }\n\n // The dimensions can never be smaller than the padding and border\n node.layout[dim[axis]] = fmaxf(\n boundAxis(node, axis, node.style[dim[axis]]),\n getPaddingAndBorderAxis(node, axis)\n );\n }\n\n function setTrailingPosition(node, child, axis) {\n child.layout[trailing[axis]] = node.layout[dim[axis]] -\n child.layout[dim[axis]] - child.layout[pos[axis]];\n }\n\n // If both left and right are defined, then use left. Otherwise return\n // +left or -right depending on which is defined.\n function getRelativePosition(node, axis) {\n if (node.style[leading[axis]] !== undefined) {\n return getPosition(node, leading[axis]);\n }\n return -getPosition(node, trailing[axis]);\n }\n\n function layoutNodeImpl(node, parentMaxWidth, parentMaxHeight, /*css_direction_t*/parentDirection) {\n var/*css_direction_t*/ direction = resolveDirection(node, parentDirection);\n var/*(c)!css_flex_direction_t*//*(java)!int*/ mainAxis = resolveAxis(getFlexDirection(node), direction);\n var/*(c)!css_flex_direction_t*//*(java)!int*/ crossAxis = getCrossFlexDirection(mainAxis, direction);\n var/*(c)!css_flex_direction_t*//*(java)!int*/ resolvedRowAxis = resolveAxis(CSS_FLEX_DIRECTION_ROW, direction);\n\n // Handle width and height style attributes\n setDimensionFromStyle(node, mainAxis);\n setDimensionFromStyle(node, crossAxis);\n\n // Set the resolved resolution in the node's layout\n node.layout.direction = direction;\n\n // The position is set by the parent, but we need to complete it with a\n // delta composed of the margin and left/top/right/bottom\n node.layout[leading[mainAxis]] += getLeadingMargin(node, mainAxis) +\n getRelativePosition(node, mainAxis);\n node.layout[trailing[mainAxis]] += getTrailingMargin(node, mainAxis) +\n getRelativePosition(node, mainAxis);\n node.layout[leading[crossAxis]] += getLeadingMargin(node, crossAxis) +\n getRelativePosition(node, crossAxis);\n node.layout[trailing[crossAxis]] += getTrailingMargin(node, crossAxis) +\n getRelativePosition(node, crossAxis);\n\n // Inline immutable values from the target node to avoid excessive method\n // invocations during the layout calculation.\n var/*int*/ childCount = node.children.length;\n var/*float*/ paddingAndBorderAxisResolvedRow = getPaddingAndBorderAxis(node, resolvedRowAxis);\n var/*float*/ paddingAndBorderAxisColumn = getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_COLUMN);\n\n if (isMeasureDefined(node)) {\n var/*bool*/ isResolvedRowDimDefined = isLayoutDimDefined(node, resolvedRowAxis);\n\n var/*float*/ width = CSS_UNDEFINED;\n var/*css_measure_mode_t*/ widthMode = CSS_MEASURE_MODE_UNDEFINED;\n if (isStyleDimDefined(node, resolvedRowAxis)) {\n width = node.style.width;\n widthMode = CSS_MEASURE_MODE_EXACTLY;\n } else if (isResolvedRowDimDefined) {\n width = node.layout[dim[resolvedRowAxis]];\n widthMode = CSS_MEASURE_MODE_EXACTLY;\n } else {\n width = parentMaxWidth -\n getMarginAxis(node, resolvedRowAxis);\n widthMode = CSS_MEASURE_MODE_AT_MOST;\n }\n width -= paddingAndBorderAxisResolvedRow;\n if (isUndefined(width)) {\n widthMode = CSS_MEASURE_MODE_UNDEFINED;\n }\n\n var/*float*/ height = CSS_UNDEFINED;\n var/*css_measure_mode_t*/ heightMode = CSS_MEASURE_MODE_UNDEFINED;\n if (isStyleDimDefined(node, CSS_FLEX_DIRECTION_COLUMN)) {\n height = node.style.height;\n heightMode = CSS_MEASURE_MODE_EXACTLY;\n } else if (isLayoutDimDefined(node, CSS_FLEX_DIRECTION_COLUMN)) {\n height = node.layout[dim[CSS_FLEX_DIRECTION_COLUMN]];\n heightMode = CSS_MEASURE_MODE_EXACTLY;\n } else {\n height = parentMaxHeight -\n getMarginAxis(node, resolvedRowAxis);\n heightMode = CSS_MEASURE_MODE_AT_MOST;\n }\n height -= getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_COLUMN);\n if (isUndefined(height)) {\n heightMode = CSS_MEASURE_MODE_UNDEFINED;\n }\n\n // We only need to give a dimension for the text if we haven't got any\n // for it computed yet. It can either be from the style attribute or because\n // the element is flexible.\n var/*bool*/ isRowUndefined = !isStyleDimDefined(node, resolvedRowAxis) && !isResolvedRowDimDefined;\n var/*bool*/ isColumnUndefined = !isStyleDimDefined(node, CSS_FLEX_DIRECTION_COLUMN) &&\n isUndefined(node.layout[dim[CSS_FLEX_DIRECTION_COLUMN]]);\n\n // Let's not measure the text if we already know both dimensions\n if (isRowUndefined || isColumnUndefined) {\n var/*css_dim_t*/ measureDim = node.style.measure(\n /*(c)!node->context,*/\n /*(java)!layoutContext.measureOutput,*/\n width,\n widthMode,\n height,\n heightMode\n );\n if (isRowUndefined) {\n node.layout.width = measureDim.width +\n paddingAndBorderAxisResolvedRow;\n }\n if (isColumnUndefined) {\n node.layout.height = measureDim.height +\n paddingAndBorderAxisColumn;\n }\n }\n if (childCount === 0) {\n return;\n }\n }\n\n var/*bool*/ isNodeFlexWrap = isFlexWrap(node);\n\n var/*css_justify_t*/ justifyContent = getJustifyContent(node);\n\n var/*float*/ leadingPaddingAndBorderMain = getLeadingPaddingAndBorder(node, mainAxis);\n var/*float*/ leadingPaddingAndBorderCross = getLeadingPaddingAndBorder(node, crossAxis);\n var/*float*/ paddingAndBorderAxisMain = getPaddingAndBorderAxis(node, mainAxis);\n var/*float*/ paddingAndBorderAxisCross = getPaddingAndBorderAxis(node, crossAxis);\n\n var/*bool*/ isMainDimDefined = isLayoutDimDefined(node, mainAxis);\n var/*bool*/ isCrossDimDefined = isLayoutDimDefined(node, crossAxis);\n var/*bool*/ isMainRowDirection = isRowDirection(mainAxis);\n\n var/*int*/ i;\n var/*int*/ ii;\n var/*css_node_t**/ child;\n var/*(c)!css_flex_direction_t*//*(java)!int*/ axis;\n\n var/*css_node_t**/ firstAbsoluteChild = null;\n var/*css_node_t**/ currentAbsoluteChild = null;\n\n var/*float*/ definedMainDim = CSS_UNDEFINED;\n if (isMainDimDefined) {\n definedMainDim = node.layout[dim[mainAxis]] - paddingAndBorderAxisMain;\n }\n\n // We want to execute the next two loops one per line with flex-wrap\n var/*int*/ startLine = 0;\n var/*int*/ endLine = 0;\n // var/*int*/ nextOffset = 0;\n var/*int*/ alreadyComputedNextLayout = 0;\n // We aggregate the total dimensions of the container in those two variables\n var/*float*/ linesCrossDim = 0;\n var/*float*/ linesMainDim = 0;\n var/*int*/ linesCount = 0;\n while (endLine < childCount) {\n // Layout non flexible children and count children by type\n\n // mainContentDim is accumulation of the dimensions and margin of all the\n // non flexible children. This will be used in order to either set the\n // dimensions of the node if none already exist, or to compute the\n // remaining space left for the flexible children.\n var/*float*/ mainContentDim = 0;\n\n // There are three kind of children, non flexible, flexible and absolute.\n // We need to know how many there are in order to distribute the space.\n var/*int*/ flexibleChildrenCount = 0;\n var/*float*/ totalFlexible = 0;\n var/*int*/ nonFlexibleChildrenCount = 0;\n\n // Use the line loop to position children in the main axis for as long\n // as they are using a simple stacking behaviour. Children that are\n // immediately stacked in the initial loop will not be touched again\n // in .\n var/*bool*/ isSimpleStackMain =\n (isMainDimDefined && justifyContent === CSS_JUSTIFY_FLEX_START) ||\n (!isMainDimDefined && justifyContent !== CSS_JUSTIFY_CENTER);\n var/*int*/ firstComplexMain = (isSimpleStackMain ? childCount : startLine);\n\n // Use the initial line loop to position children in the cross axis for\n // as long as they are relatively positioned with alignment STRETCH or\n // FLEX_START. Children that are immediately stacked in the initial loop\n // will not be touched again in .\n var/*bool*/ isSimpleStackCross = true;\n var/*int*/ firstComplexCross = childCount;\n\n var/*css_node_t**/ firstFlexChild = null;\n var/*css_node_t**/ currentFlexChild = null;\n\n var/*float*/ mainDim = leadingPaddingAndBorderMain;\n var/*float*/ crossDim = 0;\n\n var/*float*/ maxWidth = CSS_UNDEFINED;\n var/*float*/ maxHeight = CSS_UNDEFINED;\n for (i = startLine; i < childCount; ++i) {\n child = node.children[i];\n child.lineIndex = linesCount;\n\n child.nextAbsoluteChild = null;\n child.nextFlexChild = null;\n\n var/*css_align_t*/ alignItem = getAlignItem(node, child);\n\n // Pre-fill cross axis dimensions when the child is using stretch before\n // we call the recursive layout pass\n if (alignItem === CSS_ALIGN_STRETCH &&\n getPositionType(child) === CSS_POSITION_RELATIVE &&\n isCrossDimDefined &&\n !isStyleDimDefined(child, crossAxis)) {\n child.layout[dim[crossAxis]] = fmaxf(\n boundAxis(child, crossAxis, node.layout[dim[crossAxis]] -\n paddingAndBorderAxisCross - getMarginAxis(child, crossAxis)),\n // You never want to go smaller than padding\n getPaddingAndBorderAxis(child, crossAxis)\n );\n } else if (getPositionType(child) === CSS_POSITION_ABSOLUTE) {\n // Store a private linked list of absolutely positioned children\n // so that we can efficiently traverse them later.\n if (firstAbsoluteChild === null) {\n firstAbsoluteChild = child;\n }\n if (currentAbsoluteChild !== null) {\n currentAbsoluteChild.nextAbsoluteChild = child;\n }\n currentAbsoluteChild = child;\n\n // Pre-fill dimensions when using absolute position and both offsets for the axis are defined (either both\n // left and right or top and bottom).\n for (ii = 0; ii < 2; ii++) {\n axis = (ii !== 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;\n if (isLayoutDimDefined(node, axis) &&\n !isStyleDimDefined(child, axis) &&\n isPosDefined(child, leading[axis]) &&\n isPosDefined(child, trailing[axis])) {\n child.layout[dim[axis]] = fmaxf(\n boundAxis(child, axis, node.layout[dim[axis]] -\n getPaddingAndBorderAxis(node, axis) -\n getMarginAxis(child, axis) -\n getPosition(child, leading[axis]) -\n getPosition(child, trailing[axis])),\n // You never want to go smaller than padding\n getPaddingAndBorderAxis(child, axis)\n );\n }\n }\n }\n\n var/*float*/ nextContentDim = 0;\n\n // It only makes sense to consider a child flexible if we have a computed\n // dimension for the node.\n if (isMainDimDefined && isFlex(child)) {\n flexibleChildrenCount++;\n totalFlexible += child.style.flex;\n\n // Store a private linked list of flexible children so that we can\n // efficiently traverse them later.\n if (firstFlexChild === null) {\n firstFlexChild = child;\n }\n if (currentFlexChild !== null) {\n currentFlexChild.nextFlexChild = child;\n }\n currentFlexChild = child;\n\n // Even if we don't know its exact size yet, we already know the padding,\n // border and margin. We'll use this partial information, which represents\n // the smallest possible size for the child, to compute the remaining\n // available space.\n nextContentDim = getPaddingAndBorderAxis(child, mainAxis) +\n getMarginAxis(child, mainAxis);\n\n } else {\n maxWidth = CSS_UNDEFINED;\n maxHeight = CSS_UNDEFINED;\n\n if (!isMainRowDirection) {\n if (isLayoutDimDefined(node, resolvedRowAxis)) {\n maxWidth = node.layout[dim[resolvedRowAxis]] -\n paddingAndBorderAxisResolvedRow;\n } else {\n maxWidth = parentMaxWidth -\n getMarginAxis(node, resolvedRowAxis) -\n paddingAndBorderAxisResolvedRow;\n }\n } else {\n if (isLayoutDimDefined(node, CSS_FLEX_DIRECTION_COLUMN)) {\n maxHeight = node.layout[dim[CSS_FLEX_DIRECTION_COLUMN]] -\n paddingAndBorderAxisColumn;\n } else {\n maxHeight = parentMaxHeight -\n getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN) -\n paddingAndBorderAxisColumn;\n }\n }\n\n // This is the main recursive call. We layout non flexible children.\n if (alreadyComputedNextLayout === 0) {\n layoutNode(/*(java)!layoutContext, */child, maxWidth, maxHeight, direction);\n }\n\n // Absolute positioned elements do not take part of the layout, so we\n // don't use them to compute mainContentDim\n if (getPositionType(child) === CSS_POSITION_RELATIVE) {\n nonFlexibleChildrenCount++;\n // At this point we know the final size and margin of the element.\n nextContentDim = getDimWithMargin(child, mainAxis);\n }\n }\n\n // The element we are about to add would make us go to the next line\n if (isNodeFlexWrap &&\n isMainDimDefined &&\n mainContentDim + nextContentDim > definedMainDim &&\n // If there's only one element, then it's bigger than the content\n // and needs its own line\n i !== startLine) {\n nonFlexibleChildrenCount--;\n alreadyComputedNextLayout = 1;\n break;\n }\n\n // Disable simple stacking in the main axis for the current line as\n // we found a non-trivial child. The remaining children will be laid out\n // in .\n if (isSimpleStackMain &&\n (getPositionType(child) !== CSS_POSITION_RELATIVE || isFlex(child))) {\n isSimpleStackMain = false;\n firstComplexMain = i;\n }\n\n // Disable simple stacking in the cross axis for the current line as\n // we found a non-trivial child. The remaining children will be laid out\n // in .\n if (isSimpleStackCross &&\n (getPositionType(child) !== CSS_POSITION_RELATIVE ||\n (alignItem !== CSS_ALIGN_STRETCH && alignItem !== CSS_ALIGN_FLEX_START) ||\n (alignItem == CSS_ALIGN_STRETCH && !isCrossDimDefined))) {\n isSimpleStackCross = false;\n firstComplexCross = i;\n }\n\n if (isSimpleStackMain) {\n child.layout[pos[mainAxis]] += mainDim;\n if (isMainDimDefined) {\n setTrailingPosition(node, child, mainAxis);\n }\n\n mainDim += getDimWithMargin(child, mainAxis);\n crossDim = fmaxf(crossDim, boundAxis(child, crossAxis, getDimWithMargin(child, crossAxis)));\n }\n\n if (isSimpleStackCross) {\n child.layout[pos[crossAxis]] += linesCrossDim + leadingPaddingAndBorderCross;\n if (isCrossDimDefined) {\n setTrailingPosition(node, child, crossAxis);\n }\n }\n\n alreadyComputedNextLayout = 0;\n mainContentDim += nextContentDim;\n endLine = i + 1;\n }\n\n // Layout flexible children and allocate empty space\n\n // In order to position the elements in the main axis, we have two\n // controls. The space between the beginning and the first element\n // and the space between each two elements.\n var/*float*/ leadingMainDim = 0;\n var/*float*/ betweenMainDim = 0;\n\n // The remaining available space that needs to be allocated\n var/*float*/ remainingMainDim = 0;\n if (isMainDimDefined) {\n remainingMainDim = definedMainDim - mainContentDim;\n } else {\n remainingMainDim = fmaxf(mainContentDim, 0) - mainContentDim;\n }\n\n // If there are flexible children in the mix, they are going to fill the\n // remaining space\n if (flexibleChildrenCount !== 0) {\n var/*float*/ flexibleMainDim = remainingMainDim / totalFlexible;\n var/*float*/ baseMainDim;\n var/*float*/ boundMainDim;\n\n // If the flex share of remaining space doesn't meet min/max bounds,\n // remove this child from flex calculations.\n currentFlexChild = firstFlexChild;\n while (currentFlexChild !== null) {\n baseMainDim = flexibleMainDim * currentFlexChild.style.flex +\n getPaddingAndBorderAxis(currentFlexChild, mainAxis);\n boundMainDim = boundAxis(currentFlexChild, mainAxis, baseMainDim);\n\n if (baseMainDim !== boundMainDim) {\n remainingMainDim -= boundMainDim;\n totalFlexible -= currentFlexChild.style.flex;\n }\n\n currentFlexChild = currentFlexChild.nextFlexChild;\n }\n flexibleMainDim = remainingMainDim / totalFlexible;\n\n // The non flexible children can overflow the container, in this case\n // we should just assume that there is no space available.\n if (flexibleMainDim < 0) {\n flexibleMainDim = 0;\n }\n\n currentFlexChild = firstFlexChild;\n while (currentFlexChild !== null) {\n // At this point we know the final size of the element in the main\n // dimension\n currentFlexChild.layout[dim[mainAxis]] = boundAxis(currentFlexChild, mainAxis,\n flexibleMainDim * currentFlexChild.style.flex +\n getPaddingAndBorderAxis(currentFlexChild, mainAxis)\n );\n\n maxWidth = CSS_UNDEFINED;\n if (isLayoutDimDefined(node, resolvedRowAxis)) {\n maxWidth = node.layout[dim[resolvedRowAxis]] -\n paddingAndBorderAxisResolvedRow;\n } else if (!isMainRowDirection) {\n maxWidth = parentMaxWidth -\n getMarginAxis(node, resolvedRowAxis) -\n paddingAndBorderAxisResolvedRow;\n }\n maxHeight = CSS_UNDEFINED;\n if (isLayoutDimDefined(node, CSS_FLEX_DIRECTION_COLUMN)) {\n maxHeight = node.layout[dim[CSS_FLEX_DIRECTION_COLUMN]] -\n paddingAndBorderAxisColumn;\n } else if (isMainRowDirection) {\n maxHeight = parentMaxHeight -\n getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN) -\n paddingAndBorderAxisColumn;\n }\n\n // And we recursively call the layout algorithm for this child\n layoutNode(/*(java)!layoutContext, */currentFlexChild, maxWidth, maxHeight, direction);\n\n child = currentFlexChild;\n currentFlexChild = currentFlexChild.nextFlexChild;\n child.nextFlexChild = null;\n }\n\n // We use justifyContent to figure out how to allocate the remaining\n // space available\n } else if (justifyContent !== CSS_JUSTIFY_FLEX_START) {\n if (justifyContent === CSS_JUSTIFY_CENTER) {\n leadingMainDim = remainingMainDim / 2;\n } else if (justifyContent === CSS_JUSTIFY_FLEX_END) {\n leadingMainDim = remainingMainDim;\n } else if (justifyContent === CSS_JUSTIFY_SPACE_BETWEEN) {\n remainingMainDim = fmaxf(remainingMainDim, 0);\n if (flexibleChildrenCount + nonFlexibleChildrenCount - 1 !== 0) {\n betweenMainDim = remainingMainDim /\n (flexibleChildrenCount + nonFlexibleChildrenCount - 1);\n } else {\n betweenMainDim = 0;\n }\n } else if (justifyContent === CSS_JUSTIFY_SPACE_AROUND) {\n // Space on the edges is half of the space between elements\n betweenMainDim = remainingMainDim /\n (flexibleChildrenCount + nonFlexibleChildrenCount);\n leadingMainDim = betweenMainDim / 2;\n }\n }\n\n // Position elements in the main axis and compute dimensions\n\n // At this point, all the children have their dimensions set. We need to\n // find their position. In order to do that, we accumulate data in\n // variables that are also useful to compute the total dimensions of the\n // container!\n mainDim += leadingMainDim;\n\n for (i = firstComplexMain; i < endLine; ++i) {\n child = node.children[i];\n\n if (getPositionType(child) === CSS_POSITION_ABSOLUTE &&\n isPosDefined(child, leading[mainAxis])) {\n // In case the child is position absolute and has left/top being\n // defined, we override the position to whatever the user said\n // (and margin/border).\n child.layout[pos[mainAxis]] = getPosition(child, leading[mainAxis]) +\n getLeadingBorder(node, mainAxis) +\n getLeadingMargin(child, mainAxis);\n } else {\n // If the child is position absolute (without top/left) or relative,\n // we put it at the current accumulated offset.\n child.layout[pos[mainAxis]] += mainDim;\n\n // Define the trailing position accordingly.\n if (isMainDimDefined) {\n setTrailingPosition(node, child, mainAxis);\n }\n\n // Now that we placed the element, we need to update the variables\n // We only need to do that for relative elements. Absolute elements\n // do not take part in that phase.\n if (getPositionType(child) === CSS_POSITION_RELATIVE) {\n // The main dimension is the sum of all the elements dimension plus\n // the spacing.\n mainDim += betweenMainDim + getDimWithMargin(child, mainAxis);\n // The cross dimension is the max of the elements dimension since there\n // can only be one element in that cross dimension.\n crossDim = fmaxf(crossDim, boundAxis(child, crossAxis, getDimWithMargin(child, crossAxis)));\n }\n }\n }\n\n var/*float*/ containerCrossAxis = node.layout[dim[crossAxis]];\n if (!isCrossDimDefined) {\n containerCrossAxis = fmaxf(\n // For the cross dim, we add both sides at the end because the value\n // is aggregate via a max function. Intermediate negative values\n // can mess this computation otherwise\n boundAxis(node, crossAxis, crossDim + paddingAndBorderAxisCross),\n paddingAndBorderAxisCross\n );\n }\n\n // Position elements in the cross axis\n for (i = firstComplexCross; i < endLine; ++i) {\n child = node.children[i];\n\n if (getPositionType(child) === CSS_POSITION_ABSOLUTE &&\n isPosDefined(child, leading[crossAxis])) {\n // In case the child is absolutely positionned and has a\n // top/left/bottom/right being set, we override all the previously\n // computed positions to set it correctly.\n child.layout[pos[crossAxis]] = getPosition(child, leading[crossAxis]) +\n getLeadingBorder(node, crossAxis) +\n getLeadingMargin(child, crossAxis);\n\n } else {\n var/*float*/ leadingCrossDim = leadingPaddingAndBorderCross;\n\n // For a relative children, we're either using alignItems (parent) or\n // alignSelf (child) in order to determine the position in the cross axis\n if (getPositionType(child) === CSS_POSITION_RELATIVE) {\n /*eslint-disable */\n // This variable is intentionally re-defined as the code is transpiled to a block scope language\n var/*css_align_t*/ alignItem = getAlignItem(node, child);\n /*eslint-enable */\n if (alignItem === CSS_ALIGN_STRETCH) {\n // You can only stretch if the dimension has not already been defined\n // previously.\n if (!isStyleDimDefined(child, crossAxis)) {\n var/*float*/ dimCrossAxis = child.layout[dim[crossAxis]];\n child.layout[dim[crossAxis]] = fmaxf(\n boundAxis(child, crossAxis, containerCrossAxis -\n paddingAndBorderAxisCross - getMarginAxis(child, crossAxis)),\n // You never want to go smaller than padding\n getPaddingAndBorderAxis(child, crossAxis)\n );\n\n // If the size has changed, and this child has children we need to re-layout this child\n if (dimCrossAxis != child.layout[dim[crossAxis]] && child.children.length > 0) {\n // Reset child margins before re-layout as they are added back in layoutNode and would be doubled\n child.layout[leading[mainAxis]] -= getLeadingMargin(child, mainAxis) +\n getRelativePosition(child, mainAxis);\n child.layout[trailing[mainAxis]] -= getTrailingMargin(child, mainAxis) +\n getRelativePosition(child, mainAxis);\n child.layout[leading[crossAxis]] -= getLeadingMargin(child, crossAxis) +\n getRelativePosition(child, crossAxis);\n child.layout[trailing[crossAxis]] -= getTrailingMargin(child, crossAxis) +\n getRelativePosition(child, crossAxis);\n\n layoutNode(/*(java)!layoutContext, */child, maxWidth, maxHeight, direction);\n }\n }\n } else if (alignItem !== CSS_ALIGN_FLEX_START) {\n // The remaining space between the parent dimensions+padding and child\n // dimensions+margin.\n var/*float*/ remainingCrossDim = containerCrossAxis -\n paddingAndBorderAxisCross - getDimWithMargin(child, crossAxis);\n\n if (alignItem === CSS_ALIGN_CENTER) {\n leadingCrossDim += remainingCrossDim / 2;\n } else { // CSS_ALIGN_FLEX_END\n leadingCrossDim += remainingCrossDim;\n }\n }\n }\n\n // And we apply the position\n child.layout[pos[crossAxis]] += linesCrossDim + leadingCrossDim;\n\n // Define the trailing position accordingly.\n if (isCrossDimDefined) {\n setTrailingPosition(node, child, crossAxis);\n }\n }\n }\n\n linesCrossDim += crossDim;\n linesMainDim = fmaxf(linesMainDim, mainDim);\n linesCount += 1;\n startLine = endLine;\n }\n\n // \n //\n // Note(prenaux): More than one line, we need to layout the crossAxis\n // according to alignContent.\n //\n // Note that we could probably remove and handle the one line case\n // here too, but for the moment this is safer since it won't interfere with\n // previously working code.\n //\n // See specs:\n // http://www.w3.org/TR/2012/CR-css3-flexbox-20120918/#layout-algorithm\n // section 9.4\n //\n if (linesCount > 1 && isCrossDimDefined) {\n var/*float*/ nodeCrossAxisInnerSize = node.layout[dim[crossAxis]] -\n paddingAndBorderAxisCross;\n var/*float*/ remainingAlignContentDim = nodeCrossAxisInnerSize - linesCrossDim;\n\n var/*float*/ crossDimLead = 0;\n var/*float*/ currentLead = leadingPaddingAndBorderCross;\n\n var/*css_align_t*/ alignContent = getAlignContent(node);\n if (alignContent === CSS_ALIGN_FLEX_END) {\n currentLead += remainingAlignContentDim;\n } else if (alignContent === CSS_ALIGN_CENTER) {\n currentLead += remainingAlignContentDim / 2;\n } else if (alignContent === CSS_ALIGN_STRETCH) {\n if (nodeCrossAxisInnerSize > linesCrossDim) {\n crossDimLead = (remainingAlignContentDim / linesCount);\n }\n }\n\n var/*int*/ endIndex = 0;\n for (i = 0; i < linesCount; ++i) {\n var/*int*/ startIndex = endIndex;\n\n // compute the line's height and find the endIndex\n var/*float*/ lineHeight = 0;\n for (ii = startIndex; ii < childCount; ++ii) {\n child = node.children[ii];\n if (getPositionType(child) !== CSS_POSITION_RELATIVE) {\n continue;\n }\n if (child.lineIndex !== i) {\n break;\n }\n if (isLayoutDimDefined(child, crossAxis)) {\n lineHeight = fmaxf(\n lineHeight,\n child.layout[dim[crossAxis]] + getMarginAxis(child, crossAxis)\n );\n }\n }\n endIndex = ii;\n lineHeight += crossDimLead;\n\n for (ii = startIndex; ii < endIndex; ++ii) {\n child = node.children[ii];\n if (getPositionType(child) !== CSS_POSITION_RELATIVE) {\n continue;\n }\n\n var/*css_align_t*/ alignContentAlignItem = getAlignItem(node, child);\n if (alignContentAlignItem === CSS_ALIGN_FLEX_START) {\n child.layout[pos[crossAxis]] = currentLead + getLeadingMargin(child, crossAxis);\n } else if (alignContentAlignItem === CSS_ALIGN_FLEX_END) {\n child.layout[pos[crossAxis]] = currentLead + lineHeight - getTrailingMargin(child, crossAxis) - child.layout[dim[crossAxis]];\n } else if (alignContentAlignItem === CSS_ALIGN_CENTER) {\n var/*float*/ childHeight = child.layout[dim[crossAxis]];\n child.layout[pos[crossAxis]] = currentLead + (lineHeight - childHeight) / 2;\n } else if (alignContentAlignItem === CSS_ALIGN_STRETCH) {\n child.layout[pos[crossAxis]] = currentLead + getLeadingMargin(child, crossAxis);\n // TODO(prenaux): Correctly set the height of items with undefined\n // (auto) crossAxis dimension.\n }\n }\n\n currentLead += lineHeight;\n }\n }\n\n var/*bool*/ needsMainTrailingPos = false;\n var/*bool*/ needsCrossTrailingPos = false;\n\n // If the user didn't specify a width or height, and it has not been set\n // by the container, then we set it via the children.\n if (!isMainDimDefined) {\n node.layout[dim[mainAxis]] = fmaxf(\n // We're missing the last padding at this point to get the final\n // dimension\n boundAxis(node, mainAxis, linesMainDim + getTrailingPaddingAndBorder(node, mainAxis)),\n // We can never assign a width smaller than the padding and borders\n paddingAndBorderAxisMain\n );\n\n if (mainAxis === CSS_FLEX_DIRECTION_ROW_REVERSE ||\n mainAxis === CSS_FLEX_DIRECTION_COLUMN_REVERSE) {\n needsMainTrailingPos = true;\n }\n }\n\n if (!isCrossDimDefined) {\n node.layout[dim[crossAxis]] = fmaxf(\n // For the cross dim, we add both sides at the end because the value\n // is aggregate via a max function. Intermediate negative values\n // can mess this computation otherwise\n boundAxis(node, crossAxis, linesCrossDim + paddingAndBorderAxisCross),\n paddingAndBorderAxisCross\n );\n\n if (crossAxis === CSS_FLEX_DIRECTION_ROW_REVERSE ||\n crossAxis === CSS_FLEX_DIRECTION_COLUMN_REVERSE) {\n needsCrossTrailingPos = true;\n }\n }\n\n // Set trailing position if necessary\n if (needsMainTrailingPos || needsCrossTrailingPos) {\n for (i = 0; i < childCount; ++i) {\n child = node.children[i];\n\n if (needsMainTrailingPos) {\n setTrailingPosition(node, child, mainAxis);\n }\n\n if (needsCrossTrailingPos) {\n setTrailingPosition(node, child, crossAxis);\n }\n }\n }\n\n // Calculate dimensions for absolutely positioned elements\n currentAbsoluteChild = firstAbsoluteChild;\n while (currentAbsoluteChild !== null) {\n // Pre-fill dimensions when using absolute position and both offsets for\n // the axis are defined (either both left and right or top and bottom).\n for (ii = 0; ii < 2; ii++) {\n axis = (ii !== 0) ? CSS_FLEX_DIRECTION_ROW : CSS_FLEX_DIRECTION_COLUMN;\n\n if (isLayoutDimDefined(node, axis) &&\n !isStyleDimDefined(currentAbsoluteChild, axis) &&\n isPosDefined(currentAbsoluteChild, leading[axis]) &&\n isPosDefined(currentAbsoluteChild, trailing[axis])) {\n currentAbsoluteChild.layout[dim[axis]] = fmaxf(\n boundAxis(currentAbsoluteChild, axis, node.layout[dim[axis]] -\n getBorderAxis(node, axis) -\n getMarginAxis(currentAbsoluteChild, axis) -\n getPosition(currentAbsoluteChild, leading[axis]) -\n getPosition(currentAbsoluteChild, trailing[axis])\n ),\n // You never want to go smaller than padding\n getPaddingAndBorderAxis(currentAbsoluteChild, axis)\n );\n }\n\n if (isPosDefined(currentAbsoluteChild, trailing[axis]) &&\n !isPosDefined(currentAbsoluteChild, leading[axis])) {\n currentAbsoluteChild.layout[leading[axis]] =\n node.layout[dim[axis]] -\n currentAbsoluteChild.layout[dim[axis]] -\n getPosition(currentAbsoluteChild, trailing[axis]);\n }\n }\n\n child = currentAbsoluteChild;\n currentAbsoluteChild = currentAbsoluteChild.nextAbsoluteChild;\n child.nextAbsoluteChild = null;\n }\n }\n\n function layoutNode(node, parentMaxWidth, parentMaxHeight, parentDirection) {\n node.shouldUpdate = true;\n\n var direction = node.style.direction || CSS_DIRECTION_LTR;\n var skipLayout =\n !node.isDirty &&\n node.lastLayout &&\n node.lastLayout.requestedHeight === node.layout.height &&\n node.lastLayout.requestedWidth === node.layout.width &&\n node.lastLayout.parentMaxWidth === parentMaxWidth &&\n node.lastLayout.parentMaxHeight === parentMaxHeight &&\n node.lastLayout.direction === direction;\n\n if (skipLayout) {\n node.layout.width = node.lastLayout.width;\n node.layout.height = node.lastLayout.height;\n node.layout.top = node.lastLayout.top;\n node.layout.left = node.lastLayout.left;\n } else {\n if (!node.lastLayout) {\n node.lastLayout = {};\n }\n\n node.lastLayout.requestedWidth = node.layout.width;\n node.lastLayout.requestedHeight = node.layout.height;\n node.lastLayout.parentMaxWidth = parentMaxWidth;\n node.lastLayout.parentMaxHeight = parentMaxHeight;\n node.lastLayout.direction = direction;\n\n // Reset child layouts\n node.children.forEach(function(child) {\n child.layout.width = undefined;\n child.layout.height = undefined;\n child.layout.top = 0;\n child.layout.left = 0;\n });\n\n layoutNodeImpl(node, parentMaxWidth, parentMaxHeight, parentDirection);\n\n node.lastLayout.width = node.layout.width;\n node.lastLayout.height = node.layout.height;\n node.lastLayout.top = node.layout.top;\n node.lastLayout.left = node.layout.left;\n }\n }\n\n return {\n layoutNodeImpl: layoutNodeImpl,\n computeLayout: layoutNode,\n fillNodes: fillNodes\n };\n})();\n\n// This module export is only used for the purposes of unit testing this file. When\n// the library is packaged this file is included within css-layout.js which forms\n// the public API.\nif (typeof exports === 'object') {\n module.exports = computeLayout;\n}\n\n\n return function(node) {\n /*eslint-disable */\n // disabling ESLint because this code relies on the above include\n computeLayout.fillNodes(node);\n computeLayout.computeLayout(node);\n /*eslint-enable */\n };\n}));\n"]} \ No newline at end of file +{"version":3,"sources":["css-layout.js"],"names":["root","factory","define","amd","exports","module","computeLayout","this","fillNodes","node","layout","isDirty","width","undefined","height","top","left","right","bottom","style","children","measure","length","Error","forEach","isUndefined","value","Number","isNaN","isRowDirection","flexDirection","CSS_FLEX_DIRECTION_ROW","CSS_FLEX_DIRECTION_ROW_REVERSE","isColumnDirection","CSS_FLEX_DIRECTION_COLUMN","CSS_FLEX_DIRECTION_COLUMN_REVERSE","getFlex","flex","isFlexBasisAuto","POSITIVE_FLEX_IS_AUTO","getFlexGrowFactor","getFlexShrinkFactor","getLeadingMargin","axis","marginStart","marginLeft","marginRight","marginTop","marginBottom","margin","getTrailingMargin","marginEnd","getLeadingPadding","paddingStart","paddingLeft","paddingRight","paddingTop","paddingBottom","padding","getTrailingPadding","paddingEnd","getLeadingBorder","borderStartWidth","borderLeftWidth","borderRightWidth","borderTopWidth","borderBottomWidth","borderWidth","getTrailingBorder","borderEndWidth","getLeadingPaddingAndBorder","getTrailingPaddingAndBorder","getMarginAxis","getPaddingAndBorderAxis","getJustifyContent","justifyContent","getAlignContent","alignContent","getAlignItem","child","alignSelf","alignItems","resolveAxis","direction","CSS_DIRECTION_RTL","resolveDirection","parentDirection","CSS_DIRECTION_INHERIT","CSS_DIRECTION_LTR","getFlexDirection","getCrossFlexDirection","getPositionType","position","CSS_POSITION_RELATIVE","getOverflow","overflow","CSS_OVERFLOW_VISIBLE","isFlex","isFlexWrap","flexWrap","getDimWithMargin","measuredDim","isStyleDimDefined","dim","isLayoutDimDefined","isPosDefined","pos","isMeasureDefined","getPosition","boundAxisWithinMinAndMax","min","row","minWidth","row-reverse","column","minHeight","column-reverse","max","maxWidth","maxHeight","boundValue","fminf","a","b","fmaxf","boundAxis","setTrailingPosition","size","CSS_POSITION_ABSOLUTE","trailing","getRelativePosition","leading","setPosition","mainAxis","crossAxis","assert","condition","message","layoutNodeImpl","availableWidth","availableHeight","widthMeasureMode","heightMeasureMode","performLayout","CSS_MEASURE_MODE_UNDEFINED","paddingAndBorderAxisRow","paddingAndBorderAxisColumn","marginAxisRow","marginAxisColumn","innerWidth","innerHeight","CSS_MEASURE_MODE_EXACTLY","measuredWidth","measuredHeight","measureDim","CSS_MEASURE_MODE_AT_MOST","childCount","i","childWidth","childHeight","childWidthMeasureMode","childHeightMeasureMode","isMainAxisRow","isNodeFlexWrap","firstAbsoluteChild","currentAbsoluteChild","leadingPaddingAndBorderMain","trailingPaddingAndBorderMain","leadingPaddingAndBorderCross","paddingAndBorderAxisMain","paddingAndBorderAxisCross","measureModeMainDim","measureModeCrossDim","availableInnerWidth","availableInnerHeight","availableInnerMainDim","availableInnerCrossDim","childDirection","nextChild","flexBasis","CSS_UNDEFINED","CSS_OVERFLOW_HIDDEN","layoutNodeInternal","startOfLineIndex","endOfLineIndex","lineCount","totalLineCrossDim","maxLineMainDim","itemsOnLine","sizeConsumedOnCurrentLine","totalFlexGrowFactors","totalFlexShrinkScaledFactors","firstRelativeChild","currentRelativeChild","lineIndex","outerFlexBasis","canSkipFlex","leadingMainDim","betweenMainDim","remainingFreeSpace","remainingFreeSpaceAfterFlex","childFlexBasis","flexShrinkScaledFactor","flexGrowFactor","baseMainSize","boundMainSize","deltaFreeSpace","deltaFlexShrinkScaledFactors","deltaFlexGrowFactors","updatedMainSize","requiresStretchLayout","CSS_ALIGN_STRETCH","CSS_JUSTIFY_FLEX_START","CSS_JUSTIFY_CENTER","CSS_JUSTIFY_FLEX_END","CSS_JUSTIFY_SPACE_BETWEEN","CSS_JUSTIFY_SPACE_AROUND","mainDim","crossDim","containerCrossAxis","leadingCrossDim","alignItem","isCrossSizeDefinite","CSS_ALIGN_FLEX_START","remainingCrossDim","CSS_ALIGN_CENTER","remainingAlignContentDim","crossDimLead","currentLead","CSS_ALIGN_FLEX_END","endIndex","j","startIndex","lineHeight","alignContentAlignItem","needsMainTrailingPos","needsCrossTrailingPos","CSS_LEFT","CSS_RIGHT","CSS_TOP","CSS_BOTTOM","reason","needToVisitNode","generationCount","gCurrentGenerationCount","lastParentDirection","cachedMeasurements","cachedLayout","cachedResults","len","newCacheEntry","push","computedWidth","computedHeight","measureWidth","measureHeight","shouldUpdate","layoutNode"],"mappings":"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","file":"css-layout.min.js","sourcesContent":["// UMD (Universal Module Definition)\n// See https://github.com/umdjs/umd for reference\n//\n// This file uses the following specific UMD implementation:\n// https://github.com/umdjs/umd/blob/master/templates/returnExports.js\n(function(root, factory) {\n if (typeof define === 'function' && define.amd) {\n // AMD. Register as an anonymous module.\n define([], factory);\n } else if (typeof exports === 'object') {\n // Node. Does not work with strict CommonJS, but\n // only CommonJS-like environments that support module.exports,\n // like Node.\n module.exports = factory();\n } else {\n // Browser globals (root is window)\n root.computeLayout = factory();\n }\n}(this, function() {\n /**\n * Copyright (c) 2014, Facebook, Inc.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree. An additional grant\n * of patent rights can be found in the PATENTS file in the same directory.\n */\n\nvar computeLayout = (function() {\n \n var POSITIVE_FLEX_IS_AUTO = false;\n \n var gCurrentGenerationCount = 0;\n \n var CSS_UNDEFINED;\n \n var CSS_LEFT = 'left';\n var CSS_TOP = 'top';\n var CSS_RIGHT = 'right';\n var CSS_BOTTOM = 'bottom';\n \n var CSS_DIRECTION_INHERIT = 'inherit';\n var CSS_DIRECTION_LTR = 'ltr';\n var CSS_DIRECTION_RTL = 'rtl';\n\n var CSS_FLEX_DIRECTION_ROW = 'row';\n var CSS_FLEX_DIRECTION_ROW_REVERSE = 'row-reverse';\n var CSS_FLEX_DIRECTION_COLUMN = 'column';\n var CSS_FLEX_DIRECTION_COLUMN_REVERSE = 'column-reverse';\n\n var CSS_JUSTIFY_FLEX_START = 'flex-start';\n var CSS_JUSTIFY_CENTER = 'center';\n var CSS_JUSTIFY_FLEX_END = 'flex-end';\n var CSS_JUSTIFY_SPACE_BETWEEN = 'space-between';\n var CSS_JUSTIFY_SPACE_AROUND = 'space-around';\n\n var CSS_ALIGN_FLEX_START = 'flex-start';\n var CSS_ALIGN_CENTER = 'center';\n var CSS_ALIGN_FLEX_END = 'flex-end';\n var CSS_ALIGN_STRETCH = 'stretch';\n\n var CSS_POSITION_RELATIVE = 'relative';\n var CSS_POSITION_ABSOLUTE = 'absolute';\n \n var CSS_OVERFLOW_VISIBLE = 'visible';\n var CSS_OVERFLOW_HIDDEN = 'hidden';\n \n var CSS_MEASURE_MODE_UNDEFINED = 'undefined';\n var CSS_MEASURE_MODE_EXACTLY = 'exactly';\n var CSS_MEASURE_MODE_AT_MOST = 'at-most';\n\n var leading = {\n 'row': 'left',\n 'row-reverse': 'right',\n 'column': 'top',\n 'column-reverse': 'bottom'\n };\n var trailing = {\n 'row': 'right',\n 'row-reverse': 'left',\n 'column': 'bottom',\n 'column-reverse': 'top'\n };\n var pos = {\n 'row': 'left',\n 'row-reverse': 'right',\n 'column': 'top',\n 'column-reverse': 'bottom'\n };\n var dim = {\n 'row': 'width',\n 'row-reverse': 'width',\n 'column': 'height',\n 'column-reverse': 'height'\n };\n var measuredDim = {\n 'row': 'measuredWidth',\n 'row-reverse': 'measuredWidth',\n 'column': 'measuredHeight',\n 'column-reverse': 'measuredHeight'\n };\n\n // When transpiled to Java / C the node type has layout, children and style\n // properties. For the JavaScript version this function adds these properties\n // if they don't already exist.\n function fillNodes(node) {\n if (!node.layout || node.isDirty) {\n node.layout = {\n width: undefined,\n height: undefined,\n top: 0,\n left: 0,\n right: 0,\n bottom: 0\n };\n }\n\n if (!node.style) {\n node.style = {};\n }\n\n if (!node.children) {\n node.children = [];\n }\n\n if (node.style.measure && node.children && node.children.length) {\n throw new Error('Using custom measure function is supported only for leaf nodes.');\n }\n\n node.children.forEach(fillNodes);\n return node;\n }\n\n function isUndefined(value) {\n return value === undefined || Number.isNaN(value);\n }\n\n function isRowDirection(flexDirection) {\n return flexDirection === CSS_FLEX_DIRECTION_ROW ||\n flexDirection === CSS_FLEX_DIRECTION_ROW_REVERSE;\n }\n\n function isColumnDirection(flexDirection) {\n return flexDirection === CSS_FLEX_DIRECTION_COLUMN ||\n flexDirection === CSS_FLEX_DIRECTION_COLUMN_REVERSE;\n }\n \n function getFlex(node) {\n if (node.style.flex === undefined) {\n return 0;\n }\n return node.style.flex;\n }\n \n function isFlexBasisAuto(node) {\n if (POSITIVE_FLEX_IS_AUTO) {\n // All flex values are auto.\n return true;\n } else {\n // A flex value > 0 implies a basis of zero.\n return getFlex(node) <= 0;\n }\n }\n \n function getFlexGrowFactor(node) {\n // Flex grow is implied by positive values for flex.\n if (getFlex(node) > 0) {\n return getFlex(node);\n }\n return 0;\n }\n \n function getFlexShrinkFactor(node) {\n if (POSITIVE_FLEX_IS_AUTO) {\n // A flex shrink factor of 1 is implied by non-zero values for flex.\n if (getFlex(node) !== 0) {\n return 1;\n }\n } else {\n // A flex shrink factor of 1 is implied by negative values for flex.\n if (getFlex(node) < 0) {\n return 1;\n }\n }\n return 0;\n }\n\n function getLeadingMargin(node, axis) {\n if (node.style.marginStart !== undefined && isRowDirection(axis)) {\n return node.style.marginStart;\n }\n\n var value = null;\n switch (axis) {\n case 'row': value = node.style.marginLeft; break;\n case 'row-reverse': value = node.style.marginRight; break;\n case 'column': value = node.style.marginTop; break;\n case 'column-reverse': value = node.style.marginBottom; break;\n }\n\n if (value !== undefined) {\n return value;\n }\n\n if (node.style.margin !== undefined) {\n return node.style.margin;\n }\n\n return 0;\n }\n\n function getTrailingMargin(node, axis) {\n if (node.style.marginEnd !== undefined && isRowDirection(axis)) {\n return node.style.marginEnd;\n }\n\n var value = null;\n switch (axis) {\n case 'row': value = node.style.marginRight; break;\n case 'row-reverse': value = node.style.marginLeft; break;\n case 'column': value = node.style.marginBottom; break;\n case 'column-reverse': value = node.style.marginTop; break;\n }\n\n if (value != null) {\n return value;\n }\n\n if (node.style.margin !== undefined) {\n return node.style.margin;\n }\n\n return 0;\n }\n\n function getLeadingPadding(node, axis) {\n if (node.style.paddingStart !== undefined && node.style.paddingStart >= 0\n && isRowDirection(axis)) {\n return node.style.paddingStart;\n }\n\n var value = null;\n switch (axis) {\n case 'row': value = node.style.paddingLeft; break;\n case 'row-reverse': value = node.style.paddingRight; break;\n case 'column': value = node.style.paddingTop; break;\n case 'column-reverse': value = node.style.paddingBottom; break;\n }\n\n if (value != null && value >= 0) {\n return value;\n }\n\n if (node.style.padding !== undefined && node.style.padding >= 0) {\n return node.style.padding;\n }\n\n return 0;\n }\n\n function getTrailingPadding(node, axis) {\n if (node.style.paddingEnd !== undefined && node.style.paddingEnd >= 0\n && isRowDirection(axis)) {\n return node.style.paddingEnd;\n }\n\n var value = null;\n switch (axis) {\n case 'row': value = node.style.paddingRight; break;\n case 'row-reverse': value = node.style.paddingLeft; break;\n case 'column': value = node.style.paddingBottom; break;\n case 'column-reverse': value = node.style.paddingTop; break;\n }\n\n if (value != null && value >= 0) {\n return value;\n }\n\n if (node.style.padding !== undefined && node.style.padding >= 0) {\n return node.style.padding;\n }\n\n return 0;\n }\n\n function getLeadingBorder(node, axis) {\n if (node.style.borderStartWidth !== undefined && node.style.borderStartWidth >= 0\n && isRowDirection(axis)) {\n return node.style.borderStartWidth;\n }\n\n var value = null;\n switch (axis) {\n case 'row': value = node.style.borderLeftWidth; break;\n case 'row-reverse': value = node.style.borderRightWidth; break;\n case 'column': value = node.style.borderTopWidth; break;\n case 'column-reverse': value = node.style.borderBottomWidth; break;\n }\n\n if (value != null && value >= 0) {\n return value;\n }\n\n if (node.style.borderWidth !== undefined && node.style.borderWidth >= 0) {\n return node.style.borderWidth;\n }\n\n return 0;\n }\n\n function getTrailingBorder(node, axis) {\n if (node.style.borderEndWidth !== undefined && node.style.borderEndWidth >= 0\n && isRowDirection(axis)) {\n return node.style.borderEndWidth;\n }\n\n var value = null;\n switch (axis) {\n case 'row': value = node.style.borderRightWidth; break;\n case 'row-reverse': value = node.style.borderLeftWidth; break;\n case 'column': value = node.style.borderBottomWidth; break;\n case 'column-reverse': value = node.style.borderTopWidth; break;\n }\n\n if (value != null && value >= 0) {\n return value;\n }\n\n if (node.style.borderWidth !== undefined && node.style.borderWidth >= 0) {\n return node.style.borderWidth;\n }\n\n return 0;\n }\n\n function getLeadingPaddingAndBorder(node, axis) {\n return getLeadingPadding(node, axis) + getLeadingBorder(node, axis);\n }\n\n function getTrailingPaddingAndBorder(node, axis) {\n return getTrailingPadding(node, axis) + getTrailingBorder(node, axis);\n }\n\n function getMarginAxis(node, axis) {\n return getLeadingMargin(node, axis) + getTrailingMargin(node, axis);\n }\n\n function getPaddingAndBorderAxis(node, axis) {\n return getLeadingPaddingAndBorder(node, axis) +\n getTrailingPaddingAndBorder(node, axis);\n }\n\n function getJustifyContent(node) {\n if (node.style.justifyContent) {\n return node.style.justifyContent;\n }\n return 'flex-start';\n }\n\n function getAlignContent(node) {\n if (node.style.alignContent) {\n return node.style.alignContent;\n }\n return 'flex-start';\n }\n\n function getAlignItem(node, child) {\n if (child.style.alignSelf) {\n return child.style.alignSelf;\n }\n if (node.style.alignItems) {\n return node.style.alignItems;\n }\n return 'stretch';\n }\n\n function resolveAxis(axis, direction) {\n if (direction === CSS_DIRECTION_RTL) {\n if (axis === CSS_FLEX_DIRECTION_ROW) {\n return CSS_FLEX_DIRECTION_ROW_REVERSE;\n } else if (axis === CSS_FLEX_DIRECTION_ROW_REVERSE) {\n return CSS_FLEX_DIRECTION_ROW;\n }\n }\n\n return axis;\n }\n\n function resolveDirection(node, parentDirection) {\n var direction;\n if (node.style.direction) {\n direction = node.style.direction;\n } else {\n direction = CSS_DIRECTION_INHERIT;\n }\n\n if (direction === CSS_DIRECTION_INHERIT) {\n direction = (parentDirection === undefined ? CSS_DIRECTION_LTR : parentDirection);\n }\n\n return direction;\n }\n\n function getFlexDirection(node) {\n if (node.style.flexDirection) {\n return node.style.flexDirection;\n }\n return CSS_FLEX_DIRECTION_COLUMN;\n }\n\n function getCrossFlexDirection(flexDirection, direction) {\n if (isColumnDirection(flexDirection)) {\n return resolveAxis(CSS_FLEX_DIRECTION_ROW, direction);\n } else {\n return CSS_FLEX_DIRECTION_COLUMN;\n }\n }\n\n function getPositionType(node) {\n if (node.style.position) {\n return node.style.position;\n }\n return CSS_POSITION_RELATIVE;\n }\n \n function getOverflow(node) {\n if (node.style.overflow) {\n return node.style.overflow;\n }\n return CSS_OVERFLOW_VISIBLE;\n }\n\n function isFlex(node) {\n return (\n getPositionType(node) === CSS_POSITION_RELATIVE &&\n node.style.flex !== undefined && node.style.flex !== 0\n );\n }\n\n function isFlexWrap(node) {\n return node.style.flexWrap === 'wrap';\n }\n\n function getDimWithMargin(node, axis) {\n return node.layout[measuredDim[axis]] + getMarginAxis(node, axis);\n }\n \n function isStyleDimDefined(node, axis) { \n return node.style[dim[axis]] !== undefined && node.style[dim[axis]] >= 0;\n }\n \n function isLayoutDimDefined(node, axis) { \n return node.layout[measuredDim[axis]] !== undefined && node.layout[measuredDim[axis]] >= 0;\n }\n\n function isPosDefined(node, pos) {\n return node.style[pos] !== undefined;\n }\n\n function isMeasureDefined(node) {\n return node.style.measure !== undefined;\n }\n\n function getPosition(node, pos) {\n if (node.style[pos] !== undefined) {\n return node.style[pos];\n }\n return 0;\n }\n \n function boundAxisWithinMinAndMax(node, axis, value) {\n var min = {\n 'row': node.style.minWidth,\n 'row-reverse': node.style.minWidth,\n 'column': node.style.minHeight,\n 'column-reverse': node.style.minHeight\n }[axis];\n\n var max = {\n 'row': node.style.maxWidth,\n 'row-reverse': node.style.maxWidth,\n 'column': node.style.maxHeight,\n 'column-reverse': node.style.maxHeight\n }[axis];\n\n var boundValue = value;\n if (max !== undefined && max >= 0 && boundValue > max) {\n boundValue = max;\n }\n if (min !== undefined && min >= 0 && boundValue < min) {\n boundValue = min;\n }\n return boundValue;\n }\n \n function fminf(a, b) {\n if (a < b) {\n return a;\n }\n return b;\n }\n\n function fmaxf(a, b) {\n if (a > b) {\n return a;\n }\n return b;\n }\n \n // Like boundAxisWithinMinAndMax but also ensures that the value doesn't go below the\n // padding and border amount.\n function boundAxis(node, axis, value) {\n return fmaxf(boundAxisWithinMinAndMax(node, axis, value), getPaddingAndBorderAxis(node, axis));\n }\n\n function setTrailingPosition(node, child, axis) {\n var size = (getPositionType(child) === CSS_POSITION_ABSOLUTE) ?\n 0 :\n child.layout[measuredDim[axis]];\n child.layout[trailing[axis]] = node.layout[measuredDim[axis]] - size - child.layout[pos[axis]];\n }\n\n // If both left and right are defined, then use left. Otherwise return\n // +left or -right depending on which is defined.\n function getRelativePosition(node, axis) {\n if (node.style[leading[axis]] !== undefined) {\n return getPosition(node, leading[axis]);\n }\n return -getPosition(node, trailing[axis]);\n }\n \n function setPosition(node, direction) {\n var mainAxis = resolveAxis(getFlexDirection(node), direction);\n var crossAxis = getCrossFlexDirection(mainAxis, direction);\n \n node.layout[leading[mainAxis]] = getLeadingMargin(node, mainAxis) +\n getRelativePosition(node, mainAxis);\n node.layout[trailing[mainAxis]] = getTrailingMargin(node, mainAxis) +\n getRelativePosition(node, mainAxis);\n node.layout[leading[crossAxis]] = getLeadingMargin(node, crossAxis) +\n getRelativePosition(node, crossAxis);\n node.layout[trailing[crossAxis]] = getTrailingMargin(node, crossAxis) +\n getRelativePosition(node, crossAxis);\n }\n \n function assert(condition, message) {\n if (!condition) {\n throw new Error(message);\n }\n }\n \n //\n // This is the main routine that implements a subset of the flexbox layout algorithm\n // described in the W3C CSS documentation: https://www.w3.org/TR/css3-flexbox/.\n //\n // Limitations of this algorithm, compared to the full standard:\n // * Display property is always assumed to be 'flex' except for Text nodes, which\n // are assumed to be 'inline-flex'.\n // * The 'zIndex' property (or any form of z ordering) is not supported. Nodes are\n // stacked in document order.\n // * The 'order' property is not supported. The order of flex items is always defined\n // by document order.\n // * The 'visibility' property is always assumed to be 'visible'. Values of 'collapse'\n // and 'hidden' are not supported.\n // * The 'wrap' property supports only 'nowrap' (which is the default) or 'wrap'. The\n // rarely-used 'wrap-reverse' is not supported.\n // * Rather than allowing arbitrary combinations of flexGrow, flexShrink and\n // flexBasis, this algorithm supports only the three most common combinations:\n // flex: 0 is equiavlent to flex: 0 0 auto\n // flex: n (where n is a positive value) is equivalent to flex: n 1 auto\n // If POSITIVE_FLEX_IS_AUTO is 0, then it is equivalent to flex: n 0 0\n // This is faster because the content doesn't need to be measured, but it's\n // less flexible because the basis is always 0 and can't be overriden with\n // the width/height attributes.\n // flex: -1 (or any negative value) is equivalent to flex: 0 1 auto\n // * Margins cannot be specified as 'auto'. They must be specified in terms of pixel\n // values, and the default value is 0.\n // * The 'baseline' value is not supported for alignItems and alignSelf properties.\n // * Values of width, maxWidth, minWidth, height, maxHeight and minHeight must be\n // specified as pixel values, not as percentages.\n // * There is no support for calculation of dimensions based on intrinsic aspect ratios\n // (e.g. images).\n // * There is no support for forced breaks.\n // * It does not support vertical inline directions (top-to-bottom or bottom-to-top text).\n //\n // Deviations from standard:\n // * Section 4.5 of the spec indicates that all flex items have a default minimum\n // main size. For text blocks, for example, this is the width of the widest word. \n // Calculating the minimum width is expensive, so we forego it and assume a default \n // minimum main size of 0.\n // * Min/Max sizes in the main axis are not honored when resolving flexible lengths.\n // * The spec indicates that the default value for 'flexDirection' is 'row', but\n // the algorithm below assumes a default of 'column'.\n //\n // Input parameters:\n // - node: current node to be sized and layed out\n // - availableWidth & availableHeight: available size to be used for sizing the node\n // or CSS_UNDEFINED if the size is not available; interpretation depends on layout\n // flags\n // - parentDirection: the inline (text) direction within the parent (left-to-right or\n // right-to-left)\n // - widthMeasureMode: indicates the sizing rules for the width (see below for explanation)\n // - heightMeasureMode: indicates the sizing rules for the height (see below for explanation)\n // - performLayout: specifies whether the caller is interested in just the dimensions\n // of the node or it requires the entire node and its subtree to be layed out\n // (with final positions)\n //\n // Details:\n // This routine is called recursively to lay out subtrees of flexbox elements. It uses the\n // information in node.style, which is treated as a read-only input. It is responsible for\n // setting the layout.direction and layout.measured_dimensions fields for the input node as well\n // as the layout.position and layout.line_index fields for its child nodes. The\n // layout.measured_dimensions field includes any border or padding for the node but does\n // not include margins.\n //\n // The spec describes four different layout modes: \"fill available\", \"max content\", \"min content\",\n // and \"fit content\". Of these, we don't use \"min content\" because we don't support default\n // minimum main sizes (see above for details). Each of our measure modes maps to a layout mode\n // from the spec (https://www.w3.org/TR/css3-sizing/#terms):\n // - CSS_MEASURE_MODE_UNDEFINED: max content\n // - CSS_MEASURE_MODE_EXACTLY: fill available\n // - CSS_MEASURE_MODE_AT_MOST: fit content\n // \n // When calling layoutNodeImpl and layoutNodeInternal, if the caller passes an available size of\n // undefined then it must also pass a measure mode of CSS_MEASURE_MODE_UNDEFINED in that dimension.\n //\n function layoutNodeImpl(node, availableWidth, availableHeight, /*css_direction_t*/parentDirection, widthMeasureMode, heightMeasureMode, performLayout) {\n assert(isUndefined(availableWidth) ? widthMeasureMode === CSS_MEASURE_MODE_UNDEFINED : true, 'availableWidth is indefinite so widthMeasureMode must be CSS_MEASURE_MODE_UNDEFINED');\n assert(isUndefined(availableHeight) ? heightMeasureMode === CSS_MEASURE_MODE_UNDEFINED : true, 'availableHeight is indefinite so heightMeasureMode must be CSS_MEASURE_MODE_UNDEFINED');\n \n var/*float*/ paddingAndBorderAxisRow = getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW);\n var/*float*/ paddingAndBorderAxisColumn = getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_COLUMN);\n var/*float*/ marginAxisRow = getMarginAxis(node, CSS_FLEX_DIRECTION_ROW);\n var/*float*/ marginAxisColumn = getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN);\n\n // Set the resolved resolution in the node's layout.\n var/*css_direction_t*/ direction = resolveDirection(node, parentDirection);\n node.layout.direction = direction;\n\n // For content (text) nodes, determine the dimensions based on the text contents.\n if (isMeasureDefined(node)) {\n var/*float*/ innerWidth = availableWidth - marginAxisRow - paddingAndBorderAxisRow;\n var/*float*/ innerHeight = availableHeight - marginAxisColumn - paddingAndBorderAxisColumn;\n \n if (widthMeasureMode === CSS_MEASURE_MODE_EXACTLY && heightMeasureMode === CSS_MEASURE_MODE_EXACTLY) {\n\n // Don't bother sizing the text if both dimensions are already defined.\n node.layout.measuredWidth = boundAxis(node, CSS_FLEX_DIRECTION_ROW, availableWidth - marginAxisRow);\n node.layout.measuredHeight = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, availableHeight - marginAxisColumn);\n } else if (innerWidth <= 0) {\n\n // Don't bother sizing the text if there's no horizontal space.\n node.layout.measuredWidth = boundAxis(node, CSS_FLEX_DIRECTION_ROW, 0);\n node.layout.measuredHeight = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, 0);\n } else {\n\n // Measure the text under the current constraints.\n var/*css_dim_t*/ measureDim = node.style.measure(\n /*(c)!node->context,*/\n /*(java)!layoutContext.measureOutput,*/\n innerWidth,\n widthMeasureMode,\n innerHeight,\n heightMeasureMode\n );\n\n node.layout.measuredWidth = boundAxis(node, CSS_FLEX_DIRECTION_ROW,\n (widthMeasureMode === CSS_MEASURE_MODE_UNDEFINED || widthMeasureMode === CSS_MEASURE_MODE_AT_MOST) ?\n measureDim.width + paddingAndBorderAxisRow :\n availableWidth - marginAxisRow);\n node.layout.measuredHeight = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN,\n (heightMeasureMode === CSS_MEASURE_MODE_UNDEFINED || heightMeasureMode === CSS_MEASURE_MODE_AT_MOST) ?\n measureDim.height + paddingAndBorderAxisColumn :\n availableHeight - marginAxisColumn);\n }\n \n return;\n }\n\n // For nodes with no children, use the available values if they were provided, or\n // the minimum size as indicated by the padding and border sizes.\n var/*int*/ childCount = node.children.length;\n if (childCount === 0) {\n node.layout.measuredWidth = boundAxis(node, CSS_FLEX_DIRECTION_ROW,\n (widthMeasureMode === CSS_MEASURE_MODE_UNDEFINED || widthMeasureMode === CSS_MEASURE_MODE_AT_MOST) ?\n paddingAndBorderAxisRow :\n availableWidth - marginAxisRow);\n node.layout.measuredHeight = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN,\n (heightMeasureMode === CSS_MEASURE_MODE_UNDEFINED || heightMeasureMode === CSS_MEASURE_MODE_AT_MOST) ?\n paddingAndBorderAxisColumn :\n availableHeight - marginAxisColumn);\n return;\n }\n\n // If we're not being asked to perform a full layout, we can handle a number of common\n // cases here without incurring the cost of the remaining function.\n if (!performLayout) {\n // If we're being asked to size the content with an at most constraint but there is no available width,\n // the measurement will always be zero.\n if (widthMeasureMode === CSS_MEASURE_MODE_AT_MOST && availableWidth <= 0 &&\n heightMeasureMode === CSS_MEASURE_MODE_AT_MOST && availableHeight <= 0) {\n node.layout.measuredWidth = boundAxis(node, CSS_FLEX_DIRECTION_ROW, 0);\n node.layout.measuredHeight = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, 0);\n return;\n }\n \n if (widthMeasureMode === CSS_MEASURE_MODE_AT_MOST && availableWidth <= 0) {\n node.layout.measuredWidth = boundAxis(node, CSS_FLEX_DIRECTION_ROW, 0);\n node.layout.measuredHeight = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, isUndefined(availableHeight) ? 0 : (availableHeight - marginAxisColumn));\n return;\n }\n\n if (heightMeasureMode === CSS_MEASURE_MODE_AT_MOST && availableHeight <= 0) {\n node.layout.measuredWidth = boundAxis(node, CSS_FLEX_DIRECTION_ROW, isUndefined(availableWidth) ? 0 : (availableWidth - marginAxisRow));\n node.layout.measuredHeight = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, 0);\n return;\n }\n \n // If we're being asked to use an exact width/height, there's no need to measure the children.\n if (widthMeasureMode === CSS_MEASURE_MODE_EXACTLY && heightMeasureMode === CSS_MEASURE_MODE_EXACTLY) {\n node.layout.measuredWidth = boundAxis(node, CSS_FLEX_DIRECTION_ROW, availableWidth - marginAxisRow);\n node.layout.measuredHeight = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, availableHeight - marginAxisColumn);\n return;\n }\n }\n\n // STEP 1: CALCULATE VALUES FOR REMAINDER OF ALGORITHM\n var/*(c)!css_flex_direction_t*//*(java)!int*/ mainAxis = resolveAxis(getFlexDirection(node), direction);\n var/*(c)!css_flex_direction_t*//*(java)!int*/ crossAxis = getCrossFlexDirection(mainAxis, direction);\n var/*bool*/ isMainAxisRow = isRowDirection(mainAxis);\n var/*css_justify_t*/ justifyContent = getJustifyContent(node);\n var/*bool*/ isNodeFlexWrap = isFlexWrap(node);\n\n var/*css_node_t**/ firstAbsoluteChild = undefined;\n var/*css_node_t**/ currentAbsoluteChild = undefined;\n\n var/*float*/ leadingPaddingAndBorderMain = getLeadingPaddingAndBorder(node, mainAxis);\n var/*float*/ trailingPaddingAndBorderMain = getTrailingPaddingAndBorder(node, mainAxis);\n var/*float*/ leadingPaddingAndBorderCross = getLeadingPaddingAndBorder(node, crossAxis);\n var/*float*/ paddingAndBorderAxisMain = getPaddingAndBorderAxis(node, mainAxis);\n var/*float*/ paddingAndBorderAxisCross = getPaddingAndBorderAxis(node, crossAxis);\n \n var/*css_measure_mode_t*/ measureModeMainDim = isMainAxisRow ? widthMeasureMode : heightMeasureMode;\n var/*css_measure_mode_t*/ measureModeCrossDim = isMainAxisRow ? heightMeasureMode : widthMeasureMode;\n\n // STEP 2: DETERMINE AVAILABLE SIZE IN MAIN AND CROSS DIRECTIONS\n var/*float*/ availableInnerWidth = availableWidth - marginAxisRow - paddingAndBorderAxisRow;\n var/*float*/ availableInnerHeight = availableHeight - marginAxisColumn - paddingAndBorderAxisColumn;\n var/*float*/ availableInnerMainDim = isMainAxisRow ? availableInnerWidth : availableInnerHeight;\n var/*float*/ availableInnerCrossDim = isMainAxisRow ? availableInnerHeight : availableInnerWidth;\n\n // STEP 3: DETERMINE FLEX BASIS FOR EACH ITEM\n var/*css_node_t**/ child;\n var/*int*/ i;\n var/*float*/ childWidth;\n var/*float*/ childHeight;\n var/*css_measure_mode_t*/ childWidthMeasureMode;\n var/*css_measure_mode_t*/ childHeightMeasureMode;\n for (i = 0; i < childCount; i++) {\n child = node.children[i];\n\n if (performLayout) {\n // Set the initial position (relative to the parent).\n var/*css_direction_t*/ childDirection = resolveDirection(child, direction);\n setPosition(child, childDirection);\n }\n \n // Absolute-positioned children don't participate in flex layout. Add them\n // to a list that we can process later.\n if (getPositionType(child) === CSS_POSITION_ABSOLUTE) {\n\n // Store a private linked list of absolutely positioned children\n // so that we can efficiently traverse them later.\n if (firstAbsoluteChild === undefined) {\n firstAbsoluteChild = child;\n }\n if (currentAbsoluteChild !== undefined) {\n currentAbsoluteChild.nextChild = child;\n }\n currentAbsoluteChild = child;\n child.nextChild = undefined;\n } else {\n \n if (isMainAxisRow && isStyleDimDefined(child, CSS_FLEX_DIRECTION_ROW)) {\n \n // The width is definite, so use that as the flex basis.\n child.layout.flexBasis = fmaxf(child.style.width, getPaddingAndBorderAxis(child, CSS_FLEX_DIRECTION_ROW));\n } else if (!isMainAxisRow && isStyleDimDefined(child, CSS_FLEX_DIRECTION_COLUMN)) {\n \n // The height is definite, so use that as the flex basis.\n child.layout.flexBasis = fmaxf(child.style.height, getPaddingAndBorderAxis(child, CSS_FLEX_DIRECTION_COLUMN));\n } else if (!isFlexBasisAuto(child) && !isUndefined(availableInnerMainDim)) {\n \n // If the basis isn't 'auto', it is assumed to be zero.\n child.layout.flexBasis = fmaxf(0, getPaddingAndBorderAxis(child, mainAxis));\n } else {\n \n // Compute the flex basis and hypothetical main size (i.e. the clamped flex basis).\n childWidth = CSS_UNDEFINED;\n childHeight = CSS_UNDEFINED;\n childWidthMeasureMode = CSS_MEASURE_MODE_UNDEFINED;\n childHeightMeasureMode = CSS_MEASURE_MODE_UNDEFINED;\n \n if (isStyleDimDefined(child, CSS_FLEX_DIRECTION_ROW)) {\n childWidth = child.style.width + getMarginAxis(child, CSS_FLEX_DIRECTION_ROW);\n childWidthMeasureMode = CSS_MEASURE_MODE_EXACTLY;\n }\n if (isStyleDimDefined(child, CSS_FLEX_DIRECTION_COLUMN)) {\n childHeight = child.style.height + getMarginAxis(child, CSS_FLEX_DIRECTION_COLUMN);\n childHeightMeasureMode = CSS_MEASURE_MODE_EXACTLY;\n }\n \n // According to the spec, if the main size is not definite and the\n // child's inline axis is parallel to the main axis (i.e. it's\n // horizontal), the child should be sized using \"UNDEFINED\" in\n // the main size. Otherwise use \"AT_MOST\" in the cross axis.\n if (!isMainAxisRow && isUndefined(childWidth) && !isUndefined(availableInnerWidth)) {\n childWidth = availableInnerWidth;\n childWidthMeasureMode = CSS_MEASURE_MODE_AT_MOST;\n }\n\n // The W3C spec doesn't say anything about the 'overflow' property,\n // but all major browsers appear to implement the following logic.\n if (getOverflow(node) === CSS_OVERFLOW_HIDDEN) {\n if (isMainAxisRow && isUndefined(childHeight) && !isUndefined(availableInnerHeight)) {\n childHeight = availableInnerHeight;\n childHeightMeasureMode = CSS_MEASURE_MODE_AT_MOST;\n }\n }\n\n // Measure the child\n layoutNodeInternal(child, childWidth, childHeight, direction, childWidthMeasureMode, childHeightMeasureMode, false, 'measure');\n \n child.layout.flexBasis = fmaxf(isMainAxisRow ? child.layout.measuredWidth : child.layout.measuredHeight, getPaddingAndBorderAxis(child, mainAxis));\n }\n }\n }\n\n // STEP 4: COLLECT FLEX ITEMS INTO FLEX LINES\n \n // Indexes of children that represent the first and last items in the line.\n var/*int*/ startOfLineIndex = 0;\n var/*int*/ endOfLineIndex = 0;\n \n // Number of lines.\n var/*int*/ lineCount = 0;\n \n // Accumulated cross dimensions of all lines so far.\n var/*float*/ totalLineCrossDim = 0;\n\n // Max main dimension of all the lines.\n var/*float*/ maxLineMainDim = 0;\n\n while (endOfLineIndex < childCount) {\n \n // Number of items on the currently line. May be different than the difference\n // between start and end indicates because we skip over absolute-positioned items.\n var/*int*/ itemsOnLine = 0;\n\n // sizeConsumedOnCurrentLine is accumulation of the dimensions and margin\n // of all the children on the current line. This will be used in order to\n // either set the dimensions of the node if none already exist or to compute\n // the remaining space left for the flexible children.\n var/*float*/ sizeConsumedOnCurrentLine = 0;\n\n var/*float*/ totalFlexGrowFactors = 0;\n var/*float*/ totalFlexShrinkScaledFactors = 0;\n\n i = startOfLineIndex;\n\n // Maintain a linked list of the child nodes that can shrink and/or grow.\n var/*css_node_t**/ firstRelativeChild = undefined;\n var/*css_node_t**/ currentRelativeChild = undefined;\n\n // Add items to the current line until it's full or we run out of items.\n while (i < childCount) {\n child = node.children[i];\n child.lineIndex = lineCount;\n\n if (getPositionType(child) !== CSS_POSITION_ABSOLUTE) {\n var/*float*/ outerFlexBasis = child.layout.flexBasis + getMarginAxis(child, mainAxis);\n \n // If this is a multi-line flow and this item pushes us over the available size, we've\n // hit the end of the current line. Break out of the loop and lay out the current line.\n if (sizeConsumedOnCurrentLine + outerFlexBasis > availableInnerMainDim && isNodeFlexWrap && itemsOnLine > 0) {\n break;\n }\n\n sizeConsumedOnCurrentLine += outerFlexBasis;\n itemsOnLine++;\n\n if (isFlex(child)) {\n totalFlexGrowFactors += getFlexGrowFactor(child);\n \n // Unlike the grow factor, the shrink factor is scaled relative to the child\n // dimension.\n totalFlexShrinkScaledFactors += getFlexShrinkFactor(child) * child.layout.flexBasis;\n }\n\n // Store a private linked list of children that need to be layed out.\n if (firstRelativeChild === undefined) {\n firstRelativeChild = child;\n }\n if (currentRelativeChild !== undefined) {\n currentRelativeChild.nextChild = child;\n }\n currentRelativeChild = child;\n child.nextChild = undefined;\n }\n \n i++;\n endOfLineIndex++;\n }\n \n // If we don't need to measure the cross axis, we can skip the entire flex step.\n var/*bool*/ canSkipFlex = !performLayout && measureModeCrossDim === CSS_MEASURE_MODE_EXACTLY;\n\n // In order to position the elements in the main axis, we have two\n // controls. The space between the beginning and the first element\n // and the space between each two elements.\n var/*float*/ leadingMainDim = 0;\n var/*float*/ betweenMainDim = 0;\n\n // STEP 5: RESOLVING FLEXIBLE LENGTHS ON MAIN AXIS\n // Calculate the remaining available space that needs to be allocated.\n // If the main dimension size isn't known, it is computed based on\n // the line length, so there's no more space left to distribute.\n var/*float*/ remainingFreeSpace = 0;\n if (!isUndefined(availableInnerMainDim)) {\n remainingFreeSpace = availableInnerMainDim - sizeConsumedOnCurrentLine;\n } else if (sizeConsumedOnCurrentLine < 0) {\n // availableInnerMainDim is indefinite which means the node is being sized based on its content.\n // sizeConsumedOnCurrentLine is negative which means the node will allocate 0 pixels for\n // its content. Consequently, remainingFreeSpace is 0 - sizeConsumedOnCurrentLine.\n remainingFreeSpace = -sizeConsumedOnCurrentLine;\n }\n \n var/*float*/ remainingFreeSpaceAfterFlex = remainingFreeSpace;\n\n if (!canSkipFlex) {\n var/*float*/ childFlexBasis;\n var/*float*/ flexShrinkScaledFactor;\n var/*float*/ flexGrowFactor;\n var/*float*/ baseMainSize;\n var/*float*/ boundMainSize;\n \n // Do two passes over the flex items to figure out how to distribute the remaining space.\n // The first pass finds the items whose min/max constraints trigger, freezes them at those\n // sizes, and excludes those sizes from the remaining space. The second pass sets the size\n // of each flexible item. It distributes the remaining space amongst the items whose min/max\n // constraints didn't trigger in pass 1. For the other items, it sets their sizes by forcing\n // their min/max constraints to trigger again. \n //\n // This two pass approach for resolving min/max constraints deviates from the spec. The\n // spec (https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths) describes a process\n // that needs to be repeated a variable number of times. The algorithm implemented here\n // won't handle all cases but it was simpler to implement and it mitigates performance\n // concerns because we know exactly how many passes it'll do.\n \n // First pass: detect the flex items whose min/max constraints trigger\n var/*float*/ deltaFreeSpace = 0;\n var/*float*/ deltaFlexShrinkScaledFactors = 0;\n var/*float*/ deltaFlexGrowFactors = 0;\n currentRelativeChild = firstRelativeChild;\n while (currentRelativeChild !== undefined) {\n childFlexBasis = currentRelativeChild.layout.flexBasis;\n\n if (remainingFreeSpace < 0) {\n flexShrinkScaledFactor = getFlexShrinkFactor(currentRelativeChild) * childFlexBasis;\n \n // Is this child able to shrink?\n if (flexShrinkScaledFactor !== 0) {\n baseMainSize = childFlexBasis +\n remainingFreeSpace / totalFlexShrinkScaledFactors * flexShrinkScaledFactor;\n boundMainSize = boundAxis(currentRelativeChild, mainAxis, baseMainSize);\n if (baseMainSize !== boundMainSize) {\n // By excluding this item's size and flex factor from remaining, this item's\n // min/max constraints should also trigger in the second pass resulting in the\n // item's size calculation being identical in the first and second passes.\n deltaFreeSpace -= boundMainSize;\n deltaFlexShrinkScaledFactors -= flexShrinkScaledFactor;\n }\n }\n } else if (remainingFreeSpace > 0) {\n flexGrowFactor = getFlexGrowFactor(currentRelativeChild);\n\n // Is this child able to grow?\n if (flexGrowFactor !== 0) {\n baseMainSize = childFlexBasis +\n remainingFreeSpace / totalFlexGrowFactors * flexGrowFactor;\n boundMainSize = boundAxis(currentRelativeChild, mainAxis, baseMainSize);\n if (baseMainSize !== boundMainSize) {\n // By excluding this item's size and flex factor from remaining, this item's\n // min/max constraints should also trigger in the second pass resulting in the\n // item's size calculation being identical in the first and second passes.\n deltaFreeSpace -= boundMainSize;\n deltaFlexGrowFactors -= flexGrowFactor;\n }\n }\n }\n \n currentRelativeChild = currentRelativeChild.nextChild;\n }\n \n totalFlexShrinkScaledFactors += deltaFlexShrinkScaledFactors;\n totalFlexGrowFactors += deltaFlexGrowFactors;\n remainingFreeSpace += deltaFreeSpace;\n remainingFreeSpaceAfterFlex = remainingFreeSpace;\n \n // Second pass: resolve the sizes of the flexible items\n currentRelativeChild = firstRelativeChild;\n while (currentRelativeChild !== undefined) {\n childFlexBasis = currentRelativeChild.layout.flexBasis;\n var/*float*/ updatedMainSize = childFlexBasis;\n\n if (remainingFreeSpace < 0) {\n flexShrinkScaledFactor = getFlexShrinkFactor(currentRelativeChild) * childFlexBasis;\n \n // Is this child able to shrink?\n if (flexShrinkScaledFactor !== 0) {\n updatedMainSize = boundAxis(currentRelativeChild, mainAxis, childFlexBasis +\n remainingFreeSpace / totalFlexShrinkScaledFactors * flexShrinkScaledFactor);\n }\n } else if (remainingFreeSpace > 0) {\n flexGrowFactor = getFlexGrowFactor(currentRelativeChild);\n\n // Is this child able to grow?\n if (flexGrowFactor !== 0) {\n updatedMainSize = boundAxis(currentRelativeChild, mainAxis, childFlexBasis +\n remainingFreeSpace / totalFlexGrowFactors * flexGrowFactor);\n }\n }\n \n remainingFreeSpaceAfterFlex -= updatedMainSize - childFlexBasis;\n \n if (isMainAxisRow) {\n childWidth = updatedMainSize + getMarginAxis(currentRelativeChild, CSS_FLEX_DIRECTION_ROW);\n childWidthMeasureMode = CSS_MEASURE_MODE_EXACTLY;\n \n if (!isStyleDimDefined(currentRelativeChild, CSS_FLEX_DIRECTION_COLUMN)) {\n childHeight = availableInnerCrossDim;\n childHeightMeasureMode = isUndefined(childHeight) ? CSS_MEASURE_MODE_UNDEFINED : CSS_MEASURE_MODE_AT_MOST;\n } else {\n childHeight = currentRelativeChild.style.height + getMarginAxis(currentRelativeChild, CSS_FLEX_DIRECTION_COLUMN);\n childHeightMeasureMode = CSS_MEASURE_MODE_EXACTLY;\n }\n } else {\n childHeight = updatedMainSize + getMarginAxis(currentRelativeChild, CSS_FLEX_DIRECTION_COLUMN);\n childHeightMeasureMode = CSS_MEASURE_MODE_EXACTLY;\n \n if (!isStyleDimDefined(currentRelativeChild, CSS_FLEX_DIRECTION_ROW)) {\n childWidth = availableInnerCrossDim;\n childWidthMeasureMode = isUndefined(childWidth) ? CSS_MEASURE_MODE_UNDEFINED : CSS_MEASURE_MODE_AT_MOST;\n } else {\n childWidth = currentRelativeChild.style.width + getMarginAxis(currentRelativeChild, CSS_FLEX_DIRECTION_ROW);\n childWidthMeasureMode = CSS_MEASURE_MODE_EXACTLY;\n }\n }\n \n var/*bool*/ requiresStretchLayout = !isStyleDimDefined(currentRelativeChild, crossAxis) &&\n getAlignItem(node, currentRelativeChild) === CSS_ALIGN_STRETCH;\n\n // Recursively call the layout algorithm for this child with the updated main size.\n layoutNodeInternal(currentRelativeChild, childWidth, childHeight, direction, childWidthMeasureMode, childHeightMeasureMode, performLayout && !requiresStretchLayout, 'flex');\n\n currentRelativeChild = currentRelativeChild.nextChild;\n }\n }\n \n remainingFreeSpace = remainingFreeSpaceAfterFlex;\n\n // STEP 6: MAIN-AXIS JUSTIFICATION & CROSS-AXIS SIZE DETERMINATION\n\n // At this point, all the children have their dimensions set in the main axis.\n // Their dimensions are also set in the cross axis with the exception of items\n // that are aligned 'stretch'. We need to compute these stretch values and\n // set the final positions.\n\n // If we are using \"at most\" rules in the main axis, we won't distribute\n // any remaining space at this point.\n if (measureModeMainDim === CSS_MEASURE_MODE_AT_MOST) {\n remainingFreeSpace = 0;\n }\n\n // Use justifyContent to figure out how to allocate the remaining space\n // available in the main axis.\n if (justifyContent !== CSS_JUSTIFY_FLEX_START) {\n if (justifyContent === CSS_JUSTIFY_CENTER) {\n leadingMainDim = remainingFreeSpace / 2;\n } else if (justifyContent === CSS_JUSTIFY_FLEX_END) {\n leadingMainDim = remainingFreeSpace;\n } else if (justifyContent === CSS_JUSTIFY_SPACE_BETWEEN) {\n remainingFreeSpace = fmaxf(remainingFreeSpace, 0);\n if (itemsOnLine > 1) {\n betweenMainDim = remainingFreeSpace / (itemsOnLine - 1);\n } else {\n betweenMainDim = 0;\n }\n } else if (justifyContent === CSS_JUSTIFY_SPACE_AROUND) {\n // Space on the edges is half of the space between elements\n betweenMainDim = remainingFreeSpace / itemsOnLine;\n leadingMainDim = betweenMainDim / 2;\n }\n }\n\n var/*float*/ mainDim = leadingPaddingAndBorderMain + leadingMainDim;\n var/*float*/ crossDim = 0;\n\n for (i = startOfLineIndex; i < endOfLineIndex; ++i) {\n child = node.children[i];\n\n if (getPositionType(child) === CSS_POSITION_ABSOLUTE &&\n isPosDefined(child, leading[mainAxis])) {\n if (performLayout) {\n // In case the child is position absolute and has left/top being\n // defined, we override the position to whatever the user said\n // (and margin/border).\n child.layout[pos[mainAxis]] = getPosition(child, leading[mainAxis]) +\n getLeadingBorder(node, mainAxis) +\n getLeadingMargin(child, mainAxis);\n }\n } else {\n if (performLayout) {\n // If the child is position absolute (without top/left) or relative,\n // we put it at the current accumulated offset.\n child.layout[pos[mainAxis]] += mainDim;\n }\n \n // Now that we placed the element, we need to update the variables.\n // We need to do that only for relative elements. Absolute elements\n // do not take part in that phase.\n if (getPositionType(child) === CSS_POSITION_RELATIVE) {\n if (canSkipFlex) {\n // If we skipped the flex step, then we can't rely on the measuredDims because\n // they weren't computed. This means we can't call getDimWithMargin.\n mainDim += betweenMainDim + getMarginAxis(child, mainAxis) + child.layout.flexBasis;\n crossDim = availableInnerCrossDim;\n } else {\n // The main dimension is the sum of all the elements dimension plus\n // the spacing.\n mainDim += betweenMainDim + getDimWithMargin(child, mainAxis);\n \n // The cross dimension is the max of the elements dimension since there\n // can only be one element in that cross dimension.\n crossDim = fmaxf(crossDim, getDimWithMargin(child, crossAxis));\n }\n }\n }\n }\n\n mainDim += trailingPaddingAndBorderMain;\n \n var/*float*/ containerCrossAxis = availableInnerCrossDim;\n if (measureModeCrossDim === CSS_MEASURE_MODE_UNDEFINED || measureModeCrossDim === CSS_MEASURE_MODE_AT_MOST) {\n // Compute the cross axis from the max cross dimension of the children.\n containerCrossAxis = boundAxis(node, crossAxis, crossDim + paddingAndBorderAxisCross) - paddingAndBorderAxisCross;\n \n if (measureModeCrossDim === CSS_MEASURE_MODE_AT_MOST) {\n containerCrossAxis = fminf(containerCrossAxis, availableInnerCrossDim);\n }\n }\n\n // If there's no flex wrap, the cross dimension is defined by the container.\n if (!isNodeFlexWrap && measureModeCrossDim === CSS_MEASURE_MODE_EXACTLY) {\n crossDim = availableInnerCrossDim;\n }\n\n // Clamp to the min/max size specified on the container.\n crossDim = boundAxis(node, crossAxis, crossDim + paddingAndBorderAxisCross) - paddingAndBorderAxisCross;\n\n // STEP 7: CROSS-AXIS ALIGNMENT\n // We can skip child alignment if we're just measuring the container.\n if (performLayout) {\n for (i = startOfLineIndex; i < endOfLineIndex; ++i) {\n child = node.children[i];\n\n if (getPositionType(child) === CSS_POSITION_ABSOLUTE) {\n // If the child is absolutely positioned and has a top/left/bottom/right\n // set, override all the previously computed positions to set it correctly.\n if (isPosDefined(child, leading[crossAxis])) {\n child.layout[pos[crossAxis]] = getPosition(child, leading[crossAxis]) +\n getLeadingBorder(node, crossAxis) +\n getLeadingMargin(child, crossAxis);\n } else {\n child.layout[pos[crossAxis]] = leadingPaddingAndBorderCross +\n getLeadingMargin(child, crossAxis);\n }\n } else {\n var/*float*/ leadingCrossDim = leadingPaddingAndBorderCross;\n\n // For a relative children, we're either using alignItems (parent) or\n // alignSelf (child) in order to determine the position in the cross axis\n var/*css_align_t*/ alignItem = getAlignItem(node, child);\n \n // If the child uses align stretch, we need to lay it out one more time, this time\n // forcing the cross-axis size to be the computed cross size for the current line.\n if (alignItem === CSS_ALIGN_STRETCH) {\n childWidth = child.layout.measuredWidth + getMarginAxis(child, CSS_FLEX_DIRECTION_ROW);\n childHeight = child.layout.measuredHeight + getMarginAxis(child, CSS_FLEX_DIRECTION_COLUMN);\n var/*bool*/ isCrossSizeDefinite = false;\n \n if (isMainAxisRow) {\n isCrossSizeDefinite = isStyleDimDefined(child, CSS_FLEX_DIRECTION_COLUMN);\n childHeight = crossDim;\n } else {\n isCrossSizeDefinite = isStyleDimDefined(child, CSS_FLEX_DIRECTION_ROW);\n childWidth = crossDim;\n }\n \n // If the child defines a definite size for its cross axis, there's no need to stretch.\n if (!isCrossSizeDefinite) {\n childWidthMeasureMode = isUndefined(childWidth) ? CSS_MEASURE_MODE_UNDEFINED : CSS_MEASURE_MODE_EXACTLY;\n childHeightMeasureMode = isUndefined(childHeight) ? CSS_MEASURE_MODE_UNDEFINED : CSS_MEASURE_MODE_EXACTLY;\n layoutNodeInternal(child, childWidth, childHeight, direction, childWidthMeasureMode, childHeightMeasureMode, true, 'stretch');\n }\n } else if (alignItem !== CSS_ALIGN_FLEX_START) {\n var/*float*/ remainingCrossDim = containerCrossAxis - getDimWithMargin(child, crossAxis);\n\n if (alignItem === CSS_ALIGN_CENTER) {\n leadingCrossDim += remainingCrossDim / 2;\n } else { // CSS_ALIGN_FLEX_END\n leadingCrossDim += remainingCrossDim;\n }\n }\n\n // And we apply the position\n child.layout[pos[crossAxis]] += totalLineCrossDim + leadingCrossDim;\n }\n }\n }\n\n totalLineCrossDim += crossDim;\n maxLineMainDim = fmaxf(maxLineMainDim, mainDim);\n\n // Reset variables for new line.\n lineCount++;\n startOfLineIndex = endOfLineIndex;\n endOfLineIndex = startOfLineIndex;\n }\n\n // STEP 8: MULTI-LINE CONTENT ALIGNMENT\n if (lineCount > 1 && performLayout && !isUndefined(availableInnerCrossDim)) {\n var/*float*/ remainingAlignContentDim = availableInnerCrossDim - totalLineCrossDim;\n\n var/*float*/ crossDimLead = 0;\n var/*float*/ currentLead = leadingPaddingAndBorderCross;\n\n var/*css_align_t*/ alignContent = getAlignContent(node);\n if (alignContent === CSS_ALIGN_FLEX_END) {\n currentLead += remainingAlignContentDim;\n } else if (alignContent === CSS_ALIGN_CENTER) {\n currentLead += remainingAlignContentDim / 2;\n } else if (alignContent === CSS_ALIGN_STRETCH) {\n if (availableInnerCrossDim > totalLineCrossDim) {\n crossDimLead = (remainingAlignContentDim / lineCount);\n }\n }\n\n var/*int*/ endIndex = 0;\n for (i = 0; i < lineCount; ++i) {\n var/*int*/ startIndex = endIndex;\n var/*int*/ j;\n\n // compute the line's height and find the endIndex\n var/*float*/ lineHeight = 0;\n for (j = startIndex; j < childCount; ++j) {\n child = node.children[j];\n if (getPositionType(child) !== CSS_POSITION_RELATIVE) {\n continue;\n }\n if (child.lineIndex !== i) {\n break;\n }\n if (isLayoutDimDefined(child, crossAxis)) {\n lineHeight = fmaxf(lineHeight,\n child.layout[measuredDim[crossAxis]] + getMarginAxis(child, crossAxis));\n }\n }\n endIndex = j;\n lineHeight += crossDimLead;\n\n if (performLayout) {\n for (j = startIndex; j < endIndex; ++j) {\n child = node.children[j];\n if (getPositionType(child) !== CSS_POSITION_RELATIVE) {\n continue;\n }\n\n var/*css_align_t*/ alignContentAlignItem = getAlignItem(node, child);\n if (alignContentAlignItem === CSS_ALIGN_FLEX_START) {\n child.layout[pos[crossAxis]] = currentLead + getLeadingMargin(child, crossAxis);\n } else if (alignContentAlignItem === CSS_ALIGN_FLEX_END) {\n child.layout[pos[crossAxis]] = currentLead + lineHeight - getTrailingMargin(child, crossAxis) - child.layout[measuredDim[crossAxis]];\n } else if (alignContentAlignItem === CSS_ALIGN_CENTER) {\n childHeight = child.layout[measuredDim[crossAxis]];\n child.layout[pos[crossAxis]] = currentLead + (lineHeight - childHeight) / 2;\n } else if (alignContentAlignItem === CSS_ALIGN_STRETCH) {\n child.layout[pos[crossAxis]] = currentLead + getLeadingMargin(child, crossAxis);\n // TODO(prenaux): Correctly set the height of items with indefinite\n // (auto) crossAxis dimension.\n }\n }\n }\n\n currentLead += lineHeight;\n }\n }\n\n // STEP 9: COMPUTING FINAL DIMENSIONS\n node.layout.measuredWidth = boundAxis(node, CSS_FLEX_DIRECTION_ROW, availableWidth - marginAxisRow);\n node.layout.measuredHeight = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, availableHeight - marginAxisColumn);\n\n // If the user didn't specify a width or height for the node, set the\n // dimensions based on the children.\n if (measureModeMainDim === CSS_MEASURE_MODE_UNDEFINED) {\n // Clamp the size to the min/max size, if specified, and make sure it\n // doesn't go below the padding and border amount.\n node.layout[measuredDim[mainAxis]] = boundAxis(node, mainAxis, maxLineMainDim);\n } else if (measureModeMainDim === CSS_MEASURE_MODE_AT_MOST) {\n node.layout[measuredDim[mainAxis]] = fmaxf(\n fminf(availableInnerMainDim + paddingAndBorderAxisMain,\n boundAxisWithinMinAndMax(node, mainAxis, maxLineMainDim)),\n paddingAndBorderAxisMain);\n }\n\n if (measureModeCrossDim === CSS_MEASURE_MODE_UNDEFINED) {\n // Clamp the size to the min/max size, if specified, and make sure it\n // doesn't go below the padding and border amount.\n node.layout[measuredDim[crossAxis]] = boundAxis(node, crossAxis, totalLineCrossDim + paddingAndBorderAxisCross);\n } else if (measureModeCrossDim === CSS_MEASURE_MODE_AT_MOST) {\n node.layout[measuredDim[crossAxis]] = fmaxf(\n fminf(availableInnerCrossDim + paddingAndBorderAxisCross,\n boundAxisWithinMinAndMax(node, crossAxis, totalLineCrossDim + paddingAndBorderAxisCross)),\n paddingAndBorderAxisCross);\n }\n \n // STEP 10: SETTING TRAILING POSITIONS FOR CHILDREN\n if (performLayout) {\n var/*bool*/ needsMainTrailingPos = false;\n var/*bool*/ needsCrossTrailingPos = false;\n\n if (mainAxis === CSS_FLEX_DIRECTION_ROW_REVERSE ||\n mainAxis === CSS_FLEX_DIRECTION_COLUMN_REVERSE) {\n needsMainTrailingPos = true;\n }\n\n if (crossAxis === CSS_FLEX_DIRECTION_ROW_REVERSE ||\n crossAxis === CSS_FLEX_DIRECTION_COLUMN_REVERSE) {\n needsCrossTrailingPos = true;\n }\n\n // Set trailing position if necessary.\n if (needsMainTrailingPos || needsCrossTrailingPos) {\n for (i = 0; i < childCount; ++i) {\n child = node.children[i];\n\n if (needsMainTrailingPos) {\n setTrailingPosition(node, child, mainAxis);\n }\n\n if (needsCrossTrailingPos) {\n setTrailingPosition(node, child, crossAxis);\n }\n }\n }\n }\n \n // STEP 11: SIZING AND POSITIONING ABSOLUTE CHILDREN\n currentAbsoluteChild = firstAbsoluteChild;\n while (currentAbsoluteChild !== undefined) {\n // Now that we know the bounds of the container, perform layout again on the\n // absolutely-positioned children.\n if (performLayout) {\n\n childWidth = CSS_UNDEFINED;\n childHeight = CSS_UNDEFINED;\n\n if (isStyleDimDefined(currentAbsoluteChild, CSS_FLEX_DIRECTION_ROW)) {\n childWidth = currentAbsoluteChild.style.width + getMarginAxis(currentAbsoluteChild, CSS_FLEX_DIRECTION_ROW);\n } else {\n // If the child doesn't have a specified width, compute the width based on the left/right offsets if they're defined.\n if (isPosDefined(currentAbsoluteChild, CSS_LEFT) && isPosDefined(currentAbsoluteChild, CSS_RIGHT)) {\n childWidth = node.layout.measuredWidth -\n (getLeadingBorder(node, CSS_FLEX_DIRECTION_ROW) + getTrailingBorder(node, CSS_FLEX_DIRECTION_ROW)) -\n (currentAbsoluteChild.style[CSS_LEFT] + currentAbsoluteChild.style[CSS_RIGHT]);\n childWidth = boundAxis(currentAbsoluteChild, CSS_FLEX_DIRECTION_ROW, childWidth);\n }\n }\n \n if (isStyleDimDefined(currentAbsoluteChild, CSS_FLEX_DIRECTION_COLUMN)) {\n childHeight = currentAbsoluteChild.style.height + getMarginAxis(currentAbsoluteChild, CSS_FLEX_DIRECTION_COLUMN);\n } else {\n // If the child doesn't have a specified height, compute the height based on the top/bottom offsets if they're defined.\n if (isPosDefined(currentAbsoluteChild, CSS_TOP) && isPosDefined(currentAbsoluteChild, CSS_BOTTOM)) {\n childHeight = node.layout.measuredHeight -\n (getLeadingBorder(node, CSS_FLEX_DIRECTION_COLUMN) + getTrailingBorder(node, CSS_FLEX_DIRECTION_COLUMN)) -\n (currentAbsoluteChild.style[CSS_TOP] + currentAbsoluteChild.style[CSS_BOTTOM]);\n childHeight = boundAxis(currentAbsoluteChild, CSS_FLEX_DIRECTION_COLUMN, childHeight);\n }\n }\n\n // If we're still missing one or the other dimension, measure the content.\n if (isUndefined(childWidth) || isUndefined(childHeight)) {\n childWidthMeasureMode = isUndefined(childWidth) ? CSS_MEASURE_MODE_UNDEFINED : CSS_MEASURE_MODE_EXACTLY;\n childHeightMeasureMode = isUndefined(childHeight) ? CSS_MEASURE_MODE_UNDEFINED : CSS_MEASURE_MODE_EXACTLY;\n \n // According to the spec, if the main size is not definite and the\n // child's inline axis is parallel to the main axis (i.e. it's\n // horizontal), the child should be sized using \"UNDEFINED\" in\n // the main size. Otherwise use \"AT_MOST\" in the cross axis.\n if (!isMainAxisRow && isUndefined(childWidth) && !isUndefined(availableInnerWidth)) {\n childWidth = availableInnerWidth;\n childWidthMeasureMode = CSS_MEASURE_MODE_AT_MOST;\n }\n\n // The W3C spec doesn't say anything about the 'overflow' property,\n // but all major browsers appear to implement the following logic.\n if (getOverflow(node) === CSS_OVERFLOW_HIDDEN) {\n if (isMainAxisRow && isUndefined(childHeight) && !isUndefined(availableInnerHeight)) {\n childHeight = availableInnerHeight;\n childHeightMeasureMode = CSS_MEASURE_MODE_AT_MOST;\n }\n }\n\n layoutNodeInternal(currentAbsoluteChild, childWidth, childHeight, direction, childWidthMeasureMode, childHeightMeasureMode, false, 'abs-measure');\n childWidth = currentAbsoluteChild.layout.measuredWidth + getMarginAxis(currentAbsoluteChild, CSS_FLEX_DIRECTION_ROW);\n childHeight = currentAbsoluteChild.layout.measuredHeight + getMarginAxis(currentAbsoluteChild, CSS_FLEX_DIRECTION_COLUMN);\n }\n \n layoutNodeInternal(currentAbsoluteChild, childWidth, childHeight, direction, CSS_MEASURE_MODE_EXACTLY, CSS_MEASURE_MODE_EXACTLY, true, 'abs-layout');\n \n if (isPosDefined(currentAbsoluteChild, trailing[CSS_FLEX_DIRECTION_ROW]) &&\n !isPosDefined(currentAbsoluteChild, leading[CSS_FLEX_DIRECTION_ROW])) {\n currentAbsoluteChild.layout[leading[CSS_FLEX_DIRECTION_ROW]] =\n node.layout[measuredDim[CSS_FLEX_DIRECTION_ROW]] -\n currentAbsoluteChild.layout[measuredDim[CSS_FLEX_DIRECTION_ROW]] -\n getPosition(currentAbsoluteChild, trailing[CSS_FLEX_DIRECTION_ROW]);\n }\n \n if (isPosDefined(currentAbsoluteChild, trailing[CSS_FLEX_DIRECTION_COLUMN]) &&\n !isPosDefined(currentAbsoluteChild, leading[CSS_FLEX_DIRECTION_COLUMN])) {\n currentAbsoluteChild.layout[leading[CSS_FLEX_DIRECTION_COLUMN]] =\n node.layout[measuredDim[CSS_FLEX_DIRECTION_COLUMN]] -\n currentAbsoluteChild.layout[measuredDim[CSS_FLEX_DIRECTION_COLUMN]] -\n getPosition(currentAbsoluteChild, trailing[CSS_FLEX_DIRECTION_COLUMN]);\n }\n }\n\n currentAbsoluteChild = currentAbsoluteChild.nextChild;\n }\n }\n \n //\n // This is a wrapper around the layoutNodeImpl function. It determines\n // whether the layout request is redundant and can be skipped.\n //\n // Parameters:\n // Input parameters are the same as layoutNodeImpl (see above)\n // Return parameter is true if layout was performed, false if skipped\n //\n function layoutNodeInternal(node, availableWidth, availableHeight, parentDirection,\n widthMeasureMode, heightMeasureMode, performLayout, reason) {\n var layout = node.layout;\n\n var needToVisitNode = (node.isDirty && layout.generationCount !== gCurrentGenerationCount) ||\n layout.lastParentDirection !== parentDirection;\n\n if (needToVisitNode) {\n // Invalidate the cached results.\n if (layout.cachedMeasurements !== undefined) {\n layout.cachedMeasurements = []; \n }\n if (layout.cachedLayout !== undefined) {\n layout.cachedLayout.widthMeasureMode = undefined;\n layout.cachedLayout.heightMeasureMode = undefined;\n }\n }\n\n var cachedResults;\n \n // Determine whether the results are already cached. We maintain a separate\n // cache for layouts and measurements. A layout operation modifies the positions\n // and dimensions for nodes in the subtree. The algorithm assumes that each node\n // gets layed out a maximum of one time per tree layout, but multiple measurements\n // may be required to resolve all of the flex dimensions.\n if (performLayout) {\n if (layout.cachedLayout &&\n layout.cachedLayout.availableWidth === availableWidth &&\n layout.cachedLayout.availableHeight === availableHeight &&\n layout.cachedLayout.widthMeasureMode === widthMeasureMode &&\n layout.cachedLayout.heightMeasureMode === heightMeasureMode) {\n cachedResults = layout.cachedLayout;\n }\n } else if (layout.cachedMeasurements) {\n for (var i = 0, len = layout.cachedMeasurements.length; i < len; i++) {\n if (layout.cachedMeasurements[i].availableWidth === availableWidth &&\n layout.cachedMeasurements[i].availableHeight === availableHeight &&\n layout.cachedMeasurements[i].widthMeasureMode === widthMeasureMode &&\n layout.cachedMeasurements[i].heightMeasureMode === heightMeasureMode) {\n cachedResults = layout.cachedMeasurements[i];\n break;\n }\n }\n }\n \n if (!needToVisitNode && cachedResults !== undefined) {\n layout.measureWidth = cachedResults.computedWidth;\n layout.measureHeight = cachedResults.computedHeight;\n } else {\n layoutNodeImpl(node, availableWidth, availableHeight, parentDirection, widthMeasureMode, heightMeasureMode, performLayout);\n layout.lastParentDirection = parentDirection;\n \n if (cachedResults === undefined) {\n var newCacheEntry;\n if (performLayout) {\n // Use the single layout cache entry.\n if (layout.cachedLayout === undefined) {\n layout.cachedLayout = {};\n }\n newCacheEntry = layout.cachedLayout;\n } else {\n // Allocate a new measurement cache entry.\n if (layout.cachedMeasurements === undefined) {\n layout.cachedMeasurements = [];\n }\n newCacheEntry = {};\n layout.cachedMeasurements.push(newCacheEntry);\n }\n \n newCacheEntry.availableWidth = availableWidth;\n newCacheEntry.availableHeight = availableHeight;\n newCacheEntry.widthMeasureMode = widthMeasureMode;\n newCacheEntry.heightMeasureMode = heightMeasureMode;\n newCacheEntry.computedWidth = layout.measuredWidth;\n newCacheEntry.computedHeight = layout.measuredHeight;\n }\n }\n \n if (performLayout) {\n node.layout.width = node.layout.measuredWidth;\n node.layout.height = node.layout.measuredHeight;\n layout.shouldUpdate = true;\n }\n \n layout.generationCount = gCurrentGenerationCount;\n return (needToVisitNode || cachedResults === undefined);\n }\n \n function layoutNode(node, availableWidth, availableHeight, parentDirection) {\n // Increment the generation count. This will force the recursive routine to visit\n // all dirty nodes at least once. Subsequent visits will be skipped if the input\n // parameters don't change.\n gCurrentGenerationCount++;\n \n // If the caller didn't specify a height/width, use the dimensions\n // specified in the style.\n if (isUndefined(availableWidth) && isStyleDimDefined(node, CSS_FLEX_DIRECTION_ROW)) {\n availableWidth = node.style.width + getMarginAxis(node, CSS_FLEX_DIRECTION_ROW);\n }\n if (isUndefined(availableHeight) && isStyleDimDefined(node, CSS_FLEX_DIRECTION_COLUMN)) {\n availableHeight = node.style.height + getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN);\n }\n \n var widthMeasureMode = isUndefined(availableWidth) ? CSS_MEASURE_MODE_UNDEFINED : CSS_MEASURE_MODE_EXACTLY;\n var heightMeasureMode = isUndefined(availableHeight) ? CSS_MEASURE_MODE_UNDEFINED : CSS_MEASURE_MODE_EXACTLY;\n \n if (layoutNodeInternal(node, availableWidth, availableHeight, parentDirection, widthMeasureMode, heightMeasureMode, true, 'initial')) {\n setPosition(node, node.layout.direction);\n }\n }\n\n return {\n layoutNodeImpl: layoutNodeImpl,\n computeLayout: layoutNode,\n fillNodes: fillNodes\n };\n})();\n\n// This module export is only used for the purposes of unit testing this file. When\n// the library is packaged this file is included within css-layout.js which forms\n// the public API.\nif (typeof exports === 'object') {\n module.exports = computeLayout;\n}\n\n\n return function(node) {\n /*eslint-disable */\n // disabling ESLint because this code relies on the above include\n computeLayout.fillNodes(node);\n computeLayout.computeLayout(node);\n /*eslint-enable */\n };\n}));\n"]} \ No newline at end of file diff --git a/src/CSharpTranspiler.js b/src/CSharpTranspiler.js index 1342347a..b9ae21bb 100644 --- a/src/CSharpTranspiler.js +++ b/src/CSharpTranspiler.js @@ -9,11 +9,18 @@ function __transpileToCSharpCommon(code) { return code + .replace(/'abs-layout'/g, '"abs-layout"') + .replace(/'abs-measure'/g, '"abs-measure"') + .replace(/'flex'/g, '"flex"') + .replace(/'measure'/g, '"measure"') + .replace(/'stretch'/g, '"stretch"') + .replace(/undefined/g, 'null') .replace(/CSS_UNDEFINED/g, 'CSSConstants.UNDEFINED') .replace(/CSS_JUSTIFY_/g, 'CSSJustify.') .replace(/CSS_MEASURE_MODE_/g, 'CSSMeasureMode.') .replace(/CSS_ALIGN_/g, 'CSSAlign.') .replace(/CSS_POSITION_/g, 'CSSPositionType.') + .replace(/CSS_OVERFLOW_/g, 'CSSOverflow.') .replace(/css_flex_direction_t/g, 'CSSFlexDirection') .replace(/css_direction_t/g, 'CSSDirection') .replace(/css_align_t/g, 'CSSAlign') @@ -21,6 +28,10 @@ function __transpileToCSharpCommon(code) { .replace(/css_measure_mode_t/g, 'CSSMeasureMode') .replace(/css_dim_t/g, 'MeasureOutput') .replace(/bool/g, 'boolean') + .replace(/style\[CSS_LEFT/g, 'style.position[POSITION_LEFT') + .replace(/style\[CSS_TOP/g, 'style.position[POSITION_TOP') + .replace(/style\[CSS_RIGHT/g, 'style.position[POSITION_RIGHT') + .replace(/style\[CSS_BOTTOM/g, 'style.position[POSITION_BOTTOM') .replace(/style\[dim/g, 'style.dimensions[dim') .replace(/(style|layout)\.width/g, '$1.dimensions[DIMENSION_WIDTH]') .replace(/(style|layout)\.height/g, '$1.dimensions[DIMENSION_HEIGHT]') @@ -28,22 +39,24 @@ function __transpileToCSharpCommon(code) { .replace(/layout\[pos/g, 'layout.position[pos') .replace(/layout\[leading/g, 'layout.position[leading') .replace(/layout\[trailing/g, 'layout.position[trailing') + .replace(/layout\[measuredDim/g, 'layout.measuredDimensions[dim') + .replace(/layout\.measuredWidth/g, 'layout.measuredDimensions[DIMENSION_WIDTH]') + .replace(/layout\.measuredHeight/g, 'layout.measuredDimensions[DIMENSION_HEIGHT]') .replace(/getPositionType\((.+?)\)/g, '$1.style.positionType') .replace(/getJustifyContent\((.+?)\)/g, '$1.style.justifyContent') .replace(/getAlignContent\((.+?)\)/g, '$1.style.alignContent') .replace(/isPosDefined\((.+?),\s*(.+?)\)/g, '!isUndefined\($1.style.position[$2]\)') - .replace(/isStyleDimDefined\((.+?),\s*(.+?)\)/g, '\(!isUndefined\($1.style.dimensions[dim[$2]]\) && $1.style.dimensions[dim[$2]] >= 0.0\)') - .replace(/isLayoutDimDefined\((.+?),\s*(.+?)\)/g, '\(!isUndefined\($1.layout.dimensions[dim[$2]]\) && $1.layout.dimensions[dim[$2]] >= 0.0\)') + .replace(/isStyleDimDefined\((.+?),\s*(.+?)\)/g, '($1.style.dimensions[dim[$2]] >= 0.0)') + .replace(/isLayoutDimDefined\((.+?),\s*(.+?)\)/g, '($1.layout.measuredDimensions[dim[$2]] >= 0.0)') .replace(/getPosition\((.+?),\s*(.+?)\)/g, '\(isUndefined\($1.style.position[$2]\) ? 0 : $1.style.position[$2]\)') - .replace(/setTrailingPosition\((.+?),\s*(.+?),\s*(.+?)\)/g, '$2.layout.position[trailing[$3]] = $1.layout.dimensions[dim[$3]] - $2.layout.dimensions[dim[$3]] - $2.layout.position[pos[$3]]') - .replace(/isFlex\((.+?)\)/g, '\($1.style.positionType == CSSPositionType.RELATIVE && $1.style.flex > 0\)') + .replace(/setTrailingPosition\((.+?),\s*(.+?),\s*(.+?)\)/g, '$2.layout.position[trailing[$3]] = $1.layout.measuredDimensions[dim[$3]] - ($2.style.positionType == CSSPositionType.Absolute ? 0 : $2.layout.measuredDimensions[dim[$3]]) - $2.layout.position[pos[$3]]') + .replace(/isFlex\((.+?)\)/g, '\($1.style.positionType == CSSPositionType.RELATIVE && $1.style.flex != 0\)') .replace(/isFlexWrap\((.+?)\)/g, '\($1.style.flexWrap == CSSWrap.WRAP\)') .replace(/getPaddingAndBorderAxis\((.+?),\s*(.+?)\)/g, '\(getLeadingPaddingAndBorder($1, $2) + getTrailingPaddingAndBorder($1, $2)\)') - .replace(/getBorderAxis\((.+?),\s*(.+?)\)/g, '\(getLeadingBorder($1, $2) + getTrailingBorder($1, $2)\)') .replace(/getMarginAxis\((.+?),\s*(.+?)\)/g, '\(getLeadingMargin($1, $2) + getTrailingMargin($1, $2)\)') .replace(/getLeadingPaddingAndBorder\((.+?),\s*(.+?)\)/g, '\(getLeadingPadding($1, $2) + getLeadingBorder($1, $2)\)') .replace(/getTrailingPaddingAndBorder\((.+?),\s*(.+?)\)/g, '\(getTrailingPadding($1, $2) + getTrailingBorder($1, $2)\)') - .replace(/getDimWithMargin\((.+?),\s*(.+?)\)/g, '\($1.layout.dimensions[dim[$2]] + getLeadingMargin($1, $2) + getTrailingMargin($1, $2)\)') + .replace(/getDimWithMargin\((.+?),\s*(.+?)\)/g, '\($1.layout.measuredDimensions[dim[$2]] + getLeadingMargin($1, $2) + getTrailingMargin($1, $2)\)') .replace(/getLeadingMargin\((.+?),\s*(.+?)\)/g, '$1.style.margin.getWithFallback(leadingSpacing[$2], leading[$2])') .replace(/getTrailingMargin\((.+?),\s*(.+?)\)/g, '$1.style.margin.getWithFallback(trailingSpacing[$2], trailing[$2])') .replace(/getLeadingPadding\((.+?),\s*(.+?)\)/g, '$1.style.padding.getWithFallback(leadingSpacing[$2], leading[$2])') @@ -51,14 +64,18 @@ function __transpileToCSharpCommon(code) { .replace(/getLeadingBorder\((.+?),\s*(.+?)\)/g, '$1.style.border.getWithFallback(leadingSpacing[$2], leading[$2])') .replace(/getTrailingBorder\((.+?),\s*(.+?)\)/g, '$1.style.border.getWithFallback(trailingSpacing[$2], trailing[$2])') .replace(/isRowDirection\((.+?)\)/g, '\($1 == CSS_FLEX_DIRECTION_ROW || $1 == CSS_FLEX_DIRECTION_ROW_REVERSE\)') + .replace(/assert\((.+?),\s*'(.+?)'\)/g, 'Assertions.assertCondition($1, "$2")') .replace(/isUndefined\((.+?)\)/g, 'float.IsNaN\($1\)') + .replace(/getOverflow\((.+?)\)/g, '$1.style.overflow') + .replace(/layoutNodeInternal\((.+?)\)/g, 'layoutNodeInternal(layoutContext, $1)') + .replace(/style\.position\[CSS_/g, 'style.position[POSITION_') .replace(/\/\*\(c\)!([^*]+)\*\//g, '') .replace(/var\/\*\(java\)!([^*]+)\*\//g, '$1') .replace(/\/\*\(java\)!([^*]+)\*\//g, '$1') // additional case conversions - .replace(/(CSSConstants|CSSWrap|CSSJustify|CSSMeasureMode|CSSAlign|CSSPositionType)\.([_A-Z]+)/g, + .replace(/(CSSConstants|CSSWrap|CSSJustify|CSSMeasureMode|CSSAlign|CSSPositionType|CSSOverflow)\.([_A-Z]+)/g, function(str, match1, match2) { return match1 + '.' + constantToPascalCase(match2); }); @@ -139,12 +156,12 @@ var CSharpTranspiler = { transpileLayoutEngine: function(code) { return indent( __transpileToCSharpCommon(code) - .replace(/function\s+layoutNode.*/, '') .replace('node.style.measure', 'node.measure') .replace(/\.children\.length/g, '.getChildCount()') .replace(/node.children\[i\]/g, 'node.getChildAt(i)') - .replace(/node.children\[ii\]/g, 'node.getChildAt(ii)') + .replace(/node.children\[j\]/g, 'node.getChildAt(j)') .replace(/fmaxf/g, 'Math.Max') + .replace(/fminf/g, 'Math.Min') .replace(/\/\*\([^\/]+\*\/\n/g, '') // remove comments for other languages .replace(/var\/\*([^\/]+)\*\//g, '$1') .replace(/ === /g, ' == ') diff --git a/src/JavaTranspiler.js b/src/JavaTranspiler.js index 8623794f..c7ec14b1 100644 --- a/src/JavaTranspiler.js +++ b/src/JavaTranspiler.js @@ -9,11 +9,18 @@ function __transpileToJavaCommon(code) { return code + .replace(/'abs-layout'/g, '"abs-layout"') + .replace(/'abs-measure'/g, '"abs-measure"') + .replace(/'flex'/g, '"flex"') + .replace(/'measure'/g, '"measure"') + .replace(/'stretch'/g, '"stretch"') + .replace(/undefined/g, 'null') .replace(/CSS_UNDEFINED/g, 'CSSConstants.UNDEFINED') .replace(/CSS_JUSTIFY_/g, 'CSSJustify.') .replace(/CSS_MEASURE_MODE_/g, 'CSSMeasureMode.') .replace(/CSS_ALIGN_/g, 'CSSAlign.') .replace(/CSS_POSITION_/g, 'CSSPositionType.') + .replace(/CSS_OVERFLOW_/g, 'CSSOverflow.') .replace(/css_flex_direction_t/g, 'CSSFlexDirection') .replace(/css_direction_t/g, 'CSSDirection') .replace(/css_align_t/g, 'CSSAlign') @@ -21,6 +28,10 @@ function __transpileToJavaCommon(code) { .replace(/css_measure_mode_t/g, 'CSSMeasureMode') .replace(/css_dim_t/g, 'MeasureOutput') .replace(/bool/g, 'boolean') + .replace(/style\[CSS_LEFT/g, 'style.position[POSITION_LEFT') + .replace(/style\[CSS_TOP/g, 'style.position[POSITION_TOP') + .replace(/style\[CSS_RIGHT/g, 'style.position[POSITION_RIGHT') + .replace(/style\[CSS_BOTTOM/g, 'style.position[POSITION_BOTTOM') .replace(/style\[dim/g, 'style.dimensions[dim') .replace(/(style|layout)\.width/g, '$1.dimensions[DIMENSION_WIDTH]') .replace(/(style|layout)\.height/g, '$1.dimensions[DIMENSION_HEIGHT]') @@ -28,22 +39,24 @@ function __transpileToJavaCommon(code) { .replace(/layout\[pos/g, 'layout.position[pos') .replace(/layout\[leading/g, 'layout.position[leading') .replace(/layout\[trailing/g, 'layout.position[trailing') + .replace(/layout\[measuredDim/g, 'layout.measuredDimensions[dim') + .replace(/layout\.measuredWidth/g, 'layout.measuredDimensions[DIMENSION_WIDTH]') + .replace(/layout\.measuredHeight/g, 'layout.measuredDimensions[DIMENSION_HEIGHT]') .replace(/getPositionType\((.+?)\)/g, '$1.style.positionType') .replace(/getJustifyContent\((.+?)\)/g, '$1.style.justifyContent') .replace(/getAlignContent\((.+?)\)/g, '$1.style.alignContent') .replace(/isPosDefined\((.+?),\s*(.+?)\)/g, '!isUndefined\($1.style.position[$2]\)') - .replace(/isStyleDimDefined\((.+?),\s*(.+?)\)/g, '\(!isUndefined\($1.style.dimensions[dim[$2]]\) && $1.style.dimensions[dim[$2]] >= 0.0\)') - .replace(/isLayoutDimDefined\((.+?),\s*(.+?)\)/g, '\(!isUndefined\($1.layout.dimensions[dim[$2]]\) && $1.layout.dimensions[dim[$2]] >= 0.0\)') + .replace(/isStyleDimDefined\((.+?),\s*(.+?)\)/g, '($1.style.dimensions[dim[$2]] >= 0.0)') + .replace(/isLayoutDimDefined\((.+?),\s*(.+?)\)/g, '($1.layout.measuredDimensions[dim[$2]] >= 0.0)') .replace(/getPosition\((.+?),\s*(.+?)\)/g, '\(isUndefined\($1.style.position[$2]\) ? 0 : $1.style.position[$2]\)') - .replace(/setTrailingPosition\((.+?),\s*(.+?),\s*(.+?)\)/g, '$2.layout.position[trailing[$3]] = $1.layout.dimensions[dim[$3]] - $2.layout.dimensions[dim[$3]] - $2.layout.position[pos[$3]]') - .replace(/isFlex\((.+?)\)/g, '\($1.style.positionType == CSSPositionType.RELATIVE && $1.style.flex > 0\)') + .replace(/setTrailingPosition\((.+?),\s*(.+?),\s*(.+?)\)/g, '$2.layout.position[trailing[$3]] = $1.layout.measuredDimensions[dim[$3]] - ($2.style.positionType == CSSPositionType.ABSOLUTE ? 0 : $2.layout.measuredDimensions[dim[$3]]) - $2.layout.position[pos[$3]]') + .replace(/isFlex\((.+?)\)/g, '\($1.style.positionType == CSSPositionType.RELATIVE && $1.style.flex != 0\)') .replace(/isFlexWrap\((.+?)\)/g, '\($1.style.flexWrap == CSSWrap.WRAP\)') .replace(/getPaddingAndBorderAxis\((.+?),\s*(.+?)\)/g, '\(getLeadingPaddingAndBorder($1, $2) + getTrailingPaddingAndBorder($1, $2)\)') - .replace(/getBorderAxis\((.+?),\s*(.+?)\)/g, '\(getLeadingBorder($1, $2) + getTrailingBorder($1, $2)\)') .replace(/getMarginAxis\((.+?),\s*(.+?)\)/g, '\(getLeadingMargin($1, $2) + getTrailingMargin($1, $2)\)') .replace(/getLeadingPaddingAndBorder\((.+?),\s*(.+?)\)/g, '\(getLeadingPadding($1, $2) + getLeadingBorder($1, $2)\)') .replace(/getTrailingPaddingAndBorder\((.+?),\s*(.+?)\)/g, '\(getTrailingPadding($1, $2) + getTrailingBorder($1, $2)\)') - .replace(/getDimWithMargin\((.+?),\s*(.+?)\)/g, '\($1.layout.dimensions[dim[$2]] + getLeadingMargin($1, $2) + getTrailingMargin($1, $2)\)') + .replace(/getDimWithMargin\((.+?),\s*(.+?)\)/g, '\($1.layout.measuredDimensions[dim[$2]] + getLeadingMargin($1, $2) + getTrailingMargin($1, $2)\)') .replace(/getLeadingMargin\((.+?),\s*(.+?)\)/g, '$1.style.margin.getWithFallback(leadingSpacing[$2], leading[$2])') .replace(/getTrailingMargin\((.+?),\s*(.+?)\)/g, '$1.style.margin.getWithFallback(trailingSpacing[$2], trailing[$2])') .replace(/getLeadingPadding\((.+?),\s*(.+?)\)/g, '$1.style.padding.getWithFallback(leadingSpacing[$2], leading[$2])') @@ -51,7 +64,11 @@ function __transpileToJavaCommon(code) { .replace(/getLeadingBorder\((.+?),\s*(.+?)\)/g, '$1.style.border.getWithFallback(leadingSpacing[$2], leading[$2])') .replace(/getTrailingBorder\((.+?),\s*(.+?)\)/g, '$1.style.border.getWithFallback(trailingSpacing[$2], trailing[$2])') .replace(/isRowDirection\((.+?)\)/g, '\($1 == CSS_FLEX_DIRECTION_ROW || $1 == CSS_FLEX_DIRECTION_ROW_REVERSE\)') + .replace(/assert\((.+?),\s*'(.+?)'\)/g, 'Assertions.assertCondition($1, "$2")') .replace(/isUndefined\((.+?)\)/g, 'Float.isNaN\($1\)') + .replace(/getOverflow\((.+?)\)/g, '$1.style.overflow') + .replace(/layoutNodeInternal\((.+?)\)/g, 'layoutNodeInternal(layoutContext, $1)') + .replace(/style\.position\[CSS_/g, 'style.position[POSITION_') .replace(/\/\*\(c\)!([^*]+)\*\//g, '') .replace(/var\/\*\(java\)!([^*]+)\*\//g, '$1') .replace(/\/\*\(java\)!([^*]+)\*\//g, '$1'); @@ -118,12 +135,12 @@ var JavaTranspiler = { transpileLayoutEngine: function(code) { return indent( __transpileToJavaCommon(code) - .replace(/function\s+layoutNode.*/, '') .replace('node.style.measure', 'node.measure') .replace(/\.children\.length/g, '.getChildCount()') .replace(/node.children\[i\]/g, 'node.getChildAt(i)') - .replace(/node.children\[ii\]/g, 'node.getChildAt(ii)') + .replace(/node.children\[j\]/g, 'node.getChildAt(j)') .replace(/fmaxf/g, 'Math.max') + .replace(/fminf/g, 'Math.min') .replace(/\/\*\([^\/]+\*\/\n/g, '') // remove comments for other languages .replace(/var\/\*([^\/]+)\*\//g, '$1') .replace(/ === /g, ' == ') diff --git a/src/Layout-test-utils.js b/src/Layout-test-utils.js index 3195b09f..6af941e6 100644 --- a/src/Layout-test-utils.js +++ b/src/Layout-test-utils.js @@ -92,6 +92,7 @@ var layoutTestUtils = (function() { margin: 0; padding: 0; + min-width: 0; } hack to ignore three hundred px width of the body {} @@ -118,17 +119,24 @@ var layoutTestUtils = (function() { } function extractNodes(node) { - var layout = node.layout; - delete node.layout; + var keysToCopy = [ + 'width', + 'height', + 'left', + 'top' + ]; + var layout = {}; + keysToCopy.forEach(function(key) { + layout[key] = node.layout[key]; + }); + if (node.children && node.children.length > 0) { layout.children = node.children.map(extractNodes); } else { delete node.children; } - - delete layout.right; - delete layout.bottom; - delete layout.direction; + + delete node.layout; return layout; } @@ -183,13 +191,17 @@ var layoutTestUtils = (function() { function computeDOMLayout(node) { var body = getIframe().contentDocument.body; - + + function setStyle(div, name, value) { + div.style['-webkit-' + name] = value; + div.style['webkit' + capitalizeFirst(name)] = value; + div.style[name] = value; + } + function transfer(div, node, name, ext) { if (name in node.style) { var value = node.style[name] + (ext || ''); - div.style['-webkit-' + name] = value; - div.style['webkit' + capitalizeFirst(name)] = value; - div.style[name] = value; + setStyle(div, name, value); } } @@ -202,7 +214,19 @@ var layoutTestUtils = (function() { transfer(div, node, type + 'Start' + suffix, 'px'); transfer(div, node, type + 'End' + suffix, 'px'); } - + + function transferFlex(div, node) { + if ('flex' in node.style) { + var flex = node.style.flex; + var resolvedFlex = ( + flex < 0 ? '0 1 auto' : + flex > 0 ? (flex + ' 0 0') : + '0 0 auto' + ); + setStyle(div, 'flex', resolvedFlex); + } + } + function renderNode(parent, node) { var div = document.createElement('div'); transfer(div, node, 'width', 'px'); @@ -220,13 +244,14 @@ var layoutTestUtils = (function() { transferSpacing(div, node, 'border', 'Width'); transfer(div, node, 'flexDirection'); transfer(div, node, 'direction'); - transfer(div, node, 'flex'); + transferFlex(div, node); transfer(div, node, 'flexWrap'); transfer(div, node, 'justifyContent'); transfer(div, node, 'alignSelf'); transfer(div, node, 'alignItems'); transfer(div, node, 'alignContent'); transfer(div, node, 'position'); + transfer(div, node, 'overflow'); parent.appendChild(div); (node.children || []).forEach(function(child) { renderNode(div, child); diff --git a/src/Layout.c b/src/Layout.c index 1d03edf8..52acc085 100644 --- a/src/Layout.c +++ b/src/Layout.c @@ -7,6 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. */ +#include #include #include #include @@ -29,6 +30,13 @@ __forceinline const float fmaxf(const float a, const float b) { #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); } @@ -40,12 +48,14 @@ static bool eq(float a, float b) { 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; @@ -73,21 +83,23 @@ void init_css_node(css_node_t *node) { 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); } @@ -97,13 +109,13 @@ static void indent(int n) { } } -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); } @@ -118,7 +130,7 @@ static bool four_equal(float four[4]) { static void print_css_node_rec( - css_node_t *node, + css_node_t* node, css_print_options_t options, int level ) { @@ -142,11 +154,11 @@ static void print_css_node_rec( 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) { @@ -187,6 +199,12 @@ static void print_css_node_rec( 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 { @@ -199,7 +217,7 @@ static void print_css_node_rec( } 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]); @@ -222,6 +240,10 @@ static void print_css_node_rec( 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', "); @@ -245,11 +267,10 @@ static void print_css_node_rec( } } -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, @@ -285,7 +306,41 @@ static bool isColumnDirection(css_flex_direction_t flex_direction) { 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]; } @@ -293,7 +348,7 @@ static float getLeadingMargin(css_node_t *node, css_flex_direction_t axis) { 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]; } @@ -301,7 +356,7 @@ static float getTrailingMargin(css_node_t *node, css_flex_direction_t axis) { 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) { @@ -315,7 +370,7 @@ static float getLeadingPadding(css_node_t *node, css_flex_direction_t axis) { 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) { @@ -329,7 +384,7 @@ static float getTrailingPadding(css_node_t *node, css_flex_direction_t axis) { 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) { @@ -343,7 +398,7 @@ static float getLeadingBorder(css_node_t *node, css_flex_direction_t axis) { 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) { @@ -357,34 +412,30 @@ static float getTrailingBorder(css_node_t *node, css_flex_direction_t axis) { 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) { @@ -394,7 +445,7 @@ static css_direction_t resolveDirection(css_node_t *node, css_direction_t parent 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; } @@ -418,46 +469,46 @@ static css_flex_direction_t getCrossFlexDirection(css_flex_direction_t flex_dire } } -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; @@ -465,7 +516,7 @@ static float getPosition(css_node_t *node, css_position_t position) { 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; @@ -489,32 +540,22 @@ static float boundAxis(css_node_t *node, css_flex_direction_t axis, float value) 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; @@ -522,352 +563,388 @@ static float getRelativePosition(css_node_t *node, css_flex_direction_t axis) { 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); + + 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); +} - // Handle width and height style attributes - setDimensionFromStyle(node, mainAxis); - setDimensionFromStyle(node, crossAxis); +// +// 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 **/ - // Set the resolved resolution in the node's layout + 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; + + if (widthMeasureMode == CSS_MEASURE_MODE_EXACTLY && heightMeasureMode == CSS_MEASURE_MODE_EXACTLY) { - 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; + // 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) { + + // Don't bother sizing the text if there's no horizontal 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); + + css_measure_mode_t measureModeMainDim = isMainAxisRow ? widthMeasureMode : heightMeasureMode; + css_measure_mode_t 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 + 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); + + if (performLayout) { + // Set the initial position (relative to the parent). + css_direction_t 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.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)); + } + } } - // 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 . - bool isSimpleStackMain = - (isMainDimDefined && justifyContent == CSS_JUSTIFY_FLEX_START) || - (!isMainDimDefined && justifyContent != CSS_JUSTIFY_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 . - bool isSimpleStackCross = true; - int firstComplexCross = childCount; + float totalFlexGrowFactors = 0; + float totalFlexShrinkScaledFactors = 0; - css_node_t* firstFlexChild = NULL; - css_node_t* currentFlexChild = NULL; + i = startOfLineIndex; - float mainDim = leadingPaddingAndBorderMain; - float crossDim = 0; + // Maintain a linked list of the child nodes that can shrink and/or grow. + css_node_t* firstRelativeChild = NULL; + css_node_t* currentRelativeChild = NULL; - float maxWidth = CSS_UNDEFINED; - float maxHeight = CSS_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->get_child(node->context, i); - child->line_index = linesCount; + child->line_index = lineCount; - 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 (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; } - 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) - ); - } + 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; } - - 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; + + 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. + 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 @@ -875,212 +952,300 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, float parentM 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; + } + + float remainingFreeSpaceAfterFlex = remainingFreeSpace; + + 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 deltaFreeSpace = 0; + 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; + 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; + deltaFlexGrowFactors -= flexGrowFactor; + } + } + } + + currentRelativeChild = currentRelativeChild->next_child; + } + + totalFlexShrinkScaledFactors += deltaFlexShrinkScaledFactors; + totalFlexGrowFactors += deltaFlexGrowFactors; + remainingFreeSpace += deltaFreeSpace; + remainingFreeSpaceAfterFlex = remainingFreeSpace; + + // Second pass: resolve the sizes of the flexible items + 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); + } + } + + remainingFreeSpaceAfterFlex -= updatedMainSize - childFlexBasis; + + 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; + } + } + + remainingFreeSpace = remainingFreeSpaceAfterFlex; + + // 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; } - // 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 + - getPaddingAndBorderAxis(currentFlexChild, mainAxis); - boundMainDim = boundAxis(currentFlexChild, mainAxis, baseMainDim); - - if (baseMainDim != boundMainDim) { - remainingMainDim -= boundMainDim; - totalFlexible -= currentFlexChild->style.flex; - } - - 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; - } - - 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) - ); - - 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; - } - - // And we recursively call the layout algorithm for this child - layoutNode(currentFlexChild, maxWidth, maxHeight, direction); - - child = currentFlexChild; - currentFlexChild = currentFlexChild->next_flex_child; - child->next_flex_child = NULL; - } - - // We use justifyContent to figure out how to allocate the remaining - // space available - } else if (justifyContent != CSS_JUSTIFY_FLEX_START) { + // 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); - } 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) { + // 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); } - - // 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.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) - ); - - // 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); - - layoutNode(child, maxWidth, maxHeight, direction); - } + 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 (isMainAxisRow) { + isCrossSizeDefinite = isStyleDimDefined(child, CSS_FLEX_DIRECTION_COLUMN); + childHeight = crossDim; + } else { + isCrossSizeDefinite = isStyleDimDefined(child, CSS_FLEX_DIRECTION_ROW); + childWidth = crossDim; + } + + // 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; @@ -1088,41 +1253,25 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, float parentM 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; @@ -1133,19 +1282,20 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, float parentM } 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; } @@ -1153,33 +1303,33 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, float parentM 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. + } } } @@ -1187,137 +1337,343 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth, float parentM } } - 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 (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 (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 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]; +} + + +// +// 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. + 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); + + 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_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]; + 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/src/Layout.h b/src/Layout.h index 1df58225..3af37e5f 100644 --- a/src/Layout.h +++ b/src/Layout.h @@ -44,6 +44,11 @@ typedef enum { 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 { @@ -79,7 +84,8 @@ typedef enum { 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 { @@ -87,20 +93,40 @@ typedef enum { 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 { @@ -116,6 +142,7 @@ typedef struct { 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]; @@ -143,8 +170,7 @@ struct css_node { 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); @@ -166,12 +192,8 @@ typedef enum { } 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/src/Layout.js b/src/Layout.js index e926592e..72a68360 100755 --- a/src/Layout.js +++ b/src/Layout.js @@ -1,4 +1,4 @@ -/** + /** * Copyright (c) 2014, Facebook, Inc. * All rights reserved. * @@ -8,9 +8,18 @@ */ var computeLayout = (function() { - + + var POSITIVE_FLEX_IS_AUTO = false; + + var gCurrentGenerationCount = 0; + var CSS_UNDEFINED; - + + var CSS_LEFT = 'left'; + var CSS_TOP = 'top'; + var CSS_RIGHT = 'right'; + var CSS_BOTTOM = 'bottom'; + var CSS_DIRECTION_INHERIT = 'inherit'; var CSS_DIRECTION_LTR = 'ltr'; var CSS_DIRECTION_RTL = 'rtl'; @@ -33,7 +42,10 @@ var computeLayout = (function() { var CSS_POSITION_RELATIVE = 'relative'; var CSS_POSITION_ABSOLUTE = 'absolute'; - + + var CSS_OVERFLOW_VISIBLE = 'visible'; + var CSS_OVERFLOW_HIDDEN = 'hidden'; + var CSS_MEASURE_MODE_UNDEFINED = 'undefined'; var CSS_MEASURE_MODE_EXACTLY = 'exactly'; var CSS_MEASURE_MODE_AT_MOST = 'at-most'; @@ -62,6 +74,12 @@ var computeLayout = (function() { 'column': 'height', 'column-reverse': 'height' }; + var measuredDim = { + 'row': 'measuredWidth', + 'row-reverse': 'measuredWidth', + 'column': 'measuredHeight', + 'column-reverse': 'measuredHeight' + }; // When transpiled to Java / C the node type has layout, children and style // properties. For the JavaScript version this function adds these properties @@ -95,7 +113,7 @@ var computeLayout = (function() { } function isUndefined(value) { - return value === undefined || isNaN(value); + return value === undefined || Number.isNaN(value); } function isRowDirection(flexDirection) { @@ -107,6 +125,46 @@ var computeLayout = (function() { return flexDirection === CSS_FLEX_DIRECTION_COLUMN || flexDirection === CSS_FLEX_DIRECTION_COLUMN_REVERSE; } + + function getFlex(node) { + if (node.style.flex === undefined) { + return 0; + } + return node.style.flex; + } + + function isFlexBasisAuto(node) { + if (POSITIVE_FLEX_IS_AUTO) { + // All flex values are auto. + return true; + } else { + // A flex value > 0 implies a basis of zero. + return getFlex(node) <= 0; + } + } + + function getFlexGrowFactor(node) { + // Flex grow is implied by positive values for flex. + if (getFlex(node) > 0) { + return getFlex(node); + } + return 0; + } + + function getFlexShrinkFactor(node) { + if (POSITIVE_FLEX_IS_AUTO) { + // A flex shrink factor of 1 is implied by non-zero values for flex. + if (getFlex(node) !== 0) { + return 1; + } + } else { + // A flex shrink factor of 1 is implied by negative values for flex. + if (getFlex(node) < 0) { + return 1; + } + } + return 0; + } function getLeadingMargin(node, axis) { if (node.style.marginStart !== undefined && isRowDirection(axis)) { @@ -264,10 +322,6 @@ var computeLayout = (function() { return getTrailingPadding(node, axis) + getTrailingBorder(node, axis); } - function getBorderAxis(node, axis) { - return getLeadingBorder(node, axis) + getTrailingBorder(node, axis); - } - function getMarginAxis(node, axis) { return getLeadingMargin(node, axis) + getTrailingMargin(node, axis); } @@ -347,13 +401,20 @@ var computeLayout = (function() { if (node.style.position) { return node.style.position; } - return 'relative'; + return CSS_POSITION_RELATIVE; + } + + function getOverflow(node) { + if (node.style.overflow) { + return node.style.overflow; + } + return CSS_OVERFLOW_VISIBLE; } function isFlex(node) { return ( getPositionType(node) === CSS_POSITION_RELATIVE && - node.style.flex > 0 + node.style.flex !== undefined && node.style.flex !== 0 ); } @@ -362,15 +423,15 @@ var computeLayout = (function() { } function getDimWithMargin(node, axis) { - return node.layout[dim[axis]] + getMarginAxis(node, axis); + return node.layout[measuredDim[axis]] + getMarginAxis(node, axis); } - - function isStyleDimDefined(node, axis) { + + function isStyleDimDefined(node, axis) { return node.style[dim[axis]] !== undefined && node.style[dim[axis]] >= 0; } - - function isLayoutDimDefined(node, axis) { - return node.layout[dim[axis]] !== undefined && node.layout[dim[axis]] >= 0; + + function isLayoutDimDefined(node, axis) { + return node.layout[measuredDim[axis]] !== undefined && node.layout[measuredDim[axis]] >= 0; } function isPosDefined(node, pos) { @@ -387,8 +448,8 @@ var computeLayout = (function() { } return 0; } - - function boundAxis(node, axis, value) { + + function boundAxisWithinMinAndMax(node, axis, value) { var min = { 'row': node.style.minWidth, 'row-reverse': node.style.minWidth, @@ -412,6 +473,13 @@ var computeLayout = (function() { } return boundValue; } + + function fminf(a, b) { + if (a < b) { + return a; + } + return b; + } function fmaxf(a, b) { if (a > b) { @@ -419,28 +487,18 @@ var computeLayout = (function() { } return b; } - - // When the user specifically sets a value for width or height - function setDimensionFromStyle(node, 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[dim[axis]] = fmaxf( - boundAxis(node, axis, node.style[dim[axis]]), - getPaddingAndBorderAxis(node, axis) - ); + + // Like boundAxisWithinMinAndMax but also ensures that the value doesn't go below the + // padding and border amount. + function boundAxis(node, axis, value) { + return fmaxf(boundAxisWithinMinAndMax(node, axis, value), getPaddingAndBorderAxis(node, axis)); } function setTrailingPosition(node, child, axis) { - child.layout[trailing[axis]] = node.layout[dim[axis]] - - child.layout[dim[axis]] - child.layout[pos[axis]]; + var size = (getPositionType(child) === CSS_POSITION_ABSOLUTE) ? + 0 : + child.layout[measuredDim[axis]]; + child.layout[trailing[axis]] = node.layout[measuredDim[axis]] - size - child.layout[pos[axis]]; } // If both left and right are defined, then use left. Otherwise return @@ -451,352 +509,392 @@ var computeLayout = (function() { } return -getPosition(node, trailing[axis]); } + + function setPosition(node, direction) { + var mainAxis = resolveAxis(getFlexDirection(node), direction); + var crossAxis = getCrossFlexDirection(mainAxis, direction); + + node.layout[leading[mainAxis]] = getLeadingMargin(node, mainAxis) + + getRelativePosition(node, mainAxis); + node.layout[trailing[mainAxis]] = getTrailingMargin(node, mainAxis) + + getRelativePosition(node, mainAxis); + node.layout[leading[crossAxis]] = getLeadingMargin(node, crossAxis) + + getRelativePosition(node, crossAxis); + node.layout[trailing[crossAxis]] = getTrailingMargin(node, crossAxis) + + getRelativePosition(node, crossAxis); + } + + function assert(condition, message) { + if (!condition) { + throw new Error(message); + } + } + + // + // 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. + // + function layoutNodeImpl(node, availableWidth, availableHeight, /*css_direction_t*/parentDirection, widthMeasureMode, heightMeasureMode, performLayout) { + 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'); + + var/*float*/ paddingAndBorderAxisRow = getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_ROW); + var/*float*/ paddingAndBorderAxisColumn = getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_COLUMN); + var/*float*/ marginAxisRow = getMarginAxis(node, CSS_FLEX_DIRECTION_ROW); + var/*float*/ marginAxisColumn = getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN); - function layoutNodeImpl(node, parentMaxWidth, parentMaxHeight, /*css_direction_t*/parentDirection) { + // Set the resolved resolution in the node's layout. var/*css_direction_t*/ direction = resolveDirection(node, parentDirection); - var/*(c)!css_flex_direction_t*//*(java)!int*/ mainAxis = resolveAxis(getFlexDirection(node), direction); - var/*(c)!css_flex_direction_t*//*(java)!int*/ crossAxis = getCrossFlexDirection(mainAxis, direction); - var/*(c)!css_flex_direction_t*//*(java)!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[leading[mainAxis]] += getLeadingMargin(node, mainAxis) + - getRelativePosition(node, mainAxis); - node.layout[trailing[mainAxis]] += getTrailingMargin(node, mainAxis) + - getRelativePosition(node, mainAxis); - node.layout[leading[crossAxis]] += getLeadingMargin(node, crossAxis) + - getRelativePosition(node, crossAxis); - node.layout[trailing[crossAxis]] += getTrailingMargin(node, crossAxis) + - getRelativePosition(node, crossAxis); - - // Inline immutable values from the target node to avoid excessive method - // invocations during the layout calculation. - var/*int*/ childCount = node.children.length; - var/*float*/ paddingAndBorderAxisResolvedRow = getPaddingAndBorderAxis(node, resolvedRowAxis); - var/*float*/ paddingAndBorderAxisColumn = getPaddingAndBorderAxis(node, CSS_FLEX_DIRECTION_COLUMN); - + // For content (text) nodes, determine the dimensions based on the text contents. if (isMeasureDefined(node)) { - var/*bool*/ isResolvedRowDimDefined = isLayoutDimDefined(node, resolvedRowAxis); + var/*float*/ innerWidth = availableWidth - marginAxisRow - paddingAndBorderAxisRow; + var/*float*/ innerHeight = availableHeight - marginAxisColumn - paddingAndBorderAxisColumn; + + if (widthMeasureMode === CSS_MEASURE_MODE_EXACTLY && heightMeasureMode === CSS_MEASURE_MODE_EXACTLY) { - var/*float*/ width = CSS_UNDEFINED; - var/*css_measure_mode_t*/ widthMode = CSS_MEASURE_MODE_UNDEFINED; - if (isStyleDimDefined(node, resolvedRowAxis)) { - width = node.style.width; - widthMode = CSS_MEASURE_MODE_EXACTLY; - } else if (isResolvedRowDimDefined) { - width = node.layout[dim[resolvedRowAxis]]; - widthMode = CSS_MEASURE_MODE_EXACTLY; + // Don't bother sizing the text if both dimensions are already defined. + node.layout.measuredWidth = boundAxis(node, CSS_FLEX_DIRECTION_ROW, availableWidth - marginAxisRow); + node.layout.measuredHeight = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, availableHeight - marginAxisColumn); + } else if (innerWidth <= 0) { + + // Don't bother sizing the text if there's no horizontal space. + node.layout.measuredWidth = boundAxis(node, CSS_FLEX_DIRECTION_ROW, 0); + node.layout.measuredHeight = 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; - } - var/*float*/ height = CSS_UNDEFINED; - var/*css_measure_mode_t*/ heightMode = CSS_MEASURE_MODE_UNDEFINED; - if (isStyleDimDefined(node, CSS_FLEX_DIRECTION_COLUMN)) { - height = node.style.height; - heightMode = CSS_MEASURE_MODE_EXACTLY; - } else if (isLayoutDimDefined(node, CSS_FLEX_DIRECTION_COLUMN)) { - height = node.layout[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. - var/*bool*/ isRowUndefined = !isStyleDimDefined(node, resolvedRowAxis) && !isResolvedRowDimDefined; - var/*bool*/ isColumnUndefined = !isStyleDimDefined(node, CSS_FLEX_DIRECTION_COLUMN) && - isUndefined(node.layout[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. var/*css_dim_t*/ measureDim = node.style.measure( /*(c)!node->context,*/ /*(java)!layoutContext.measureOutput,*/ - width, - widthMode, - height, - heightMode + innerWidth, + widthMeasureMode, + innerHeight, + heightMeasureMode ); - if (isRowUndefined) { - node.layout.width = measureDim.width + - paddingAndBorderAxisResolvedRow; - } - if (isColumnUndefined) { - node.layout.height = measureDim.height + - paddingAndBorderAxisColumn; - } + + node.layout.measuredWidth = boundAxis(node, CSS_FLEX_DIRECTION_ROW, + (widthMeasureMode === CSS_MEASURE_MODE_UNDEFINED || widthMeasureMode === CSS_MEASURE_MODE_AT_MOST) ? + measureDim.width + paddingAndBorderAxisRow : + availableWidth - marginAxisRow); + node.layout.measuredHeight = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, + (heightMeasureMode === CSS_MEASURE_MODE_UNDEFINED || heightMeasureMode === CSS_MEASURE_MODE_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. + var/*int*/ childCount = node.children.length; + if (childCount === 0) { + node.layout.measuredWidth = boundAxis(node, CSS_FLEX_DIRECTION_ROW, + (widthMeasureMode === CSS_MEASURE_MODE_UNDEFINED || widthMeasureMode === CSS_MEASURE_MODE_AT_MOST) ? + paddingAndBorderAxisRow : + availableWidth - marginAxisRow); + node.layout.measuredHeight = 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.measuredWidth = boundAxis(node, CSS_FLEX_DIRECTION_ROW, 0); + node.layout.measuredHeight = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, 0); + return; + } + + if (widthMeasureMode === CSS_MEASURE_MODE_AT_MOST && availableWidth <= 0) { + node.layout.measuredWidth = boundAxis(node, CSS_FLEX_DIRECTION_ROW, 0); + node.layout.measuredHeight = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, isUndefined(availableHeight) ? 0 : (availableHeight - marginAxisColumn)); + return; + } + + if (heightMeasureMode === CSS_MEASURE_MODE_AT_MOST && availableHeight <= 0) { + node.layout.measuredWidth = boundAxis(node, CSS_FLEX_DIRECTION_ROW, isUndefined(availableWidth) ? 0 : (availableWidth - marginAxisRow)); + node.layout.measuredHeight = 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.measuredWidth = boundAxis(node, CSS_FLEX_DIRECTION_ROW, availableWidth - marginAxisRow); + node.layout.measuredHeight = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, availableHeight - marginAxisColumn); return; } } + // STEP 1: CALCULATE VALUES FOR REMAINDER OF ALGORITHM + var/*(c)!css_flex_direction_t*//*(java)!int*/ mainAxis = resolveAxis(getFlexDirection(node), direction); + var/*(c)!css_flex_direction_t*//*(java)!int*/ crossAxis = getCrossFlexDirection(mainAxis, direction); + var/*bool*/ isMainAxisRow = isRowDirection(mainAxis); + var/*css_justify_t*/ justifyContent = getJustifyContent(node); var/*bool*/ isNodeFlexWrap = isFlexWrap(node); - var/*css_justify_t*/ justifyContent = getJustifyContent(node); + var/*css_node_t**/ firstAbsoluteChild = undefined; + var/*css_node_t**/ currentAbsoluteChild = undefined; var/*float*/ leadingPaddingAndBorderMain = getLeadingPaddingAndBorder(node, mainAxis); + var/*float*/ trailingPaddingAndBorderMain = getTrailingPaddingAndBorder(node, mainAxis); var/*float*/ leadingPaddingAndBorderCross = getLeadingPaddingAndBorder(node, crossAxis); var/*float*/ paddingAndBorderAxisMain = getPaddingAndBorderAxis(node, mainAxis); var/*float*/ paddingAndBorderAxisCross = getPaddingAndBorderAxis(node, crossAxis); + + var/*css_measure_mode_t*/ measureModeMainDim = isMainAxisRow ? widthMeasureMode : heightMeasureMode; + var/*css_measure_mode_t*/ measureModeCrossDim = isMainAxisRow ? heightMeasureMode : widthMeasureMode; - var/*bool*/ isMainDimDefined = isLayoutDimDefined(node, mainAxis); - var/*bool*/ isCrossDimDefined = isLayoutDimDefined(node, crossAxis); - var/*bool*/ isMainRowDirection = isRowDirection(mainAxis); + // STEP 2: DETERMINE AVAILABLE SIZE IN MAIN AND CROSS DIRECTIONS + var/*float*/ availableInnerWidth = availableWidth - marginAxisRow - paddingAndBorderAxisRow; + var/*float*/ availableInnerHeight = availableHeight - marginAxisColumn - paddingAndBorderAxisColumn; + var/*float*/ availableInnerMainDim = isMainAxisRow ? availableInnerWidth : availableInnerHeight; + var/*float*/ availableInnerCrossDim = isMainAxisRow ? availableInnerHeight : availableInnerWidth; - var/*int*/ i; - var/*int*/ ii; + // STEP 3: DETERMINE FLEX BASIS FOR EACH ITEM var/*css_node_t**/ child; - var/*(c)!css_flex_direction_t*//*(java)!int*/ axis; + var/*int*/ i; + var/*float*/ childWidth; + var/*float*/ childHeight; + var/*css_measure_mode_t*/ childWidthMeasureMode; + var/*css_measure_mode_t*/ childHeightMeasureMode; + for (i = 0; i < childCount; i++) { + child = node.children[i]; - var/*css_node_t**/ firstAbsoluteChild = null; - var/*css_node_t**/ currentAbsoluteChild = null; + if (performLayout) { + // Set the initial position (relative to the parent). + var/*css_direction_t*/ 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 (getPositionType(child) === CSS_POSITION_ABSOLUTE) { - var/*float*/ definedMainDim = CSS_UNDEFINED; - if (isMainDimDefined) { - definedMainDim = node.layout[dim[mainAxis]] - paddingAndBorderAxisMain; + // Store a private linked list of absolutely positioned children + // so that we can efficiently traverse them later. + if (firstAbsoluteChild === undefined) { + firstAbsoluteChild = child; + } + if (currentAbsoluteChild !== undefined) { + currentAbsoluteChild.nextChild = child; + } + currentAbsoluteChild = child; + child.nextChild = undefined; + } else { + + if (isMainAxisRow && isStyleDimDefined(child, CSS_FLEX_DIRECTION_ROW)) { + + // The width is definite, so use that as the flex basis. + child.layout.flexBasis = fmaxf(child.style.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.flexBasis = fmaxf(child.style.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.flexBasis = 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.width + getMarginAxis(child, CSS_FLEX_DIRECTION_ROW); + childWidthMeasureMode = CSS_MEASURE_MODE_EXACTLY; + } + if (isStyleDimDefined(child, CSS_FLEX_DIRECTION_COLUMN)) { + childHeight = child.style.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 (getOverflow(node) === 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.flexBasis = fmaxf(isMainAxisRow ? child.layout.measuredWidth : child.layout.measuredHeight, getPaddingAndBorderAxis(child, mainAxis)); + } + } } - // We want to execute the next two loops one per line with flex-wrap - var/*int*/ startLine = 0; - var/*int*/ endLine = 0; - // var/*int*/ nextOffset = 0; - var/*int*/ alreadyComputedNextLayout = 0; - // We aggregate the total dimensions of the container in those two variables - var/*float*/ linesCrossDim = 0; - var/*float*/ linesMainDim = 0; - var/*int*/ linesCount = 0; - while (endLine < 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. + var/*int*/ startOfLineIndex = 0; + var/*int*/ endOfLineIndex = 0; + + // Number of lines. + var/*int*/ lineCount = 0; + + // Accumulated cross dimensions of all lines so far. + var/*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. - var/*float*/ mainContentDim = 0; + // Max main dimension of all the lines. + var/*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. - var/*int*/ flexibleChildrenCount = 0; - var/*float*/ totalFlexible = 0; - var/*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. + var/*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 . - var/*bool*/ isSimpleStackMain = - (isMainDimDefined && justifyContent === CSS_JUSTIFY_FLEX_START) || - (!isMainDimDefined && justifyContent !== CSS_JUSTIFY_CENTER); - var/*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. + var/*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 . - var/*bool*/ isSimpleStackCross = true; - var/*int*/ firstComplexCross = childCount; + var/*float*/ totalFlexGrowFactors = 0; + var/*float*/ totalFlexShrinkScaledFactors = 0; - var/*css_node_t**/ firstFlexChild = null; - var/*css_node_t**/ currentFlexChild = null; + i = startOfLineIndex; - var/*float*/ mainDim = leadingPaddingAndBorderMain; - var/*float*/ crossDim = 0; + // Maintain a linked list of the child nodes that can shrink and/or grow. + var/*css_node_t**/ firstRelativeChild = undefined; + var/*css_node_t**/ currentRelativeChild = undefined; - var/*float*/ maxWidth = CSS_UNDEFINED; - var/*float*/ maxHeight = CSS_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.children[i]; - child.lineIndex = linesCount; + child.lineIndex = lineCount; - child.nextAbsoluteChild = null; - child.nextFlexChild = null; - - var/*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 && - getPositionType(child) === CSS_POSITION_RELATIVE && - isCrossDimDefined && - !isStyleDimDefined(child, crossAxis)) { - child.layout[dim[crossAxis]] = fmaxf( - boundAxis(child, crossAxis, node.layout[dim[crossAxis]] - - paddingAndBorderAxisCross - getMarginAxis(child, crossAxis)), - // You never want to go smaller than padding - getPaddingAndBorderAxis(child, crossAxis) - ); - } else if (getPositionType(child) === 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 (getPositionType(child) !== CSS_POSITION_ABSOLUTE) { + var/*float*/ outerFlexBasis = child.layout.flexBasis + 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; } - 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 (isLayoutDimDefined(node, axis) && - !isStyleDimDefined(child, axis) && - isPosDefined(child, leading[axis]) && - isPosDefined(child, trailing[axis])) { - child.layout[dim[axis]] = fmaxf( - boundAxis(child, axis, node.layout[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) - ); - } + 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.flexBasis; } + + // Store a private linked list of children that need to be layed out. + if (firstRelativeChild === undefined) { + firstRelativeChild = child; + } + if (currentRelativeChild !== undefined) { + currentRelativeChild.nextChild = child; + } + currentRelativeChild = child; + child.nextChild = undefined; } - - var/*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.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 = getPaddingAndBorderAxis(child, mainAxis) + - getMarginAxis(child, mainAxis); - - } else { - maxWidth = CSS_UNDEFINED; - maxHeight = CSS_UNDEFINED; - - if (!isMainRowDirection) { - if (isLayoutDimDefined(node, resolvedRowAxis)) { - maxWidth = node.layout[dim[resolvedRowAxis]] - - paddingAndBorderAxisResolvedRow; - } else { - maxWidth = parentMaxWidth - - getMarginAxis(node, resolvedRowAxis) - - paddingAndBorderAxisResolvedRow; - } - } else { - if (isLayoutDimDefined(node, CSS_FLEX_DIRECTION_COLUMN)) { - maxHeight = node.layout[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(/*(java)!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 (getPositionType(child) === CSS_POSITION_RELATIVE) { - nonFlexibleChildrenCount++; - // At this point we know the final size and margin of the element. - nextContentDim = getDimWithMargin(child, mainAxis); - } - } - - // The element we are about to add would make us go to the next line - if (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 && - (getPositionType(child) !== 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 && - (getPositionType(child) !== 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[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[pos[crossAxis]] += linesCrossDim + leadingPaddingAndBorderCross; - if (isCrossDimDefined) { - setTrailingPosition(node, child, 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. + var/*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 @@ -804,212 +902,300 @@ var computeLayout = (function() { var/*float*/ leadingMainDim = 0; var/*float*/ betweenMainDim = 0; - // The remaining available space that needs to be allocated - var/*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. + var/*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; + } + + var/*float*/ remainingFreeSpaceAfterFlex = remainingFreeSpace; + + if (!canSkipFlex) { + var/*float*/ childFlexBasis; + var/*float*/ flexShrinkScaledFactor; + var/*float*/ flexGrowFactor; + var/*float*/ baseMainSize; + var/*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 + var/*float*/ deltaFreeSpace = 0; + var/*float*/ deltaFlexShrinkScaledFactors = 0; + var/*float*/ deltaFlexGrowFactors = 0; + currentRelativeChild = firstRelativeChild; + while (currentRelativeChild !== undefined) { + 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; + 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; + deltaFlexGrowFactors -= flexGrowFactor; + } + } + } + + currentRelativeChild = currentRelativeChild.nextChild; + } + + totalFlexShrinkScaledFactors += deltaFlexShrinkScaledFactors; + totalFlexGrowFactors += deltaFlexGrowFactors; + remainingFreeSpace += deltaFreeSpace; + remainingFreeSpaceAfterFlex = remainingFreeSpace; + + // Second pass: resolve the sizes of the flexible items + currentRelativeChild = firstRelativeChild; + while (currentRelativeChild !== undefined) { + childFlexBasis = currentRelativeChild.layout.flexBasis; + var/*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); + } + } + + remainingFreeSpaceAfterFlex -= updatedMainSize - childFlexBasis; + + 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.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.width + getMarginAxis(currentRelativeChild, CSS_FLEX_DIRECTION_ROW); + childWidthMeasureMode = CSS_MEASURE_MODE_EXACTLY; + } + } + + var/*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.nextChild; + } + } + + remainingFreeSpace = remainingFreeSpaceAfterFlex; + + // 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; } - // If there are flexible children in the mix, they are going to fill the - // remaining space - if (flexibleChildrenCount !== 0) { - var/*float*/ flexibleMainDim = remainingMainDim / totalFlexible; - var/*float*/ baseMainDim; - var/*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 + - getPaddingAndBorderAxis(currentFlexChild, 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[dim[mainAxis]] = boundAxis(currentFlexChild, mainAxis, - flexibleMainDim * currentFlexChild.style.flex + - getPaddingAndBorderAxis(currentFlexChild, mainAxis) - ); - - maxWidth = CSS_UNDEFINED; - if (isLayoutDimDefined(node, resolvedRowAxis)) { - maxWidth = node.layout[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[dim[CSS_FLEX_DIRECTION_COLUMN]] - - paddingAndBorderAxisColumn; - } else if (isMainRowDirection) { - maxHeight = parentMaxHeight - - getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN) - - paddingAndBorderAxisColumn; - } - - // And we recursively call the layout algorithm for this child - layoutNode(/*(java)!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 !== CSS_JUSTIFY_FLEX_START) { + // 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 + var/*float*/ mainDim = leadingPaddingAndBorderMain + leadingMainDim; + var/*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.children[i]; if (getPositionType(child) === CSS_POSITION_ABSOLUTE && isPosDefined(child, leading[mainAxis])) { - // In case the child is position absolute and has left/top being - // defined, we override the position to whatever the user said - // (and margin/border). - child.layout[pos[mainAxis]] = getPosition(child, leading[mainAxis]) + - 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[pos[mainAxis]] += mainDim; - - // Define the trailing position accordingly. - if (isMainDimDefined) { - setTrailingPosition(node, 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[pos[mainAxis]] = getPosition(child, leading[mainAxis]) + + getLeadingBorder(node, mainAxis) + + getLeadingMargin(child, 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[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 (getPositionType(child) === CSS_POSITION_RELATIVE) { - // The main dimension is the sum of all the elements dimension plus - // the spacing. - mainDim += betweenMainDim + getDimWithMargin(child, mainAxis); - // The cross dimension is the max of the elements dimension since there - // can only be one element in that cross dimension. - crossDim = fmaxf(crossDim, 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.flexBasis; + 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)); + } } } } - var/*float*/ containerCrossAxis = node.layout[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; + + var/*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.children[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 (getPositionType(child) === CSS_POSITION_ABSOLUTE && - isPosDefined(child, leading[crossAxis])) { - // In case the child is absolutely positionned and has a - // top/left/bottom/right being set, we override all the previously - // computed positions to set it correctly. - child.layout[pos[crossAxis]] = getPosition(child, leading[crossAxis]) + - getLeadingBorder(node, crossAxis) + - getLeadingMargin(child, crossAxis); + // Clamp to the min/max size specified on the container. + crossDim = boundAxis(node, crossAxis, crossDim + paddingAndBorderAxisCross) - paddingAndBorderAxisCross; - } else { - var/*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.children[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 (getPositionType(child) === CSS_POSITION_RELATIVE) { - /*eslint-disable */ - // This variable is intentionally re-defined as the code is transpiled to a block scope language + if (getPositionType(child) === 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[pos[crossAxis]] = getPosition(child, leading[crossAxis]) + + getLeadingBorder(node, crossAxis) + + getLeadingMargin(child, crossAxis); + } else { + child.layout[pos[crossAxis]] = leadingPaddingAndBorderCross + + getLeadingMargin(child, crossAxis); + } + } else { + var/*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 var/*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)) { - var/*float*/ dimCrossAxis = child.layout[dim[crossAxis]]; - child.layout[dim[crossAxis]] = fmaxf( - boundAxis(child, crossAxis, containerCrossAxis - - paddingAndBorderAxisCross - getMarginAxis(child, crossAxis)), - // You never want to go smaller than padding - getPaddingAndBorderAxis(child, crossAxis) - ); - - // If the size has changed, and this child has children we need to re-layout this child - if (dimCrossAxis != child.layout[dim[crossAxis]] && child.children.length > 0) { - // Reset child margins before re-layout as they are added back in layoutNode and would be doubled - child.layout[leading[mainAxis]] -= getLeadingMargin(child, mainAxis) + - getRelativePosition(child, mainAxis); - child.layout[trailing[mainAxis]] -= getTrailingMargin(child, mainAxis) + - getRelativePosition(child, mainAxis); - child.layout[leading[crossAxis]] -= getLeadingMargin(child, crossAxis) + - getRelativePosition(child, crossAxis); - child.layout[trailing[crossAxis]] -= getTrailingMargin(child, crossAxis) + - getRelativePosition(child, crossAxis); - - layoutNode(/*(java)!layoutContext, */child, maxWidth, maxHeight, direction); - } + childWidth = child.layout.measuredWidth + getMarginAxis(child, CSS_FLEX_DIRECTION_ROW); + childHeight = child.layout.measuredHeight + getMarginAxis(child, CSS_FLEX_DIRECTION_COLUMN); + var/*bool*/ isCrossSizeDefinite = false; + + if (isMainAxisRow) { + isCrossSizeDefinite = isStyleDimDefined(child, CSS_FLEX_DIRECTION_COLUMN); + childHeight = crossDim; + } else { + isCrossSizeDefinite = isStyleDimDefined(child, CSS_FLEX_DIRECTION_ROW); + childWidth = crossDim; + } + + // 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. - var/*float*/ remainingCrossDim = containerCrossAxis - - paddingAndBorderAxisCross - getDimWithMargin(child, crossAxis); + var/*float*/ remainingCrossDim = containerCrossAxis - getDimWithMargin(child, crossAxis); if (alignItem === CSS_ALIGN_CENTER) { leadingCrossDim += remainingCrossDim / 2; @@ -1017,41 +1203,25 @@ var computeLayout = (function() { leadingCrossDim += remainingCrossDim; } } - } - // And we apply the position - child.layout[pos[crossAxis]] += linesCrossDim + leadingCrossDim; - - // Define the trailing position accordingly. - if (isCrossDimDefined) { - setTrailingPosition(node, child, crossAxis); + // And we apply the position + child.layout[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) { - var/*float*/ nodeCrossAxisInnerSize = node.layout[dim[crossAxis]] - - paddingAndBorderAxisCross; - var/*float*/ remainingAlignContentDim = nodeCrossAxisInnerSize - linesCrossDim; + // STEP 8: MULTI-LINE CONTENT ALIGNMENT + if (lineCount > 1 && performLayout && !isUndefined(availableInnerCrossDim)) { + var/*float*/ remainingAlignContentDim = availableInnerCrossDim - totalLineCrossDim; var/*float*/ crossDimLead = 0; var/*float*/ currentLead = leadingPaddingAndBorderCross; @@ -1062,19 +1232,20 @@ var computeLayout = (function() { } 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); } } var/*int*/ endIndex = 0; - for (i = 0; i < linesCount; ++i) { + for (i = 0; i < lineCount; ++i) { var/*int*/ startIndex = endIndex; + var/*int*/ j; // compute the line's height and find the endIndex var/*float*/ lineHeight = 0; - for (ii = startIndex; ii < childCount; ++ii) { - child = node.children[ii]; + for (j = startIndex; j < childCount; ++j) { + child = node.children[j]; if (getPositionType(child) !== CSS_POSITION_RELATIVE) { continue; } @@ -1082,33 +1253,33 @@ var computeLayout = (function() { break; } if (isLayoutDimDefined(child, crossAxis)) { - lineHeight = fmaxf( - lineHeight, - child.layout[dim[crossAxis]] + getMarginAxis(child, crossAxis) - ); + lineHeight = fmaxf(lineHeight, + child.layout[measuredDim[crossAxis]] + getMarginAxis(child, crossAxis)); } } - endIndex = ii; + endIndex = j; lineHeight += crossDimLead; - for (ii = startIndex; ii < endIndex; ++ii) { - child = node.children[ii]; - if (getPositionType(child) !== CSS_POSITION_RELATIVE) { - continue; - } + if (performLayout) { + for (j = startIndex; j < endIndex; ++j) { + child = node.children[j]; + if (getPositionType(child) !== CSS_POSITION_RELATIVE) { + continue; + } - var/*css_align_t*/ alignContentAlignItem = getAlignItem(node, child); - if (alignContentAlignItem === CSS_ALIGN_FLEX_START) { - child.layout[pos[crossAxis]] = currentLead + getLeadingMargin(child, crossAxis); - } else if (alignContentAlignItem === CSS_ALIGN_FLEX_END) { - child.layout[pos[crossAxis]] = currentLead + lineHeight - getTrailingMargin(child, crossAxis) - child.layout[dim[crossAxis]]; - } else if (alignContentAlignItem === CSS_ALIGN_CENTER) { - var/*float*/ childHeight = child.layout[dim[crossAxis]]; - child.layout[pos[crossAxis]] = currentLead + (lineHeight - childHeight) / 2; - } else if (alignContentAlignItem === CSS_ALIGN_STRETCH) { - child.layout[pos[crossAxis]] = currentLead + getLeadingMargin(child, crossAxis); - // TODO(prenaux): Correctly set the height of items with undefined - // (auto) crossAxis dimension. + var/*css_align_t*/ alignContentAlignItem = getAlignItem(node, child); + if (alignContentAlignItem === CSS_ALIGN_FLEX_START) { + child.layout[pos[crossAxis]] = currentLead + getLeadingMargin(child, crossAxis); + } else if (alignContentAlignItem === CSS_ALIGN_FLEX_END) { + child.layout[pos[crossAxis]] = currentLead + lineHeight - getTrailingMargin(child, crossAxis) - child.layout[measuredDim[crossAxis]]; + } else if (alignContentAlignItem === CSS_ALIGN_CENTER) { + childHeight = child.layout[measuredDim[crossAxis]]; + child.layout[pos[crossAxis]] = currentLead + (lineHeight - childHeight) / 2; + } else if (alignContentAlignItem === CSS_ALIGN_STRETCH) { + child.layout[pos[crossAxis]] = currentLead + getLeadingMargin(child, crossAxis); + // TODO(prenaux): Correctly set the height of items with indefinite + // (auto) crossAxis dimension. + } } } @@ -1116,138 +1287,266 @@ var computeLayout = (function() { } } - var/*bool*/ needsMainTrailingPos = false; - var/*bool*/ needsCrossTrailingPos = false; + // STEP 9: COMPUTING FINAL DIMENSIONS + node.layout.measuredWidth = boundAxis(node, CSS_FLEX_DIRECTION_ROW, availableWidth - marginAxisRow); + node.layout.measuredHeight = 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[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[measuredDim[mainAxis]] = boundAxis(node, mainAxis, maxLineMainDim); + } else if (measureModeMainDim === CSS_MEASURE_MODE_AT_MOST) { + node.layout[measuredDim[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[measuredDim[crossAxis]] = boundAxis(node, crossAxis, totalLineCrossDim + paddingAndBorderAxisCross); + } else if (measureModeCrossDim === CSS_MEASURE_MODE_AT_MOST) { + node.layout[measuredDim[crossAxis]] = fmaxf( + fminf(availableInnerCrossDim + paddingAndBorderAxisCross, + boundAxisWithinMinAndMax(node, crossAxis, totalLineCrossDim + paddingAndBorderAxisCross)), + paddingAndBorderAxisCross); + } + + // STEP 10: SETTING TRAILING POSITIONS FOR CHILDREN + if (performLayout) { + var/*bool*/ needsMainTrailingPos = false; + var/*bool*/ needsCrossTrailingPos = false; if (mainAxis === CSS_FLEX_DIRECTION_ROW_REVERSE || mainAxis === CSS_FLEX_DIRECTION_COLUMN_REVERSE) { needsMainTrailingPos = true; } - } - - if (!isCrossDimDefined) { - node.layout[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.children[i]; + // Set trailing position if necessary. + if (needsMainTrailingPos || needsCrossTrailingPos) { + for (i = 0; i < childCount; ++i) { + child = node.children[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; + while (currentAbsoluteChild !== undefined) { + // 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[dim[axis]] = fmaxf( - boundAxis(currentAbsoluteChild, axis, node.layout[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.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.measuredWidth - + (getLeadingBorder(node, CSS_FLEX_DIRECTION_ROW) + getTrailingBorder(node, CSS_FLEX_DIRECTION_ROW)) - + (currentAbsoluteChild.style[CSS_LEFT] + currentAbsoluteChild.style[CSS_RIGHT]); + childWidth = boundAxis(currentAbsoluteChild, CSS_FLEX_DIRECTION_ROW, childWidth); + } + } + + if (isStyleDimDefined(currentAbsoluteChild, CSS_FLEX_DIRECTION_COLUMN)) { + childHeight = currentAbsoluteChild.style.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.measuredHeight - + (getLeadingBorder(node, CSS_FLEX_DIRECTION_COLUMN) + getTrailingBorder(node, CSS_FLEX_DIRECTION_COLUMN)) - + (currentAbsoluteChild.style[CSS_TOP] + currentAbsoluteChild.style[CSS_BOTTOM]); + childHeight = boundAxis(currentAbsoluteChild, CSS_FLEX_DIRECTION_COLUMN, childHeight); + } } - if (isPosDefined(currentAbsoluteChild, trailing[axis]) && - !isPosDefined(currentAbsoluteChild, leading[axis])) { - currentAbsoluteChild.layout[leading[axis]] = - node.layout[dim[axis]] - - currentAbsoluteChild.layout[dim[axis]] - - getPosition(currentAbsoluteChild, trailing[axis]); + // 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 (getOverflow(node) === 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.measuredWidth + getMarginAxis(currentAbsoluteChild, CSS_FLEX_DIRECTION_ROW); + childHeight = currentAbsoluteChild.layout.measuredHeight + 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[leading[CSS_FLEX_DIRECTION_ROW]] = + node.layout[measuredDim[CSS_FLEX_DIRECTION_ROW]] - + currentAbsoluteChild.layout[measuredDim[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[leading[CSS_FLEX_DIRECTION_COLUMN]] = + node.layout[measuredDim[CSS_FLEX_DIRECTION_COLUMN]] - + currentAbsoluteChild.layout[measuredDim[CSS_FLEX_DIRECTION_COLUMN]] - + getPosition(currentAbsoluteChild, trailing[CSS_FLEX_DIRECTION_COLUMN]); } } - child = currentAbsoluteChild; - currentAbsoluteChild = currentAbsoluteChild.nextAbsoluteChild; - child.nextAbsoluteChild = null; + currentAbsoluteChild = currentAbsoluteChild.nextChild; } } + + // + // 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 + // + function layoutNodeInternal(node, availableWidth, availableHeight, parentDirection, + widthMeasureMode, heightMeasureMode, performLayout, reason) { + var layout = node.layout; - function layoutNode(node, parentMaxWidth, parentMaxHeight, parentDirection) { - node.shouldUpdate = true; + var needToVisitNode = (node.isDirty && layout.generationCount !== gCurrentGenerationCount) || + layout.lastParentDirection !== parentDirection; - var direction = node.style.direction || CSS_DIRECTION_LTR; - var skipLayout = - !node.isDirty && - node.lastLayout && - node.lastLayout.requestedHeight === node.layout.height && - node.lastLayout.requestedWidth === node.layout.width && - node.lastLayout.parentMaxWidth === parentMaxWidth && - node.lastLayout.parentMaxHeight === parentMaxHeight && - node.lastLayout.direction === direction; - - if (skipLayout) { - node.layout.width = node.lastLayout.width; - node.layout.height = node.lastLayout.height; - node.layout.top = node.lastLayout.top; - node.layout.left = node.lastLayout.left; - } else { - if (!node.lastLayout) { - node.lastLayout = {}; + if (needToVisitNode) { + // Invalidate the cached results. + if (layout.cachedMeasurements !== undefined) { + layout.cachedMeasurements = []; } + if (layout.cachedLayout !== undefined) { + layout.cachedLayout.widthMeasureMode = undefined; + layout.cachedLayout.heightMeasureMode = undefined; + } + } - node.lastLayout.requestedWidth = node.layout.width; - node.lastLayout.requestedHeight = node.layout.height; - node.lastLayout.parentMaxWidth = parentMaxWidth; - node.lastLayout.parentMaxHeight = parentMaxHeight; - node.lastLayout.direction = direction; - - // Reset child layouts - node.children.forEach(function(child) { - child.layout.width = undefined; - child.layout.height = undefined; - child.layout.top = 0; - child.layout.left = 0; - }); - - layoutNodeImpl(node, parentMaxWidth, parentMaxHeight, parentDirection); - - node.lastLayout.width = node.layout.width; - node.lastLayout.height = node.layout.height; - node.lastLayout.top = node.layout.top; - node.lastLayout.left = node.layout.left; + var cachedResults; + + // 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. + if (performLayout) { + if (layout.cachedLayout && + layout.cachedLayout.availableWidth === availableWidth && + layout.cachedLayout.availableHeight === availableHeight && + layout.cachedLayout.widthMeasureMode === widthMeasureMode && + layout.cachedLayout.heightMeasureMode === heightMeasureMode) { + cachedResults = layout.cachedLayout; + } + } else if (layout.cachedMeasurements) { + for (var i = 0, len = layout.cachedMeasurements.length; i < len; i++) { + if (layout.cachedMeasurements[i].availableWidth === availableWidth && + layout.cachedMeasurements[i].availableHeight === availableHeight && + layout.cachedMeasurements[i].widthMeasureMode === widthMeasureMode && + layout.cachedMeasurements[i].heightMeasureMode === heightMeasureMode) { + cachedResults = layout.cachedMeasurements[i]; + break; + } + } + } + + if (!needToVisitNode && cachedResults !== undefined) { + layout.measureWidth = cachedResults.computedWidth; + layout.measureHeight = cachedResults.computedHeight; + } else { + layoutNodeImpl(node, availableWidth, availableHeight, parentDirection, widthMeasureMode, heightMeasureMode, performLayout); + layout.lastParentDirection = parentDirection; + + if (cachedResults === undefined) { + var newCacheEntry; + if (performLayout) { + // Use the single layout cache entry. + if (layout.cachedLayout === undefined) { + layout.cachedLayout = {}; + } + newCacheEntry = layout.cachedLayout; + } else { + // Allocate a new measurement cache entry. + if (layout.cachedMeasurements === undefined) { + layout.cachedMeasurements = []; + } + newCacheEntry = {}; + layout.cachedMeasurements.push(newCacheEntry); + } + + newCacheEntry.availableWidth = availableWidth; + newCacheEntry.availableHeight = availableHeight; + newCacheEntry.widthMeasureMode = widthMeasureMode; + newCacheEntry.heightMeasureMode = heightMeasureMode; + newCacheEntry.computedWidth = layout.measuredWidth; + newCacheEntry.computedHeight = layout.measuredHeight; + } + } + + if (performLayout) { + node.layout.width = node.layout.measuredWidth; + node.layout.height = node.layout.measuredHeight; + layout.shouldUpdate = true; + } + + layout.generationCount = gCurrentGenerationCount; + return (needToVisitNode || cachedResults === undefined); + } + + function layoutNode(node, availableWidth, availableHeight, 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.width + getMarginAxis(node, CSS_FLEX_DIRECTION_ROW); + } + if (isUndefined(availableHeight) && isStyleDimDefined(node, CSS_FLEX_DIRECTION_COLUMN)) { + availableHeight = node.style.height + getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN); + } + + var widthMeasureMode = isUndefined(availableWidth) ? CSS_MEASURE_MODE_UNDEFINED : CSS_MEASURE_MODE_EXACTLY; + var 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); } } diff --git a/src/__tests__/Layout-test.c b/src/__tests__/Layout-test.c index 13c449ea..7a9b913d 100644 --- a/src/__tests__/Layout-test.c +++ b/src/__tests__/Layout-test.c @@ -4070,6 +4070,7 @@ int main() node_1->context = "measureWithRatio2"; node_1 = node_0->get_child(node_0->context, 1); node_1->style.flex_direction = CSS_FLEX_DIRECTION_ROW; + node_1->style.overflow = CSS_OVERFLOW_HIDDEN; node_1->style.dimensions[CSS_HEIGHT] = 100; init_css_node_children(node_1, 2); { @@ -4503,7 +4504,7 @@ int main() node_1 = node_0->get_child(node_0->context, 0); node_1->layout.position[CSS_TOP] = 20; node_1->layout.position[CSS_LEFT] = 20; - node_1->layout.dimensions[CSS_WIDTH] = 100; + node_1->layout.dimensions[CSS_WIDTH] = 60; node_1->layout.dimensions[CSS_HEIGHT] = 36; init_css_node_children(node_1, 1); { @@ -8004,6 +8005,1047 @@ int main() test("should center items correctly inside a stretched layout", root_node, root_layout); } + { + css_node_t *root_node = new_test_css_node(); + { + css_node_t *node_0 = root_node; + node_0->style.dimensions[CSS_WIDTH] = 100; + node_0->style.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 1); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->style.flex = -1; + node_1->style.dimensions[CSS_WIDTH] = 100; + init_css_node_children(node_1, 1); + { + css_node_t *node_2; + node_2 = node_1->get_child(node_1->context, 0); + node_2->style.dimensions[CSS_WIDTH] = 100; + node_2->style.dimensions[CSS_HEIGHT] = 25; + } + } + } + + css_node_t *root_layout = new_test_css_node(); + { + css_node_t *node_0 = root_layout; + node_0->layout.position[CSS_TOP] = 0; + node_0->layout.position[CSS_LEFT] = 0; + node_0->layout.dimensions[CSS_WIDTH] = 100; + node_0->layout.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 1); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 0; + node_1->layout.dimensions[CSS_WIDTH] = 100; + node_1->layout.dimensions[CSS_HEIGHT] = 25; + init_css_node_children(node_1, 1); + { + css_node_t *node_2; + node_2 = node_1->get_child(node_1->context, 0); + node_2->layout.position[CSS_TOP] = 0; + node_2->layout.position[CSS_LEFT] = 0; + node_2->layout.dimensions[CSS_WIDTH] = 100; + node_2->layout.dimensions[CSS_HEIGHT] = 25; + } + } + } + + test("should not shrink column node when there is space left over", root_node, root_layout); + } + + { + css_node_t *root_node = new_test_css_node(); + { + css_node_t *node_0 = root_node; + node_0->style.dimensions[CSS_WIDTH] = 100; + node_0->style.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 1); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->style.flex = -1; + node_1->style.dimensions[CSS_WIDTH] = 100; + init_css_node_children(node_1, 1); + { + css_node_t *node_2; + node_2 = node_1->get_child(node_1->context, 0); + node_2->style.dimensions[CSS_WIDTH] = 100; + node_2->style.dimensions[CSS_HEIGHT] = 200; + } + } + } + + css_node_t *root_layout = new_test_css_node(); + { + css_node_t *node_0 = root_layout; + node_0->layout.position[CSS_TOP] = 0; + node_0->layout.position[CSS_LEFT] = 0; + node_0->layout.dimensions[CSS_WIDTH] = 100; + node_0->layout.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 1); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 0; + node_1->layout.dimensions[CSS_WIDTH] = 100; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_1, 1); + { + css_node_t *node_2; + node_2 = node_1->get_child(node_1->context, 0); + node_2->layout.position[CSS_TOP] = 0; + node_2->layout.position[CSS_LEFT] = 0; + node_2->layout.dimensions[CSS_WIDTH] = 100; + node_2->layout.dimensions[CSS_HEIGHT] = 200; + } + } + } + + test("should shrink column node when there is not any space left over", root_node, root_layout); + } + + { + css_node_t *root_node = new_test_css_node(); + { + css_node_t *node_0 = root_node; + node_0->style.dimensions[CSS_WIDTH] = 100; + node_0->style.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 3); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->style.dimensions[CSS_WIDTH] = 100; + node_1->style.dimensions[CSS_HEIGHT] = 25; + node_1 = node_0->get_child(node_0->context, 1); + node_1->style.flex = -1; + node_1->style.dimensions[CSS_WIDTH] = 100; + init_css_node_children(node_1, 1); + { + css_node_t *node_2; + node_2 = node_1->get_child(node_1->context, 0); + node_2->style.dimensions[CSS_WIDTH] = 100; + node_2->style.dimensions[CSS_HEIGHT] = 30; + } + node_1 = node_0->get_child(node_0->context, 2); + node_1->style.dimensions[CSS_WIDTH] = 100; + node_1->style.dimensions[CSS_HEIGHT] = 15; + } + } + + css_node_t *root_layout = new_test_css_node(); + { + css_node_t *node_0 = root_layout; + node_0->layout.position[CSS_TOP] = 0; + node_0->layout.position[CSS_LEFT] = 0; + node_0->layout.dimensions[CSS_WIDTH] = 100; + node_0->layout.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 3); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 0; + node_1->layout.dimensions[CSS_WIDTH] = 100; + node_1->layout.dimensions[CSS_HEIGHT] = 25; + node_1 = node_0->get_child(node_0->context, 1); + node_1->layout.position[CSS_TOP] = 25; + node_1->layout.position[CSS_LEFT] = 0; + node_1->layout.dimensions[CSS_WIDTH] = 100; + node_1->layout.dimensions[CSS_HEIGHT] = 30; + init_css_node_children(node_1, 1); + { + css_node_t *node_2; + node_2 = node_1->get_child(node_1->context, 0); + node_2->layout.position[CSS_TOP] = 0; + node_2->layout.position[CSS_LEFT] = 0; + node_2->layout.dimensions[CSS_WIDTH] = 100; + node_2->layout.dimensions[CSS_HEIGHT] = 30; + } + node_1 = node_0->get_child(node_0->context, 2); + node_1->layout.position[CSS_TOP] = 55; + node_1->layout.position[CSS_LEFT] = 0; + node_1->layout.dimensions[CSS_WIDTH] = 100; + node_1->layout.dimensions[CSS_HEIGHT] = 15; + } + } + + test("should not shrink column node with siblings when there is space left over", root_node, root_layout); + } + + { + css_node_t *root_node = new_test_css_node(); + { + css_node_t *node_0 = root_node; + node_0->style.dimensions[CSS_WIDTH] = 100; + node_0->style.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 3); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->style.dimensions[CSS_WIDTH] = 100; + node_1->style.dimensions[CSS_HEIGHT] = 25; + node_1 = node_0->get_child(node_0->context, 1); + node_1->style.flex = -1; + node_1->style.dimensions[CSS_WIDTH] = 100; + init_css_node_children(node_1, 1); + { + css_node_t *node_2; + node_2 = node_1->get_child(node_1->context, 0); + node_2->style.dimensions[CSS_WIDTH] = 100; + node_2->style.dimensions[CSS_HEIGHT] = 80; + } + node_1 = node_0->get_child(node_0->context, 2); + node_1->style.dimensions[CSS_WIDTH] = 100; + node_1->style.dimensions[CSS_HEIGHT] = 15; + } + } + + css_node_t *root_layout = new_test_css_node(); + { + css_node_t *node_0 = root_layout; + node_0->layout.position[CSS_TOP] = 0; + node_0->layout.position[CSS_LEFT] = 0; + node_0->layout.dimensions[CSS_WIDTH] = 100; + node_0->layout.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 3); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 0; + node_1->layout.dimensions[CSS_WIDTH] = 100; + node_1->layout.dimensions[CSS_HEIGHT] = 25; + node_1 = node_0->get_child(node_0->context, 1); + node_1->layout.position[CSS_TOP] = 25; + node_1->layout.position[CSS_LEFT] = 0; + node_1->layout.dimensions[CSS_WIDTH] = 100; + node_1->layout.dimensions[CSS_HEIGHT] = 60; + init_css_node_children(node_1, 1); + { + css_node_t *node_2; + node_2 = node_1->get_child(node_1->context, 0); + node_2->layout.position[CSS_TOP] = 0; + node_2->layout.position[CSS_LEFT] = 0; + node_2->layout.dimensions[CSS_WIDTH] = 100; + node_2->layout.dimensions[CSS_HEIGHT] = 80; + } + node_1 = node_0->get_child(node_0->context, 2); + node_1->layout.position[CSS_TOP] = 85; + node_1->layout.position[CSS_LEFT] = 0; + node_1->layout.dimensions[CSS_WIDTH] = 100; + node_1->layout.dimensions[CSS_HEIGHT] = 15; + } + } + + test("should shrink column node with siblings when there is not any space left over", root_node, root_layout); + } + + { + css_node_t *root_node = new_test_css_node(); + { + css_node_t *node_0 = root_node; + node_0->style.dimensions[CSS_WIDTH] = 100; + node_0->style.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 3); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->style.flex = -1; + node_1->style.dimensions[CSS_WIDTH] = 100; + node_1->style.dimensions[CSS_HEIGHT] = 30; + node_1 = node_0->get_child(node_0->context, 1); + node_1->style.dimensions[CSS_WIDTH] = 100; + node_1->style.dimensions[CSS_HEIGHT] = 40; + node_1 = node_0->get_child(node_0->context, 2); + node_1->style.flex = -1; + node_1->style.dimensions[CSS_WIDTH] = 100; + node_1->style.dimensions[CSS_HEIGHT] = 50; + } + } + + css_node_t *root_layout = new_test_css_node(); + { + css_node_t *node_0 = root_layout; + node_0->layout.position[CSS_TOP] = 0; + node_0->layout.position[CSS_LEFT] = 0; + node_0->layout.dimensions[CSS_WIDTH] = 100; + node_0->layout.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 3); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 0; + node_1->layout.dimensions[CSS_WIDTH] = 100; + node_1->layout.dimensions[CSS_HEIGHT] = 22.5; + node_1 = node_0->get_child(node_0->context, 1); + node_1->layout.position[CSS_TOP] = 22.5; + node_1->layout.position[CSS_LEFT] = 0; + node_1->layout.dimensions[CSS_WIDTH] = 100; + node_1->layout.dimensions[CSS_HEIGHT] = 40; + node_1 = node_0->get_child(node_0->context, 2); + node_1->layout.position[CSS_TOP] = 62.5; + node_1->layout.position[CSS_LEFT] = 0; + node_1->layout.dimensions[CSS_WIDTH] = 100; + node_1->layout.dimensions[CSS_HEIGHT] = 37.5; + } + } + + test("should shrink column nodes proportional to their main size when there is not any space left over", root_node, root_layout); + } + + { + css_node_t *root_node = new_test_css_node(); + { + css_node_t *node_0 = root_node; + node_0->style.flex_direction = CSS_FLEX_DIRECTION_ROW; + node_0->style.dimensions[CSS_WIDTH] = 100; + node_0->style.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 1); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->style.flex = -1; + node_1->style.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_1, 1); + { + css_node_t *node_2; + node_2 = node_1->get_child(node_1->context, 0); + node_2->style.dimensions[CSS_WIDTH] = 25; + node_2->style.dimensions[CSS_HEIGHT] = 100; + } + } + } + + css_node_t *root_layout = new_test_css_node(); + { + css_node_t *node_0 = root_layout; + node_0->layout.position[CSS_TOP] = 0; + node_0->layout.position[CSS_LEFT] = 0; + node_0->layout.dimensions[CSS_WIDTH] = 100; + node_0->layout.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 1); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 0; + node_1->layout.dimensions[CSS_WIDTH] = 25; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_1, 1); + { + css_node_t *node_2; + node_2 = node_1->get_child(node_1->context, 0); + node_2->layout.position[CSS_TOP] = 0; + node_2->layout.position[CSS_LEFT] = 0; + node_2->layout.dimensions[CSS_WIDTH] = 25; + node_2->layout.dimensions[CSS_HEIGHT] = 100; + } + } + } + + test("should not shrink visible row node when there is space left over", root_node, root_layout); + } + + { + css_node_t *root_node = new_test_css_node(); + { + css_node_t *node_0 = root_node; + node_0->style.flex_direction = CSS_FLEX_DIRECTION_ROW; + node_0->style.dimensions[CSS_WIDTH] = 100; + node_0->style.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 1); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->style.flex = -1; + node_1->style.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_1, 1); + { + css_node_t *node_2; + node_2 = node_1->get_child(node_1->context, 0); + node_2->style.dimensions[CSS_WIDTH] = 200; + node_2->style.dimensions[CSS_HEIGHT] = 100; + } + } + } + + css_node_t *root_layout = new_test_css_node(); + { + css_node_t *node_0 = root_layout; + node_0->layout.position[CSS_TOP] = 0; + node_0->layout.position[CSS_LEFT] = 0; + node_0->layout.dimensions[CSS_WIDTH] = 100; + node_0->layout.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 1); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 0; + node_1->layout.dimensions[CSS_WIDTH] = 100; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_1, 1); + { + css_node_t *node_2; + node_2 = node_1->get_child(node_1->context, 0); + node_2->layout.position[CSS_TOP] = 0; + node_2->layout.position[CSS_LEFT] = 0; + node_2->layout.dimensions[CSS_WIDTH] = 200; + node_2->layout.dimensions[CSS_HEIGHT] = 100; + } + } + } + + test("should shrink visible row node when there is not any space left over", root_node, root_layout); + } + + { + css_node_t *root_node = new_test_css_node(); + { + css_node_t *node_0 = root_node; + node_0->style.flex_direction = CSS_FLEX_DIRECTION_ROW; + node_0->style.dimensions[CSS_WIDTH] = 100; + node_0->style.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 3); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->style.dimensions[CSS_WIDTH] = 25; + node_1->style.dimensions[CSS_HEIGHT] = 100; + node_1 = node_0->get_child(node_0->context, 1); + node_1->style.flex = -1; + node_1->style.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_1, 1); + { + css_node_t *node_2; + node_2 = node_1->get_child(node_1->context, 0); + node_2->style.dimensions[CSS_WIDTH] = 30; + node_2->style.dimensions[CSS_HEIGHT] = 100; + } + node_1 = node_0->get_child(node_0->context, 2); + node_1->style.dimensions[CSS_WIDTH] = 15; + node_1->style.dimensions[CSS_HEIGHT] = 100; + } + } + + css_node_t *root_layout = new_test_css_node(); + { + css_node_t *node_0 = root_layout; + node_0->layout.position[CSS_TOP] = 0; + node_0->layout.position[CSS_LEFT] = 0; + node_0->layout.dimensions[CSS_WIDTH] = 100; + node_0->layout.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 3); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 0; + node_1->layout.dimensions[CSS_WIDTH] = 25; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + node_1 = node_0->get_child(node_0->context, 1); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 25; + node_1->layout.dimensions[CSS_WIDTH] = 30; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_1, 1); + { + css_node_t *node_2; + node_2 = node_1->get_child(node_1->context, 0); + node_2->layout.position[CSS_TOP] = 0; + node_2->layout.position[CSS_LEFT] = 0; + node_2->layout.dimensions[CSS_WIDTH] = 30; + node_2->layout.dimensions[CSS_HEIGHT] = 100; + } + node_1 = node_0->get_child(node_0->context, 2); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 55; + node_1->layout.dimensions[CSS_WIDTH] = 15; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + } + } + + test("should not shrink visible row node with siblings when there is space left over", root_node, root_layout); + } + + { + css_node_t *root_node = new_test_css_node(); + { + css_node_t *node_0 = root_node; + node_0->style.flex_direction = CSS_FLEX_DIRECTION_ROW; + node_0->style.dimensions[CSS_WIDTH] = 100; + node_0->style.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 3); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->style.dimensions[CSS_WIDTH] = 25; + node_1->style.dimensions[CSS_HEIGHT] = 100; + node_1 = node_0->get_child(node_0->context, 1); + node_1->style.flex = -1; + node_1->style.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_1, 1); + { + css_node_t *node_2; + node_2 = node_1->get_child(node_1->context, 0); + node_2->style.dimensions[CSS_WIDTH] = 80; + node_2->style.dimensions[CSS_HEIGHT] = 100; + } + node_1 = node_0->get_child(node_0->context, 2); + node_1->style.dimensions[CSS_WIDTH] = 15; + node_1->style.dimensions[CSS_HEIGHT] = 100; + } + } + + css_node_t *root_layout = new_test_css_node(); + { + css_node_t *node_0 = root_layout; + node_0->layout.position[CSS_TOP] = 0; + node_0->layout.position[CSS_LEFT] = 0; + node_0->layout.dimensions[CSS_WIDTH] = 100; + node_0->layout.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 3); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 0; + node_1->layout.dimensions[CSS_WIDTH] = 25; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + node_1 = node_0->get_child(node_0->context, 1); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 25; + node_1->layout.dimensions[CSS_WIDTH] = 60; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_1, 1); + { + css_node_t *node_2; + node_2 = node_1->get_child(node_1->context, 0); + node_2->layout.position[CSS_TOP] = 0; + node_2->layout.position[CSS_LEFT] = 0; + node_2->layout.dimensions[CSS_WIDTH] = 80; + node_2->layout.dimensions[CSS_HEIGHT] = 100; + } + node_1 = node_0->get_child(node_0->context, 2); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 85; + node_1->layout.dimensions[CSS_WIDTH] = 15; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + } + } + + test("should shrink visible row node with siblings when there is not any space left over", root_node, root_layout); + } + + { + css_node_t *root_node = new_test_css_node(); + { + css_node_t *node_0 = root_node; + node_0->style.flex_direction = CSS_FLEX_DIRECTION_ROW; + node_0->style.dimensions[CSS_WIDTH] = 100; + node_0->style.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 3); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->style.flex = -1; + node_1->style.dimensions[CSS_WIDTH] = 30; + node_1->style.dimensions[CSS_HEIGHT] = 100; + node_1 = node_0->get_child(node_0->context, 1); + node_1->style.dimensions[CSS_WIDTH] = 40; + node_1->style.dimensions[CSS_HEIGHT] = 100; + node_1 = node_0->get_child(node_0->context, 2); + node_1->style.flex = -1; + node_1->style.dimensions[CSS_WIDTH] = 50; + node_1->style.dimensions[CSS_HEIGHT] = 100; + } + } + + css_node_t *root_layout = new_test_css_node(); + { + css_node_t *node_0 = root_layout; + node_0->layout.position[CSS_TOP] = 0; + node_0->layout.position[CSS_LEFT] = 0; + node_0->layout.dimensions[CSS_WIDTH] = 100; + node_0->layout.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 3); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 0; + node_1->layout.dimensions[CSS_WIDTH] = 22.5; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + node_1 = node_0->get_child(node_0->context, 1); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 22.5; + node_1->layout.dimensions[CSS_WIDTH] = 40; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + node_1 = node_0->get_child(node_0->context, 2); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 62.5; + node_1->layout.dimensions[CSS_WIDTH] = 37.5; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + } + } + + test("should shrink visible row nodes when there is not any space left over", root_node, root_layout); + } + + { + css_node_t *root_node = new_test_css_node(); + { + css_node_t *node_0 = root_node; + node_0->style.flex_direction = CSS_FLEX_DIRECTION_ROW; + node_0->style.dimensions[CSS_WIDTH] = 100; + node_0->style.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 1); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->style.overflow = CSS_OVERFLOW_HIDDEN; + node_1->style.flex = -1; + node_1->style.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_1, 1); + { + css_node_t *node_2; + node_2 = node_1->get_child(node_1->context, 0); + node_2->style.dimensions[CSS_WIDTH] = 25; + node_2->style.dimensions[CSS_HEIGHT] = 100; + } + } + } + + css_node_t *root_layout = new_test_css_node(); + { + css_node_t *node_0 = root_layout; + node_0->layout.position[CSS_TOP] = 0; + node_0->layout.position[CSS_LEFT] = 0; + node_0->layout.dimensions[CSS_WIDTH] = 100; + node_0->layout.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 1); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 0; + node_1->layout.dimensions[CSS_WIDTH] = 25; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_1, 1); + { + css_node_t *node_2; + node_2 = node_1->get_child(node_1->context, 0); + node_2->layout.position[CSS_TOP] = 0; + node_2->layout.position[CSS_LEFT] = 0; + node_2->layout.dimensions[CSS_WIDTH] = 25; + node_2->layout.dimensions[CSS_HEIGHT] = 100; + } + } + } + + test("should not shrink hidden row node when there is space left over", root_node, root_layout); + } + + { + css_node_t *root_node = new_test_css_node(); + { + css_node_t *node_0 = root_node; + node_0->style.flex_direction = CSS_FLEX_DIRECTION_ROW; + node_0->style.dimensions[CSS_WIDTH] = 100; + node_0->style.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 1); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->style.overflow = CSS_OVERFLOW_HIDDEN; + node_1->style.flex = -1; + node_1->style.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_1, 1); + { + css_node_t *node_2; + node_2 = node_1->get_child(node_1->context, 0); + node_2->style.dimensions[CSS_WIDTH] = 200; + node_2->style.dimensions[CSS_HEIGHT] = 100; + } + } + } + + css_node_t *root_layout = new_test_css_node(); + { + css_node_t *node_0 = root_layout; + node_0->layout.position[CSS_TOP] = 0; + node_0->layout.position[CSS_LEFT] = 0; + node_0->layout.dimensions[CSS_WIDTH] = 100; + node_0->layout.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 1); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 0; + node_1->layout.dimensions[CSS_WIDTH] = 100; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_1, 1); + { + css_node_t *node_2; + node_2 = node_1->get_child(node_1->context, 0); + node_2->layout.position[CSS_TOP] = 0; + node_2->layout.position[CSS_LEFT] = 0; + node_2->layout.dimensions[CSS_WIDTH] = 200; + node_2->layout.dimensions[CSS_HEIGHT] = 100; + } + } + } + + test("should shrink hidden row node when there is not any space left over", root_node, root_layout); + } + + { + css_node_t *root_node = new_test_css_node(); + { + css_node_t *node_0 = root_node; + node_0->style.flex_direction = CSS_FLEX_DIRECTION_ROW; + node_0->style.dimensions[CSS_WIDTH] = 100; + node_0->style.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 3); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->style.dimensions[CSS_WIDTH] = 25; + node_1->style.dimensions[CSS_HEIGHT] = 100; + node_1 = node_0->get_child(node_0->context, 1); + node_1->style.overflow = CSS_OVERFLOW_HIDDEN; + node_1->style.flex = -1; + node_1->style.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_1, 1); + { + css_node_t *node_2; + node_2 = node_1->get_child(node_1->context, 0); + node_2->style.dimensions[CSS_WIDTH] = 30; + node_2->style.dimensions[CSS_HEIGHT] = 100; + } + node_1 = node_0->get_child(node_0->context, 2); + node_1->style.dimensions[CSS_WIDTH] = 15; + node_1->style.dimensions[CSS_HEIGHT] = 100; + } + } + + css_node_t *root_layout = new_test_css_node(); + { + css_node_t *node_0 = root_layout; + node_0->layout.position[CSS_TOP] = 0; + node_0->layout.position[CSS_LEFT] = 0; + node_0->layout.dimensions[CSS_WIDTH] = 100; + node_0->layout.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 3); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 0; + node_1->layout.dimensions[CSS_WIDTH] = 25; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + node_1 = node_0->get_child(node_0->context, 1); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 25; + node_1->layout.dimensions[CSS_WIDTH] = 30; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_1, 1); + { + css_node_t *node_2; + node_2 = node_1->get_child(node_1->context, 0); + node_2->layout.position[CSS_TOP] = 0; + node_2->layout.position[CSS_LEFT] = 0; + node_2->layout.dimensions[CSS_WIDTH] = 30; + node_2->layout.dimensions[CSS_HEIGHT] = 100; + } + node_1 = node_0->get_child(node_0->context, 2); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 55; + node_1->layout.dimensions[CSS_WIDTH] = 15; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + } + } + + test("should not shrink hidden row node with siblings when there is space left over", root_node, root_layout); + } + + { + css_node_t *root_node = new_test_css_node(); + { + css_node_t *node_0 = root_node; + node_0->style.flex_direction = CSS_FLEX_DIRECTION_ROW; + node_0->style.dimensions[CSS_WIDTH] = 100; + node_0->style.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 3); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->style.dimensions[CSS_WIDTH] = 25; + node_1->style.dimensions[CSS_HEIGHT] = 100; + node_1 = node_0->get_child(node_0->context, 1); + node_1->style.overflow = CSS_OVERFLOW_HIDDEN; + node_1->style.flex = -1; + node_1->style.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_1, 1); + { + css_node_t *node_2; + node_2 = node_1->get_child(node_1->context, 0); + node_2->style.dimensions[CSS_WIDTH] = 80; + node_2->style.dimensions[CSS_HEIGHT] = 100; + } + node_1 = node_0->get_child(node_0->context, 2); + node_1->style.dimensions[CSS_WIDTH] = 15; + node_1->style.dimensions[CSS_HEIGHT] = 100; + } + } + + css_node_t *root_layout = new_test_css_node(); + { + css_node_t *node_0 = root_layout; + node_0->layout.position[CSS_TOP] = 0; + node_0->layout.position[CSS_LEFT] = 0; + node_0->layout.dimensions[CSS_WIDTH] = 100; + node_0->layout.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 3); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 0; + node_1->layout.dimensions[CSS_WIDTH] = 25; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + node_1 = node_0->get_child(node_0->context, 1); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 25; + node_1->layout.dimensions[CSS_WIDTH] = 60; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_1, 1); + { + css_node_t *node_2; + node_2 = node_1->get_child(node_1->context, 0); + node_2->layout.position[CSS_TOP] = 0; + node_2->layout.position[CSS_LEFT] = 0; + node_2->layout.dimensions[CSS_WIDTH] = 80; + node_2->layout.dimensions[CSS_HEIGHT] = 100; + } + node_1 = node_0->get_child(node_0->context, 2); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 85; + node_1->layout.dimensions[CSS_WIDTH] = 15; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + } + } + + test("should shrink hidden row node with siblings when there is not any space left over", root_node, root_layout); + } + + { + css_node_t *root_node = new_test_css_node(); + { + css_node_t *node_0 = root_node; + node_0->style.flex_direction = CSS_FLEX_DIRECTION_ROW; + node_0->style.dimensions[CSS_WIDTH] = 100; + node_0->style.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 3); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->style.overflow = CSS_OVERFLOW_HIDDEN; + node_1->style.flex = -1; + node_1->style.dimensions[CSS_WIDTH] = 30; + node_1->style.dimensions[CSS_HEIGHT] = 100; + node_1 = node_0->get_child(node_0->context, 1); + node_1->style.dimensions[CSS_WIDTH] = 40; + node_1->style.dimensions[CSS_HEIGHT] = 100; + node_1 = node_0->get_child(node_0->context, 2); + node_1->style.overflow = CSS_OVERFLOW_HIDDEN; + node_1->style.flex = -1; + node_1->style.dimensions[CSS_WIDTH] = 50; + node_1->style.dimensions[CSS_HEIGHT] = 100; + } + } + + css_node_t *root_layout = new_test_css_node(); + { + css_node_t *node_0 = root_layout; + node_0->layout.position[CSS_TOP] = 0; + node_0->layout.position[CSS_LEFT] = 0; + node_0->layout.dimensions[CSS_WIDTH] = 100; + node_0->layout.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 3); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 0; + node_1->layout.dimensions[CSS_WIDTH] = 22.5; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + node_1 = node_0->get_child(node_0->context, 1); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 22.5; + node_1->layout.dimensions[CSS_WIDTH] = 40; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + node_1 = node_0->get_child(node_0->context, 2); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 62.5; + node_1->layout.dimensions[CSS_WIDTH] = 37.5; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + } + } + + test("should shrink hidden row nodes proportional to their main size when there is not any space left over", root_node, root_layout); + } + + { + css_node_t *root_node = new_test_css_node(); + { + css_node_t *node_0 = root_node; + node_0->style.flex_direction = CSS_FLEX_DIRECTION_ROW; + node_0->style.dimensions[CSS_WIDTH] = 213; + node_0->style.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 3); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->style.dimensions[CSS_WIDTH] = 25; + node_1->style.dimensions[CSS_HEIGHT] = 100; + node_1 = node_0->get_child(node_0->context, 1); + node_1->style.flex_direction = CSS_FLEX_DIRECTION_ROW; + node_1->style.align_items = CSS_ALIGN_FLEX_START; + node_1->style.flex = -1; + node_1->style.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_1, 1); + { + css_node_t *node_2; + node_2 = node_1->get_child(node_1->context, 0); + node_2->measure = measure; + node_2->context = "loooooooooong with space"; + } + node_1 = node_0->get_child(node_0->context, 2); + node_1->style.dimensions[CSS_WIDTH] = 15; + node_1->style.dimensions[CSS_HEIGHT] = 100; + } + } + + css_node_t *root_layout = new_test_css_node(); + { + css_node_t *node_0 = root_layout; + node_0->layout.position[CSS_TOP] = 0; + node_0->layout.position[CSS_LEFT] = 0; + node_0->layout.dimensions[CSS_WIDTH] = 213; + node_0->layout.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 3); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 0; + node_1->layout.dimensions[CSS_WIDTH] = 25; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + node_1 = node_0->get_child(node_0->context, 1); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 25; + node_1->layout.dimensions[CSS_WIDTH] = 172; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_1, 1); + { + css_node_t *node_2; + node_2 = node_1->get_child(node_1->context, 0); + node_2->layout.position[CSS_TOP] = 0; + node_2->layout.position[CSS_LEFT] = 0; + node_2->layout.dimensions[CSS_WIDTH] = 172; + node_2->layout.dimensions[CSS_HEIGHT] = 18; + } + node_1 = node_0->get_child(node_0->context, 2); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 197; + node_1->layout.dimensions[CSS_WIDTH] = 15; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + } + } + + test("should not shrink text node with siblings when there is space left over", root_node, root_layout); + } + + { + css_node_t *root_node = new_test_css_node(); + { + css_node_t *node_0 = root_node; + node_0->style.flex_direction = CSS_FLEX_DIRECTION_ROW; + node_0->style.dimensions[CSS_WIDTH] = 140; + node_0->style.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 3); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->style.dimensions[CSS_WIDTH] = 25; + node_1->style.dimensions[CSS_HEIGHT] = 100; + node_1 = node_0->get_child(node_0->context, 1); + node_1->style.flex_direction = CSS_FLEX_DIRECTION_ROW; + node_1->style.align_items = CSS_ALIGN_FLEX_START; + node_1->style.flex = -1; + node_1->style.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_1, 1); + { + css_node_t *node_2; + node_2 = node_1->get_child(node_1->context, 0); + node_2->style.flex = -1; + node_2->measure = measure; + node_2->context = "loooooooooong with space"; + } + node_1 = node_0->get_child(node_0->context, 2); + node_1->style.dimensions[CSS_WIDTH] = 15; + node_1->style.dimensions[CSS_HEIGHT] = 100; + } + } + + css_node_t *root_layout = new_test_css_node(); + { + css_node_t *node_0 = root_layout; + node_0->layout.position[CSS_TOP] = 0; + node_0->layout.position[CSS_LEFT] = 0; + node_0->layout.dimensions[CSS_WIDTH] = 140; + node_0->layout.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_0, 3); + { + css_node_t *node_1; + node_1 = node_0->get_child(node_0->context, 0); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 0; + node_1->layout.dimensions[CSS_WIDTH] = 25; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + node_1 = node_0->get_child(node_0->context, 1); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 25; + node_1->layout.dimensions[CSS_WIDTH] = 100; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + init_css_node_children(node_1, 1); + { + css_node_t *node_2; + node_2 = node_1->get_child(node_1->context, 0); + node_2->layout.position[CSS_TOP] = 0; + node_2->layout.position[CSS_LEFT] = 0; + node_2->layout.dimensions[CSS_WIDTH] = 100; + node_2->layout.dimensions[CSS_HEIGHT] = 36; + } + node_1 = node_0->get_child(node_0->context, 2); + node_1->layout.position[CSS_TOP] = 0; + node_1->layout.position[CSS_LEFT] = 125; + node_1->layout.dimensions[CSS_WIDTH] = 15; + node_1->layout.dimensions[CSS_HEIGHT] = 100; + } + } + + test("should shrink text node with siblings when there is not any space left over", root_node, root_layout); + } + { css_node_t *root_node = new_test_css_node(); { diff --git a/src/__tests__/Layout-test.js b/src/__tests__/Layout-test.js index a70ce673..d7edb768 100755 --- a/src/__tests__/Layout-test.js +++ b/src/__tests__/Layout-test.js @@ -1238,7 +1238,7 @@ describe('Layout', function() { testLayoutAgainstExpectedOnly( {style: {width: 320, flexDirection: 'column'}, children: [ {style: {measure: measureWithRatio2}}, - {style: {height: 100, flexDirection: 'row'}, children: [ + {style: {height: 100, flexDirection: 'row', overflow: 'hidden'}, children: [ {style: {measure: measureWithRatio2}}, {style: {measure: measureWithRatio2}} ]}, @@ -1354,14 +1354,16 @@ describe('Layout', function() { }); it('should layout node with text bounded by grand-parent', function() { - testLayout( + testLayoutAgainstExpectedOnly( {style: {width: 100, padding: 10, alignSelf: 'flex-start'}, children: [ {style: {margin: 10, alignSelf: 'flex-start'}, children: [ {style: {measure: text(texts.big)}} ]} ]}, {width: 100, height: 40 + textSizes.bigHeight, top: 0, left: 0, children: [ - {width: textSizes.bigMinWidth, height: textSizes.bigHeight, top: 20, left: 20, children: [ + // In the flexbox engine implementation, min width of text is not supported so we max + // out at the amount of available space (60) + {width: Math.min(60, textSizes.bigMinWidth), height: textSizes.bigHeight, top: 20, left: 20, children: [ {width: textSizes.bigMinWidth, height: textSizes.bigHeight, top: 0, left: 0} ]} ]} @@ -2485,6 +2487,307 @@ describe('Layout', function() { }); }); +describe('Layout flex:-1', function() { + // Tests for items with flex:-1 in a container with flexDirection:column + + it('should not shrink column node when there is space left over', function() { + testLayout( + {style: {width: 100, height: 100}, children: [ + {style: {width: 100, flex: -1}, children: [ + {style: {width: 100, height: 25}} + ]} + ]}, + {width: 100, height: 100, top: 0, left: 0, children: [ + {width: 100, height: 25, top: 0, left: 0, children: [ + {width: 100, height: 25, top: 0, left: 0} + ]} + ]} + ); + }); + + it('should shrink column node when there is not any space left over', function() { + testLayout( + {style: {width: 100, height: 100}, children: [ + {style: {width: 100, flex: -1}, children: [ + {style: {width: 100, height: 200}} + ]} + ]}, + {width: 100, height: 100, top: 0, left: 0, children: [ + {width: 100, height: 100, top: 0, left: 0, children: [ + {width: 100, height: 200, top: 0, left: 0} + ]} + ]} + ); + }); + + it('should not shrink column node with siblings when there is space left over', function() { + testLayout( + {style: {width: 100, height: 100}, children: [ + {style: {width: 100, height: 25}}, + {style: {width: 100, flex: -1}, children: [ + {style: {width: 100, height: 30}} + ]}, + {style: {width: 100, height: 15}}, + ]}, + {width: 100, height: 100, top: 0, left: 0, children: [ + {width: 100, height: 25, top: 0, left: 0}, + {width: 100, height: 30, top: 25, left: 0, children: [ + {width: 100, height: 30, top: 0, left: 0} + ]}, + {width: 100, height: 15, top: 55, left: 0}, + ]} + ); + }); + + it('should shrink column node with siblings when there is not any space left over', function() { + testLayout( + {style: {width: 100, height: 100}, children: [ + {style: {width: 100, height: 25}}, + {style: {width: 100, flex: -1}, children: [ + {style: {width: 100, height: 80}} + ]}, + {style: {width: 100, height: 15}}, + ]}, + {width: 100, height: 100, top: 0, left: 0, children: [ + {width: 100, height: 25, top: 0, left: 0}, + {width: 100, height: 60, top: 25, left: 0, children: [ + {width: 100, height: 80, top: 0, left: 0} + ]}, + {width: 100, height: 15, top: 85, left: 0}, + ]} + ); + }); + + it('should shrink column nodes proportional to their main size when there is not any space left over', function() { + testLayout( + {style: {width: 100, height: 100}, children: [ + {style: {width: 100, height: 30, flex: -1}}, + {style: {width: 100, height: 40}}, + {style: {width: 100, height: 50, flex: -1}} + ]}, + {width: 100, height: 100, top: 0, left: 0, children: [ + {width: 100, height: 22.5, top: 0, left: 0}, + {width: 100, height: 40, top: 22.5, left: 0}, + {width: 100, height: 37.5, top: 62.5, left: 0} + ]} + ); + }); + + // Tests for items with flex:-1 and overflow:visible in a container with flexDirection:row + + it('should not shrink visible row node when there is space left over', function() { + testLayout( + {style: {width: 100, height: 100, flexDirection: 'row'}, children: [ + {style: {height: 100, flex: -1}, children: [ + {style: {width: 25, height: 100}} + ]} + ]}, + {width: 100, height: 100, top: 0, left: 0, children: [ + {width: 25, height: 100, top: 0, left: 0, children: [ + {width: 25, height: 100, top: 0, left: 0} + ]} + ]} + ); + }); + + it('should shrink visible row node when there is not any space left over', function() { + testLayout( + {style: {width: 100, height: 100, flexDirection: 'row'}, children: [ + {style: {height: 100, flex: -1}, children: [ + {style: {width: 200, height: 100}} + ]} + ]}, + {width: 100, height: 100, top: 0, left: 0, children: [ + // width would be 100 if we implemented https://www.w3.org/TR/css-flexbox-1/#min-size-auto and min-width didn't default to 0 + {width: 100, height: 100, top: 0, left: 0, children: [ + {width: 200, height: 100, top: 0, left: 0} + ]} + ]} + ); + }); + + it('should not shrink visible row node with siblings when there is space left over', function() { + testLayout( + {style: {width: 100, height: 100, flexDirection: 'row'}, children: [ + {style: {width: 25, height: 100}}, + {style: {height: 100, flex: -1}, children: [ + {style: {width: 30, height: 100}} + ]}, + {style: {width: 15, height: 100}}, + ]}, + {width: 100, height: 100, top: 0, left: 0, children: [ + {width: 25, height: 100, top: 0, left: 0}, + {width: 30, height: 100, top: 0, left: 25, children: [ + {width: 30, height: 100, top: 0, left: 0} + ]}, + {width: 15, height: 100, top: 0, left: 55}, + ]} + ); + }); + + it('should shrink visible row node with siblings when there is not any space left over', function() { + testLayout( + {style: {width: 100, height: 100, flexDirection: 'row'}, children: [ + {style: {width: 25, height: 100}}, + {style: {height: 100, flex: -1}, children: [ + {style: {width: 80, height: 100}} + ]}, + {style: {width: 15, height: 100}}, + ]}, + {width: 100, height: 100, top: 0, left: 0, children: [ + {width: 25, height: 100, top: 0, left: 0}, + // width would be 80 if we implemented https://www.w3.org/TR/css-flexbox-1/#min-size-auto and min-width didn't default to 0 + {width: 60, height: 100, top: 0, left: 25, children: [ + {width: 80, height: 100, top: 0, left: 0} + ]}, + {width: 15, height: 100, top: 0, left: 85}, + ]} + ); + }); + + it('should shrink visible row nodes when there is not any space left over', function() { + testLayout( + {style: {width: 100, height: 100, flexDirection: 'row'}, children: [ + {style: {width: 30, height: 100, flex: -1}}, + {style: {width: 40, height: 100}}, + {style: {width: 50, height: 100, flex: -1}} + ]}, + {width: 100, height: 100, top: 0, left: 0, children: [ + // width would be 30 if we implemented https://www.w3.org/TR/css-flexbox-1/#min-size-auto and min-width didn't default to 0 + {width: 22.5, height: 100, top: 0, left: 0}, + {width: 40, height: 100, top: 0, left: 22.5}, + // width would be 50 if we implemented https://www.w3.org/TR/css-flexbox-1/#min-size-auto and min-width didn't default to 0 + {width: 37.5, height: 100, top: 0, left: 62.5} + ]} + ); + }); + + // Tests for items with flex:-1 and overflow:hidden in a container with flexDirection:row + + it('should not shrink hidden row node when there is space left over', function() { + testLayout( + {style: {width: 100, height: 100, flexDirection: 'row'}, children: [ + {style: {height: 100, flex: -1, overflow: 'hidden'}, children: [ + {style: {width: 25, height: 100}} + ]} + ]}, + {width: 100, height: 100, top: 0, left: 0, children: [ + {width: 25, height: 100, top: 0, left: 0, children: [ + {width: 25, height: 100, top: 0, left: 0} + ]} + ]} + ); + }); + + it('should shrink hidden row node when there is not any space left over', function() { + testLayout( + {style: {width: 100, height: 100, flexDirection: 'row'}, children: [ + {style: {height: 100, flex: -1, overflow: 'hidden'}, children: [ + {style: {width: 200, height: 100}} + ]} + ]}, + {width: 100, height: 100, top: 0, left: 0, children: [ + {width: 100, height: 100, top: 0, left: 0, children: [ + {width: 200, height: 100, top: 0, left: 0} + ]} + ]} + ); + }); + + it('should not shrink hidden row node with siblings when there is space left over', function() { + testLayout( + {style: {width: 100, height: 100, flexDirection: 'row'}, children: [ + {style: {width: 25, height: 100}}, + {style: {height: 100, flex: -1, overflow: 'hidden'}, children: [ + {style: {width: 30, height: 100}} + ]}, + {style: {width: 15, height: 100}}, + ]}, + {width: 100, height: 100, top: 0, left: 0, children: [ + {width: 25, height: 100, top: 0, left: 0}, + {width: 30, height: 100, top: 0, left: 25, children: [ + {width: 30, height: 100, top: 0, left: 0} + ]}, + {width: 15, height: 100, top: 0, left: 55}, + ]} + ); + }); + + it('should shrink hidden row node with siblings when there is not any space left over', function() { + testLayout( + {style: {width: 100, height: 100, flexDirection: 'row'}, children: [ + {style: {width: 25, height: 100}}, + {style: {height: 100, flex: -1, overflow: 'hidden'}, children: [ + {style: {width: 80, height: 100}} + ]}, + {style: {width: 15, height: 100}}, + ]}, + {width: 100, height: 100, top: 0, left: 0, children: [ + {width: 25, height: 100, top: 0, left: 0}, + {width: 60, height: 100, top: 0, left: 25, children: [ + {width: 80, height: 100, top: 0, left: 0} + ]}, + {width: 15, height: 100, top: 0, left: 85}, + ]} + ); + }); + + it('should shrink hidden row nodes proportional to their main size when there is not any space left over', function() { + testLayout( + {style: {width: 100, height: 100, flexDirection: 'row'}, children: [ + {style: {width: 30, height: 100, flex: -1, overflow: 'hidden'}}, + {style: {width: 40, height: 100}}, + {style: {width: 50, height: 100, flex: -1, overflow: 'hidden'}} + ]}, + {width: 100, height: 100, top: 0, left: 0, children: [ + {width: 22.5, height: 100, top: 0, left: 0}, + {width: 40, height: 100, top: 0, left: 22.5}, + {width: 37.5, height: 100, top: 0, left: 62.5} + ]} + ); + }); + + // Tests for items with flex:-1 containing a text node + + it('should not shrink text node with siblings when there is space left over', function() { + testLayoutAgainstExpectedOnly( + {style: {width: 213, height: 100, flexDirection: 'row'}, children: [ + {style: {width: 25, height: 100}}, + {style: {height: 100, flex: -1, flexDirection: 'row', alignItems: 'flex-start'}, children: [ + {style: {measure: text(texts.big)}} + ]}, + {style: {width: 15, height: 100}}, + ]}, + {width: 213, height: 100, top: 0, left: 0, children: [ + {width: 25, height: 100, top: 0, left: 0}, + {width: textSizes.bigWidth, height: 100, top: 0, left: 25, children: [ + {width: textSizes.bigWidth, height: textSizes.smallHeight, top: 0, left: 0} + ]}, + {width: 15, height: 100, top: 0, left: 25 + textSizes.bigWidth}, + ]} + ); + }); + + it('should shrink text node with siblings when there is not any space left over', function() { + testLayout( + {style: {width: 140, height: 100, flexDirection: 'row'}, children: [ + {style: {width: 25, height: 100}}, + {style: {height: 100, flex: -1, flexDirection: 'row', alignItems: 'flex-start'}, children: [ + {style: {flex: -1, measure: text(texts.big)}} + ]}, + {style: {width: 15, height: 100}}, + ]}, + {width: 140, height: 100, top: 0, left: 0, children: [ + {width: 25, height: 100, top: 0, left: 0}, + {width: textSizes.bigMinWidth, height: 100, top: 0, left: 25, children: [ + {width: textSizes.bigMinWidth, height: textSizes.bigHeight, top: 0, left: 0} + ]}, + {width: 15, height: 100, top: 0, left: 25 + textSizes.bigMinWidth}, + ]} + ); + }); +}); + describe('Layout alignContent', function() { it('should layout with alignContent: stretch, and alignItems: flex-start', function() { diff --git a/src/csharp/Facebook.CSSLayout.Tests/LayoutEngineTest.cs b/src/csharp/Facebook.CSSLayout.Tests/LayoutEngineTest.cs index c44dd882..63fb0c9d 100644 --- a/src/csharp/Facebook.CSSLayout.Tests/LayoutEngineTest.cs +++ b/src/csharp/Facebook.CSSLayout.Tests/LayoutEngineTest.cs @@ -36,7 +36,7 @@ public class LayoutEngineTest Math.Min(width, TestConstants.SMALL_WIDTH), TestConstants.SMALL_HEIGHT); } else if (testNode.context.Equals(TestConstants.LONG_TEXT)) { - if (CSSConstants.IsUndefined(width)) { + if (widthMode == CSSMeasureMode.Undefined) { width = 10000000; } return new MeasureOutput(width >= TestConstants.BIG_WIDTH @@ -55,10 +55,10 @@ public class LayoutEngineTest } } else if (testNode.context.Equals(TestConstants.MEASURE_WITH_MATCH_PARENT)) { if (widthMode == CSSMeasureMode.Undefined) { - width = 99999; + width = 99999; } if (heightMode == CSSMeasureMode.Undefined) { - height = 99999; + height = 99999; } return new MeasureOutput(width, height); } else { @@ -4371,6 +4371,7 @@ public class LayoutEngineTest node_1.context = "measureWithRatio2"; node_1 = node_0.getChildAt(1); node_1.style.flexDirection = CSSFlexDirection.Row; + node_1.style.overflow = CSSOverflow.Hidden; node_1.style.dimensions[DIMENSION_HEIGHT] = 100; addChildren(node_1, 2); { @@ -4822,7 +4823,7 @@ public class LayoutEngineTest node_1 = node_0.getChildAt(0); node_1.layout.position[POSITION_TOP] = 20; node_1.layout.position[POSITION_LEFT] = 20; - node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_WIDTH] = 60; node_1.layout.dimensions[DIMENSION_HEIGHT] = 36; addChildren(node_1, 1); { @@ -8388,7 +8389,7 @@ public class LayoutEngineTest } } - test("should layout child whose cross axis is undefined and whose alignSelf is stretch", root_node, root_layout); + test("should layout child whose cross axis is null and whose alignSelf is stretch", root_node, root_layout); } [Test] @@ -8483,6 +8484,1081 @@ public class LayoutEngineTest [Test] public void TestCase188() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 1); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_WIDTH] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.style.dimensions[DIMENSION_WIDTH] = 100; + node_2.style.dimensions[DIMENSION_HEIGHT] = 25; + } + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 1); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 25; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.layout.position[POSITION_TOP] = 0; + node_2.layout.position[POSITION_LEFT] = 0; + node_2.layout.dimensions[DIMENSION_WIDTH] = 100; + node_2.layout.dimensions[DIMENSION_HEIGHT] = 25; + } + } + } + + test("should not shrink column node when there is space left over", root_node, root_layout); + } + + [Test] + public void TestCase189() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 1); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_WIDTH] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.style.dimensions[DIMENSION_WIDTH] = 100; + node_2.style.dimensions[DIMENSION_HEIGHT] = 200; + } + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 1); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.layout.position[POSITION_TOP] = 0; + node_2.layout.position[POSITION_LEFT] = 0; + node_2.layout.dimensions[DIMENSION_WIDTH] = 100; + node_2.layout.dimensions[DIMENSION_HEIGHT] = 200; + } + } + } + + test("should shrink column node when there is not any space left over", root_node, root_layout); + } + + [Test] + public void TestCase190() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.dimensions[DIMENSION_WIDTH] = 100; + node_1.style.dimensions[DIMENSION_HEIGHT] = 25; + node_1 = node_0.getChildAt(1); + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_WIDTH] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.style.dimensions[DIMENSION_WIDTH] = 100; + node_2.style.dimensions[DIMENSION_HEIGHT] = 30; + } + node_1 = node_0.getChildAt(2); + node_1.style.dimensions[DIMENSION_WIDTH] = 100; + node_1.style.dimensions[DIMENSION_HEIGHT] = 15; + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 25; + node_1 = node_0.getChildAt(1); + node_1.layout.position[POSITION_TOP] = 25; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 30; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.layout.position[POSITION_TOP] = 0; + node_2.layout.position[POSITION_LEFT] = 0; + node_2.layout.dimensions[DIMENSION_WIDTH] = 100; + node_2.layout.dimensions[DIMENSION_HEIGHT] = 30; + } + node_1 = node_0.getChildAt(2); + node_1.layout.position[POSITION_TOP] = 55; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 15; + } + } + + test("should not shrink column node with siblings when there is space left over", root_node, root_layout); + } + + [Test] + public void TestCase191() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.dimensions[DIMENSION_WIDTH] = 100; + node_1.style.dimensions[DIMENSION_HEIGHT] = 25; + node_1 = node_0.getChildAt(1); + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_WIDTH] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.style.dimensions[DIMENSION_WIDTH] = 100; + node_2.style.dimensions[DIMENSION_HEIGHT] = 80; + } + node_1 = node_0.getChildAt(2); + node_1.style.dimensions[DIMENSION_WIDTH] = 100; + node_1.style.dimensions[DIMENSION_HEIGHT] = 15; + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 25; + node_1 = node_0.getChildAt(1); + node_1.layout.position[POSITION_TOP] = 25; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 60; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.layout.position[POSITION_TOP] = 0; + node_2.layout.position[POSITION_LEFT] = 0; + node_2.layout.dimensions[DIMENSION_WIDTH] = 100; + node_2.layout.dimensions[DIMENSION_HEIGHT] = 80; + } + node_1 = node_0.getChildAt(2); + node_1.layout.position[POSITION_TOP] = 85; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 15; + } + } + + test("should shrink column node with siblings when there is not any space left over", root_node, root_layout); + } + + [Test] + public void TestCase192() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_WIDTH] = 100; + node_1.style.dimensions[DIMENSION_HEIGHT] = 30; + node_1 = node_0.getChildAt(1); + node_1.style.dimensions[DIMENSION_WIDTH] = 100; + node_1.style.dimensions[DIMENSION_HEIGHT] = 40; + node_1 = node_0.getChildAt(2); + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_WIDTH] = 100; + node_1.style.dimensions[DIMENSION_HEIGHT] = 50; + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 22.5f; + node_1 = node_0.getChildAt(1); + node_1.layout.position[POSITION_TOP] = 22.5f; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 40; + node_1 = node_0.getChildAt(2); + node_1.layout.position[POSITION_TOP] = 62.5f; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 37.5f; + } + } + + test("should shrink column nodes proportional to their main size when there is not any space left over", root_node, root_layout); + } + + [Test] + public void TestCase193() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.flexDirection = CSSFlexDirection.Row; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 1); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.style.dimensions[DIMENSION_WIDTH] = 25; + node_2.style.dimensions[DIMENSION_HEIGHT] = 100; + } + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 1); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 25; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.layout.position[POSITION_TOP] = 0; + node_2.layout.position[POSITION_LEFT] = 0; + node_2.layout.dimensions[DIMENSION_WIDTH] = 25; + node_2.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + } + } + + test("should not shrink visible row node when there is space left over", root_node, root_layout); + } + + [Test] + public void TestCase194() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.flexDirection = CSSFlexDirection.Row; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 1); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.style.dimensions[DIMENSION_WIDTH] = 200; + node_2.style.dimensions[DIMENSION_HEIGHT] = 100; + } + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 1); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.layout.position[POSITION_TOP] = 0; + node_2.layout.position[POSITION_LEFT] = 0; + node_2.layout.dimensions[DIMENSION_WIDTH] = 200; + node_2.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + } + } + + test("should shrink visible row node when there is not any space left over", root_node, root_layout); + } + + [Test] + public void TestCase195() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.flexDirection = CSSFlexDirection.Row; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.dimensions[DIMENSION_WIDTH] = 25; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.style.dimensions[DIMENSION_WIDTH] = 30; + node_2.style.dimensions[DIMENSION_HEIGHT] = 100; + } + node_1 = node_0.getChildAt(2); + node_1.style.dimensions[DIMENSION_WIDTH] = 15; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 25; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 25; + node_1.layout.dimensions[DIMENSION_WIDTH] = 30; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.layout.position[POSITION_TOP] = 0; + node_2.layout.position[POSITION_LEFT] = 0; + node_2.layout.dimensions[DIMENSION_WIDTH] = 30; + node_2.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + node_1 = node_0.getChildAt(2); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 55; + node_1.layout.dimensions[DIMENSION_WIDTH] = 15; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + test("should not shrink visible row node with siblings when there is space left over", root_node, root_layout); + } + + [Test] + public void TestCase196() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.flexDirection = CSSFlexDirection.Row; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.dimensions[DIMENSION_WIDTH] = 25; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.style.dimensions[DIMENSION_WIDTH] = 80; + node_2.style.dimensions[DIMENSION_HEIGHT] = 100; + } + node_1 = node_0.getChildAt(2); + node_1.style.dimensions[DIMENSION_WIDTH] = 15; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 25; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 25; + node_1.layout.dimensions[DIMENSION_WIDTH] = 60; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.layout.position[POSITION_TOP] = 0; + node_2.layout.position[POSITION_LEFT] = 0; + node_2.layout.dimensions[DIMENSION_WIDTH] = 80; + node_2.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + node_1 = node_0.getChildAt(2); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 85; + node_1.layout.dimensions[DIMENSION_WIDTH] = 15; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + test("should shrink visible row node with siblings when there is not any space left over", root_node, root_layout); + } + + [Test] + public void TestCase197() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.flexDirection = CSSFlexDirection.Row; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_WIDTH] = 30; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.style.dimensions[DIMENSION_WIDTH] = 40; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(2); + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_WIDTH] = 50; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 22.5f; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 22.5f; + node_1.layout.dimensions[DIMENSION_WIDTH] = 40; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(2); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 62.5f; + node_1.layout.dimensions[DIMENSION_WIDTH] = 37.5f; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + test("should shrink visible row nodes when there is not any space left over", root_node, root_layout); + } + + [Test] + public void TestCase198() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.flexDirection = CSSFlexDirection.Row; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 1); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.overflow = CSSOverflow.Hidden; + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.style.dimensions[DIMENSION_WIDTH] = 25; + node_2.style.dimensions[DIMENSION_HEIGHT] = 100; + } + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 1); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 25; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.layout.position[POSITION_TOP] = 0; + node_2.layout.position[POSITION_LEFT] = 0; + node_2.layout.dimensions[DIMENSION_WIDTH] = 25; + node_2.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + } + } + + test("should not shrink hidden row node when there is space left over", root_node, root_layout); + } + + [Test] + public void TestCase199() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.flexDirection = CSSFlexDirection.Row; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 1); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.overflow = CSSOverflow.Hidden; + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.style.dimensions[DIMENSION_WIDTH] = 200; + node_2.style.dimensions[DIMENSION_HEIGHT] = 100; + } + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 1); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.layout.position[POSITION_TOP] = 0; + node_2.layout.position[POSITION_LEFT] = 0; + node_2.layout.dimensions[DIMENSION_WIDTH] = 200; + node_2.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + } + } + + test("should shrink hidden row node when there is not any space left over", root_node, root_layout); + } + + [Test] + public void TestCase200() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.flexDirection = CSSFlexDirection.Row; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.dimensions[DIMENSION_WIDTH] = 25; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.style.overflow = CSSOverflow.Hidden; + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.style.dimensions[DIMENSION_WIDTH] = 30; + node_2.style.dimensions[DIMENSION_HEIGHT] = 100; + } + node_1 = node_0.getChildAt(2); + node_1.style.dimensions[DIMENSION_WIDTH] = 15; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 25; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 25; + node_1.layout.dimensions[DIMENSION_WIDTH] = 30; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.layout.position[POSITION_TOP] = 0; + node_2.layout.position[POSITION_LEFT] = 0; + node_2.layout.dimensions[DIMENSION_WIDTH] = 30; + node_2.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + node_1 = node_0.getChildAt(2); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 55; + node_1.layout.dimensions[DIMENSION_WIDTH] = 15; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + test("should not shrink hidden row node with siblings when there is space left over", root_node, root_layout); + } + + [Test] + public void TestCase201() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.flexDirection = CSSFlexDirection.Row; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.dimensions[DIMENSION_WIDTH] = 25; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.style.overflow = CSSOverflow.Hidden; + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.style.dimensions[DIMENSION_WIDTH] = 80; + node_2.style.dimensions[DIMENSION_HEIGHT] = 100; + } + node_1 = node_0.getChildAt(2); + node_1.style.dimensions[DIMENSION_WIDTH] = 15; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 25; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 25; + node_1.layout.dimensions[DIMENSION_WIDTH] = 60; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.layout.position[POSITION_TOP] = 0; + node_2.layout.position[POSITION_LEFT] = 0; + node_2.layout.dimensions[DIMENSION_WIDTH] = 80; + node_2.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + node_1 = node_0.getChildAt(2); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 85; + node_1.layout.dimensions[DIMENSION_WIDTH] = 15; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + test("should shrink hidden row node with siblings when there is not any space left over", root_node, root_layout); + } + + [Test] + public void TestCase202() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.flexDirection = CSSFlexDirection.Row; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.overflow = CSSOverflow.Hidden; + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_WIDTH] = 30; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.style.dimensions[DIMENSION_WIDTH] = 40; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(2); + node_1.style.overflow = CSSOverflow.Hidden; + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_WIDTH] = 50; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 22.5f; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 22.5f; + node_1.layout.dimensions[DIMENSION_WIDTH] = 40; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(2); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 62.5f; + node_1.layout.dimensions[DIMENSION_WIDTH] = 37.5f; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + test("should shrink hidden row nodes proportional to their main size when there is not any space left over", root_node, root_layout); + } + + [Test] + public void TestCase203() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.flexDirection = CSSFlexDirection.Row; + node_0.style.dimensions[DIMENSION_WIDTH] = 213; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.dimensions[DIMENSION_WIDTH] = 25; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.style.flexDirection = CSSFlexDirection.Row; + node_1.style.alignItems = CSSAlign.FlexStart; + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.setMeasureFunction(sTestMeasureFunction); + node_2.context = "loooooooooong with space"; + } + node_1 = node_0.getChildAt(2); + node_1.style.dimensions[DIMENSION_WIDTH] = 15; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 213; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 25; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 25; + node_1.layout.dimensions[DIMENSION_WIDTH] = 172; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.layout.position[POSITION_TOP] = 0; + node_2.layout.position[POSITION_LEFT] = 0; + node_2.layout.dimensions[DIMENSION_WIDTH] = 172; + node_2.layout.dimensions[DIMENSION_HEIGHT] = 18; + } + node_1 = node_0.getChildAt(2); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 197; + node_1.layout.dimensions[DIMENSION_WIDTH] = 15; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + test("should not shrink text node with siblings when there is space left over", root_node, root_layout); + } + + [Test] + public void TestCase204() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.flexDirection = CSSFlexDirection.Row; + node_0.style.dimensions[DIMENSION_WIDTH] = 140; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.dimensions[DIMENSION_WIDTH] = 25; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.style.flexDirection = CSSFlexDirection.Row; + node_1.style.alignItems = CSSAlign.FlexStart; + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.style.flex = -1; + node_2.setMeasureFunction(sTestMeasureFunction); + node_2.context = "loooooooooong with space"; + } + node_1 = node_0.getChildAt(2); + node_1.style.dimensions[DIMENSION_WIDTH] = 15; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 140; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 25; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 25; + node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.layout.position[POSITION_TOP] = 0; + node_2.layout.position[POSITION_LEFT] = 0; + node_2.layout.dimensions[DIMENSION_WIDTH] = 100; + node_2.layout.dimensions[DIMENSION_HEIGHT] = 36; + } + node_1 = node_0.getChildAt(2); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 125; + node_1.layout.dimensions[DIMENSION_WIDTH] = 15; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + test("should shrink text node with siblings when there is not any space left over", root_node, root_layout); + } + + [Test] + public void TestCase205() { TestCSSNode root_node = new TestCSSNode(); { diff --git a/src/csharp/Facebook.CSSLayout/Assertions.cs b/src/csharp/Facebook.CSSLayout/Assertions.cs index 61e88490..1bede13e 100755 --- a/src/csharp/Facebook.CSSLayout/Assertions.cs +++ b/src/csharp/Facebook.CSSLayout/Assertions.cs @@ -18,5 +18,10 @@ namespace Facebook.CSSLayout Debug.Assert(v != null); return v; } + + public static void assertCondition(bool condition, string explanation) + { + Debug.Assert(condition, explanation); + } } } diff --git a/src/csharp/Facebook.CSSLayout/CSSCachedMeasurement.cs b/src/csharp/Facebook.CSSLayout/CSSCachedMeasurement.cs new file mode 100644 index 00000000..8eabe5d3 --- /dev/null +++ b/src/csharp/Facebook.CSSLayout/CSSCachedMeasurement.cs @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2014, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +namespace Facebook.CSSLayout +{ + sealed 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/src/csharp/Facebook.CSSLayout/CSSLayout.cs b/src/csharp/Facebook.CSSLayout/CSSLayout.cs index 8cba7126..e3b8f621 100644 --- a/src/csharp/Facebook.CSSLayout/CSSLayout.cs +++ b/src/csharp/Facebook.CSSLayout/CSSLayout.cs @@ -16,6 +16,10 @@ namespace Facebook.CSSLayout 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 const int MAX_CACHED_RESULT_COUNT = 16; + public const int POSITION_LEFT = 0; public const int POSITION_TOP = 1; public const int POSITION_RIGHT = 2; @@ -25,12 +29,25 @@ namespace Facebook.CSSLayout public const int DIMENSION_HEIGHT = 1; public float[] position = new float[4]; - public float[] dimensions = new float[2]; + public float[] dimensions = { + CSSConstants.Undefined, + CSSConstants.Undefined + }; 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 = { + CSSConstants.Undefined, + CSSConstants.Undefined + }; + + public CSSCachedMeasurement cachedLayout = new CSSCachedMeasurement(); public void resetResult() { @@ -38,17 +55,18 @@ namespace Facebook.CSSLayout FillArray(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; } public override string ToString() diff --git a/src/csharp/Facebook.CSSLayout/CSSLayoutContext.cs b/src/csharp/Facebook.CSSLayout/CSSLayoutContext.cs index 29d23a0f..91fd1bd3 100755 --- a/src/csharp/Facebook.CSSLayout/CSSLayoutContext.cs +++ b/src/csharp/Facebook.CSSLayout/CSSLayoutContext.cs @@ -22,5 +22,6 @@ namespace Facebook.CSSLayout { /*package*/ public MeasureOutput measureOutput = new MeasureOutput(); + public int currentGenerationCount; } } diff --git a/src/csharp/Facebook.CSSLayout/CSSMeasureMode.cs b/src/csharp/Facebook.CSSLayout/CSSMeasureMode.cs index 3e8dc546..6f48f888 100644 --- a/src/csharp/Facebook.CSSLayout/CSSMeasureMode.cs +++ b/src/csharp/Facebook.CSSLayout/CSSMeasureMode.cs @@ -1,4 +1,4 @@ -/** +/** * Copyright (c) 2014, Facebook, Inc. * All rights reserved. * diff --git a/src/csharp/Facebook.CSSLayout/CSSNode.cs b/src/csharp/Facebook.CSSLayout/CSSNode.cs index bf42d689..736fa289 100644 --- a/src/csharp/Facebook.CSSLayout/CSSNode.cs +++ b/src/csharp/Facebook.CSSLayout/CSSNode.cs @@ -58,8 +58,7 @@ namespace Facebook.CSSLayout internal readonly CachedCSSLayout lastLayout = new CachedCSSLayout(); internal int lineIndex = 0; - internal /*package*/ CSSNode nextAbsoluteChild; - internal /*package*/ CSSNode nextFlexChild; + internal /*package*/ CSSNode nextChild; // 4 is kinda arbitrary, but the default of 10 seems really high for an average View. readonly List mChildren = new List(4); @@ -155,7 +154,6 @@ namespace Facebook.CSSLayout public void CalculateLayout() { - layout.resetResult(); LayoutEngine.layoutNode(DummyLayoutContext, this, CSSConstants.Undefined, CSSConstants.Undefined, null); } diff --git a/src/csharp/Facebook.CSSLayout/CSSOverflow.cs b/src/csharp/Facebook.CSSLayout/CSSOverflow.cs new file mode 100644 index 00000000..1a78f0cb --- /dev/null +++ b/src/csharp/Facebook.CSSLayout/CSSOverflow.cs @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2014, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +namespace Facebook.CSSLayout +{ + public enum CSSOverflow + { + Visible, + Hidden + } +} diff --git a/src/csharp/Facebook.CSSLayout/CSSStyle.cs b/src/csharp/Facebook.CSSLayout/CSSStyle.cs index e14aa010..d03f06bd 100644 --- a/src/csharp/Facebook.CSSLayout/CSSStyle.cs +++ b/src/csharp/Facebook.CSSLayout/CSSStyle.cs @@ -22,6 +22,7 @@ namespace Facebook.CSSLayout public CSSAlign alignSelf = CSSAlign.Auto; public CSSPositionType positionType = CSSPositionType.Relative; public CSSWrap flexWrap = CSSWrap.NoWrap; + public CSSOverflow overflow = CSSOverflow.Visible; public float flex; public Spacing margin = new Spacing(); diff --git a/src/csharp/Facebook.CSSLayout/Facebook.CSSLayout.csproj b/src/csharp/Facebook.CSSLayout/Facebook.CSSLayout.csproj index 0fb115e9..43c2579f 100755 --- a/src/csharp/Facebook.CSSLayout/Facebook.CSSLayout.csproj +++ b/src/csharp/Facebook.CSSLayout/Facebook.CSSLayout.csproj @@ -41,14 +41,16 @@ + - + + @@ -68,4 +70,4 @@ --> - + \ No newline at end of file diff --git a/src/csharp/Facebook.CSSLayout/LayoutEngine.cs b/src/csharp/Facebook.CSSLayout/LayoutEngine.cs index 9f448a37..175ec525 100644 --- a/src/csharp/Facebook.CSSLayout/LayoutEngine.cs +++ b/src/csharp/Facebook.CSSLayout/LayoutEngine.cs @@ -19,6 +19,8 @@ namespace Facebook.CSSLayout static class LayoutEngine { + const boolean POSITIVE_FLEX_IS_AUTO = false; + const int POSITION_LEFT = CSSLayout.POSITION_LEFT; const int POSITION_TOP = CSSLayout.POSITION_TOP; const int POSITION_RIGHT = CSSLayout.POSITION_RIGHT; @@ -80,7 +82,52 @@ namespace Facebook.CSSLayout Spacing.END }; - private static float boundAxis(CSSNode node, int axis, float value) + 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 boundAxisWithinMinAndMax(CSSNode node, int axis, float value) { float min = CSSConstants.Undefined; float max = CSSConstants.Undefined; @@ -109,29 +156,15 @@ namespace Facebook.CSSLayout return boundValue; } - - private static void setDimensionFromStyle(CSSNode node, int axis) + + private static float boundAxis(CSSNode node, int axis, float value) { - // 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]]), + float paddingAndBorderAxis = 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; + 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) @@ -145,6 +178,21 @@ namespace Facebook.CSSLayout 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); + } static int resolveAxis(int axis, CSSDirection direction) { @@ -205,394 +253,516 @@ namespace Facebook.CSSLayout return node.IsMeasureDefined; } - static boolean needsRelayout(CSSNode node, float parentMaxWidth, float parentMaxHeight) + internal static void layoutNode(CSSLayoutContext layoutContext, CSSNode node, float availableWidth, float availableHeight, CSSDirection? parentDirection) { - 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); + // 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); + } } - internal static void layoutNode(CSSLayoutContext layoutContext, CSSNode node, float parentMaxWidth, float parentMaxHeight, CSSDirection? parentDirection) + // + // 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 + // + internal static boolean layoutNodeInternal(CSSLayoutContext layoutContext, CSSNode node, float availableWidth, float availableHeight, CSSDirection? parentDirection, CSSMeasureMode widthMeasureMode, CSSMeasureMode heightMeasureMode, boolean performLayout, string reason) { - 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; + CSSLayout layout = node.layout; - layoutNodeImpl(layoutContext, node, parentMaxWidth, parentMaxHeight, parentDirection); - node.lastLayout.copy(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. + 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 { - node.layout.copy(node.lastLayout); + 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; + } + } } - node.markHasNewLayout(); - } - - static void layoutNodeImpl(CSSLayoutContext layoutContext, CSSNode node, float parentMaxWidth, float parentMaxHeight, CSSDirection? parentDirection) - { - var childCount_ = node.getChildCount(); - for (int i_ = 0; i_ < childCount_; i_++) + if (!needToVisitNode && cachedResults != null) { - node.getChildAt(i_).layout.resetResult(); + 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. + // + static void layoutNodeImpl(CSSLayoutContext layoutContext, CSSNode node, 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) { + + // Don't bother sizing the text if there's no horizontal 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.AtMost; - } - 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.AtMost; - } - 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.AtMost) ? + measureDim.width + paddingAndBorderAxisRow : + availableWidth - marginAxisRow); + node.layout.measuredDimensions[DIMENSION_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, + (heightMeasureMode == CSSMeasureMode.Undefined || heightMeasureMode == CSSMeasureMode.AtMost) ? + 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.AtMost) ? + paddingAndBorderAxisRow : + availableWidth - marginAxisRow); + node.layout.measuredDimensions[DIMENSION_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, + (heightMeasureMode == CSSMeasureMode.Undefined || heightMeasureMode == CSSMeasureMode.AtMost) ? + 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.AtMost && availableWidth <= 0 && + heightMeasureMode == CSSMeasureMode.AtMost && 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.AtMost && 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.AtMost && 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.AtMost; + } + + // 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.AtMost; + } + } + + // 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.FlexStart) || - (!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.FlexStart) || - (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 @@ -600,212 +770,300 @@ namespace Facebook.CSSLayout 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 remainingFreeSpaceAfterFlex = remainingFreeSpace; + + 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 deltaFreeSpace = 0; + 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; + 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; + deltaFlexGrowFactors -= flexGrowFactor; + } + } + } + + currentRelativeChild = currentRelativeChild.nextChild; + } + + totalFlexShrinkScaledFactors += deltaFlexShrinkScaledFactors; + totalFlexGrowFactors += deltaFlexGrowFactors; + remainingFreeSpace += deltaFreeSpace; + remainingFreeSpaceAfterFlex = remainingFreeSpace; + + // Second pass: resolve the sizes of the flexible items + 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); + } + } + + remainingFreeSpaceAfterFlex -= 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.AtMost; + } 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.AtMost; + } 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 = remainingFreeSpaceAfterFlex; + + // 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.AtMost) { + 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.FlexStart) { + // Use justifyContent to figure out how to allocate the remaining space + // available in the main axis. + if (justifyContent != CSSJustify.FlexStart) { if (justifyContent == CSSJustify.Center) { - leadingMainDim = remainingMainDim / 2; + leadingMainDim = remainingFreeSpace / 2; } else if (justifyContent == CSSJustify.FlexEnd) { - leadingMainDim = remainingMainDim; + leadingMainDim = remainingFreeSpace; } else if (justifyContent == CSSJustify.SpaceBetween) { - 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.SpaceAround) { // 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.AtMost) { + // Compute the cross axis from the max cross dimension of the children. + containerCrossAxis = boundAxis(node, crossAxis, crossDim + paddingAndBorderAxisCross) - paddingAndBorderAxisCross; + + if (measureModeCrossDim == CSSMeasureMode.AtMost) { + 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.FlexStart) { - // 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; @@ -813,41 +1071,25 @@ namespace Facebook.CSSLayout 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; @@ -858,53 +1100,54 @@ namespace Facebook.CSSLayout } 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.FlexStart) { - child.layout.position[pos[crossAxis]] = currentLead + child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]); - } else if (alignContentAlignItem == CSSAlign.FlexEnd) { - 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.FlexStart) { + child.layout.position[pos[crossAxis]] = currentLead + child.style.margin.getWithFallback(leadingSpacing[crossAxis], leading[crossAxis]); + } else if (alignContentAlignItem == CSSAlign.FlexEnd) { + 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. + } } } @@ -912,94 +1155,149 @@ namespace Facebook.CSSLayout } } - 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.AtMost) { + 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.AtMost) { + 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.AtMost; + } + + // 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.AtMost; + } + } + + 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/src/java/src/com/facebook/csslayout/CSSCachedMeasurement.java b/src/java/src/com/facebook/csslayout/CSSCachedMeasurement.java new file mode 100644 index 00000000..92b3f77b --- /dev/null +++ b/src/java/src/com/facebook/csslayout/CSSCachedMeasurement.java @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2014, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +package com.facebook.csslayout; + +public 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/src/java/src/com/facebook/csslayout/CSSLayout.java b/src/java/src/com/facebook/csslayout/CSSLayout.java index 26ec4d56..a484bd94 100644 --- a/src/java/src/com/facebook/csslayout/CSSLayout.java +++ b/src/java/src/com/facebook/csslayout/CSSLayout.java @@ -14,6 +14,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; @@ -25,24 +29,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/src/java/src/com/facebook/csslayout/CSSLayoutContext.java b/src/java/src/com/facebook/csslayout/CSSLayoutContext.java index 8c93e0eb..64612e3a 100644 --- a/src/java/src/com/facebook/csslayout/CSSLayoutContext.java +++ b/src/java/src/com/facebook/csslayout/CSSLayoutContext.java @@ -17,4 +17,5 @@ package com.facebook.csslayout; */ public class CSSLayoutContext { /*package*/ final MeasureOutput measureOutput = new MeasureOutput(); + int currentGenerationCount; } diff --git a/src/java/src/com/facebook/csslayout/CSSNode.java b/src/java/src/com/facebook/csslayout/CSSNode.java index 87c63124..011d98ed 100644 --- a/src/java/src/com/facebook/csslayout/CSSNode.java +++ b/src/java/src/com/facebook/csslayout/CSSNode.java @@ -63,9 +63,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; @@ -139,7 +138,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/src/java/src/com/facebook/csslayout/CSSOverflow.java b/src/java/src/com/facebook/csslayout/CSSOverflow.java new file mode 100644 index 00000000..d8201a37 --- /dev/null +++ b/src/java/src/com/facebook/csslayout/CSSOverflow.java @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2014, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +package com.facebook.csslayout; + +public enum CSSOverflow { + VISIBLE, + HIDDEN, +} diff --git a/src/java/src/com/facebook/csslayout/CSSStyle.java b/src/java/src/com/facebook/csslayout/CSSStyle.java index f4d325dc..ccb79265 100644 --- a/src/java/src/com/facebook/csslayout/CSSStyle.java +++ b/src/java/src/com/facebook/csslayout/CSSStyle.java @@ -23,6 +23,7 @@ public class CSSStyle { public CSSAlign alignSelf; public CSSPositionType positionType; public CSSWrap flexWrap; + public CSSOverflow overflow; public float flex; public Spacing margin = new Spacing(); @@ -51,6 +52,7 @@ public class CSSStyle { alignSelf = CSSAlign.AUTO; positionType = CSSPositionType.RELATIVE; flexWrap = CSSWrap.NOWRAP; + overflow = CSSOverflow.VISIBLE; flex = 0f; margin.reset();; diff --git a/src/java/src/com/facebook/csslayout/LayoutEngine.java b/src/java/src/com/facebook/csslayout/LayoutEngine.java index 99fe9100..92965141 100644 --- a/src/java/src/com/facebook/csslayout/LayoutEngine.java +++ b/src/java/src/com/facebook/csslayout/LayoutEngine.java @@ -8,6 +8,8 @@ */ 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; @@ -19,7 +21,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 = @@ -73,8 +77,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; @@ -99,26 +137,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) { @@ -130,6 +156,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, @@ -180,395 +220,516 @@ 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(); } + + // + // 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. + 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) { + + // Don't bother sizing the text if there's no horizontal 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 @@ -576,212 +737,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 remainingFreeSpaceAfterFlex = remainingFreeSpace; + + 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 deltaFreeSpace = 0; + 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; + 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; + deltaFlexGrowFactors -= flexGrowFactor; + } + } + } + + currentRelativeChild = currentRelativeChild.nextChild; + } + + totalFlexShrinkScaledFactors += deltaFlexShrinkScaledFactors; + totalFlexGrowFactors += deltaFlexGrowFactors; + remainingFreeSpace += deltaFreeSpace; + remainingFreeSpaceAfterFlex = remainingFreeSpace; + + // Second pass: resolve the sizes of the flexible items + 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); + } + } + + remainingFreeSpaceAfterFlex -= 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 = remainingFreeSpaceAfterFlex; + + // 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; @@ -789,41 +1038,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; @@ -834,53 +1067,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. + } } } @@ -888,93 +1122,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/src/java/tests/com/facebook/csslayout/LayoutCachingTest.java b/src/java/tests/com/facebook/csslayout/LayoutCachingTest.java index 492571cf..0fa61088 100644 --- a/src/java/tests/com/facebook/csslayout/LayoutCachingTest.java +++ b/src/java/tests/com/facebook/csslayout/LayoutCachingTest.java @@ -43,7 +43,7 @@ public class LayoutCachingTest { root.addChildAt(c0, 0); root.addChildAt(c1, 1); c0.addChildAt(c0c0, 0); - + root.calculateLayout(layoutContext); assertTreeHasNewLayout(true, root); markLayoutAppliedForTree(root); diff --git a/src/java/tests/com/facebook/csslayout/LayoutEngineTest.java b/src/java/tests/com/facebook/csslayout/LayoutEngineTest.java index 7958ffb9..54560437 100644 --- a/src/java/tests/com/facebook/csslayout/LayoutEngineTest.java +++ b/src/java/tests/com/facebook/csslayout/LayoutEngineTest.java @@ -4374,6 +4374,7 @@ public class LayoutEngineTest { node_1.context = "measureWithRatio2"; node_1 = node_0.getChildAt(1); node_1.style.flexDirection = CSSFlexDirection.ROW; + node_1.style.overflow = CSSOverflow.HIDDEN; node_1.style.dimensions[DIMENSION_HEIGHT] = 100; addChildren(node_1, 2); { @@ -4825,7 +4826,7 @@ public class LayoutEngineTest { node_1 = node_0.getChildAt(0); node_1.layout.position[POSITION_TOP] = 20; node_1.layout.position[POSITION_LEFT] = 20; - node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_WIDTH] = 60; node_1.layout.dimensions[DIMENSION_HEIGHT] = 36; addChildren(node_1, 1); { @@ -8391,7 +8392,7 @@ public class LayoutEngineTest { } } - test("should layout child whose cross axis is undefined and whose alignSelf is stretch", root_node, root_layout); + test("should layout child whose cross axis is null and whose alignSelf is stretch", root_node, root_layout); } @Test @@ -8486,6 +8487,1081 @@ public class LayoutEngineTest { @Test public void testCase188() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 1); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_WIDTH] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.style.dimensions[DIMENSION_WIDTH] = 100; + node_2.style.dimensions[DIMENSION_HEIGHT] = 25; + } + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 1); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 25; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.layout.position[POSITION_TOP] = 0; + node_2.layout.position[POSITION_LEFT] = 0; + node_2.layout.dimensions[DIMENSION_WIDTH] = 100; + node_2.layout.dimensions[DIMENSION_HEIGHT] = 25; + } + } + } + + test("should not shrink column node when there is space left over", root_node, root_layout); + } + + @Test + public void testCase189() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 1); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_WIDTH] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.style.dimensions[DIMENSION_WIDTH] = 100; + node_2.style.dimensions[DIMENSION_HEIGHT] = 200; + } + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 1); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.layout.position[POSITION_TOP] = 0; + node_2.layout.position[POSITION_LEFT] = 0; + node_2.layout.dimensions[DIMENSION_WIDTH] = 100; + node_2.layout.dimensions[DIMENSION_HEIGHT] = 200; + } + } + } + + test("should shrink column node when there is not any space left over", root_node, root_layout); + } + + @Test + public void testCase190() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.dimensions[DIMENSION_WIDTH] = 100; + node_1.style.dimensions[DIMENSION_HEIGHT] = 25; + node_1 = node_0.getChildAt(1); + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_WIDTH] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.style.dimensions[DIMENSION_WIDTH] = 100; + node_2.style.dimensions[DIMENSION_HEIGHT] = 30; + } + node_1 = node_0.getChildAt(2); + node_1.style.dimensions[DIMENSION_WIDTH] = 100; + node_1.style.dimensions[DIMENSION_HEIGHT] = 15; + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 25; + node_1 = node_0.getChildAt(1); + node_1.layout.position[POSITION_TOP] = 25; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 30; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.layout.position[POSITION_TOP] = 0; + node_2.layout.position[POSITION_LEFT] = 0; + node_2.layout.dimensions[DIMENSION_WIDTH] = 100; + node_2.layout.dimensions[DIMENSION_HEIGHT] = 30; + } + node_1 = node_0.getChildAt(2); + node_1.layout.position[POSITION_TOP] = 55; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 15; + } + } + + test("should not shrink column node with siblings when there is space left over", root_node, root_layout); + } + + @Test + public void testCase191() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.dimensions[DIMENSION_WIDTH] = 100; + node_1.style.dimensions[DIMENSION_HEIGHT] = 25; + node_1 = node_0.getChildAt(1); + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_WIDTH] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.style.dimensions[DIMENSION_WIDTH] = 100; + node_2.style.dimensions[DIMENSION_HEIGHT] = 80; + } + node_1 = node_0.getChildAt(2); + node_1.style.dimensions[DIMENSION_WIDTH] = 100; + node_1.style.dimensions[DIMENSION_HEIGHT] = 15; + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 25; + node_1 = node_0.getChildAt(1); + node_1.layout.position[POSITION_TOP] = 25; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 60; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.layout.position[POSITION_TOP] = 0; + node_2.layout.position[POSITION_LEFT] = 0; + node_2.layout.dimensions[DIMENSION_WIDTH] = 100; + node_2.layout.dimensions[DIMENSION_HEIGHT] = 80; + } + node_1 = node_0.getChildAt(2); + node_1.layout.position[POSITION_TOP] = 85; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 15; + } + } + + test("should shrink column node with siblings when there is not any space left over", root_node, root_layout); + } + + @Test + public void testCase192() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_WIDTH] = 100; + node_1.style.dimensions[DIMENSION_HEIGHT] = 30; + node_1 = node_0.getChildAt(1); + node_1.style.dimensions[DIMENSION_WIDTH] = 100; + node_1.style.dimensions[DIMENSION_HEIGHT] = 40; + node_1 = node_0.getChildAt(2); + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_WIDTH] = 100; + node_1.style.dimensions[DIMENSION_HEIGHT] = 50; + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 22.5f; + node_1 = node_0.getChildAt(1); + node_1.layout.position[POSITION_TOP] = 22.5f; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 40; + node_1 = node_0.getChildAt(2); + node_1.layout.position[POSITION_TOP] = 62.5f; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 37.5f; + } + } + + test("should shrink column nodes proportional to their main size when there is not any space left over", root_node, root_layout); + } + + @Test + public void testCase193() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.flexDirection = CSSFlexDirection.ROW; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 1); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.style.dimensions[DIMENSION_WIDTH] = 25; + node_2.style.dimensions[DIMENSION_HEIGHT] = 100; + } + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 1); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 25; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.layout.position[POSITION_TOP] = 0; + node_2.layout.position[POSITION_LEFT] = 0; + node_2.layout.dimensions[DIMENSION_WIDTH] = 25; + node_2.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + } + } + + test("should not shrink visible row node when there is space left over", root_node, root_layout); + } + + @Test + public void testCase194() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.flexDirection = CSSFlexDirection.ROW; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 1); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.style.dimensions[DIMENSION_WIDTH] = 200; + node_2.style.dimensions[DIMENSION_HEIGHT] = 100; + } + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 1); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.layout.position[POSITION_TOP] = 0; + node_2.layout.position[POSITION_LEFT] = 0; + node_2.layout.dimensions[DIMENSION_WIDTH] = 200; + node_2.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + } + } + + test("should shrink visible row node when there is not any space left over", root_node, root_layout); + } + + @Test + public void testCase195() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.flexDirection = CSSFlexDirection.ROW; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.dimensions[DIMENSION_WIDTH] = 25; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.style.dimensions[DIMENSION_WIDTH] = 30; + node_2.style.dimensions[DIMENSION_HEIGHT] = 100; + } + node_1 = node_0.getChildAt(2); + node_1.style.dimensions[DIMENSION_WIDTH] = 15; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 25; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 25; + node_1.layout.dimensions[DIMENSION_WIDTH] = 30; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.layout.position[POSITION_TOP] = 0; + node_2.layout.position[POSITION_LEFT] = 0; + node_2.layout.dimensions[DIMENSION_WIDTH] = 30; + node_2.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + node_1 = node_0.getChildAt(2); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 55; + node_1.layout.dimensions[DIMENSION_WIDTH] = 15; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + test("should not shrink visible row node with siblings when there is space left over", root_node, root_layout); + } + + @Test + public void testCase196() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.flexDirection = CSSFlexDirection.ROW; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.dimensions[DIMENSION_WIDTH] = 25; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.style.dimensions[DIMENSION_WIDTH] = 80; + node_2.style.dimensions[DIMENSION_HEIGHT] = 100; + } + node_1 = node_0.getChildAt(2); + node_1.style.dimensions[DIMENSION_WIDTH] = 15; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 25; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 25; + node_1.layout.dimensions[DIMENSION_WIDTH] = 60; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.layout.position[POSITION_TOP] = 0; + node_2.layout.position[POSITION_LEFT] = 0; + node_2.layout.dimensions[DIMENSION_WIDTH] = 80; + node_2.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + node_1 = node_0.getChildAt(2); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 85; + node_1.layout.dimensions[DIMENSION_WIDTH] = 15; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + test("should shrink visible row node with siblings when there is not any space left over", root_node, root_layout); + } + + @Test + public void testCase197() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.flexDirection = CSSFlexDirection.ROW; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_WIDTH] = 30; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.style.dimensions[DIMENSION_WIDTH] = 40; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(2); + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_WIDTH] = 50; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 22.5f; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 22.5f; + node_1.layout.dimensions[DIMENSION_WIDTH] = 40; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(2); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 62.5f; + node_1.layout.dimensions[DIMENSION_WIDTH] = 37.5f; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + test("should shrink visible row nodes when there is not any space left over", root_node, root_layout); + } + + @Test + public void testCase198() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.flexDirection = CSSFlexDirection.ROW; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 1); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.overflow = CSSOverflow.HIDDEN; + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.style.dimensions[DIMENSION_WIDTH] = 25; + node_2.style.dimensions[DIMENSION_HEIGHT] = 100; + } + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 1); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 25; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.layout.position[POSITION_TOP] = 0; + node_2.layout.position[POSITION_LEFT] = 0; + node_2.layout.dimensions[DIMENSION_WIDTH] = 25; + node_2.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + } + } + + test("should not shrink hidden row node when there is space left over", root_node, root_layout); + } + + @Test + public void testCase199() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.flexDirection = CSSFlexDirection.ROW; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 1); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.overflow = CSSOverflow.HIDDEN; + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.style.dimensions[DIMENSION_WIDTH] = 200; + node_2.style.dimensions[DIMENSION_HEIGHT] = 100; + } + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 1); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.layout.position[POSITION_TOP] = 0; + node_2.layout.position[POSITION_LEFT] = 0; + node_2.layout.dimensions[DIMENSION_WIDTH] = 200; + node_2.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + } + } + + test("should shrink hidden row node when there is not any space left over", root_node, root_layout); + } + + @Test + public void testCase200() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.flexDirection = CSSFlexDirection.ROW; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.dimensions[DIMENSION_WIDTH] = 25; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.style.overflow = CSSOverflow.HIDDEN; + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.style.dimensions[DIMENSION_WIDTH] = 30; + node_2.style.dimensions[DIMENSION_HEIGHT] = 100; + } + node_1 = node_0.getChildAt(2); + node_1.style.dimensions[DIMENSION_WIDTH] = 15; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 25; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 25; + node_1.layout.dimensions[DIMENSION_WIDTH] = 30; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.layout.position[POSITION_TOP] = 0; + node_2.layout.position[POSITION_LEFT] = 0; + node_2.layout.dimensions[DIMENSION_WIDTH] = 30; + node_2.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + node_1 = node_0.getChildAt(2); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 55; + node_1.layout.dimensions[DIMENSION_WIDTH] = 15; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + test("should not shrink hidden row node with siblings when there is space left over", root_node, root_layout); + } + + @Test + public void testCase201() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.flexDirection = CSSFlexDirection.ROW; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.dimensions[DIMENSION_WIDTH] = 25; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.style.overflow = CSSOverflow.HIDDEN; + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.style.dimensions[DIMENSION_WIDTH] = 80; + node_2.style.dimensions[DIMENSION_HEIGHT] = 100; + } + node_1 = node_0.getChildAt(2); + node_1.style.dimensions[DIMENSION_WIDTH] = 15; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 25; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 25; + node_1.layout.dimensions[DIMENSION_WIDTH] = 60; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.layout.position[POSITION_TOP] = 0; + node_2.layout.position[POSITION_LEFT] = 0; + node_2.layout.dimensions[DIMENSION_WIDTH] = 80; + node_2.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + node_1 = node_0.getChildAt(2); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 85; + node_1.layout.dimensions[DIMENSION_WIDTH] = 15; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + test("should shrink hidden row node with siblings when there is not any space left over", root_node, root_layout); + } + + @Test + public void testCase202() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.flexDirection = CSSFlexDirection.ROW; + node_0.style.dimensions[DIMENSION_WIDTH] = 100; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.overflow = CSSOverflow.HIDDEN; + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_WIDTH] = 30; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.style.dimensions[DIMENSION_WIDTH] = 40; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(2); + node_1.style.overflow = CSSOverflow.HIDDEN; + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_WIDTH] = 50; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 100; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 22.5f; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 22.5f; + node_1.layout.dimensions[DIMENSION_WIDTH] = 40; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(2); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 62.5f; + node_1.layout.dimensions[DIMENSION_WIDTH] = 37.5f; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + test("should shrink hidden row nodes proportional to their main size when there is not any space left over", root_node, root_layout); + } + + @Test + public void testCase203() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.flexDirection = CSSFlexDirection.ROW; + node_0.style.dimensions[DIMENSION_WIDTH] = 213; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.dimensions[DIMENSION_WIDTH] = 25; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.style.flexDirection = CSSFlexDirection.ROW; + node_1.style.alignItems = CSSAlign.FLEX_START; + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.setMeasureFunction(sTestMeasureFunction); + node_2.context = "loooooooooong with space"; + } + node_1 = node_0.getChildAt(2); + node_1.style.dimensions[DIMENSION_WIDTH] = 15; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 213; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 25; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 25; + node_1.layout.dimensions[DIMENSION_WIDTH] = 172; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.layout.position[POSITION_TOP] = 0; + node_2.layout.position[POSITION_LEFT] = 0; + node_2.layout.dimensions[DIMENSION_WIDTH] = 172; + node_2.layout.dimensions[DIMENSION_HEIGHT] = 18; + } + node_1 = node_0.getChildAt(2); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 197; + node_1.layout.dimensions[DIMENSION_WIDTH] = 15; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + test("should not shrink text node with siblings when there is space left over", root_node, root_layout); + } + + @Test + public void testCase204() + { + TestCSSNode root_node = new TestCSSNode(); + { + TestCSSNode node_0 = root_node; + node_0.style.flexDirection = CSSFlexDirection.ROW; + node_0.style.dimensions[DIMENSION_WIDTH] = 140; + node_0.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.style.dimensions[DIMENSION_WIDTH] = 25; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.style.flexDirection = CSSFlexDirection.ROW; + node_1.style.alignItems = CSSAlign.FLEX_START; + node_1.style.flex = -1; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.style.flex = -1; + node_2.setMeasureFunction(sTestMeasureFunction); + node_2.context = "loooooooooong with space"; + } + node_1 = node_0.getChildAt(2); + node_1.style.dimensions[DIMENSION_WIDTH] = 15; + node_1.style.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + TestCSSNode root_layout = new TestCSSNode(); + { + TestCSSNode node_0 = root_layout; + node_0.layout.position[POSITION_TOP] = 0; + node_0.layout.position[POSITION_LEFT] = 0; + node_0.layout.dimensions[DIMENSION_WIDTH] = 140; + node_0.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_0, 3); + { + TestCSSNode node_1; + node_1 = node_0.getChildAt(0); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 0; + node_1.layout.dimensions[DIMENSION_WIDTH] = 25; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + node_1 = node_0.getChildAt(1); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 25; + node_1.layout.dimensions[DIMENSION_WIDTH] = 100; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + addChildren(node_1, 1); + { + TestCSSNode node_2; + node_2 = node_1.getChildAt(0); + node_2.layout.position[POSITION_TOP] = 0; + node_2.layout.position[POSITION_LEFT] = 0; + node_2.layout.dimensions[DIMENSION_WIDTH] = 100; + node_2.layout.dimensions[DIMENSION_HEIGHT] = 36; + } + node_1 = node_0.getChildAt(2); + node_1.layout.position[POSITION_TOP] = 0; + node_1.layout.position[POSITION_LEFT] = 125; + node_1.layout.dimensions[DIMENSION_WIDTH] = 15; + node_1.layout.dimensions[DIMENSION_HEIGHT] = 100; + } + } + + test("should shrink text node with siblings when there is not any space left over", root_node, root_layout); + } + + @Test + public void testCase205() { TestCSSNode root_node = new TestCSSNode(); { diff --git a/src/transpile.js b/src/transpile.js index e2ffa16a..8d3fa24d 100644 --- a/src/transpile.js +++ b/src/transpile.js @@ -39,8 +39,7 @@ global.layoutTestUtils = { }; global.describe = function(name, cb) { - if (name === 'Layout' || - name === 'Layout alignContent') { + if (name.toLowerCase().indexOf('javascript only') === -1) { cb(); } }; @@ -175,6 +174,10 @@ function printLayout(test) { 'exactly': 'CSS_MEASURE_MODE_EXACTLY', 'at-most': 'CSS_MEASURE_MODE_AT_MOST' }); + addEnum(node, 'overflow', 'overflow', { + 'visible': 'CSS_OVERFLOW_VISIBLE', + 'hidden': 'CSS_OVERFLOW_HIDDEN' + }); addFloat(node, 'flex', 'flex'); addFloat(node, 'width', 'dimensions[CSS_WIDTH]'); addFloat(node, 'height', 'dimensions[CSS_HEIGHT]'); @@ -256,8 +259,13 @@ function printLayout(test) { function transpileAnnotatedJStoC(jsCode) { return jsCode + .replace(/'abs-layout'/g, '"abs-layout"') + .replace(/'abs-measure'/g, '"abs-measure"') + .replace(/'flex'/g, '"flex"') + .replace(/'measure'/g, '"measure"') + .replace(/'stretch'/g, '"stretch"') .replace('node.style.measure', 'node.measure') - .replace(/null/g, 'NULL') + .replace(/undefined/g, 'NULL') .replace(/\.children\.length/g, '.children_count') .replace(/\.width/g, '.dimensions[CSS_WIDTH]') .replace(/\.height/g, '.dimensions[CSS_HEIGHT]') @@ -266,23 +274,30 @@ function transpileAnnotatedJStoC(jsCode) { .replace(/\.minWidth/g, '.minDimensions[CSS_WIDTH]') .replace(/\.minHeight/g, '.minDimensions[CSS_HEIGHT]') .replace(/\.lineIndex/g, '.line_index') - .replace(/\.nextAbsoluteChild/g, '.next_absolute_child') - .replace(/\.nextFlexChild/g, '.next_flex_child') - .replace(/layout\[dim/g, 'layout.dimensions[dim') + .replace(/\.nextChild/g, '.next_child') + .replace(/\.flexBasis/g, '.flex_basis') .replace(/layout\[pos/g, 'layout.position[pos') .replace(/layout\[leading/g, 'layout.position[leading') .replace(/layout\[trailing/g, 'layout.position[trailing') + .replace(/layout\[measuredDim/g, 'layout.measured_dimensions[dim') + .replace(/layout\.measuredWidth/g, 'layout.measured_dimensions[CSS_WIDTH]') + .replace(/layout\.measuredHeight/g, 'layout.measured_dimensions[CSS_HEIGHT]') .replace(/style\[dim/g, 'style.dimensions[dim') + .replace(/style\[CSS_LEFT/g, 'style.position[CSS_LEFT') + .replace(/style\[CSS_TOP/g, 'style.position[CSS_TOP') + .replace(/style\[CSS_RIGHT/g, 'style.position[CSS_RIGHT') + .replace(/style\[CSS_BOTTOM/g, 'style.position[CSS_BOTTOM') .replace(/node.children\[i\]/g, 'node->get_child(node->context, i)') - .replace(/node.children\[ii\]/g, 'node->get_child(node->context, ii)') + .replace(/node.children\[j\]/g, 'node->get_child(node->context, j)') .replace(/node\./g, 'node->') .replace(/child\./g, 'child->') - .replace(/parent\./g, 'parent->') .replace(/currentAbsoluteChild\./g, 'currentAbsoluteChild->') - .replace(/currentFlexChild\./g, 'currentFlexChild->') + .replace(/currentRelativeChild\./g, 'currentRelativeChild->') .replace(/getPositionType\((.+?)\)/g, '$1->style.position_type') .replace(/getJustifyContent\((.+?)\)/g, '$1->style.justify_content') .replace(/getAlignContent\((.+?)\)/g, '$1->style.align_content') + .replace(/assert\((.+?),\s*'(.+?)'\);/g, 'assert($1); // $2') + .replace(/getOverflow\((.+?)\)/g, '$1->style.overflow') .replace(/var\/\*\(c\)!([^*]+)\*\//g, '$1') .replace(/var\/\*([^\/]+)\*\//g, '$1') .replace(/ === /g, ' == ') @@ -290,8 +305,7 @@ function transpileAnnotatedJStoC(jsCode) { .replace(/\n {2}/g, '\n') .replace(/\/\*\(c\)!([^*]+)\*\//g, '$1') .replace(/\/[*]!([^*]+)[*]\//g, '$1') - .replace(/\/\*\(java\)!([^*]+)\*\//g, '') - .split('\n').slice(1, -1).join('\n'); + .replace(/\/\*\(java\)!([^*]+)\*\//g, ''); } function makeConstDefs() { @@ -318,14 +332,18 @@ function generateFile(fileName, generatedContent) { fs.writeFileSync(fileName, content); } +// Extract the function body by trimming the first ('function layoutNode(...) {') and +// last ('}') lines. Also, start the function body with a blank line so that regexes +// that use \n to match the start of a line will match the actual first line. +var computeLayoutCode = [''].concat(computeLayout.toString().split('\n').slice(1, -1)).join('\n'); var allTestsInC = allTests.map(printLayout); generateFile(__dirname + '/__tests__/Layout-test.c', allTestsInC.join('\n\n')); generateFile(__dirname + '/Layout-test-utils.c', makeConstDefs()); -generateFile(__dirname + '/Layout.c', transpileAnnotatedJStoC(computeLayout.toString())); -generateFile(__dirname + '/java/src/com/facebook/csslayout/LayoutEngine.java', JavaTranspiler.transpileLayoutEngine(computeLayout.toString())); +generateFile(__dirname + '/Layout.c', transpileAnnotatedJStoC(computeLayoutCode)); +generateFile(__dirname + '/java/src/com/facebook/csslayout/LayoutEngine.java', JavaTranspiler.transpileLayoutEngine(computeLayoutCode)); generateFile(__dirname + '/java/tests/com/facebook/csslayout/TestConstants.java', JavaTranspiler.transpileCConstDefs(makeConstDefs())); generateFile(__dirname + '/java/tests/com/facebook/csslayout/LayoutEngineTest.java', JavaTranspiler.transpileCTestsArray(allTestsInC)); -generateFile(__dirname + '/csharp/Facebook.CSSLayout/LayoutEngine.cs', CSharpTranspiler.transpileLayoutEngine(computeLayout.toString())); +generateFile(__dirname + '/csharp/Facebook.CSSLayout/LayoutEngine.cs', CSharpTranspiler.transpileLayoutEngine(computeLayoutCode)); generateFile(__dirname + '/csharp/Facebook.CSSLayout.Tests/TestConstants.cs', CSharpTranspiler.transpileCConstDefs(makeConstDefs())); generateFile(__dirname + '/csharp/Facebook.CSSLayout.Tests/LayoutEngineTest.cs', CSharpTranspiler.transpileCTestsArray(allTestsInC));