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.

1150 lines
52KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. struct FlexBoxLayoutCalculation
  21. {
  22. using Coord = double;
  23. enum class Axis { main, cross };
  24. FlexBoxLayoutCalculation (FlexBox& fb, Coord w, Coord h)
  25. : owner (fb), parentWidth (w), parentHeight (h), numItems (owner.items.size()),
  26. isRowDirection (fb.flexDirection == FlexBox::Direction::row
  27. || fb.flexDirection == FlexBox::Direction::rowReverse),
  28. containerLineLength (getContainerSize (Axis::main))
  29. {
  30. lineItems.calloc (numItems * numItems);
  31. lineInfo.calloc (numItems);
  32. }
  33. struct ItemWithState
  34. {
  35. ItemWithState (FlexItem& source) noexcept : item (&source) {}
  36. FlexItem* item;
  37. Coord lockedWidth = 0, lockedHeight = 0;
  38. Coord lockedMarginLeft = 0, lockedMarginRight = 0, lockedMarginTop = 0, lockedMarginBottom = 0;
  39. Coord preferredWidth = 0, preferredHeight = 0;
  40. bool locked = false;
  41. void resetItemLockedSize() noexcept
  42. {
  43. lockedWidth = preferredWidth;
  44. lockedHeight = preferredHeight;
  45. lockedMarginLeft = getValueOrZeroIfAuto (item->margin.left);
  46. lockedMarginRight = getValueOrZeroIfAuto (item->margin.right);
  47. lockedMarginTop = getValueOrZeroIfAuto (item->margin.top);
  48. lockedMarginBottom = getValueOrZeroIfAuto (item->margin.bottom);
  49. }
  50. };
  51. struct RowInfo
  52. {
  53. int numItems;
  54. Coord crossSize, lineY, totalLength;
  55. };
  56. FlexBox& owner;
  57. const Coord parentWidth, parentHeight;
  58. const int numItems;
  59. const bool isRowDirection;
  60. const Coord containerLineLength;
  61. int numberOfRows = 1;
  62. Coord containerCrossLength = 0;
  63. HeapBlock<ItemWithState*> lineItems;
  64. HeapBlock<RowInfo> lineInfo;
  65. Array<ItemWithState> itemStates;
  66. ItemWithState& getItem (int x, int y) const noexcept { return *lineItems[y * numItems + x]; }
  67. static bool isAuto (Coord value) noexcept
  68. {
  69. return exactlyEqual (value, static_cast<Coord> (FlexItem::autoValue));
  70. }
  71. static bool isAssigned (Coord value) noexcept
  72. {
  73. return ! exactlyEqual (value, static_cast<Coord> (FlexItem::notAssigned));
  74. }
  75. static Coord getValueOrZeroIfAuto (Coord value) noexcept { return isAuto (value) ? Coord() : value; }
  76. //==============================================================================
  77. bool isSingleLine() const { return owner.flexWrap == FlexBox::Wrap::noWrap; }
  78. template <typename Value>
  79. Value& pickForAxis (Axis axis, Value& x, Value& y) const
  80. {
  81. return (isRowDirection ? axis == Axis::main : axis == Axis::cross) ? x : y;
  82. }
  83. auto& getStartMargin (Axis axis, ItemWithState& item) const
  84. {
  85. return pickForAxis (axis, item.item->margin.left, item.item->margin.top);
  86. }
  87. auto& getEndMargin (Axis axis, ItemWithState& item) const
  88. {
  89. return pickForAxis (axis, item.item->margin.right, item.item->margin.bottom);
  90. }
  91. auto& getStartLockedMargin (Axis axis, ItemWithState& item) const
  92. {
  93. return pickForAxis (axis, item.lockedMarginLeft, item.lockedMarginTop);
  94. }
  95. auto& getEndLockedMargin (Axis axis, ItemWithState& item) const
  96. {
  97. return pickForAxis (axis, item.lockedMarginRight, item.lockedMarginBottom);
  98. }
  99. auto& getLockedSize (Axis axis, ItemWithState& item) const
  100. {
  101. return pickForAxis (axis, item.lockedWidth, item.lockedHeight);
  102. }
  103. auto& getPreferredSize (Axis axis, ItemWithState& item) const
  104. {
  105. return pickForAxis (axis, item.preferredWidth, item.preferredHeight);
  106. }
  107. Coord getContainerSize (Axis axis) const
  108. {
  109. return pickForAxis (axis, parentWidth, parentHeight);
  110. }
  111. auto& getItemSize (Axis axis, ItemWithState& item) const
  112. {
  113. return pickForAxis (axis, item.item->width, item.item->height);
  114. }
  115. auto& getMinSize (Axis axis, ItemWithState& item) const
  116. {
  117. return pickForAxis (axis, item.item->minWidth, item.item->minHeight);
  118. }
  119. auto& getMaxSize (Axis axis, ItemWithState& item) const
  120. {
  121. return pickForAxis (axis, item.item->maxWidth, item.item->maxHeight);
  122. }
  123. //==============================================================================
  124. void createStates()
  125. {
  126. itemStates.ensureStorageAllocated (numItems);
  127. for (auto& item : owner.items)
  128. itemStates.add (item);
  129. std::stable_sort (itemStates.begin(), itemStates.end(),
  130. [] (const ItemWithState& i1, const ItemWithState& i2) { return i1.item->order < i2.item->order; });
  131. for (auto& item : itemStates)
  132. {
  133. for (auto& axis : { Axis::main, Axis::cross })
  134. getPreferredSize (axis, item) = computePreferredSize (axis, item);
  135. }
  136. }
  137. void initialiseItems() noexcept
  138. {
  139. if (isSingleLine()) // for single-line, all items go in line 1
  140. {
  141. lineInfo[0].numItems = numItems;
  142. int i = 0;
  143. for (auto& item : itemStates)
  144. {
  145. item.resetItemLockedSize();
  146. lineItems[i++] = &item;
  147. }
  148. }
  149. else // if multi-line, group the flexbox items into multiple lines
  150. {
  151. auto currentLength = containerLineLength;
  152. int column = 0, row = 0;
  153. bool firstRow = true;
  154. for (auto& item : itemStates)
  155. {
  156. item.resetItemLockedSize();
  157. const auto flexitemLength = getItemMainSize (item);
  158. if (flexitemLength > currentLength)
  159. {
  160. if (! firstRow)
  161. row++;
  162. if (row >= numItems)
  163. break;
  164. column = 0;
  165. currentLength = containerLineLength;
  166. numberOfRows = jmax (numberOfRows, row + 1);
  167. }
  168. currentLength -= flexitemLength;
  169. lineItems[row * numItems + column] = &item;
  170. ++column;
  171. lineInfo[row].numItems = jmax (lineInfo[row].numItems, column);
  172. firstRow = false;
  173. }
  174. }
  175. }
  176. void resolveFlexibleLengths() noexcept
  177. {
  178. for (int row = 0; row < numberOfRows; ++row)
  179. {
  180. resetRowItems (row);
  181. for (int maxLoops = numItems; --maxLoops >= 0;)
  182. {
  183. resetUnlockedRowItems (row);
  184. if (layoutRowItems (row))
  185. break;
  186. }
  187. }
  188. }
  189. void resolveAutoMarginsOnMainAxis() noexcept
  190. {
  191. for (int row = 0; row < numberOfRows; ++row)
  192. {
  193. Coord allFlexGrow = 0;
  194. const auto numColumns = lineInfo[row].numItems;
  195. const auto remainingLength = containerLineLength - lineInfo[row].totalLength;
  196. for (int column = 0; column < numColumns; ++column)
  197. {
  198. auto& item = getItem (column, row);
  199. if (isAuto (getStartMargin (Axis::main, item))) ++allFlexGrow;
  200. if (isAuto (getEndMargin (Axis::main, item))) ++allFlexGrow;
  201. }
  202. const auto changeUnitWidth = remainingLength / allFlexGrow;
  203. if (changeUnitWidth > 0)
  204. {
  205. for (int column = 0; column < numColumns; ++column)
  206. {
  207. auto& item = getItem (column, row);
  208. if (isAuto (getStartMargin (Axis::main, item)))
  209. getStartLockedMargin (Axis::main, item) = changeUnitWidth;
  210. if (isAuto (getEndMargin (Axis::main, item)))
  211. getEndLockedMargin (Axis::main, item) = changeUnitWidth;
  212. }
  213. }
  214. }
  215. }
  216. void calculateCrossSizesByLine() noexcept
  217. {
  218. // https://www.w3.org/TR/css-flexbox-1/#algo-cross-line
  219. // If the flex container is single-line and has a definite cross size, the cross size of the
  220. // flex line is the flex container’s inner cross size.
  221. if (isSingleLine())
  222. {
  223. lineInfo[0].crossSize = getContainerSize (Axis::cross);
  224. }
  225. else
  226. {
  227. for (int row = 0; row < numberOfRows; ++row)
  228. {
  229. Coord maxSize = 0;
  230. const auto numColumns = lineInfo[row].numItems;
  231. for (int column = 0; column < numColumns; ++column)
  232. maxSize = jmax (maxSize, getItemCrossSize (getItem (column, row)));
  233. lineInfo[row].crossSize = maxSize;
  234. }
  235. }
  236. }
  237. void calculateCrossSizeOfAllItems() noexcept
  238. {
  239. for (int row = 0; row < numberOfRows; ++row)
  240. {
  241. const auto numColumns = lineInfo[row].numItems;
  242. for (int column = 0; column < numColumns; ++column)
  243. {
  244. auto& item = getItem (column, row);
  245. if (isAssigned (item.item->maxHeight) && item.lockedHeight > item.item->maxHeight)
  246. item.lockedHeight = item.item->maxHeight;
  247. if (isAssigned (item.item->maxWidth) && item.lockedWidth > item.item->maxWidth)
  248. item.lockedWidth = item.item->maxWidth;
  249. }
  250. }
  251. }
  252. void alignLinesPerAlignContent() noexcept
  253. {
  254. containerCrossLength = getContainerSize (Axis::cross);
  255. if (owner.alignContent == FlexBox::AlignContent::flexStart)
  256. {
  257. for (int row = 0; row < numberOfRows; ++row)
  258. for (int row2 = row; row2 < numberOfRows; ++row2)
  259. lineInfo[row].lineY = row == 0 ? 0 : lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
  260. }
  261. else if (owner.alignContent == FlexBox::AlignContent::flexEnd)
  262. {
  263. for (int row = 0; row < numberOfRows; ++row)
  264. {
  265. Coord crossHeights = 0;
  266. for (int row2 = row; row2 < numberOfRows; ++row2)
  267. crossHeights += lineInfo[row2].crossSize;
  268. lineInfo[row].lineY = containerCrossLength - crossHeights;
  269. }
  270. }
  271. else
  272. {
  273. Coord totalHeight = 0;
  274. for (int row = 0; row < numberOfRows; ++row)
  275. totalHeight += lineInfo[row].crossSize;
  276. if (owner.alignContent == FlexBox::AlignContent::stretch)
  277. {
  278. const auto difference = jmax (Coord(), (containerCrossLength - totalHeight) / numberOfRows);
  279. for (int row = 0; row < numberOfRows; ++row)
  280. {
  281. lineInfo[row].crossSize += difference;
  282. lineInfo[row].lineY = row == 0 ? 0 : lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
  283. }
  284. }
  285. else if (owner.alignContent == FlexBox::AlignContent::center)
  286. {
  287. const auto additionalength = (containerCrossLength - totalHeight) / 2;
  288. for (int row = 0; row < numberOfRows; ++row)
  289. lineInfo[row].lineY = row == 0 ? additionalength : lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
  290. }
  291. else if (owner.alignContent == FlexBox::AlignContent::spaceBetween)
  292. {
  293. const auto additionalength = numberOfRows <= 1 ? Coord() : jmax (Coord(), (containerCrossLength - totalHeight)
  294. / static_cast<Coord> (numberOfRows - 1));
  295. lineInfo[0].lineY = 0;
  296. for (int row = 1; row < numberOfRows; ++row)
  297. lineInfo[row].lineY += additionalength + lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
  298. }
  299. else if (owner.alignContent == FlexBox::AlignContent::spaceAround)
  300. {
  301. const auto additionalength = numberOfRows <= 1 ? Coord() : jmax (Coord(), (containerCrossLength - totalHeight)
  302. / static_cast<Coord> (2 + (2 * (numberOfRows - 1))));
  303. lineInfo[0].lineY = additionalength;
  304. for (int row = 1; row < numberOfRows; ++row)
  305. lineInfo[row].lineY += (2 * additionalength) + lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
  306. }
  307. }
  308. }
  309. void resolveAutoMarginsOnCrossAxis() noexcept
  310. {
  311. for (int row = 0; row < numberOfRows; ++row)
  312. {
  313. const auto numColumns = lineInfo[row].numItems;
  314. const auto crossSizeForLine = lineInfo[row].crossSize;
  315. for (int column = 0; column < numColumns; ++column)
  316. {
  317. auto& item = getItem (column, row);
  318. getStartLockedMargin (Axis::cross, item) = [&]
  319. {
  320. if (isAuto (getStartMargin (Axis::cross, item)) && isAuto (getEndMargin (Axis::cross, item)))
  321. return (crossSizeForLine - getLockedSize (Axis::cross, item)) / 2;
  322. if (isAuto (getStartMargin (Axis::cross, item)))
  323. return crossSizeForLine - getLockedSize (Axis::cross, item) - getEndMargin (Axis::cross, item);
  324. return getStartLockedMargin (Axis::cross, item);
  325. }();
  326. }
  327. }
  328. }
  329. // Align all flex items along the cross-axis per align-self, if neither of the item’s cross-axis margins are auto.
  330. void alignItemsInCrossAxisInLinesPerAlignSelf() noexcept
  331. {
  332. for (int row = 0; row < numberOfRows; ++row)
  333. {
  334. const auto numColumns = lineInfo[row].numItems;
  335. const auto lineSize = lineInfo[row].crossSize;
  336. for (int column = 0; column < numColumns; ++column)
  337. {
  338. auto& item = getItem (column, row);
  339. if (isAuto (getStartMargin (Axis::cross, item)) || isAuto (getEndMargin (Axis::cross, item)))
  340. continue;
  341. const auto alignment = [&]
  342. {
  343. switch (item.item->alignSelf)
  344. {
  345. case FlexItem::AlignSelf::stretch: return FlexBox::AlignItems::stretch;
  346. case FlexItem::AlignSelf::flexStart: return FlexBox::AlignItems::flexStart;
  347. case FlexItem::AlignSelf::flexEnd: return FlexBox::AlignItems::flexEnd;
  348. case FlexItem::AlignSelf::center: return FlexBox::AlignItems::center;
  349. case FlexItem::AlignSelf::autoAlign: break;
  350. }
  351. return owner.alignItems;
  352. }();
  353. getStartLockedMargin (Axis::cross, item) = [&]
  354. {
  355. switch (alignment)
  356. {
  357. // https://www.w3.org/TR/css-flexbox-1/#valdef-align-items-flex-start
  358. // The cross-start margin edge of the flex item is placed flush with the
  359. // cross-start edge of the line.
  360. case FlexBox::AlignItems::flexStart:
  361. return (Coord) getStartMargin (Axis::cross, item);
  362. // https://www.w3.org/TR/css-flexbox-1/#valdef-align-items-flex-end
  363. // The cross-end margin edge of the flex item is placed flush with the cross-end
  364. // edge of the line.
  365. case FlexBox::AlignItems::flexEnd:
  366. return lineSize - getLockedSize (Axis::cross, item) - getEndMargin (Axis::cross, item);
  367. // https://www.w3.org/TR/css-flexbox-1/#valdef-align-items-center
  368. // The flex item’s margin box is centered in the cross axis within the line.
  369. case FlexBox::AlignItems::center:
  370. return getStartMargin (Axis::cross, item) + (lineSize - getLockedSize (Axis::cross, item) - getStartMargin (Axis::cross, item) - getEndMargin (Axis::cross, item)) / 2;
  371. // https://www.w3.org/TR/css-flexbox-1/#valdef-align-items-stretch
  372. case FlexBox::AlignItems::stretch:
  373. return (Coord) getStartMargin (Axis::cross, item);
  374. }
  375. jassertfalse;
  376. return 0.0;
  377. }();
  378. if (alignment == FlexBox::AlignItems::stretch)
  379. {
  380. auto newSize = isAssigned (getItemSize (Axis::cross, item)) ? computePreferredSize (Axis::cross, item)
  381. : lineSize - getStartMargin (Axis::cross, item) - getEndMargin (Axis::cross, item);
  382. if (isAssigned (getMaxSize (Axis::cross, item)))
  383. newSize = jmin (newSize, (Coord) getMaxSize (Axis::cross, item));
  384. if (isAssigned (getMinSize (Axis::cross, item)))
  385. newSize = jmax (newSize, (Coord) getMinSize (Axis::cross, item));
  386. getLockedSize (Axis::cross, item) = newSize;
  387. }
  388. }
  389. }
  390. }
  391. void alignItemsByJustifyContent() noexcept
  392. {
  393. Coord additionalMarginRight = 0, additionalMarginLeft = 0;
  394. recalculateTotalItemLengthPerLineArray();
  395. for (int row = 0; row < numberOfRows; ++row)
  396. {
  397. const auto numColumns = lineInfo[row].numItems;
  398. Coord x = 0;
  399. if (owner.justifyContent == FlexBox::JustifyContent::flexEnd)
  400. {
  401. x = containerLineLength - lineInfo[row].totalLength;
  402. }
  403. else if (owner.justifyContent == FlexBox::JustifyContent::center)
  404. {
  405. x = (containerLineLength - lineInfo[row].totalLength) / 2;
  406. }
  407. else if (owner.justifyContent == FlexBox::JustifyContent::spaceBetween)
  408. {
  409. additionalMarginRight
  410. = jmax (Coord(), (containerLineLength - lineInfo[row].totalLength) / jmax (1, numColumns - 1));
  411. }
  412. else if (owner.justifyContent == FlexBox::JustifyContent::spaceAround)
  413. {
  414. additionalMarginLeft = additionalMarginRight
  415. = jmax (Coord(), (containerLineLength - lineInfo[row].totalLength) / jmax (1, 2 * numColumns));
  416. }
  417. for (int column = 0; column < numColumns; ++column)
  418. {
  419. auto& item = getItem (column, row);
  420. getStartLockedMargin (Axis::main, item) += additionalMarginLeft;
  421. getEndLockedMargin (Axis::main, item) += additionalMarginRight;
  422. item.item->currentBounds.setPosition (isRowDirection ? (float) (x + item.lockedMarginLeft)
  423. : (float) item.lockedMarginLeft,
  424. isRowDirection ? (float) item.lockedMarginTop
  425. : (float) (x + item.lockedMarginTop));
  426. x += getItemMainSize (item);
  427. }
  428. }
  429. }
  430. void layoutAllItems() noexcept
  431. {
  432. for (int row = 0; row < numberOfRows; ++row)
  433. {
  434. const auto lineY = lineInfo[row].lineY;
  435. const auto numColumns = lineInfo[row].numItems;
  436. for (int column = 0; column < numColumns; ++column)
  437. {
  438. auto& item = getItem (column, row);
  439. if (isRowDirection)
  440. item.item->currentBounds.setY ((float) (lineY + item.lockedMarginTop));
  441. else
  442. item.item->currentBounds.setX ((float) (lineY + item.lockedMarginLeft));
  443. item.item->currentBounds.setSize ((float) item.lockedWidth,
  444. (float) item.lockedHeight);
  445. }
  446. }
  447. reverseLocations();
  448. reverseWrap();
  449. }
  450. private:
  451. void resetRowItems (const int row) noexcept
  452. {
  453. const auto numColumns = lineInfo[row].numItems;
  454. for (int column = 0; column < numColumns; ++column)
  455. resetItem (getItem (column, row));
  456. }
  457. void resetUnlockedRowItems (const int row) noexcept
  458. {
  459. const auto numColumns = lineInfo[row].numItems;
  460. for (int column = 0; column < numColumns; ++column)
  461. {
  462. auto& item = getItem (column, row);
  463. if (! item.locked)
  464. resetItem (item);
  465. }
  466. }
  467. void resetItem (ItemWithState& item) noexcept
  468. {
  469. item.locked = false;
  470. for (auto& axis : { Axis::main, Axis::cross })
  471. getLockedSize (axis, item) = computePreferredSize (axis, item);
  472. }
  473. bool layoutRowItems (const int row) noexcept
  474. {
  475. const auto numColumns = lineInfo[row].numItems;
  476. auto flexContainerLength = containerLineLength;
  477. Coord totalItemsLength = 0, totalFlexGrow = 0, totalFlexShrink = 0;
  478. for (int column = 0; column < numColumns; ++column)
  479. {
  480. const auto& item = getItem (column, row);
  481. if (item.locked)
  482. {
  483. flexContainerLength -= getItemMainSize (item);
  484. }
  485. else
  486. {
  487. totalItemsLength += getItemMainSize (item);
  488. totalFlexGrow += item.item->flexGrow;
  489. totalFlexShrink += item.item->flexShrink;
  490. }
  491. }
  492. Coord changeUnit = 0;
  493. const auto difference = flexContainerLength - totalItemsLength;
  494. const bool positiveFlexibility = difference > 0;
  495. if (positiveFlexibility)
  496. {
  497. if (! approximatelyEqual (totalFlexGrow, 0.0))
  498. changeUnit = difference / totalFlexGrow;
  499. }
  500. else
  501. {
  502. if (! approximatelyEqual (totalFlexShrink, 0.0))
  503. changeUnit = difference / totalFlexShrink;
  504. }
  505. bool ok = true;
  506. for (int column = 0; column < numColumns; ++column)
  507. {
  508. auto& item = getItem (column, row);
  509. if (! item.locked)
  510. if (! addToItemLength (item, (positiveFlexibility ? item.item->flexGrow
  511. : item.item->flexShrink) * changeUnit, row))
  512. ok = false;
  513. }
  514. return ok;
  515. }
  516. void recalculateTotalItemLengthPerLineArray() noexcept
  517. {
  518. for (int row = 0; row < numberOfRows; ++row)
  519. {
  520. lineInfo[row].totalLength = 0;
  521. const auto numColumns = lineInfo[row].numItems;
  522. for (int column = 0; column < numColumns; ++column)
  523. lineInfo[row].totalLength += getItemMainSize (getItem (column, row));
  524. }
  525. }
  526. void reverseLocations() noexcept
  527. {
  528. if (owner.flexDirection == FlexBox::Direction::rowReverse)
  529. {
  530. for (auto& item : owner.items)
  531. item.currentBounds.setX ((float) (containerLineLength - item.currentBounds.getRight()));
  532. }
  533. else if (owner.flexDirection == FlexBox::Direction::columnReverse)
  534. {
  535. for (auto& item : owner.items)
  536. item.currentBounds.setY ((float) (containerLineLength - item.currentBounds.getBottom()));
  537. }
  538. }
  539. void reverseWrap() noexcept
  540. {
  541. if (owner.flexWrap == FlexBox::Wrap::wrapReverse)
  542. {
  543. if (isRowDirection)
  544. {
  545. for (auto& item : owner.items)
  546. item.currentBounds.setY ((float) (containerCrossLength - item.currentBounds.getBottom()));
  547. }
  548. else
  549. {
  550. for (auto& item : owner.items)
  551. item.currentBounds.setX ((float) (containerCrossLength - item.currentBounds.getRight()));
  552. }
  553. }
  554. }
  555. Coord getItemMainSize (const ItemWithState& item) const noexcept
  556. {
  557. return isRowDirection ? item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight
  558. : item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom;
  559. }
  560. Coord getItemCrossSize (const ItemWithState& item) const noexcept
  561. {
  562. return isRowDirection ? item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom
  563. : item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight;
  564. }
  565. bool addToItemLength (ItemWithState& item, const Coord length, int row) const noexcept
  566. {
  567. bool ok = false;
  568. const auto prefSize = computePreferredSize (Axis::main, item);
  569. const auto pickForMainAxis = [this] (auto& a, auto& b) -> auto& { return pickForAxis (Axis::main, a, b); };
  570. if (isAssigned (pickForMainAxis (item.item->maxWidth, item.item->maxHeight))
  571. && pickForMainAxis (item.item->maxWidth, item.item->maxHeight) < prefSize + length)
  572. {
  573. pickForMainAxis (item.lockedWidth, item.lockedHeight) = pickForMainAxis (item.item->maxWidth, item.item->maxHeight);
  574. item.locked = true;
  575. }
  576. else if (isAssigned (prefSize) && pickForMainAxis (item.item->minWidth, item.item->minHeight) > prefSize + length)
  577. {
  578. pickForMainAxis (item.lockedWidth, item.lockedHeight) = pickForMainAxis (item.item->minWidth, item.item->minHeight);
  579. item.locked = true;
  580. }
  581. else
  582. {
  583. ok = true;
  584. pickForMainAxis (item.lockedWidth, item.lockedHeight) = prefSize + length;
  585. }
  586. lineInfo[row].totalLength += pickForMainAxis (item.lockedWidth, item.lockedHeight)
  587. + pickForMainAxis (item.lockedMarginLeft, item.lockedMarginTop)
  588. + pickForMainAxis (item.lockedMarginRight, item.lockedMarginBottom);
  589. return ok;
  590. }
  591. Coord computePreferredSize (Axis axis, ItemWithState& itemWithState) const noexcept
  592. {
  593. const auto& item = *itemWithState.item;
  594. auto preferredSize = (item.flexBasis > 0 && axis == Axis::main) ? item.flexBasis
  595. : (isAssigned (getItemSize (axis, itemWithState)) ? getItemSize (axis, itemWithState)
  596. : getMinSize (axis, itemWithState));
  597. const auto minSize = getMinSize (axis, itemWithState);
  598. if (isAssigned (minSize) && preferredSize < minSize)
  599. return minSize;
  600. const auto maxSize = getMaxSize (axis, itemWithState);
  601. if (isAssigned (maxSize) && maxSize < preferredSize)
  602. return maxSize;
  603. return preferredSize;
  604. }
  605. };
  606. //==============================================================================
  607. FlexBox::FlexBox (JustifyContent jc) noexcept : justifyContent (jc) {}
  608. FlexBox::FlexBox (Direction d, Wrap w, AlignContent ac, AlignItems ai, JustifyContent jc) noexcept
  609. : flexDirection (d), flexWrap (w), alignContent (ac), alignItems (ai), justifyContent (jc)
  610. {
  611. }
  612. void FlexBox::performLayout (Rectangle<float> targetArea)
  613. {
  614. if (! items.isEmpty())
  615. {
  616. FlexBoxLayoutCalculation layout (*this, targetArea.getWidth(), targetArea.getHeight());
  617. layout.createStates();
  618. layout.initialiseItems();
  619. layout.resolveFlexibleLengths();
  620. layout.resolveAutoMarginsOnMainAxis();
  621. layout.calculateCrossSizesByLine();
  622. layout.calculateCrossSizeOfAllItems();
  623. layout.alignLinesPerAlignContent();
  624. layout.resolveAutoMarginsOnCrossAxis();
  625. layout.alignItemsInCrossAxisInLinesPerAlignSelf();
  626. layout.alignItemsByJustifyContent();
  627. layout.layoutAllItems();
  628. for (auto& item : items)
  629. {
  630. item.currentBounds += targetArea.getPosition();
  631. if (auto* comp = item.associatedComponent)
  632. comp->setBounds (Rectangle<int>::leftTopRightBottom ((int) item.currentBounds.getX(),
  633. (int) item.currentBounds.getY(),
  634. (int) item.currentBounds.getRight(),
  635. (int) item.currentBounds.getBottom()));
  636. if (auto* box = item.associatedFlexBox)
  637. box->performLayout (item.currentBounds);
  638. }
  639. }
  640. }
  641. void FlexBox::performLayout (Rectangle<int> targetArea)
  642. {
  643. performLayout (targetArea.toFloat());
  644. }
  645. //==============================================================================
  646. FlexItem::FlexItem() noexcept {}
  647. FlexItem::FlexItem (float w, float h) noexcept : currentBounds (w, h), minWidth (w), minHeight (h) {}
  648. FlexItem::FlexItem (float w, float h, Component& c) noexcept : FlexItem (w, h) { associatedComponent = &c; }
  649. FlexItem::FlexItem (float w, float h, FlexBox& fb) noexcept : FlexItem (w, h) { associatedFlexBox = &fb; }
  650. FlexItem::FlexItem (Component& c) noexcept : associatedComponent (&c) {}
  651. FlexItem::FlexItem (FlexBox& fb) noexcept : associatedFlexBox (&fb) {}
  652. FlexItem::Margin::Margin() noexcept : left(), right(), top(), bottom() {}
  653. FlexItem::Margin::Margin (float v) noexcept : left (v), right (v), top (v), bottom (v) {}
  654. FlexItem::Margin::Margin (float t, float r, float b, float l) noexcept : left (l), right (r), top (t), bottom (b) {}
  655. //==============================================================================
  656. FlexItem FlexItem::withFlex (float newFlexGrow) const noexcept
  657. {
  658. auto fi = *this;
  659. fi.flexGrow = newFlexGrow;
  660. return fi;
  661. }
  662. FlexItem FlexItem::withFlex (float newFlexGrow, float newFlexShrink) const noexcept
  663. {
  664. auto fi = withFlex (newFlexGrow);
  665. fi.flexShrink = newFlexShrink;
  666. return fi;
  667. }
  668. FlexItem FlexItem::withFlex (float newFlexGrow, float newFlexShrink, float newFlexBasis) const noexcept
  669. {
  670. auto fi = withFlex (newFlexGrow, newFlexShrink);
  671. fi.flexBasis = newFlexBasis;
  672. return fi;
  673. }
  674. FlexItem FlexItem::withWidth (float newWidth) const noexcept { auto fi = *this; fi.width = newWidth; return fi; }
  675. FlexItem FlexItem::withMinWidth (float newMinWidth) const noexcept { auto fi = *this; fi.minWidth = newMinWidth; return fi; }
  676. FlexItem FlexItem::withMaxWidth (float newMaxWidth) const noexcept { auto fi = *this; fi.maxWidth = newMaxWidth; return fi; }
  677. FlexItem FlexItem::withMinHeight (float newMinHeight) const noexcept { auto fi = *this; fi.minHeight = newMinHeight; return fi; }
  678. FlexItem FlexItem::withMaxHeight (float newMaxHeight) const noexcept { auto fi = *this; fi.maxHeight = newMaxHeight; return fi; }
  679. FlexItem FlexItem::withHeight (float newHeight) const noexcept { auto fi = *this; fi.height = newHeight; return fi; }
  680. FlexItem FlexItem::withMargin (Margin m) const noexcept { auto fi = *this; fi.margin = m; return fi; }
  681. FlexItem FlexItem::withOrder (int newOrder) const noexcept { auto fi = *this; fi.order = newOrder; return fi; }
  682. FlexItem FlexItem::withAlignSelf (AlignSelf a) const noexcept { auto fi = *this; fi.alignSelf = a; return fi; }
  683. //==============================================================================
  684. //==============================================================================
  685. #if JUCE_UNIT_TESTS
  686. class FlexBoxTests final : public UnitTest
  687. {
  688. public:
  689. FlexBoxTests() : UnitTest ("FlexBox", UnitTestCategories::gui) {}
  690. void runTest() override
  691. {
  692. using AlignSelf = FlexItem::AlignSelf;
  693. using Direction = FlexBox::Direction;
  694. const Rectangle<float> rect (10.0f, 20.0f, 300.0f, 200.0f);
  695. const auto doLayout = [&rect] (Direction direction, Array<FlexItem> items)
  696. {
  697. juce::FlexBox flex;
  698. flex.flexDirection = direction;
  699. flex.items = std::move (items);
  700. flex.performLayout (rect);
  701. return flex;
  702. };
  703. beginTest ("flex item with mostly auto properties");
  704. {
  705. const auto test = [this, &doLayout] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
  706. {
  707. const auto flex = doLayout (direction, { juce::FlexItem{}.withAlignSelf (alignment) });
  708. expect (flex.items.getFirst().currentBounds == expectedBounds);
  709. };
  710. test (Direction::row, AlignSelf::autoAlign, { rect.getX(), rect.getY(), 0.0f, rect.getHeight() });
  711. test (Direction::row, AlignSelf::stretch, { rect.getX(), rect.getY(), 0.0f, rect.getHeight() });
  712. test (Direction::row, AlignSelf::flexStart, { rect.getX(), rect.getY(), 0.0f, 0.0f });
  713. test (Direction::row, AlignSelf::flexEnd, { rect.getX(), rect.getBottom(), 0.0f, 0.0f });
  714. test (Direction::row, AlignSelf::center, { rect.getX(), rect.getCentreY(), 0.0f, 0.0f });
  715. test (Direction::column, AlignSelf::autoAlign, { rect.getX(), rect.getY(), rect.getWidth(), 0.0f });
  716. test (Direction::column, AlignSelf::stretch, { rect.getX(), rect.getY(), rect.getWidth(), 0.0f });
  717. test (Direction::column, AlignSelf::flexStart, { rect.getX(), rect.getY(), 0.0f, 0.0f });
  718. test (Direction::column, AlignSelf::flexEnd, { rect.getRight(), rect.getY(), 0.0f, 0.0f });
  719. test (Direction::column, AlignSelf::center, { rect.getCentreX(), rect.getY(), 0.0f, 0.0f });
  720. }
  721. beginTest ("flex item with specified width and height");
  722. {
  723. constexpr auto w = 50.0f;
  724. constexpr auto h = 60.0f;
  725. const auto test = [&] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
  726. {
  727. const auto flex = doLayout (direction, { juce::FlexItem().withAlignSelf (alignment)
  728. .withWidth (w)
  729. .withHeight (h) });
  730. expect (flex.items.getFirst().currentBounds == expectedBounds);
  731. };
  732. test (Direction::row, AlignSelf::autoAlign, { rect.getX(), rect.getY(), w, h });
  733. test (Direction::row, AlignSelf::stretch, { rect.getX(), rect.getY(), w, h });
  734. test (Direction::row, AlignSelf::flexStart, { rect.getX(), rect.getY(), w, h });
  735. test (Direction::row, AlignSelf::flexEnd, { rect.getX(), rect.getBottom() - h, w, h });
  736. test (Direction::row, AlignSelf::center, { rect.getX(), rect.getY() + (rect.getHeight() - h) * 0.5f, w, h });
  737. test (Direction::column, AlignSelf::autoAlign, { rect.getX(), rect.getY(), w, h });
  738. test (Direction::column, AlignSelf::stretch, { rect.getX(), rect.getY(), w, h });
  739. test (Direction::column, AlignSelf::flexStart, { rect.getX(), rect.getY(), w, h });
  740. test (Direction::column, AlignSelf::flexEnd, { rect.getRight() - w, rect.getY(), w, h });
  741. test (Direction::column, AlignSelf::center, { rect.getX() + (rect.getWidth() - w) * 0.5f, rect.getY(), w, h });
  742. }
  743. beginTest ("flex item with oversized width and height");
  744. {
  745. const auto w = rect.getWidth() * 2;
  746. const auto h = rect.getHeight() * 2;
  747. const auto test = [this, &doLayout, &w, &h] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
  748. {
  749. const auto flex = doLayout (direction, { juce::FlexItem().withAlignSelf (alignment)
  750. .withWidth (w)
  751. .withHeight (h) });
  752. expect (flex.items.getFirst().currentBounds == expectedBounds);
  753. };
  754. const Rectangle<float> baseRow (rect.getX(), rect.getY(), rect.getWidth(), h);
  755. test (Direction::row, AlignSelf::autoAlign, baseRow);
  756. test (Direction::row, AlignSelf::stretch, baseRow);
  757. test (Direction::row, AlignSelf::flexStart, baseRow);
  758. test (Direction::row, AlignSelf::flexEnd, baseRow.withBottomY (rect.getBottom()));
  759. test (Direction::row, AlignSelf::center, baseRow.withCentre (rect.getCentre()));
  760. const Rectangle<float> baseColumn (rect.getX(), rect.getY(), w, rect.getHeight());
  761. test (Direction::column, AlignSelf::autoAlign, baseColumn);
  762. test (Direction::column, AlignSelf::stretch, baseColumn);
  763. test (Direction::column, AlignSelf::flexStart, baseColumn);
  764. test (Direction::column, AlignSelf::flexEnd, baseColumn.withRightX (rect.getRight()));
  765. test (Direction::column, AlignSelf::center, baseColumn.withCentre (rect.getCentre()));
  766. }
  767. beginTest ("flex item with minimum width and height");
  768. {
  769. constexpr auto w = 50.0f;
  770. constexpr auto h = 60.0f;
  771. const auto test = [&] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
  772. {
  773. const auto flex = doLayout (direction, { juce::FlexItem().withAlignSelf (alignment)
  774. .withMinWidth (w)
  775. .withMinHeight (h) });
  776. expect (flex.items.getFirst().currentBounds == expectedBounds);
  777. };
  778. test (Direction::row, AlignSelf::autoAlign, { rect.getX(), rect.getY(), w, rect.getHeight() });
  779. test (Direction::row, AlignSelf::stretch, { rect.getX(), rect.getY(), w, rect.getHeight() });
  780. test (Direction::row, AlignSelf::flexStart, { rect.getX(), rect.getY(), w, h });
  781. test (Direction::row, AlignSelf::flexEnd, { rect.getX(), rect.getBottom() - h, w, h });
  782. test (Direction::row, AlignSelf::center, { rect.getX(), rect.getY() + (rect.getHeight() - h) * 0.5f, w, h });
  783. test (Direction::column, AlignSelf::autoAlign, { rect.getX(), rect.getY(), rect.getWidth(), h });
  784. test (Direction::column, AlignSelf::stretch, { rect.getX(), rect.getY(), rect.getWidth(), h });
  785. test (Direction::column, AlignSelf::flexStart, { rect.getX(), rect.getY(), w, h });
  786. test (Direction::column, AlignSelf::flexEnd, { rect.getRight() - w, rect.getY(), w, h });
  787. test (Direction::column, AlignSelf::center, { rect.getX() + (rect.getWidth() - w) * 0.5f, rect.getY(), w, h });
  788. }
  789. beginTest ("flex item with maximum width and height");
  790. {
  791. constexpr auto w = 50.0f;
  792. constexpr auto h = 60.0f;
  793. const auto test = [&] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
  794. {
  795. const auto flex = doLayout (direction, { juce::FlexItem().withAlignSelf (alignment)
  796. .withMaxWidth (w)
  797. .withMaxHeight (h) });
  798. expect (flex.items.getFirst().currentBounds == expectedBounds);
  799. };
  800. test (Direction::row, AlignSelf::autoAlign, { rect.getX(), rect.getY(), 0.0f, h });
  801. test (Direction::row, AlignSelf::stretch, { rect.getX(), rect.getY(), 0.0f, h });
  802. test (Direction::row, AlignSelf::flexStart, { rect.getX(), rect.getY(), 0.0f, 0.0f });
  803. test (Direction::row, AlignSelf::flexEnd, { rect.getX(), rect.getBottom(), 0.0f, 0.0f });
  804. test (Direction::row, AlignSelf::center, { rect.getX(), rect.getCentreY(), 0.0f, 0.0f });
  805. test (Direction::column, AlignSelf::autoAlign, { rect.getX(), rect.getY(), w, 0.0f });
  806. test (Direction::column, AlignSelf::stretch, { rect.getX(), rect.getY(), w, 0.0f });
  807. test (Direction::column, AlignSelf::flexStart, { rect.getX(), rect.getY(), 0.0f, 0.0f });
  808. test (Direction::column, AlignSelf::flexEnd, { rect.getRight(), rect.getY(), 0.0f, 0.0f });
  809. test (Direction::column, AlignSelf::center, { rect.getCentreX(), rect.getY(), 0.0f, 0.0f });
  810. }
  811. beginTest ("flex item with specified flex");
  812. {
  813. const auto test = [this, &doLayout] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
  814. {
  815. const auto flex = doLayout (direction, { juce::FlexItem().withAlignSelf (alignment).withFlex (1.0f) });
  816. expect (flex.items.getFirst().currentBounds == expectedBounds);
  817. };
  818. test (Direction::row, AlignSelf::autoAlign, { rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight() });
  819. test (Direction::row, AlignSelf::stretch, { rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight() });
  820. test (Direction::row, AlignSelf::flexStart, { rect.getX(), rect.getY(), rect.getWidth(), 0.0f });
  821. test (Direction::row, AlignSelf::flexEnd, { rect.getX(), rect.getBottom(), rect.getWidth(), 0.0f });
  822. test (Direction::row, AlignSelf::center, { rect.getX(), rect.getCentreY(), rect.getWidth(), 0.0f });
  823. test (Direction::column, AlignSelf::autoAlign, { rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight() });
  824. test (Direction::column, AlignSelf::stretch, { rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight() });
  825. test (Direction::column, AlignSelf::flexStart, { rect.getX(), rect.getY(), 0.0f, rect.getHeight() });
  826. test (Direction::column, AlignSelf::flexEnd, { rect.getRight(), rect.getY(), 0.0f, rect.getHeight() });
  827. test (Direction::column, AlignSelf::center, { rect.getCentreX(), rect.getY(), 0.0f, rect.getHeight() });
  828. }
  829. beginTest ("flex item with margin");
  830. {
  831. const FlexItem::Margin margin (10.0f, 20.0f, 30.0f, 40.0f);
  832. const auto test = [this, &doLayout, &margin] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
  833. {
  834. const auto flex = doLayout (direction, { juce::FlexItem().withAlignSelf (alignment).withMargin (margin) });
  835. expect (flex.items.getFirst().currentBounds == expectedBounds);
  836. };
  837. const auto remainingHeight = rect.getHeight() - margin.top - margin.bottom;
  838. const auto remainingWidth = rect.getWidth() - margin.left - margin.right;
  839. test (Direction::row, AlignSelf::autoAlign, { rect.getX() + margin.left, rect.getY() + margin.top, 0.0f, remainingHeight });
  840. test (Direction::row, AlignSelf::stretch, { rect.getX() + margin.left, rect.getY() + margin.top, 0.0f, remainingHeight });
  841. test (Direction::row, AlignSelf::flexStart, { rect.getX() + margin.left, rect.getY() + margin.top, 0.0f, 0.0f });
  842. test (Direction::row, AlignSelf::flexEnd, { rect.getX() + margin.left, rect.getBottom() - margin.bottom, 0.0f, 0.0f });
  843. test (Direction::row, AlignSelf::center, { rect.getX() + margin.left, rect.getY() + margin.top + remainingHeight * 0.5f, 0.0f, 0.0f });
  844. test (Direction::column, AlignSelf::autoAlign, { rect.getX() + margin.left, rect.getY() + margin.top, remainingWidth, 0.0f });
  845. test (Direction::column, AlignSelf::stretch, { rect.getX() + margin.left, rect.getY() + margin.top, remainingWidth, 0.0f });
  846. test (Direction::column, AlignSelf::flexStart, { rect.getX() + margin.left, rect.getY() + margin.top, 0.0f, 0.0f });
  847. test (Direction::column, AlignSelf::flexEnd, { rect.getRight() - margin.right, rect.getY() + margin.top, 0.0f, 0.0f });
  848. test (Direction::column, AlignSelf::center, { rect.getX() + margin.left + remainingWidth * 0.5f, rect.getY() + margin.top, 0.0f, 0.0f });
  849. }
  850. const AlignSelf alignments[] { AlignSelf::autoAlign,
  851. AlignSelf::stretch,
  852. AlignSelf::flexStart,
  853. AlignSelf::flexEnd,
  854. AlignSelf::center };
  855. beginTest ("flex item with auto margin");
  856. {
  857. for (const auto& alignment : alignments)
  858. {
  859. for (const auto& direction : { Direction::row, Direction::column })
  860. {
  861. const auto flex = doLayout (direction, { juce::FlexItem().withAlignSelf (alignment)
  862. .withMargin ((float) FlexItem::autoValue) });
  863. expect (flex.items.getFirst().currentBounds == Rectangle<float> (rect.getCentre(), rect.getCentre()));
  864. }
  865. }
  866. const auto testTop = [this, &doLayout] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
  867. {
  868. const auto flex = doLayout (direction, { juce::FlexItem().withAlignSelf (alignment)
  869. .withMargin ({ (float) FlexItem::autoValue, 0.0f, 0.0f, 0.0f }) });
  870. expect (flex.items.getFirst().currentBounds == expectedBounds);
  871. };
  872. for (const auto& alignment : alignments)
  873. testTop (Direction::row, alignment, { rect.getX(), rect.getBottom(), 0.0f, 0.0f });
  874. testTop (Direction::column, AlignSelf::autoAlign, { rect.getX(), rect.getBottom(), rect.getWidth(), 0.0f });
  875. testTop (Direction::column, AlignSelf::stretch, { rect.getX(), rect.getBottom(), rect.getWidth(), 0.0f });
  876. testTop (Direction::column, AlignSelf::flexStart, { rect.getX(), rect.getBottom(), 0.0f, 0.0f });
  877. testTop (Direction::column, AlignSelf::flexEnd, { rect.getRight(), rect.getBottom(), 0.0f, 0.0f });
  878. testTop (Direction::column, AlignSelf::center, { rect.getCentreX(), rect.getBottom(), 0.0f, 0.0f });
  879. const auto testBottom = [this, &doLayout] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
  880. {
  881. const auto flex = doLayout (direction, { juce::FlexItem().withAlignSelf (alignment)
  882. .withMargin ({ 0.0f, 0.0f, (float) FlexItem::autoValue, 0.0f }) });
  883. expect (flex.items.getFirst().currentBounds == expectedBounds);
  884. };
  885. for (const auto& alignment : alignments)
  886. testBottom (Direction::row, alignment, { rect.getX(), rect.getY(), 0.0f, 0.0f });
  887. testBottom (Direction::column, AlignSelf::autoAlign, { rect.getX(), rect.getY(), rect.getWidth(), 0.0f });
  888. testBottom (Direction::column, AlignSelf::stretch, { rect.getX(), rect.getY(), rect.getWidth(), 0.0f });
  889. testBottom (Direction::column, AlignSelf::flexStart, { rect.getX(), rect.getY(), 0.0f, 0.0f });
  890. testBottom (Direction::column, AlignSelf::flexEnd, { rect.getRight(), rect.getY(), 0.0f, 0.0f });
  891. testBottom (Direction::column, AlignSelf::center, { rect.getCentreX(), rect.getY(), 0.0f, 0.0f });
  892. const auto testLeft = [this, &doLayout] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
  893. {
  894. const auto flex = doLayout (direction, { juce::FlexItem().withAlignSelf (alignment)
  895. .withMargin ({ 0.0f, 0.0f, 0.0f, (float) FlexItem::autoValue }) });
  896. expect (flex.items.getFirst().currentBounds == expectedBounds);
  897. };
  898. testLeft (Direction::row, AlignSelf::autoAlign, { rect.getRight(), rect.getY(), 0.0f, rect.getHeight() });
  899. testLeft (Direction::row, AlignSelf::stretch, { rect.getRight(), rect.getY(), 0.0f, rect.getHeight() });
  900. testLeft (Direction::row, AlignSelf::flexStart, { rect.getRight(), rect.getY(), 0.0f, 0.0f });
  901. testLeft (Direction::row, AlignSelf::flexEnd, { rect.getRight(), rect.getBottom(), 0.0f, 0.0f });
  902. testLeft (Direction::row, AlignSelf::center, { rect.getRight(), rect.getCentreY(), 0.0f, 0.0f });
  903. for (const auto& alignment : alignments)
  904. testLeft (Direction::column, alignment, { rect.getRight(), rect.getY(), 0.0f, 0.0f });
  905. const auto testRight = [this, &doLayout] (Direction direction, AlignSelf alignment, Rectangle<float> expectedBounds)
  906. {
  907. const auto flex = doLayout (direction, { juce::FlexItem().withAlignSelf (alignment)
  908. .withMargin ({ 0.0f, (float) FlexItem::autoValue, 0.0f, 0.0f }) });
  909. expect (flex.items.getFirst().currentBounds == expectedBounds);
  910. };
  911. testRight (Direction::row, AlignSelf::autoAlign, { rect.getX(), rect.getY(), 0.0f, rect.getHeight() });
  912. testRight (Direction::row, AlignSelf::stretch, { rect.getX(), rect.getY(), 0.0f, rect.getHeight() });
  913. testRight (Direction::row, AlignSelf::flexStart, { rect.getX(), rect.getY(), 0.0f, 0.0f });
  914. testRight (Direction::row, AlignSelf::flexEnd, { rect.getX(), rect.getBottom(), 0.0f, 0.0f });
  915. testRight (Direction::row, AlignSelf::center, { rect.getX(), rect.getCentreY(), 0.0f, 0.0f });
  916. for (const auto& alignment : alignments)
  917. testRight (Direction::column, alignment, { rect.getX(), rect.getY(), 0.0f, 0.0f });
  918. }
  919. beginTest ("in a multiline layout, items too large to fit on the main axis are given a line to themselves");
  920. {
  921. const auto spacer = 10.0f;
  922. for (const auto alignment : alignments)
  923. {
  924. juce::FlexBox flex;
  925. flex.flexWrap = FlexBox::Wrap::wrap;
  926. flex.items = { FlexItem().withAlignSelf (alignment)
  927. .withWidth (spacer)
  928. .withHeight (spacer),
  929. FlexItem().withAlignSelf (alignment)
  930. .withWidth (rect.getWidth() * 2)
  931. .withHeight (rect.getHeight()),
  932. FlexItem().withAlignSelf (alignment)
  933. .withWidth (spacer)
  934. .withHeight (spacer) };
  935. flex.performLayout (rect);
  936. expect (flex.items[0].currentBounds == Rectangle<float> (rect.getX(), rect.getY(), spacer, spacer));
  937. expect (flex.items[1].currentBounds == Rectangle<float> (rect.getX(), rect.getY() + spacer, rect.getWidth(), rect.getHeight()));
  938. expect (flex.items[2].currentBounds == Rectangle<float> (rect.getX(), rect.getBottom() + spacer, 10.0f, 10.0f));
  939. }
  940. }
  941. }
  942. };
  943. static FlexBoxTests flexBoxTests;
  944. #endif
  945. } // namespace juce