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.

1021 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. for (auto s : strings)
  251. jassert (s.size() == strings[0].size()); // all rows must have the same number of columns
  252. return strings;
  253. }
  254. static NamedArea findArea (juce::Array<juce::StringArray>& stringsArrays)
  255. {
  256. NamedArea area;
  257. for (auto& stringArray : stringsArrays)
  258. {
  259. for (auto& string : stringArray)
  260. {
  261. // find anchor
  262. if (area.name.isEmpty())
  263. {
  264. if (string != emptyAreaCharacter)
  265. {
  266. area.name = string;
  267. area.lines.row.start = stringsArrays.indexOf (stringArray) + 1; // non-zero indexed;
  268. area.lines.column.start = stringArray.indexOf (string) + 1; // non-zero indexed;
  269. area.lines.row.end = stringsArrays.indexOf (stringArray) + 2;
  270. area.lines.column.end = stringArray.indexOf (string) + 2;
  271. // mark as visited
  272. string = emptyAreaCharacter;
  273. }
  274. }
  275. else
  276. {
  277. if (string == emptyAreaCharacter)
  278. {
  279. break;
  280. }
  281. else if (string == area.name)
  282. {
  283. area.lines.row.end = stringsArrays.indexOf (stringArray) + 2;
  284. area.lines.column.end = stringArray.indexOf (string) + 2;
  285. // mark as visited
  286. string = emptyAreaCharacter;
  287. }
  288. }
  289. }
  290. }
  291. return area;
  292. }
  293. //==============================================================================
  294. static std::map<juce::String, LineArea> deduceNamedAreas (const juce::StringArray& areasStrings)
  295. {
  296. auto stringsArrays = parseAreasProperty (areasStrings);
  297. std::map<juce::String, LineArea> areas;
  298. for (auto area = findArea (stringsArrays); area.name.isNotEmpty(); area = findArea (stringsArrays))
  299. {
  300. if (areas.count (area.name) == 0)
  301. areas[area.name] = area.lines;
  302. else
  303. // Make sure your template-areas property only has one area with the same name and is well-formed
  304. jassertfalse;
  305. }
  306. return areas;
  307. }
  308. //==============================================================================
  309. static float getCoord (int trackNumber, float relativeUnit, Px gap, const juce::Array<Grid::TrackInfo>& tracks)
  310. {
  311. float c = 0;
  312. for (const auto* it = tracks.begin(); it != tracks.begin() + trackNumber - 1; ++it)
  313. c += (it->isFraction ? it->size * relativeUnit : it->size) + static_cast<float> (gap.pixels);
  314. return c;
  315. }
  316. static juce::Rectangle<float> getCellBounds (int columnNumber, int rowNumber,
  317. const juce::Array<Grid::TrackInfo>& columnTracks,
  318. const juce::Array<Grid::TrackInfo>& rowTracks,
  319. Grid::SizeCalculation calculation,
  320. Px columnGap, Px rowGap)
  321. {
  322. jassert (columnNumber >= 1 && columnNumber <= columnTracks.size());
  323. jassert (rowNumber >= 1 && rowNumber <= rowTracks.size());
  324. const auto x = getCoord (columnNumber, calculation.relativeWidthUnit, columnGap, columnTracks);
  325. const auto y = getCoord (rowNumber, calculation.relativeHeightUnit, rowGap, rowTracks);
  326. const auto& columnTrackInfo = columnTracks.getReference (columnNumber - 1);
  327. const float width = columnTrackInfo.isFraction ? columnTrackInfo.size * calculation.relativeWidthUnit
  328. : columnTrackInfo.size;
  329. const auto& rowTrackInfo = rowTracks.getReference (rowNumber - 1);
  330. const float height = rowTrackInfo.isFraction ? rowTrackInfo.size * calculation.relativeHeightUnit
  331. : rowTrackInfo.size;
  332. return { x, y, width, height };
  333. }
  334. static juce::Rectangle<float> alignCell (juce::Rectangle<float> area,
  335. int columnNumber, int rowNumber,
  336. int numberOfColumns, int numberOfRows,
  337. Grid::SizeCalculation calculation,
  338. Grid::AlignContent alignContent,
  339. Grid::JustifyContent justifyContent)
  340. {
  341. if (alignContent == Grid::AlignContent::end)
  342. area.setY (area.getY() + calculation.remainingHeight);
  343. if (justifyContent == Grid::JustifyContent::end)
  344. area.setX (area.getX() + calculation.remainingWidth);
  345. if (alignContent == Grid::AlignContent::center)
  346. area.setY (area.getY() + calculation.remainingHeight / 2);
  347. if (justifyContent == Grid::JustifyContent::center)
  348. area.setX (area.getX() + calculation.remainingWidth / 2);
  349. if (alignContent == Grid::AlignContent::spaceBetween)
  350. {
  351. const auto shift = ((rowNumber - 1) * (calculation.remainingHeight / float(numberOfRows - 1)));
  352. area.setY (area.getY() + shift);
  353. }
  354. if (justifyContent == Grid::JustifyContent::spaceBetween)
  355. {
  356. const auto shift = ((columnNumber - 1) * (calculation.remainingWidth / float(numberOfColumns - 1)));
  357. area.setX (area.getX() + shift);
  358. }
  359. if (alignContent == Grid::AlignContent::spaceEvenly)
  360. {
  361. const auto shift = (rowNumber * (calculation.remainingHeight / float(numberOfRows + 1)));
  362. area.setY (area.getY() + shift);
  363. }
  364. if (justifyContent == Grid::JustifyContent::spaceEvenly)
  365. {
  366. const auto shift = (columnNumber * (calculation.remainingWidth / float(numberOfColumns + 1)));
  367. area.setX (area.getX() + shift);
  368. }
  369. if (alignContent == Grid::AlignContent::spaceAround)
  370. {
  371. const auto inbetweenShift = calculation.remainingHeight / float(numberOfRows);
  372. const auto sidesShift = inbetweenShift / 2;
  373. auto shift = (rowNumber - 1) * inbetweenShift + sidesShift;
  374. area.setY (area.getY() + shift);
  375. }
  376. if (justifyContent == Grid::JustifyContent::spaceAround)
  377. {
  378. const auto inbetweenShift = calculation.remainingWidth / float(numberOfColumns);
  379. const auto sidesShift = inbetweenShift / 2;
  380. auto shift = (columnNumber - 1) * inbetweenShift + sidesShift;
  381. area.setX (area.getX() + shift);
  382. }
  383. return area;
  384. }
  385. static juce::Rectangle<float> getAreaBounds (int columnLineNumberStart, int columnLineNumberEnd,
  386. int rowLineNumberStart, int rowLineNumberEnd,
  387. const juce::Array<Grid::TrackInfo>& columnTracks,
  388. const juce::Array<Grid::TrackInfo>& rowTracks,
  389. Grid::SizeCalculation calculation,
  390. Grid::AlignContent alignContent,
  391. Grid::JustifyContent justifyContent,
  392. Px columnGap, Px rowGap)
  393. {
  394. auto startCell = getCellBounds (columnLineNumberStart, rowLineNumberStart,
  395. columnTracks, rowTracks,
  396. calculation,
  397. columnGap, rowGap);
  398. auto endCell = getCellBounds (columnLineNumberEnd - 1, rowLineNumberEnd - 1,
  399. columnTracks, rowTracks,
  400. calculation,
  401. columnGap, rowGap);
  402. startCell = alignCell (startCell,
  403. columnLineNumberStart, rowLineNumberStart,
  404. columnTracks.size(), rowTracks.size(),
  405. calculation,
  406. alignContent,
  407. justifyContent);
  408. endCell = alignCell (endCell,
  409. columnLineNumberEnd - 1, rowLineNumberEnd - 1,
  410. columnTracks.size(), rowTracks.size(),
  411. calculation,
  412. alignContent,
  413. justifyContent);
  414. return startCell.getUnion (endCell);
  415. }
  416. };
  417. //==============================================================================
  418. struct Grid::AutoPlacement
  419. {
  420. using ItemPlacementArray = juce::Array<std::pair<GridItem*, Grid::PlacementHelpers::LineArea>>;
  421. //==============================================================================
  422. struct OccupancyPlane
  423. {
  424. struct Cell { int column, row; };
  425. OccupancyPlane (int highestColumnToUse, int highestRowToUse, bool isColoumnFirst)
  426. : highestCrossDimension (isColoumnFirst ? highestRowToUse : highestColumnToUse),
  427. columnFirst (isColoumnFirst)
  428. {}
  429. Grid::PlacementHelpers::LineArea setCell (Cell cell, int columnSpan, int rowSpan)
  430. {
  431. for (int i = 0; i < columnSpan; i++)
  432. for (int j = 0; j < rowSpan; j++)
  433. setCell (cell.column + i, cell.row + j);
  434. return { { cell.column, cell.column + columnSpan }, { cell.row, cell.row + rowSpan } };
  435. }
  436. Grid::PlacementHelpers::LineArea setCell (Cell start, Cell end)
  437. {
  438. return setCell (start, std::abs (end.column - start.column),
  439. std::abs (end.row - start.row));
  440. }
  441. Cell nextAvailable (Cell referenceCell, int columnSpan, int rowSpan)
  442. {
  443. while (isOccupied (referenceCell, columnSpan, rowSpan) || isOutOfBounds (referenceCell, columnSpan, rowSpan))
  444. referenceCell = advance (referenceCell);
  445. return referenceCell;
  446. }
  447. Cell nextAvailableOnRow (Cell referenceCell, int columnSpan, int rowSpan, int rowNumber)
  448. {
  449. if (columnFirst && (rowNumber + rowSpan) > highestCrossDimension)
  450. highestCrossDimension = rowNumber + rowSpan;
  451. while (isOccupied (referenceCell, columnSpan, rowSpan)
  452. || (referenceCell.row != rowNumber))
  453. referenceCell = advance (referenceCell);
  454. return referenceCell;
  455. }
  456. Cell nextAvailableOnColumn (Cell referenceCell, int columnSpan, int rowSpan, int columnNumber)
  457. {
  458. if (! columnFirst && (columnNumber + columnSpan) > highestCrossDimension)
  459. highestCrossDimension = columnNumber + columnSpan;
  460. while (isOccupied (referenceCell, columnSpan, rowSpan)
  461. || (referenceCell.column != columnNumber))
  462. referenceCell = advance (referenceCell);
  463. return referenceCell;
  464. }
  465. private:
  466. struct SortableCell
  467. {
  468. int column, row;
  469. bool columnFirst;
  470. bool operator< (const SortableCell& other) const
  471. {
  472. if (columnFirst)
  473. {
  474. if (row == other.row)
  475. return column < other.column;
  476. return row < other.row;
  477. }
  478. if (row == other.row)
  479. return column < other.column;
  480. return row < other.row;
  481. }
  482. };
  483. void setCell (int column, int row)
  484. {
  485. occupiedCells.insert ({ column, row, columnFirst });
  486. }
  487. bool isOccupied (Cell cell) const
  488. {
  489. return occupiedCells.count ({ cell.column, cell.row, columnFirst }) > 0;
  490. }
  491. bool isOccupied (Cell cell, int columnSpan, int rowSpan) const
  492. {
  493. for (int i = 0; i < columnSpan; i++)
  494. for (int j = 0; j < rowSpan; j++)
  495. if (isOccupied ({ cell.column + i, cell.row + j }))
  496. return true;
  497. return false;
  498. }
  499. bool isOutOfBounds (Cell cell, int columnSpan, int rowSpan) const
  500. {
  501. const auto crossSpan = columnFirst ? rowSpan : columnSpan;
  502. return (getCrossDimension (cell) + crossSpan) > getHighestCrossDimension();
  503. }
  504. int getHighestCrossDimension() const
  505. {
  506. return std::max (getCrossDimension ({ occupiedCells.crbegin()->column, occupiedCells.crbegin()->row }),
  507. highestCrossDimension);
  508. }
  509. Cell advance (Cell cell) const
  510. {
  511. if ((getCrossDimension (cell) + 1) >= getHighestCrossDimension())
  512. return fromDimensions (getMainDimension (cell) + 1, 1);
  513. return fromDimensions (getMainDimension (cell), getCrossDimension (cell) + 1);
  514. }
  515. int getMainDimension (Cell cell) const { return columnFirst ? cell.column : cell.row; }
  516. int getCrossDimension (Cell cell) const { return columnFirst ? cell.row : cell.column; }
  517. Cell fromDimensions (int mainDimension, int crossDimension) const
  518. {
  519. if (columnFirst)
  520. return { mainDimension, crossDimension };
  521. return { crossDimension, mainDimension };
  522. }
  523. int highestCrossDimension;
  524. bool columnFirst;
  525. std::set<SortableCell> occupiedCells;
  526. };
  527. //==============================================================================
  528. static bool isFixed (GridItem::StartAndEndProperty prop)
  529. {
  530. return prop.start.hasName() || prop.start.hasAbsolute() || prop.end.hasName() || prop.end.hasAbsolute();
  531. }
  532. static bool hasFullyFixedPlacement (const GridItem& item)
  533. {
  534. if (item.area.isNotEmpty())
  535. return true;
  536. if (isFixed (item.column) && isFixed (item.row))
  537. return true;
  538. return false;
  539. }
  540. static bool hasPartialFixedPlacement (const GridItem& item)
  541. {
  542. if (item.area.isNotEmpty())
  543. return false;
  544. if (isFixed (item.column) ^ isFixed (item.row))
  545. return true;
  546. return false;
  547. }
  548. static bool hasAutoPlacement (const GridItem& item)
  549. {
  550. return ! hasFullyFixedPlacement (item) && ! hasPartialFixedPlacement (item);
  551. }
  552. //==============================================================================
  553. static bool hasDenseAutoFlow (Grid::AutoFlow autoFlow)
  554. {
  555. return autoFlow == Grid::AutoFlow::columnDense
  556. || autoFlow == Grid::AutoFlow::rowDense;
  557. }
  558. static bool isColumnAutoFlow (Grid::AutoFlow autoFlow)
  559. {
  560. return autoFlow == Grid::AutoFlow::column
  561. || autoFlow == Grid::AutoFlow::columnDense;
  562. }
  563. //==============================================================================
  564. static int getSpanFromAuto (GridItem::StartAndEndProperty prop)
  565. {
  566. if (prop.end.hasSpan())
  567. return prop.end.number;
  568. if (prop.start.hasSpan())
  569. return prop.start.number;
  570. return 1;
  571. }
  572. //==============================================================================
  573. ItemPlacementArray deduceAllItems (Grid& grid) const
  574. {
  575. const auto namedAreas = Grid::PlacementHelpers::deduceNamedAreas (grid.templateAreas);
  576. OccupancyPlane plane (juce::jmax (grid.templateColumns.size() + 1, 2),
  577. juce::jmax (grid.templateRows.size() + 1, 2),
  578. isColumnAutoFlow (grid.autoFlow));
  579. ItemPlacementArray itemPlacementArray;
  580. juce::Array<GridItem*> sortedItems;
  581. for (auto& item : grid.items)
  582. sortedItems.add (&item);
  583. sortedItems.sort (*this, true);
  584. // place fixed items first
  585. for (auto* item : sortedItems)
  586. {
  587. if (hasFullyFixedPlacement (*item))
  588. {
  589. const auto a = Grid::PlacementHelpers::deduceLineArea (*item, grid, namedAreas);
  590. plane.setCell ({ a.column.start, a.row.start }, { a.column.end, a.row.end });
  591. itemPlacementArray.add ({ item, a });
  592. }
  593. }
  594. OccupancyPlane::Cell lastInsertionCell = { 1, 1 };
  595. for (auto* item : sortedItems)
  596. {
  597. if (hasPartialFixedPlacement (*item))
  598. {
  599. if (isFixed (item->column))
  600. {
  601. const auto p = Grid::PlacementHelpers::deduceLineRange (item->column, grid.templateColumns);
  602. const auto columnSpan = std::abs (p.start - p.end);
  603. const auto rowSpan = getSpanFromAuto (item->row);
  604. const auto insertionCell = hasDenseAutoFlow (grid.autoFlow) ? OccupancyPlane::Cell { p.start, 1 }
  605. : lastInsertionCell;
  606. const auto nextAvailableCell = plane.nextAvailableOnColumn (insertionCell, columnSpan, rowSpan, p.start);
  607. const auto lineArea = plane.setCell (nextAvailableCell, columnSpan, rowSpan);
  608. lastInsertionCell = nextAvailableCell;
  609. itemPlacementArray.add ({ item, lineArea });
  610. }
  611. else if (isFixed (item->row))
  612. {
  613. const auto p = Grid::PlacementHelpers::deduceLineRange (item->row, grid.templateRows);
  614. const auto columnSpan = getSpanFromAuto (item->column);
  615. const auto rowSpan = std::abs (p.start - p.end);
  616. const auto insertionCell = hasDenseAutoFlow (grid.autoFlow) ? OccupancyPlane::Cell { 1, p.start }
  617. : lastInsertionCell;
  618. const auto nextAvailableCell = plane.nextAvailableOnRow (insertionCell, columnSpan, rowSpan, p.start);
  619. const auto lineArea = plane.setCell (nextAvailableCell, columnSpan, rowSpan);
  620. lastInsertionCell = nextAvailableCell;
  621. itemPlacementArray.add ({ item, lineArea });
  622. }
  623. }
  624. }
  625. lastInsertionCell = { 1, 1 };
  626. for (auto* item : sortedItems)
  627. {
  628. if (hasAutoPlacement (*item))
  629. {
  630. const auto columnSpan = getSpanFromAuto (item->column);
  631. const auto rowSpan = getSpanFromAuto (item->row);
  632. const auto nextAvailableCell = plane.nextAvailable (lastInsertionCell, columnSpan, rowSpan);
  633. const auto lineArea = plane.setCell (nextAvailableCell, columnSpan, rowSpan);
  634. if (! hasDenseAutoFlow (grid.autoFlow))
  635. lastInsertionCell = nextAvailableCell;
  636. itemPlacementArray.add ({ item, lineArea });
  637. }
  638. }
  639. return itemPlacementArray;
  640. }
  641. //==============================================================================
  642. static std::pair<int, int> getHighestEndLinesNumbers (const ItemPlacementArray& items)
  643. {
  644. int columnEndLine = 1;
  645. int rowEndLine = 1;
  646. for (auto& item : items)
  647. {
  648. const auto p = item.second;
  649. columnEndLine = std::max (p.column.end, columnEndLine);
  650. rowEndLine = std::max (p.row.end, rowEndLine);
  651. }
  652. return { columnEndLine, rowEndLine };
  653. }
  654. static std::pair<juce::Array<TrackInfo>, juce::Array<TrackInfo>> createImplicitTracks (const Grid& grid,
  655. const ItemPlacementArray& items)
  656. {
  657. const auto columnAndRowLineEnds = getHighestEndLinesNumbers (items);
  658. juce::Array<TrackInfo> implicitColumnTracks, implicitRowTracks;
  659. for (int i = grid.templateColumns.size() + 1; i < columnAndRowLineEnds.first; i++)
  660. implicitColumnTracks.add (grid.autoColumns);
  661. for (int i = grid.templateRows.size() + 1; i < columnAndRowLineEnds.second; i++)
  662. implicitRowTracks.add (grid.autoRows);
  663. return { implicitColumnTracks, implicitRowTracks };
  664. }
  665. //==============================================================================
  666. static int compareElements (const GridItem* i1, const GridItem* i2) noexcept
  667. {
  668. return i1->order < i2->order ? -1 : (i2->order < i1->order ? 1 : 0);
  669. }
  670. //==============================================================================
  671. static void applySizeForAutoTracks (juce::Array<Grid::TrackInfo>& columns,
  672. juce::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).hasKeyword)
  694. rows.getReference (i).size = getHighestItemOnRow (i + 1, itemPlacementArray);
  695. for (int i = 0; i < columns.size(); i++)
  696. if (columns.getReference (i).hasKeyword)
  697. columns.getReference (i).size = getHighestItemOnColumn (i + 1, itemPlacementArray);
  698. }
  699. };
  700. //==============================================================================
  701. struct Grid::BoxAlignment
  702. {
  703. static juce::Rectangle<float> alignItem (const GridItem& item,
  704. const Grid& grid,
  705. juce::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 = juce::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 != GridItem::notAssigned)
  724. r.setWidth (item.width);
  725. if (item.height != GridItem::notAssigned)
  726. r.setHeight (item.height);
  727. if (alignType == Grid::AlignItems::start && justifyType == Grid::JustifyItems::start)
  728. return r;
  729. if (alignType == Grid::AlignItems::end)
  730. r.setY (r.getY() + (area.getHeight() - r.getHeight()));
  731. if (justifyType == Grid::JustifyItems::end)
  732. r.setX (r.getX() + (area.getWidth() - r.getWidth()));
  733. if (alignType == Grid::AlignItems::center)
  734. r.setCentre (r.getCentreX(), area.getCentreY());
  735. if (justifyType == Grid::JustifyItems::center)
  736. r.setCentre (area.getCentreX(), r.getCentreY());
  737. return r;
  738. }
  739. };
  740. //==============================================================================
  741. Grid::TrackInfo::TrackInfo() noexcept : hasKeyword (true) {}
  742. Grid::TrackInfo::TrackInfo (Px sizeInPixels) noexcept : size (static_cast<float> (sizeInPixels.pixels)), isFraction (false) {}
  743. Grid::TrackInfo::TrackInfo (Fr fractionOfFreeSpace) noexcept : size ((float)fractionOfFreeSpace.fraction), isFraction (true) {}
  744. Grid::TrackInfo::TrackInfo (Px sizeInPixels, const juce::String& endLineNameToUse) noexcept : Grid::TrackInfo (sizeInPixels)
  745. {
  746. endLineName = endLineNameToUse;
  747. }
  748. Grid::TrackInfo::TrackInfo (Fr fractionOfFreeSpace, const juce::String& endLineNameToUse) noexcept : Grid::TrackInfo (fractionOfFreeSpace)
  749. {
  750. endLineName = endLineNameToUse;
  751. }
  752. Grid::TrackInfo::TrackInfo (const juce::String& startLineNameToUse, Px sizeInPixels) noexcept : Grid::TrackInfo (sizeInPixels)
  753. {
  754. startLineName = startLineNameToUse;
  755. }
  756. Grid::TrackInfo::TrackInfo (const juce::String& startLineNameToUse, Fr fractionOfFreeSpace) noexcept : Grid::TrackInfo (fractionOfFreeSpace)
  757. {
  758. startLineName = startLineNameToUse;
  759. }
  760. Grid::TrackInfo::TrackInfo (const juce::String& startLineNameToUse, Px sizeInPixels, const juce::String& endLineNameToUse) noexcept
  761. : Grid::TrackInfo (startLineNameToUse, sizeInPixels)
  762. {
  763. endLineName = endLineNameToUse;
  764. }
  765. Grid::TrackInfo::TrackInfo (const juce::String& startLineNameToUse, Fr fractionOfFreeSpace, const juce::String& endLineNameToUse) noexcept
  766. : Grid::TrackInfo (startLineNameToUse, fractionOfFreeSpace)
  767. {
  768. endLineName = endLineNameToUse;
  769. }
  770. //==============================================================================
  771. Grid::Grid() noexcept {}
  772. Grid::~Grid() noexcept {}
  773. //==============================================================================
  774. void Grid::performLayout (juce::Rectangle<int> targetArea)
  775. {
  776. const auto itemsAndAreas = Grid::AutoPlacement().deduceAllItems (*this);
  777. const auto implicitTracks = Grid::AutoPlacement::createImplicitTracks (*this, itemsAndAreas);
  778. auto columnTracks = templateColumns;
  779. auto rowTracks = templateRows;
  780. columnTracks.addArray (implicitTracks.first);
  781. rowTracks.addArray (implicitTracks.second);
  782. Grid::AutoPlacement::applySizeForAutoTracks (columnTracks, rowTracks, itemsAndAreas);
  783. Grid::SizeCalculation calculation;
  784. calculation.computeSizes (targetArea.toFloat().getWidth(),
  785. targetArea.toFloat().getHeight(),
  786. columnGap,
  787. rowGap,
  788. columnTracks,
  789. rowTracks);
  790. for (auto& itemAndArea : itemsAndAreas)
  791. {
  792. const auto a = itemAndArea.second;
  793. const auto areaBounds = Grid::PlacementHelpers::getAreaBounds (a.column.start, a.column.end,
  794. a.row.start, a.row.end,
  795. columnTracks,
  796. rowTracks,
  797. calculation,
  798. alignContent,
  799. justifyContent,
  800. columnGap,
  801. rowGap);
  802. auto* item = itemAndArea.first;
  803. item->currentBounds = Grid::BoxAlignment::alignItem (*item, *this, areaBounds)
  804. + targetArea.toFloat().getPosition();
  805. if (auto* c = item->associatedComponent)
  806. c->setBounds (item->currentBounds.toNearestInt());
  807. }
  808. }