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.

juce_FlexBox.cpp 52KB

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