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.

1263 lines
49KB

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