Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

juce_Grid.cpp 53KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365
  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. struct AllTracksIncludingImplicit
  21. {
  22. Array<Grid::TrackInfo> items;
  23. int numImplicitLeading; // The number of implicit items before the explicit items
  24. };
  25. struct Tracks
  26. {
  27. AllTracksIncludingImplicit columns, rows;
  28. };
  29. struct Grid::SizeCalculation
  30. {
  31. static float getTotalAbsoluteSize (const Array<TrackInfo>& tracks, Px gapSize) noexcept
  32. {
  33. float totalCellSize = 0.0f;
  34. for (const auto& trackInfo : tracks)
  35. if (! trackInfo.isFractional() || trackInfo.isAuto())
  36. totalCellSize += trackInfo.getSize();
  37. float totalGap = tracks.size() > 1 ? static_cast<float> ((tracks.size() - 1) * gapSize.pixels)
  38. : 0.0f;
  39. return totalCellSize + totalGap;
  40. }
  41. static float getRelativeUnitSize (float size, float totalAbsolute, const Array<TrackInfo>& tracks) noexcept
  42. {
  43. const float totalRelative = jlimit (0.0f, size, size - totalAbsolute);
  44. float factorsSum = 0.0f;
  45. for (const auto& trackInfo : tracks)
  46. if (trackInfo.isFractional())
  47. factorsSum += trackInfo.getSize();
  48. jassert (factorsSum != 0.0f);
  49. return totalRelative / factorsSum;
  50. }
  51. //==============================================================================
  52. static float getTotalAbsoluteHeight (const Array<TrackInfo>& rowTracks, Px rowGap)
  53. {
  54. return getTotalAbsoluteSize (rowTracks, rowGap);
  55. }
  56. static float getTotalAbsoluteWidth (const Array<TrackInfo>& columnTracks, Px columnGap)
  57. {
  58. return getTotalAbsoluteSize (columnTracks, columnGap);
  59. }
  60. static float getRelativeWidthUnit (float gridWidth, Px columnGap, const Array<TrackInfo>& columnTracks)
  61. {
  62. return getRelativeUnitSize (gridWidth, getTotalAbsoluteWidth (columnTracks, columnGap), columnTracks);
  63. }
  64. static float getRelativeHeightUnit (float gridHeight, Px rowGap, const Array<TrackInfo>& rowTracks)
  65. {
  66. return getRelativeUnitSize (gridHeight, getTotalAbsoluteHeight (rowTracks, rowGap), rowTracks);
  67. }
  68. //==============================================================================
  69. static bool hasAnyFractions (const Array<TrackInfo>& tracks)
  70. {
  71. return std::any_of (tracks.begin(),
  72. tracks.end(),
  73. [] (const auto& t) { return t.isFractional(); });
  74. }
  75. void computeSizes (float gridWidth, float gridHeight,
  76. Px columnGapToUse, Px rowGapToUse,
  77. const Tracks& tracks)
  78. {
  79. if (hasAnyFractions (tracks.columns.items))
  80. relativeWidthUnit = getRelativeWidthUnit (gridWidth, columnGapToUse, tracks.columns.items);
  81. else
  82. remainingWidth = gridWidth - getTotalAbsoluteSize (tracks.columns.items, columnGapToUse);
  83. if (hasAnyFractions (tracks.rows.items))
  84. relativeHeightUnit = getRelativeHeightUnit (gridHeight, rowGapToUse, tracks.rows.items);
  85. else
  86. remainingHeight = gridHeight - getTotalAbsoluteSize (tracks.rows.items, rowGapToUse);
  87. }
  88. float relativeWidthUnit = 0.0f;
  89. float relativeHeightUnit = 0.0f;
  90. float remainingWidth = 0.0f;
  91. float remainingHeight = 0.0f;
  92. };
  93. //==============================================================================
  94. struct Grid::PlacementHelpers
  95. {
  96. enum { invalid = -999999 };
  97. static constexpr auto emptyAreaCharacter = ".";
  98. //==============================================================================
  99. struct LineRange { int start, end; };
  100. struct LineArea { LineRange column, row; };
  101. struct LineInfo { StringArray lineNames; };
  102. struct NamedArea
  103. {
  104. String name;
  105. LineArea lines;
  106. };
  107. //==============================================================================
  108. static Array<LineInfo> getArrayOfLinesFromTracks (const Array<TrackInfo>& tracks)
  109. {
  110. // fill line info array
  111. Array<LineInfo> lines;
  112. for (int i = 1; i <= tracks.size(); ++i)
  113. {
  114. const auto& currentTrack = tracks.getReference (i - 1);
  115. if (i == 1) // start line
  116. {
  117. LineInfo li;
  118. li.lineNames.add (currentTrack.getStartLineName());
  119. lines.add (li);
  120. }
  121. if (i > 1 && i <= tracks.size()) // two lines in between tracks
  122. {
  123. const auto& prevTrack = tracks.getReference (i - 2);
  124. LineInfo li;
  125. li.lineNames.add (prevTrack.getEndLineName());
  126. li.lineNames.add (currentTrack.getStartLineName());
  127. lines.add (li);
  128. }
  129. if (i == tracks.size()) // end line
  130. {
  131. LineInfo li;
  132. li.lineNames.add (currentTrack.getEndLineName());
  133. lines.add (li);
  134. }
  135. }
  136. jassert (lines.size() == tracks.size() + 1);
  137. return lines;
  138. }
  139. //==============================================================================
  140. static int deduceAbsoluteLineNumberFromLineName (GridItem::Property prop,
  141. const Array<TrackInfo>& tracks)
  142. {
  143. jassert (prop.hasAbsolute());
  144. const auto lines = getArrayOfLinesFromTracks (tracks);
  145. int count = 0;
  146. for (int i = 0; i < lines.size(); i++)
  147. {
  148. for (const auto& name : lines.getReference (i).lineNames)
  149. {
  150. if (prop.getName() == name)
  151. {
  152. ++count;
  153. break;
  154. }
  155. }
  156. if (count == prop.getNumber())
  157. return i + 1;
  158. }
  159. jassertfalse;
  160. return count;
  161. }
  162. static int deduceAbsoluteLineNumber (GridItem::Property prop,
  163. const Array<TrackInfo>& tracks)
  164. {
  165. jassert (prop.hasAbsolute());
  166. if (prop.hasName())
  167. return deduceAbsoluteLineNumberFromLineName (prop, tracks);
  168. if (prop.getNumber() > 0)
  169. return prop.getNumber();
  170. if (prop.getNumber() < 0)
  171. return tracks.size() + 2 + prop.getNumber();
  172. // An integer value of 0 is invalid
  173. jassertfalse;
  174. return 1;
  175. }
  176. static int deduceAbsoluteLineNumberFromNamedSpan (int startLineNumber,
  177. GridItem::Property propertyWithSpan,
  178. const Array<TrackInfo>& tracks)
  179. {
  180. jassert (propertyWithSpan.hasSpan());
  181. const auto lines = getArrayOfLinesFromTracks (tracks);
  182. int count = 0;
  183. for (int i = startLineNumber; i < lines.size(); i++)
  184. {
  185. for (const auto& name : lines.getReference (i).lineNames)
  186. {
  187. if (propertyWithSpan.getName() == name)
  188. {
  189. ++count;
  190. break;
  191. }
  192. }
  193. if (count == propertyWithSpan.getNumber())
  194. return i + 1;
  195. }
  196. jassertfalse;
  197. return count;
  198. }
  199. static int deduceAbsoluteLineNumberBasedOnSpan (int startLineNumber,
  200. GridItem::Property propertyWithSpan,
  201. const Array<TrackInfo>& tracks)
  202. {
  203. jassert (propertyWithSpan.hasSpan());
  204. if (propertyWithSpan.hasName())
  205. return deduceAbsoluteLineNumberFromNamedSpan (startLineNumber, propertyWithSpan, tracks);
  206. return startLineNumber + propertyWithSpan.getNumber();
  207. }
  208. //==============================================================================
  209. static LineRange deduceLineRange (GridItem::StartAndEndProperty prop, const Array<TrackInfo>& tracks)
  210. {
  211. jassert (! (prop.start.hasAuto() && prop.end.hasAuto()));
  212. if (prop.start.hasAbsolute() && prop.end.hasAuto())
  213. {
  214. prop.end = GridItem::Span (1);
  215. }
  216. else if (prop.start.hasAuto() && prop.end.hasAbsolute())
  217. {
  218. prop.start = GridItem::Span (1);
  219. }
  220. auto s = [&]() -> LineRange
  221. {
  222. if (prop.start.hasAbsolute() && prop.end.hasAbsolute())
  223. {
  224. return { deduceAbsoluteLineNumber (prop.start, tracks),
  225. deduceAbsoluteLineNumber (prop.end, tracks) };
  226. }
  227. if (prop.start.hasAbsolute() && prop.end.hasSpan())
  228. {
  229. const auto start = deduceAbsoluteLineNumber (prop.start, tracks);
  230. return { start, deduceAbsoluteLineNumberBasedOnSpan (start, prop.end, tracks) };
  231. }
  232. if (prop.start.hasSpan() && prop.end.hasAbsolute())
  233. {
  234. const auto start = deduceAbsoluteLineNumber (prop.end, tracks);
  235. return { start, deduceAbsoluteLineNumberBasedOnSpan (start, prop.start, tracks) };
  236. }
  237. // Can't have an item with spans on both start and end.
  238. jassertfalse;
  239. return {};
  240. }();
  241. // swap if start overtakes end
  242. if (s.start > s.end)
  243. std::swap (s.start, s.end);
  244. else if (s.start == s.end)
  245. s.end = s.start + 1;
  246. return s;
  247. }
  248. static LineArea deduceLineArea (const GridItem& item,
  249. const Grid& grid,
  250. const std::map<String, LineArea>& namedAreas)
  251. {
  252. if (item.area.isNotEmpty() && ! grid.templateAreas.isEmpty())
  253. {
  254. // Must be a named area!
  255. jassert (namedAreas.count (item.area) != 0);
  256. return namedAreas.at (item.area);
  257. }
  258. return { deduceLineRange (item.column, grid.templateColumns),
  259. deduceLineRange (item.row, grid.templateRows) };
  260. }
  261. //==============================================================================
  262. static Array<StringArray> parseAreasProperty (const StringArray& areasStrings)
  263. {
  264. Array<StringArray> strings;
  265. for (const auto& areaString : areasStrings)
  266. strings.add (StringArray::fromTokens (areaString, false));
  267. if (strings.size() > 0)
  268. {
  269. for (auto s : strings)
  270. {
  271. jassert (s.size() == strings[0].size()); // all rows must have the same number of columns
  272. }
  273. }
  274. return strings;
  275. }
  276. static NamedArea findArea (Array<StringArray>& stringsArrays)
  277. {
  278. NamedArea area;
  279. for (auto& stringArray : stringsArrays)
  280. {
  281. for (auto& string : stringArray)
  282. {
  283. // find anchor
  284. if (area.name.isEmpty())
  285. {
  286. if (string != emptyAreaCharacter)
  287. {
  288. area.name = string;
  289. area.lines.row.start = stringsArrays.indexOf (stringArray) + 1; // non-zero indexed;
  290. area.lines.column.start = stringArray.indexOf (string) + 1; // non-zero indexed;
  291. area.lines.row.end = stringsArrays.indexOf (stringArray) + 2;
  292. area.lines.column.end = stringArray.indexOf (string) + 2;
  293. // mark as visited
  294. string = emptyAreaCharacter;
  295. }
  296. }
  297. else
  298. {
  299. if (string == area.name)
  300. {
  301. area.lines.row.end = stringsArrays.indexOf (stringArray) + 2;
  302. area.lines.column.end = stringArray.indexOf (string) + 2;
  303. // mark as visited
  304. string = emptyAreaCharacter;
  305. }
  306. }
  307. }
  308. }
  309. return area;
  310. }
  311. //==============================================================================
  312. static std::map<String, LineArea> deduceNamedAreas (const StringArray& areasStrings)
  313. {
  314. auto stringsArrays = parseAreasProperty (areasStrings);
  315. std::map<String, LineArea> areas;
  316. for (auto area = findArea (stringsArrays); area.name.isNotEmpty(); area = findArea (stringsArrays))
  317. {
  318. if (areas.count (area.name) == 0)
  319. areas[area.name] = area.lines;
  320. else
  321. // Make sure your template-areas property only has one area with the same name and is well-formed
  322. jassertfalse;
  323. }
  324. return areas;
  325. }
  326. //==============================================================================
  327. static float getCoord (int trackNumber, float relativeUnit, Px gap, const Array<TrackInfo>& tracks)
  328. {
  329. float c = 0;
  330. for (const auto* it = tracks.begin(); it != tracks.begin() + trackNumber; ++it)
  331. c += it->getAbsoluteSize (relativeUnit) + static_cast<float> (gap.pixels);
  332. return c;
  333. }
  334. static Rectangle<float> getCellBounds (int columnNumber, int rowNumber,
  335. const Tracks& tracks,
  336. SizeCalculation calculation,
  337. Px columnGap, Px rowGap)
  338. {
  339. const auto correctedColumn = columnNumber - 1 + tracks.columns.numImplicitLeading;
  340. const auto correctedRow = rowNumber - 1 + tracks.rows .numImplicitLeading;
  341. jassert (isPositiveAndBelow (correctedColumn, tracks.columns.items.size()));
  342. jassert (isPositiveAndBelow (correctedRow, tracks.rows .items.size()));
  343. return { getCoord (correctedColumn, calculation.relativeWidthUnit, columnGap, tracks.columns.items),
  344. getCoord (correctedRow, calculation.relativeHeightUnit, rowGap, tracks.rows .items),
  345. tracks.columns.items.getReference (correctedColumn).getAbsoluteSize (calculation.relativeWidthUnit),
  346. tracks.rows .items.getReference (correctedRow) .getAbsoluteSize (calculation.relativeHeightUnit) };
  347. }
  348. static Rectangle<float> alignCell (Rectangle<float> area,
  349. int columnNumber, int rowNumber,
  350. int numberOfColumns, int numberOfRows,
  351. SizeCalculation calculation,
  352. AlignContent alignContent,
  353. JustifyContent justifyContent)
  354. {
  355. if (alignContent == AlignContent::end)
  356. area.setY (area.getY() + calculation.remainingHeight);
  357. if (justifyContent == JustifyContent::end)
  358. area.setX (area.getX() + calculation.remainingWidth);
  359. if (alignContent == AlignContent::center)
  360. area.setY (area.getY() + calculation.remainingHeight / 2);
  361. if (justifyContent == JustifyContent::center)
  362. area.setX (area.getX() + calculation.remainingWidth / 2);
  363. if (alignContent == AlignContent::spaceBetween)
  364. {
  365. const auto shift = ((float) (rowNumber - 1) * (calculation.remainingHeight / float(numberOfRows - 1)));
  366. area.setY (area.getY() + shift);
  367. }
  368. if (justifyContent == JustifyContent::spaceBetween)
  369. {
  370. const auto shift = ((float) (columnNumber - 1) * (calculation.remainingWidth / float(numberOfColumns - 1)));
  371. area.setX (area.getX() + shift);
  372. }
  373. if (alignContent == AlignContent::spaceEvenly)
  374. {
  375. const auto shift = ((float) rowNumber * (calculation.remainingHeight / float(numberOfRows + 1)));
  376. area.setY (area.getY() + shift);
  377. }
  378. if (justifyContent == JustifyContent::spaceEvenly)
  379. {
  380. const auto shift = ((float) columnNumber * (calculation.remainingWidth / float(numberOfColumns + 1)));
  381. area.setX (area.getX() + shift);
  382. }
  383. if (alignContent == AlignContent::spaceAround)
  384. {
  385. const auto inbetweenShift = calculation.remainingHeight / float(numberOfRows);
  386. const auto sidesShift = inbetweenShift / 2;
  387. auto shift = (float) (rowNumber - 1) * inbetweenShift + sidesShift;
  388. area.setY (area.getY() + shift);
  389. }
  390. if (justifyContent == JustifyContent::spaceAround)
  391. {
  392. const auto inbetweenShift = calculation.remainingWidth / float(numberOfColumns);
  393. const auto sidesShift = inbetweenShift / 2;
  394. auto shift = (float) (columnNumber - 1) * inbetweenShift + sidesShift;
  395. area.setX (area.getX() + shift);
  396. }
  397. return area;
  398. }
  399. static Rectangle<float> getAreaBounds (PlacementHelpers::LineRange columnRange,
  400. PlacementHelpers::LineRange rowRange,
  401. const Tracks& tracks,
  402. SizeCalculation calculation,
  403. AlignContent alignContent,
  404. JustifyContent justifyContent,
  405. Px columnGap, Px rowGap)
  406. {
  407. const auto findAlignedCell = [&] (int column, int row)
  408. {
  409. const auto cell = getCellBounds (column, row, tracks, calculation, columnGap, rowGap);
  410. return alignCell (cell,
  411. column,
  412. row,
  413. tracks.columns.items.size(),
  414. tracks.rows.items.size(),
  415. calculation,
  416. alignContent,
  417. justifyContent);
  418. };
  419. const auto startCell = findAlignedCell (columnRange.start, rowRange.start);
  420. const auto endCell = findAlignedCell (columnRange.end - 1, rowRange.end - 1);
  421. const auto horizontalRange = startCell.getHorizontalRange().getUnionWith (endCell.getHorizontalRange());
  422. const auto verticalRange = startCell.getVerticalRange() .getUnionWith (endCell.getVerticalRange());
  423. return { horizontalRange.getStart(), verticalRange.getStart(),
  424. horizontalRange.getLength(), verticalRange.getLength() };
  425. }
  426. };
  427. template <typename Item>
  428. static Array<Item> operator+ (const Array<Item>& a, const Array<Item>& b)
  429. {
  430. auto copy = a;
  431. copy.addArray (b);
  432. return copy;
  433. }
  434. //==============================================================================
  435. struct Grid::AutoPlacement
  436. {
  437. using ItemPlacementArray = Array<std::pair<GridItem*, PlacementHelpers::LineArea>>;
  438. //==============================================================================
  439. struct OccupancyPlane
  440. {
  441. struct Cell { int column, row; };
  442. OccupancyPlane (int highestColumnToUse, int highestRowToUse, bool isColumnFirst)
  443. : highestCrossDimension (isColumnFirst ? highestRowToUse : highestColumnToUse),
  444. columnFirst (isColumnFirst)
  445. {}
  446. PlacementHelpers::LineArea setCell (Cell cell, int columnSpan, int rowSpan)
  447. {
  448. for (int i = 0; i < columnSpan; i++)
  449. for (int j = 0; j < rowSpan; j++)
  450. setCell (cell.column + i, cell.row + j);
  451. return { { cell.column, cell.column + columnSpan }, { cell.row, cell.row + rowSpan } };
  452. }
  453. PlacementHelpers::LineArea setCell (Cell start, Cell end)
  454. {
  455. return setCell (start, std::abs (end.column - start.column),
  456. std::abs (end.row - start.row));
  457. }
  458. Cell nextAvailable (Cell referenceCell, int columnSpan, int rowSpan)
  459. {
  460. while (isOccupied (referenceCell, columnSpan, rowSpan) || isOutOfBounds (referenceCell, columnSpan, rowSpan))
  461. referenceCell = advance (referenceCell);
  462. return referenceCell;
  463. }
  464. Cell nextAvailableOnRow (Cell referenceCell, int columnSpan, int rowSpan, int rowNumber)
  465. {
  466. if (columnFirst && (rowNumber + rowSpan) > highestCrossDimension)
  467. highestCrossDimension = rowNumber + rowSpan;
  468. while (isOccupied (referenceCell, columnSpan, rowSpan)
  469. || (referenceCell.row != rowNumber))
  470. referenceCell = advance (referenceCell);
  471. return referenceCell;
  472. }
  473. Cell nextAvailableOnColumn (Cell referenceCell, int columnSpan, int rowSpan, int columnNumber)
  474. {
  475. if (! columnFirst && (columnNumber + columnSpan) > highestCrossDimension)
  476. highestCrossDimension = columnNumber + columnSpan;
  477. while (isOccupied (referenceCell, columnSpan, rowSpan)
  478. || (referenceCell.column != columnNumber))
  479. referenceCell = advance (referenceCell);
  480. return referenceCell;
  481. }
  482. void updateMaxCrossDimensionFromAutoPlacementItem (int columnSpan, int rowSpan)
  483. {
  484. highestCrossDimension = jmax (highestCrossDimension, 1 + getCrossDimension ({ columnSpan, rowSpan }));
  485. }
  486. private:
  487. struct SortableCell
  488. {
  489. int column, row;
  490. bool columnFirst;
  491. bool operator< (const SortableCell& other) const
  492. {
  493. if (columnFirst)
  494. {
  495. if (row == other.row)
  496. return column < other.column;
  497. return row < other.row;
  498. }
  499. if (row == other.row)
  500. return column < other.column;
  501. return row < other.row;
  502. }
  503. };
  504. void setCell (int column, int row)
  505. {
  506. occupiedCells.insert ({ column, row, columnFirst });
  507. }
  508. bool isOccupied (Cell cell) const
  509. {
  510. return occupiedCells.count ({ cell.column, cell.row, columnFirst }) > 0;
  511. }
  512. bool isOccupied (Cell cell, int columnSpan, int rowSpan) const
  513. {
  514. for (int i = 0; i < columnSpan; i++)
  515. for (int j = 0; j < rowSpan; j++)
  516. if (isOccupied ({ cell.column + i, cell.row + j }))
  517. return true;
  518. return false;
  519. }
  520. bool isOutOfBounds (Cell cell, int columnSpan, int rowSpan) const
  521. {
  522. const auto highestIndexOfCell = getCrossDimension (cell) + getCrossDimension ({ columnSpan, rowSpan });
  523. const auto highestIndexOfGrid = getHighestCrossDimension();
  524. return highestIndexOfGrid < highestIndexOfCell;
  525. }
  526. int getHighestCrossDimension() const
  527. {
  528. Cell cell { 1, 1 };
  529. if (occupiedCells.size() > 0)
  530. cell = { occupiedCells.crbegin()->column, occupiedCells.crbegin()->row };
  531. return std::max (getCrossDimension (cell), highestCrossDimension);
  532. }
  533. Cell advance (Cell cell) const
  534. {
  535. if ((getCrossDimension (cell) + 1) >= getHighestCrossDimension())
  536. return fromDimensions (getMainDimension (cell) + 1, 1);
  537. return fromDimensions (getMainDimension (cell), getCrossDimension (cell) + 1);
  538. }
  539. int getMainDimension (Cell cell) const { return columnFirst ? cell.column : cell.row; }
  540. int getCrossDimension (Cell cell) const { return columnFirst ? cell.row : cell.column; }
  541. Cell fromDimensions (int mainDimension, int crossDimension) const
  542. {
  543. if (columnFirst)
  544. return { mainDimension, crossDimension };
  545. return { crossDimension, mainDimension };
  546. }
  547. int highestCrossDimension;
  548. bool columnFirst;
  549. std::set<SortableCell> occupiedCells;
  550. };
  551. //==============================================================================
  552. static bool isFixed (GridItem::StartAndEndProperty prop)
  553. {
  554. return prop.start.hasName() || prop.start.hasAbsolute() || prop.end.hasName() || prop.end.hasAbsolute();
  555. }
  556. static bool hasFullyFixedPlacement (const GridItem& item)
  557. {
  558. if (item.area.isNotEmpty())
  559. return true;
  560. if (isFixed (item.column) && isFixed (item.row))
  561. return true;
  562. return false;
  563. }
  564. static bool hasPartialFixedPlacement (const GridItem& item)
  565. {
  566. if (item.area.isNotEmpty())
  567. return false;
  568. if (isFixed (item.column) ^ isFixed (item.row))
  569. return true;
  570. return false;
  571. }
  572. static bool hasAutoPlacement (const GridItem& item)
  573. {
  574. return ! hasFullyFixedPlacement (item) && ! hasPartialFixedPlacement (item);
  575. }
  576. //==============================================================================
  577. static bool hasDenseAutoFlow (AutoFlow autoFlow)
  578. {
  579. return autoFlow == AutoFlow::columnDense
  580. || autoFlow == AutoFlow::rowDense;
  581. }
  582. static bool isColumnAutoFlow (AutoFlow autoFlow)
  583. {
  584. return autoFlow == AutoFlow::column
  585. || autoFlow == AutoFlow::columnDense;
  586. }
  587. //==============================================================================
  588. static int getSpanFromAuto (GridItem::StartAndEndProperty prop)
  589. {
  590. if (prop.end.hasSpan())
  591. return prop.end.getNumber();
  592. if (prop.start.hasSpan())
  593. return prop.start.getNumber();
  594. return 1;
  595. }
  596. //==============================================================================
  597. ItemPlacementArray deduceAllItems (Grid& grid) const
  598. {
  599. const auto namedAreas = PlacementHelpers::deduceNamedAreas (grid.templateAreas);
  600. OccupancyPlane plane (jmax (grid.templateColumns.size() + 1, 2),
  601. jmax (grid.templateRows.size() + 1, 2),
  602. isColumnAutoFlow (grid.autoFlow));
  603. ItemPlacementArray itemPlacementArray;
  604. Array<GridItem*> sortedItems;
  605. for (auto& item : grid.items)
  606. sortedItems.add (&item);
  607. std::stable_sort (sortedItems.begin(), sortedItems.end(),
  608. [] (const GridItem* i1, const GridItem* i2) { return i1->order < i2->order; });
  609. // place fixed items first
  610. for (auto* item : sortedItems)
  611. {
  612. if (hasFullyFixedPlacement (*item))
  613. {
  614. const auto a = PlacementHelpers::deduceLineArea (*item, grid, namedAreas);
  615. plane.setCell ({ a.column.start, a.row.start }, { a.column.end, a.row.end });
  616. itemPlacementArray.add ({ item, a });
  617. }
  618. }
  619. OccupancyPlane::Cell lastInsertionCell = { 1, 1 };
  620. for (auto* item : sortedItems)
  621. {
  622. if (hasPartialFixedPlacement (*item))
  623. {
  624. if (isFixed (item->column))
  625. {
  626. const auto p = PlacementHelpers::deduceLineRange (item->column, grid.templateColumns);
  627. const auto columnSpan = std::abs (p.start - p.end);
  628. const auto rowSpan = getSpanFromAuto (item->row);
  629. const auto insertionCell = hasDenseAutoFlow (grid.autoFlow) ? OccupancyPlane::Cell { p.start, 1 }
  630. : lastInsertionCell;
  631. const auto nextAvailableCell = plane.nextAvailableOnColumn (insertionCell, columnSpan, rowSpan, p.start);
  632. const auto lineArea = plane.setCell (nextAvailableCell, columnSpan, rowSpan);
  633. lastInsertionCell = nextAvailableCell;
  634. itemPlacementArray.add ({ item, lineArea });
  635. }
  636. else if (isFixed (item->row))
  637. {
  638. const auto p = PlacementHelpers::deduceLineRange (item->row, grid.templateRows);
  639. const auto columnSpan = getSpanFromAuto (item->column);
  640. const auto rowSpan = std::abs (p.start - p.end);
  641. const auto insertionCell = hasDenseAutoFlow (grid.autoFlow) ? OccupancyPlane::Cell { 1, p.start }
  642. : lastInsertionCell;
  643. const auto nextAvailableCell = plane.nextAvailableOnRow (insertionCell, columnSpan, rowSpan, p.start);
  644. const auto lineArea = plane.setCell (nextAvailableCell, columnSpan, rowSpan);
  645. lastInsertionCell = nextAvailableCell;
  646. itemPlacementArray.add ({ item, lineArea });
  647. }
  648. }
  649. }
  650. // https://www.w3.org/TR/css-grid-1/#auto-placement-algo step 3.3
  651. for (auto* item : sortedItems)
  652. if (hasAutoPlacement (*item))
  653. plane.updateMaxCrossDimensionFromAutoPlacementItem (getSpanFromAuto (item->column), getSpanFromAuto (item->row));
  654. lastInsertionCell = { 1, 1 };
  655. for (auto* item : sortedItems)
  656. {
  657. if (hasAutoPlacement (*item))
  658. {
  659. const auto columnSpan = getSpanFromAuto (item->column);
  660. const auto rowSpan = getSpanFromAuto (item->row);
  661. const auto nextAvailableCell = plane.nextAvailable (lastInsertionCell, columnSpan, rowSpan);
  662. const auto lineArea = plane.setCell (nextAvailableCell, columnSpan, rowSpan);
  663. if (! hasDenseAutoFlow (grid.autoFlow))
  664. lastInsertionCell = nextAvailableCell;
  665. itemPlacementArray.add ({ item, lineArea });
  666. }
  667. }
  668. return itemPlacementArray;
  669. }
  670. //==============================================================================
  671. template <typename Accessor>
  672. static PlacementHelpers::LineRange findFullLineRange (const ItemPlacementArray& items, Accessor&& accessor)
  673. {
  674. if (items.isEmpty())
  675. return { 1, 1 };
  676. const auto combine = [&accessor] (const auto& acc, const auto& item)
  677. {
  678. const auto newRange = accessor (item);
  679. return PlacementHelpers::LineRange { std::min (acc.start, newRange.start),
  680. std::max (acc.end, newRange.end) };
  681. };
  682. return std::accumulate (std::next (items.begin()), items.end(), accessor (*items.begin()), combine);
  683. }
  684. static PlacementHelpers::LineArea findFullLineArea (const ItemPlacementArray& items)
  685. {
  686. return { findFullLineRange (items, [] (const auto& item) { return item.second.column; }),
  687. findFullLineRange (items, [] (const auto& item) { return item.second.row; }) };
  688. }
  689. template <typename Item>
  690. static Array<Item> repeated (int repeats, const Item& item)
  691. {
  692. Array<Item> result;
  693. result.insertMultiple (-1, item, repeats);
  694. return result;
  695. }
  696. static Tracks createImplicitTracks (const Grid& grid, const ItemPlacementArray& items)
  697. {
  698. const auto fullArea = findFullLineArea (items);
  699. const auto leadingColumns = std::max (0, 1 - fullArea.column.start);
  700. const auto leadingRows = std::max (0, 1 - fullArea.row.start);
  701. const auto trailingColumns = std::max (0, fullArea.column.end - grid.templateColumns.size() - 1);
  702. const auto trailingRows = std::max (0, fullArea.row .end - grid.templateRows .size() - 1);
  703. return { { repeated (leadingColumns, grid.autoColumns) + grid.templateColumns + repeated (trailingColumns, grid.autoColumns),
  704. leadingColumns },
  705. { repeated (leadingRows, grid.autoRows) + grid.templateRows + repeated (trailingRows, grid.autoRows),
  706. leadingRows } };
  707. }
  708. //==============================================================================
  709. static void applySizeForAutoTracks (Tracks& tracks, const ItemPlacementArray& placements)
  710. {
  711. const auto setSizes = [&placements] (auto& tracksInDirection, const auto& getItem, const auto& getItemSize)
  712. {
  713. auto& array = tracksInDirection.items;
  714. for (int index = 0; index < array.size(); ++index)
  715. {
  716. if (array.getReference (index).isAuto())
  717. {
  718. const auto combiner = [&] (const auto acc, const auto& element)
  719. {
  720. const auto item = getItem (element.second);
  721. const auto isNotSpan = std::abs (item.end - item.start) <= 1;
  722. return isNotSpan && item.start == index + 1 - tracksInDirection.numImplicitLeading
  723. ? std::max (acc, getItemSize (*element.first))
  724. : acc;
  725. };
  726. array.getReference (index).size = std::accumulate (placements.begin(), placements.end(), 0.0f, combiner);
  727. }
  728. }
  729. };
  730. setSizes (tracks.rows,
  731. [] (const auto& i) { return i.row; },
  732. [] (const auto& i) { return i.height + i.margin.top + i.margin.bottom; });
  733. setSizes (tracks.columns,
  734. [] (const auto& i) { return i.column; },
  735. [] (const auto& i) { return i.width + i.margin.left + i.margin.right; });
  736. }
  737. };
  738. //==============================================================================
  739. struct Grid::BoxAlignment
  740. {
  741. static Rectangle<float> alignItem (const GridItem& item,
  742. const Grid& grid,
  743. Rectangle<float> area)
  744. {
  745. // if item align is auto, inherit value from grid
  746. const auto alignType = item.alignSelf == GridItem::AlignSelf::autoValue
  747. ? grid.alignItems
  748. : static_cast<AlignItems> (item.alignSelf);
  749. const auto justifyType = item.justifySelf == GridItem::JustifySelf::autoValue
  750. ? grid.justifyItems
  751. : static_cast<JustifyItems> (item.justifySelf);
  752. // subtract margin from area
  753. area = BorderSize<float> (item.margin.top, item.margin.left, item.margin.bottom, item.margin.right)
  754. .subtractedFrom (area);
  755. // align and justify
  756. auto r = area;
  757. if (item.width != (float) GridItem::notAssigned) r.setWidth (item.width);
  758. if (item.height != (float) GridItem::notAssigned) r.setHeight (item.height);
  759. if (item.maxWidth != (float) GridItem::notAssigned) r.setWidth (jmin (item.maxWidth, r.getWidth()));
  760. if (item.minWidth > 0.0f) r.setWidth (jmax (item.minWidth, r.getWidth()));
  761. if (item.maxHeight != (float) GridItem::notAssigned) r.setHeight (jmin (item.maxHeight, r.getHeight()));
  762. if (item.minHeight > 0.0f) r.setHeight (jmax (item.minHeight, r.getHeight()));
  763. if (alignType == AlignItems::start && justifyType == JustifyItems::start)
  764. return r;
  765. if (alignType == AlignItems::end) r.setY (r.getY() + (area.getHeight() - r.getHeight()));
  766. if (justifyType == JustifyItems::end) r.setX (r.getX() + (area.getWidth() - r.getWidth()));
  767. if (alignType == AlignItems::center) r.setCentre (r.getCentreX(), area.getCentreY());
  768. if (justifyType == JustifyItems::center) r.setCentre (area.getCentreX(), r.getCentreY());
  769. return r;
  770. }
  771. };
  772. //==============================================================================
  773. Grid::TrackInfo::TrackInfo() noexcept : hasKeyword (true) {}
  774. Grid::TrackInfo::TrackInfo (Px sizeInPixels) noexcept
  775. : size (static_cast<float> (sizeInPixels.pixels)), isFraction (false) {}
  776. Grid::TrackInfo::TrackInfo (Fr fractionOfFreeSpace) noexcept
  777. : size ((float)fractionOfFreeSpace.fraction), isFraction (true) {}
  778. Grid::TrackInfo::TrackInfo (Px sizeInPixels, const String& endLineNameToUse) noexcept
  779. : TrackInfo (sizeInPixels)
  780. {
  781. endLineName = endLineNameToUse;
  782. }
  783. Grid::TrackInfo::TrackInfo (Fr fractionOfFreeSpace, const String& endLineNameToUse) noexcept
  784. : TrackInfo (fractionOfFreeSpace)
  785. {
  786. endLineName = endLineNameToUse;
  787. }
  788. Grid::TrackInfo::TrackInfo (const String& startLineNameToUse, Px sizeInPixels) noexcept
  789. : TrackInfo (sizeInPixels)
  790. {
  791. startLineName = startLineNameToUse;
  792. }
  793. Grid::TrackInfo::TrackInfo (const String& startLineNameToUse, Fr fractionOfFreeSpace) noexcept
  794. : TrackInfo (fractionOfFreeSpace)
  795. {
  796. startLineName = startLineNameToUse;
  797. }
  798. Grid::TrackInfo::TrackInfo (const String& startLineNameToUse, Px sizeInPixels, const String& endLineNameToUse) noexcept
  799. : TrackInfo (startLineNameToUse, sizeInPixels)
  800. {
  801. endLineName = endLineNameToUse;
  802. }
  803. Grid::TrackInfo::TrackInfo (const String& startLineNameToUse, Fr fractionOfFreeSpace, const String& endLineNameToUse) noexcept
  804. : TrackInfo (startLineNameToUse, fractionOfFreeSpace)
  805. {
  806. endLineName = endLineNameToUse;
  807. }
  808. float Grid::TrackInfo::getAbsoluteSize (float relativeFractionalUnit) const
  809. {
  810. return isFractional() ? size * relativeFractionalUnit : size;
  811. }
  812. //==============================================================================
  813. void Grid::performLayout (Rectangle<int> targetArea)
  814. {
  815. const auto itemsAndAreas = AutoPlacement().deduceAllItems (*this);
  816. auto implicitTracks = AutoPlacement::createImplicitTracks (*this, itemsAndAreas);
  817. AutoPlacement::applySizeForAutoTracks (implicitTracks, itemsAndAreas);
  818. SizeCalculation calculation;
  819. calculation.computeSizes (targetArea.toFloat().getWidth(),
  820. targetArea.toFloat().getHeight(),
  821. columnGap,
  822. rowGap,
  823. implicitTracks);
  824. for (auto& itemAndArea : itemsAndAreas)
  825. {
  826. const auto a = itemAndArea.second;
  827. const auto areaBounds = PlacementHelpers::getAreaBounds (a.column,
  828. a.row,
  829. implicitTracks,
  830. calculation,
  831. alignContent,
  832. justifyContent,
  833. columnGap,
  834. rowGap);
  835. auto* item = itemAndArea.first;
  836. item->currentBounds = BoxAlignment::alignItem (*item, *this, areaBounds)
  837. + targetArea.toFloat().getPosition();
  838. if (auto* c = item->associatedComponent)
  839. c->setBounds (item->currentBounds.toNearestIntEdges());
  840. }
  841. }
  842. //==============================================================================
  843. #if JUCE_UNIT_TESTS
  844. struct GridTests : public UnitTest
  845. {
  846. GridTests()
  847. : UnitTest ("Grid", UnitTestCategories::gui)
  848. {}
  849. void runTest() override
  850. {
  851. using Fr = Grid::Fr;
  852. using Tr = Grid::TrackInfo;
  853. using Rect = Rectangle<float>;
  854. beginTest ("Layout calculation of an empty grid is a no-op");
  855. {
  856. const Rectangle<int> bounds { 100, 200 };
  857. Grid grid;
  858. grid.performLayout (bounds);
  859. }
  860. {
  861. Grid grid;
  862. grid.templateColumns.add (Tr (1_fr));
  863. grid.templateRows.addArray ({ Tr (20_px), Tr (1_fr) });
  864. grid.items.addArray ({ GridItem().withArea (1, 1),
  865. GridItem().withArea (2, 1) });
  866. grid.performLayout (Rectangle<int> (200, 400));
  867. beginTest ("Layout calculation test: 1 column x 2 rows: no gap");
  868. expect (grid.items[0].currentBounds == Rect (0.0f, 0.0f, 200.f, 20.0f));
  869. expect (grid.items[1].currentBounds == Rect (0.0f, 20.0f, 200.f, 380.0f));
  870. grid.templateColumns.add (Tr (50_px));
  871. grid.templateRows.add (Tr (2_fr));
  872. grid.items.addArray ( { GridItem().withArea (1, 2),
  873. GridItem().withArea (2, 2),
  874. GridItem().withArea (3, 1),
  875. GridItem().withArea (3, 2) });
  876. grid.performLayout (Rectangle<int> (150, 170));
  877. beginTest ("Layout calculation test: 2 columns x 3 rows: no gap");
  878. expect (grid.items[0].currentBounds == Rect (0.0f, 0.0f, 100.0f, 20.0f));
  879. expect (grid.items[1].currentBounds == Rect (0.0f, 20.0f, 100.0f, 50.0f));
  880. expect (grid.items[2].currentBounds == Rect (100.0f, 0.0f, 50.0f, 20.0f));
  881. expect (grid.items[3].currentBounds == Rect (100.0f, 20.0f, 50.0f, 50.0f));
  882. expect (grid.items[4].currentBounds == Rect (0.0f, 70.0f, 100.0f, 100.0f));
  883. expect (grid.items[5].currentBounds == Rect (100.0f, 70.0f, 50.0f, 100.0f));
  884. grid.columnGap = 20_px;
  885. grid.rowGap = 10_px;
  886. grid.performLayout (Rectangle<int> (200, 310));
  887. beginTest ("Layout calculation test: 2 columns x 3 rows: rowGap of 10 and columnGap of 20");
  888. expect (grid.items[0].currentBounds == Rect (0.0f, 0.0f, 130.0f, 20.0f));
  889. expect (grid.items[1].currentBounds == Rect (0.0f, 30.0f, 130.0f, 90.0f));
  890. expect (grid.items[2].currentBounds == Rect (150.0f, 0.0f, 50.0f, 20.0f));
  891. expect (grid.items[3].currentBounds == Rect (150.0f, 30.0f, 50.0f, 90.0f));
  892. expect (grid.items[4].currentBounds == Rect (0.0f, 130.0f, 130.0f, 180.0f));
  893. expect (grid.items[5].currentBounds == Rect (150.0f, 130.0f, 50.0f, 180.0f));
  894. }
  895. {
  896. Grid grid;
  897. grid.templateColumns.addArray ({ Tr ("first", 20_px, "in"), Tr ("in", 1_fr, "in"), Tr (20_px, "last") });
  898. grid.templateRows.addArray ({ Tr (1_fr),
  899. Tr (20_px)});
  900. {
  901. beginTest ("Grid items placement tests: integer and custom ident, counting forward");
  902. GridItem i1, i2, i3, i4, i5;
  903. i1.column = { 1, 4 };
  904. i1.row = { 1, 2 };
  905. i2.column = { 1, 3 };
  906. i2.row = { 1, 3 };
  907. i3.column = { "first", "in" };
  908. i3.row = { 2, 3 };
  909. i4.column = { "first", { 2, "in" } };
  910. i4.row = { 1, 2 };
  911. i5.column = { "first", "last" };
  912. i5.row = { 1, 2 };
  913. grid.items.addArray ({ i1, i2, i3, i4, i5 });
  914. grid.performLayout ({ 140, 100 });
  915. expect (grid.items[0].currentBounds == Rect (0.0f, 0.0f, 140.0f, 80.0f));
  916. expect (grid.items[1].currentBounds == Rect (0.0f, 0.0f, 120.0f, 100.0f));
  917. expect (grid.items[2].currentBounds == Rect (0.0f, 80.0f, 20.0f, 20.0f));
  918. expect (grid.items[3].currentBounds == Rect (0.0f, 0.0f, 120.0f, 80.0f));
  919. expect (grid.items[4].currentBounds == Rect (0.0f, 0.0f, 140.0f, 80.0f));
  920. }
  921. }
  922. {
  923. Grid grid;
  924. grid.templateColumns.addArray ({ Tr ("first", 20_px, "in"), Tr ("in", 1_fr, "in"), Tr (20_px, "last") });
  925. grid.templateRows.addArray ({ Tr (1_fr),
  926. Tr (20_px)});
  927. beginTest ("Grid items placement tests: integer and custom ident, counting forward, reversed end and start");
  928. GridItem i1, i2, i3, i4, i5;
  929. i1.column = { 4, 1 };
  930. i1.row = { 2, 1 };
  931. i2.column = { 3, 1 };
  932. i2.row = { 3, 1 };
  933. i3.column = { "in", "first" };
  934. i3.row = { 3, 2 };
  935. i4.column = { "first", { 2, "in" } };
  936. i4.row = { 1, 2 };
  937. i5.column = { "last", "first" };
  938. i5.row = { 1, 2 };
  939. grid.items.addArray ({ i1, i2, i3, i4, i5 });
  940. grid.performLayout ({ 140, 100 });
  941. expect (grid.items[0].currentBounds == Rect (0.0f, 0.0f, 140.0f, 80.0f));
  942. expect (grid.items[1].currentBounds == Rect (0.0f, 0.0f, 120.0f, 100.0f));
  943. expect (grid.items[2].currentBounds == Rect (0.0f, 80.0f, 20.0f, 20.0f));
  944. expect (grid.items[3].currentBounds == Rect (0.0f, 0.0f, 120.0f, 80.0f));
  945. expect (grid.items[4].currentBounds == Rect (0.0f, 0.0f, 140.0f, 80.0f));
  946. }
  947. {
  948. Grid grid;
  949. grid.templateColumns = { Tr ("first", 20_px, "in"), Tr ("in", 1_fr, "in"), Tr (20_px, "last") };
  950. grid.templateRows = { Tr (1_fr), Tr (20_px) };
  951. beginTest ("Grid items placement tests: integer, counting backward");
  952. grid.items = { GridItem{}.withColumn ({ -2, -1 }).withRow ({ 1, 3 }),
  953. GridItem{}.withColumn ({ -10, -1 }).withRow ({ 1, -1 }) };
  954. grid.performLayout ({ 140, 100 });
  955. expect (grid.items[0].currentBounds == Rect (120.0f, 0.0f, 20.0f, 100.0f));
  956. expect (grid.items[1].currentBounds == Rect (0.0f, 0.0f, 140.0f, 100.0f));
  957. }
  958. {
  959. beginTest ("Grid items placement tests: areas");
  960. Grid grid;
  961. grid.templateColumns = { Tr (50_px), Tr (100_px), Tr (Fr (1_fr)), Tr (50_px) };
  962. grid.templateRows = { Tr (50_px),
  963. Tr (1_fr),
  964. Tr (50_px) };
  965. grid.templateAreas = { "header header header header",
  966. "main main . sidebar",
  967. "footer footer footer footer" };
  968. grid.items.addArray ({ GridItem().withArea ("header"),
  969. GridItem().withArea ("main"),
  970. GridItem().withArea ("sidebar"),
  971. GridItem().withArea ("footer"),
  972. });
  973. grid.performLayout ({ 300, 150 });
  974. expect (grid.items[0].currentBounds == Rect (0.f, 0.f, 300.f, 50.f));
  975. expect (grid.items[1].currentBounds == Rect (0.f, 50.f, 150.f, 50.f));
  976. expect (grid.items[2].currentBounds == Rect (250.f, 50.f, 50.f, 50.f));
  977. expect (grid.items[3].currentBounds == Rect (0.f, 100.f, 300.f, 50.f));
  978. }
  979. {
  980. beginTest ("Grid implicit rows and columns: triggered by areas");
  981. Grid grid;
  982. grid.templateColumns = { Tr (50_px), Tr (100_px), Tr (1_fr), Tr (50_px) };
  983. grid.templateRows = { Tr (50_px),
  984. Tr (1_fr),
  985. Tr (50_px) };
  986. grid.autoRows = Tr (30_px);
  987. grid.autoColumns = Tr (30_px);
  988. grid.templateAreas = { "header header header header header",
  989. "main main . sidebar sidebar",
  990. "footer footer footer footer footer",
  991. "sub sub sub sub sub"};
  992. grid.items.addArray ({ GridItem().withArea ("header"),
  993. GridItem().withArea ("main"),
  994. GridItem().withArea ("sidebar"),
  995. GridItem().withArea ("footer"),
  996. GridItem().withArea ("sub"),
  997. });
  998. grid.performLayout ({ 330, 180 });
  999. expect (grid.items[0].currentBounds == Rect (0.f, 0.f, 330.f, 50.f));
  1000. expect (grid.items[1].currentBounds == Rect (0.f, 50.f, 150.f, 50.f));
  1001. expect (grid.items[2].currentBounds == Rect (250.f, 50.f, 80.f, 50.f));
  1002. expect (grid.items[3].currentBounds == Rect (0.f, 100.f, 330.f, 50.f));
  1003. expect (grid.items[4].currentBounds == Rect (0.f, 150.f, 330.f, 30.f));
  1004. }
  1005. {
  1006. beginTest ("Grid implicit rows and columns: triggered by areas");
  1007. Grid grid;
  1008. grid.templateColumns = { Tr (50_px), Tr (100_px), Tr (1_fr), Tr (50_px) };
  1009. grid.templateRows = { Tr (50_px),
  1010. Tr (1_fr),
  1011. Tr (50_px) };
  1012. grid.autoRows = Tr (1_fr);
  1013. grid.autoColumns = Tr (1_fr);
  1014. grid.templateAreas = { "header header header header",
  1015. "main main . sidebar",
  1016. "footer footer footer footer" };
  1017. grid.items.addArray ({ GridItem().withArea ("header"),
  1018. GridItem().withArea ("main"),
  1019. GridItem().withArea ("sidebar"),
  1020. GridItem().withArea ("footer"),
  1021. GridItem().withArea (4, 5, 6, 7)
  1022. });
  1023. grid.performLayout ({ 350, 250 });
  1024. expect (grid.items[0].currentBounds == Rect (0.f, 0.f, 250.f, 50.f));
  1025. expect (grid.items[1].currentBounds == Rect (0.f, 50.f, 150.f, 50.f));
  1026. expect (grid.items[2].currentBounds == Rect (200.f, 50.f, 50.f, 50.f));
  1027. expect (grid.items[3].currentBounds == Rect (0.f, 100.f, 250.f, 50.f));
  1028. expect (grid.items[4].currentBounds == Rect (250.f, 150.f, 100.f, 100.f));
  1029. }
  1030. {
  1031. beginTest ("Grid implicit rows and columns: triggered by out-of-bounds indices");
  1032. Grid grid;
  1033. grid.templateColumns = { Tr (1_fr), Tr (1_fr) };
  1034. grid.templateRows = { Tr (60_px), Tr (60_px) };
  1035. grid.autoColumns = Tr (20_px);
  1036. grid.autoRows = Tr (1_fr);
  1037. grid.items = { GridItem{}.withColumn ({ 5, 8 }).withRow ({ -5, -4 }),
  1038. GridItem{}.withColumn ({ 4, 7 }).withRow ({ -4, -3 }),
  1039. GridItem{}.withColumn ({ -2, -1 }).withRow ({ 4, 5 }) };
  1040. grid.performLayout ({ 500, 400 });
  1041. // -3 -2 -1
  1042. // 1 2 3 4 5 6 7 8
  1043. // -5 +---+---+---+---+---+---+---+ 0
  1044. // | | | | | 0 | 0 | 0 |
  1045. // -4 +---+---+---+---+---+---+---+ 70
  1046. // | | | | 1 | 1 | 1 | |
  1047. // -3 1 +---+---+---+---+---+---+---+ 140
  1048. // | x | x | | | | | |
  1049. // -2 2 +---+---+---+---+---+---+---+ 200 y positions
  1050. // | x | x | | | | | |
  1051. // -1 3 +---+---+---+---+---+---+---+ 260
  1052. // | | | | | | | |
  1053. // 4 +---+---+---+---+---+---+---+ 330
  1054. // | | 2 | | | | | |
  1055. // 5 +---+---+---+---+---+---+---+ 400
  1056. //
  1057. // 0 200 400 420 440 460 480 500
  1058. // x positions
  1059. //
  1060. // The cells marked "x" are the explicit cells specified by the template rows
  1061. // and columns.
  1062. //
  1063. // The cells marked 0/1/2 correspond to the GridItems at those indices in the
  1064. // items array.
  1065. //
  1066. // Note that negative indices count back from the last explicit line
  1067. // number in that direction, so "2" and "-2" both correspond to the same line.
  1068. expect (grid.items[0].currentBounds == Rect (440.0f, 0.0f, 60.0f, 70.0f));
  1069. expect (grid.items[1].currentBounds == Rect (420.0f, 70.0f, 60.0f, 70.0f));
  1070. expect (grid.items[2].currentBounds == Rect (200.0f, 330.0f, 200.0f, 70.0f));
  1071. }
  1072. }
  1073. };
  1074. static GridTests gridUnitTests;
  1075. #endif
  1076. } // namespace juce