33 enum class Axis { main, cross };
36 : owner (
fb), parentWidth (w), parentHeight (h), numItems (owner.items.size()),
39 containerLineLength (getContainerSize (Axis::main))
41 lineItems.calloc (numItems * numItems);
42 lineInfo.calloc (numItems);
50 Coord lockedWidth = 0, lockedHeight = 0;
51 Coord lockedMarginLeft = 0, lockedMarginRight = 0, lockedMarginTop = 0, lockedMarginBottom = 0;
52 Coord preferredWidth = 0, preferredHeight = 0;
55 void resetItemLockedSize()
noexcept
57 lockedWidth = preferredWidth;
58 lockedHeight = preferredHeight;
59 lockedMarginLeft = getValueOrZeroIfAuto (item->
margin.
left);
60 lockedMarginRight = getValueOrZeroIfAuto (item->
margin.
right);
61 lockedMarginTop = getValueOrZeroIfAuto (item->
margin.
top);
62 lockedMarginBottom = getValueOrZeroIfAuto (item->
margin.
bottom);
69 Coord crossSize, lineY, totalLength;
73 const Coord parentWidth, parentHeight;
75 const bool isRowDirection;
76 const Coord containerLineLength;
79 Coord containerCrossLength = 0;
85 ItemWithState& getItem (
int x,
int y)
const noexcept {
return *lineItems[y * numItems + x]; }
87 static bool isAuto (Coord value)
noexcept
92 static bool isAssigned (Coord value)
noexcept
97 static Coord getValueOrZeroIfAuto (Coord value)
noexcept {
return isAuto (value) ? Coord() : value; }
102 template <
typename Value>
103 Value& pickForAxis (Axis
axis, Value& x, Value& y)
const
105 return (isRowDirection ?
axis == Axis::main :
axis == Axis::cross) ? x : y;
108 auto& getStartMargin (Axis
axis, ItemWithState& item)
const
110 return pickForAxis (
axis, item.item->margin.left, item.item->margin.top);
113 auto& getEndMargin (Axis
axis, ItemWithState& item)
const
115 return pickForAxis (
axis, item.item->margin.right, item.item->margin.bottom);
118 auto& getStartLockedMargin (Axis
axis, ItemWithState& item)
const
120 return pickForAxis (
axis, item.lockedMarginLeft, item.lockedMarginTop);
123 auto& getEndLockedMargin (Axis
axis, ItemWithState& item)
const
125 return pickForAxis (
axis, item.lockedMarginRight, item.lockedMarginBottom);
128 auto& getLockedSize (Axis
axis, ItemWithState& item)
const
130 return pickForAxis (
axis, item.lockedWidth, item.lockedHeight);
133 auto& getPreferredSize (Axis
axis, ItemWithState& item)
const
135 return pickForAxis (
axis, item.preferredWidth, item.preferredHeight);
138 Coord getContainerSize (Axis
axis)
const
140 return pickForAxis (
axis, parentWidth, parentHeight);
143 auto& getItemSize (Axis
axis, ItemWithState& item)
const
145 return pickForAxis (
axis, item.item->width, item.item->height);
148 auto& getMinSize (Axis
axis, ItemWithState& item)
const
150 return pickForAxis (
axis, item.item->minWidth, item.item->minHeight);
153 auto& getMaxSize (Axis
axis, ItemWithState& item)
const
155 return pickForAxis (
axis, item.item->maxWidth, item.item->maxHeight);
163 for (
auto& item : owner.items)
164 itemStates.add (item);
167 [] (
const ItemWithState&
i1,
const ItemWithState&
i2) { return i1.item->order < i2.item->order; });
169 for (
auto& item : itemStates)
171 for (
auto&
axis : { Axis::main, Axis::cross })
172 getPreferredSize (
axis, item) = computePreferredSize (
axis, item);
176 void initialiseItems() noexcept
180 lineInfo[0].numItems = numItems;
183 for (
auto& item : itemStates)
185 item.resetItemLockedSize();
186 lineItems[i++] = &item;
192 int column = 0, row = 0;
195 for (
auto& item : itemStates)
197 item.resetItemLockedSize();
211 numberOfRows =
jmax (numberOfRows, row + 1);
215 lineItems[row * numItems + column] = &item;
217 lineInfo[row].numItems =
jmax (lineInfo[row].numItems, column);
223 void resolveFlexibleLengths() noexcept
225 for (
int row = 0; row < numberOfRows; ++row)
231 resetUnlockedRowItems (row);
233 if (layoutRowItems (row))
239 void resolveAutoMarginsOnMainAxis() noexcept
241 for (
int row = 0; row < numberOfRows; ++row)
244 const auto numColumns = lineInfo[row].numItems;
245 const auto remainingLength = containerLineLength - lineInfo[row].totalLength;
247 for (
int column = 0; column < numColumns; ++column)
249 auto& item = getItem (column, row);
251 if (isAuto (getStartMargin (Axis::main, item))) ++
allFlexGrow;
252 if (isAuto (getEndMargin (Axis::main, item))) ++
allFlexGrow;
259 for (
int column = 0; column < numColumns; ++column)
261 auto& item = getItem (column, row);
263 if (isAuto (getStartMargin (Axis::main, item)))
266 if (isAuto (getEndMargin (Axis::main, item)))
273 void calculateCrossSizesByLine() noexcept
280 lineInfo[0].crossSize = getContainerSize (Axis::cross);
284 for (
int row = 0; row < numberOfRows; ++row)
287 const auto numColumns = lineInfo[row].numItems;
289 for (
int column = 0; column < numColumns; ++column)
290 maxSize =
jmax (maxSize, getItemCrossSize (getItem (column, row)));
292 lineInfo[row].crossSize = maxSize;
297 void calculateCrossSizeOfAllItems() noexcept
299 for (
int row = 0; row < numberOfRows; ++row)
301 const auto numColumns = lineInfo[row].numItems;
303 for (
int column = 0; column < numColumns; ++column)
305 auto& item = getItem (column, row);
307 if (isAssigned (item.item->maxHeight) && item.lockedHeight > item.item->maxHeight)
308 item.lockedHeight = item.item->maxHeight;
310 if (isAssigned (item.item->maxWidth) && item.lockedWidth > item.item->maxWidth)
311 item.lockedWidth = item.item->maxWidth;
316 void alignLinesPerAlignContent() noexcept
318 containerCrossLength = getContainerSize (Axis::cross);
322 for (
int row = 0; row < numberOfRows; ++row)
324 lineInfo[row].lineY = row == 0 ? 0 : lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
328 for (
int row = 0; row < numberOfRows; ++row)
335 lineInfo[row].lineY = containerCrossLength -
crossHeights;
340 Coord totalHeight = 0;
342 for (
int row = 0; row < numberOfRows; ++row)
343 totalHeight += lineInfo[row].crossSize;
347 const auto difference =
jmax (Coord(), (containerCrossLength - totalHeight) / numberOfRows);
349 for (
int row = 0; row < numberOfRows; ++row)
352 lineInfo[row].lineY = row == 0 ? 0 : lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
359 for (
int row = 0; row < numberOfRows; ++row)
360 lineInfo[row].lineY = row == 0 ?
additionalength : lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
364 const auto additionalength = numberOfRows <= 1 ? Coord() :
jmax (Coord(), (containerCrossLength - totalHeight)
366 lineInfo[0].lineY = 0;
368 for (
int row = 1; row < numberOfRows; ++row)
369 lineInfo[row].lineY +=
additionalength + lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
373 const auto additionalength = numberOfRows <= 1 ? Coord() :
jmax (Coord(), (containerCrossLength - totalHeight)
374 /
static_cast<Coord> (2 + (2 * (numberOfRows - 1))));
378 for (
int row = 1; row < numberOfRows; ++row)
379 lineInfo[row].lineY += (2 *
additionalength) + lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
384 void resolveAutoMarginsOnCrossAxis() noexcept
386 for (
int row = 0; row < numberOfRows; ++row)
388 const auto numColumns = lineInfo[row].numItems;
391 for (
int column = 0; column < numColumns; ++column)
393 auto& item = getItem (column, row);
395 getStartLockedMargin (Axis::cross, item) = [&]
397 if (isAuto (getStartMargin (Axis::cross, item)) && isAuto (getEndMargin (Axis::cross, item)))
400 if (isAuto (getStartMargin (Axis::cross, item)))
401 return crossSizeForLine - getLockedSize (Axis::cross, item) - getEndMargin (Axis::cross, item);
403 return getStartLockedMargin (Axis::cross, item);
410 void alignItemsInCrossAxisInLinesPerAlignSelf() noexcept
412 for (
int row = 0; row < numberOfRows; ++row)
414 const auto numColumns = lineInfo[row].numItems;
415 const auto lineSize = lineInfo[row].crossSize;
417 for (
int column = 0; column < numColumns; ++column)
419 auto& item = getItem (column, row);
421 if (isAuto (getStartMargin (Axis::cross, item)) || isAuto (getEndMargin (Axis::cross, item)))
426 switch (item.item->alignSelf)
438 getStartLockedMargin (Axis::cross, item) = [&]
446 return (Coord) getStartMargin (Axis::cross, item);
452 return lineSize - getLockedSize (Axis::cross, item) - getEndMargin (Axis::cross, item);
457 return getStartMargin (Axis::cross, item) + (
lineSize - getLockedSize (Axis::cross, item) - getStartMargin (Axis::cross, item) - getEndMargin (Axis::cross, item)) / 2;
461 return (Coord) getStartMargin (Axis::cross, item);
470 auto newSize = isAssigned (getItemSize (Axis::cross, item)) ? computePreferredSize (Axis::cross, item)
471 :
lineSize - getStartMargin (Axis::cross, item) - getEndMargin (Axis::cross, item);
473 if (isAssigned (getMaxSize (Axis::cross, item)))
476 if (isAssigned (getMinSize (Axis::cross, item)))
479 getLockedSize (Axis::cross, item) =
newSize;
485 void alignItemsByJustifyContent() noexcept
489 recalculateTotalItemLengthPerLineArray();
491 for (
int row = 0; row < numberOfRows; ++row)
493 const auto numColumns = lineInfo[row].numItems;
498 x = containerLineLength - lineInfo[row].totalLength;
502 x = (containerLineLength - lineInfo[row].totalLength) / 2;
507 =
jmax (Coord(), (containerLineLength - lineInfo[row].totalLength) /
jmax (1, numColumns - 1));
512 =
jmax (Coord(), (containerLineLength - lineInfo[row].totalLength) /
jmax (1, 2 * numColumns));
515 for (
int column = 0; column < numColumns; ++column)
517 auto& item = getItem (column, row);
522 item.item->currentBounds.setPosition (isRowDirection ? (
float) (x + item.lockedMarginLeft)
523 : (
float) item.lockedMarginLeft,
524 isRowDirection ? (
float) item.lockedMarginTop
525 : (
float) (x + item.lockedMarginTop));
527 x += getItemMainSize (item);
532 void layoutAllItems() noexcept
534 for (
int row = 0; row < numberOfRows; ++row)
536 const auto lineY = lineInfo[row].lineY;
537 const auto numColumns = lineInfo[row].numItems;
539 for (
int column = 0; column < numColumns; ++column)
541 auto& item = getItem (column, row);
544 item.item->currentBounds.setY ((
float) (lineY + item.lockedMarginTop));
546 item.item->currentBounds.setX ((
float) (lineY + item.lockedMarginLeft));
548 item.item->currentBounds.setSize ((
float) item.lockedWidth,
549 (
float) item.lockedHeight);
558 void resetRowItems (
const int row)
noexcept
560 const auto numColumns = lineInfo[row].numItems;
562 for (
int column = 0; column < numColumns; ++column)
563 resetItem (getItem (column, row));
566 void resetUnlockedRowItems (
const int row)
noexcept
568 const auto numColumns = lineInfo[row].numItems;
570 for (
int column = 0; column < numColumns; ++column)
572 auto& item = getItem (column, row);
579 void resetItem (ItemWithState& item)
noexcept
583 for (
auto&
axis : { Axis::main, Axis::cross })
584 getLockedSize (
axis, item) = computePreferredSize (
axis, item);
587 bool layoutRowItems (
const int row)
noexcept
589 const auto numColumns = lineInfo[row].numItems;
593 for (
int column = 0; column < numColumns; ++column)
595 const auto& item = getItem (column, row);
626 for (
int column = 0; column < numColumns; ++column)
628 auto& item = getItem (column, row);
639 void recalculateTotalItemLengthPerLineArray() noexcept
641 for (
int row = 0; row < numberOfRows; ++row)
643 lineInfo[row].totalLength = 0;
644 const auto numColumns = lineInfo[row].numItems;
646 for (
int column = 0; column < numColumns; ++column)
647 lineInfo[row].totalLength += getItemMainSize (getItem (column, row));
651 void reverseLocations() noexcept
655 for (
auto& item : owner.items)
656 item.currentBounds.setX ((
float) (containerLineLength - item.currentBounds.getRight()));
660 for (
auto& item : owner.items)
661 item.currentBounds.setY ((
float) (containerLineLength - item.currentBounds.getBottom()));
665 void reverseWrap() noexcept
671 for (
auto& item : owner.items)
672 item.currentBounds.setY ((
float) (containerCrossLength - item.currentBounds.getBottom()));
676 for (
auto& item : owner.items)
677 item.currentBounds.setX ((
float) (containerCrossLength - item.currentBounds.getRight()));
682 Coord getItemMainSize (
const ItemWithState& item)
const noexcept
684 return isRowDirection ? item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight
685 : item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom;
688 Coord getItemCrossSize (
const ItemWithState& item)
const noexcept
690 return isRowDirection ? item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom
691 : item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight;
694 bool addToItemLength (ItemWithState& item,
const Coord length,
int row)
const noexcept
698 const auto prefSize = computePreferredSize (Axis::main, item);
700 const auto pickForMainAxis = [
this] (
auto& a,
auto& b) ->
auto& {
return pickForAxis (Axis::main, a, b); };
702 if (isAssigned (
pickForMainAxis (item.item->maxWidth, item.item->maxHeight))
719 lineInfo[row].totalLength +=
pickForMainAxis (item.lockedWidth, item.lockedHeight)
726 Coord computePreferredSize (Axis
axis, ItemWithState&
itemWithState)
const noexcept
730 auto preferredSize = (item.flexBasis > 0 &&
axis == Axis::main) ? item.flexBasis
736 if (isAssigned (minSize) && preferredSize < minSize)
741 if (isAssigned (maxSize) && maxSize < preferredSize)
744 return preferredSize;
752 : flexDirection (d), flexWrap (w), alignContent (
ac), alignItems (
ai), justifyContent (
jc)
758 if (!
items.isEmpty())
762 layout.createStates();
763 layout.initialiseItems();
764 layout.resolveFlexibleLengths();
765 layout.resolveAutoMarginsOnMainAxis();
766 layout.calculateCrossSizesByLine();
767 layout.calculateCrossSizeOfAllItems();
768 layout.alignLinesPerAlignContent();
769 layout.resolveAutoMarginsOnCrossAxis();
770 layout.alignItemsInCrossAxisInLinesPerAlignSelf();
771 layout.alignItemsByJustifyContent();
772 layout.layoutAllItems();
774 for (
auto& item :
items)
778 if (
auto* comp = item.associatedComponent)
780 (
int) item.currentBounds.getY(),
781 (
int) item.currentBounds.getRight(),
782 (
int) item.currentBounds.getBottom()));
784 if (
auto* box = item.associatedFlexBox)
785 box->performLayout (item.currentBounds);
848 FlexBoxTests() :
UnitTest (
"FlexBox", UnitTestCategories::gui) {}
850 void runTest()
override
852 using AlignSelf = FlexItem::AlignSelf;
853 using Direction = FlexBox::Direction;
855 const Rectangle<float> rect (10.0f, 20.0f, 300.0f, 200.0f);
856 const auto doLayout = [&rect] (Direction direction, Array<FlexItem> items)
860 flex.
items = std::move (items);
865 beginTest (
"flex item with mostly auto properties");
867 const auto test = [
this, &doLayout] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
870 expect (flex.
items.getFirst().currentBounds == expectedBounds);
873 test (Direction::row, AlignSelf::autoAlign, { rect.getX(), rect.getY(), 0.0f, rect.getHeight() });
874 test (Direction::row, AlignSelf::stretch, { rect.getX(), rect.getY(), 0.0f, rect.getHeight() });
875 test (Direction::row, AlignSelf::flexStart, { rect.getX(), rect.getY(), 0.0f, 0.0f });
876 test (Direction::row, AlignSelf::flexEnd, { rect.getX(), rect.getBottom(), 0.0f, 0.0f });
877 test (Direction::row, AlignSelf::center, { rect.getX(), rect.getCentreY(), 0.0f, 0.0f });
879 test (Direction::column, AlignSelf::autoAlign, { rect.getX(), rect.getY(), rect.getWidth(), 0.0f });
880 test (Direction::column, AlignSelf::stretch, { rect.getX(), rect.getY(), rect.getWidth(), 0.0f });
881 test (Direction::column, AlignSelf::flexStart, { rect.getX(), rect.getY(), 0.0f, 0.0f });
882 test (Direction::column, AlignSelf::flexEnd, { rect.getRight(), rect.getY(), 0.0f, 0.0f });
883 test (Direction::column, AlignSelf::center, { rect.getCentreX(), rect.getY(), 0.0f, 0.0f });
886 beginTest (
"flex item with specified width and height");
888 constexpr auto w = 50.0f;
889 constexpr auto h = 60.0f;
890 const auto test = [&] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
895 expect (flex.
items.getFirst().currentBounds == expectedBounds);
898 test (Direction::row, AlignSelf::autoAlign, { rect.getX(), rect.getY(), w, h });
899 test (Direction::row, AlignSelf::stretch, { rect.getX(), rect.getY(), w, h });
900 test (Direction::row, AlignSelf::flexStart, { rect.getX(), rect.getY(), w, h });
901 test (Direction::row, AlignSelf::flexEnd, { rect.getX(), rect.getBottom() - h, w, h });
902 test (Direction::row, AlignSelf::center, { rect.getX(), rect.getY() + (rect.getHeight() - h) * 0.5f, w, h });
904 test (Direction::column, AlignSelf::autoAlign, { rect.getX(), rect.getY(), w, h });
905 test (Direction::column, AlignSelf::stretch, { rect.getX(), rect.getY(), w, h });
906 test (Direction::column, AlignSelf::flexStart, { rect.getX(), rect.getY(), w, h });
907 test (Direction::column, AlignSelf::flexEnd, { rect.getRight() - w, rect.getY(), w, h });
908 test (Direction::column, AlignSelf::center, { rect.getX() + (rect.getWidth() - w) * 0.5f, rect.getY(), w, h });
911 beginTest (
"flex item with oversized width and height");
913 const auto w = rect.getWidth() * 2;
914 const auto h = rect.getHeight() * 2;
915 const auto test = [
this, &doLayout, &w, &h] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
920 expect (flex.
items.getFirst().currentBounds == expectedBounds);
923 const Rectangle<float> baseRow (rect.getX(), rect.getY(), rect.getWidth(), h);
924 test (Direction::row, AlignSelf::autoAlign, baseRow);
925 test (Direction::row, AlignSelf::stretch, baseRow);
926 test (Direction::row, AlignSelf::flexStart, baseRow);
927 test (Direction::row, AlignSelf::flexEnd, baseRow.withBottomY (rect.getBottom()));
928 test (Direction::row, AlignSelf::center, baseRow.withCentre (rect.getCentre()));
930 const Rectangle<float> baseColumn (rect.getX(), rect.getY(), w, rect.getHeight());
931 test (Direction::column, AlignSelf::autoAlign, baseColumn);
932 test (Direction::column, AlignSelf::stretch, baseColumn);
933 test (Direction::column, AlignSelf::flexStart, baseColumn);
934 test (Direction::column, AlignSelf::flexEnd, baseColumn.withRightX (rect.getRight()));
935 test (Direction::column, AlignSelf::center, baseColumn.withCentre (rect.getCentre()));
938 beginTest (
"flex item with minimum width and height");
940 constexpr auto w = 50.0f;
941 constexpr auto h = 60.0f;
942 const auto test = [&] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
947 expect (flex.
items.getFirst().currentBounds == expectedBounds);
950 test (Direction::row, AlignSelf::autoAlign, { rect.getX(), rect.getY(), w, rect.getHeight() });
951 test (Direction::row, AlignSelf::stretch, { rect.getX(), rect.getY(), w, rect.getHeight() });
952 test (Direction::row, AlignSelf::flexStart, { rect.getX(), rect.getY(), w, h });
953 test (Direction::row, AlignSelf::flexEnd, { rect.getX(), rect.getBottom() - h, w, h });
954 test (Direction::row, AlignSelf::center, { rect.getX(), rect.getY() + (rect.getHeight() - h) * 0.5f, w, h });
956 test (Direction::column, AlignSelf::autoAlign, { rect.getX(), rect.getY(), rect.getWidth(), h });
957 test (Direction::column, AlignSelf::stretch, { rect.getX(), rect.getY(), rect.getWidth(), h });
958 test (Direction::column, AlignSelf::flexStart, { rect.getX(), rect.getY(), w, h });
959 test (Direction::column, AlignSelf::flexEnd, { rect.getRight() - w, rect.getY(), w, h });
960 test (Direction::column, AlignSelf::center, { rect.getX() + (rect.getWidth() - w) * 0.5f, rect.getY(), w, h });
963 beginTest (
"flex item with maximum width and height");
965 constexpr auto w = 50.0f;
966 constexpr auto h = 60.0f;
967 const auto test = [&] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
972 expect (flex.
items.getFirst().currentBounds == expectedBounds);
975 test (Direction::row, AlignSelf::autoAlign, { rect.getX(), rect.getY(), 0.0f, h });
976 test (Direction::row, AlignSelf::stretch, { rect.getX(), rect.getY(), 0.0f, h });
977 test (Direction::row, AlignSelf::flexStart, { rect.getX(), rect.getY(), 0.0f, 0.0f });
978 test (Direction::row, AlignSelf::flexEnd, { rect.getX(), rect.getBottom(), 0.0f, 0.0f });
979 test (Direction::row, AlignSelf::center, { rect.getX(), rect.getCentreY(), 0.0f, 0.0f });
981 test (Direction::column, AlignSelf::autoAlign, { rect.getX(), rect.getY(), w, 0.0f });
982 test (Direction::column, AlignSelf::stretch, { rect.getX(), rect.getY(), w, 0.0f });
983 test (Direction::column, AlignSelf::flexStart, { rect.getX(), rect.getY(), 0.0f, 0.0f });
984 test (Direction::column, AlignSelf::flexEnd, { rect.getRight(), rect.getY(), 0.0f, 0.0f });
985 test (Direction::column, AlignSelf::center, { rect.getCentreX(), rect.getY(), 0.0f, 0.0f });
988 beginTest (
"flex item with specified flex");
990 const auto test = [
this, &doLayout] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
993 expect (flex.
items.getFirst().currentBounds == expectedBounds);
996 test (Direction::row, AlignSelf::autoAlign, { rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight() });
997 test (Direction::row, AlignSelf::stretch, { rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight() });
998 test (Direction::row, AlignSelf::flexStart, { rect.getX(), rect.getY(), rect.getWidth(), 0.0f });
999 test (Direction::row, AlignSelf::flexEnd, { rect.getX(), rect.getBottom(), rect.getWidth(), 0.0f });
1000 test (Direction::row, AlignSelf::center, { rect.getX(), rect.getCentreY(), rect.getWidth(), 0.0f });
1002 test (Direction::column, AlignSelf::autoAlign, { rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight() });
1003 test (Direction::column, AlignSelf::stretch, { rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight() });
1004 test (Direction::column, AlignSelf::flexStart, { rect.getX(), rect.getY(), 0.0f, rect.getHeight() });
1005 test (Direction::column, AlignSelf::flexEnd, { rect.getRight(), rect.getY(), 0.0f, rect.getHeight() });
1006 test (Direction::column, AlignSelf::center, { rect.getCentreX(), rect.getY(), 0.0f, rect.getHeight() });
1009 beginTest (
"flex item with margin");
1011 const FlexItem::Margin margin (10.0f, 20.0f, 30.0f, 40.0f);
1013 const auto test = [
this, &doLayout, &margin] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
1016 expect (flex.
items.getFirst().currentBounds == expectedBounds);
1019 const auto remainingHeight = rect.getHeight() - margin.top - margin.bottom;
1020 const auto remainingWidth = rect.getWidth() - margin.left - margin.right;
1022 test (Direction::row, AlignSelf::autoAlign, { rect.getX() + margin.left, rect.getY() + margin.top, 0.0f, remainingHeight });
1023 test (Direction::row, AlignSelf::stretch, { rect.getX() + margin.left, rect.getY() + margin.top, 0.0f, remainingHeight });
1024 test (Direction::row, AlignSelf::flexStart, { rect.getX() + margin.left, rect.getY() + margin.top, 0.0f, 0.0f });
1025 test (Direction::row, AlignSelf::flexEnd, { rect.getX() + margin.left, rect.getBottom() - margin.bottom, 0.0f, 0.0f });
1026 test (Direction::row, AlignSelf::center, { rect.getX() + margin.left, rect.getY() + margin.top + remainingHeight * 0.5f, 0.0f, 0.0f });
1028 test (Direction::column, AlignSelf::autoAlign, { rect.getX() + margin.left, rect.getY() + margin.top, remainingWidth, 0.0f });
1029 test (Direction::column, AlignSelf::stretch, { rect.getX() + margin.left, rect.getY() + margin.top, remainingWidth, 0.0f });
1030 test (Direction::column, AlignSelf::flexStart, { rect.getX() + margin.left, rect.getY() + margin.top, 0.0f, 0.0f });
1031 test (Direction::column, AlignSelf::flexEnd, { rect.getRight() - margin.right, rect.getY() + margin.top, 0.0f, 0.0f });
1032 test (Direction::column, AlignSelf::center, { rect.getX() + margin.left + remainingWidth * 0.5f, rect.getY() + margin.top, 0.0f, 0.0f });
1035 const AlignSelf alignments[] { AlignSelf::autoAlign,
1037 AlignSelf::flexStart,
1039 AlignSelf::center };
1041 beginTest (
"flex item with auto margin");
1043 for (
const auto& alignment : alignments)
1045 for (
const auto& direction : { Direction::row, Direction::column })
1048 .
withMargin ((
float) FlexItem::autoValue) });
1049 expect (flex.
items.getFirst().currentBounds == Rectangle<float> (rect.getCentre(), rect.getCentre()));
1053 const auto testTop = [
this, &doLayout] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
1057 expect (flex.
items.getFirst().currentBounds == expectedBounds);
1060 for (
const auto& alignment : alignments)
1061 testTop (Direction::row, alignment, { rect.getX(), rect.getBottom(), 0.0f, 0.0f });
1063 testTop (Direction::column, AlignSelf::autoAlign, { rect.getX(), rect.getBottom(), rect.getWidth(), 0.0f });
1064 testTop (Direction::column, AlignSelf::stretch, { rect.getX(), rect.getBottom(), rect.getWidth(), 0.0f });
1065 testTop (Direction::column, AlignSelf::flexStart, { rect.getX(), rect.getBottom(), 0.0f, 0.0f });
1066 testTop (Direction::column, AlignSelf::flexEnd, { rect.getRight(), rect.getBottom(), 0.0f, 0.0f });
1067 testTop (Direction::column, AlignSelf::center, { rect.getCentreX(), rect.getBottom(), 0.0f, 0.0f });
1069 const auto testBottom = [
this, &doLayout] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
1073 expect (flex.
items.getFirst().currentBounds == expectedBounds);
1076 for (
const auto& alignment : alignments)
1077 testBottom (Direction::row, alignment, { rect.getX(), rect.getY(), 0.0f, 0.0f });
1079 testBottom (Direction::column, AlignSelf::autoAlign, { rect.getX(), rect.getY(), rect.getWidth(), 0.0f });
1080 testBottom (Direction::column, AlignSelf::stretch, { rect.getX(), rect.getY(), rect.getWidth(), 0.0f });
1081 testBottom (Direction::column, AlignSelf::flexStart, { rect.getX(), rect.getY(), 0.0f, 0.0f });
1082 testBottom (Direction::column, AlignSelf::flexEnd, { rect.getRight(), rect.getY(), 0.0f, 0.0f });
1083 testBottom (Direction::column, AlignSelf::center, { rect.getCentreX(), rect.getY(), 0.0f, 0.0f });
1085 const auto testLeft = [
this, &doLayout] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
1089 expect (flex.
items.getFirst().currentBounds == expectedBounds);
1092 testLeft (Direction::row, AlignSelf::autoAlign, { rect.getRight(), rect.getY(), 0.0f, rect.getHeight() });
1093 testLeft (Direction::row, AlignSelf::stretch, { rect.getRight(), rect.getY(), 0.0f, rect.getHeight() });
1094 testLeft (Direction::row, AlignSelf::flexStart, { rect.getRight(), rect.getY(), 0.0f, 0.0f });
1095 testLeft (Direction::row, AlignSelf::flexEnd, { rect.getRight(), rect.getBottom(), 0.0f, 0.0f });
1096 testLeft (Direction::row, AlignSelf::center, { rect.getRight(), rect.getCentreY(), 0.0f, 0.0f });
1098 for (
const auto& alignment : alignments)
1099 testLeft (Direction::column, alignment, { rect.getRight(), rect.getY(), 0.0f, 0.0f });
1101 const auto testRight = [
this, &doLayout] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
1105 expect (flex.
items.getFirst().currentBounds == expectedBounds);
1108 testRight (Direction::row, AlignSelf::autoAlign, { rect.getX(), rect.getY(), 0.0f, rect.getHeight() });
1109 testRight (Direction::row, AlignSelf::stretch, { rect.getX(), rect.getY(), 0.0f, rect.getHeight() });
1110 testRight (Direction::row, AlignSelf::flexStart, { rect.getX(), rect.getY(), 0.0f, 0.0f });
1111 testRight (Direction::row, AlignSelf::flexEnd, { rect.getX(), rect.getBottom(), 0.0f, 0.0f });
1112 testRight (Direction::row, AlignSelf::center, { rect.getX(), rect.getCentreY(), 0.0f, 0.0f });
1114 for (
const auto& alignment : alignments)
1115 testRight (Direction::column, alignment, { rect.getX(), rect.getY(), 0.0f, 0.0f });
1118 beginTest (
"in a multiline layout, items too large to fit on the main axis are given a line to themselves");
1120 const auto spacer = 10.0f;
1122 for (
const auto alignment : alignments)
1125 flex.
flexWrap = FlexBox::Wrap::wrap;
1126 flex.
items = { FlexItem().withAlignSelf (alignment)
1128 .withHeight (spacer),
1129 FlexItem().withAlignSelf (alignment)
1130 .withWidth (rect.getWidth() * 2)
1131 .withHeight (rect.getHeight()),
1132 FlexItem().withAlignSelf (alignment)
1134 .withHeight (spacer) };
1137 expect (flex.
items[0].currentBounds == Rectangle<float> (rect.getX(), rect.getY(), spacer, spacer));
1138 expect (flex.
items[1].currentBounds == Rectangle<float> (rect.getX(), rect.getY() + spacer, rect.getWidth(), rect.getHeight()));
1139 expect (flex.
items[2].currentBounds == Rectangle<float> (rect.getX(), rect.getBottom() + spacer, 10.0f, 10.0f));
Holds a resizable array of primitive or copy-by-value objects.
void ensureStorageAllocated(int minNumElements)
Increases the array's internal storage to hold a minimum number of elements.
ElementType * begin() noexcept
Returns a pointer to the first element in the array.
ElementType * end() noexcept
Returns a pointer to the element which follows the last element in the array.
The base class for all JUCE user-interface objects.
Represents a FlexBox container, which contains and manages the layout of a set of FlexItem objects.
JustifyContent justifyContent
Defines how the container distributes space between and around items along the main-axis.
AlignContent
Possible values for the alignContent property.
@ stretch
Lines of items are stretched from start to end of the cross axis.
@ center
Lines of items are aligned towards the center of the cross axis.
@ spaceBetween
Lines of items are evenly spaced along the cross axis with spaces between them.
@ flexEnd
Lines of items are aligned towards the end of the cross axis.
@ spaceAround
Lines of items are evenly spaced along the cross axis with spaces around them.
@ flexStart
Lines of items are aligned towards the start of the cross axis.
Wrap
Possible values for the flexWrap property.
@ noWrap
Items are forced into a single line.
@ wrapReverse
Items are wrapped onto multiple lines from bottom to top.
Direction
Possible values for the flexDirection property.
@ rowReverse
Set the main axis direction from right to left.
@ row
Set the main axis direction from left to right.
@ columnReverse
Set the main axis direction from bottom to top.
JustifyContent
Possible values for the justifyContent property.
@ center
Items are justified towards the center of the main axis.
@ spaceBetween
Items are evenly spaced along the main axis with spaces between them.
@ flexEnd
Items are justified towards the end of the main axis.
@ spaceAround
Items are evenly spaced along the main axis with spaces around them.
FlexBox() noexcept=default
Creates an empty FlexBox container with default parameters.
Wrap flexWrap
Specifies whether items are forced into a single line or can be wrapped onto multiple lines.
void performLayout(Rectangle< float > targetArea)
Lays-out the box's items within the given rectangle.
AlignItems
Possible values for the alignItems property.
@ stretch
Items are stretched from start to end of the cross axis.
@ center
Items are aligned towards the center of the cross axis.
@ flexEnd
Items are aligned towards the end of the cross axis.
@ flexStart
Items are aligned towards the start of the cross axis.
Array< FlexItem > items
The set of items to lay-out.
AlignItems alignItems
Specifies the alignment of flex items along the cross-axis of each line.
Direction flexDirection
Specifies how flex items are placed in the flex container, and defines the direction of the main axis...
AlignContent alignContent
Specifies how a flex container's lines are placed within the flex container when there is extra space...
Describes the properties of an item inside a FlexBox container.
float height
The item's height.
AlignSelf
Possible value for the alignSelf property.
@ stretch
Item is stretched from start to end of the cross axis.
@ center
Item is aligned towards the center of the cross axis.
@ flexEnd
Item is aligned towards the end of the cross axis.
@ flexStart
Item is aligned towards the start of the cross axis.
@ autoAlign
Follows the FlexBox container's alignItems property.
AlignSelf alignSelf
This is the align-self property of the item.
FlexItem() noexcept
Creates an item with default parameters, and zero size.
FlexItem withHeight(float newHeight) const noexcept
Returns a copy of this object with a new height.
FlexItem withWidth(float newWidth) const noexcept
Returns a copy of this object with a new width.
float minWidth
The item's minimum width.
float maxHeight
The item's maximum height.
FlexItem withOrder(int newOrder) const noexcept
Returns a copy of this object with a new order.
int order
Determines the order used to lay out items in their flex container.
static const int autoValue
This constant can be used for sizes to indicate that 'auto' mode should be used.
FlexItem withAlignSelf(AlignSelf newAlignSelf) const noexcept
Returns a copy of this object with a new alignSelf value.
FlexItem withMinWidth(float newMinWidth) const noexcept
Returns a copy of this object with a new minimum width.
FlexItem withMinHeight(float newMinHeight) const noexcept
Returns a copy of this object with a new minimum height.
float minHeight
The item's minimum height.
FlexItem withMargin(Margin) const noexcept
Returns a copy of this object with a new margin.
FlexItem withMaxHeight(float newMaxHeight) const noexcept
Returns a copy of this object with a new maximum height.
float width
The item's width.
float flexGrow
Specifies the flex grow factor of this item.
float maxWidth
The item's maximum width.
FlexItem withMaxWidth(float newMaxWidth) const noexcept
Returns a copy of this object with a new maximum width.
FlexItem withFlex(float newFlexGrow) const noexcept
Returns a copy of this object with a new flex-grow value.
Margin margin
The margin to leave around this item.
static const int notAssigned
This constant can be used for sizes to indicate that no value has been set.
Very simple container class to hold a pointer to some data on the heap.
Manages a rectangle and allows geometric operations to be performed on it.
Rectangle< float > toFloat() const noexcept
Casts this rectangle to a Rectangle<float>.
Point< ValueType > getPosition() const noexcept
Returns the rectangle's top-left position as a Point.
ValueType getWidth() const noexcept
Returns the width of the rectangle.
ValueType getHeight() const noexcept
Returns the height of the rectangle.
This is a base class for classes that perform a unit test.
constexpr bool approximatelyEqual(Type a, Type b, Tolerance< Type > tolerance=Tolerance< Type >{} .withAbsolute(std::numeric_limits< Type >::min()) .withRelative(std::numeric_limits< Type >::epsilon()))
Returns true if the two floating-point numbers are approximately equal.
constexpr Type jmin(Type a, Type b)
Returns the smaller of two values.
constexpr bool exactlyEqual(Type a, Type b)
Equivalent to operator==, but suppresses float-equality warnings.
constexpr Type jmax(Type a, Type b)
Returns the larger of two values.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
Margin() noexcept
Creates a margin of size zero.
float right
Right margin size.
float left
Left margin size.
float bottom
Bottom margin size.
float top
Top margin size.