BREAKING: Change aspect ratio behavior

Summary:
== Before ==
- Aspect ratio would do its best to fit within it's parent constraints
- Aspect ratio would prioritize `alignItems: stretch` over other sizing properties.

== After ==
- Aspect ratio is allowed to make a node grow past its parent constraints. This matches many other aspects of flexbox where parent constraints are not treated as hard constraints but rather as suggestions.
- Aspect ratio only takes `alignItems: stretch` into account if no other size definition is defined. This matches the interaction of other properties with `alignItems: stretch`.

== Updating your code ==

**You probably don't need to do anything** but in case something does break in your product it should be as easy as  adding `{width: '100%', height: '100%', flexShrink: 1}` to the style declaring the `aspectRatio`.

Reviewed By: gkassabli

Differential Revision: D5639187

fbshipit-source-id: 603e8fcc3373f0b7f2461da2dad1625ab59dcb19
This commit is contained in:
Emil Sjolander 2017-08-21 03:09:09 -07:00 committed by Facebook Github Bot
parent 992777b765
commit 67c160cc6c
1 changed files with 97 additions and 87 deletions

View File

@ -10,8 +10,8 @@
#include <string.h> #include <string.h>
#include "YGNodeList.h" #include "YGNodeList.h"
#include "Yoga.h"
#include "Yoga-internal.h" #include "Yoga-internal.h"
#include "Yoga.h"
#ifdef _MSC_VER #ifdef _MSC_VER
#include <float.h> #include <float.h>
@ -1514,31 +1514,42 @@ static void YGNodeComputeFlexBasisForChild(const YGNodeRef node,
} }
} }
if (!YGFloatIsUndefined(child->style.aspectRatio)) {
if (!isMainAxisRow && childWidthMeasureMode == YGMeasureModeExactly) {
childHeight = (childWidth - marginRow) / child->style.aspectRatio;
childHeightMeasureMode = YGMeasureModeExactly;
} else if (isMainAxisRow && childHeightMeasureMode == YGMeasureModeExactly) {
childWidth = (childHeight - marginColumn) * child->style.aspectRatio;
childWidthMeasureMode = YGMeasureModeExactly;
}
}
// If child has no defined size in the cross axis and is set to stretch, // If child has no defined size in the cross axis and is set to stretch,
// set the cross // set the cross
// axis to be measured exactly with the available inner width // axis to be measured exactly with the available inner width
if (!isMainAxisRow && !YGFloatIsUndefined(width) && !isRowStyleDimDefined &&
widthMode == YGMeasureModeExactly && YGNodeAlignItem(node, child) == YGAlignStretch) { const bool hasExactWidth = !YGFloatIsUndefined(width) && widthMode == YGMeasureModeExactly;
const bool childWidthStretch = YGNodeAlignItem(node, child) == YGAlignStretch &&
childWidthMeasureMode != YGMeasureModeExactly;
if (!isMainAxisRow && !isRowStyleDimDefined && hasExactWidth && childWidthStretch) {
childWidth = width; childWidth = width;
childWidthMeasureMode = YGMeasureModeExactly; childWidthMeasureMode = YGMeasureModeExactly;
} if (!YGFloatIsUndefined(child->style.aspectRatio)) {
if (isMainAxisRow && !YGFloatIsUndefined(height) && !isColumnStyleDimDefined && childHeight = (childWidth - marginRow) / child->style.aspectRatio;
heightMode == YGMeasureModeExactly && YGNodeAlignItem(node, child) == YGAlignStretch) { childHeightMeasureMode = YGMeasureModeExactly;
childHeight = height; }
childHeightMeasureMode = YGMeasureModeExactly;
} }
if (!YGFloatIsUndefined(child->style.aspectRatio)) { const bool hasExactHeight = !YGFloatIsUndefined(height) && heightMode == YGMeasureModeExactly;
if (!isMainAxisRow && childWidthMeasureMode == YGMeasureModeExactly) { const bool childHeightStretch = YGNodeAlignItem(node, child) == YGAlignStretch &&
child->layout.computedFlexBasis = childHeightMeasureMode != YGMeasureModeExactly;
fmaxf((childWidth - marginRow) / child->style.aspectRatio, if (isMainAxisRow && !isColumnStyleDimDefined && hasExactHeight && childHeightStretch) {
YGNodePaddingAndBorderForAxis(child, YGFlexDirectionColumn, parentWidth)); childHeight = height;
return; childHeightMeasureMode = YGMeasureModeExactly;
} else if (isMainAxisRow && childHeightMeasureMode == YGMeasureModeExactly) {
child->layout.computedFlexBasis = if (!YGFloatIsUndefined(child->style.aspectRatio)) {
fmaxf((childHeight - marginColumn) * child->style.aspectRatio, childWidth = (childHeight - marginColumn) * child->style.aspectRatio;
YGNodePaddingAndBorderForAxis(child, YGFlexDirectionRow, parentWidth)); childWidthMeasureMode = YGMeasureModeExactly;
return;
} }
} }
@ -1631,13 +1642,9 @@ static void YGNodeAbsoluteLayoutChild(const YGNodeRef node,
if (YGFloatIsUndefined(childWidth) ^ YGFloatIsUndefined(childHeight)) { if (YGFloatIsUndefined(childWidth) ^ YGFloatIsUndefined(childHeight)) {
if (!YGFloatIsUndefined(child->style.aspectRatio)) { if (!YGFloatIsUndefined(child->style.aspectRatio)) {
if (YGFloatIsUndefined(childWidth)) { if (YGFloatIsUndefined(childWidth)) {
childWidth = childWidth = marginRow + (childHeight - marginColumn) * child->style.aspectRatio;
marginRow + fmaxf((childHeight - marginColumn) * child->style.aspectRatio,
YGNodePaddingAndBorderForAxis(child, YGFlexDirectionColumn, width));
} else if (YGFloatIsUndefined(childHeight)) { } else if (YGFloatIsUndefined(childHeight)) {
childHeight = childHeight = marginColumn + (childWidth - marginRow) / child->style.aspectRatio;
marginColumn + fmaxf((childWidth - marginRow) / child->style.aspectRatio,
YGNodePaddingAndBorderForAxis(child, YGFlexDirectionRow, width));
} }
} }
} }
@ -1688,11 +1695,11 @@ static void YGNodeAbsoluteLayoutChild(const YGNodeRef node,
config); config);
if (YGNodeIsTrailingPosDefined(child, mainAxis) && !YGNodeIsLeadingPosDefined(child, mainAxis)) { if (YGNodeIsTrailingPosDefined(child, mainAxis) && !YGNodeIsLeadingPosDefined(child, mainAxis)) {
child->layout.position[leading[mainAxis]] = node->layout.measuredDimensions[dim[mainAxis]] - child->layout.position[leading[mainAxis]] =
child->layout.measuredDimensions[dim[mainAxis]] - node->layout.measuredDimensions[dim[mainAxis]] -
YGNodeTrailingBorder(node, mainAxis) - child->layout.measuredDimensions[dim[mainAxis]] - YGNodeTrailingBorder(node, mainAxis) -
YGNodeTrailingMargin(child, mainAxis, width) - YGNodeTrailingMargin(child, mainAxis, width) -
YGNodeTrailingPosition(child, mainAxis, isMainAxisRow ? width : height); YGNodeTrailingPosition(child, mainAxis, isMainAxisRow ? width : height);
} else if (!YGNodeIsLeadingPosDefined(child, mainAxis) && } else if (!YGNodeIsLeadingPosDefined(child, mainAxis) &&
node->style.justifyContent == YGJustifyCenter) { node->style.justifyContent == YGJustifyCenter) {
child->layout.position[leading[mainAxis]] = (node->layout.measuredDimensions[dim[mainAxis]] - child->layout.position[leading[mainAxis]] = (node->layout.measuredDimensions[dim[mainAxis]] -
@ -1706,11 +1713,11 @@ static void YGNodeAbsoluteLayoutChild(const YGNodeRef node,
if (YGNodeIsTrailingPosDefined(child, crossAxis) && if (YGNodeIsTrailingPosDefined(child, crossAxis) &&
!YGNodeIsLeadingPosDefined(child, crossAxis)) { !YGNodeIsLeadingPosDefined(child, crossAxis)) {
child->layout.position[leading[crossAxis]] = node->layout.measuredDimensions[dim[crossAxis]] - child->layout.position[leading[crossAxis]] =
child->layout.measuredDimensions[dim[crossAxis]] - node->layout.measuredDimensions[dim[crossAxis]] -
YGNodeTrailingBorder(node, crossAxis) - child->layout.measuredDimensions[dim[crossAxis]] - YGNodeTrailingBorder(node, crossAxis) -
YGNodeTrailingMargin(child, crossAxis, width) - YGNodeTrailingMargin(child, crossAxis, width) -
YGNodeTrailingPosition(child, crossAxis, isMainAxisRow ? height : width); YGNodeTrailingPosition(child, crossAxis, isMainAxisRow ? height : width);
} else if (!YGNodeIsLeadingPosDefined(child, crossAxis) && } else if (!YGNodeIsLeadingPosDefined(child, crossAxis) &&
YGNodeAlignItem(node, child) == YGAlignCenter) { YGNodeAlignItem(node, child) == YGAlignCenter) {
child->layout.position[leading[crossAxis]] = child->layout.position[leading[crossAxis]] =
@ -1718,7 +1725,8 @@ static void YGNodeAbsoluteLayoutChild(const YGNodeRef node,
child->layout.measuredDimensions[dim[crossAxis]]) / child->layout.measuredDimensions[dim[crossAxis]]) /
2.0f; 2.0f;
} else if (!YGNodeIsLeadingPosDefined(child, crossAxis) && } else if (!YGNodeIsLeadingPosDefined(child, crossAxis) &&
((YGNodeAlignItem(node, child) == YGAlignFlexEnd) ^ (node->style.flexWrap == YGWrapWrapReverse))) { ((YGNodeAlignItem(node, child) == YGAlignFlexEnd) ^
(node->style.flexWrap == YGWrapWrapReverse))) {
child->layout.position[leading[crossAxis]] = (node->layout.measuredDimensions[dim[crossAxis]] - child->layout.position[leading[crossAxis]] = (node->layout.measuredDimensions[dim[crossAxis]] -
child->layout.measuredDimensions[dim[crossAxis]]); child->layout.measuredDimensions[dim[crossAxis]]);
} }
@ -1742,11 +1750,12 @@ static void YGNodeWithMeasureFuncSetMeasuredDimensions(const YGNodeRef node,
// We want to make sure we don't call measure with negative size // We want to make sure we don't call measure with negative size
const float innerWidth = YGFloatIsUndefined(availableWidth) const float innerWidth = YGFloatIsUndefined(availableWidth)
? availableWidth ? availableWidth
: fmaxf(0, availableWidth - marginAxisRow - paddingAndBorderAxisRow); : fmaxf(0, availableWidth - marginAxisRow - paddingAndBorderAxisRow);
const float innerHeight = YGFloatIsUndefined(availableHeight) const float innerHeight =
? availableHeight YGFloatIsUndefined(availableHeight)
: fmaxf(0, availableHeight - marginAxisColumn - paddingAndBorderAxisColumn); ? availableHeight
: fmaxf(0, availableHeight - marginAxisColumn - paddingAndBorderAxisColumn);
if (widthMeasureMode == YGMeasureModeExactly && heightMeasureMode == YGMeasureModeExactly) { if (widthMeasureMode == YGMeasureModeExactly && heightMeasureMode == YGMeasureModeExactly) {
// Don't bother sizing the text if both dimensions are already defined. // Don't bother sizing the text if both dimensions are already defined.
@ -2244,7 +2253,7 @@ static void YGNodelayoutImpl(const YGNodeRef node,
const float childMarginMainAxis = YGNodeMarginForAxis(child, mainAxis, availableInnerWidth); const float childMarginMainAxis = YGNodeMarginForAxis(child, mainAxis, availableInnerWidth);
const float flexBasisWithMaxConstraints = const float flexBasisWithMaxConstraints =
fminf(YGResolveValue(&child->style.maxDimensions[dim[mainAxis]], mainAxisParentSize), fminf(YGResolveValue(&child->style.maxDimensions[dim[mainAxis]], mainAxisParentSize),
child->layout.computedFlexBasis); child->layout.computedFlexBasis);
const float flexBasisWithMinAndMaxConstraints = const float flexBasisWithMinAndMaxConstraints =
fmaxf(YGResolveValue(&child->style.minDimensions[dim[mainAxis]], mainAxisParentSize), fmaxf(YGResolveValue(&child->style.minDimensions[dim[mainAxis]], mainAxisParentSize),
flexBasisWithMaxConstraints); flexBasisWithMaxConstraints);
@ -2498,11 +2507,21 @@ static void YGNodelayoutImpl(const YGNodeRef node,
YGMeasureMode childCrossMeasureMode; YGMeasureMode childCrossMeasureMode;
YGMeasureMode childMainMeasureMode = YGMeasureModeExactly; YGMeasureMode childMainMeasureMode = YGMeasureModeExactly;
if (!YGFloatIsUndefined(availableInnerCrossDim) && if (!YGFloatIsUndefined(currentRelativeChild->style.aspectRatio)) {
!YGNodeIsStyleDimDefined(currentRelativeChild, crossAxis, availableInnerCrossDim) && childCrossSize =
measureModeCrossDim == YGMeasureModeExactly && isMainAxisRow
!(isNodeFlexWrap && flexBasisOverflows) && ? (childMainSize - marginMain) / currentRelativeChild->style.aspectRatio
YGNodeAlignItem(node, currentRelativeChild) == YGAlignStretch) { : (childMainSize - marginMain) * currentRelativeChild->style.aspectRatio;
childCrossMeasureMode = YGMeasureModeExactly;
childCrossSize += marginCross;
} else if (!YGFloatIsUndefined(availableInnerCrossDim) &&
!YGNodeIsStyleDimDefined(currentRelativeChild,
crossAxis,
availableInnerCrossDim) &&
measureModeCrossDim == YGMeasureModeExactly &&
!(isNodeFlexWrap && flexBasisOverflows) &&
YGNodeAlignItem(node, currentRelativeChild) == YGAlignStretch) {
childCrossSize = availableInnerCrossDim; childCrossSize = availableInnerCrossDim;
childCrossMeasureMode = YGMeasureModeExactly; childCrossMeasureMode = YGMeasureModeExactly;
} else if (!YGNodeIsStyleDimDefined(currentRelativeChild, } else if (!YGNodeIsStyleDimDefined(currentRelativeChild,
@ -2523,26 +2542,6 @@ static void YGNodelayoutImpl(const YGNodeRef node,
: YGMeasureModeExactly; : YGMeasureModeExactly;
} }
if (!YGFloatIsUndefined(currentRelativeChild->style.aspectRatio)) {
childCrossSize = fmaxf(
isMainAxisRow
? (childMainSize - marginMain) / currentRelativeChild->style.aspectRatio
: (childMainSize - marginMain) * currentRelativeChild->style.aspectRatio,
YGNodePaddingAndBorderForAxis(currentRelativeChild, crossAxis, availableInnerWidth));
childCrossMeasureMode = YGMeasureModeExactly;
// Parent size constraint should have higher priority than flex
if (YGNodeIsFlex(currentRelativeChild)) {
childCrossSize = fminf(childCrossSize - marginCross, availableInnerCrossDim);
childMainSize =
marginMain + (isMainAxisRow
? childCrossSize * currentRelativeChild->style.aspectRatio
: childCrossSize / currentRelativeChild->style.aspectRatio);
}
childCrossSize += marginCross;
}
YGConstrainMaxSizeForMode(currentRelativeChild, YGConstrainMaxSizeForMode(currentRelativeChild,
mainAxis, mainAxis,
availableInnerMainDim, availableInnerMainDim,
@ -3160,9 +3159,9 @@ static inline bool YGMeasureModeNewMeasureSizeIsStricterAndStillValid(YGMeasureM
} }
float YGRoundValueToPixelGrid(const float value, float YGRoundValueToPixelGrid(const float value,
const float pointScaleFactor, const float pointScaleFactor,
const bool forceCeil, const bool forceCeil,
const bool forceFloor) { const bool forceFloor) {
float scaledValue = value * pointScaleFactor; float scaledValue = value * pointScaleFactor;
float fractial = fmodf(scaledValue, 1.0); float fractial = fmodf(scaledValue, 1.0);
if (YGFloatsEqual(fractial, 0)) { if (YGFloatsEqual(fractial, 0)) {
@ -3199,13 +3198,25 @@ bool YGNodeCanUseCachedMeasurement(const YGMeasureMode widthMode,
return false; return false;
} }
bool useRoundedComparison = config != NULL && config->pointScaleFactor != 0; bool useRoundedComparison = config != NULL && config->pointScaleFactor != 0;
const float effectiveWidth = useRoundedComparison ? YGRoundValueToPixelGrid(width, config->pointScaleFactor, false, false) : width; const float effectiveWidth =
const float effectiveHeight = useRoundedComparison ? YGRoundValueToPixelGrid(height, config->pointScaleFactor, false, false) : height; useRoundedComparison ? YGRoundValueToPixelGrid(width, config->pointScaleFactor, false, false)
const float effectiveLastWidth = useRoundedComparison ? YGRoundValueToPixelGrid(lastWidth, config->pointScaleFactor, false, false) : lastWidth; : width;
const float effectiveLastHeight = useRoundedComparison ? YGRoundValueToPixelGrid(lastHeight, config->pointScaleFactor, false, false) : lastHeight; const float effectiveHeight =
useRoundedComparison ? YGRoundValueToPixelGrid(height, config->pointScaleFactor, false, false)
: height;
const float effectiveLastWidth =
useRoundedComparison
? YGRoundValueToPixelGrid(lastWidth, config->pointScaleFactor, false, false)
: lastWidth;
const float effectiveLastHeight =
useRoundedComparison
? YGRoundValueToPixelGrid(lastHeight, config->pointScaleFactor, false, false)
: lastHeight;
const bool hasSameWidthSpec = lastWidthMode == widthMode && YGFloatsEqual(effectiveLastWidth, effectiveWidth); const bool hasSameWidthSpec =
const bool hasSameHeightSpec = lastHeightMode == heightMode && YGFloatsEqual(effectiveLastHeight, effectiveHeight); lastWidthMode == widthMode && YGFloatsEqual(effectiveLastWidth, effectiveWidth);
const bool hasSameHeightSpec =
lastHeightMode == heightMode && YGFloatsEqual(effectiveLastHeight, effectiveHeight);
const bool widthIsCompatible = const bool widthIsCompatible =
hasSameWidthSpec || YGMeasureModeSizeIsExactAndMatchesOldMeasuredSize(widthMode, hasSameWidthSpec || YGMeasureModeSizeIsExactAndMatchesOldMeasuredSize(widthMode,
@ -3479,7 +3490,8 @@ static void YGRoundToPixelGrid(const YGNodeRef node,
node->layout.position[YGEdgeTop] = node->layout.position[YGEdgeTop] =
YGRoundValueToPixelGrid(nodeTop, pointScaleFactor, false, textRounding); YGRoundValueToPixelGrid(nodeTop, pointScaleFactor, false, textRounding);
// We multiply dimension by scale factor and if the result is close to the whole number, we don't have any fraction // We multiply dimension by scale factor and if the result is close to the whole number, we don't
// have any fraction
// To verify if the result is close to whole number we want to check both floor and ceil numbers // To verify if the result is close to whole number we want to check both floor and ceil numbers
const bool hasFractionalWidth = !YGFloatsEqual(fmodf(nodeWidth * pointScaleFactor, 1.0), 0) && const bool hasFractionalWidth = !YGFloatsEqual(fmodf(nodeWidth * pointScaleFactor, 1.0), 0) &&
!YGFloatsEqual(fmodf(nodeWidth * pointScaleFactor, 1.0), 1.0); !YGFloatsEqual(fmodf(nodeWidth * pointScaleFactor, 1.0), 1.0);
@ -3487,18 +3499,16 @@ static void YGRoundToPixelGrid(const YGNodeRef node,
!YGFloatsEqual(fmodf(nodeHeight * pointScaleFactor, 1.0), 1.0); !YGFloatsEqual(fmodf(nodeHeight * pointScaleFactor, 1.0), 1.0);
node->layout.dimensions[YGDimensionWidth] = node->layout.dimensions[YGDimensionWidth] =
YGRoundValueToPixelGrid( YGRoundValueToPixelGrid(absoluteNodeRight,
absoluteNodeRight, pointScaleFactor,
pointScaleFactor, (textRounding && hasFractionalWidth),
(textRounding && hasFractionalWidth), (textRounding && !hasFractionalWidth)) -
(textRounding && !hasFractionalWidth)) -
YGRoundValueToPixelGrid(absoluteNodeLeft, pointScaleFactor, false, textRounding); YGRoundValueToPixelGrid(absoluteNodeLeft, pointScaleFactor, false, textRounding);
node->layout.dimensions[YGDimensionHeight] = node->layout.dimensions[YGDimensionHeight] =
YGRoundValueToPixelGrid( YGRoundValueToPixelGrid(absoluteNodeBottom,
absoluteNodeBottom, pointScaleFactor,
pointScaleFactor, (textRounding && hasFractionalHeight),
(textRounding && hasFractionalHeight), (textRounding && !hasFractionalHeight)) -
(textRounding && !hasFractionalHeight)) -
YGRoundValueToPixelGrid(absoluteNodeTop, pointScaleFactor, false, textRounding); YGRoundValueToPixelGrid(absoluteNodeTop, pointScaleFactor, false, textRounding);
const uint32_t childCount = YGNodeListCount(node->children); const uint32_t childCount = YGNodeListCount(node->children);