The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

1026 lines
39KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  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 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. struct Grid::SizeCalculation
  20. {
  21. static float getTotalAbsoluteSize (const juce::Array<Grid::TrackInfo>& tracks, Px gapSize) noexcept
  22. {
  23. float totalCellSize = 0.0f;
  24. for (const auto& trackInfo : tracks)
  25. if (! trackInfo.isFraction || trackInfo.hasKeyword)
  26. totalCellSize += trackInfo.size;
  27. float totalGap = tracks.size() > 1 ? static_cast<float> ((tracks.size() - 1) * gapSize.pixels)
  28. : 0.0f;
  29. return totalCellSize + totalGap;
  30. }
  31. static float getRelativeUnitSize (float size, float totalAbsolute, const juce::Array<Grid::TrackInfo>& tracks) noexcept
  32. {
  33. const float totalRelative = juce::jlimit (0.0f, size, size - totalAbsolute);
  34. float factorsSum = 0.0f;
  35. for (const auto& trackInfo : tracks)
  36. if (trackInfo.isFraction)
  37. factorsSum += trackInfo.size;
  38. jassert (factorsSum != 0.0f);
  39. return totalRelative / factorsSum;
  40. }
  41. //==============================================================================
  42. static float getTotalAbsoluteHeight (const juce::Array<Grid::TrackInfo>& rowTracks, Px rowGap)
  43. {
  44. return getTotalAbsoluteSize (rowTracks, rowGap);
  45. }
  46. static float getTotalAbsoluteWidth (const juce::Array<Grid::TrackInfo>& columnTracks, Px columnGap)
  47. {
  48. return getTotalAbsoluteSize (columnTracks, columnGap);
  49. }
  50. static float getRelativeWidthUnit (float gridWidth, Px columnGap, const juce::Array<Grid::TrackInfo>& columnTracks)
  51. {
  52. return getRelativeUnitSize (gridWidth, getTotalAbsoluteWidth (columnTracks, columnGap), columnTracks);
  53. }
  54. static float getRelativeHeightUnit (float gridHeight, Px rowGap, const juce::Array<Grid::TrackInfo>& rowTracks)
  55. {
  56. return getRelativeUnitSize (gridHeight, getTotalAbsoluteHeight (rowTracks, rowGap), rowTracks);
  57. }
  58. //==============================================================================
  59. static bool hasAnyFractions (const juce::Array<Grid::TrackInfo>& tracks)
  60. {
  61. for (auto& t : tracks)
  62. if (t.isFraction)
  63. return true;
  64. return false;
  65. }
  66. void computeSizes (float gridWidth, float gridHeight,
  67. Px columnGapToUse, Px rowGapToUse,
  68. const juce::Array<Grid::TrackInfo>& columnTracks,
  69. const juce::Array<Grid::TrackInfo>& rowTracks)
  70. {
  71. if (hasAnyFractions (columnTracks))
  72. relativeWidthUnit = getRelativeWidthUnit (gridWidth, columnGapToUse, columnTracks);
  73. else
  74. remainingWidth = gridWidth - getTotalAbsoluteSize (columnTracks, columnGapToUse);
  75. if (hasAnyFractions (rowTracks))
  76. relativeHeightUnit = getRelativeHeightUnit (gridHeight, rowGapToUse, rowTracks);
  77. else
  78. remainingHeight = gridHeight - getTotalAbsoluteSize (rowTracks, rowGapToUse);
  79. }
  80. float relativeWidthUnit = 0.0f;
  81. float relativeHeightUnit = 0.0f;
  82. float remainingWidth = 0.0f;
  83. float remainingHeight = 0.0f;
  84. };
  85. //==============================================================================
  86. struct Grid::PlacementHelpers
  87. {
  88. enum { invalid = -999999 };
  89. static constexpr auto emptyAreaCharacter = ".";
  90. //==============================================================================
  91. struct LineRange { int start, end; };
  92. struct LineArea { LineRange column, row; };
  93. struct LineInfo { juce::StringArray lineNames; };
  94. struct NamedArea
  95. {
  96. juce::String name;
  97. LineArea lines;
  98. };
  99. //==============================================================================
  100. static juce::Array<LineInfo> getArrayOfLinesFromTracks (const juce::Array<Grid::TrackInfo>& tracks)
  101. {
  102. // fill line info array
  103. juce::Array<LineInfo> lines;
  104. for (int i = 1; i <= tracks.size(); ++i)
  105. {
  106. const auto& currentTrack = tracks.getReference (i - 1);
  107. if (i == 1) // start line
  108. {
  109. LineInfo li;
  110. li.lineNames.add (currentTrack.startLineName);
  111. lines.add (li);
  112. }
  113. if (i > 1 && i <= tracks.size()) // two lines in between tracks
  114. {
  115. const auto& prevTrack = tracks.getReference (i - 2);
  116. LineInfo li;
  117. li.lineNames.add (prevTrack.endLineName);
  118. li.lineNames.add (currentTrack.startLineName);
  119. lines.add (li);
  120. }
  121. if (i == tracks.size()) // end line
  122. {
  123. LineInfo li;
  124. li.lineNames.add (currentTrack.endLineName);
  125. lines.add (li);
  126. }
  127. }
  128. jassert (lines.size() == tracks.size() + 1);
  129. return lines;
  130. }
  131. //==============================================================================
  132. static int deduceAbsoluteLineNumberFromLineName (GridItem::Property prop,
  133. const juce::Array<Grid::TrackInfo>& tracks)
  134. {
  135. jassert (prop.hasAbsolute());
  136. const auto lines = getArrayOfLinesFromTracks (tracks);
  137. int count = 0;
  138. for (int i = 0; i < lines.size(); i++)
  139. {
  140. for (const auto& name : lines.getReference (i).lineNames)
  141. {
  142. if (prop.name == name)
  143. {
  144. ++count;
  145. break;
  146. }
  147. }
  148. if (count == prop.number)
  149. return i + 1;
  150. }
  151. jassertfalse;
  152. return count;
  153. }
  154. static int deduceAbsoluteLineNumber (GridItem::Property prop,
  155. const juce::Array<Grid::TrackInfo>& tracks)
  156. {
  157. jassert (prop.hasAbsolute());
  158. if (prop.hasName())
  159. return deduceAbsoluteLineNumberFromLineName (prop, tracks);
  160. return prop.number > 0 ? prop.number : tracks.size() + 2 + prop.number;
  161. }
  162. static int deduceAbsoluteLineNumberFromNamedSpan (int startLineNumber,
  163. GridItem::Property propertyWithSpan,
  164. const juce::Array<Grid::TrackInfo>& tracks)
  165. {
  166. jassert (propertyWithSpan.hasSpan());
  167. const auto lines = getArrayOfLinesFromTracks (tracks);
  168. int count = 0;
  169. for (int i = startLineNumber; i < lines.size(); i++)
  170. {
  171. for (const auto& name : lines.getReference (i).lineNames)
  172. {
  173. if (propertyWithSpan.name == name)
  174. {
  175. ++count;
  176. break;
  177. }
  178. }
  179. if (count == propertyWithSpan.number)
  180. return i + 1;
  181. }
  182. jassertfalse;
  183. return count;
  184. }
  185. static int deduceAbsoluteLineNumberBasedOnSpan (int startLineNumber,
  186. GridItem::Property propertyWithSpan,
  187. const juce::Array<Grid::TrackInfo>& tracks)
  188. {
  189. jassert (propertyWithSpan.hasSpan());
  190. if (propertyWithSpan.hasName())
  191. return deduceAbsoluteLineNumberFromNamedSpan (startLineNumber, propertyWithSpan, tracks);
  192. return startLineNumber + propertyWithSpan.number;
  193. }
  194. //==============================================================================
  195. static LineRange deduceLineRange (GridItem::StartAndEndProperty prop, const juce::Array<Grid::TrackInfo>& tracks)
  196. {
  197. LineRange s;
  198. jassert (! (prop.start.hasAuto() && prop.end.hasAuto()));
  199. if (prop.start.hasAbsolute() && prop.end.hasAuto())
  200. {
  201. prop.end = GridItem::Span (1);
  202. }
  203. else if (prop.start.hasAuto() && prop.end.hasAbsolute())
  204. {
  205. prop.start = GridItem::Span (1);
  206. }
  207. if (prop.start.hasAbsolute() && prop.end.hasAbsolute())
  208. {
  209. s.start = deduceAbsoluteLineNumber (prop.start, tracks);
  210. s.end = deduceAbsoluteLineNumber (prop.end, tracks);
  211. }
  212. else if (prop.start.hasAbsolute() && prop.end.hasSpan())
  213. {
  214. s.start = deduceAbsoluteLineNumber (prop.start, tracks);
  215. s.end = deduceAbsoluteLineNumberBasedOnSpan (s.start, prop.end, tracks);
  216. }
  217. else if (prop.start.hasSpan() && prop.end.hasAbsolute())
  218. {
  219. s.start = deduceAbsoluteLineNumber (prop.end, tracks);
  220. s.end = deduceAbsoluteLineNumberBasedOnSpan (s.start, prop.start, tracks);
  221. }
  222. else
  223. {
  224. // Can't have an item with spans on both start and end.
  225. jassertfalse;
  226. s.start = s.end = {};
  227. }
  228. // swap if start overtakes end
  229. if (s.start > s.end)
  230. std::swap (s.start, s.end);
  231. else if (s.start == s.end)
  232. s.end = s.start + 1;
  233. return s;
  234. }
  235. static LineArea deduceLineArea (const GridItem& item,
  236. const Grid& grid,
  237. const std::map<juce::String, LineArea>& namedAreas)
  238. {
  239. if (item.area.isNotEmpty() && ! grid.templateAreas.isEmpty())
  240. return namedAreas.at (item.area);
  241. return { deduceLineRange (item.column, grid.templateColumns),
  242. deduceLineRange (item.row, grid.templateRows) };
  243. }
  244. //==============================================================================
  245. static juce::Array<juce::StringArray> parseAreasProperty (const juce::StringArray& areasStrings)
  246. {
  247. juce::Array<juce::StringArray> strings;
  248. for (const auto& areaString : areasStrings)
  249. strings.add (juce::StringArray::fromTokens (areaString, false));
  250. if (strings.size() > 0)
  251. for (auto s : strings)
  252. jassert (s.size() == strings[0].size()); // all rows must have the same number of columns
  253. return strings;
  254. }
  255. static NamedArea findArea (juce::Array<juce::StringArray>& stringsArrays)
  256. {
  257. NamedArea area;
  258. for (auto& stringArray : stringsArrays)
  259. {
  260. for (auto& string : stringArray)
  261. {
  262. // find anchor
  263. if (area.name.isEmpty())
  264. {
  265. if (string != emptyAreaCharacter)
  266. {
  267. area.name = string;
  268. area.lines.row.start = stringsArrays.indexOf (stringArray) + 1; // non-zero indexed;
  269. area.lines.column.start = stringArray.indexOf (string) + 1; // non-zero indexed;
  270. area.lines.row.end = stringsArrays.indexOf (stringArray) + 2;
  271. area.lines.column.end = stringArray.indexOf (string) + 2;
  272. // mark as visited
  273. string = emptyAreaCharacter;
  274. }
  275. }
  276. else
  277. {
  278. if (string == emptyAreaCharacter)
  279. {
  280. break;
  281. }
  282. else 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<juce::String, LineArea> deduceNamedAreas (const juce::StringArray& areasStrings)
  296. {
  297. auto stringsArrays = parseAreasProperty (areasStrings);
  298. std::map<juce::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 juce::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->isFraction ? it->size * relativeUnit : it->size) + static_cast<float> (gap.pixels);
  315. return c;
  316. }
  317. static juce::Rectangle<float> getCellBounds (int columnNumber, int rowNumber,
  318. const juce::Array<Grid::TrackInfo>& columnTracks,
  319. const juce::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.isFraction ? columnTrackInfo.size * calculation.relativeWidthUnit
  329. : columnTrackInfo.size;
  330. const auto& rowTrackInfo = rowTracks.getReference (rowNumber - 1);
  331. const float height = rowTrackInfo.isFraction ? rowTrackInfo.size * calculation.relativeHeightUnit
  332. : rowTrackInfo.size;
  333. return { x, y, width, height };
  334. }
  335. static juce::Rectangle<float> alignCell (juce::Rectangle<float> area,
  336. int columnNumber, int rowNumber,
  337. int numberOfColumns, int numberOfRows,
  338. Grid::SizeCalculation calculation,
  339. Grid::AlignContent alignContent,
  340. Grid::JustifyContent justifyContent)
  341. {
  342. if (alignContent == Grid::AlignContent::end)
  343. area.setY (area.getY() + calculation.remainingHeight);
  344. if (justifyContent == Grid::JustifyContent::end)
  345. area.setX (area.getX() + calculation.remainingWidth);
  346. if (alignContent == Grid::AlignContent::center)
  347. area.setY (area.getY() + calculation.remainingHeight / 2);
  348. if (justifyContent == Grid::JustifyContent::center)
  349. area.setX (area.getX() + calculation.remainingWidth / 2);
  350. if (alignContent == Grid::AlignContent::spaceBetween)
  351. {
  352. const auto shift = ((rowNumber - 1) * (calculation.remainingHeight / float(numberOfRows - 1)));
  353. area.setY (area.getY() + shift);
  354. }
  355. if (justifyContent == Grid::JustifyContent::spaceBetween)
  356. {
  357. const auto shift = ((columnNumber - 1) * (calculation.remainingWidth / float(numberOfColumns - 1)));
  358. area.setX (area.getX() + shift);
  359. }
  360. if (alignContent == Grid::AlignContent::spaceEvenly)
  361. {
  362. const auto shift = (rowNumber * (calculation.remainingHeight / float(numberOfRows + 1)));
  363. area.setY (area.getY() + shift);
  364. }
  365. if (justifyContent == Grid::JustifyContent::spaceEvenly)
  366. {
  367. const auto shift = (columnNumber * (calculation.remainingWidth / float(numberOfColumns + 1)));
  368. area.setX (area.getX() + shift);
  369. }
  370. if (alignContent == Grid::AlignContent::spaceAround)
  371. {
  372. const auto inbetweenShift = calculation.remainingHeight / float(numberOfRows);
  373. const auto sidesShift = inbetweenShift / 2;
  374. auto shift = (rowNumber - 1) * inbetweenShift + sidesShift;
  375. area.setY (area.getY() + shift);
  376. }
  377. if (justifyContent == Grid::JustifyContent::spaceAround)
  378. {
  379. const auto inbetweenShift = calculation.remainingWidth / float(numberOfColumns);
  380. const auto sidesShift = inbetweenShift / 2;
  381. auto shift = (columnNumber - 1) * inbetweenShift + sidesShift;
  382. area.setX (area.getX() + shift);
  383. }
  384. return area;
  385. }
  386. static juce::Rectangle<float> getAreaBounds (int columnLineNumberStart, int columnLineNumberEnd,
  387. int rowLineNumberStart, int rowLineNumberEnd,
  388. const juce::Array<Grid::TrackInfo>& columnTracks,
  389. const juce::Array<Grid::TrackInfo>& rowTracks,
  390. Grid::SizeCalculation calculation,
  391. Grid::AlignContent alignContent,
  392. Grid::JustifyContent justifyContent,
  393. Px columnGap, Px rowGap)
  394. {
  395. auto startCell = getCellBounds (columnLineNumberStart, rowLineNumberStart,
  396. columnTracks, rowTracks,
  397. calculation,
  398. columnGap, rowGap);
  399. auto endCell = getCellBounds (columnLineNumberEnd - 1, rowLineNumberEnd - 1,
  400. columnTracks, rowTracks,
  401. calculation,
  402. columnGap, rowGap);
  403. startCell = alignCell (startCell,
  404. columnLineNumberStart, rowLineNumberStart,
  405. columnTracks.size(), rowTracks.size(),
  406. calculation,
  407. alignContent,
  408. justifyContent);
  409. endCell = alignCell (endCell,
  410. columnLineNumberEnd - 1, rowLineNumberEnd - 1,
  411. columnTracks.size(), rowTracks.size(),
  412. calculation,
  413. alignContent,
  414. justifyContent);
  415. return startCell.getUnion (endCell);
  416. }
  417. };
  418. //==============================================================================
  419. struct Grid::AutoPlacement
  420. {
  421. using ItemPlacementArray = juce::Array<std::pair<GridItem*, Grid::PlacementHelpers::LineArea>>;
  422. //==============================================================================
  423. struct OccupancyPlane
  424. {
  425. struct Cell { int column, row; };
  426. OccupancyPlane (int highestColumnToUse, int highestRowToUse, bool isColoumnFirst)
  427. : highestCrossDimension (isColoumnFirst ? highestRowToUse : highestColumnToUse),
  428. columnFirst (isColoumnFirst)
  429. {}
  430. Grid::PlacementHelpers::LineArea setCell (Cell cell, int columnSpan, int rowSpan)
  431. {
  432. for (int i = 0; i < columnSpan; i++)
  433. for (int j = 0; j < rowSpan; j++)
  434. setCell (cell.column + i, cell.row + j);
  435. return { { cell.column, cell.column + columnSpan }, { cell.row, cell.row + rowSpan } };
  436. }
  437. Grid::PlacementHelpers::LineArea setCell (Cell start, Cell end)
  438. {
  439. return setCell (start, std::abs (end.column - start.column),
  440. std::abs (end.row - start.row));
  441. }
  442. Cell nextAvailable (Cell referenceCell, int columnSpan, int rowSpan)
  443. {
  444. while (isOccupied (referenceCell, columnSpan, rowSpan) || isOutOfBounds (referenceCell, columnSpan, rowSpan))
  445. referenceCell = advance (referenceCell);
  446. return referenceCell;
  447. }
  448. Cell nextAvailableOnRow (Cell referenceCell, int columnSpan, int rowSpan, int rowNumber)
  449. {
  450. if (columnFirst && (rowNumber + rowSpan) > highestCrossDimension)
  451. highestCrossDimension = rowNumber + rowSpan;
  452. while (isOccupied (referenceCell, columnSpan, rowSpan)
  453. || (referenceCell.row != rowNumber))
  454. referenceCell = advance (referenceCell);
  455. return referenceCell;
  456. }
  457. Cell nextAvailableOnColumn (Cell referenceCell, int columnSpan, int rowSpan, int columnNumber)
  458. {
  459. if (! columnFirst && (columnNumber + columnSpan) > highestCrossDimension)
  460. highestCrossDimension = columnNumber + columnSpan;
  461. while (isOccupied (referenceCell, columnSpan, rowSpan)
  462. || (referenceCell.column != columnNumber))
  463. referenceCell = advance (referenceCell);
  464. return referenceCell;
  465. }
  466. private:
  467. struct SortableCell
  468. {
  469. int column, row;
  470. bool columnFirst;
  471. bool operator< (const SortableCell& other) const
  472. {
  473. if (columnFirst)
  474. {
  475. if (row == other.row)
  476. return column < other.column;
  477. return row < other.row;
  478. }
  479. if (row == other.row)
  480. return column < other.column;
  481. return row < other.row;
  482. }
  483. };
  484. void setCell (int column, int row)
  485. {
  486. occupiedCells.insert ({ column, row, columnFirst });
  487. }
  488. bool isOccupied (Cell cell) const
  489. {
  490. return occupiedCells.count ({ cell.column, cell.row, columnFirst }) > 0;
  491. }
  492. bool isOccupied (Cell cell, int columnSpan, int rowSpan) const
  493. {
  494. for (int i = 0; i < columnSpan; i++)
  495. for (int j = 0; j < rowSpan; j++)
  496. if (isOccupied ({ cell.column + i, cell.row + j }))
  497. return true;
  498. return false;
  499. }
  500. bool isOutOfBounds (Cell cell, int columnSpan, int rowSpan) const
  501. {
  502. const auto crossSpan = columnFirst ? rowSpan : columnSpan;
  503. return (getCrossDimension (cell) + crossSpan) > getHighestCrossDimension();
  504. }
  505. int getHighestCrossDimension() const
  506. {
  507. Cell cell { 1, 1 };
  508. if (occupiedCells.size() > 0)
  509. cell = { occupiedCells.crbegin()->column, occupiedCells.crbegin()->row };
  510. return std::max (getCrossDimension (cell), highestCrossDimension);
  511. }
  512. Cell advance (Cell cell) const
  513. {
  514. if ((getCrossDimension (cell) + 1) >= getHighestCrossDimension())
  515. return fromDimensions (getMainDimension (cell) + 1, 1);
  516. return fromDimensions (getMainDimension (cell), getCrossDimension (cell) + 1);
  517. }
  518. int getMainDimension (Cell cell) const { return columnFirst ? cell.column : cell.row; }
  519. int getCrossDimension (Cell cell) const { return columnFirst ? cell.row : cell.column; }
  520. Cell fromDimensions (int mainDimension, int crossDimension) const
  521. {
  522. if (columnFirst)
  523. return { mainDimension, crossDimension };
  524. return { crossDimension, mainDimension };
  525. }
  526. int highestCrossDimension;
  527. bool columnFirst;
  528. std::set<SortableCell> occupiedCells;
  529. };
  530. //==============================================================================
  531. static bool isFixed (GridItem::StartAndEndProperty prop)
  532. {
  533. return prop.start.hasName() || prop.start.hasAbsolute() || prop.end.hasName() || prop.end.hasAbsolute();
  534. }
  535. static bool hasFullyFixedPlacement (const GridItem& item)
  536. {
  537. if (item.area.isNotEmpty())
  538. return true;
  539. if (isFixed (item.column) && isFixed (item.row))
  540. return true;
  541. return false;
  542. }
  543. static bool hasPartialFixedPlacement (const GridItem& item)
  544. {
  545. if (item.area.isNotEmpty())
  546. return false;
  547. if (isFixed (item.column) ^ isFixed (item.row))
  548. return true;
  549. return false;
  550. }
  551. static bool hasAutoPlacement (const GridItem& item)
  552. {
  553. return ! hasFullyFixedPlacement (item) && ! hasPartialFixedPlacement (item);
  554. }
  555. //==============================================================================
  556. static bool hasDenseAutoFlow (Grid::AutoFlow autoFlow)
  557. {
  558. return autoFlow == Grid::AutoFlow::columnDense
  559. || autoFlow == Grid::AutoFlow::rowDense;
  560. }
  561. static bool isColumnAutoFlow (Grid::AutoFlow autoFlow)
  562. {
  563. return autoFlow == Grid::AutoFlow::column
  564. || autoFlow == Grid::AutoFlow::columnDense;
  565. }
  566. //==============================================================================
  567. static int getSpanFromAuto (GridItem::StartAndEndProperty prop)
  568. {
  569. if (prop.end.hasSpan())
  570. return prop.end.number;
  571. if (prop.start.hasSpan())
  572. return prop.start.number;
  573. return 1;
  574. }
  575. //==============================================================================
  576. ItemPlacementArray deduceAllItems (Grid& grid) const
  577. {
  578. const auto namedAreas = Grid::PlacementHelpers::deduceNamedAreas (grid.templateAreas);
  579. OccupancyPlane plane (juce::jmax (grid.templateColumns.size() + 1, 2),
  580. juce::jmax (grid.templateRows.size() + 1, 2),
  581. isColumnAutoFlow (grid.autoFlow));
  582. ItemPlacementArray itemPlacementArray;
  583. juce::Array<GridItem*> sortedItems;
  584. for (auto& item : grid.items)
  585. sortedItems.add (&item);
  586. sortedItems.sort (*this, true);
  587. // place fixed items first
  588. for (auto* item : sortedItems)
  589. {
  590. if (hasFullyFixedPlacement (*item))
  591. {
  592. const auto a = Grid::PlacementHelpers::deduceLineArea (*item, grid, namedAreas);
  593. plane.setCell ({ a.column.start, a.row.start }, { a.column.end, a.row.end });
  594. itemPlacementArray.add ({ item, a });
  595. }
  596. }
  597. OccupancyPlane::Cell lastInsertionCell = { 1, 1 };
  598. for (auto* item : sortedItems)
  599. {
  600. if (hasPartialFixedPlacement (*item))
  601. {
  602. if (isFixed (item->column))
  603. {
  604. const auto p = Grid::PlacementHelpers::deduceLineRange (item->column, grid.templateColumns);
  605. const auto columnSpan = std::abs (p.start - p.end);
  606. const auto rowSpan = getSpanFromAuto (item->row);
  607. const auto insertionCell = hasDenseAutoFlow (grid.autoFlow) ? OccupancyPlane::Cell { p.start, 1 }
  608. : lastInsertionCell;
  609. const auto nextAvailableCell = plane.nextAvailableOnColumn (insertionCell, columnSpan, rowSpan, p.start);
  610. const auto lineArea = plane.setCell (nextAvailableCell, columnSpan, rowSpan);
  611. lastInsertionCell = nextAvailableCell;
  612. itemPlacementArray.add ({ item, lineArea });
  613. }
  614. else if (isFixed (item->row))
  615. {
  616. const auto p = Grid::PlacementHelpers::deduceLineRange (item->row, grid.templateRows);
  617. const auto columnSpan = getSpanFromAuto (item->column);
  618. const auto rowSpan = std::abs (p.start - p.end);
  619. const auto insertionCell = hasDenseAutoFlow (grid.autoFlow) ? OccupancyPlane::Cell { 1, p.start }
  620. : lastInsertionCell;
  621. const auto nextAvailableCell = plane.nextAvailableOnRow (insertionCell, columnSpan, rowSpan, p.start);
  622. const auto lineArea = plane.setCell (nextAvailableCell, columnSpan, rowSpan);
  623. lastInsertionCell = nextAvailableCell;
  624. itemPlacementArray.add ({ item, lineArea });
  625. }
  626. }
  627. }
  628. lastInsertionCell = { 1, 1 };
  629. for (auto* item : sortedItems)
  630. {
  631. if (hasAutoPlacement (*item))
  632. {
  633. const auto columnSpan = getSpanFromAuto (item->column);
  634. const auto rowSpan = getSpanFromAuto (item->row);
  635. const auto nextAvailableCell = plane.nextAvailable (lastInsertionCell, columnSpan, rowSpan);
  636. const auto lineArea = plane.setCell (nextAvailableCell, columnSpan, rowSpan);
  637. if (! hasDenseAutoFlow (grid.autoFlow))
  638. lastInsertionCell = nextAvailableCell;
  639. itemPlacementArray.add ({ item, lineArea });
  640. }
  641. }
  642. return itemPlacementArray;
  643. }
  644. //==============================================================================
  645. static std::pair<int, int> getHighestEndLinesNumbers (const ItemPlacementArray& items)
  646. {
  647. int columnEndLine = 1;
  648. int rowEndLine = 1;
  649. for (auto& item : items)
  650. {
  651. const auto p = item.second;
  652. columnEndLine = std::max (p.column.end, columnEndLine);
  653. rowEndLine = std::max (p.row.end, rowEndLine);
  654. }
  655. return { columnEndLine, rowEndLine };
  656. }
  657. static std::pair<juce::Array<TrackInfo>, juce::Array<TrackInfo>> createImplicitTracks (const Grid& grid,
  658. const ItemPlacementArray& items)
  659. {
  660. const auto columnAndRowLineEnds = getHighestEndLinesNumbers (items);
  661. juce::Array<TrackInfo> implicitColumnTracks, implicitRowTracks;
  662. for (int i = grid.templateColumns.size() + 1; i < columnAndRowLineEnds.first; i++)
  663. implicitColumnTracks.add (grid.autoColumns);
  664. for (int i = grid.templateRows.size() + 1; i < columnAndRowLineEnds.second; i++)
  665. implicitRowTracks.add (grid.autoRows);
  666. return { implicitColumnTracks, implicitRowTracks };
  667. }
  668. //==============================================================================
  669. static int compareElements (const GridItem* i1, const GridItem* i2) noexcept
  670. {
  671. return i1->order < i2->order ? -1 : (i2->order < i1->order ? 1 : 0);
  672. }
  673. //==============================================================================
  674. static void applySizeForAutoTracks (juce::Array<Grid::TrackInfo>& columns,
  675. juce::Array<Grid::TrackInfo>& rows,
  676. const ItemPlacementArray& itemPlacementArray)
  677. {
  678. auto isSpan = [](Grid::PlacementHelpers::LineRange r) -> bool { return std::abs (r.end - r.start) > 1; };
  679. auto getHighestItemOnRow = [isSpan](int rowNumber, const ItemPlacementArray& itemPlacementArrayToUse) -> float
  680. {
  681. float highestRowSize = 0.0f;
  682. for (const auto& i : itemPlacementArrayToUse)
  683. if (! isSpan (i.second.row) && i.second.row.start == rowNumber)
  684. highestRowSize = std::max (highestRowSize, i.first->height + i.first->margin.top + i.first->margin.bottom);
  685. return highestRowSize;
  686. };
  687. auto getHighestItemOnColumn = [isSpan](int rowNumber, const ItemPlacementArray& itemPlacementArrayToUse) -> float
  688. {
  689. float highestColumnSize = 0.0f;
  690. for (const auto& i : itemPlacementArrayToUse)
  691. if (! isSpan (i.second.column) && i.second.column.start == rowNumber)
  692. highestColumnSize = std::max (highestColumnSize, i.first->width + i.first->margin.left + i.first->margin.right);
  693. return highestColumnSize;
  694. };
  695. for (int i = 0; i < rows.size(); i++)
  696. if (rows.getReference (i).hasKeyword)
  697. rows.getReference (i).size = getHighestItemOnRow (i + 1, itemPlacementArray);
  698. for (int i = 0; i < columns.size(); i++)
  699. if (columns.getReference (i).hasKeyword)
  700. columns.getReference (i).size = getHighestItemOnColumn (i + 1, itemPlacementArray);
  701. }
  702. };
  703. //==============================================================================
  704. struct Grid::BoxAlignment
  705. {
  706. static juce::Rectangle<float> alignItem (const GridItem& item,
  707. const Grid& grid,
  708. juce::Rectangle<float> area)
  709. {
  710. // if item align is auto, inherit value from grid
  711. Grid::AlignItems alignType = Grid::AlignItems::start;
  712. Grid::JustifyItems justifyType = Grid::JustifyItems::start;
  713. if (item.alignSelf == GridItem::AlignSelf::autoValue)
  714. alignType = grid.alignItems;
  715. else
  716. alignType = static_cast<Grid::AlignItems> (item.alignSelf);
  717. if (item.justifySelf == GridItem::JustifySelf::autoValue)
  718. justifyType = grid.justifyItems;
  719. else
  720. justifyType = static_cast<Grid::JustifyItems> (item.justifySelf);
  721. // subtract margin from area
  722. area = juce::BorderSize<float> (item.margin.top, item.margin.left, item.margin.bottom, item.margin.right)
  723. .subtractedFrom (area);
  724. // align and justify
  725. auto r = area;
  726. if (item.width != GridItem::notAssigned)
  727. r.setWidth (item.width);
  728. if (item.height != GridItem::notAssigned)
  729. r.setHeight (item.height);
  730. if (alignType == Grid::AlignItems::start && justifyType == Grid::JustifyItems::start)
  731. return r;
  732. if (alignType == Grid::AlignItems::end)
  733. r.setY (r.getY() + (area.getHeight() - r.getHeight()));
  734. if (justifyType == Grid::JustifyItems::end)
  735. r.setX (r.getX() + (area.getWidth() - r.getWidth()));
  736. if (alignType == Grid::AlignItems::center)
  737. r.setCentre (r.getCentreX(), area.getCentreY());
  738. if (justifyType == Grid::JustifyItems::center)
  739. r.setCentre (area.getCentreX(), r.getCentreY());
  740. return r;
  741. }
  742. };
  743. //==============================================================================
  744. Grid::TrackInfo::TrackInfo() noexcept : hasKeyword (true) {}
  745. Grid::TrackInfo::TrackInfo (Px sizeInPixels) noexcept : size (static_cast<float> (sizeInPixels.pixels)), isFraction (false) {}
  746. Grid::TrackInfo::TrackInfo (Fr fractionOfFreeSpace) noexcept : size ((float)fractionOfFreeSpace.fraction), isFraction (true) {}
  747. Grid::TrackInfo::TrackInfo (Px sizeInPixels, const juce::String& endLineNameToUse) noexcept : Grid::TrackInfo (sizeInPixels)
  748. {
  749. endLineName = endLineNameToUse;
  750. }
  751. Grid::TrackInfo::TrackInfo (Fr fractionOfFreeSpace, const juce::String& endLineNameToUse) noexcept : Grid::TrackInfo (fractionOfFreeSpace)
  752. {
  753. endLineName = endLineNameToUse;
  754. }
  755. Grid::TrackInfo::TrackInfo (const juce::String& startLineNameToUse, Px sizeInPixels) noexcept : Grid::TrackInfo (sizeInPixels)
  756. {
  757. startLineName = startLineNameToUse;
  758. }
  759. Grid::TrackInfo::TrackInfo (const juce::String& startLineNameToUse, Fr fractionOfFreeSpace) noexcept : Grid::TrackInfo (fractionOfFreeSpace)
  760. {
  761. startLineName = startLineNameToUse;
  762. }
  763. Grid::TrackInfo::TrackInfo (const juce::String& startLineNameToUse, Px sizeInPixels, const juce::String& endLineNameToUse) noexcept
  764. : Grid::TrackInfo (startLineNameToUse, sizeInPixels)
  765. {
  766. endLineName = endLineNameToUse;
  767. }
  768. Grid::TrackInfo::TrackInfo (const juce::String& startLineNameToUse, Fr fractionOfFreeSpace, const juce::String& endLineNameToUse) noexcept
  769. : Grid::TrackInfo (startLineNameToUse, fractionOfFreeSpace)
  770. {
  771. endLineName = endLineNameToUse;
  772. }
  773. //==============================================================================
  774. Grid::Grid() noexcept {}
  775. Grid::~Grid() noexcept {}
  776. //==============================================================================
  777. void Grid::performLayout (juce::Rectangle<int> targetArea)
  778. {
  779. const auto itemsAndAreas = Grid::AutoPlacement().deduceAllItems (*this);
  780. const auto implicitTracks = Grid::AutoPlacement::createImplicitTracks (*this, itemsAndAreas);
  781. auto columnTracks = templateColumns;
  782. auto rowTracks = templateRows;
  783. columnTracks.addArray (implicitTracks.first);
  784. rowTracks.addArray (implicitTracks.second);
  785. Grid::AutoPlacement::applySizeForAutoTracks (columnTracks, rowTracks, itemsAndAreas);
  786. Grid::SizeCalculation calculation;
  787. calculation.computeSizes (targetArea.toFloat().getWidth(),
  788. targetArea.toFloat().getHeight(),
  789. columnGap,
  790. rowGap,
  791. columnTracks,
  792. rowTracks);
  793. for (auto& itemAndArea : itemsAndAreas)
  794. {
  795. const auto a = itemAndArea.second;
  796. const auto areaBounds = Grid::PlacementHelpers::getAreaBounds (a.column.start, a.column.end,
  797. a.row.start, a.row.end,
  798. columnTracks,
  799. rowTracks,
  800. calculation,
  801. alignContent,
  802. justifyContent,
  803. columnGap,
  804. rowGap);
  805. auto* item = itemAndArea.first;
  806. item->currentBounds = Grid::BoxAlignment::alignItem (*item, *this, areaBounds)
  807. + targetArea.toFloat().getPosition();
  808. if (auto* c = item->associatedComponent)
  809. c->setBounds (item->currentBounds.toNearestInt());
  810. }
  811. }