Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1135 lines
52KB

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