diff --git a/tests/YGMeasureCacheTest.cpp b/tests/YGMeasureCacheTest.cpp index 850c17e6d7..0567265458 100644 --- a/tests/YGMeasureCacheTest.cpp +++ b/tests/YGMeasureCacheTest.cpp @@ -176,3 +176,48 @@ TEST( ASSERT_EQ(1, measureCount); } + +TEST(YogaTest, keep_cached_measurement_when_owner_resizes_without_percentages) { + YGNodeRef root = YGNodeNew(); + YGNodeStyleSetWidth(root, 100); + + YGNodeRef root_child0 = YGNodeNew(); + int measureCount = 0; + YGNodeSetContext(root_child0, &measureCount); + YGNodeSetMeasureFunc(root_child0, _measureMax); + YGNodeStyleSetWidth(root_child0, 50); + YGNodeInsertChild(root, root_child0, 0); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + const int measuresBeforeResize = measureCount; + + YGNodeStyleSetWidth(root, 200); + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + ASSERT_EQ(measuresBeforeResize, measureCount); + + YGNodeFreeRecursive(root); +} + +TEST(YogaTest, remeasure_when_owner_resizes_with_percent_padding) { + YGNodeRef root = YGNodeNew(); + YGNodeStyleSetWidth(root, 100); + + YGNodeRef root_child0 = YGNodeNew(); + int measureCount = 0; + YGNodeSetContext(root_child0, &measureCount); + YGNodeSetMeasureFunc(root_child0, _measureMax); + YGNodeStyleSetWidth(root_child0, 50); + YGNodeStyleSetPaddingPercent(root_child0, YGEdgeTop, 10); + YGNodeInsertChild(root, root_child0, 0); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + ASSERT_FLOAT_EQ(10, YGNodeLayoutGetPadding(root_child0, YGEdgeTop)); + const int measuresBeforeResize = measureCount; + + YGNodeStyleSetWidth(root, 200); + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + ASSERT_FLOAT_EQ(20, YGNodeLayoutGetPadding(root_child0, YGEdgeTop)); + ASSERT_GT(measureCount, measuresBeforeResize); + + YGNodeFreeRecursive(root); +} diff --git a/tests/YGRelayoutTest.cpp b/tests/YGRelayoutTest.cpp index 593572055f..b37b905d23 100644 --- a/tests/YGRelayoutTest.cpp +++ b/tests/YGRelayoutTest.cpp @@ -253,3 +253,79 @@ TEST(YogaTest, has_new_layout_flag_set_static) { YGNodeFreeRecursive(root); } + +TEST(YogaTest, percent_padding_follows_owner_size_on_relayout) { + YGConfigRef config = YGConfigNew(); + + YGNodeRef root = YGNodeNewWithConfig(config); + YGNodeStyleSetWidth(root, 100); + + YGNodeRef root_child0 = YGNodeNewWithConfig(config); + YGNodeStyleSetWidth(root_child0, 50); + YGNodeStyleSetPaddingPercent(root_child0, YGEdgeLeft, 50); + YGNodeInsertChild(root, root_child0, 0); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + ASSERT_FLOAT_EQ(50, YGNodeLayoutGetPadding(root_child0, YGEdgeLeft)); + + YGNodeStyleSetWidth(root, 60); + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + ASSERT_FLOAT_EQ(30, YGNodeLayoutGetPadding(root_child0, YGEdgeLeft)); + + YGNodeFreeRecursive(root); + + YGConfigFree(config); +} + +TEST(YogaTest, percent_min_width_follows_owner_size_on_relayout) { + YGConfigRef config = YGConfigNew(); + + YGNodeRef root = YGNodeNewWithConfig(config); + YGNodeStyleSetWidth(root, 100); + + YGNodeRef root_child0 = YGNodeNewWithConfig(config); + YGNodeStyleSetWidth(root_child0, 50); + YGNodeStyleSetMinWidthPercent(root_child0, 100); + YGNodeInsertChild(root, root_child0, 0); + + YGNodeRef root_child0_child0 = YGNodeNewWithConfig(config); + YGNodeStyleSetHeight(root_child0_child0, 10); + YGNodeInsertChild(root_child0, root_child0_child0, 0); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetWidth(root_child0_child0)); + + YGNodeStyleSetWidth(root, 200); + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0)); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetWidth(root_child0_child0)); + + YGNodeFreeRecursive(root); + + YGConfigFree(config); +} + +TEST(YogaTest, percent_min_height_follows_owner_size_on_relayout) { + YGConfigRef config = YGConfigNew(); + + YGNodeRef root = YGNodeNewWithConfig(config); + YGNodeStyleSetFlexDirection(root, YGFlexDirectionRow); + YGNodeStyleSetHeight(root, 100); + + YGNodeRef root_child0 = YGNodeNewWithConfig(config); + YGNodeStyleSetHeight(root_child0, 50); + YGNodeStyleSetMinHeightPercent(root_child0, 100); + YGNodeInsertChild(root, root_child0, 0); + + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + ASSERT_FLOAT_EQ(100, YGNodeLayoutGetHeight(root_child0)); + + YGNodeStyleSetHeight(root, 200); + YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR); + ASSERT_FLOAT_EQ(200, YGNodeLayoutGetHeight(root_child0)); + + YGNodeFreeRecursive(root); + + YGConfigFree(config); +} diff --git a/yoga/algorithm/CalculateLayout.cpp b/yoga/algorithm/CalculateLayout.cpp index e8998c0693..401efaaee2 100644 --- a/yoga/algorithm/CalculateLayout.cpp +++ b/yoga/algorithm/CalculateLayout.cpp @@ -2672,6 +2672,19 @@ static void calculateLayoutImpl( // Input parameters are the same as calculateLayoutImpl (see above) // Return parameter is true if layout was performed, false if skipped // +// A cached result holds only for the owner size it was computed against when +// the node's style resolves a percentage against that size. +static bool isCachedForOwnerSize( + const yoga::Node* const node, + const CachedMeasurement& cachedMeasurement, + const float ownerWidth, + const float ownerHeight) { + return (!node->style().resolvesAgainstOwnerWidth() || + yoga::inexactEquals(cachedMeasurement.ownerWidth, ownerWidth)) && + (!node->style().resolvesAgainstOwnerHeight() || + yoga::inexactEquals(cachedMeasurement.ownerHeight, ownerHeight)); +} + bool calculateLayoutInternal( yoga::Node* const node, const float availableWidth, @@ -2704,6 +2717,8 @@ bool calculateLayoutInternal( layout->cachedLayout.heightSizingMode = SizingMode::MaxContent; layout->cachedLayout.computedWidth = -1; layout->cachedLayout.computedHeight = -1; + layout->cachedLayout.ownerWidth = -1; + layout->cachedLayout.ownerHeight = -1; } CachedMeasurement* cachedResults = nullptr; @@ -2736,7 +2751,9 @@ bool calculateLayoutInternal( layout->cachedLayout.computedHeight, marginAxisRow, marginAxisColumn, - node->getConfig())) { + node->getConfig()) && + isCachedForOwnerSize( + node, layout->cachedLayout, ownerWidth, ownerHeight)) { cachedResults = &layout->cachedLayout; } else { // Try to use the measurement cache. @@ -2754,7 +2771,9 @@ bool calculateLayoutInternal( layout->cachedMeasurements[i].computedHeight, marginAxisRow, marginAxisColumn, - node->getConfig())) { + node->getConfig()) && + isCachedForOwnerSize( + node, layout->cachedMeasurements[i], ownerWidth, ownerHeight)) { cachedResults = &layout->cachedMeasurements[i]; break; } @@ -2766,7 +2785,9 @@ bool calculateLayoutInternal( yoga::inexactEquals( layout->cachedLayout.availableHeight, availableHeight) && layout->cachedLayout.widthSizingMode == widthSizingMode && - layout->cachedLayout.heightSizingMode == heightSizingMode) { + layout->cachedLayout.heightSizingMode == heightSizingMode && + isCachedForOwnerSize( + node, layout->cachedLayout, ownerWidth, ownerHeight)) { cachedResults = &layout->cachedLayout; } } else { @@ -2776,7 +2797,9 @@ bool calculateLayoutInternal( yoga::inexactEquals( layout->cachedMeasurements[i].availableHeight, availableHeight) && layout->cachedMeasurements[i].widthSizingMode == widthSizingMode && - layout->cachedMeasurements[i].heightSizingMode == heightSizingMode) { + layout->cachedMeasurements[i].heightSizingMode == heightSizingMode && + isCachedForOwnerSize( + node, layout->cachedMeasurements[i], ownerWidth, ownerHeight)) { cachedResults = &layout->cachedMeasurements[i]; break; } @@ -2839,6 +2862,8 @@ bool calculateLayoutInternal( layout->measuredDimension(Dimension::Width); newCacheEntry->computedHeight = layout->measuredDimension(Dimension::Height); + newCacheEntry->ownerWidth = ownerWidth; + newCacheEntry->ownerHeight = ownerHeight; } } diff --git a/yoga/node/CachedMeasurement.h b/yoga/node/CachedMeasurement.h index 9c76280acf..4cee330f70 100644 --- a/yoga/node/CachedMeasurement.h +++ b/yoga/node/CachedMeasurement.h @@ -25,6 +25,9 @@ struct CachedMeasurement { float computedWidth{-1}; float computedHeight{-1}; + float ownerWidth{-1}; + float ownerHeight{-1}; + bool operator==(CachedMeasurement measurement) const { bool isEqual = widthSizingMode == measurement.widthSizingMode && heightSizingMode == measurement.heightSizingMode; @@ -45,6 +48,14 @@ struct CachedMeasurement { !yoga::isUndefined(measurement.computedHeight)) { isEqual = isEqual && computedHeight == measurement.computedHeight; } + if (!yoga::isUndefined(ownerWidth) || + !yoga::isUndefined(measurement.ownerWidth)) { + isEqual = isEqual && ownerWidth == measurement.ownerWidth; + } + if (!yoga::isUndefined(ownerHeight) || + !yoga::isUndefined(measurement.ownerHeight)) { + isEqual = isEqual && ownerHeight == measurement.ownerHeight; + } return isEqual; } diff --git a/yoga/style/Style.h b/yoga/style/Style.h index a25d3a28eb..9ce453b7fc 100644 --- a/yoga/style/Style.h +++ b/yoga/style/Style.h @@ -7,6 +7,7 @@ #pragma once +#include #include #include #include @@ -341,6 +342,22 @@ class YG_EXPORT Style { : FloatOptional{0.0}); } + // Percentage margins and paddings resolve against the owner's width on both + // axes, and a percentage size or size bound against the owner's size on its + // own axis. + bool resolvesAgainstOwnerWidth() const { + return anyIsPercent(margin_) || anyIsPercent(padding_) || + dimensions_[yoga::to_underlying(Dimension::Width)].isPercent() || + minDimensions_[yoga::to_underlying(Dimension::Width)].isPercent() || + maxDimensions_[yoga::to_underlying(Dimension::Width)].isPercent(); + } + + bool resolvesAgainstOwnerHeight() const { + return dimensions_[yoga::to_underlying(Dimension::Height)].isPercent() || + minDimensions_[yoga::to_underlying(Dimension::Height)].isPercent() || + maxDimensions_[yoga::to_underlying(Dimension::Height)].isPercent(); + } + FloatOptional aspectRatio() const { return pool_.getNumber(aspectRatio_); } @@ -677,6 +694,15 @@ class YG_EXPORT Style { using Edges = std::array()>; using Gutters = std::array()>; + template + static inline bool anyIsPercent( + const std::array& handles) { + return std::any_of( + handles.begin(), handles.end(), [](const StyleValueHandle& handle) { + return handle.isPercent(); + }); + } + static inline bool numbersEqual( const StyleValueHandle& lhsHandle, const StyleValuePool& lhsPool,