1142 lines
51 KiB
C++
1142 lines
51 KiB
C++
/*
|
||
==============================================================================
|
||
|
||
This file is part of the JUCE library.
|
||
Copyright (c) 2022 - Raw Material Software Limited
|
||
|
||
JUCE is an open source library subject to commercial or open-source
|
||
licensing.
|
||
|
||
By using JUCE, you agree to the terms of both the JUCE 7 End-User License
|
||
Agreement and JUCE Privacy Policy.
|
||
|
||
End User License Agreement: www.juce.com/juce-7-licence
|
||
Privacy Policy: www.juce.com/juce-privacy-policy
|
||
|
||
Or: You may also use this code under the terms of the GPL v3 (see
|
||
www.gnu.org/licenses).
|
||
|
||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||
DISCLAIMED.
|
||
|
||
==============================================================================
|
||
*/
|
||
|
||
namespace juce
|
||
{
|
||
|
||
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 (getContainerSize (Axis::main))
|
||
{
|
||
lineItems.calloc (numItems * numItems);
|
||
lineInfo.calloc (numItems);
|
||
}
|
||
|
||
struct ItemWithState
|
||
{
|
||
ItemWithState (FlexItem& source) noexcept : item (&source) {}
|
||
|
||
FlexItem* item;
|
||
Coord lockedWidth = 0, lockedHeight = 0;
|
||
Coord lockedMarginLeft = 0, lockedMarginRight = 0, lockedMarginTop = 0, lockedMarginBottom = 0;
|
||
Coord preferredWidth = 0, preferredHeight = 0;
|
||
bool locked = false;
|
||
|
||
void resetItemLockedSize() noexcept
|
||
{
|
||
lockedWidth = preferredWidth;
|
||
lockedHeight = preferredHeight;
|
||
lockedMarginLeft = getValueOrZeroIfAuto (item->margin.left);
|
||
lockedMarginRight = getValueOrZeroIfAuto (item->margin.right);
|
||
lockedMarginTop = getValueOrZeroIfAuto (item->margin.top);
|
||
lockedMarginBottom = getValueOrZeroIfAuto (item->margin.bottom);
|
||
}
|
||
};
|
||
|
||
struct RowInfo
|
||
{
|
||
int numItems;
|
||
Coord crossSize, lineY, totalLength;
|
||
};
|
||
|
||
FlexBox& owner;
|
||
const Coord parentWidth, parentHeight;
|
||
const int numItems;
|
||
const bool isRowDirection;
|
||
const Coord containerLineLength;
|
||
|
||
int numberOfRows = 1;
|
||
Coord containerCrossLength = 0;
|
||
|
||
HeapBlock<ItemWithState*> lineItems;
|
||
HeapBlock<RowInfo> lineInfo;
|
||
Array<ItemWithState> itemStates;
|
||
|
||
ItemWithState& getItem (int x, int y) const noexcept { return *lineItems[y * numItems + x]; }
|
||
|
||
static bool isAuto (Coord value) noexcept { return value == FlexItem::autoValue; }
|
||
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 <typename Value>
|
||
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()
|
||
{
|
||
itemStates.ensureStorageAllocated (numItems);
|
||
|
||
for (auto& item : owner.items)
|
||
itemStates.add (item);
|
||
|
||
std::stable_sort (itemStates.begin(), itemStates.end(),
|
||
[] (const ItemWithState& i1, const ItemWithState& i2) { return i1.item->order < i2.item->order; });
|
||
|
||
for (auto& item : itemStates)
|
||
{
|
||
for (auto& axis : { Axis::main, Axis::cross })
|
||
getPreferredSize (axis, item) = computePreferredSize (axis, item);
|
||
}
|
||
}
|
||
|
||
void initialiseItems() noexcept
|
||
{
|
||
if (isSingleLine()) // for single-line, all items go in line 1
|
||
{
|
||
lineInfo[0].numItems = numItems;
|
||
int i = 0;
|
||
|
||
for (auto& item : itemStates)
|
||
{
|
||
item.resetItemLockedSize();
|
||
lineItems[i++] = &item;
|
||
}
|
||
}
|
||
else // if multi-line, group the flexbox items into multiple lines
|
||
{
|
||
auto currentLength = containerLineLength;
|
||
int column = 0, row = 0;
|
||
bool firstRow = true;
|
||
|
||
for (auto& item : itemStates)
|
||
{
|
||
item.resetItemLockedSize();
|
||
|
||
const auto flexitemLength = getItemMainSize (item);
|
||
|
||
if (flexitemLength > currentLength)
|
||
{
|
||
if (! firstRow)
|
||
row++;
|
||
|
||
if (row >= numItems)
|
||
break;
|
||
|
||
column = 0;
|
||
currentLength = containerLineLength;
|
||
numberOfRows = jmax (numberOfRows, row + 1);
|
||
}
|
||
|
||
currentLength -= flexitemLength;
|
||
lineItems[row * numItems + column] = &item;
|
||
++column;
|
||
lineInfo[row].numItems = jmax (lineInfo[row].numItems, column);
|
||
firstRow = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
void resolveFlexibleLengths() noexcept
|
||
{
|
||
for (int row = 0; row < numberOfRows; ++row)
|
||
{
|
||
resetRowItems (row);
|
||
|
||
for (int maxLoops = numItems; --maxLoops >= 0;)
|
||
{
|
||
resetUnlockedRowItems (row);
|
||
|
||
if (layoutRowItems (row))
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
void resolveAutoMarginsOnMainAxis() noexcept
|
||
{
|
||
for (int row = 0; row < numberOfRows; ++row)
|
||
{
|
||
Coord allFlexGrow = 0;
|
||
const auto numColumns = lineInfo[row].numItems;
|
||
const auto remainingLength = containerLineLength - lineInfo[row].totalLength;
|
||
|
||
for (int column = 0; column < numColumns; ++column)
|
||
{
|
||
auto& item = getItem (column, row);
|
||
|
||
if (isAuto (getStartMargin (Axis::main, item))) ++allFlexGrow;
|
||
if (isAuto (getEndMargin (Axis::main, item))) ++allFlexGrow;
|
||
}
|
||
|
||
const auto changeUnitWidth = remainingLength / allFlexGrow;
|
||
|
||
if (changeUnitWidth > 0)
|
||
{
|
||
for (int column = 0; column < numColumns; ++column)
|
||
{
|
||
auto& item = getItem (column, row);
|
||
|
||
if (isAuto (getStartMargin (Axis::main, item)))
|
||
getStartLockedMargin (Axis::main, item) = changeUnitWidth;
|
||
|
||
if (isAuto (getEndMargin (Axis::main, item)))
|
||
getEndLockedMargin (Axis::main, item) = changeUnitWidth;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void calculateCrossSizesByLine() noexcept
|
||
{
|
||
// 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 (isSingleLine())
|
||
{
|
||
lineInfo[0].crossSize = getContainerSize (Axis::cross);
|
||
}
|
||
else
|
||
{
|
||
for (int row = 0; row < numberOfRows; ++row)
|
||
{
|
||
Coord maxSize = 0;
|
||
const auto numColumns = lineInfo[row].numItems;
|
||
|
||
for (int column = 0; column < numColumns; ++column)
|
||
maxSize = jmax (maxSize, getItemCrossSize (getItem (column, row)));
|
||
|
||
lineInfo[row].crossSize = maxSize;
|
||
}
|
||
}
|
||
}
|
||
|
||
void calculateCrossSizeOfAllItems() noexcept
|
||
{
|
||
for (int row = 0; row < numberOfRows; ++row)
|
||
{
|
||
const auto numColumns = lineInfo[row].numItems;
|
||
|
||
for (int column = 0; column < numColumns; ++column)
|
||
{
|
||
auto& item = getItem (column, row);
|
||
|
||
if (isAssigned (item.item->maxHeight) && item.lockedHeight > item.item->maxHeight)
|
||
item.lockedHeight = item.item->maxHeight;
|
||
|
||
if (isAssigned (item.item->maxWidth) && item.lockedWidth > item.item->maxWidth)
|
||
item.lockedWidth = item.item->maxWidth;
|
||
}
|
||
}
|
||
}
|
||
|
||
void alignLinesPerAlignContent() noexcept
|
||
{
|
||
containerCrossLength = getContainerSize (Axis::cross);
|
||
|
||
if (owner.alignContent == FlexBox::AlignContent::flexStart)
|
||
{
|
||
for (int row = 0; row < numberOfRows; ++row)
|
||
for (int row2 = row; row2 < numberOfRows; ++row2)
|
||
lineInfo[row].lineY = row == 0 ? 0 : lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
|
||
}
|
||
else if (owner.alignContent == FlexBox::AlignContent::flexEnd)
|
||
{
|
||
for (int row = 0; row < numberOfRows; ++row)
|
||
{
|
||
Coord crossHeights = 0;
|
||
|
||
for (int row2 = row; row2 < numberOfRows; ++row2)
|
||
crossHeights += lineInfo[row2].crossSize;
|
||
|
||
lineInfo[row].lineY = containerCrossLength - crossHeights;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Coord totalHeight = 0;
|
||
|
||
for (int row = 0; row < numberOfRows; ++row)
|
||
totalHeight += lineInfo[row].crossSize;
|
||
|
||
if (owner.alignContent == FlexBox::AlignContent::stretch)
|
||
{
|
||
const auto difference = jmax (Coord(), (containerCrossLength - totalHeight) / numberOfRows);
|
||
|
||
for (int row = 0; row < numberOfRows; ++row)
|
||
{
|
||
lineInfo[row].crossSize += difference;
|
||
lineInfo[row].lineY = row == 0 ? 0 : lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
|
||
}
|
||
}
|
||
else if (owner.alignContent == FlexBox::AlignContent::center)
|
||
{
|
||
const auto additionalength = (containerCrossLength - totalHeight) / 2;
|
||
|
||
for (int row = 0; row < numberOfRows; ++row)
|
||
lineInfo[row].lineY = row == 0 ? additionalength : lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
|
||
}
|
||
else if (owner.alignContent == FlexBox::AlignContent::spaceBetween)
|
||
{
|
||
const auto additionalength = numberOfRows <= 1 ? Coord() : jmax (Coord(), (containerCrossLength - totalHeight)
|
||
/ static_cast<Coord> (numberOfRows - 1));
|
||
lineInfo[0].lineY = 0;
|
||
|
||
for (int row = 1; row < numberOfRows; ++row)
|
||
lineInfo[row].lineY += additionalength + lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
|
||
}
|
||
else if (owner.alignContent == FlexBox::AlignContent::spaceAround)
|
||
{
|
||
const auto additionalength = numberOfRows <= 1 ? Coord() : jmax (Coord(), (containerCrossLength - totalHeight)
|
||
/ static_cast<Coord> (2 + (2 * (numberOfRows - 1))));
|
||
|
||
lineInfo[0].lineY = additionalength;
|
||
|
||
for (int row = 1; row < numberOfRows; ++row)
|
||
lineInfo[row].lineY += (2 * additionalength) + lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
|
||
}
|
||
}
|
||
}
|
||
|
||
void resolveAutoMarginsOnCrossAxis() noexcept
|
||
{
|
||
for (int row = 0; row < numberOfRows; ++row)
|
||
{
|
||
const auto numColumns = lineInfo[row].numItems;
|
||
const auto crossSizeForLine = lineInfo[row].crossSize;
|
||
|
||
for (int column = 0; column < numColumns; ++column)
|
||
{
|
||
auto& item = getItem (column, row);
|
||
|
||
getStartLockedMargin (Axis::cross, item) = [&]
|
||
{
|
||
if (isAuto (getStartMargin (Axis::cross, item)) && isAuto (getEndMargin (Axis::cross, item)))
|
||
return (crossSizeForLine - getLockedSize (Axis::cross, item)) / 2;
|
||
|
||
if (isAuto (getStartMargin (Axis::cross, item)))
|
||
return crossSizeForLine - getLockedSize (Axis::cross, item) - getEndMargin (Axis::cross, item);
|
||
|
||
return getStartLockedMargin (Axis::cross, item);
|
||
}();
|
||
}
|
||
}
|
||
}
|
||
|
||
// Align all flex items along the cross-axis per align-self, if neither of the item’s cross-axis margins are auto.
|
||
void alignItemsInCrossAxisInLinesPerAlignSelf() noexcept
|
||
{
|
||
for (int row = 0; row < numberOfRows; ++row)
|
||
{
|
||
const auto numColumns = lineInfo[row].numItems;
|
||
const auto lineSize = lineInfo[row].crossSize;
|
||
|
||
for (int column = 0; column < numColumns; ++column)
|
||
{
|
||
auto& item = getItem (column, row);
|
||
|
||
if (isAuto (getStartMargin (Axis::cross, item)) || isAuto (getEndMargin (Axis::cross, item)))
|
||
continue;
|
||
|
||
const auto alignment = [&]
|
||
{
|
||
switch (item.item->alignSelf)
|
||
{
|
||
case FlexItem::AlignSelf::stretch: return FlexBox::AlignItems::stretch;
|
||
case FlexItem::AlignSelf::flexStart: return FlexBox::AlignItems::flexStart;
|
||
case FlexItem::AlignSelf::flexEnd: return FlexBox::AlignItems::flexEnd;
|
||
case FlexItem::AlignSelf::center: return FlexBox::AlignItems::center;
|
||
case FlexItem::AlignSelf::autoAlign: break;
|
||
}
|
||
|
||
return owner.alignItems;
|
||
}();
|
||
|
||
getStartLockedMargin (Axis::cross, item) = [&]
|
||
{
|
||
switch (alignment)
|
||
{
|
||
// 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);
|
||
}
|
||
|
||
jassertfalse;
|
||
return 0.0;
|
||
}();
|
||
|
||
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);
|
||
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void alignItemsByJustifyContent() noexcept
|
||
{
|
||
Coord additionalMarginRight = 0, additionalMarginLeft = 0;
|
||
|
||
recalculateTotalItemLengthPerLineArray();
|
||
|
||
for (int row = 0; row < numberOfRows; ++row)
|
||
{
|
||
const auto numColumns = lineInfo[row].numItems;
|
||
Coord x = 0;
|
||
|
||
if (owner.justifyContent == FlexBox::JustifyContent::flexEnd)
|
||
{
|
||
x = containerLineLength - lineInfo[row].totalLength;
|
||
}
|
||
else if (owner.justifyContent == FlexBox::JustifyContent::center)
|
||
{
|
||
x = (containerLineLength - lineInfo[row].totalLength) / 2;
|
||
}
|
||
else if (owner.justifyContent == FlexBox::JustifyContent::spaceBetween)
|
||
{
|
||
additionalMarginRight
|
||
= jmax (Coord(), (containerLineLength - lineInfo[row].totalLength) / jmax (1, numColumns - 1));
|
||
}
|
||
else if (owner.justifyContent == FlexBox::JustifyContent::spaceAround)
|
||
{
|
||
additionalMarginLeft = additionalMarginRight
|
||
= jmax (Coord(), (containerLineLength - lineInfo[row].totalLength) / jmax (1, 2 * numColumns));
|
||
}
|
||
|
||
for (int column = 0; column < numColumns; ++column)
|
||
{
|
||
auto& item = getItem (column, row);
|
||
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
|
||
void layoutAllItems() noexcept
|
||
{
|
||
for (int row = 0; row < numberOfRows; ++row)
|
||
{
|
||
const auto lineY = lineInfo[row].lineY;
|
||
const auto numColumns = lineInfo[row].numItems;
|
||
|
||
for (int column = 0; column < numColumns; ++column)
|
||
{
|
||
auto& item = getItem (column, row);
|
||
|
||
if (isRowDirection)
|
||
item.item->currentBounds.setY ((float) (lineY + item.lockedMarginTop));
|
||
else
|
||
item.item->currentBounds.setX ((float) (lineY + item.lockedMarginLeft));
|
||
|
||
item.item->currentBounds.setSize ((float) item.lockedWidth,
|
||
(float) item.lockedHeight);
|
||
}
|
||
}
|
||
|
||
reverseLocations();
|
||
reverseWrap();
|
||
}
|
||
|
||
private:
|
||
void resetRowItems (const int row) noexcept
|
||
{
|
||
const auto numColumns = lineInfo[row].numItems;
|
||
|
||
for (int column = 0; column < numColumns; ++column)
|
||
resetItem (getItem (column, row));
|
||
}
|
||
|
||
void resetUnlockedRowItems (const int row) noexcept
|
||
{
|
||
const auto numColumns = lineInfo[row].numItems;
|
||
|
||
for (int column = 0; column < numColumns; ++column)
|
||
{
|
||
auto& item = getItem (column, row);
|
||
|
||
if (! item.locked)
|
||
resetItem (item);
|
||
}
|
||
}
|
||
|
||
void resetItem (ItemWithState& item) noexcept
|
||
{
|
||
item.locked = false;
|
||
|
||
for (auto& axis : { Axis::main, Axis::cross })
|
||
getLockedSize (axis, item) = computePreferredSize (axis, item);
|
||
}
|
||
|
||
bool layoutRowItems (const int row) noexcept
|
||
{
|
||
const auto numColumns = lineInfo[row].numItems;
|
||
auto flexContainerLength = containerLineLength;
|
||
Coord totalItemsLength = 0, totalFlexGrow = 0, totalFlexShrink = 0;
|
||
|
||
for (int column = 0; column < numColumns; ++column)
|
||
{
|
||
const auto& item = getItem (column, row);
|
||
|
||
if (item.locked)
|
||
{
|
||
flexContainerLength -= getItemMainSize (item);
|
||
}
|
||
else
|
||
{
|
||
totalItemsLength += getItemMainSize (item);
|
||
totalFlexGrow += item.item->flexGrow;
|
||
totalFlexShrink += item.item->flexShrink;
|
||
}
|
||
}
|
||
|
||
Coord changeUnit = 0;
|
||
const auto difference = flexContainerLength - totalItemsLength;
|
||
const bool positiveFlexibility = difference > 0;
|
||
|
||
if (positiveFlexibility)
|
||
{
|
||
if (totalFlexGrow != 0.0)
|
||
changeUnit = difference / totalFlexGrow;
|
||
}
|
||
else
|
||
{
|
||
if (totalFlexShrink != 0.0)
|
||
changeUnit = difference / totalFlexShrink;
|
||
}
|
||
|
||
bool ok = true;
|
||
|
||
for (int column = 0; column < numColumns; ++column)
|
||
{
|
||
auto& item = getItem (column, row);
|
||
|
||
if (! item.locked)
|
||
if (! addToItemLength (item, (positiveFlexibility ? item.item->flexGrow
|
||
: item.item->flexShrink) * changeUnit, row))
|
||
ok = false;
|
||
}
|
||
|
||
return ok;
|
||
}
|
||
|
||
void recalculateTotalItemLengthPerLineArray() noexcept
|
||
{
|
||
for (int row = 0; row < numberOfRows; ++row)
|
||
{
|
||
lineInfo[row].totalLength = 0;
|
||
const auto numColumns = lineInfo[row].numItems;
|
||
|
||
for (int column = 0; column < numColumns; ++column)
|
||
lineInfo[row].totalLength += getItemMainSize (getItem (column, row));
|
||
}
|
||
}
|
||
|
||
void reverseLocations() noexcept
|
||
{
|
||
if (owner.flexDirection == FlexBox::Direction::rowReverse)
|
||
{
|
||
for (auto& item : owner.items)
|
||
item.currentBounds.setX ((float) (containerLineLength - item.currentBounds.getRight()));
|
||
}
|
||
else if (owner.flexDirection == FlexBox::Direction::columnReverse)
|
||
{
|
||
for (auto& item : owner.items)
|
||
item.currentBounds.setY ((float) (containerLineLength - item.currentBounds.getBottom()));
|
||
}
|
||
}
|
||
|
||
void reverseWrap() noexcept
|
||
{
|
||
if (owner.flexWrap == FlexBox::Wrap::wrapReverse)
|
||
{
|
||
if (isRowDirection)
|
||
{
|
||
for (auto& item : owner.items)
|
||
item.currentBounds.setY ((float) (containerCrossLength - item.currentBounds.getBottom()));
|
||
}
|
||
else
|
||
{
|
||
for (auto& item : owner.items)
|
||
item.currentBounds.setX ((float) (containerCrossLength - item.currentBounds.getRight()));
|
||
}
|
||
}
|
||
}
|
||
|
||
Coord getItemMainSize (const ItemWithState& item) const noexcept
|
||
{
|
||
return isRowDirection ? item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight
|
||
: item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom;
|
||
}
|
||
|
||
Coord getItemCrossSize (const ItemWithState& item) const noexcept
|
||
{
|
||
return isRowDirection ? item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom
|
||
: item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight;
|
||
}
|
||
|
||
bool addToItemLength (ItemWithState& item, const Coord length, int row) const noexcept
|
||
{
|
||
bool ok = false;
|
||
|
||
const auto prefSize = computePreferredSize (Axis::main, item);
|
||
|
||
const auto pickForMainAxis = [this] (auto& a, auto& b) -> auto& { return pickForAxis (Axis::main, a, b); };
|
||
|
||
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
|
||
{
|
||
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 computePreferredSize (Axis axis, ItemWithState& itemWithState) const noexcept
|
||
{
|
||
const auto& item = *itemWithState.item;
|
||
|
||
auto preferredSize = (item.flexBasis > 0 && axis == Axis::main) ? item.flexBasis
|
||
: (isAssigned (getItemSize (axis, itemWithState)) ? getItemSize (axis, itemWithState)
|
||
: getMinSize (axis, itemWithState));
|
||
|
||
const auto minSize = getMinSize (axis, itemWithState);
|
||
|
||
if (isAssigned (minSize) && preferredSize < minSize)
|
||
return minSize;
|
||
|
||
const auto maxSize = getMaxSize (axis, itemWithState);
|
||
|
||
if (isAssigned (maxSize) && maxSize < preferredSize)
|
||
return maxSize;
|
||
|
||
return preferredSize;
|
||
}
|
||
};
|
||
|
||
//==============================================================================
|
||
FlexBox::FlexBox (JustifyContent jc) noexcept : justifyContent (jc) {}
|
||
|
||
FlexBox::FlexBox (Direction d, Wrap w, AlignContent ac, AlignItems ai, JustifyContent jc) noexcept
|
||
: flexDirection (d), flexWrap (w), alignContent (ac), alignItems (ai), justifyContent (jc)
|
||
{
|
||
}
|
||
|
||
void FlexBox::performLayout (Rectangle<float> targetArea)
|
||
{
|
||
if (! items.isEmpty())
|
||
{
|
||
FlexBoxLayoutCalculation layout (*this, targetArea.getWidth(), targetArea.getHeight());
|
||
|
||
layout.createStates();
|
||
layout.initialiseItems();
|
||
layout.resolveFlexibleLengths();
|
||
layout.resolveAutoMarginsOnMainAxis();
|
||
layout.calculateCrossSizesByLine();
|
||
layout.calculateCrossSizeOfAllItems();
|
||
layout.alignLinesPerAlignContent();
|
||
layout.resolveAutoMarginsOnCrossAxis();
|
||
layout.alignItemsInCrossAxisInLinesPerAlignSelf();
|
||
layout.alignItemsByJustifyContent();
|
||
layout.layoutAllItems();
|
||
|
||
for (auto& item : items)
|
||
{
|
||
item.currentBounds += targetArea.getPosition();
|
||
|
||
if (auto* comp = item.associatedComponent)
|
||
comp->setBounds (Rectangle<int>::leftTopRightBottom ((int) item.currentBounds.getX(),
|
||
(int) item.currentBounds.getY(),
|
||
(int) item.currentBounds.getRight(),
|
||
(int) item.currentBounds.getBottom()));
|
||
|
||
if (auto* box = item.associatedFlexBox)
|
||
box->performLayout (item.currentBounds);
|
||
}
|
||
}
|
||
}
|
||
|
||
void FlexBox::performLayout (Rectangle<int> targetArea)
|
||
{
|
||
performLayout (targetArea.toFloat());
|
||
}
|
||
|
||
//==============================================================================
|
||
FlexItem::FlexItem() noexcept {}
|
||
FlexItem::FlexItem (float w, float h) noexcept : currentBounds (w, h), minWidth (w), minHeight (h) {}
|
||
FlexItem::FlexItem (float w, float h, Component& c) noexcept : FlexItem (w, h) { associatedComponent = &c; }
|
||
FlexItem::FlexItem (float w, float h, FlexBox& fb) noexcept : FlexItem (w, h) { associatedFlexBox = &fb; }
|
||
FlexItem::FlexItem (Component& c) noexcept : associatedComponent (&c) {}
|
||
FlexItem::FlexItem (FlexBox& fb) noexcept : associatedFlexBox (&fb) {}
|
||
|
||
FlexItem::Margin::Margin() noexcept : left(), right(), top(), bottom() {}
|
||
FlexItem::Margin::Margin (float v) noexcept : left (v), right (v), top (v), bottom (v) {}
|
||
FlexItem::Margin::Margin (float t, float r, float b, float l) noexcept : left (l), right (r), top (t), bottom (b) {}
|
||
|
||
//==============================================================================
|
||
FlexItem FlexItem::withFlex (float newFlexGrow) const noexcept
|
||
{
|
||
auto fi = *this;
|
||
fi.flexGrow = newFlexGrow;
|
||
return fi;
|
||
}
|
||
|
||
FlexItem FlexItem::withFlex (float newFlexGrow, float newFlexShrink) const noexcept
|
||
{
|
||
auto fi = withFlex (newFlexGrow);
|
||
fi.flexShrink = newFlexShrink;
|
||
return fi;
|
||
}
|
||
|
||
FlexItem FlexItem::withFlex (float newFlexGrow, float newFlexShrink, float newFlexBasis) const noexcept
|
||
{
|
||
auto fi = withFlex (newFlexGrow, newFlexShrink);
|
||
fi.flexBasis = newFlexBasis;
|
||
return fi;
|
||
}
|
||
|
||
FlexItem FlexItem::withWidth (float newWidth) const noexcept { auto fi = *this; fi.width = newWidth; return fi; }
|
||
FlexItem FlexItem::withMinWidth (float newMinWidth) const noexcept { auto fi = *this; fi.minWidth = newMinWidth; return fi; }
|
||
FlexItem FlexItem::withMaxWidth (float newMaxWidth) const noexcept { auto fi = *this; fi.maxWidth = newMaxWidth; return fi; }
|
||
|
||
FlexItem FlexItem::withMinHeight (float newMinHeight) const noexcept { auto fi = *this; fi.minHeight = newMinHeight; return fi; }
|
||
FlexItem FlexItem::withMaxHeight (float newMaxHeight) const noexcept { auto fi = *this; fi.maxHeight = newMaxHeight; return fi; }
|
||
FlexItem FlexItem::withHeight (float newHeight) const noexcept { auto fi = *this; fi.height = newHeight; return fi; }
|
||
|
||
FlexItem FlexItem::withMargin (Margin m) const noexcept { auto fi = *this; fi.margin = m; return fi; }
|
||
FlexItem FlexItem::withOrder (int newOrder) const noexcept { auto fi = *this; fi.order = newOrder; return fi; }
|
||
FlexItem FlexItem::withAlignSelf (AlignSelf a) const noexcept { auto fi = *this; fi.alignSelf = a; return fi; }
|
||
|
||
//==============================================================================
|
||
//==============================================================================
|
||
#if JUCE_UNIT_TESTS
|
||
|
||
class FlexBoxTests : public UnitTest
|
||
{
|
||
public:
|
||
FlexBoxTests() : UnitTest ("FlexBox", UnitTestCategories::gui) {}
|
||
|
||
void runTest() override
|
||
{
|
||
using AlignSelf = FlexItem::AlignSelf;
|
||
using Direction = FlexBox::Direction;
|
||
|
||
const Rectangle<float> rect (10.0f, 20.0f, 300.0f, 200.0f);
|
||
const auto doLayout = [&rect] (Direction direction, Array<FlexItem> items)
|
||
{
|
||
juce::FlexBox flex;
|
||
flex.flexDirection = direction;
|
||
flex.items = std::move (items);
|
||
flex.performLayout (rect);
|
||
return flex;
|
||
};
|
||
|
||
beginTest ("flex item with mostly auto properties");
|
||
{
|
||
const auto test = [this, &doLayout] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
|
||
{
|
||
const auto flex = doLayout (direction, { juce::FlexItem{}.withAlignSelf (alignment) });
|
||
expect (flex.items.getFirst().currentBounds == expectedBounds);
|
||
};
|
||
|
||
test (Direction::row, AlignSelf::autoAlign, { rect.getX(), rect.getY(), 0.0f, rect.getHeight() });
|
||
test (Direction::row, AlignSelf::stretch, { rect.getX(), rect.getY(), 0.0f, rect.getHeight() });
|
||
test (Direction::row, AlignSelf::flexStart, { rect.getX(), rect.getY(), 0.0f, 0.0f });
|
||
test (Direction::row, AlignSelf::flexEnd, { rect.getX(), rect.getBottom(), 0.0f, 0.0f });
|
||
test (Direction::row, AlignSelf::center, { rect.getX(), rect.getCentreY(), 0.0f, 0.0f });
|
||
|
||
test (Direction::column, AlignSelf::autoAlign, { rect.getX(), rect.getY(), rect.getWidth(), 0.0f });
|
||
test (Direction::column, AlignSelf::stretch, { rect.getX(), rect.getY(), rect.getWidth(), 0.0f });
|
||
test (Direction::column, AlignSelf::flexStart, { rect.getX(), rect.getY(), 0.0f, 0.0f });
|
||
test (Direction::column, AlignSelf::flexEnd, { rect.getRight(), rect.getY(), 0.0f, 0.0f });
|
||
test (Direction::column, AlignSelf::center, { rect.getCentreX(), rect.getY(), 0.0f, 0.0f });
|
||
}
|
||
|
||
beginTest ("flex item with specified width and height");
|
||
{
|
||
constexpr auto w = 50.0f;
|
||
constexpr auto h = 60.0f;
|
||
const auto test = [&] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
|
||
{
|
||
const auto flex = doLayout (direction, { juce::FlexItem().withAlignSelf (alignment)
|
||
.withWidth (w)
|
||
.withHeight (h) });
|
||
expect (flex.items.getFirst().currentBounds == expectedBounds);
|
||
};
|
||
|
||
test (Direction::row, AlignSelf::autoAlign, { rect.getX(), rect.getY(), w, h });
|
||
test (Direction::row, AlignSelf::stretch, { rect.getX(), rect.getY(), w, h });
|
||
test (Direction::row, AlignSelf::flexStart, { rect.getX(), rect.getY(), w, h });
|
||
test (Direction::row, AlignSelf::flexEnd, { rect.getX(), rect.getBottom() - h, w, h });
|
||
test (Direction::row, AlignSelf::center, { rect.getX(), rect.getY() + (rect.getHeight() - h) * 0.5f, w, h });
|
||
|
||
test (Direction::column, AlignSelf::autoAlign, { rect.getX(), rect.getY(), w, h });
|
||
test (Direction::column, AlignSelf::stretch, { rect.getX(), rect.getY(), w, h });
|
||
test (Direction::column, AlignSelf::flexStart, { rect.getX(), rect.getY(), w, h });
|
||
test (Direction::column, AlignSelf::flexEnd, { rect.getRight() - w, rect.getY(), w, h });
|
||
test (Direction::column, AlignSelf::center, { rect.getX() + (rect.getWidth() - w) * 0.5f, rect.getY(), w, h });
|
||
}
|
||
|
||
beginTest ("flex item with oversized width and height");
|
||
{
|
||
const auto w = rect.getWidth() * 2;
|
||
const auto h = rect.getHeight() * 2;
|
||
const auto test = [this, &doLayout, &w, &h] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
|
||
{
|
||
const auto flex = doLayout (direction, { juce::FlexItem().withAlignSelf (alignment)
|
||
.withWidth (w)
|
||
.withHeight (h) });
|
||
expect (flex.items.getFirst().currentBounds == expectedBounds);
|
||
};
|
||
|
||
const Rectangle<float> baseRow (rect.getX(), rect.getY(), rect.getWidth(), h);
|
||
test (Direction::row, AlignSelf::autoAlign, baseRow);
|
||
test (Direction::row, AlignSelf::stretch, baseRow);
|
||
test (Direction::row, AlignSelf::flexStart, baseRow);
|
||
test (Direction::row, AlignSelf::flexEnd, baseRow.withBottomY (rect.getBottom()));
|
||
test (Direction::row, AlignSelf::center, baseRow.withCentre (rect.getCentre()));
|
||
|
||
const Rectangle<float> baseColumn (rect.getX(), rect.getY(), w, rect.getHeight());
|
||
test (Direction::column, AlignSelf::autoAlign, baseColumn);
|
||
test (Direction::column, AlignSelf::stretch, baseColumn);
|
||
test (Direction::column, AlignSelf::flexStart, baseColumn);
|
||
test (Direction::column, AlignSelf::flexEnd, baseColumn.withRightX (rect.getRight()));
|
||
test (Direction::column, AlignSelf::center, baseColumn.withCentre (rect.getCentre()));
|
||
}
|
||
|
||
beginTest ("flex item with minimum width and height");
|
||
{
|
||
constexpr auto w = 50.0f;
|
||
constexpr auto h = 60.0f;
|
||
const auto test = [&] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
|
||
{
|
||
const auto flex = doLayout (direction, { juce::FlexItem().withAlignSelf (alignment)
|
||
.withMinWidth (w)
|
||
.withMinHeight (h) });
|
||
expect (flex.items.getFirst().currentBounds == expectedBounds);
|
||
};
|
||
|
||
test (Direction::row, AlignSelf::autoAlign, { rect.getX(), rect.getY(), w, rect.getHeight() });
|
||
test (Direction::row, AlignSelf::stretch, { rect.getX(), rect.getY(), w, rect.getHeight() });
|
||
test (Direction::row, AlignSelf::flexStart, { rect.getX(), rect.getY(), w, h });
|
||
test (Direction::row, AlignSelf::flexEnd, { rect.getX(), rect.getBottom() - h, w, h });
|
||
test (Direction::row, AlignSelf::center, { rect.getX(), rect.getY() + (rect.getHeight() - h) * 0.5f, w, h });
|
||
|
||
test (Direction::column, AlignSelf::autoAlign, { rect.getX(), rect.getY(), rect.getWidth(), h });
|
||
test (Direction::column, AlignSelf::stretch, { rect.getX(), rect.getY(), rect.getWidth(), h });
|
||
test (Direction::column, AlignSelf::flexStart, { rect.getX(), rect.getY(), w, h });
|
||
test (Direction::column, AlignSelf::flexEnd, { rect.getRight() - w, rect.getY(), w, h });
|
||
test (Direction::column, AlignSelf::center, { rect.getX() + (rect.getWidth() - w) * 0.5f, rect.getY(), w, h });
|
||
}
|
||
|
||
beginTest ("flex item with maximum width and height");
|
||
{
|
||
constexpr auto w = 50.0f;
|
||
constexpr auto h = 60.0f;
|
||
const auto test = [&] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
|
||
{
|
||
const auto flex = doLayout (direction, { juce::FlexItem().withAlignSelf (alignment)
|
||
.withMaxWidth (w)
|
||
.withMaxHeight (h) });
|
||
expect (flex.items.getFirst().currentBounds == expectedBounds);
|
||
};
|
||
|
||
test (Direction::row, AlignSelf::autoAlign, { rect.getX(), rect.getY(), 0.0f, h });
|
||
test (Direction::row, AlignSelf::stretch, { rect.getX(), rect.getY(), 0.0f, h });
|
||
test (Direction::row, AlignSelf::flexStart, { rect.getX(), rect.getY(), 0.0f, 0.0f });
|
||
test (Direction::row, AlignSelf::flexEnd, { rect.getX(), rect.getBottom(), 0.0f, 0.0f });
|
||
test (Direction::row, AlignSelf::center, { rect.getX(), rect.getCentreY(), 0.0f, 0.0f });
|
||
|
||
test (Direction::column, AlignSelf::autoAlign, { rect.getX(), rect.getY(), w, 0.0f });
|
||
test (Direction::column, AlignSelf::stretch, { rect.getX(), rect.getY(), w, 0.0f });
|
||
test (Direction::column, AlignSelf::flexStart, { rect.getX(), rect.getY(), 0.0f, 0.0f });
|
||
test (Direction::column, AlignSelf::flexEnd, { rect.getRight(), rect.getY(), 0.0f, 0.0f });
|
||
test (Direction::column, AlignSelf::center, { rect.getCentreX(), rect.getY(), 0.0f, 0.0f });
|
||
}
|
||
|
||
beginTest ("flex item with specified flex");
|
||
{
|
||
const auto test = [this, &doLayout] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
|
||
{
|
||
const auto flex = doLayout (direction, { juce::FlexItem().withAlignSelf (alignment).withFlex (1.0f) });
|
||
expect (flex.items.getFirst().currentBounds == expectedBounds);
|
||
};
|
||
|
||
test (Direction::row, AlignSelf::autoAlign, { rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight() });
|
||
test (Direction::row, AlignSelf::stretch, { rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight() });
|
||
test (Direction::row, AlignSelf::flexStart, { rect.getX(), rect.getY(), rect.getWidth(), 0.0f });
|
||
test (Direction::row, AlignSelf::flexEnd, { rect.getX(), rect.getBottom(), rect.getWidth(), 0.0f });
|
||
test (Direction::row, AlignSelf::center, { rect.getX(), rect.getCentreY(), rect.getWidth(), 0.0f });
|
||
|
||
test (Direction::column, AlignSelf::autoAlign, { rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight() });
|
||
test (Direction::column, AlignSelf::stretch, { rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight() });
|
||
test (Direction::column, AlignSelf::flexStart, { rect.getX(), rect.getY(), 0.0f, rect.getHeight() });
|
||
test (Direction::column, AlignSelf::flexEnd, { rect.getRight(), rect.getY(), 0.0f, rect.getHeight() });
|
||
test (Direction::column, AlignSelf::center, { rect.getCentreX(), rect.getY(), 0.0f, rect.getHeight() });
|
||
}
|
||
|
||
beginTest ("flex item with margin");
|
||
{
|
||
const FlexItem::Margin margin (10.0f, 20.0f, 30.0f, 40.0f);
|
||
|
||
const auto test = [this, &doLayout, &margin] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
|
||
{
|
||
const auto flex = doLayout (direction, { juce::FlexItem().withAlignSelf (alignment).withMargin (margin) });
|
||
expect (flex.items.getFirst().currentBounds == expectedBounds);
|
||
};
|
||
|
||
const auto remainingHeight = rect.getHeight() - margin.top - margin.bottom;
|
||
const auto remainingWidth = rect.getWidth() - margin.left - margin.right;
|
||
|
||
test (Direction::row, AlignSelf::autoAlign, { rect.getX() + margin.left, rect.getY() + margin.top, 0.0f, remainingHeight });
|
||
test (Direction::row, AlignSelf::stretch, { rect.getX() + margin.left, rect.getY() + margin.top, 0.0f, remainingHeight });
|
||
test (Direction::row, AlignSelf::flexStart, { rect.getX() + margin.left, rect.getY() + margin.top, 0.0f, 0.0f });
|
||
test (Direction::row, AlignSelf::flexEnd, { rect.getX() + margin.left, rect.getBottom() - margin.bottom, 0.0f, 0.0f });
|
||
test (Direction::row, AlignSelf::center, { rect.getX() + margin.left, rect.getY() + margin.top + remainingHeight * 0.5f, 0.0f, 0.0f });
|
||
|
||
test (Direction::column, AlignSelf::autoAlign, { rect.getX() + margin.left, rect.getY() + margin.top, remainingWidth, 0.0f });
|
||
test (Direction::column, AlignSelf::stretch, { rect.getX() + margin.left, rect.getY() + margin.top, remainingWidth, 0.0f });
|
||
test (Direction::column, AlignSelf::flexStart, { rect.getX() + margin.left, rect.getY() + margin.top, 0.0f, 0.0f });
|
||
test (Direction::column, AlignSelf::flexEnd, { rect.getRight() - margin.right, rect.getY() + margin.top, 0.0f, 0.0f });
|
||
test (Direction::column, AlignSelf::center, { rect.getX() + margin.left + remainingWidth * 0.5f, rect.getY() + margin.top, 0.0f, 0.0f });
|
||
}
|
||
|
||
const AlignSelf alignments[] { AlignSelf::autoAlign,
|
||
AlignSelf::stretch,
|
||
AlignSelf::flexStart,
|
||
AlignSelf::flexEnd,
|
||
AlignSelf::center };
|
||
|
||
beginTest ("flex item with auto margin");
|
||
{
|
||
for (const auto& alignment : alignments)
|
||
{
|
||
for (const auto& direction : { Direction::row, Direction::column })
|
||
{
|
||
const auto flex = doLayout (direction, { juce::FlexItem().withAlignSelf (alignment)
|
||
.withMargin ((float) FlexItem::autoValue) });
|
||
expect (flex.items.getFirst().currentBounds == Rectangle<float> (rect.getCentre(), rect.getCentre()));
|
||
}
|
||
}
|
||
|
||
const auto testTop = [this, &doLayout] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
|
||
{
|
||
const auto flex = doLayout (direction, { juce::FlexItem().withAlignSelf (alignment)
|
||
.withMargin ({ (float) FlexItem::autoValue, 0.0f, 0.0f, 0.0f }) });
|
||
expect (flex.items.getFirst().currentBounds == expectedBounds);
|
||
};
|
||
|
||
for (const auto& alignment : alignments)
|
||
testTop (Direction::row, alignment, { rect.getX(), rect.getBottom(), 0.0f, 0.0f });
|
||
|
||
testTop (Direction::column, AlignSelf::autoAlign, { rect.getX(), rect.getBottom(), rect.getWidth(), 0.0f });
|
||
testTop (Direction::column, AlignSelf::stretch, { rect.getX(), rect.getBottom(), rect.getWidth(), 0.0f });
|
||
testTop (Direction::column, AlignSelf::flexStart, { rect.getX(), rect.getBottom(), 0.0f, 0.0f });
|
||
testTop (Direction::column, AlignSelf::flexEnd, { rect.getRight(), rect.getBottom(), 0.0f, 0.0f });
|
||
testTop (Direction::column, AlignSelf::center, { rect.getCentreX(), rect.getBottom(), 0.0f, 0.0f });
|
||
|
||
const auto testBottom = [this, &doLayout] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
|
||
{
|
||
const auto flex = doLayout (direction, { juce::FlexItem().withAlignSelf (alignment)
|
||
.withMargin ({ 0.0f, 0.0f, (float) FlexItem::autoValue, 0.0f }) });
|
||
expect (flex.items.getFirst().currentBounds == expectedBounds);
|
||
};
|
||
|
||
for (const auto& alignment : alignments)
|
||
testBottom (Direction::row, alignment, { rect.getX(), rect.getY(), 0.0f, 0.0f });
|
||
|
||
testBottom (Direction::column, AlignSelf::autoAlign, { rect.getX(), rect.getY(), rect.getWidth(), 0.0f });
|
||
testBottom (Direction::column, AlignSelf::stretch, { rect.getX(), rect.getY(), rect.getWidth(), 0.0f });
|
||
testBottom (Direction::column, AlignSelf::flexStart, { rect.getX(), rect.getY(), 0.0f, 0.0f });
|
||
testBottom (Direction::column, AlignSelf::flexEnd, { rect.getRight(), rect.getY(), 0.0f, 0.0f });
|
||
testBottom (Direction::column, AlignSelf::center, { rect.getCentreX(), rect.getY(), 0.0f, 0.0f });
|
||
|
||
const auto testLeft = [this, &doLayout] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
|
||
{
|
||
const auto flex = doLayout (direction, { juce::FlexItem().withAlignSelf (alignment)
|
||
.withMargin ({ 0.0f, 0.0f, 0.0f, (float) FlexItem::autoValue }) });
|
||
expect (flex.items.getFirst().currentBounds == expectedBounds);
|
||
};
|
||
|
||
testLeft (Direction::row, AlignSelf::autoAlign, { rect.getRight(), rect.getY(), 0.0f, rect.getHeight() });
|
||
testLeft (Direction::row, AlignSelf::stretch, { rect.getRight(), rect.getY(), 0.0f, rect.getHeight() });
|
||
testLeft (Direction::row, AlignSelf::flexStart, { rect.getRight(), rect.getY(), 0.0f, 0.0f });
|
||
testLeft (Direction::row, AlignSelf::flexEnd, { rect.getRight(), rect.getBottom(), 0.0f, 0.0f });
|
||
testLeft (Direction::row, AlignSelf::center, { rect.getRight(), rect.getCentreY(), 0.0f, 0.0f });
|
||
|
||
for (const auto& alignment : alignments)
|
||
testLeft (Direction::column, alignment, { rect.getRight(), rect.getY(), 0.0f, 0.0f });
|
||
|
||
const auto testRight = [this, &doLayout] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
|
||
{
|
||
const auto flex = doLayout (direction, { juce::FlexItem().withAlignSelf (alignment)
|
||
.withMargin ({ 0.0f, (float) FlexItem::autoValue, 0.0f, 0.0f }) });
|
||
expect (flex.items.getFirst().currentBounds == expectedBounds);
|
||
};
|
||
|
||
testRight (Direction::row, AlignSelf::autoAlign, { rect.getX(), rect.getY(), 0.0f, rect.getHeight() });
|
||
testRight (Direction::row, AlignSelf::stretch, { rect.getX(), rect.getY(), 0.0f, rect.getHeight() });
|
||
testRight (Direction::row, AlignSelf::flexStart, { rect.getX(), rect.getY(), 0.0f, 0.0f });
|
||
testRight (Direction::row, AlignSelf::flexEnd, { rect.getX(), rect.getBottom(), 0.0f, 0.0f });
|
||
testRight (Direction::row, AlignSelf::center, { rect.getX(), rect.getCentreY(), 0.0f, 0.0f });
|
||
|
||
for (const auto& alignment : alignments)
|
||
testRight (Direction::column, alignment, { rect.getX(), rect.getY(), 0.0f, 0.0f });
|
||
}
|
||
|
||
beginTest ("in a multiline layout, items too large to fit on the main axis are given a line to themselves");
|
||
{
|
||
const auto spacer = 10.0f;
|
||
|
||
for (const auto alignment : alignments)
|
||
{
|
||
juce::FlexBox flex;
|
||
flex.flexWrap = FlexBox::Wrap::wrap;
|
||
flex.items = { FlexItem().withAlignSelf (alignment)
|
||
.withWidth (spacer)
|
||
.withHeight (spacer),
|
||
FlexItem().withAlignSelf (alignment)
|
||
.withWidth (rect.getWidth() * 2)
|
||
.withHeight (rect.getHeight()),
|
||
FlexItem().withAlignSelf (alignment)
|
||
.withWidth (spacer)
|
||
.withHeight (spacer) };
|
||
flex.performLayout (rect);
|
||
|
||
expect (flex.items[0].currentBounds == Rectangle<float> (rect.getX(), rect.getY(), spacer, spacer));
|
||
expect (flex.items[1].currentBounds == Rectangle<float> (rect.getX(), rect.getY() + spacer, rect.getWidth(), rect.getHeight()));
|
||
expect (flex.items[2].currentBounds == Rectangle<float> (rect.getX(), rect.getBottom() + spacer, 10.0f, 10.0f));
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
static FlexBoxTests flexBoxTests;
|
||
|
||
#endif
|
||
|
||
} // namespace juce
|