juce_FlexBox.cpp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861
  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  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 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. namespace juce
  20. {
  21. struct FlexBoxLayoutCalculation
  22. {
  23. using Coord = double;
  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 (isRowDirection ? parentWidth : parentHeight)
  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. void setWidthChecked (Coord newWidth) noexcept
  51. {
  52. if (isAssigned (item->maxWidth)) newWidth = jmin (newWidth, static_cast<Coord> (item->maxWidth));
  53. if (isAssigned (item->minWidth)) newWidth = jmax (newWidth, static_cast<Coord> (item->minWidth));
  54. lockedWidth = newWidth;
  55. }
  56. void setHeightChecked (Coord newHeight) noexcept
  57. {
  58. if (isAssigned (item->maxHeight)) newHeight = jmin (newHeight, (Coord) item->maxHeight);
  59. if (isAssigned (item->minHeight)) newHeight = jmax (newHeight, (Coord) item->minHeight);
  60. lockedHeight = newHeight;
  61. }
  62. };
  63. struct RowInfo
  64. {
  65. int numItems;
  66. Coord crossSize, lineY, totalLength;
  67. };
  68. FlexBox& owner;
  69. const Coord parentWidth, parentHeight;
  70. const int numItems;
  71. const bool isRowDirection;
  72. const Coord containerLineLength;
  73. int numberOfRows = 1;
  74. Coord containerCrossLength = 0;
  75. HeapBlock<ItemWithState*> lineItems;
  76. HeapBlock<RowInfo> lineInfo;
  77. Array<ItemWithState> itemStates;
  78. ItemWithState& getItem (int x, int y) const noexcept { return *lineItems[y * numItems + x]; }
  79. static bool isAuto (Coord value) noexcept { return value == FlexItem::autoValue; }
  80. static bool isAssigned (Coord value) noexcept { return value != FlexItem::notAssigned; }
  81. static Coord getValueOrZeroIfAuto (Coord value) noexcept { return isAuto (value) ? Coord() : value; }
  82. //==============================================================================
  83. void createStates()
  84. {
  85. itemStates.ensureStorageAllocated (numItems);
  86. for (auto& item : owner.items)
  87. itemStates.add (item);
  88. std::stable_sort (itemStates.begin(), itemStates.end(),
  89. [] (const ItemWithState& i1, const ItemWithState& i2) { return i1.item->order < i2.item->order; });
  90. for (auto& item : itemStates)
  91. {
  92. item.preferredWidth = getPreferredWidth (item);
  93. item.preferredHeight = getPreferredHeight (item);
  94. }
  95. }
  96. void initialiseItems() noexcept
  97. {
  98. if (owner.flexWrap == FlexBox::Wrap::noWrap) // for single-line, all items go in line 1
  99. {
  100. lineInfo[0].numItems = numItems;
  101. int i = 0;
  102. for (auto& item : itemStates)
  103. {
  104. item.resetItemLockedSize();
  105. lineItems[i++] = &item;
  106. }
  107. }
  108. else // if multi-line, group the flexbox items into multiple lines
  109. {
  110. auto currentLength = containerLineLength;
  111. int column = 0, row = 0;
  112. bool firstRow = true;
  113. for (auto& item : itemStates)
  114. {
  115. item.resetItemLockedSize();
  116. const auto flexitemLength = getItemLength (item);
  117. if (flexitemLength > currentLength)
  118. {
  119. if (! firstRow)
  120. row++;
  121. if (row >= numItems)
  122. break;
  123. column = 0;
  124. currentLength = containerLineLength;
  125. numberOfRows = jmax (numberOfRows, row + 1);
  126. }
  127. currentLength -= flexitemLength;
  128. lineItems[row * numItems + column] = &item;
  129. ++column;
  130. lineInfo[row].numItems = jmax (lineInfo[row].numItems, column);
  131. firstRow = false;
  132. }
  133. }
  134. }
  135. void resolveFlexibleLengths() noexcept
  136. {
  137. for (int row = 0; row < numberOfRows; ++row)
  138. {
  139. resetRowItems (row);
  140. for (int maxLoops = numItems; --maxLoops >= 0;)
  141. {
  142. resetUnlockedRowItems (row);
  143. if (layoutRowItems (row))
  144. break;
  145. }
  146. }
  147. }
  148. void resolveAutoMarginsOnMainAxis() noexcept
  149. {
  150. for (int row = 0; row < numberOfRows; ++row)
  151. {
  152. Coord allFlexGrow = 0;
  153. const auto numColumns = lineInfo[row].numItems;
  154. const auto remainingLength = containerLineLength - lineInfo[row].totalLength;
  155. for (int column = 0; column < numColumns; ++column)
  156. {
  157. auto& item = getItem (column, row);
  158. if (isRowDirection)
  159. {
  160. if (isAuto (item.item->margin.left)) ++allFlexGrow;
  161. if (isAuto (item.item->margin.right)) ++allFlexGrow;
  162. }
  163. else
  164. {
  165. if (isAuto (item.item->margin.top)) ++allFlexGrow;
  166. if (isAuto (item.item->margin.bottom)) ++allFlexGrow;
  167. }
  168. }
  169. auto changeUnitWidth = remainingLength / allFlexGrow;
  170. if (changeUnitWidth > 0)
  171. {
  172. for (int column = 0; column < numColumns; ++column)
  173. {
  174. auto& item = getItem (column, row);
  175. if (isRowDirection)
  176. {
  177. if (isAuto (item.item->margin.left)) item.lockedMarginLeft = changeUnitWidth;
  178. if (isAuto (item.item->margin.right)) item.lockedMarginRight = changeUnitWidth;
  179. }
  180. else
  181. {
  182. if (isAuto (item.item->margin.top)) item.lockedMarginTop = changeUnitWidth;
  183. if (isAuto (item.item->margin.bottom)) item.lockedMarginBottom = changeUnitWidth;
  184. }
  185. }
  186. }
  187. }
  188. }
  189. void calculateCrossSizesByLine() noexcept
  190. {
  191. for (int row = 0; row < numberOfRows; ++row)
  192. {
  193. Coord maxSize = 0;
  194. const auto numColumns = lineInfo[row].numItems;
  195. for (int column = 0; column < numColumns; ++column)
  196. {
  197. auto& item = getItem (column, row);
  198. maxSize = jmax (maxSize, isRowDirection ? item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom
  199. : item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight);
  200. }
  201. lineInfo[row].crossSize = maxSize;
  202. }
  203. }
  204. void calculateCrossSizeOfAllItems() noexcept
  205. {
  206. for (int row = 0; row < numberOfRows; ++row)
  207. {
  208. const auto numColumns = lineInfo[row].numItems;
  209. for (int column = 0; column < numColumns; ++column)
  210. {
  211. auto& item = getItem (column, row);
  212. if (isAssigned (item.item->maxHeight) && item.lockedHeight > item.item->maxHeight)
  213. item.lockedHeight = item.item->maxHeight;
  214. if (isAssigned (item.item->maxWidth) && item.lockedWidth > item.item->maxWidth)
  215. item.lockedWidth = item.item->maxWidth;
  216. }
  217. }
  218. }
  219. void alignLinesPerAlignContent() noexcept
  220. {
  221. containerCrossLength = isRowDirection ? parentHeight : parentWidth;
  222. if (owner.alignContent == FlexBox::AlignContent::flexStart)
  223. {
  224. for (int row = 0; row < numberOfRows; ++row)
  225. for (int row2 = row; row2 < numberOfRows; ++row2)
  226. lineInfo[row].lineY = row == 0 ? 0 : lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
  227. }
  228. else if (owner.alignContent == FlexBox::AlignContent::flexEnd)
  229. {
  230. for (int row = 0; row < numberOfRows; ++row)
  231. {
  232. Coord crossHeights = 0;
  233. for (int row2 = row; row2 < numberOfRows; ++row2)
  234. crossHeights += lineInfo[row2].crossSize;
  235. lineInfo[row].lineY = containerCrossLength - crossHeights;
  236. }
  237. }
  238. else
  239. {
  240. Coord totalHeight = 0;
  241. for (int row = 0; row < numberOfRows; ++row)
  242. totalHeight += lineInfo[row].crossSize;
  243. if (owner.alignContent == FlexBox::AlignContent::stretch)
  244. {
  245. const auto difference = jmax (Coord(), (containerCrossLength - totalHeight) / numberOfRows);
  246. for (int row = 0; row < numberOfRows; ++row)
  247. {
  248. lineInfo[row].crossSize += difference;
  249. lineInfo[row].lineY = row == 0 ? 0 : lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
  250. }
  251. }
  252. else if (owner.alignContent == FlexBox::AlignContent::center)
  253. {
  254. const auto additionalength = (containerCrossLength - totalHeight) / 2;
  255. for (int row = 0; row < numberOfRows; ++row)
  256. lineInfo[row].lineY = row == 0 ? additionalength : lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
  257. }
  258. else if (owner.alignContent == FlexBox::AlignContent::spaceBetween)
  259. {
  260. const auto additionalength = numberOfRows <= 1 ? Coord() : jmax (Coord(), (containerCrossLength - totalHeight)
  261. / static_cast<Coord> (numberOfRows - 1));
  262. lineInfo[0].lineY = 0;
  263. for (int row = 1; row < numberOfRows; ++row)
  264. lineInfo[row].lineY += additionalength + lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
  265. }
  266. else if (owner.alignContent == FlexBox::AlignContent::spaceAround)
  267. {
  268. const auto additionalength = numberOfRows <= 1 ? Coord() : jmax (Coord(), (containerCrossLength - totalHeight)
  269. / static_cast<Coord> (2 + (2 * (numberOfRows - 1))));
  270. lineInfo[0].lineY = additionalength;
  271. for (int row = 1; row < numberOfRows; ++row)
  272. lineInfo[row].lineY += (2 * additionalength) + lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
  273. }
  274. }
  275. }
  276. void resolveAutoMarginsOnCrossAxis() noexcept
  277. {
  278. for (int row = 0; row < numberOfRows; ++row)
  279. {
  280. const auto numColumns = lineInfo[row].numItems;
  281. const auto crossSizeForLine = lineInfo[row].crossSize;
  282. for (int column = 0; column < numColumns; ++column)
  283. {
  284. auto& item = getItem (column, row);
  285. if (isRowDirection)
  286. {
  287. if (isAuto (item.item->margin.top) && isAuto (item.item->margin.bottom))
  288. item.lockedMarginTop = (crossSizeForLine - item.lockedHeight) / 2;
  289. else if (isAuto (item.item->margin.top))
  290. item.lockedMarginTop = crossSizeForLine - item.lockedHeight - item.item->margin.bottom;
  291. }
  292. else if (isAuto (item.item->margin.left) && isAuto (item.item->margin.right))
  293. {
  294. item.lockedMarginLeft = jmax (Coord(), (crossSizeForLine - item.lockedWidth) / 2);
  295. }
  296. else if (isAuto (item.item->margin.top))
  297. {
  298. item.lockedMarginLeft = jmax (Coord(), crossSizeForLine - item.lockedHeight - item.item->margin.bottom);
  299. }
  300. }
  301. }
  302. }
  303. void alignItemsInCrossAxisInLinesPerAlignItems() noexcept
  304. {
  305. for (int row = 0; row < numberOfRows; ++row)
  306. {
  307. const auto numColumns = lineInfo[row].numItems;
  308. const auto lineSize = lineInfo[row].crossSize;
  309. for (int column = 0; column < numColumns; ++column)
  310. {
  311. auto& item = getItem (column, row);
  312. if (item.item->alignSelf == FlexItem::AlignSelf::autoAlign)
  313. {
  314. if (owner.alignItems == FlexBox::AlignItems::stretch)
  315. {
  316. item.lockedMarginTop = item.item->margin.top;
  317. if (isRowDirection)
  318. item.setHeightChecked (lineSize - item.item->margin.top - item.item->margin.bottom);
  319. else
  320. item.setWidthChecked (lineSize - item.item->margin.left - item.item->margin.right);
  321. }
  322. else if (owner.alignItems == FlexBox::AlignItems::flexStart)
  323. {
  324. item.lockedMarginTop = item.item->margin.top;
  325. }
  326. else if (owner.alignItems == FlexBox::AlignItems::flexEnd)
  327. {
  328. if (isRowDirection)
  329. item.lockedMarginTop = lineSize - item.lockedHeight - item.item->margin.bottom;
  330. else
  331. item.lockedMarginLeft = lineSize - item.lockedWidth - item.item->margin.right;
  332. }
  333. else if (owner.alignItems == FlexBox::AlignItems::center)
  334. {
  335. if (isRowDirection)
  336. item.lockedMarginTop = (lineSize - item.lockedHeight - item.item->margin.top - item.item->margin.bottom) / 2;
  337. else
  338. item.lockedMarginLeft = (lineSize - item.lockedWidth - item.item->margin.left - item.item->margin.right) / 2;
  339. }
  340. }
  341. }
  342. }
  343. }
  344. void alignLinesPerAlignSelf() noexcept
  345. {
  346. for (int row = 0; row < numberOfRows; ++row)
  347. {
  348. const auto numColumns = lineInfo[row].numItems;
  349. const auto lineSize = lineInfo[row].crossSize;
  350. for (int column = 0; column < numColumns; ++column)
  351. {
  352. auto& item = getItem (column, row);
  353. if (! isAuto (item.item->margin.top))
  354. {
  355. if (item.item->alignSelf == FlexItem::AlignSelf::flexStart)
  356. {
  357. if (isRowDirection)
  358. item.lockedMarginTop = item.item->margin.top;
  359. else
  360. item.lockedMarginLeft = item.item->margin.left;
  361. }
  362. else if (item.item->alignSelf == FlexItem::AlignSelf::flexEnd)
  363. {
  364. if (isRowDirection)
  365. item.lockedMarginTop = lineSize - item.lockedHeight - item.item->margin.bottom;
  366. else
  367. item.lockedMarginLeft = lineSize - item.lockedWidth - item.item->margin.right;
  368. }
  369. else if (item.item->alignSelf == FlexItem::AlignSelf::center)
  370. {
  371. if (isRowDirection)
  372. item.lockedMarginTop = item.item->margin.top + (lineSize - item.lockedHeight - item.item->margin.top - item.item->margin.bottom) / 2;
  373. else
  374. item.lockedMarginLeft = item.item->margin.left + (lineSize - item.lockedWidth - item.item->margin.left - item.item->margin.right) / 2;
  375. }
  376. else if (item.item->alignSelf == FlexItem::AlignSelf::stretch)
  377. {
  378. item.lockedMarginTop = item.item->margin.top;
  379. item.lockedMarginLeft = item.item->margin.left;
  380. if (isRowDirection)
  381. item.setHeightChecked (isAssigned (item.item->height) ? getPreferredHeight (item)
  382. : lineSize - item.item->margin.top - item.item->margin.bottom);
  383. else
  384. item.setWidthChecked (isAssigned (item.item->width) ? getPreferredWidth (item)
  385. : lineSize - item.item->margin.left - item.item->margin.right);
  386. }
  387. }
  388. }
  389. }
  390. }
  391. void alignItemsByJustifyContent() noexcept
  392. {
  393. Coord additionalMarginRight = 0, additionalMarginLeft = 0;
  394. recalculateTotalItemLengthPerLineArray();
  395. for (int row = 0; row < numberOfRows; ++row)
  396. {
  397. const auto numColumns = lineInfo[row].numItems;
  398. Coord x = 0;
  399. if (owner.justifyContent == FlexBox::JustifyContent::flexEnd)
  400. {
  401. x = containerLineLength - lineInfo[row].totalLength;
  402. }
  403. else if (owner.justifyContent == FlexBox::JustifyContent::center)
  404. {
  405. x = (containerLineLength - lineInfo[row].totalLength) / 2;
  406. }
  407. else if (owner.justifyContent == FlexBox::JustifyContent::spaceBetween)
  408. {
  409. additionalMarginRight
  410. = jmax (Coord(), (containerLineLength - lineInfo[row].totalLength) / jmax (1, numColumns - 1));
  411. }
  412. else if (owner.justifyContent == FlexBox::JustifyContent::spaceAround)
  413. {
  414. additionalMarginLeft = additionalMarginRight
  415. = jmax (Coord(), (containerLineLength - lineInfo[row].totalLength) / jmax (1, 2 * numColumns));
  416. }
  417. for (int column = 0; column < numColumns; ++column)
  418. {
  419. auto& item = getItem (column, row);
  420. if (isRowDirection)
  421. {
  422. item.lockedMarginLeft += additionalMarginLeft;
  423. item.lockedMarginRight += additionalMarginRight;
  424. item.item->currentBounds.setPosition ((float) (x + item.lockedMarginLeft), (float) item.lockedMarginTop);
  425. x += item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight;
  426. }
  427. else
  428. {
  429. item.lockedMarginTop += additionalMarginLeft;
  430. item.lockedMarginBottom += additionalMarginRight;
  431. item.item->currentBounds.setPosition ((float) item.lockedMarginLeft, (float) (x + item.lockedMarginTop));
  432. x += item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom;
  433. }
  434. }
  435. }
  436. }
  437. void layoutAllItems() noexcept
  438. {
  439. for (int row = 0; row < numberOfRows; ++row)
  440. {
  441. const auto lineY = lineInfo[row].lineY;
  442. const auto numColumns = lineInfo[row].numItems;
  443. for (int column = 0; column < numColumns; ++column)
  444. {
  445. auto& item = getItem (column, row);
  446. if (isRowDirection)
  447. item.item->currentBounds.setY ((float) (lineY + item.lockedMarginTop));
  448. else
  449. item.item->currentBounds.setX ((float) (lineY + item.lockedMarginLeft));
  450. item.item->currentBounds.setSize ((float) item.lockedWidth,
  451. (float) item.lockedHeight);
  452. }
  453. }
  454. reverseLocations();
  455. reverseWrap();
  456. }
  457. private:
  458. void resetRowItems (const int row) noexcept
  459. {
  460. const auto numColumns = lineInfo[row].numItems;
  461. for (int column = 0; column < numColumns; ++column)
  462. resetItem (getItem (column, row));
  463. }
  464. void resetUnlockedRowItems (const int row) noexcept
  465. {
  466. const auto numColumns = lineInfo[row].numItems;
  467. for (int column = 0; column < numColumns; ++column)
  468. {
  469. auto& item = getItem (column, row);
  470. if (! item.locked)
  471. resetItem (item);
  472. }
  473. }
  474. void resetItem (ItemWithState& item) noexcept
  475. {
  476. item.locked = false;
  477. item.lockedWidth = getPreferredWidth (item);
  478. item.lockedHeight = getPreferredHeight (item);
  479. }
  480. bool layoutRowItems (const int row) noexcept
  481. {
  482. const auto numColumns = lineInfo[row].numItems;
  483. auto flexContainerLength = containerLineLength;
  484. Coord totalItemsLength = 0, totalFlexGrow = 0, totalFlexShrink = 0;
  485. for (int column = 0; column < numColumns; ++column)
  486. {
  487. const auto& item = getItem (column, row);
  488. if (item.locked)
  489. {
  490. flexContainerLength -= getItemLength (item);
  491. }
  492. else
  493. {
  494. totalItemsLength += getItemLength (item);
  495. totalFlexGrow += item.item->flexGrow;
  496. totalFlexShrink += item.item->flexShrink;
  497. }
  498. }
  499. Coord changeUnit = 0;
  500. const auto difference = flexContainerLength - totalItemsLength;
  501. const bool positiveFlexibility = difference > 0;
  502. if (positiveFlexibility)
  503. {
  504. if (totalFlexGrow != 0.0)
  505. changeUnit = difference / totalFlexGrow;
  506. }
  507. else
  508. {
  509. if (totalFlexShrink != 0.0)
  510. changeUnit = difference / totalFlexShrink;
  511. }
  512. bool ok = true;
  513. for (int column = 0; column < numColumns; ++column)
  514. {
  515. auto& item = getItem (column, row);
  516. if (! item.locked)
  517. if (! addToItemLength (item, (positiveFlexibility ? item.item->flexGrow
  518. : item.item->flexShrink) * changeUnit, row))
  519. ok = false;
  520. }
  521. return ok;
  522. }
  523. void recalculateTotalItemLengthPerLineArray() noexcept
  524. {
  525. for (int row = 0; row < numberOfRows; ++row)
  526. {
  527. lineInfo[row].totalLength = 0;
  528. const auto numColumns = lineInfo[row].numItems;
  529. for (int column = 0; column < numColumns; ++column)
  530. {
  531. const auto& item = getItem (column, row);
  532. lineInfo[row].totalLength += isRowDirection ? item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight
  533. : item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom;
  534. }
  535. }
  536. }
  537. void reverseLocations() noexcept
  538. {
  539. if (owner.flexDirection == FlexBox::Direction::rowReverse)
  540. {
  541. for (auto& item : owner.items)
  542. item.currentBounds.setX ((float) (containerLineLength - item.currentBounds.getRight()));
  543. }
  544. else if (owner.flexDirection == FlexBox::Direction::columnReverse)
  545. {
  546. for (auto& item : owner.items)
  547. item.currentBounds.setY ((float) (containerLineLength - item.currentBounds.getBottom()));
  548. }
  549. }
  550. void reverseWrap() noexcept
  551. {
  552. if (owner.flexWrap == FlexBox::Wrap::wrapReverse)
  553. {
  554. if (isRowDirection)
  555. {
  556. for (auto& item : owner.items)
  557. item.currentBounds.setY ((float) (containerCrossLength - item.currentBounds.getBottom()));
  558. }
  559. else
  560. {
  561. for (auto& item : owner.items)
  562. item.currentBounds.setX ((float) (containerCrossLength - item.currentBounds.getRight()));
  563. }
  564. }
  565. }
  566. Coord getItemLength (const ItemWithState& item) const noexcept
  567. {
  568. return isRowDirection ? item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight
  569. : item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom;
  570. }
  571. Coord getItemCrossSize (const ItemWithState& item) const noexcept
  572. {
  573. return isRowDirection ? item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom
  574. : item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight;
  575. }
  576. bool addToItemLength (ItemWithState& item, const Coord length, int row) const noexcept
  577. {
  578. bool ok = false;
  579. if (isRowDirection)
  580. {
  581. const auto prefWidth = getPreferredWidth (item);
  582. if (isAssigned (item.item->maxWidth) && item.item->maxWidth < prefWidth + length)
  583. {
  584. item.lockedWidth = item.item->maxWidth;
  585. item.locked = true;
  586. }
  587. else if (isAssigned (prefWidth) && item.item->minWidth > prefWidth + length)
  588. {
  589. item.lockedWidth = item.item->minWidth;
  590. item.locked = true;
  591. }
  592. else
  593. {
  594. ok = true;
  595. item.lockedWidth = prefWidth + length;
  596. }
  597. lineInfo[row].totalLength += item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight;
  598. }
  599. else
  600. {
  601. const auto prefHeight = getPreferredHeight (item);
  602. if (isAssigned (item.item->maxHeight) && item.item->maxHeight < prefHeight + length)
  603. {
  604. item.lockedHeight = item.item->maxHeight;
  605. item.locked = true;
  606. }
  607. else if (isAssigned (prefHeight) && item.item->minHeight > prefHeight + length)
  608. {
  609. item.lockedHeight = item.item->minHeight;
  610. item.locked = true;
  611. }
  612. else
  613. {
  614. ok = true;
  615. item.lockedHeight = prefHeight + length;
  616. }
  617. lineInfo[row].totalLength += item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom;
  618. }
  619. return ok;
  620. }
  621. Coord getPreferredWidth (const ItemWithState& itemWithState) const noexcept
  622. {
  623. const auto& item = *itemWithState.item;
  624. auto preferredWidth = (item.flexBasis > 0 && isRowDirection)
  625. ? item.flexBasis
  626. : (isAssigned (item.width) ? item.width : item.minWidth);
  627. if (isAssigned (item.minWidth) && preferredWidth < item.minWidth) return item.minWidth;
  628. if (isAssigned (item.maxWidth) && preferredWidth > item.maxWidth) return item.maxWidth;
  629. return preferredWidth;
  630. }
  631. Coord getPreferredHeight (const ItemWithState& itemWithState) const noexcept
  632. {
  633. const auto& item = *itemWithState.item;
  634. auto preferredHeight = (item.flexBasis > 0 && ! isRowDirection)
  635. ? item.flexBasis
  636. : (isAssigned (item.height) ? item.height : item.minHeight);
  637. if (isAssigned (item.minHeight) && preferredHeight < item.minHeight) return item.minHeight;
  638. if (isAssigned (item.maxHeight) && preferredHeight > item.maxHeight) return item.maxHeight;
  639. return preferredHeight;
  640. }
  641. };
  642. //==============================================================================
  643. FlexBox::FlexBox() noexcept {}
  644. FlexBox::~FlexBox() noexcept {}
  645. FlexBox::FlexBox (JustifyContent jc) noexcept : justifyContent (jc) {}
  646. FlexBox::FlexBox (Direction d, Wrap w, AlignContent ac, AlignItems ai, JustifyContent jc) noexcept
  647. : flexDirection (d), flexWrap (w), alignContent (ac), alignItems (ai), justifyContent (jc)
  648. {
  649. }
  650. void FlexBox::performLayout (Rectangle<float> targetArea)
  651. {
  652. if (! items.isEmpty())
  653. {
  654. FlexBoxLayoutCalculation layout (*this, targetArea.getWidth(), targetArea.getHeight());
  655. layout.createStates();
  656. layout.initialiseItems();
  657. layout.resolveFlexibleLengths();
  658. layout.resolveAutoMarginsOnMainAxis();
  659. layout.calculateCrossSizesByLine();
  660. layout.calculateCrossSizeOfAllItems();
  661. layout.alignLinesPerAlignContent();
  662. layout.resolveAutoMarginsOnCrossAxis();
  663. layout.alignItemsInCrossAxisInLinesPerAlignItems();
  664. layout.alignLinesPerAlignSelf();
  665. layout.alignItemsByJustifyContent();
  666. layout.layoutAllItems();
  667. for (auto& item : items)
  668. {
  669. item.currentBounds += targetArea.getPosition();
  670. if (auto* comp = item.associatedComponent)
  671. comp->setBounds (Rectangle<int>::leftTopRightBottom ((int) item.currentBounds.getX(),
  672. (int) item.currentBounds.getY(),
  673. (int) item.currentBounds.getRight(),
  674. (int) item.currentBounds.getBottom()));
  675. if (auto* box = item.associatedFlexBox)
  676. box->performLayout (item.currentBounds);
  677. }
  678. }
  679. }
  680. void FlexBox::performLayout (Rectangle<int> targetArea)
  681. {
  682. performLayout (targetArea.toFloat());
  683. }
  684. //==============================================================================
  685. FlexItem::FlexItem() noexcept {}
  686. FlexItem::FlexItem (float w, float h) noexcept : currentBounds (w, h), minWidth (w), minHeight (h) {}
  687. FlexItem::FlexItem (float w, float h, Component& c) noexcept : FlexItem (w, h) { associatedComponent = &c; }
  688. FlexItem::FlexItem (float w, float h, FlexBox& fb) noexcept : FlexItem (w, h) { associatedFlexBox = &fb; }
  689. FlexItem::FlexItem (Component& c) noexcept : associatedComponent (&c) {}
  690. FlexItem::FlexItem (FlexBox& fb) noexcept : associatedFlexBox (&fb) {}
  691. FlexItem::Margin::Margin() noexcept : left(), right(), top(), bottom() {}
  692. FlexItem::Margin::Margin (float v) noexcept : left (v), right (v), top (v), bottom (v) {}
  693. FlexItem::Margin::Margin (float t, float r, float b, float l) noexcept : left (l), right (r), top (t), bottom (b) {}
  694. //==============================================================================
  695. FlexItem FlexItem::withFlex (float newFlexGrow) const noexcept
  696. {
  697. auto fi = *this;
  698. fi.flexGrow = newFlexGrow;
  699. return fi;
  700. }
  701. FlexItem FlexItem::withFlex (float newFlexGrow, float newFlexShrink) const noexcept
  702. {
  703. auto fi = withFlex (newFlexGrow);
  704. fi.flexShrink = newFlexShrink;
  705. return fi;
  706. }
  707. FlexItem FlexItem::withFlex (float newFlexGrow, float newFlexShrink, float newFlexBasis) const noexcept
  708. {
  709. auto fi = withFlex (newFlexGrow, newFlexShrink);
  710. fi.flexBasis = newFlexBasis;
  711. return fi;
  712. }
  713. FlexItem FlexItem::withWidth (float newWidth) const noexcept { auto fi = *this; fi.width = newWidth; return fi; }
  714. FlexItem FlexItem::withMinWidth (float newMinWidth) const noexcept { auto fi = *this; fi.minWidth = newMinWidth; return fi; }
  715. FlexItem FlexItem::withMaxWidth (float newMaxWidth) const noexcept { auto fi = *this; fi.maxWidth = newMaxWidth; return fi; }
  716. FlexItem FlexItem::withMinHeight (float newMinHeight) const noexcept { auto fi = *this; fi.minHeight = newMinHeight; return fi; }
  717. FlexItem FlexItem::withMaxHeight (float newMaxHeight) const noexcept { auto fi = *this; fi.maxHeight = newMaxHeight; return fi; }
  718. FlexItem FlexItem::withHeight (float newHeight) const noexcept { auto fi = *this; fi.height = newHeight; return fi; }
  719. FlexItem FlexItem::withMargin (Margin m) const noexcept { auto fi = *this; fi.margin = m; return fi; }
  720. FlexItem FlexItem::withOrder (int newOrder) const noexcept { auto fi = *this; fi.order = newOrder; return fi; }
  721. FlexItem FlexItem::withAlignSelf (AlignSelf a) const noexcept { auto fi = *this; fi.alignSelf = a; return fi; }
  722. } // namespace juce