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.

1181 lines
54KB

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