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>
119 [] (
const auto& t) { return t.isFractional(); });
126 if (hasAnyFractions (tracks.columns.items))
136 if (hasAnyFractions (tracks.rows.items))
154 for (
int i =
trackItems.size() - 1; 0 <= i; --i)
175 if (! currentItem.isFractional())
183 if (currentItem.isFractional())
188 start =
end + roundingFunction (
static_cast<float> (
gap.pixels));
193 tracks.columns.items,
195 fractionallyDividedWidth,
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)
251 if (i > 1 && i <= tracks.
size())
262 if (i == tracks.
size())
276 static int deduceAbsoluteLineNumberFromLineName (GridItem::Property
prop,
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,
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();
328 const auto lines = getArrayOfLinesFromTracks (tracks);
335 for (
const auto& name : line.lineNames)
345 return (
int) index + 1;
365 static LineRange deduceLineRange (GridItem::StartAndEndProperty
prop,
const Array<TrackInfo>& tracks)
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())
424 return { deduceLineRange (item.column,
grid.templateColumns),
425 deduceLineRange (item.row,
grid.templateRows) };
436 if (strings.size() > 0)
438 for (
auto s : strings)
440 jassert (s.size() == strings[0].size());
456 if (area.name.isEmpty())
458 if (
string != emptyAreaCharacter)
462 area.lines.column.start =
stringArray.indexOf (
string) + 1;
465 area.lines.column.end =
stringArray.indexOf (
string) + 2;
468 string = emptyAreaCharacter;
473 if (
string == area.name)
476 area.lines.column.end =
stringArray.indexOf (
string) + 2;
479 string = emptyAreaCharacter;
497 if (
areas.count (area.name) == 0)
498 areas[area.name] = area.lines;
508 template <
typename RoundingFunction>
510 const Tracks& tracks,
528 template <
typename RoundingFunction>
529 static Rectangle<float> alignCell (Rectangle<float> area,
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);
551 area.setY (area.getY() + shift);
557 area.setX (area.getX() + shift);
563 area.setY (area.getY() + shift);
569 area.setX (area.getX() + shift);
578 area.setY (area.getY() + shift);
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,
604 return alignCell (cell,
607 tracks.columns.items.size(),
608 tracks.rows.items.size(),
632 struct Cell {
int column, row; };
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)
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;
728 if (isOccupied ({ cell.column + i, cell.row +
j }))
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; }
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));
850 [] (
const GridItem*
i1,
const GridItem*
i2) { return i1->order < i2->order; });
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 });
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 }
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 }
904 if (hasAutoPlacement (*item))
905 plane.updateMaxCrossDimensionFromAutoPlacementItem (getSpanFromAuto (item->
column), getSpanFromAuto (item->
row));
911 if (hasAutoPlacement (*item))
913 const auto columnSpan = getSpanFromAuto (item->column);
914 const auto rowSpan = getSpanFromAuto (item->row);
919 if (! hasDenseAutoFlow (
grid.autoFlow))
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)
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)
977 static void applySizeForAutoTracks (Tracks& tracks,
const ItemPlacementArray&
placements)
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;
1002 [] (
const auto& i) { return i.row; },
1003 [] (
const auto& i) { return i.height + i.margin.top + i.margin.bottom; });
1006 [] (
const auto& i) { return i.column; },
1007 [] (
const auto& i) { return i.width + i.margin.left + i.margin.right; });
1027 .subtractedFrom (area);
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) {}
1135 const auto areaBounds = Helpers::PlacementHelpers::getAreaBounds (a.column,
1142 const auto rounded = [&] (
auto rect) ->
decltype (rect)
1150 return rounded (Helpers::BoxAlignment::alignItem (*item, *
this,
areaBounds));
1155 if (
auto* c = item->associatedComponent)
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);
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.
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.
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.
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...
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
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.