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.

1145 lines
52KB

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