From 8afcaa36e661c921bded4ab4127cc6ed785c9004 Mon Sep 17 00:00:00 2001 From: Emil Sjolander Date: Mon, 13 Jun 2016 07:52:14 -0700 Subject: [PATCH] Import latest css-layout Summary: Import latest master from css-layout. This includes the following pull requests: https://github.com/facebook/css-layout/pull/192 https://github.com/facebook/css-layout/pull/193 https://github.com/facebook/css-layout/pull/195 https://github.com/facebook/css-layout/pull/196 Reviewed By: javache Differential Revision: D3411535 fbshipit-source-id: 95bee9bd0282f98f6fafa15335b910b644395ad3 --- React/Layout/Layout.c | 122 ++++++-- React/Layout/Layout.h | 1 + .../csslayout/CSSCachedMeasurement.java | 4 +- .../com/facebook/csslayout/CSSLayout.java | 24 +- .../java/com/facebook/csslayout/CSSNode.java | 83 ++++- .../com/facebook/csslayout/LayoutEngine.java | 295 +++++++++++------- .../main/java/com/facebook/csslayout/README | 2 +- .../com/facebook/csslayout/README.facebook | 2 +- 8 files changed, 383 insertions(+), 150 deletions(-) diff --git a/React/Layout/Layout.c b/React/Layout/Layout.c index d94535b30b..e1fe7221db 100644 --- a/React/Layout/Layout.c +++ b/React/Layout/Layout.c @@ -871,6 +871,25 @@ } } + // If child has no defined size in the cross axis and is set to stretch, set the cross + // axis to be measured exactly with the available inner width + if (!isMainAxisRow && + !isUndefined(availableInnerWidth) && + !isStyleDimDefined(child, CSS_FLEX_DIRECTION_ROW) && + widthMeasureMode == CSS_MEASURE_MODE_EXACTLY && + getAlignItem(node, child) == CSS_ALIGN_STRETCH) { + childWidth = availableInnerWidth; + childWidthMeasureMode = CSS_MEASURE_MODE_EXACTLY; + } + if (isMainAxisRow && + !isUndefined(availableInnerHeight) && + !isStyleDimDefined(child, CSS_FLEX_DIRECTION_COLUMN) && + heightMeasureMode == CSS_MEASURE_MODE_EXACTLY && + getAlignItem(node, child) == CSS_ALIGN_STRETCH) { + childHeight = availableInnerHeight; + childHeightMeasureMode = CSS_MEASURE_MODE_EXACTLY; + } + // Measure the child layoutNodeInternal(child, childWidth, childHeight, direction, childWidthMeasureMode, childHeightMeasureMode, false, "measure"); @@ -1080,7 +1099,13 @@ childWidth = updatedMainSize + getMarginAxis(currentRelativeChild, CSS_FLEX_DIRECTION_ROW); childWidthMeasureMode = CSS_MEASURE_MODE_EXACTLY; - if (!isStyleDimDefined(currentRelativeChild, CSS_FLEX_DIRECTION_COLUMN)) { + if (!isUndefined(availableInnerCrossDim) && + !isStyleDimDefined(currentRelativeChild, CSS_FLEX_DIRECTION_COLUMN) && + heightMeasureMode == CSS_MEASURE_MODE_EXACTLY && + getAlignItem(node, currentRelativeChild) == CSS_ALIGN_STRETCH) { + childHeight = availableInnerCrossDim; + childHeightMeasureMode = CSS_MEASURE_MODE_EXACTLY; + } else if (!isStyleDimDefined(currentRelativeChild, CSS_FLEX_DIRECTION_COLUMN)) { childHeight = availableInnerCrossDim; childHeightMeasureMode = isUndefined(childHeight) ? CSS_MEASURE_MODE_UNDEFINED : CSS_MEASURE_MODE_AT_MOST; } else { @@ -1091,7 +1116,13 @@ childHeight = updatedMainSize + getMarginAxis(currentRelativeChild, CSS_FLEX_DIRECTION_COLUMN); childHeightMeasureMode = CSS_MEASURE_MODE_EXACTLY; - if (!isStyleDimDefined(currentRelativeChild, CSS_FLEX_DIRECTION_ROW)) { + if (!isUndefined(availableInnerCrossDim) && + !isStyleDimDefined(currentRelativeChild, CSS_FLEX_DIRECTION_ROW) && + widthMeasureMode == CSS_MEASURE_MODE_EXACTLY && + getAlignItem(node, currentRelativeChild) == CSS_ALIGN_STRETCH) { + childWidth = availableInnerCrossDim; + childWidthMeasureMode = CSS_MEASURE_MODE_EXACTLY; + } else if (!isStyleDimDefined(currentRelativeChild, CSS_FLEX_DIRECTION_ROW)) { childWidth = availableInnerCrossDim; childWidthMeasureMode = isUndefined(childWidth) ? CSS_MEASURE_MODE_UNDEFINED : CSS_MEASURE_MODE_AT_MOST; } else { @@ -1528,35 +1559,80 @@ return performLayout? kLayoutModeNames[mode] : kMeasureModeNames[mode]; } - static bool canUseCachedMeasurement(float availableWidth, float availableHeight, - float marginRow, float marginColumn, - css_measure_mode_t widthMeasureMode, css_measure_mode_t heightMeasureMode, - css_cached_measurement_t cachedLayout) { + static bool canUseCachedMeasurement( + bool is_text_node, + float available_width, + float available_height, + float margin_row, + float margin_column, + css_measure_mode_t width_measure_mode, + css_measure_mode_t height_measure_mode, + css_cached_measurement_t cached_layout) { - // Is it an exact match? - if (eq(cachedLayout.available_width, availableWidth) && - eq(cachedLayout.available_height, availableHeight) && - cachedLayout.width_measure_mode == widthMeasureMode && - cachedLayout.height_measure_mode == heightMeasureMode) { + bool is_height_same = + (cached_layout.height_measure_mode == CSS_MEASURE_MODE_UNDEFINED && height_measure_mode == CSS_MEASURE_MODE_UNDEFINED) || + (cached_layout.height_measure_mode == height_measure_mode && eq(cached_layout.available_height, available_height)); + + bool is_width_same = + (cached_layout.width_measure_mode == CSS_MEASURE_MODE_UNDEFINED && width_measure_mode == CSS_MEASURE_MODE_UNDEFINED) || + (cached_layout.width_measure_mode == width_measure_mode && eq(cached_layout.available_width, available_width)); + + if (is_height_same && is_width_same) { return true; } - // If the width is an exact match, try a fuzzy match on the height. - if (cachedLayout.width_measure_mode == widthMeasureMode && - eq(cachedLayout.available_width, availableWidth) && - heightMeasureMode == CSS_MEASURE_MODE_EXACTLY && - eq(availableHeight - marginColumn, cachedLayout.computed_height)) { + bool is_height_valid = + (cached_layout.height_measure_mode == CSS_MEASURE_MODE_UNDEFINED && height_measure_mode == CSS_MEASURE_MODE_AT_MOST && cached_layout.computed_height <= (available_height - margin_column)) || + (height_measure_mode == CSS_MEASURE_MODE_EXACTLY && eq(cached_layout.computed_height, available_height - margin_column)); + + if (is_width_same && is_height_valid) { return true; } - // If the height is an exact match, try a fuzzy match on the width. - if (cachedLayout.height_measure_mode == heightMeasureMode && - eq(cachedLayout.available_height, availableHeight) && - widthMeasureMode == CSS_MEASURE_MODE_EXACTLY && - eq(availableWidth - marginRow, cachedLayout.computed_width)) { + bool is_width_valid = + (cached_layout.width_measure_mode == CSS_MEASURE_MODE_UNDEFINED && width_measure_mode == CSS_MEASURE_MODE_AT_MOST && cached_layout.computed_width <= (available_width - margin_row)) || + (width_measure_mode == CSS_MEASURE_MODE_EXACTLY && eq(cached_layout.computed_width, available_width - margin_row)); + + if (is_height_same && is_width_valid) { return true; } + if (is_height_valid && is_width_valid) { + return true; + } + + // We know this to be text so we can apply some more specialized heuristics. + if (is_text_node) { + if (is_width_same) { + if (height_measure_mode == CSS_MEASURE_MODE_UNDEFINED) { + // Width is the same and height is not restricted. Re-use cahced value. + return true; + } + + if (height_measure_mode == CSS_MEASURE_MODE_AT_MOST && + cached_layout.computed_height < (available_height - margin_column)) { + // Width is the same and height restriction is greater than the cached height. Re-use cached value. + return true; + } + + // Width is the same but height restriction imposes smaller height than previously measured. + // Update the cached value to respect the new height restriction. + cached_layout.computed_height = available_height - margin_column; + return true; + } + + if (cached_layout.width_measure_mode == CSS_MEASURE_MODE_UNDEFINED) { + if (width_measure_mode == CSS_MEASURE_MODE_UNDEFINED || + (width_measure_mode == CSS_MEASURE_MODE_AT_MOST && + cached_layout.computed_width <= (available_width - margin_row))) { + // Previsouly this text was measured with no width restriction, if width is now restricted + // but to a larger value than the previsouly measured width we can re-use the measurement + // as we know it will fit. + return true; + } + } + } + return false; } @@ -1598,13 +1674,13 @@ float marginAxisColumn = getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN); // First, try to use the layout cache. - if (canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn, + if (canUseCachedMeasurement(node->is_text_node && node->is_text_node(node->context), availableWidth, availableHeight, marginAxisRow, marginAxisColumn, widthMeasureMode, heightMeasureMode, layout->cached_layout)) { cachedResults = &layout->cached_layout; } else { // Try to use the measurement cache. for (int i = 0; i < layout->next_cached_measurements_index; i++) { - if (canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn, + if (canUseCachedMeasurement(node->is_text_node && node->is_text_node(node->context), availableWidth, availableHeight, marginAxisRow, marginAxisColumn, widthMeasureMode, heightMeasureMode, layout->cached_measurements[i])) { cachedResults = &layout->cached_measurements[i]; break; diff --git a/React/Layout/Layout.h b/React/Layout/Layout.h index 59d702e0d1..f726cd746e 100644 --- a/React/Layout/Layout.h +++ b/React/Layout/Layout.h @@ -188,6 +188,7 @@ void (*print)(void *context); struct css_node* (*get_child)(void *context, int i); bool (*is_dirty)(void *context); + bool (*is_text_node)(void *context); void *context; }; diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSCachedMeasurement.java b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSCachedMeasurement.java index 11b188293e..aac36f649c 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSCachedMeasurement.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSCachedMeasurement.java @@ -7,7 +7,7 @@ */ // NOTE: this file is auto-copied from https://github.com/facebook/css-layout -// @generated SignedSource<> +// @generated SignedSource<<7463b170625c103400318b8a42378786>> package com.facebook.csslayout; @@ -16,7 +16,7 @@ public class CSSCachedMeasurement { public float availableHeight; public CSSMeasureMode widthMeasureMode = null; public CSSMeasureMode heightMeasureMode = null; - + public float computedWidth; public float computedHeight; } diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSLayout.java b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSLayout.java index 60f33fe5b5..23bfaf248c 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSLayout.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSLayout.java @@ -7,7 +7,7 @@ */ // NOTE: this file is auto-copied from https://github.com/facebook/css-layout -// @generated SignedSource<<4ed6ae4c11cdd41f5db380e3b9a06f23>> +// @generated SignedSource<> package com.facebook.csslayout; @@ -20,7 +20,7 @@ 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; @@ -32,36 +32,36 @@ public class CSSLayout { public float[] position = new float[4]; public float[] dimensions = new float[2]; public CSSDirection direction = CSSDirection.LTR; - + 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; - + flexBasis = 0; - + generationCount = 0; lastParentDirection = null; - + nextCachedMeasurementsIndex = 0; measuredDimensions[DIMENSION_WIDTH] = CSSConstants.UNDEFINED; measuredDimensions[DIMENSION_HEIGHT] = CSSConstants.UNDEFINED; - + cachedLayout.widthMeasureMode = null; cachedLayout.heightMeasureMode = null; } diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSNode.java b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSNode.java index 6145219f70..b9cacf6804 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/CSSNode.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/CSSNode.java @@ -7,7 +7,7 @@ */ // NOTE: this file is auto-copied from https://github.com/facebook/css-layout -// @generated SignedSource<<67fbba6df7c2472877c7b04327fb1863>> +// @generated SignedSource<> package com.facebook.csslayout; @@ -67,11 +67,12 @@ public class CSSNode { public int lineIndex = 0; /*package*/ CSSNode nextChild; - + private @Nullable ArrayList mChildren; private @Nullable CSSNode mParent; private @Nullable MeasureFunction mMeasureFunction = null; private LayoutState mLayoutState = LayoutState.DIRTY; + private boolean mIsTextNode = false; public int getChildCount() { return mChildren == null ? 0 : mChildren.size(); @@ -127,6 +128,14 @@ public class CSSNode { return mMeasureFunction != null; } + public void setIsTextNode(boolean isTextNode) { + mIsTextNode = isTextNode; + } + + public boolean isTextNode() { + return mIsTextNode; + } + /*package*/ MeasureOutput measure(MeasureOutput measureOutput, float width, CSSMeasureMode widthMode, float height, CSSMeasureMode heightMode) { if (!isMeasureDefined()) { throw new RuntimeException("Measure function isn't defined!"); @@ -450,6 +459,62 @@ public class CSSNode { } } + /** + * Get this node's max width, as defined in the style + */ + public float getStyleMaxWidth() { + return style.maxWidth; + } + + public void setStyleMaxWidth(float maxWidth) { + if (!valuesEqual(style.maxWidth, maxWidth)) { + style.maxWidth = maxWidth; + dirty(); + } + } + + /** + * Get this node's min width, as defined in the style + */ + public float getStyleMinWidth() { + return style.minWidth; + } + + public void setStyleMinWidth(float minWidth) { + if (!valuesEqual(style.minWidth, minWidth)) { + style.minWidth = minWidth; + dirty(); + } + } + + /** + * Get this node's max height, as defined in the style + */ + public float getStyleMaxHeight() { + return style.maxHeight; + } + + public void setStyleMaxHeight(float maxHeight) { + if (!valuesEqual(style.maxHeight, maxHeight)) { + style.maxHeight = maxHeight; + dirty(); + } + } + + /** + * Get this node's min height, as defined in the style + */ + public float getStyleMinHeight() { + return style.minHeight; + } + + public void setStyleMinHeight(float minHeight) { + if (!valuesEqual(style.minHeight, minHeight)) { + style.minHeight = minHeight; + dirty(); + } + } + public float getLayoutX() { return layout.position[POSITION_LEFT]; } @@ -479,6 +544,20 @@ public class CSSNode { } } + /** + * Get this node's overflow property, as defined in the style + */ + public CSSOverflow getOverflow() { + return style.overflow; + } + + public void setOverflow(CSSOverflow overflow) { + if (style.overflow != overflow) { + style.overflow = overflow; + dirty(); + } + } + /** * Resets this instance to its default state. This method is meant to be used when * recycling {@link CSSNode} instances. diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/LayoutEngine.java b/ReactAndroid/src/main/java/com/facebook/csslayout/LayoutEngine.java index c8ef501141..042b253660 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/LayoutEngine.java +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/LayoutEngine.java @@ -7,7 +7,7 @@ */ // NOTE: this file is auto-copied from https://github.com/facebook/css-layout -// @generated SignedSource<<9165e7546c964b47cb01be9ff8070aa8>> +// @generated SignedSource<> package com.facebook.csslayout; @@ -24,9 +24,9 @@ import static com.facebook.csslayout.CSSLayout.POSITION_TOP; * Calculates layouts based on CSS style. See {@link #layoutNode(CSSNode, float, float)}. */ public class LayoutEngine { - + private static final boolean POSITIVE_FLEX_IS_AUTO = false; - + private static final int CSS_FLEX_DIRECTION_COLUMN = CSSFlexDirection.COLUMN.ordinal(); private static final int CSS_FLEX_DIRECTION_COLUMN_REVERSE = @@ -80,7 +80,7 @@ public class LayoutEngine { Spacing.END, Spacing.END }; - + private static boolean isFlexBasisAuto(CSSNode node) { if (POSITIVE_FLEX_IS_AUTO) { // All flex values are auto. @@ -90,7 +90,7 @@ public class LayoutEngine { 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) { @@ -98,7 +98,7 @@ public class LayoutEngine { } 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. @@ -140,7 +140,7 @@ public class LayoutEngine { return boundValue; } - + private static float boundAxis(CSSNode node, int axis, float value) { float paddingAndBorderAxis = node.style.padding.getWithFallback(leadingSpacing[axis], leading[axis]) + @@ -159,11 +159,11 @@ 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]) + @@ -233,7 +233,7 @@ public class LayoutEngine { // 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) { @@ -244,46 +244,92 @@ public class LayoutEngine { 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); } } - - /*package*/ static boolean canUseCachedMeasurement(float availableWidth, float availableHeight, - float marginRow, float marginColumn, - CSSMeasureMode widthMeasureMode, CSSMeasureMode heightMeasureMode, + + /*package*/ static boolean canUseCachedMeasurement( + boolean isTextNode, + float availableWidth, + float availableHeight, + float marginRow, + float marginColumn, + CSSMeasureMode widthMeasureMode, + CSSMeasureMode heightMeasureMode, CSSCachedMeasurement cachedLayout) { - // Is it an exact match? - if (FloatUtil.floatsEqual(cachedLayout.availableWidth, availableWidth) && - FloatUtil.floatsEqual(cachedLayout.availableHeight, availableHeight) && - cachedLayout.widthMeasureMode == widthMeasureMode && - cachedLayout.heightMeasureMode == heightMeasureMode) { + + boolean isHeightSame = + (cachedLayout.heightMeasureMode == CSSMeasureMode.UNDEFINED && heightMeasureMode == CSSMeasureMode.UNDEFINED) || + (cachedLayout.heightMeasureMode == heightMeasureMode && FloatUtil.floatsEqual(cachedLayout.availableHeight, availableHeight)); + + boolean isWidthSame = + (cachedLayout.widthMeasureMode == CSSMeasureMode.UNDEFINED && widthMeasureMode == CSSMeasureMode.UNDEFINED) || + (cachedLayout.widthMeasureMode == widthMeasureMode && FloatUtil.floatsEqual(cachedLayout.availableWidth, availableWidth)); + + if (isHeightSame && isWidthSame) { return true; } - - // If the width is an exact match, try a fuzzy match on the height. - if (FloatUtil.floatsEqual(cachedLayout.availableWidth, availableWidth) && - cachedLayout.widthMeasureMode == widthMeasureMode && - heightMeasureMode == CSSMeasureMode.EXACTLY && - FloatUtil.floatsEqual(availableHeight - marginColumn, cachedLayout.computedHeight)) { + + boolean isHeightValid = + (cachedLayout.heightMeasureMode == CSSMeasureMode.UNDEFINED && heightMeasureMode == CSSMeasureMode.AT_MOST && cachedLayout.computedHeight <= (availableHeight - marginColumn)) || + (heightMeasureMode == CSSMeasureMode.EXACTLY && FloatUtil.floatsEqual(cachedLayout.computedHeight, availableHeight - marginColumn)); + + if (isWidthSame && isHeightValid) { return true; } - - // If the height is an exact match, try a fuzzy match on the width. - if (FloatUtil.floatsEqual(cachedLayout.availableHeight, availableHeight) && - cachedLayout.heightMeasureMode == heightMeasureMode && - widthMeasureMode == CSSMeasureMode.EXACTLY && - FloatUtil.floatsEqual(availableWidth - marginRow, cachedLayout.computedWidth)) { + + boolean isWidthValid = + (cachedLayout.widthMeasureMode == CSSMeasureMode.UNDEFINED && widthMeasureMode == CSSMeasureMode.AT_MOST && cachedLayout.computedWidth <= (availableWidth - marginRow)) || + (widthMeasureMode == CSSMeasureMode.EXACTLY && FloatUtil.floatsEqual(cachedLayout.computedWidth, availableWidth - marginRow)); + + if (isHeightSame && isWidthValid) { return true; } - + + if (isHeightValid && isWidthValid) { + return true; + } + + // We know this to be text so we can apply some more specialized heuristics. + if (isTextNode) { + if (isWidthSame) { + if (heightMeasureMode == CSSMeasureMode.UNDEFINED) { + // Width is the same and height is not restricted. Re-use cahced value. + return true; + } + + if (heightMeasureMode == CSSMeasureMode.AT_MOST && + cachedLayout.computedHeight < (availableHeight - marginColumn)) { + // Width is the same and height restriction is greater than the cached height. Re-use cached value. + return true; + } + + // Width is the same but height restriction imposes smaller height than previously measured. + // Update the cached value to respect the new height restriction. + cachedLayout.computedHeight = availableHeight - marginColumn; + return true; + } + + if (cachedLayout.widthMeasureMode == CSSMeasureMode.UNDEFINED) { + if (widthMeasureMode == CSSMeasureMode.UNDEFINED || + (widthMeasureMode == CSSMeasureMode.AT_MOST && + cachedLayout.computedWidth <= (availableWidth - marginRow))) { + // Previsouly this text was measured with no width restriction, if width is now restricted + // but to a larger value than the previsouly measured width we can re-use the measurement + // as we know it will fit. + return true; + } + } + } + return false; } - + // // This is a wrapper around the layoutNodeImpl function. It determines // whether the layout request is redundant and can be skipped. @@ -303,19 +349,19 @@ public class LayoutEngine { 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 @@ -329,16 +375,16 @@ public class LayoutEngine { 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]); - + node.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN]); + // First, try to use the layout cache. - if (canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn, + if (canUseCachedMeasurement(node.isTextNode(), availableWidth, availableHeight, marginAxisRow, marginAxisColumn, widthMeasureMode, heightMeasureMode, layout.cachedLayout)) { cachedResults = layout.cachedLayout; } else { // Try to use the measurement cache. for (int i = 0; i < layout.nextCachedMeasurementsIndex; i++) { - if (canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn, + if (canUseCachedMeasurement(node.isTextNode(), availableWidth, availableHeight, marginAxisRow, marginAxisColumn, widthMeasureMode, heightMeasureMode, layout.cachedMeasurements[i])) { cachedResults = layout.cachedMeasurements[i]; break; @@ -350,7 +396,7 @@ public class LayoutEngine { FloatUtil.floatsEqual(layout.cachedLayout.availableHeight, availableHeight) && layout.cachedLayout.widthMeasureMode == widthMeasureMode && layout.cachedLayout.heightMeasureMode == heightMeasureMode) { - + cachedResults = layout.cachedLayout; } } else { @@ -365,15 +411,15 @@ public class LayoutEngine { } } } - + 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; @@ -392,7 +438,7 @@ public class LayoutEngine { } layout.nextCachedMeasurementsIndex++; } - + newCacheEntry.availableWidth = availableWidth; newCacheEntry.availableHeight = availableHeight; newCacheEntry.widthMeasureMode = widthMeasureMode; @@ -401,18 +447,18 @@ public class LayoutEngine { 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/. @@ -449,8 +495,8 @@ public class LayoutEngine { // // 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 + // 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 @@ -484,7 +530,7 @@ public class LayoutEngine { // - 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. // @@ -501,7 +547,7 @@ public class LayoutEngine { 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])); @@ -515,7 +561,7 @@ public class LayoutEngine { if (isMeasureDefined(node)) { float innerWidth = availableWidth - marginAxisRow - paddingAndBorderAxisRow; float innerHeight = availableHeight - marginAxisColumn - paddingAndBorderAxisColumn; - + if (widthMeasureMode == CSSMeasureMode.EXACTLY && heightMeasureMode == CSSMeasureMode.EXACTLY) { // Don't bother sizing the text if both dimensions are already defined. @@ -547,7 +593,7 @@ public class LayoutEngine { measureDim.height + paddingAndBorderAxisColumn : availableHeight - marginAxisColumn); } - + return; } @@ -577,7 +623,7 @@ public class LayoutEngine { 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)); @@ -589,7 +635,7 @@ public class LayoutEngine { 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); @@ -613,7 +659,7 @@ public class LayoutEngine { 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; @@ -638,7 +684,7 @@ public class LayoutEngine { 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) { @@ -654,27 +700,27 @@ public class LayoutEngine { 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; @@ -683,7 +729,7 @@ public class LayoutEngine { 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 @@ -702,23 +748,42 @@ public class LayoutEngine { } } + // If child has no defined size in the cross axis and is set to stretch, set the cross + // axis to be measured exactly with the available inner width + if (!isMainAxisRow && + !Float.isNaN(availableInnerWidth) && + !(child.style.dimensions[dim[CSS_FLEX_DIRECTION_ROW]] >= 0.0) && + widthMeasureMode == CSSMeasureMode.EXACTLY && + getAlignItem(node, child) == CSSAlign.STRETCH) { + childWidth = availableInnerWidth; + childWidthMeasureMode = CSSMeasureMode.EXACTLY; + } + if (isMainAxisRow && + !Float.isNaN(availableInnerHeight) && + !(child.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0) && + heightMeasureMode == CSSMeasureMode.EXACTLY && + getAlignItem(node, child) == CSSAlign.STRETCH) { + childHeight = availableInnerHeight; + childHeightMeasureMode = CSSMeasureMode.EXACTLY; + } + // 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])))); } } } // 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; @@ -726,7 +791,7 @@ public class LayoutEngine { float maxLineMainDim = 0; while (endOfLineIndex < childCount) { - + // Number of items on the currently line. May be different than the difference // between start and end indicates because we skip over absolute-positioned items. int itemsOnLine = 0; @@ -753,7 +818,7 @@ public class LayoutEngine { 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) { @@ -765,7 +830,7 @@ public class LayoutEngine { 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; @@ -781,11 +846,11 @@ public class LayoutEngine { currentRelativeChild = child; child.nextChild = null; } - + i++; endOfLineIndex++; } - + // If we don't need to measure the cross axis, we can skip the entire flex step. boolean canSkipFlex = !performLayout && measureModeCrossDim == CSSMeasureMode.EXACTLY; @@ -808,7 +873,7 @@ public class LayoutEngine { // its content. Consequently, remainingFreeSpace is 0 - sizeConsumedOnCurrentLine. remainingFreeSpace = -sizeConsumedOnCurrentLine; } - + float originalRemainingFreeSpace = remainingFreeSpace; float deltaFreeSpace = 0; @@ -818,20 +883,20 @@ public class LayoutEngine { 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. + // their min/max constraints to trigger again. // // This two pass approach for resolving min/max constraints deviates from the spec. The // spec (https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths) describes a process // that needs to be repeated a variable number of times. The algorithm implemented here // won't handle all cases but it was simpler to implement and it mitigates performance // concerns because we know exactly how many passes it'll do. - + // First pass: detect the flex items whose min/max constraints trigger float deltaFlexShrinkScaledFactors = 0; float deltaFlexGrowFactors = 0; @@ -841,7 +906,7 @@ public class LayoutEngine { if (remainingFreeSpace < 0) { flexShrinkScaledFactor = getFlexShrinkFactor(currentRelativeChild) * childFlexBasis; - + // Is this child able to shrink? if (flexShrinkScaledFactor != 0) { baseMainSize = childFlexBasis + @@ -872,14 +937,14 @@ public class LayoutEngine { } } } - + currentRelativeChild = currentRelativeChild.nextChild; } - + totalFlexShrinkScaledFactors += deltaFlexShrinkScaledFactors; totalFlexGrowFactors += deltaFlexGrowFactors; remainingFreeSpace += deltaFreeSpace; - + // Second pass: resolve the sizes of the flexible items deltaFreeSpace = 0; currentRelativeChild = firstRelativeChild; @@ -889,7 +954,7 @@ public class LayoutEngine { if (remainingFreeSpace < 0) { flexShrinkScaledFactor = getFlexShrinkFactor(currentRelativeChild) * childFlexBasis; - + // Is this child able to shrink? if (flexShrinkScaledFactor != 0) { updatedMainSize = boundAxis(currentRelativeChild, mainAxis, childFlexBasis + @@ -904,14 +969,20 @@ public class LayoutEngine { remainingFreeSpace / totalFlexGrowFactors * flexGrowFactor); } } - + deltaFreeSpace -= updatedMainSize - childFlexBasis; - + if (isMainAxisRow) { childWidth = updatedMainSize + (currentRelativeChild.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW]) + currentRelativeChild.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW])); childWidthMeasureMode = CSSMeasureMode.EXACTLY; - - if (!(currentRelativeChild.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0)) { + + if (!Float.isNaN(availableInnerCrossDim) && + !(currentRelativeChild.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0) && + heightMeasureMode == CSSMeasureMode.EXACTLY && + getAlignItem(node, currentRelativeChild) == CSSAlign.STRETCH) { + childHeight = availableInnerCrossDim; + childHeightMeasureMode = CSSMeasureMode.EXACTLY; + } else if (!(currentRelativeChild.style.dimensions[dim[CSS_FLEX_DIRECTION_COLUMN]] >= 0.0)) { childHeight = availableInnerCrossDim; childHeightMeasureMode = Float.isNaN(childHeight) ? CSSMeasureMode.UNDEFINED : CSSMeasureMode.AT_MOST; } else { @@ -921,8 +992,14 @@ public class LayoutEngine { } 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)) { + + if (!Float.isNaN(availableInnerCrossDim) && + !(currentRelativeChild.style.dimensions[dim[CSS_FLEX_DIRECTION_ROW]] >= 0.0) && + widthMeasureMode == CSSMeasureMode.EXACTLY && + getAlignItem(node, currentRelativeChild) == CSSAlign.STRETCH) { + childWidth = availableInnerCrossDim; + childWidthMeasureMode = CSSMeasureMode.EXACTLY; + } else if (!(currentRelativeChild.style.dimensions[dim[CSS_FLEX_DIRECTION_ROW]] >= 0.0)) { childWidth = availableInnerCrossDim; childWidthMeasureMode = Float.isNaN(childWidth) ? CSSMeasureMode.UNDEFINED : CSSMeasureMode.AT_MOST; } else { @@ -930,7 +1007,7 @@ public class LayoutEngine { childWidthMeasureMode = CSSMeasureMode.EXACTLY; } } - + boolean requiresStretchLayout = !(currentRelativeChild.style.dimensions[dim[crossAxis]] >= 0.0) && getAlignItem(node, currentRelativeChild) == CSSAlign.STRETCH; @@ -940,7 +1017,7 @@ public class LayoutEngine { currentRelativeChild = currentRelativeChild.nextChild; } } - + remainingFreeSpace = originalRemainingFreeSpace + deltaFreeSpace; // STEP 6: MAIN-AXIS JUSTIFICATION & CROSS-AXIS SIZE DETERMINATION @@ -999,7 +1076,7 @@ public class LayoutEngine { // 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. @@ -1013,7 +1090,7 @@ public class LayoutEngine { // 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]))); @@ -1023,12 +1100,12 @@ public class LayoutEngine { } 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); } @@ -1065,14 +1142,14 @@ public class LayoutEngine { // 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); - + // 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) { 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; @@ -1080,7 +1157,7 @@ public class LayoutEngine { 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; @@ -1207,7 +1284,7 @@ public class LayoutEngine { boundAxisWithinMinAndMax(node, crossAxis, totalLineCrossDim + paddingAndBorderAxisCross)), paddingAndBorderAxisCross); } - + // STEP 10: SETTING TRAILING POSITIONS FOR CHILDREN if (performLayout) { boolean needsMainTrailingPos = false; @@ -1238,7 +1315,7 @@ public class LayoutEngine { } } } - + // STEP 11: SIZING AND POSITIONING ABSOLUTE CHILDREN currentAbsoluteChild = firstAbsoluteChild; while (currentAbsoluteChild != null) { @@ -1260,7 +1337,7 @@ public class LayoutEngine { 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 { @@ -1277,7 +1354,7 @@ public class LayoutEngine { 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 @@ -1300,9 +1377,9 @@ public class LayoutEngine { 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]] = @@ -1310,7 +1387,7 @@ public class LayoutEngine { 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]] = diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/README b/ReactAndroid/src/main/java/com/facebook/csslayout/README index 34a003a1b4..56b8e613e0 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/README +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/README @@ -1,7 +1,7 @@ The source of truth for css-layout is: https://github.com/facebook/css-layout The code here should be kept in sync with GitHub. -HEAD at the time this code was synced: https://github.com/facebook/css-layout/commit/16f43dac87ace8b60e0e4c07a798a558c22bd21b +HEAD at the time this code was synced: https://github.com/facebook/css-layout/commit/383d8a6b3dcbdb978e012e29040e1a43157765c6 There is generated code in: - README (this file) diff --git a/ReactAndroid/src/main/java/com/facebook/csslayout/README.facebook b/ReactAndroid/src/main/java/com/facebook/csslayout/README.facebook index b4d26531d5..4ef7756cc4 100644 --- a/ReactAndroid/src/main/java/com/facebook/csslayout/README.facebook +++ b/ReactAndroid/src/main/java/com/facebook/csslayout/README.facebook @@ -1,7 +1,7 @@ The source of truth for css-layout is: https://github.com/facebook/css-layout The code here should be kept in sync with GitHub. -HEAD at the time this code was synced: https://github.com/facebook/css-layout/commit/16f43dac87ace8b60e0e4c07a798a558c22bd21b +HEAD at the time this code was synced: https://github.com/facebook/css-layout/commit/383d8a6b3dcbdb978e012e29040e1a43157765c6 There is generated code in: - README.facebook (this file)