From e247e06fb7f41fcce512a2490ebb230b1a01190e Mon Sep 17 00:00:00 2001 From: reuk Date: Mon, 1 Nov 2021 17:07:31 +0000 Subject: [PATCH] FlexBox: Refactor and tidy implementation --- .../juce_gui_basics/layout/juce_FlexBox.cpp | 380 ++++++++---------- 1 file changed, 172 insertions(+), 208 deletions(-) diff --git a/modules/juce_gui_basics/layout/juce_FlexBox.cpp b/modules/juce_gui_basics/layout/juce_FlexBox.cpp index c93adeea36..e2fdb68950 100644 --- a/modules/juce_gui_basics/layout/juce_FlexBox.cpp +++ b/modules/juce_gui_basics/layout/juce_FlexBox.cpp @@ -30,11 +30,13 @@ struct FlexBoxLayoutCalculation { using Coord = double; + enum class Axis { main, cross }; + FlexBoxLayoutCalculation (FlexBox& fb, Coord w, Coord h) - : owner (fb), parentWidth (w), parentHeight (h), numItems (owner.items.size()), - isRowDirection (fb.flexDirection == FlexBox::Direction::row - || fb.flexDirection == FlexBox::Direction::rowReverse), - containerLineLength (isRowDirection ? parentWidth : parentHeight) + : owner (fb), parentWidth (w), parentHeight (h), numItems (owner.items.size()), + isRowDirection (fb.flexDirection == FlexBox::Direction::row + || fb.flexDirection == FlexBox::Direction::rowReverse), + containerLineLength (getContainerSize (Axis::main)) { lineItems.calloc (numItems * numItems); lineInfo.calloc (numItems); @@ -59,22 +61,6 @@ struct FlexBoxLayoutCalculation lockedMarginTop = getValueOrZeroIfAuto (item->margin.top); lockedMarginBottom = getValueOrZeroIfAuto (item->margin.bottom); } - - void setWidthChecked (Coord newWidth) noexcept - { - if (isAssigned (item->maxWidth)) newWidth = jmin (newWidth, static_cast (item->maxWidth)); - if (isAssigned (item->minWidth)) newWidth = jmax (newWidth, static_cast (item->minWidth)); - - lockedWidth = newWidth; - } - - void setHeightChecked (Coord newHeight) noexcept - { - if (isAssigned (item->maxHeight)) newHeight = jmin (newHeight, (Coord) item->maxHeight); - if (isAssigned (item->minHeight)) newHeight = jmax (newHeight, (Coord) item->minHeight); - - lockedHeight = newHeight; - } }; struct RowInfo @@ -102,6 +88,65 @@ struct FlexBoxLayoutCalculation static bool isAssigned (Coord value) noexcept { return value != FlexItem::notAssigned; } static Coord getValueOrZeroIfAuto (Coord value) noexcept { return isAuto (value) ? Coord() : value; } + //============================================================================== + bool isSingleLine() const { return owner.flexWrap == FlexBox::Wrap::noWrap; } + + template + Value& pickForAxis (Axis axis, Value& x, Value& y) const + { + return (isRowDirection ? axis == Axis::main : axis == Axis::cross) ? x : y; + } + + auto& getStartMargin (Axis axis, ItemWithState& item) const + { + return pickForAxis (axis, item.item->margin.left, item.item->margin.top); + } + + auto& getEndMargin (Axis axis, ItemWithState& item) const + { + return pickForAxis (axis, item.item->margin.right, item.item->margin.bottom); + } + + auto& getStartLockedMargin (Axis axis, ItemWithState& item) const + { + return pickForAxis (axis, item.lockedMarginLeft, item.lockedMarginTop); + } + + auto& getEndLockedMargin (Axis axis, ItemWithState& item) const + { + return pickForAxis (axis, item.lockedMarginRight, item.lockedMarginBottom); + } + + auto& getLockedSize (Axis axis, ItemWithState& item) const + { + return pickForAxis (axis, item.lockedWidth, item.lockedHeight); + } + + auto& getPreferredSize (Axis axis, ItemWithState& item) const + { + return pickForAxis (axis, item.preferredWidth, item.preferredHeight); + } + + Coord getContainerSize (Axis axis) const + { + return pickForAxis (axis, parentWidth, parentHeight); + } + + auto& getItemSize (Axis axis, ItemWithState& item) const + { + return pickForAxis (axis, item.item->width, item.item->height); + } + + auto& getMinSize (Axis axis, ItemWithState& item) const + { + return pickForAxis (axis, item.item->minWidth, item.item->minHeight); + } + + auto& getMaxSize (Axis axis, ItemWithState& item) const + { + return pickForAxis (axis, item.item->maxWidth, item.item->maxHeight); + } + //============================================================================== void createStates() { @@ -115,14 +160,14 @@ struct FlexBoxLayoutCalculation for (auto& item : itemStates) { - item.preferredWidth = getPreferredWidth (item); - item.preferredHeight = getPreferredHeight (item); + for (auto& axis : { Axis::main, Axis::cross }) + getPreferredSize (axis, item) = computePreferredSize (axis, item); } } void initialiseItems() noexcept { - if (owner.flexWrap == FlexBox::Wrap::noWrap) // for single-line, all items go in line 1 + if (isSingleLine()) // for single-line, all items go in line 1 { lineInfo[0].numItems = numItems; int i = 0; @@ -143,7 +188,7 @@ struct FlexBoxLayoutCalculation { item.resetItemLockedSize(); - const auto flexitemLength = getItemLength (item); + const auto flexitemLength = getItemMainSize (item); if (flexitemLength > currentLength) { @@ -195,16 +240,8 @@ struct FlexBoxLayoutCalculation { auto& item = getItem (column, row); - if (isRowDirection) - { - if (isAuto (item.item->margin.left)) ++allFlexGrow; - if (isAuto (item.item->margin.right)) ++allFlexGrow; - } - else - { - if (isAuto (item.item->margin.top)) ++allFlexGrow; - if (isAuto (item.item->margin.bottom)) ++allFlexGrow; - } + if (isAuto (getStartMargin (Axis::main, item))) ++allFlexGrow; + if (isAuto (getEndMargin (Axis::main, item))) ++allFlexGrow; } const auto changeUnitWidth = remainingLength / allFlexGrow; @@ -215,16 +252,11 @@ struct FlexBoxLayoutCalculation { auto& item = getItem (column, row); - if (isRowDirection) - { - if (isAuto (item.item->margin.left)) item.lockedMarginLeft = changeUnitWidth; - if (isAuto (item.item->margin.right)) item.lockedMarginRight = changeUnitWidth; - } - else - { - if (isAuto (item.item->margin.top)) item.lockedMarginTop = changeUnitWidth; - if (isAuto (item.item->margin.bottom)) item.lockedMarginBottom = changeUnitWidth; - } + if (isAuto (getStartMargin (Axis::main, item))) + getStartLockedMargin (Axis::main, item) = changeUnitWidth; + + if (isAuto (getEndMargin (Axis::main, item))) + getEndLockedMargin (Axis::main, item) = changeUnitWidth; } } } @@ -235,9 +267,9 @@ struct FlexBoxLayoutCalculation // https://www.w3.org/TR/css-flexbox-1/#algo-cross-line // If the flex container is single-line and has a definite cross size, the cross size of the // flex line is the flex container’s inner cross size. - if (owner.flexWrap == FlexBox::Wrap::noWrap) + if (isSingleLine()) { - lineInfo[0].crossSize = isRowDirection ? parentHeight : parentWidth; + lineInfo[0].crossSize = getContainerSize (Axis::cross); } else { @@ -247,12 +279,7 @@ struct FlexBoxLayoutCalculation const auto numColumns = lineInfo[row].numItems; for (int column = 0; column < numColumns; ++column) - { - auto& item = getItem (column, row); - - maxSize = jmax (maxSize, isRowDirection ? item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom - : item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight); - } + maxSize = jmax (maxSize, getItemCrossSize (getItem (column, row))); lineInfo[row].crossSize = maxSize; } @@ -280,7 +307,7 @@ struct FlexBoxLayoutCalculation void alignLinesPerAlignContent() noexcept { - containerCrossLength = isRowDirection ? parentHeight : parentWidth; + containerCrossLength = getContainerSize (Axis::cross); if (owner.alignContent == FlexBox::AlignContent::flexStart) { @@ -357,32 +384,16 @@ struct FlexBoxLayoutCalculation { auto& item = getItem (column, row); - if (isRowDirection) - { - item.lockedMarginTop = [&] - { - if (isAuto (item.item->margin.top) && isAuto (item.item->margin.bottom)) - return (crossSizeForLine - item.lockedHeight) / 2; - - if (isAuto (item.item->margin.top)) - return crossSizeForLine - item.lockedHeight - item.item->margin.bottom; - - return item.lockedMarginTop; - }(); - } - else + getStartLockedMargin (Axis::cross, item) = [&] { - item.lockedMarginLeft = [&] - { - if (isAuto (item.item->margin.left) && isAuto (item.item->margin.right)) - return (crossSizeForLine - item.lockedWidth) / 2; + if (isAuto (getStartMargin (Axis::cross, item)) && isAuto (getEndMargin (Axis::cross, item))) + return (crossSizeForLine - getLockedSize (Axis::cross, item)) / 2; - if (isAuto (item.item->margin.left)) - return crossSizeForLine - item.lockedWidth - item.item->margin.right; + if (isAuto (getStartMargin (Axis::cross, item))) + return crossSizeForLine - getLockedSize (Axis::cross, item) - getEndMargin (Axis::cross, item); - return item.lockedMarginLeft; - }(); - } + return getStartLockedMargin (Axis::cross, item); + }(); } } } @@ -399,8 +410,7 @@ struct FlexBoxLayoutCalculation { auto& item = getItem (column, row); - if (isAuto (isRowDirection ? item.item->margin.top : item.item->margin.left) - || isAuto (isRowDirection ? item.item->margin.bottom : item.item->margin.right)) + if (isAuto (getStartMargin (Axis::cross, item)) || isAuto (getEndMargin (Axis::cross, item))) continue; const auto alignment = [&] @@ -417,60 +427,48 @@ struct FlexBoxLayoutCalculation return owner.alignItems; }(); - switch (alignment) + getStartLockedMargin (Axis::cross, item) = [&] { - // https://www.w3.org/TR/css-flexbox-1/#valdef-align-items-flex-start - // The cross-start margin edge of the flex item is placed flush with the - // cross-start edge of the line. - case FlexBox::AlignItems::flexStart: + switch (alignment) { - if (isRowDirection) - item.lockedMarginTop = item.item->margin.top; - else - item.lockedMarginLeft = item.item->margin.left; - break; + // https://www.w3.org/TR/css-flexbox-1/#valdef-align-items-flex-start + // The cross-start margin edge of the flex item is placed flush with the + // cross-start edge of the line. + case FlexBox::AlignItems::flexStart: + return (Coord) getStartMargin (Axis::cross, item); + + // https://www.w3.org/TR/css-flexbox-1/#valdef-align-items-flex-end + // The cross-end margin edge of the flex item is placed flush with the cross-end + // edge of the line. + case FlexBox::AlignItems::flexEnd: + return lineSize - getLockedSize (Axis::cross, item) - getEndMargin (Axis::cross, item); + + // https://www.w3.org/TR/css-flexbox-1/#valdef-align-items-center + // The flex item’s margin box is centered in the cross axis within the line. + case FlexBox::AlignItems::center: + return getStartMargin (Axis::cross, item) + (lineSize - getLockedSize (Axis::cross, item) - getStartMargin (Axis::cross, item) - getEndMargin (Axis::cross, item)) / 2; + + // https://www.w3.org/TR/css-flexbox-1/#valdef-align-items-stretch + case FlexBox::AlignItems::stretch: + return (Coord) getStartMargin (Axis::cross, item); } - // https://www.w3.org/TR/css-flexbox-1/#valdef-align-items-flex-end - // The cross-end margin edge of the flex item is placed flush with the cross-end - // edge of the line. - case FlexBox::AlignItems::flexEnd: - { - if (isRowDirection) - item.lockedMarginTop = lineSize - item.lockedHeight - item.item->margin.bottom; - else - item.lockedMarginLeft = lineSize - item.lockedWidth - item.item->margin.right; - break; - } + jassertfalse; + return 0.0; + }(); - // https://www.w3.org/TR/css-flexbox-1/#valdef-align-items-center - // The flex item’s margin box is centered in the cross axis within the line. - case FlexBox::AlignItems::center: - { - if (isRowDirection) - item.lockedMarginTop = item.item->margin.top + (lineSize - item.lockedHeight - item.item->margin.top - item.item->margin.bottom) / 2; - else - item.lockedMarginLeft = item.item->margin.left + (lineSize - item.lockedWidth - item.item->margin.left - item.item->margin.right) / 2; - break; - } + if (alignment == FlexBox::AlignItems::stretch) + { + auto newSize = isAssigned (getItemSize (Axis::cross, item)) ? computePreferredSize (Axis::cross, item) + : lineSize - getStartMargin (Axis::cross, item) - getEndMargin (Axis::cross, item); - // https://www.w3.org/TR/css-flexbox-1/#valdef-align-items-stretch - case FlexBox::AlignItems::stretch: - { - if (isRowDirection) - { - item.lockedMarginTop = item.item->margin.top; - item.setHeightChecked (isAssigned (item.item->height) ? getPreferredHeight (item) - : lineSize - item.item->margin.top - item.item->margin.bottom); - } - else - { - item.lockedMarginLeft = item.item->margin.left; - item.setWidthChecked (isAssigned (item.item->width) ? getPreferredWidth (item) - : lineSize - item.item->margin.left - item.item->margin.right); - } - break; - } + if (isAssigned (getMaxSize (Axis::cross, item))) + newSize = jmin (newSize, (Coord) getMaxSize (Axis::cross, item)); + + if (isAssigned (getMinSize (Axis::cross, item))) + newSize = jmax (newSize, (Coord) getMinSize (Axis::cross, item)); + + getLockedSize (Axis::cross, item) = newSize; } } } @@ -510,20 +508,15 @@ struct FlexBoxLayoutCalculation { auto& item = getItem (column, row); - if (isRowDirection) - { - item.lockedMarginLeft += additionalMarginLeft; - item.lockedMarginRight += additionalMarginRight; - item.item->currentBounds.setPosition ((float) (x + item.lockedMarginLeft), (float) item.lockedMarginTop); - x += item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight; - } - else - { - item.lockedMarginTop += additionalMarginLeft; - item.lockedMarginBottom += additionalMarginRight; - item.item->currentBounds.setPosition ((float) item.lockedMarginLeft, (float) (x + item.lockedMarginTop)); - x += item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom; - } + getStartLockedMargin (Axis::main, item) += additionalMarginLeft; + getEndLockedMargin (Axis::main, item) += additionalMarginRight; + + item.item->currentBounds.setPosition (isRowDirection ? (float) (x + item.lockedMarginLeft) + : (float) item.lockedMarginLeft, + isRowDirection ? (float) item.lockedMarginTop + : (float) (x + item.lockedMarginTop)); + + x += getItemMainSize (item); } } } @@ -578,8 +571,9 @@ private: void resetItem (ItemWithState& item) noexcept { item.locked = false; - item.lockedWidth = getPreferredWidth (item); - item.lockedHeight = getPreferredHeight (item); + + for (auto& axis : { Axis::main, Axis::cross }) + getLockedSize (axis, item) = computePreferredSize (axis, item); } bool layoutRowItems (const int row) noexcept @@ -594,11 +588,11 @@ private: if (item.locked) { - flexContainerLength -= getItemLength (item); + flexContainerLength -= getItemMainSize (item); } else { - totalItemsLength += getItemLength (item); + totalItemsLength += getItemMainSize (item); totalFlexGrow += item.item->flexGrow; totalFlexShrink += item.item->flexShrink; } @@ -642,12 +636,7 @@ private: const auto numColumns = lineInfo[row].numItems; for (int column = 0; column < numColumns; ++column) - { - const auto& item = getItem (column, row); - - lineInfo[row].totalLength += isRowDirection ? item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight - : item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom; - } + lineInfo[row].totalLength += getItemMainSize (getItem (column, row)); } } @@ -682,7 +671,7 @@ private: } } - Coord getItemLength (const ItemWithState& item) const noexcept + Coord getItemMainSize (const ItemWithState& item) const noexcept { return isRowDirection ? item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight : item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom; @@ -698,84 +687,59 @@ private: { bool ok = false; - if (isRowDirection) - { - const auto prefWidth = getPreferredWidth (item); + const auto prefSize = computePreferredSize (Axis::main, item); - if (isAssigned (item.item->maxWidth) && item.item->maxWidth < prefWidth + length) - { - item.lockedWidth = item.item->maxWidth; - item.locked = true; - } - else if (isAssigned (prefWidth) && item.item->minWidth > prefWidth + length) - { - item.lockedWidth = item.item->minWidth; - item.locked = true; - } - else - { - ok = true; - item.lockedWidth = prefWidth + length; - } + const auto pickForMainAxis = [this] (auto& a, auto& b) -> auto& { return pickForAxis (Axis::main, a, b); }; - lineInfo[row].totalLength += item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight; + if (isAssigned (pickForMainAxis (item.item->maxWidth, item.item->maxHeight)) + && pickForMainAxis (item.item->maxWidth, item.item->maxHeight) < prefSize + length) + { + pickForMainAxis (item.lockedWidth, item.lockedHeight) = pickForMainAxis (item.item->maxWidth, item.item->maxHeight); + item.locked = true; + } + else if (isAssigned (prefSize) && pickForMainAxis (item.item->minWidth, item.item->minHeight) > prefSize + length) + { + pickForMainAxis (item.lockedWidth, item.lockedHeight) = pickForMainAxis (item.item->minWidth, item.item->minHeight); + item.locked = true; } else { - const auto prefHeight = getPreferredHeight (item); - - if (isAssigned (item.item->maxHeight) && item.item->maxHeight < prefHeight + length) - { - item.lockedHeight = item.item->maxHeight; - item.locked = true; - } - else if (isAssigned (prefHeight) && item.item->minHeight > prefHeight + length) - { - item.lockedHeight = item.item->minHeight; - item.locked = true; - } - else - { - ok = true; - item.lockedHeight = prefHeight + length; - } - - lineInfo[row].totalLength += item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom; + ok = true; + pickForMainAxis (item.lockedWidth, item.lockedHeight) = prefSize + length; } + lineInfo[row].totalLength += pickForMainAxis (item.lockedWidth, item.lockedHeight) + + pickForMainAxis (item.lockedMarginLeft, item.lockedMarginTop) + + pickForMainAxis (item.lockedMarginRight, item.lockedMarginBottom); + return ok; } - Coord getPreferredWidth (const ItemWithState& itemWithState) const noexcept + Coord computePreferredSize (Axis axis, ItemWithState& itemWithState) const noexcept { const auto& item = *itemWithState.item; - auto preferredWidth = (item.flexBasis > 0 && isRowDirection) - ? item.flexBasis - : (isAssigned (item.width) ? item.width : item.minWidth); - if (isAssigned (item.minWidth) && preferredWidth < item.minWidth) return item.minWidth; - if (isAssigned (item.maxWidth) && preferredWidth > item.maxWidth) return item.maxWidth; + auto preferredSize = (item.flexBasis > 0 && axis == Axis::main) ? item.flexBasis + : (isAssigned (getItemSize (axis, itemWithState)) ? getItemSize (axis, itemWithState) + : getMinSize (axis, itemWithState)); - return preferredWidth; - } + const auto minSize = getMinSize (axis, itemWithState); - Coord getPreferredHeight (const ItemWithState& itemWithState) const noexcept - { - const auto& item = *itemWithState.item; - auto preferredHeight = (item.flexBasis > 0 && ! isRowDirection) - ? item.flexBasis - : (isAssigned (item.height) ? item.height : item.minHeight); + if (isAssigned (minSize) && preferredSize < minSize) + return minSize; + + const auto maxSize = getMaxSize (axis, itemWithState); - if (isAssigned (item.minHeight) && preferredHeight < item.minHeight) return item.minHeight; - if (isAssigned (item.maxHeight) && preferredHeight > item.maxHeight) return item.maxHeight; + if (isAssigned (maxSize) && maxSize < preferredSize) + return maxSize; - return preferredHeight; + return preferredSize; } }; //============================================================================== -FlexBox::FlexBox() noexcept {} -FlexBox::~FlexBox() noexcept {} +FlexBox::FlexBox() noexcept = default; +FlexBox::~FlexBox() noexcept = default; FlexBox::FlexBox (JustifyContent jc) noexcept : justifyContent (jc) {}