29template <
typename Item>
30static Array<Item> operator+ (
const Array<Item>& a,
const Array<Item>& b)
43 int numImplicitLeading;
54 T operator() (T t)
const {
return t; }
60 T operator() (T t)
const {
return std::round (t); }
63 template <
typename RoundingFunction>
68 float totalCellSize = 0.0f;
70 for (
const auto& trackInfo : tracks)
71 if (! trackInfo.isFractional() || trackInfo.isAuto())
72 totalCellSize += roundingFunction (trackInfo.getSize());
74 float totalGap = tracks.
size() > 1 ? (
float) (tracks.
size() - 1) * roundingFunction ((
float) gapSize.pixels)
77 return totalCellSize + totalGap;
80 static float getRelativeUnitSize (
float size,
float totalAbsolute,
const Array<TrackInfo>& tracks)
noexcept
82 const float totalRelative =
jlimit (0.0f, size, size - totalAbsolute);
83 float factorsSum = 0.0f;
85 for (
const auto& trackInfo : tracks)
86 if (trackInfo.isFractional())
87 factorsSum += trackInfo.getSize();
90 return totalRelative / factorsSum;
96 return getTotalAbsoluteSize (rowTracks, rowGapSize);
101 return getTotalAbsoluteSize (columnTracks, columnGapSize);
104 float getRelativeWidthUnit (
float gridWidth,
Px columnGapSize,
const Array<TrackInfo>& columnTracks)
106 return getRelativeUnitSize (gridWidth, getTotalAbsoluteWidth (columnTracks, columnGapSize), columnTracks);
109 float getRelativeHeightUnit (
float gridHeight,
Px rowGapSize,
const Array<TrackInfo>& rowTracks)
111 return getRelativeUnitSize (gridHeight, getTotalAbsoluteHeight (rowTracks, rowGapSize), rowTracks);
119 [] (
const auto& t) { return t.isFractional(); });
122 void computeSizes (
float gridWidth,
float gridHeight,
123 Px columnGapToUse,
Px rowGapToUse,
126 if (hasAnyFractions (tracks.columns.items))
128 relativeWidthUnit = getRelativeWidthUnit (gridWidth, columnGapToUse, tracks.columns.items);
129 fractionallyDividedWidth = gridWidth - getTotalAbsoluteSize (tracks.columns.items, columnGapToUse);
133 remainingWidth = gridWidth - getTotalAbsoluteSize (tracks.columns.items, columnGapToUse);
136 if (hasAnyFractions (tracks.rows.items))
138 relativeHeightUnit = getRelativeHeightUnit (gridHeight, rowGapToUse, tracks.rows.items);
139 fractionallyDividedHeight = gridHeight - getTotalAbsoluteSize (tracks.rows.items, rowGapToUse);
143 remainingHeight = gridHeight - getTotalAbsoluteSize (tracks.rows.items, rowGapToUse);
146 const auto calculateTrackBounds = [&] (
auto& outBounds,
147 const auto& trackItems,
149 auto totalSizeForFractionalItems,
152 const auto lastFractionalIndex = [&]
154 for (
int i = trackItems.size() - 1; 0 <= i; --i)
155 if (trackItems[i].isFractional())
162 float carriedError = 0.0f;
164 for (
int i = 0; i < trackItems.size(); ++i)
166 const auto& currentItem = trackItems[i];
168 const auto currentTrackSize = [&]
170 if (i == lastFractionalIndex)
171 return totalSizeForFractionalItems;
173 const auto absoluteSize = currentItem.getAbsoluteSize (relativeUnit);
175 if (! currentItem.isFractional())
176 return roundingFunction (absoluteSize);
178 const auto result = roundingFunction (absoluteSize - carriedError);
179 carriedError += result - absoluteSize;
183 if (currentItem.isFractional())
184 totalSizeForFractionalItems -= currentTrackSize;
186 const auto end =
start + currentTrackSize;
187 outBounds.emplace_back (
start,
end);
188 start =
end + roundingFunction (
static_cast<float> (gap.pixels));
192 calculateTrackBounds (columnTrackBounds,
193 tracks.columns.items,
195 fractionallyDividedWidth,
198 calculateTrackBounds (rowTrackBounds,
201 fractionallyDividedHeight,
205 float relativeWidthUnit = 0.0f;
206 float relativeHeightUnit = 0.0f;
207 float fractionallyDividedWidth = 0.0f;
208 float fractionallyDividedHeight = 0.0f;
209 float remainingWidth = 0.0f;
210 float remainingHeight = 0.0f;
214 RoundingFunction roundingFunction;
220 enum { invalid = -999999 };
221 static constexpr auto emptyAreaCharacter =
".";
240 for (
int i = 1; i <= tracks.
size(); ++i)
247 li.lineNames.
add (currentTrack.getStartLineName());
251 if (i > 1 && i <= tracks.
size())
256 li.lineNames.
add (prevTrack.getEndLineName());
257 li.lineNames.add (currentTrack.getStartLineName());
262 if (i == tracks.
size())
265 li.lineNames.add (currentTrack.getEndLineName());
276 static int deduceAbsoluteLineNumberFromLineName (GridItem::Property prop,
277 const Array<TrackInfo>& tracks)
281 const auto lines = getArrayOfLinesFromTracks (tracks);
284 for (
const auto [index, line] :
enumerate (lines))
286 for (
const auto& name : line.lineNames)
288 if (prop.getName() == name)
295 if (count == prop.getNumber())
296 return (
int) index + 1;
303 static int deduceAbsoluteLineNumber (GridItem::Property prop,
304 const Array<TrackInfo>& tracks)
309 return deduceAbsoluteLineNumberFromLineName (prop, tracks);
311 if (prop.getNumber() > 0)
312 return prop.getNumber();
314 if (prop.getNumber() < 0)
315 return tracks.size() + 2 + prop.getNumber();
322 static int deduceAbsoluteLineNumberFromNamedSpan (
int startLineNumber,
323 GridItem::Property propertyWithSpan,
324 const Array<TrackInfo>& tracks)
326 jassert (propertyWithSpan.hasSpan());
328 const auto lines = getArrayOfLinesFromTracks (tracks);
331 const auto enumerated =
enumerate (lines);
333 for (
const auto [index, line] :
makeRange (enumerated.
begin() + startLineNumber, enumerated.
end()))
335 for (
const auto& name : line.lineNames)
337 if (propertyWithSpan.getName() == name)
344 if (count == propertyWithSpan.getNumber())
345 return (
int) index + 1;
352 static int deduceAbsoluteLineNumberBasedOnSpan (
int startLineNumber,
353 GridItem::Property propertyWithSpan,
354 const Array<TrackInfo>& tracks)
356 jassert (propertyWithSpan.hasSpan());
358 if (propertyWithSpan.hasName())
359 return deduceAbsoluteLineNumberFromNamedSpan (startLineNumber, propertyWithSpan, tracks);
361 return startLineNumber + propertyWithSpan.getNumber();
365 static LineRange deduceLineRange (GridItem::StartAndEndProperty prop,
const Array<TrackInfo>& tracks)
367 jassert (! (prop.start.hasAuto() && prop.end.hasAuto()));
369 if (prop.start.hasAbsolute() && prop.end.hasAuto())
371 prop.end = GridItem::Span (1);
373 else if (prop.start.hasAuto() && prop.end.hasAbsolute())
375 prop.start = GridItem::Span (1);
378 auto s = [&]() -> LineRange
380 if (prop.start.hasAbsolute() && prop.end.hasAbsolute())
382 return { deduceAbsoluteLineNumber (prop.start, tracks),
383 deduceAbsoluteLineNumber (prop.end, tracks) };
386 if (prop.start.hasAbsolute() && prop.end.hasSpan())
388 const auto start = deduceAbsoluteLineNumber (prop.start, tracks);
389 return {
start, deduceAbsoluteLineNumberBasedOnSpan (
start, prop.end, tracks) };
392 if (prop.start.hasSpan() && prop.end.hasAbsolute())
394 const auto start = deduceAbsoluteLineNumber (prop.end, tracks);
395 return {
start, deduceAbsoluteLineNumberBasedOnSpan (
start, prop.start, tracks) };
406 else if (s.start == s.end)
412 static LineArea deduceLineArea (
const GridItem& item,
416 if (item.area.isNotEmpty() && ! grid.templateAreas.isEmpty())
421 return namedAreas.
at (item.area);
424 return { deduceLineRange (item.column, grid.templateColumns),
425 deduceLineRange (item.row, grid.templateRows) };
429 static Array<StringArray> parseAreasProperty (
const StringArray& areasStrings)
431 Array<StringArray> strings;
433 for (
const auto& areaString : areasStrings)
434 strings.add (StringArray::fromTokens (areaString, false));
436 if (strings.size() > 0)
438 for (
auto s : strings)
440 jassert (s.size() == strings[0].size());
447 static NamedArea findArea (Array<StringArray>& stringsArrays)
451 for (
auto& stringArray : stringsArrays)
453 for (
auto&
string : stringArray)
456 if (area.name.isEmpty())
458 if (
string != emptyAreaCharacter)
461 area.lines.row.start = stringsArrays.indexOf (stringArray) + 1;
462 area.lines.column.start = stringArray.indexOf (
string) + 1;
464 area.lines.row.end = stringsArrays.indexOf (stringArray) + 2;
465 area.lines.column.end = stringArray.indexOf (
string) + 2;
468 string = emptyAreaCharacter;
473 if (
string == area.name)
475 area.lines.row.end = stringsArrays.indexOf (stringArray) + 2;
476 area.lines.column.end = stringArray.indexOf (
string) + 2;
479 string = emptyAreaCharacter;
491 auto stringsArrays = parseAreasProperty (areasStrings);
495 for (
auto area = findArea (stringsArrays); area.name.isNotEmpty(); area = findArea (stringsArrays))
497 if (areas.
count (area.name) == 0)
498 areas[area.name] = area.lines;
508 template <
typename RoundingFunction>
509 static Rectangle<float> getCellBounds (
int columnNumber,
int rowNumber,
510 const Tracks& tracks,
511 const SizeCalculation<RoundingFunction>& calculation)
513 const auto correctedColumn = columnNumber - 1 + tracks.columns.numImplicitLeading;
514 const auto correctedRow = rowNumber - 1 + tracks.rows .numImplicitLeading;
521 calculation.columnTrackBounds[(
size_t) correctedColumn].getStart(),
522 calculation.rowTrackBounds[(
size_t) correctedRow].getStart(),
523 calculation.columnTrackBounds[(
size_t) correctedColumn].getEnd() - calculation.columnTrackBounds[(
size_t) correctedColumn].getStart(),
524 calculation.rowTrackBounds[(
size_t) correctedRow].getEnd() - calculation.rowTrackBounds[(
size_t) correctedRow].getStart()
528 template <
typename RoundingFunction>
529 static Rectangle<float> alignCell (Rectangle<float> area,
530 int columnNumber,
int rowNumber,
531 int numberOfColumns,
int numberOfRows,
532 const SizeCalculation<RoundingFunction>& calculation,
537 area.setY (area.getY() + calculation.remainingHeight);
540 area.setX (area.getX() + calculation.remainingWidth);
543 area.setY (area.getY() + calculation.remainingHeight / 2);
546 area.setX (area.getX() + calculation.remainingWidth / 2);
550 const auto shift = ((
float) (rowNumber - 1) * (calculation.remainingHeight /
float (numberOfRows - 1)));
551 area.setY (area.getY() + shift);
556 const auto shift = ((
float) (columnNumber - 1) * (calculation.remainingWidth /
float (numberOfColumns - 1)));
557 area.setX (area.getX() + shift);
562 const auto shift = ((
float) rowNumber * (calculation.remainingHeight /
float (numberOfRows + 1)));
563 area.setY (area.getY() + shift);
568 const auto shift = ((
float) columnNumber * (calculation.remainingWidth /
float (numberOfColumns + 1)));
569 area.setX (area.getX() + shift);
574 const auto inbetweenShift = calculation.remainingHeight /
float (numberOfRows);
575 const auto sidesShift = inbetweenShift / 2;
576 auto shift = (
float) (rowNumber - 1) * inbetweenShift + sidesShift;
578 area.setY (area.getY() + shift);
583 const auto inbetweenShift = calculation.remainingWidth /
float (numberOfColumns);
584 const auto sidesShift = inbetweenShift / 2;
585 auto shift = (
float) (columnNumber - 1) * inbetweenShift + sidesShift;
587 area.setX (area.getX() + shift);
593 template <
typename RoundingFunction>
594 static Rectangle<float> getAreaBounds (PlacementHelpers::LineRange columnRange,
595 PlacementHelpers::LineRange rowRange,
596 const Tracks& tracks,
597 const SizeCalculation<RoundingFunction>& calculation,
601 const auto findAlignedCell = [&] (
int column,
int row)
603 const auto cell = getCellBounds (
column,
row, tracks, calculation);
604 return alignCell (cell,
607 tracks.columns.items.size(),
608 tracks.rows.items.size(),
614 const auto startCell = findAlignedCell (columnRange.start, rowRange.start);
615 const auto endCell = findAlignedCell (columnRange.end - 1, rowRange.end - 1);
617 const auto horizontalRange = startCell.getHorizontalRange().getUnionWith (endCell.getHorizontalRange());
618 const auto verticalRange = startCell.getVerticalRange() .getUnionWith (endCell.getVerticalRange());
619 return { horizontalRange.getStart(), verticalRange.getStart(),
620 horizontalRange.getLength(), verticalRange.getLength() };
632 struct Cell {
int column, row; };
634 OccupancyPlane (
int highestColumnToUse,
int highestRowToUse,
bool isColumnFirst)
635 : highestCrossDimension (isColumnFirst ? highestRowToUse : highestColumnToUse),
636 columnFirst (isColumnFirst)
641 for (
int i = 0; i < columnSpan; i++)
642 for (
int j = 0; j < rowSpan; j++)
643 setCell (cell.column + i, cell.row + j);
645 return { { cell.column, cell.column + columnSpan }, { cell.row, cell.row + rowSpan } };
648 PlacementHelpers::LineArea setCell (Cell
start, Cell
end)
654 Cell nextAvailable (Cell referenceCell,
int columnSpan,
int rowSpan)
656 while (isOccupied (referenceCell, columnSpan, rowSpan) || isOutOfBounds (referenceCell, columnSpan, rowSpan))
657 referenceCell = advance (referenceCell);
659 return referenceCell;
662 Cell nextAvailableOnRow (Cell referenceCell,
int columnSpan,
int rowSpan,
int rowNumber)
664 if (columnFirst && (rowNumber + rowSpan) > highestCrossDimension)
665 highestCrossDimension = rowNumber + rowSpan;
667 while (isOccupied (referenceCell, columnSpan, rowSpan)
668 || (referenceCell.row != rowNumber))
669 referenceCell = advance (referenceCell);
671 return referenceCell;
674 Cell nextAvailableOnColumn (Cell referenceCell,
int columnSpan,
int rowSpan,
int columnNumber)
676 if (! columnFirst && (columnNumber + columnSpan) > highestCrossDimension)
677 highestCrossDimension = columnNumber + columnSpan;
679 while (isOccupied (referenceCell, columnSpan, rowSpan)
680 || (referenceCell.column != columnNumber))
681 referenceCell = advance (referenceCell);
683 return referenceCell;
686 void updateMaxCrossDimensionFromAutoPlacementItem (
int columnSpan,
int rowSpan)
688 highestCrossDimension =
jmax (highestCrossDimension, 1 + getCrossDimension ({ columnSpan, rowSpan }));
697 bool operator< (
const SortableCell& other)
const
701 if (row == other.row)
702 return column < other.column;
704 return row < other.row;
707 if (row == other.row)
708 return column < other.column;
710 return row < other.row;
719 bool isOccupied (Cell cell)
const
721 return occupiedCells.
count ({ cell.column, cell.row, columnFirst }) > 0;
724 bool isOccupied (Cell cell,
int columnSpan,
int rowSpan)
const
726 for (
int i = 0; i < columnSpan; i++)
727 for (
int j = 0; j < rowSpan; j++)
728 if (isOccupied ({ cell.column + i, cell.row + j }))
734 bool isOutOfBounds (Cell cell,
int columnSpan,
int rowSpan)
const
736 const auto highestIndexOfCell = getCrossDimension (cell) + getCrossDimension ({ columnSpan, rowSpan });
737 const auto highestIndexOfGrid = getHighestCrossDimension();
739 return highestIndexOfGrid < highestIndexOfCell;
742 int getHighestCrossDimension()
const
746 if (occupiedCells.
size() > 0)
747 cell = { occupiedCells.
crbegin()->column, occupiedCells.
crbegin()->row };
749 return std::max (getCrossDimension (cell), highestCrossDimension);
752 Cell advance (Cell cell)
const
754 if ((getCrossDimension (cell) + 1) >= getHighestCrossDimension())
755 return fromDimensions (getMainDimension (cell) + 1, 1);
757 return fromDimensions (getMainDimension (cell), getCrossDimension (cell) + 1);
760 int getMainDimension (Cell cell)
const {
return columnFirst ? cell.column : cell.row; }
761 int getCrossDimension (Cell cell)
const {
return columnFirst ? cell.row : cell.column; }
763 Cell fromDimensions (
int mainDimension,
int crossDimension)
const
766 return { mainDimension, crossDimension };
768 return { crossDimension, mainDimension };
771 int highestCrossDimension;
777 static bool isFixed (GridItem::StartAndEndProperty prop)
779 return prop.start.hasName() || prop.start.hasAbsolute() || prop.
end.hasName() || prop.end.hasAbsolute();
782 static bool hasFullyFixedPlacement (
const GridItem& item)
784 if (item.area.isNotEmpty())
787 if (isFixed (item.column) && isFixed (item.row))
793 static bool hasPartialFixedPlacement (
const GridItem& item)
795 if (item.area.isNotEmpty())
798 if (isFixed (item.column) ^ isFixed (item.row))
804 static bool hasAutoPlacement (
const GridItem& item)
806 return ! hasFullyFixedPlacement (item) && ! hasPartialFixedPlacement (item);
823 static int getSpanFromAuto (GridItem::StartAndEndProperty prop)
825 if (prop.end.hasSpan())
826 return prop.end.getNumber();
828 if (prop.start.hasSpan())
829 return prop.start.getNumber();
835 ItemPlacementArray deduceAllItems (
Grid& grid)
const
837 const auto namedAreas = PlacementHelpers::deduceNamedAreas (grid.templateAreas);
839 OccupancyPlane plane (
jmax (grid.templateColumns.size() + 1, 2),
840 jmax (grid.templateRows.size() + 1, 2),
841 isColumnAutoFlow (grid.autoFlow));
843 ItemPlacementArray itemPlacementArray;
844 Array<GridItem*> sortedItems;
846 for (
auto& item : grid.
items)
847 sortedItems.add (&item);
850 [] (
const GridItem* i1,
const GridItem* i2) { return i1->order < i2->order; });
853 for (
auto* item : sortedItems)
855 if (hasFullyFixedPlacement (*item))
857 const auto a = PlacementHelpers::deduceLineArea (*item, grid, namedAreas);
858 plane.setCell ({ a.column.start, a.row.start }, { a.column.end, a.row.end });
859 itemPlacementArray.add ({ item, a });
863 OccupancyPlane::Cell lastInsertionCell = { 1, 1 };
865 for (
auto* item : sortedItems)
867 if (hasPartialFixedPlacement (*item))
869 if (isFixed (item->column))
871 const auto p = PlacementHelpers::deduceLineRange (item->column, grid.templateColumns);
872 const auto columnSpan = std::abs (p.start - p.end);
873 const auto rowSpan = getSpanFromAuto (item->row);
875 const auto insertionCell = hasDenseAutoFlow (grid.autoFlow) ? OccupancyPlane::Cell { p.start, 1 }
877 const auto nextAvailableCell = plane.nextAvailableOnColumn (insertionCell, columnSpan, rowSpan, p.start);
878 const auto lineArea = plane.setCell (nextAvailableCell, columnSpan, rowSpan);
879 lastInsertionCell = nextAvailableCell;
881 itemPlacementArray.add ({ item, lineArea });
883 else if (isFixed (item->row))
885 const auto p = PlacementHelpers::deduceLineRange (item->row, grid.templateRows);
886 const auto columnSpan = getSpanFromAuto (item->column);
887 const auto rowSpan = std::abs (p.start - p.end);
889 const auto insertionCell = hasDenseAutoFlow (grid.autoFlow) ? OccupancyPlane::Cell { 1, p.start }
892 const auto nextAvailableCell = plane.nextAvailableOnRow (insertionCell, columnSpan, rowSpan, p.start);
893 const auto lineArea = plane.setCell (nextAvailableCell, columnSpan, rowSpan);
895 lastInsertionCell = nextAvailableCell;
897 itemPlacementArray.add ({ item, lineArea });
903 for (
auto* item : sortedItems)
904 if (hasAutoPlacement (*item))
905 plane.updateMaxCrossDimensionFromAutoPlacementItem (getSpanFromAuto (item->
column), getSpanFromAuto (item->
row));
907 lastInsertionCell = { 1, 1 };
909 for (
auto* item : sortedItems)
911 if (hasAutoPlacement (*item))
913 const auto columnSpan = getSpanFromAuto (item->column);
914 const auto rowSpan = getSpanFromAuto (item->row);
916 const auto nextAvailableCell = plane.nextAvailable (lastInsertionCell, columnSpan, rowSpan);
917 const auto lineArea = plane.setCell (nextAvailableCell, columnSpan, rowSpan);
919 if (! hasDenseAutoFlow (grid.autoFlow))
920 lastInsertionCell = nextAvailableCell;
922 itemPlacementArray.add ({ item, lineArea });
926 return itemPlacementArray;
930 template <
typename Accessor>
931 static PlacementHelpers::LineRange findFullLineRange (
const ItemPlacementArray&
items, Accessor&& accessor)
936 const auto combine = [&accessor] (
const auto& acc,
const auto& item)
938 const auto newRange = accessor (item);
939 return PlacementHelpers::LineRange {
std::min (acc.start, newRange.start),
946 static PlacementHelpers::LineArea findFullLineArea (
const ItemPlacementArray&
items)
948 return { findFullLineRange (
items, [] (
const auto& item) {
return item.second.column; }),
949 findFullLineRange (
items, [] (
const auto& item) {
return item.second.row; }) };
952 template <
typename Item>
953 static Array<Item> repeated (
int repeats,
const Item& item)
956 result.insertMultiple (-1, item, repeats);
960 static Tracks createImplicitTracks (
const Grid& grid,
const ItemPlacementArray&
items)
962 const auto fullArea = findFullLineArea (
items);
964 const auto leadingColumns =
std::max (0, 1 - fullArea.column.start);
965 const auto leadingRows =
std::max (0, 1 - fullArea.row.start);
967 const auto trailingColumns =
std::max (0, fullArea.column.end - grid.templateColumns.size() - 1);
968 const auto trailingRows =
std::max (0, fullArea.row .end - grid.templateRows .size() - 1);
970 return { { repeated (leadingColumns, grid.autoColumns) + grid.templateColumns + repeated (trailingColumns, grid.autoColumns),
972 { repeated (leadingRows, grid.autoRows) + grid.templateRows + repeated (trailingRows, grid.autoRows),
977 static void applySizeForAutoTracks (Tracks& tracks,
const ItemPlacementArray& placements)
979 const auto setSizes = [&placements] (
auto& tracksInDirection,
const auto& getItem,
const auto& getItemSize)
981 auto& array = tracksInDirection.items;
983 for (
int index = 0; index < array.size(); ++index)
985 if (array.getReference (index).isAuto())
987 const auto combiner = [&] (
const auto acc,
const auto& element)
989 const auto item = getItem (element.second);
990 const auto isNotSpan = std::abs (item.end - item.start) <= 1;
991 return isNotSpan && item.start == index + 1 - tracksInDirection.numImplicitLeading
992 ?
std::max (acc, getItemSize (*element.first))
996 array.getReference (index).size =
std::accumulate (placements.begin(), placements.end(), 0.0f, combiner);
1001 setSizes (tracks.rows,
1002 [] (
const auto& i) { return i.row; },
1003 [] (
const auto& i) { return i.height + i.margin.top + i.margin.bottom; });
1005 setSizes (tracks.columns,
1006 [] (
const auto& i) { return i.column; },
1007 [] (
const auto& i) { return i.width + i.margin.left + i.margin.right; });
1033 if (!
approximatelyEqual (item.height, (
float) GridItem::notAssigned)) r.setHeight (item.height);
1034 if (!
approximatelyEqual (item.maxWidth, (
float) GridItem::notAssigned)) r.setWidth (
jmin (item.maxWidth, r.getWidth()));
1035 if (item.minWidth > 0.0f) r.setWidth (
jmax (item.minWidth, r.getWidth()));
1036 if (!
approximatelyEqual (item.maxHeight, (
float) GridItem::notAssigned)) r.setHeight (
jmin (item.maxHeight, r.getHeight()));
1037 if (item.minHeight > 0.0f) r.setHeight (
jmax (item.minHeight, r.getHeight()));
1057 : size (
static_cast<float> (sizeInPixels.pixels)), isFraction (
false) {}
1060 : size ((
float)fractionOfFreeSpace.fraction), isFraction (
true) {}
1063 : TrackInfo (sizeInPixels)
1065 endLineName = endLineNameToUse;
1069 : TrackInfo (fractionOfFreeSpace)
1071 endLineName = endLineNameToUse;
1075 : TrackInfo (sizeInPixels)
1077 startLineName = startLineNameToUse;
1081 : TrackInfo (fractionOfFreeSpace)
1083 startLineName = startLineNameToUse;
1087 : TrackInfo (startLineNameToUse, sizeInPixels)
1089 endLineName = endLineNameToUse;
1092Grid::TrackInfo::TrackInfo (
const String& startLineNameToUse, Fr fractionOfFreeSpace,
const String& endLineNameToUse) noexcept
1093 : TrackInfo (startLineNameToUse, fractionOfFreeSpace)
1095 endLineName = endLineNameToUse;
1098float Grid::TrackInfo::getAbsoluteSize (
float relativeFractionalUnit)
const
1100 return isFractional() ?
size * relativeFractionalUnit :
size;
1108 auto implicitTracks = Helpers::AutoPlacement::createImplicitTracks (*
this, itemsAndAreas);
1110 Helpers::AutoPlacement::applySizeForAutoTracks (implicitTracks, itemsAndAreas);
1115 const auto doComputeSizes = [&] (
auto& sizeCalculation)
1124 doComputeSizes (calculation);
1125 doComputeSizes (roundedCalculation);
1127 for (
auto& itemAndArea : itemsAndAreas)
1129 auto* item = itemAndArea.first;
1131 const auto getBounds = [&] (
const auto& sizeCalculation)
1133 const auto a = itemAndArea.second;
1135 const auto areaBounds = Helpers::PlacementHelpers::getAreaBounds (a.column,
1142 const auto rounded = [&] (
auto rect) ->
decltype (rect)
1144 return { sizeCalculation.roundingFunction (rect.getX()),
1145 sizeCalculation.roundingFunction (rect.getY()),
1146 sizeCalculation.roundingFunction (rect.getWidth()),
1147 sizeCalculation.roundingFunction (rect.getHeight()) };
1150 return rounded (Helpers::BoxAlignment::alignItem (*item, *
this, areaBounds));
1155 if (
auto* c = item->associatedComponent)
1156 c->setBounds (getBounds (roundedCalculation).toNearestIntEdges() + targetArea.
getPosition());
1163struct GridTests final :
public UnitTest
1166 :
UnitTest (
"Grid", UnitTestCategories::gui)
1169 void runTest()
override
1171 using Fr = Grid::Fr;
1172 using Tr = Grid::TrackInfo;
1173 using Rect = Rectangle<float>;
1175 beginTest (
"Layout calculation of an empty grid is a no-op");
1177 const Rectangle<int> bounds { 100, 200 };
1179 grid.performLayout (bounds);
1185 grid.templateColumns.add (Tr (1_fr));
1186 grid.templateRows.addArray ({ Tr (20_px), Tr (1_fr) });
1188 grid.items.addArray ({ GridItem().withArea (1, 1),
1189 GridItem().withArea (2, 1) });
1191 grid.performLayout (Rectangle<int> (200, 400));
1193 beginTest (
"Layout calculation test: 1 column x 2 rows: no gap");
1194 expect (grid.items[0].currentBounds == Rect (0.0f, 0.0f, 200.f, 20.0f));
1195 expect (grid.items[1].currentBounds == Rect (0.0f, 20.0f, 200.f, 380.0f));
1197 grid.templateColumns.add (Tr (50_px));
1198 grid.templateRows.add (Tr (2_fr));
1200 grid.items.addArray ( { GridItem().withArea (1, 2),
1201 GridItem().withArea (2, 2),
1202 GridItem().withArea (3, 1),
1203 GridItem().withArea (3, 2) });
1205 grid.performLayout (Rectangle<int> (150, 170));
1207 beginTest (
"Layout calculation test: 2 columns x 3 rows: no gap");
1208 expect (grid.items[0].currentBounds == Rect (0.0f, 0.0f, 100.0f, 20.0f));
1209 expect (grid.items[1].currentBounds == Rect (0.0f, 20.0f, 100.0f, 50.0f));
1210 expect (grid.items[2].currentBounds == Rect (100.0f, 0.0f, 50.0f, 20.0f));
1211 expect (grid.items[3].currentBounds == Rect (100.0f, 20.0f, 50.0f, 50.0f));
1212 expect (grid.items[4].currentBounds == Rect (0.0f, 70.0f, 100.0f, 100.0f));
1213 expect (grid.items[5].currentBounds == Rect (100.0f, 70.0f, 50.0f, 100.0f));
1215 grid.columnGap = 20_px;
1216 grid.rowGap = 10_px;
1218 grid.performLayout (Rectangle<int> (200, 310));
1220 beginTest (
"Layout calculation test: 2 columns x 3 rows: rowGap of 10 and columnGap of 20");
1221 expect (grid.items[0].currentBounds == Rect (0.0f, 0.0f, 130.0f, 20.0f));
1222 expect (grid.items[1].currentBounds == Rect (0.0f, 30.0f, 130.0f, 90.0f));
1223 expect (grid.items[2].currentBounds == Rect (150.0f, 0.0f, 50.0f, 20.0f));
1224 expect (grid.items[3].currentBounds == Rect (150.0f, 30.0f, 50.0f, 90.0f));
1225 expect (grid.items[4].currentBounds == Rect (0.0f, 130.0f, 130.0f, 180.0f));
1226 expect (grid.items[5].currentBounds == Rect (150.0f, 130.0f, 50.0f, 180.0f));
1232 grid.templateColumns.addArray ({ Tr (
"first", 20_px,
"in"), Tr (
"in", 1_fr,
"in"), Tr (20_px,
"last") });
1233 grid.templateRows.addArray ({ Tr (1_fr),
1237 beginTest (
"Grid items placement tests: integer and custom ident, counting forward");
1239 GridItem i1, i2, i3, i4, i5;
1240 i1.column = { 1, 4 };
1243 i2.column = { 1, 3 };
1246 i3.column = {
"first",
"in" };
1249 i4.column = {
"first", { 2,
"in" } };
1252 i5.column = {
"first",
"last" };
1255 grid.items.addArray ({ i1, i2, i3, i4, i5 });
1257 grid.performLayout ({ 140, 100 });
1259 expect (grid.items[0].currentBounds == Rect (0.0f, 0.0f, 140.0f, 80.0f));
1260 expect (grid.items[1].currentBounds == Rect (0.0f, 0.0f, 120.0f, 100.0f));
1261 expect (grid.items[2].currentBounds == Rect (0.0f, 80.0f, 20.0f, 20.0f));
1262 expect (grid.items[3].currentBounds == Rect (0.0f, 0.0f, 120.0f, 80.0f));
1263 expect (grid.items[4].currentBounds == Rect (0.0f, 0.0f, 140.0f, 80.0f));
1270 grid.templateColumns.addArray ({ Tr (
"first", 20_px,
"in"), Tr (
"in", 1_fr,
"in"), Tr (20_px,
"last") });
1271 grid.templateRows.addArray ({ Tr (1_fr),
1274 beginTest (
"Grid items placement tests: integer and custom ident, counting forward, reversed end and start");
1276 GridItem i1, i2, i3, i4, i5;
1277 i1.column = { 4, 1 };
1280 i2.column = { 3, 1 };
1283 i3.column = {
"in",
"first" };
1286 i4.column = {
"first", { 2,
"in" } };
1289 i5.column = {
"last",
"first" };
1292 grid.items.addArray ({ i1, i2, i3, i4, i5 });
1294 grid.performLayout ({ 140, 100 });
1296 expect (grid.items[0].currentBounds == Rect (0.0f, 0.0f, 140.0f, 80.0f));
1297 expect (grid.items[1].currentBounds == Rect (0.0f, 0.0f, 120.0f, 100.0f));
1298 expect (grid.items[2].currentBounds == Rect (0.0f, 80.0f, 20.0f, 20.0f));
1299 expect (grid.items[3].currentBounds == Rect (0.0f, 0.0f, 120.0f, 80.0f));
1300 expect (grid.items[4].currentBounds == Rect (0.0f, 0.0f, 140.0f, 80.0f));
1306 grid.templateColumns = { Tr (
"first", 20_px,
"in"), Tr (
"in", 1_fr,
"in"), Tr (20_px,
"last") };
1307 grid.templateRows = { Tr (1_fr), Tr (20_px) };
1309 beginTest (
"Grid items placement tests: integer, counting backward");
1311 grid.items = { GridItem{}.withColumn ({ -2, -1 }).withRow ({ 1, 3 }),
1312 GridItem{}.withColumn ({ -10, -1 }).withRow ({ 1, -1 }) };
1314 grid.performLayout ({ 140, 100 });
1316 expect (grid.items[0].currentBounds == Rect (120.0f, 0.0f, 20.0f, 100.0f));
1317 expect (grid.items[1].currentBounds == Rect (0.0f, 0.0f, 140.0f, 100.0f));
1321 beginTest (
"Grid items placement tests: areas");
1325 grid.templateColumns = { Tr (50_px), Tr (100_px), Tr (Fr (1_fr)), Tr (50_px) };
1326 grid.templateRows = { Tr (50_px),
1330 grid.templateAreas = {
"header header header header",
1331 "main main . sidebar",
1332 "footer footer footer footer" };
1334 grid.items.addArray ({ GridItem().withArea (
"header"),
1335 GridItem().withArea (
"main"),
1336 GridItem().withArea (
"sidebar"),
1337 GridItem().withArea (
"footer"),
1340 grid.performLayout ({ 300, 150 });
1342 expect (grid.items[0].currentBounds == Rect (0.f, 0.f, 300.f, 50.f));
1343 expect (grid.items[1].currentBounds == Rect (0.f, 50.f, 150.f, 50.f));
1344 expect (grid.items[2].currentBounds == Rect (250.f, 50.f, 50.f, 50.f));
1345 expect (grid.items[3].currentBounds == Rect (0.f, 100.f, 300.f, 50.f));
1349 beginTest (
"Grid implicit rows and columns: triggered by areas");
1353 grid.templateColumns = { Tr (50_px), Tr (100_px), Tr (1_fr), Tr (50_px) };
1354 grid.templateRows = { Tr (50_px),
1358 grid.autoRows = Tr (30_px);
1359 grid.autoColumns = Tr (30_px);
1361 grid.templateAreas = {
"header header header header header",
1362 "main main . sidebar sidebar",
1363 "footer footer footer footer footer",
1364 "sub sub sub sub sub"};
1366 grid.items.addArray ({ GridItem().withArea (
"header"),
1367 GridItem().withArea (
"main"),
1368 GridItem().withArea (
"sidebar"),
1369 GridItem().withArea (
"footer"),
1370 GridItem().withArea (
"sub"),
1373 grid.performLayout ({ 330, 180 });
1375 expect (grid.items[0].currentBounds == Rect (0.f, 0.f, 330.f, 50.f));
1376 expect (grid.items[1].currentBounds == Rect (0.f, 50.f, 150.f, 50.f));
1377 expect (grid.items[2].currentBounds == Rect (250.f, 50.f, 80.f, 50.f));
1378 expect (grid.items[3].currentBounds == Rect (0.f, 100.f, 330.f, 50.f));
1379 expect (grid.items[4].currentBounds == Rect (0.f, 150.f, 330.f, 30.f));
1383 beginTest (
"Grid implicit rows and columns: triggered by areas");
1387 grid.templateColumns = { Tr (50_px), Tr (100_px), Tr (1_fr), Tr (50_px) };
1388 grid.templateRows = { Tr (50_px),
1392 grid.autoRows = Tr (1_fr);
1393 grid.autoColumns = Tr (1_fr);
1395 grid.templateAreas = {
"header header header header",
1396 "main main . sidebar",
1397 "footer footer footer footer" };
1399 grid.items.addArray ({ GridItem().withArea (
"header"),
1400 GridItem().withArea (
"main"),
1401 GridItem().withArea (
"sidebar"),
1402 GridItem().withArea (
"footer"),
1403 GridItem().withArea (4, 5, 6, 7)
1406 grid.performLayout ({ 350, 250 });
1408 expect (grid.items[0].currentBounds == Rect (0.f, 0.f, 250.f, 50.f));
1409 expect (grid.items[1].currentBounds == Rect (0.f, 50.f, 150.f, 50.f));
1410 expect (grid.items[2].currentBounds == Rect (200.f, 50.f, 50.f, 50.f));
1411 expect (grid.items[3].currentBounds == Rect (0.f, 100.f, 250.f, 50.f));
1412 expect (grid.items[4].currentBounds == Rect (250.f, 150.f, 100.f, 100.f));
1416 beginTest (
"Grid implicit rows and columns: triggered by out-of-bounds indices");
1420 grid.templateColumns = { Tr (1_fr), Tr (1_fr) };
1421 grid.templateRows = { Tr (60_px), Tr (60_px) };
1423 grid.autoColumns = Tr (20_px);
1424 grid.autoRows = Tr (1_fr);
1426 grid.items = { GridItem{}.withColumn ({ 5, 8 }).withRow ({ -5, -4 }),
1427 GridItem{}.withColumn ({ 4, 7 }).withRow ({ -4, -3 }),
1428 GridItem{}.withColumn ({ -2, -1 }).withRow ({ 4, 5 }) };
1430 grid.performLayout ({ 500, 400 });
1460 expect (grid.items[0].currentBounds == Rect (440.0f, 0.0f, 60.0f, 70.0f));
1461 expect (grid.items[1].currentBounds == Rect (420.0f, 70.0f, 60.0f, 70.0f));
1462 expect (grid.items[2].currentBounds == Rect (200.0f, 330.0f, 200.0f, 70.0f));
1466 beginTest (
"Items with specified sizes should translate to correctly rounded Component dimensions");
1468 static constexpr int targetSize = 100;
1474 item.width = (
float) targetSize;
1475 item.height = (
float) targetSize;
1480 grid.
items = { item };
1482 for (
int totalSize = 100 - 20; totalSize < 100 + 20; ++totalSize)
1484 Rectangle<int> bounds { 0, 0, totalSize, totalSize };
1487 expectEquals (component.
getWidth(), targetSize);
1488 expectEquals (component.
getHeight(), targetSize);
1493 beginTest (
"Track sizes specified in Px should translate to correctly rounded Component dimensions");
1495 static constexpr int targetSize = 100;
1501 item.setArea (1, 3);
1509 grid.
items = { item };
1511 for (
int totalSize = 100 - 20; totalSize < 100 + 20; ++totalSize)
1513 Rectangle<int> bounds { 0, 0, totalSize, totalSize };
1516 expectEquals (component.
getWidth(), targetSize);
1521 beginTest (
"Evaluate invariants on randomised Grid layouts");
1528 Rectangle<int> bounds;
1531 auto createSolution = [
this] (
int numColumns,
1532 float probabilityOfFractionalColumn,
1533 Rectangle<int> bounds) -> Solution
1535 auto random = getRandom();
1541 const auto widthOfAbsolute = (
int) ((
float) bounds.getWidth() / (
float) (numColumns + 1));
1543 for (
int i = 0; i < numColumns; ++i)
1545 if (
random.nextFloat() < probabilityOfFractionalColumn)
1546 grid.templateColumns.add (Grid::Fr { 1 });
1548 grid.templateColumns.add (Grid::Px { widthOfAbsolute });
1553 for (
auto& c : itemComponents)
1554 grid.items.add (GridItem { c });
1556 grid.performLayout (bounds);
1558 return { std::move (grid), std::move (itemComponents), widthOfAbsolute, bounds };
1561 const auto getFractionalComponentWidths = [] (
const Solution& solution)
1565 for (
int i = 0; i < solution.grid.templateColumns.size(); ++i)
1566 if (solution.grid.templateColumns[i].isFractional())
1567 result.
push_back (solution.components[(
size_t) i].getWidth());
1572 const auto getAbsoluteComponentWidths = [] (
const Solution& solution)
1576 for (
int i = 0; i < solution.grid.templateColumns.size(); ++i)
1577 if (! solution.grid.templateColumns[i].isFractional())
1578 result.
push_back (solution.components[(
size_t) i].getWidth());
1583 const auto evaluateInvariants = [&] (
const Solution& solution)
1585 const auto fractionalWidths = getFractionalComponentWidths (solution);
1587 if (! fractionalWidths.empty())
1590 fractionalWidths.end());
1591 expectLessOrEqual (*max - *min, 1,
"Fr { 1 } items are expected to share the "
1592 "rounding errors equally and hence couldn't "
1593 "deviate in size by more than 1 px");
1596 const auto absoluteWidths = getAbsoluteComponentWidths (solution);
1598 for (
const auto& w : absoluteWidths)
1599 expectEquals (w, solution.absoluteWidth,
"Sizes specified in absolute dimensions should "
1602 Rectangle<int> unionOfComponentBounds;
1604 for (
const auto& c : solution.components)
1605 unionOfComponentBounds = unionOfComponentBounds.getUnion (c.getBoundsInParent());
1607 if ((
size_t) solution.grid.templateColumns.size() == absoluteWidths.size())
1608 expect (solution.bounds.contains (unionOfComponentBounds),
"Non-oversized absolute Components "
1609 "should never be placed outside the "
1610 "provided bounds.");
1612 expect (unionOfComponentBounds == solution.bounds,
"With fractional items, positioned items "
1613 "should cover the provided bounds exactly");
1616 const auto knownPreviousBad = createSolution (5, 1.0f, Rectangle<int> { 0, 0, 600, 200 }.reduced (16));
1617 evaluateInvariants (knownPreviousBad);
1619 auto random = getRandom();
1621 for (
int i = 0; i < 1000; ++i)
1623 const auto numColumns =
random.nextInt (Range<int> { 1, 26 });
1624 const auto probabilityOfFractionalColumn =
random.nextFloat();
1625 const auto bounds = Rectangle<int> {
random.nextInt (Range<int> { 0, 3 }),
1626 random.nextInt (Range<int> { 0, 3 }),
1627 random.nextInt (Range<int> { 300, 1200 }),
1628 random.nextInt (Range<int> { 100, 500 }) }
1629 .reduced (
random.nextInt (Range<int> { 0, 16 }));
1631 const auto randomSolution = createSolution (numColumns, probabilityOfFractionalColumn, bounds);
1632 evaluateInvariants (randomSolution);
1638static GridTests gridUnitTests;
Holds a resizable array of primitive or copy-by-value objects.
int size() const noexcept
Returns the current number of elements in the array.
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.
void add(const ElementType &newElement)
Appends a new element at the end of the array.
ElementType & getReference(int index) noexcept
Returns a direct reference to one of the elements in the array, without checking the index passed in.
Specifies a set of gaps to be left around the sides of a rectangle.
Rectangle< ValueType > subtractedFrom(const Rectangle< ValueType > &original) const noexcept
Returns a rectangle with these borders removed from it.
The base class for all JUCE user-interface objects.
int getHeight() const noexcept
Returns the component's height in pixels.
int getWidth() const noexcept
Returns the component's width in pixels.
Defines an item in a Grid.
@ autoValue
Follows the Grid container's alignItems property.
@ center
Content inside the item is aligned towards the center.
Margin margin
The margin to leave around this item.
JustifySelf justifySelf
This is the justify-self property of the item.
@ autoValue
Follows the Grid container's justifyItems property.
@ center
Content inside the item is justified towards the center.
AlignSelf alignSelf
This is the align-self property of the item.
Container that handles geometry for grid layouts (fixed columns and rows) using a set of declarative ...
void performLayout(Rectangle< int >)
Lays-out the grid's items within the given rectangle.
AutoFlow autoFlow
Specifies how the auto-placement algorithm places items.
JustifyItems justifyItems
Specifies the alignment of content inside the items along the rows.
Grid()=default
Creates an empty Grid container with default parameters.
JustifyItems
Possible values for the justifyItems property.
@ end
Content inside the item is justified towards the right.
@ center
Content inside the item is justified towards the center.
@ start
Content inside the item is justified towards the left.
Array< TrackInfo > templateRows
The set of row tracks to lay out.
Px columnGap
The gap in pixels between columns.
JustifyContent justifyContent
Specifies the alignment of items along the rows.
JustifyContent
Possible values for the justifyContent property.
@ end
Items are justified towards the right of the container.
@ center
Items are justified towards the center of the container.
@ spaceBetween
Items are evenly spaced along the row with spaces around them.
@ spaceAround
Items are evenly spaced along the row with spaces between them.
@ spaceEvenly
Items are evenly spaced along the row with even amount of spaces between them.
AlignContent
Possible values for the alignContent property.
@ end
Items are aligned towards the bottom of the container.
@ center
Items are aligned towards the center of the container.
@ spaceBetween
Items are evenly spaced along the column with spaces around them.
@ spaceAround
Items are evenly spaced along the column with spaces between them.
@ spaceEvenly
Items are evenly spaced along the column with even amount of spaces between them.
Array< TrackInfo > templateColumns
The set of column tracks to lay out.
AlignContent alignContent
Specifies the alignment of items along the columns.
AlignItems alignItems
Specifies the alignment of content inside the items along the columns.
Px rowGap
The gap in pixels between rows.
AlignItems
Possible values for the alignItems property.
@ end
Content inside the item is aligned towards the bottom.
@ center
Content inside the item is aligned towards the center.
@ start
Content inside the item is aligned towards the top.
AutoFlow
Possible values for the autoFlow property.
@ column
Fills the grid by adding columns of items.
@ rowDense
Fills the grid by adding rows of items and attempts to fill in gaps.
@ columnDense
Fills the grid by adding columns of items and attempts to fill in gaps.
@ row
Fills the grid by adding rows of items.
Array< GridItem > items
The set of items to lay-out.
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 getCentreX() const noexcept
Returns the x coordinate of the rectangle's centre.
ValueType getCentreY() const noexcept
Returns the y coordinate of the rectangle's centre.
ValueType getWidth() const noexcept
Returns the width of the rectangle.
void setWidth(ValueType newWidth) noexcept
Changes the rectangle's width.
ValueType getHeight() const noexcept
Returns the height of the rectangle.
A special array for holding a list of strings.
void add(String stringToAdd)
Appends a string at the end of the array.
This is a base class for classes that perform a unit test.
T minmax_element(T... args)
@ copy
The command ID that should be used to send a "Copy to clipboard" command.
constexpr auto enumerate(Range &&range, Index startingValue={})
Given a range and an optional starting offset, returns an IteratorPair that holds EnumerateIterators ...
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 Type jmax(Type a, Type b)
Returns the larger of two values.
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
Constrains a value to keep it within a given range.
constexpr auto makeRange(Begin begin, End end)
Given two iterators "begin" and "end", returns an IteratorPair with a member begin() and end() functi...
bool isPositiveAndBelow(Type1 valueToTest, Type2 upperLimit) noexcept
Returns true if a value is at least zero, and also below a specified upper limit.
RangedDirectoryIterator begin(const RangedDirectoryIterator &it)
Returns the iterator that was passed in.
A fractional ratio integer.
TrackInfo() noexcept
Creates a track with auto dimension.