861 lines
34 KiB
C++
861 lines
34 KiB
C++
|
/*
|
||
|
==============================================================================
|
||
|
|
||
|
This file is part of the JUCE library.
|
||
|
Copyright (c) 2017 - ROLI Ltd.
|
||
|
|
||
|
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 5 End-User License
|
||
|
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
||
|
27th April 2017).
|
||
|
|
||
|
End User License Agreement: www.juce.com/juce-5-licence
|
||
|
Privacy Policy: www.juce.com/juce-5-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;
|
||
|
|
||
|
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)
|
||
|
{
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
void setWidthChecked (Coord newWidth) noexcept
|
||
|
{
|
||
|
if (isAssigned (item->maxWidth)) newWidth = jmin (newWidth, static_cast<Coord> (item->maxWidth));
|
||
|
if (isAssigned (item->minWidth)) newWidth = jmax (newWidth, static_cast<Coord> (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
|
||
|
{
|
||
|
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; }
|
||
|
|
||
|
//==============================================================================
|
||
|
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)
|
||
|
{
|
||
|
item.preferredWidth = getPreferredWidth (item);
|
||
|
item.preferredHeight = getPreferredHeight (item);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void initialiseItems() noexcept
|
||
|
{
|
||
|
if (owner.flexWrap == FlexBox::Wrap::noWrap) // 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 = getItemLength (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 (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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
auto changeUnitWidth = remainingLength / allFlexGrow;
|
||
|
|
||
|
if (changeUnitWidth > 0)
|
||
|
{
|
||
|
for (int column = 0; column < numColumns; ++column)
|
||
|
{
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void calculateCrossSizesByLine() noexcept
|
||
|
{
|
||
|
for (int row = 0; row < numberOfRows; ++row)
|
||
|
{
|
||
|
Coord maxSize = 0;
|
||
|
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);
|
||
|
}
|
||
|
|
||
|
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 = isRowDirection ? parentHeight : parentWidth;
|
||
|
|
||
|
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);
|
||
|
|
||
|
if (isRowDirection)
|
||
|
{
|
||
|
if (isAuto (item.item->margin.top) && isAuto (item.item->margin.bottom))
|
||
|
item.lockedMarginTop = (crossSizeForLine - item.lockedHeight) / 2;
|
||
|
else if (isAuto (item.item->margin.top))
|
||
|
item.lockedMarginTop = crossSizeForLine - item.lockedHeight - item.item->margin.bottom;
|
||
|
}
|
||
|
else if (isAuto (item.item->margin.left) && isAuto (item.item->margin.right))
|
||
|
{
|
||
|
item.lockedMarginLeft = jmax (Coord(), (crossSizeForLine - item.lockedWidth) / 2);
|
||
|
}
|
||
|
else if (isAuto (item.item->margin.top))
|
||
|
{
|
||
|
item.lockedMarginLeft = jmax (Coord(), crossSizeForLine - item.lockedHeight - item.item->margin.bottom);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void alignItemsInCrossAxisInLinesPerAlignItems() 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 (item.item->alignSelf == FlexItem::AlignSelf::autoAlign)
|
||
|
{
|
||
|
if (owner.alignItems == FlexBox::AlignItems::stretch)
|
||
|
{
|
||
|
item.lockedMarginTop = item.item->margin.top;
|
||
|
|
||
|
if (isRowDirection)
|
||
|
item.setHeightChecked (lineSize - item.item->margin.top - item.item->margin.bottom);
|
||
|
else
|
||
|
item.setWidthChecked (lineSize - item.item->margin.left - item.item->margin.right);
|
||
|
}
|
||
|
else if (owner.alignItems == FlexBox::AlignItems::flexStart)
|
||
|
{
|
||
|
item.lockedMarginTop = item.item->margin.top;
|
||
|
}
|
||
|
else if (owner.alignItems == FlexBox::AlignItems::flexEnd)
|
||
|
{
|
||
|
if (isRowDirection)
|
||
|
item.lockedMarginTop = lineSize - item.lockedHeight - item.item->margin.bottom;
|
||
|
else
|
||
|
item.lockedMarginLeft = lineSize - item.lockedWidth - item.item->margin.right;
|
||
|
}
|
||
|
else if (owner.alignItems == FlexBox::AlignItems::center)
|
||
|
{
|
||
|
if (isRowDirection)
|
||
|
item.lockedMarginTop = (lineSize - item.lockedHeight - item.item->margin.top - item.item->margin.bottom) / 2;
|
||
|
else
|
||
|
item.lockedMarginLeft = (lineSize - item.lockedWidth - item.item->margin.left - item.item->margin.right) / 2;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void alignLinesPerAlignSelf() 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 (item.item->margin.top))
|
||
|
{
|
||
|
if (item.item->alignSelf == FlexItem::AlignSelf::flexStart)
|
||
|
{
|
||
|
if (isRowDirection)
|
||
|
item.lockedMarginTop = item.item->margin.top;
|
||
|
else
|
||
|
item.lockedMarginLeft = item.item->margin.left;
|
||
|
}
|
||
|
else if (item.item->alignSelf == FlexItem::AlignSelf::flexEnd)
|
||
|
{
|
||
|
if (isRowDirection)
|
||
|
item.lockedMarginTop = lineSize - item.lockedHeight - item.item->margin.bottom;
|
||
|
else
|
||
|
item.lockedMarginLeft = lineSize - item.lockedWidth - item.item->margin.right;
|
||
|
}
|
||
|
else if (item.item->alignSelf == FlexItem::AlignSelf::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;
|
||
|
}
|
||
|
else if (item.item->alignSelf == FlexItem::AlignSelf::stretch)
|
||
|
{
|
||
|
item.lockedMarginTop = item.item->margin.top;
|
||
|
item.lockedMarginLeft = item.item->margin.left;
|
||
|
|
||
|
if (isRowDirection)
|
||
|
item.setHeightChecked (isAssigned (item.item->height) ? getPreferredHeight (item)
|
||
|
: lineSize - item.item->margin.top - item.item->margin.bottom);
|
||
|
else
|
||
|
item.setWidthChecked (isAssigned (item.item->width) ? getPreferredWidth (item)
|
||
|
: lineSize - item.item->margin.left - item.item->margin.right);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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);
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
item.lockedWidth = getPreferredWidth (item);
|
||
|
item.lockedHeight = getPreferredHeight (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 -= getItemLength (item);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
totalItemsLength += getItemLength (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)
|
||
|
{
|
||
|
const auto& item = getItem (column, row);
|
||
|
|
||
|
lineInfo[row].totalLength += isRowDirection ? item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight
|
||
|
: item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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 getItemLength (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;
|
||
|
|
||
|
if (isRowDirection)
|
||
|
{
|
||
|
const auto prefWidth = getPreferredWidth (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;
|
||
|
}
|
||
|
|
||
|
lineInfo[row].totalLength += item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight;
|
||
|
}
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
return ok;
|
||
|
}
|
||
|
|
||
|
Coord getPreferredWidth (const 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;
|
||
|
|
||
|
return preferredWidth;
|
||
|
}
|
||
|
|
||
|
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 (item.minHeight) && preferredHeight < item.minHeight) return item.minHeight;
|
||
|
if (isAssigned (item.maxHeight) && preferredHeight > item.maxHeight) return item.maxHeight;
|
||
|
|
||
|
return preferredHeight;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
//==============================================================================
|
||
|
FlexBox::FlexBox() noexcept {}
|
||
|
FlexBox::~FlexBox() noexcept {}
|
||
|
|
||
|
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.alignItemsInCrossAxisInLinesPerAlignItems();
|
||
|
layout.alignLinesPerAlignSelf();
|
||
|
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; }
|
||
|
|
||
|
} // namespace juce
|