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));