Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions tests/YGRelayoutTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,52 @@
#include <gtest/gtest.h>
#include <yoga/Yoga.h>

#include <cmath>
#include <limits>

/*
* Behaves like wrapping text: 2000pt of content laid out at the available
* width with a 20pt line height, so a narrower box measures taller.
*/
static YGSize _simulateWrappingText(
YGNodeConstRef /*node*/,
float width,
YGMeasureMode widthMode,
float height,
YGMeasureMode heightMode) {
const float maxWidth = widthMode == YGMeasureModeUndefined
? std::numeric_limits<float>::infinity()
: width;
const float lines = std::isfinite(maxWidth) && maxWidth > 0
? std::ceil(2000.0f / maxWidth)
: 1.0f;
const float naturalHeight = lines * 20.0f;

YGSize size{std::isfinite(maxWidth) ? maxWidth : 2000.0f, naturalHeight};
if (heightMode == YGMeasureModeExactly) {
size.height = height;
} else if (heightMode == YGMeasureModeAtMost && naturalHeight > height) {
size.height = height;
}
if (widthMode == YGMeasureModeExactly) {
size.width = width;
}

return size;
}

/*
* Pins the root to an exact size, the way a host platform does when the
* available space changes (e.g. a device rotation), then lays out.
*/
static void _layoutAtSize(YGNodeRef root, float width, float height) {
YGNodeStyleSetMinWidth(root, width);
YGNodeStyleSetMaxWidth(root, width);
YGNodeStyleSetMinHeight(root, height);
YGNodeStyleSetMaxHeight(root, height);
YGNodeCalculateLayout(root, width, height, YGDirectionLTR);
}

TEST(YogaTest, dont_cache_computed_flex_basis_between_layouts) {
YGConfigRef config = YGConfigNew();
YGConfigSetExperimentalFeatureEnabled(
Expand Down Expand Up @@ -253,3 +299,60 @@ TEST(YogaTest, has_new_layout_flag_set_static) {

YGNodeFreeRecursive(root);
}

/*
* A flex basis measured from a child's content during an earlier layout must
* not be reused once the available size changes. The wrapper levels let an
* ancestor's measurement cache answer for the whole subtree during the
* max-content pass, skipping the measurement that would otherwise refresh the
* stored basis.
*/
TEST(YogaTest, measured_flex_basis_is_not_reused_after_relayout_at_new_size) {
YGConfigRef config = YGConfigNew();

YGNodeRef root = YGNodeNewWithConfig(config);

YGNodeRef scrollView = YGNodeNewWithConfig(config);
YGNodeStyleSetOverflow(scrollView, YGOverflowScroll);
YGNodeStyleSetFlexGrow(scrollView, 1);
YGNodeStyleSetFlexShrink(scrollView, 1);
YGNodeInsertChild(root, scrollView, 0);

YGNodeRef contentContainer = YGNodeNewWithConfig(config);
YGNodeInsertChild(scrollView, contentContainer, 0);

YGNodeRef autoHeightColumn = YGNodeNewWithConfig(config);
YGNodeInsertChild(contentContainer, autoHeightColumn, 0);

YGNodeRef flexColumn = YGNodeNewWithConfig(config);
YGNodeStyleSetFlex(flexColumn, 1);
YGNodeInsertChild(autoHeightColumn, flexColumn, 0);

YGNodeRef fixedSibling = YGNodeNewWithConfig(config);
YGNodeStyleSetHeight(fixedSibling, 24);
YGNodeInsertChild(flexColumn, fixedSibling, 0);

YGNodeRef wrapper0 = YGNodeNewWithConfig(config);
YGNodeInsertChild(flexColumn, wrapper0, 1);

YGNodeRef wrapper1 = YGNodeNewWithConfig(config);
YGNodeInsertChild(wrapper0, wrapper1, 0);

YGNodeRef text = YGNodeNewWithConfig(config);
YGNodeStyleSetFlex(text, 1);
YGNodeSetMeasureFunc(text, _simulateWrappingText);
YGNodeInsertChild(wrapper1, text, 0);

_layoutAtSize(root, 400, 800);
const float portraitHeight = YGNodeLayoutGetHeight(text);

_layoutAtSize(root, 800, 400);
_layoutAtSize(root, 400, 800);

ASSERT_FLOAT_EQ(portraitHeight, YGNodeLayoutGetHeight(text));
ASSERT_FLOAT_EQ(
YGNodeLayoutGetHeight(wrapper0), YGNodeLayoutGetHeight(wrapper1));

YGNodeFreeRecursive(root);
YGConfigFree(config);
}
33 changes: 28 additions & 5 deletions yoga/algorithm/CalculateLayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -266,18 +266,37 @@ static void computeFlexBasisForChild(
node->getConfig()->isExperimentalFeatureEnabled(
ExperimentalFeature::FixFlexBasisFitContent);

const bool useResolvedFlexBasis =
bool useResolvedFlexBasis =
resolvedFlexBasis.isDefined() && yoga::isDefined(mainAxisSize);

// Whether the resolved-flex-basis branch below would keep a basis stored by
// an earlier layout instead of writing a fresh one.
const bool retainsStoredFlexBasis =
child->getLayout().computedFlexBasis.isDefined() &&
!(child->getConfig()->isExperimentalFeatureEnabled(
ExperimentalFeature::WebFlexBasis) &&
child->getLayout().computedFlexBasisGeneration != generationCount);

// A flex basis measured from the child's content during an earlier layout
// must not be retained. The max-content pass that would have refreshed it
// can be skipped entirely when an ancestor's measurement cache answers for
// the whole subtree, leaving a value computed against a different available
// size. Measuring again does exactly what that skipped pass would have done
// (and usually hits the child's own measurement cache). A retained
// *resolved* basis is still valid and is left alone.
if (useResolvedFlexBasis && retainsStoredFlexBasis &&
child->getLayout().computedFlexBasisIsMeasured &&
child->getLayout().computedFlexBasisGeneration != generationCount) {
useResolvedFlexBasis = false;
}

if (useResolvedFlexBasis) {
if (child->getLayout().computedFlexBasis.isUndefined() ||
(child->getConfig()->isExperimentalFeatureEnabled(
ExperimentalFeature::WebFlexBasis) &&
child->getLayout().computedFlexBasisGeneration != generationCount)) {
if (!retainsStoredFlexBasis) {
const FloatOptional paddingAndBorder = FloatOptional(
paddingAndBorderForAxis(child, mainAxis, direction, ownerWidth));
child->setLayoutComputedFlexBasis(
yoga::maxOrDefined(resolvedFlexBasis, paddingAndBorder));
child->setLayoutComputedFlexBasisIsMeasured(false);
}
} else if (isMainAxisRow && isRowStyleDimDefined) {
// The width is definite, so use that as the flex basis.
Expand All @@ -290,6 +309,7 @@ static void computeFlexBasisForChild(
child->getResolvedDimension(
direction, Dimension::Width, ownerWidth, ownerWidth),
paddingAndBorder));
child->setLayoutComputedFlexBasisIsMeasured(false);
} else if (!isMainAxisRow && isColumnStyleDimDefined) {
// The height is definite, so use that as the flex basis.
const FloatOptional paddingAndBorder =
Expand All @@ -300,6 +320,7 @@ static void computeFlexBasisForChild(
child->getResolvedDimension(
direction, Dimension::Height, ownerHeight, ownerWidth),
paddingAndBorder));
child->setLayoutComputedFlexBasisIsMeasured(false);
} else {
// Compute the flex basis and hypothetical main size (i.e. the clamped flex
// basis).
Expand Down Expand Up @@ -454,6 +475,7 @@ static void computeFlexBasisForChild(
yoga::maxOrDefined(
child->getLayout().measuredDimension(dimension(mainAxis)),
paddingAndBorderForAxis(child, mainAxis, direction, ownerWidth))));
child->setLayoutComputedFlexBasisIsMeasured(true);
}
child->setLayoutComputedFlexBasisGeneration(generationCount);
}
Expand Down Expand Up @@ -801,6 +823,7 @@ static float computeFlexBasisForChildren(
if (child == singleFlexChild) {
child->setLayoutComputedFlexBasisGeneration(generationCount);
child->setLayoutComputedFlexBasis(FloatOptional(0));
child->setLayoutComputedFlexBasisIsMeasured(false);
} else {
computeFlexBasisForChild(
node,
Expand Down
3 changes: 2 additions & 1 deletion yoga/node/LayoutResults.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ bool LayoutResults::operator==(LayoutResults layout) const {
configVersion == layout.configVersion &&
nextCachedMeasurementsIndex == layout.nextCachedMeasurementsIndex &&
cachedLayout == layout.cachedLayout &&
computedFlexBasis == layout.computedFlexBasis;
computedFlexBasis == layout.computedFlexBasis &&
computedFlexBasisIsMeasured == layout.computedFlexBasisIsMeasured;

for (uint32_t i = 0; i < LayoutResults::MaxCachedMeasurements && isEqual;
++i) {
Expand Down
5 changes: 5 additions & 0 deletions yoga/node/LayoutResults.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ struct LayoutResults {
uint32_t computedFlexBasisGeneration = 0;
FloatOptional computedFlexBasis = {};

// True when `computedFlexBasis` holds a size measured from the child's own
// content (the max-content pass), false when it was resolved from
// `flex-basis` or from a style dimension.
bool computedFlexBasisIsMeasured = false;

// Per-flex-item floor along the main axis derived from CSS Flexbox §4.5
// automatic minimum sizing. Set by `resolveFlexibleLength` when the parent's
// config does NOT carry the `MinSizeUndefinedInsteadOfAuto` errata and the
Expand Down
6 changes: 6 additions & 0 deletions yoga/node/Node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,11 @@ void Node::setLayoutComputedFlexBasis(const FloatOptional computedFlexBasis) {
layout_.computedFlexBasis = computedFlexBasis;
}

void Node::setLayoutComputedFlexBasisIsMeasured(
const bool computedFlexBasisIsMeasured) {
layout_.computedFlexBasisIsMeasured = computedFlexBasisIsMeasured;
}

void Node::setLayoutPosition(float position, PhysicalEdge edge) {
layout_.setPosition(edge, position);
}
Expand Down Expand Up @@ -454,6 +459,7 @@ void Node::markDirtyAndPropagate() {
if (!isDirty_) {
setDirty(true);
setLayoutComputedFlexBasis(FloatOptional());
setLayoutComputedFlexBasisIsMeasured(false);
if (owner_ != nullptr) {
owner_->markDirtyAndPropagate();
}
Expand Down
1 change: 1 addition & 0 deletions yoga/node/Node.h
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ class YG_EXPORT Node : public ::YGNode {
void setChildren(const std::vector<Node*>& children);
void setLayoutLastOwnerDirection(Direction direction);
void setLayoutComputedFlexBasis(FloatOptional computedFlexBasis);
void setLayoutComputedFlexBasisIsMeasured(bool computedFlexBasisIsMeasured);
void setLayoutComputedFlexBasisGeneration(
uint32_t computedFlexBasisGeneration);
void setLayoutMeasuredDimension(float measuredDimension, Dimension dimension);
Expand Down