Files
yoga/java/com/facebook/csslayout/LayoutEngine.java
FBShipIt fdd8552c4e Import new C source of truth css-layout
fbshipit-source-id: e866918d6c62fc1cf3a04c269f782b94db9b875a
2016-07-25 15:53:07 +01:00

1412 lines
73 KiB
Java

/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
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;
import static com.facebook.csslayout.CSSLayout.POSITION_LEFT;
import static com.facebook.csslayout.CSSLayout.POSITION_RIGHT;
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 =
CSSFlexDirection.COLUMN_REVERSE.ordinal();
private static final int CSS_FLEX_DIRECTION_ROW =
CSSFlexDirection.ROW.ordinal();
private static final int CSS_FLEX_DIRECTION_ROW_REVERSE =
CSSFlexDirection.ROW_REVERSE.ordinal();
private static final int CSS_POSITION_RELATIVE = CSSPositionType.RELATIVE.ordinal();
private static final int CSS_POSITION_ABSOLUTE = CSSPositionType.ABSOLUTE.ordinal();
private static final int[] leading = {
POSITION_TOP,
POSITION_BOTTOM,
POSITION_LEFT,
POSITION_RIGHT,
};
private static final int[] trailing = {
POSITION_BOTTOM,
POSITION_TOP,
POSITION_RIGHT,
POSITION_LEFT,
};
private static final int[] pos = {
POSITION_TOP,
POSITION_BOTTOM,
POSITION_LEFT,
POSITION_RIGHT,
};
private static final int[] dim = {
DIMENSION_HEIGHT,
DIMENSION_HEIGHT,
DIMENSION_WIDTH,
DIMENSION_WIDTH,
};
private static final int[] leadingSpacing = {
Spacing.TOP,
Spacing.BOTTOM,
Spacing.START,
Spacing.START
};
private static final int[] trailingSpacing = {
Spacing.BOTTOM,
Spacing.TOP,
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 boundAxisWithinMinAndMax(CSSNode node, int axis, float value) {
float min = CSSConstants.UNDEFINED;
float max = CSSConstants.UNDEFINED;
if (axis == CSS_FLEX_DIRECTION_COLUMN ||
axis == CSS_FLEX_DIRECTION_COLUMN_REVERSE) {
min = node.style.minHeight;
max = node.style.maxHeight;
} else if (axis == CSS_FLEX_DIRECTION_ROW ||
axis == CSS_FLEX_DIRECTION_ROW_REVERSE) {
min = node.style.minWidth;
max = node.style.maxWidth;
}
float boundValue = value;
if (!Float.isNaN(max) && max >= 0.0 && boundValue > max) {
boundValue = max;
}
if (!Float.isNaN(min) && min >= 0.0 && boundValue < min) {
boundValue = min;
}
return boundValue;
}
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) {
float lead = node.style.position[leading[axis]];
if (!Float.isNaN(lead)) {
return lead;
}
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,
CSSDirection direction) {
if (direction == CSSDirection.RTL) {
if (axis == CSS_FLEX_DIRECTION_ROW) {
return CSS_FLEX_DIRECTION_ROW_REVERSE;
} else if (axis == CSS_FLEX_DIRECTION_ROW_REVERSE) {
return CSS_FLEX_DIRECTION_ROW;
}
}
return axis;
}
private static CSSDirection resolveDirection(CSSNode node, CSSDirection parentDirection) {
CSSDirection direction = node.style.direction;
if (direction == CSSDirection.INHERIT) {
direction = (parentDirection == null ? CSSDirection.LTR : parentDirection);
}
return direction;
}
private static int getFlexDirection(CSSNode node) {
return node.style.flexDirection.ordinal();
}
private static int getCrossFlexDirection(
int axis,
CSSDirection direction) {
if (axis == CSS_FLEX_DIRECTION_COLUMN ||
axis == CSS_FLEX_DIRECTION_COLUMN_REVERSE) {
return resolveAxis(CSS_FLEX_DIRECTION_ROW, direction);
} else {
return CSS_FLEX_DIRECTION_COLUMN;
}
}
private static CSSAlign getAlignItem(CSSNode node, CSSNode child) {
if (child.style.alignSelf != CSSAlign.AUTO) {
return child.style.alignSelf;
}
return node.style.alignItems;
}
private static boolean isMeasureDefined(CSSNode node) {
return node.isMeasureDefined();
}
/*package*/ static void layoutNode(
CSSLayoutContext layoutContext,
CSSNode node,
float availableWidth,
float availableHeight,
CSSDirection 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.
layoutContext.currentGenerationCount++;
CSSMeasureMode widthMeasureMode = CSSMeasureMode.UNDEFINED;
CSSMeasureMode heightMeasureMode = CSSMeasureMode.UNDEFINED;
if (!Float.isNaN(availableWidth)) {
widthMeasureMode = CSSMeasureMode.EXACTLY;
} else if (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;
widthMeasureMode = CSSMeasureMode.EXACTLY;
} else if (node.style.maxWidth >= 0.0) {
availableWidth = node.style.maxWidth;
widthMeasureMode = CSSMeasureMode.AT_MOST;
}
if (!Float.isNaN(availableHeight)) {
heightMeasureMode = CSSMeasureMode.EXACTLY;
} else if (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;
heightMeasureMode = CSSMeasureMode.EXACTLY;
} else if (node.style.maxHeight >= 0.0) {
availableHeight = node.style.maxHeight;
heightMeasureMode = CSSMeasureMode.AT_MOST;
}
if (layoutNodeInternal(layoutContext, node, availableWidth, availableHeight, parentDirection, widthMeasureMode, heightMeasureMode, true, "initial")) {
setPosition(node, node.layout.direction);
}
}
/*package*/ static boolean canUseCachedMeasurement(
boolean isTextNode,
float availableWidth,
float availableHeight,
float marginRow,
float marginColumn,
CSSMeasureMode widthMeasureMode,
CSSMeasureMode heightMeasureMode,
CSSCachedMeasurement cachedLayout) {
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;
}
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;
}
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.
//
// Parameters:
// Input parameters are the same as layoutNodeImpl (see below)
// Return parameter is true if layout was performed, false if skipped
//
private static boolean layoutNodeInternal(
CSSLayoutContext layoutContext,
CSSNode node,
float availableWidth,
float availableHeight,
CSSDirection parentDirection,
CSSMeasureMode widthMeasureMode,
CSSMeasureMode heightMeasureMode,
boolean performLayout,
String reason) {
CSSLayout layout = node.layout;
boolean needToVisitNode = (node.isDirty() && layout.generationCount != layoutContext.currentGenerationCount) ||
layout.lastParentDirection != parentDirection;
if (needToVisitNode) {
// Invalidate the cached results.
layout.nextCachedMeasurementsIndex = 0;
layout.cachedLayout.widthMeasureMode = null;
layout.cachedLayout.heightMeasureMode = null;
}
CSSCachedMeasurement cachedResults = null;
// Determine whether the results are already cached. We maintain a separate
// cache for layouts and measurements. A layout operation modifies the positions
// and dimensions for nodes in the subtree. The algorithm assumes that each node
// gets layed out a maximum of one time per tree layout, but multiple measurements
// may be required to resolve all of the flex dimensions.
// We handle nodes with measure functions specially here because they are the most
// expensive to measure, so it's worth avoiding redundant measurements if at all possible.
if (isMeasureDefined(node)) {
float marginAxisRow =
node.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW]) +
node.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW]);
float marginAxisColumn =
node.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) +
node.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN]);
// First, try to use the layout cache.
if (canUseCachedMeasurement(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(node.isTextNode(), availableWidth, availableHeight, marginAxisRow, marginAxisColumn,
widthMeasureMode, heightMeasureMode, layout.cachedMeasurements[i])) {
cachedResults = layout.cachedMeasurements[i];
break;
}
}
}
} else if (performLayout) {
if (FloatUtil.floatsEqual(layout.cachedLayout.availableWidth, availableWidth) &&
FloatUtil.floatsEqual(layout.cachedLayout.availableHeight, availableHeight) &&
layout.cachedLayout.widthMeasureMode == widthMeasureMode &&
layout.cachedLayout.heightMeasureMode == heightMeasureMode) {
cachedResults = layout.cachedLayout;
}
} else {
for (int i = 0; i < layout.nextCachedMeasurementsIndex; i++) {
if (FloatUtil.floatsEqual(layout.cachedMeasurements[i].availableWidth, availableWidth) &&
FloatUtil.floatsEqual(layout.cachedMeasurements[i].availableHeight, availableHeight) &&
layout.cachedMeasurements[i].widthMeasureMode == widthMeasureMode &&
layout.cachedMeasurements[i].heightMeasureMode == heightMeasureMode) {
cachedResults = layout.cachedMeasurements[i];
break;
}
}
}
if (!needToVisitNode && cachedResults != null) {
layout.measuredDimensions[DIMENSION_WIDTH] = cachedResults.computedWidth;
layout.measuredDimensions[DIMENSION_HEIGHT] = cachedResults.computedHeight;
} else {
layoutNodeImpl(layoutContext, node, availableWidth, availableHeight, parentDirection, widthMeasureMode, heightMeasureMode, performLayout);
layout.lastParentDirection = parentDirection;
if (cachedResults == null) {
if (layout.nextCachedMeasurementsIndex == CSSLayout.MAX_CACHED_RESULT_COUNT) {
layout.nextCachedMeasurementsIndex = 0;
}
CSSCachedMeasurement newCacheEntry = null;
if (performLayout) {
// Use the single layout cache entry.
newCacheEntry = layout.cachedLayout;
} else {
// Allocate a new measurement cache entry.
newCacheEntry = layout.cachedMeasurements[layout.nextCachedMeasurementsIndex];
if (newCacheEntry == null) {
newCacheEntry = new CSSCachedMeasurement();
layout.cachedMeasurements[layout.nextCachedMeasurementsIndex] = newCacheEntry;
}
layout.nextCachedMeasurementsIndex++;
}
newCacheEntry.availableWidth = availableWidth;
newCacheEntry.availableHeight = availableHeight;
newCacheEntry.widthMeasureMode = widthMeasureMode;
newCacheEntry.heightMeasureMode = heightMeasureMode;
newCacheEntry.computedWidth = layout.measuredDimensions[DIMENSION_WIDTH];
newCacheEntry.computedHeight = layout.measuredDimensions[DIMENSION_HEIGHT];
}
}
if (performLayout) {
node.layout.dimensions[DIMENSION_WIDTH] = node.layout.measuredDimensions[DIMENSION_WIDTH];
node.layout.dimensions[DIMENSION_HEIGHT] = node.layout.measuredDimensions[DIMENSION_HEIGHT];
node.markHasNewLayout();
}
layout.generationCount = layoutContext.currentGenerationCount;
return (needToVisitNode || cachedResults == null);
}
//
// This is the main routine that implements a subset of the flexbox layout algorithm
// described in the W3C CSS documentation: https://www.w3.org/TR/css3-flexbox/.
//
// Limitations of this algorithm, compared to the full standard:
// * Display property is always assumed to be 'flex' except for Text nodes, which
// are assumed to be 'inline-flex'.
// * The 'zIndex' property (or any form of z ordering) is not supported. Nodes are
// stacked in document order.
// * The 'order' property is not supported. The order of flex items is always defined
// by document order.
// * The 'visibility' property is always assumed to be 'visible'. Values of 'collapse'
// and 'hidden' are not supported.
// * The 'wrap' property supports only 'nowrap' (which is the default) or 'wrap'. The
// rarely-used 'wrap-reverse' is not supported.
// * Rather than allowing arbitrary combinations of flexGrow, flexShrink and
// flexBasis, this algorithm supports only the three most common combinations:
// flex: 0 is equiavlent to flex: 0 0 auto
// flex: n (where n is a positive value) is equivalent to flex: n 1 auto
// If POSITIVE_FLEX_IS_AUTO is 0, then it is equivalent to flex: n 0 0
// This is faster because the content doesn't need to be measured, but it's
// less flexible because the basis is always 0 and can't be overriden with
// the width/height attributes.
// flex: -1 (or any negative value) is equivalent to flex: 0 1 auto
// * Margins cannot be specified as 'auto'. They must be specified in terms of pixel
// values, and the default value is 0.
// * The 'baseline' value is not supported for alignItems and alignSelf properties.
// * Values of width, maxWidth, minWidth, height, maxHeight and minHeight must be
// specified as pixel values, not as percentages.
// * There is no support for calculation of dimensions based on intrinsic aspect ratios
// (e.g. images).
// * There is no support for forced breaks.
// * It does not support vertical inline directions (top-to-bottom or bottom-to-top text).
//
// Deviations from standard:
// * Section 4.5 of the spec indicates that all flex items have a default minimum
// main size. For text blocks, for example, this is the width of the widest word.
// Calculating the minimum width is expensive, so we forego it and assume a default
// minimum main size of 0.
// * Min/Max sizes in the main axis are not honored when resolving flexible lengths.
// * The spec indicates that the default value for 'flexDirection' is 'row', but
// the algorithm below assumes a default of 'column'.
//
// Input parameters:
// - node: current node to be sized and layed out
// - availableWidth & availableHeight: available size to be used for sizing the node
// or CSS_UNDEFINED if the size is not available; interpretation depends on layout
// flags
// - parentDirection: the inline (text) direction within the parent (left-to-right or
// right-to-left)
// - widthMeasureMode: indicates the sizing rules for the width (see below for explanation)
// - heightMeasureMode: indicates the sizing rules for the height (see below for explanation)
// - performLayout: specifies whether the caller is interested in just the dimensions
// of the node or it requires the entire node and its subtree to be layed out
// (with final positions)
//
// Details:
// This routine is called recursively to lay out subtrees of flexbox elements. It uses the
// information in node.style, which is treated as a read-only input. It is responsible for
// setting the layout.direction and layout.measured_dimensions fields for the input node as well
// as the layout.position and layout.line_index fields for its child nodes. The
// layout.measured_dimensions field includes any border or padding for the node but does
// not include margins.
//
// The spec describes four different layout modes: "fill available", "max content", "min content",
// and "fit content". Of these, we don't use "min content" because we don't support default
// minimum main sizes (see above for details). Each of our measure modes maps to a layout mode
// from the spec (https://www.w3.org/TR/css3-sizing/#terms):
// - CSS_MEASURE_MODE_UNDEFINED: max content
// - CSS_MEASURE_MODE_EXACTLY: fill available
// - CSS_MEASURE_MODE_AT_MOST: fit content
//
// When calling layoutNodeImpl and layoutNodeInternal, if the caller passes an available size of
// undefined then it must also pass a measure mode of CSS_MEASURE_MODE_UNDEFINED in that dimension.
//
private static void layoutNodeImpl(
CSSLayoutContext layoutContext,
CSSNode node,
float availableWidth,
float availableHeight,
CSSDirection parentDirection,
CSSMeasureMode widthMeasureMode,
CSSMeasureMode heightMeasureMode,
boolean performLayout) {
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);
node.layout.direction = direction;
// For content (text) nodes, determine the dimensions based on the text contents.
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.
node.layout.measuredDimensions[DIMENSION_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, availableWidth - marginAxisRow);
node.layout.measuredDimensions[DIMENSION_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, availableHeight - marginAxisColumn);
} else if (innerWidth <= 0 || innerHeight <= 0) {
// Don't bother sizing the text if there's no horizontal or vertical space.
node.layout.measuredDimensions[DIMENSION_WIDTH] = boundAxis(node, CSS_FLEX_DIRECTION_ROW, 0);
node.layout.measuredDimensions[DIMENSION_HEIGHT] = boundAxis(node, CSS_FLEX_DIRECTION_COLUMN, 0);
} else {
// Measure the text under the current constraints.
MeasureOutput measureDim = node.measure(
layoutContext.measureOutput,
innerWidth,
widthMeasureMode,
innerHeight,
heightMeasureMode
);
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);
}
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;
}
}
// 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;
boolean isNodeFlexWrap = (node.style.flexWrap == CSSWrap.WRAP);
CSSNode firstAbsoluteChild = null;
CSSNode currentAbsoluteChild = null;
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;
}
}
// 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;
// Max main dimension of all the lines.
float maxLineMainDim = 0;
while (endOfLineIndex < childCount) {
// Number of items on the currently line. May be different than the difference
// between start and end indicates because we skip over absolute-positioned items.
int itemsOnLine = 0;
// sizeConsumedOnCurrentLine is accumulation of the dimensions and margin
// of all the children on the current line. This will be used in order to
// either set the dimensions of the node if none already exist or to compute
// the remaining space left for the flexible children.
float sizeConsumedOnCurrentLine = 0;
float totalFlexGrowFactors = 0;
float totalFlexShrinkScaledFactors = 0;
i = startOfLineIndex;
// Maintain a linked list of the child nodes that can shrink and/or grow.
CSSNode firstRelativeChild = null;
CSSNode currentRelativeChild = null;
// 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 = lineCount;
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;
}
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;
}
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;
// In order to position the elements in the main axis, we have two
// controls. The space between the beginning and the first element
// and the space between each two elements.
float leadingMainDim = 0;
float betweenMainDim = 0;
// STEP 5: RESOLVING FLEXIBLE LENGTHS ON MAIN AXIS
// Calculate the remaining available space that needs to be allocated.
// If the main dimension size isn't known, it is computed based on
// the line length, so there's no more space left to distribute.
float remainingFreeSpace = 0;
if (!Float.isNaN(availableInnerMainDim)) {
remainingFreeSpace = availableInnerMainDim - sizeConsumedOnCurrentLine;
} else if (sizeConsumedOnCurrentLine < 0) {
// availableInnerMainDim is indefinite which means the node is being sized based on its content.
// sizeConsumedOnCurrentLine is negative which means the node will allocate 0 pixels for
// its content. Consequently, remainingFreeSpace is 0 - sizeConsumedOnCurrentLine.
remainingFreeSpace = -sizeConsumedOnCurrentLine;
}
float originalRemainingFreeSpace = remainingFreeSpace;
float deltaFreeSpace = 0;
if (!canSkipFlex) {
float childFlexBasis;
float flexShrinkScaledFactor;
float flexGrowFactor;
float baseMainSize;
float boundMainSize;
// Do two passes over the flex items to figure out how to distribute the remaining space.
// The first pass finds the items whose min/max constraints trigger, freezes them at those
// sizes, and excludes those sizes from the remaining space. The second pass sets the size
// of each flexible item. It distributes the remaining space amongst the items whose min/max
// constraints didn't trigger in pass 1. For the other items, it sets their sizes by forcing
// their min/max constraints to trigger again.
//
// This two pass approach for resolving min/max constraints deviates from the spec. The
// spec (https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths) describes a process
// that needs to be repeated a variable number of times. The algorithm implemented here
// won't handle all cases but it was simpler to implement and it mitigates performance
// concerns because we know exactly how many passes it'll do.
// First pass: detect the flex items whose min/max constraints trigger
float deltaFlexShrinkScaledFactors = 0;
float deltaFlexGrowFactors = 0;
currentRelativeChild = firstRelativeChild;
while (currentRelativeChild != null) {
childFlexBasis = currentRelativeChild.layout.flexBasis;
if (remainingFreeSpace < 0) {
flexShrinkScaledFactor = getFlexShrinkFactor(currentRelativeChild) * childFlexBasis;
// Is this child able to shrink?
if (flexShrinkScaledFactor != 0) {
baseMainSize = childFlexBasis +
remainingFreeSpace / totalFlexShrinkScaledFactors * flexShrinkScaledFactor;
boundMainSize = boundAxis(currentRelativeChild, mainAxis, baseMainSize);
if (baseMainSize != boundMainSize) {
// By excluding this item's size and flex factor from remaining, this item's
// min/max constraints should also trigger in the second pass resulting in the
// item's size calculation being identical in the first and second passes.
deltaFreeSpace -= boundMainSize - childFlexBasis;
deltaFlexShrinkScaledFactors -= flexShrinkScaledFactor;
}
}
} else if (remainingFreeSpace > 0) {
flexGrowFactor = getFlexGrowFactor(currentRelativeChild);
// Is this child able to grow?
if (flexGrowFactor != 0) {
baseMainSize = childFlexBasis +
remainingFreeSpace / totalFlexGrowFactors * flexGrowFactor;
boundMainSize = boundAxis(currentRelativeChild, mainAxis, baseMainSize);
if (baseMainSize != boundMainSize) {
// By excluding this item's size and flex factor from remaining, this item's
// min/max constraints should also trigger in the second pass resulting in the
// item's size calculation being identical in the first and second passes.
deltaFreeSpace -= boundMainSize - childFlexBasis;
deltaFlexGrowFactors -= flexGrowFactor;
}
}
}
currentRelativeChild = currentRelativeChild.nextChild;
}
totalFlexShrinkScaledFactors += deltaFlexShrinkScaledFactors;
totalFlexGrowFactors += deltaFlexGrowFactors;
remainingFreeSpace += deltaFreeSpace;
// Second pass: resolve the sizes of the flexible items
deltaFreeSpace = 0;
currentRelativeChild = firstRelativeChild;
while (currentRelativeChild != null) {
childFlexBasis = currentRelativeChild.layout.flexBasis;
float updatedMainSize = childFlexBasis;
if (remainingFreeSpace < 0) {
flexShrinkScaledFactor = getFlexShrinkFactor(currentRelativeChild) * childFlexBasis;
// Is this child able to shrink?
if (flexShrinkScaledFactor != 0) {
updatedMainSize = boundAxis(currentRelativeChild, mainAxis, childFlexBasis +
remainingFreeSpace / totalFlexShrinkScaledFactors * flexShrinkScaledFactor);
}
} else if (remainingFreeSpace > 0) {
flexGrowFactor = getFlexGrowFactor(currentRelativeChild);
// Is this child able to grow?
if (flexGrowFactor != 0) {
updatedMainSize = boundAxis(currentRelativeChild, mainAxis, childFlexBasis +
remainingFreeSpace / totalFlexGrowFactors * flexGrowFactor);
}
}
deltaFreeSpace -= updatedMainSize - childFlexBasis;
if (isMainAxisRow) {
childWidth = updatedMainSize + (currentRelativeChild.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW]) + currentRelativeChild.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW]));
childWidthMeasureMode = CSSMeasureMode.EXACTLY;
if (!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 {
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 (!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 {
childWidth = currentRelativeChild.style.dimensions[DIMENSION_WIDTH] + (currentRelativeChild.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW]) + currentRelativeChild.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW]));
childWidthMeasureMode = CSSMeasureMode.EXACTLY;
}
}
boolean requiresStretchLayout = !(currentRelativeChild.style.dimensions[dim[crossAxis]] >= 0.0) &&
getAlignItem(node, currentRelativeChild) == CSSAlign.STRETCH;
// Recursively call the layout algorithm for this child with the updated main size.
layoutNodeInternal(layoutContext, currentRelativeChild, childWidth, childHeight, direction, childWidthMeasureMode, childHeightMeasureMode, performLayout && !requiresStretchLayout, "flex");
currentRelativeChild = currentRelativeChild.nextChild;
}
}
remainingFreeSpace = originalRemainingFreeSpace + deltaFreeSpace;
// STEP 6: MAIN-AXIS JUSTIFICATION & CROSS-AXIS SIZE DETERMINATION
// At this point, all the children have their dimensions set in the main axis.
// Their dimensions are also set in the cross axis with the exception of items
// that are aligned "stretch". We need to compute these stretch values and
// set the final positions.
// If we are using "at most" rules in the main axis, we won't distribute
// any remaining space at this point.
if (measureModeMainDim == CSSMeasureMode.AT_MOST) {
remainingFreeSpace = 0;
}
// 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 = remainingFreeSpace / 2;
} else if (justifyContent == CSSJustify.FLEX_END) {
leadingMainDim = remainingFreeSpace;
} else if (justifyContent == CSSJustify.SPACE_BETWEEN) {
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 = remainingFreeSpace / itemsOnLine;
leadingMainDim = betweenMainDim / 2;
}
}
float mainDim = leadingPaddingAndBorderMain + leadingMainDim;
float crossDim = 0;
for (i = startOfLineIndex; i < endOfLineIndex; ++i) {
child = node.getChildAt(i);
if (child.style.positionType == CSSPositionType.ABSOLUTE &&
!Float.isNaN(child.style.position[leading[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]);
}
} 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) {
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])));
}
}
}
}
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);
}
}
// If there's no flex wrap, the cross dimension is defined by the container.
if (!isNodeFlexWrap && measureModeCrossDim == CSSMeasureMode.EXACTLY) {
crossDim = availableInnerCrossDim;
}
// Clamp to the min/max size specified on the container.
crossDim = boundAxis(node, crossAxis, crossDim + paddingAndBorderAxisCross) - paddingAndBorderAxisCross;
// 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);
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);
// 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;
} 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) {
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;
} else { // CSSAlign.FLEX_END
leadingCrossDim += remainingCrossDim;
}
}
// And we apply the position
child.layout.position[pos[crossAxis]] += totalLineCrossDim + leadingCrossDim;
}
}
}
totalLineCrossDim += crossDim;
maxLineMainDim = Math.max(maxLineMainDim, mainDim);
// Reset variables for new line.
lineCount++;
startOfLineIndex = endOfLineIndex;
endOfLineIndex = startOfLineIndex;
}
// STEP 8: MULTI-LINE CONTENT ALIGNMENT
if (lineCount > 1 && performLayout && !Float.isNaN(availableInnerCrossDim)) {
float remainingAlignContentDim = availableInnerCrossDim - totalLineCrossDim;
float crossDimLead = 0;
float currentLead = leadingPaddingAndBorderCross;
CSSAlign alignContent = node.style.alignContent;
if (alignContent == CSSAlign.FLEX_END) {
currentLead += remainingAlignContentDim;
} else if (alignContent == CSSAlign.CENTER) {
currentLead += remainingAlignContentDim / 2;
} else if (alignContent == CSSAlign.STRETCH) {
if (availableInnerCrossDim > totalLineCrossDim) {
crossDimLead = (remainingAlignContentDim / lineCount);
}
}
int endIndex = 0;
for (i = 0; i < lineCount; ++i) {
int startIndex = endIndex;
int j;
// compute the line's height and find the endIndex
float lineHeight = 0;
for (j = startIndex; j < childCount; ++j) {
child = node.getChildAt(j);
if (child.style.positionType != CSSPositionType.RELATIVE) {
continue;
}
if (child.lineIndex != i) {
break;
}
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 = j;
lineHeight += crossDimLead;
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.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.
}
}
}
currentLead += lineHeight;
}
}
// 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 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 (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);
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.measuredDimensions[dim[crossAxis]] - (child.style.positionType == CSSPositionType.ABSOLUTE ? 0 : child.layout.measuredDimensions[dim[crossAxis]]) - child.layout.position[pos[crossAxis]];
}
}
}
}
// STEP 11: SIZING AND POSITIONING ABSOLUTE CHILDREN
currentAbsoluteChild = firstAbsoluteChild;
while (currentAbsoluteChild != null) {
// Now that we know the bounds of the container, perform layout again on the
// absolutely-positioned children.
if (performLayout) {
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 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]]);
}
}
currentAbsoluteChild = currentAbsoluteChild.nextChild;
}
}
}