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.

832 lines
32KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. struct FlexBoxLayoutCalculation
  18. {
  19. using Coord = double;
  20. FlexBoxLayoutCalculation (const FlexBox& fb, Coord w, Coord h)
  21. : owner (fb), parentWidth (w), parentHeight (h), numItems (owner.items.size()),
  22. isRowDirection (fb.flexDirection == FlexBox::Direction::row
  23. || fb.flexDirection == FlexBox::Direction::rowReverse),
  24. containerLineLength (isRowDirection ? parentWidth : parentHeight)
  25. {
  26. lineItems.calloc ((size_t) (numItems * numItems));
  27. lineInfo.calloc ((size_t) numItems);
  28. }
  29. struct ItemWithState
  30. {
  31. ItemWithState (FlexItem& source) noexcept : item (&source) {}
  32. FlexItem* item;
  33. Coord lockedWidth = 0, lockedHeight = 0;
  34. Coord lockedMarginLeft = 0, lockedMarginRight = 0, lockedMarginTop = 0, lockedMarginBottom = 0;
  35. Coord preferredWidth = 0, preferredHeight = 0;
  36. bool locked = false;
  37. void resetItemLockedSize() noexcept
  38. {
  39. lockedWidth = preferredWidth;
  40. lockedHeight = preferredHeight;
  41. lockedMarginLeft = getValueOrZeroIfAuto (item->margin.left);
  42. lockedMarginRight = getValueOrZeroIfAuto (item->margin.right);
  43. lockedMarginTop = getValueOrZeroIfAuto (item->margin.top);
  44. lockedMarginBottom = getValueOrZeroIfAuto (item->margin.bottom);
  45. }
  46. void setWidthChecked (Coord newWidth) noexcept
  47. {
  48. if (isAssigned (item->maxWidth)) newWidth = jmin (newWidth, static_cast<Coord> (item->maxWidth));
  49. if (isAssigned (item->minWidth)) newWidth = jmax (newWidth, static_cast<Coord> (item->minWidth));
  50. lockedWidth = newWidth;
  51. }
  52. void setHeightChecked (Coord newHeight) noexcept
  53. {
  54. if (isAssigned (item->maxHeight)) newHeight = jmin (newHeight, (Coord) item->maxHeight);
  55. if (isAssigned (item->minHeight)) newHeight = jmax (newHeight, (Coord) item->minHeight);
  56. lockedHeight = newHeight;
  57. }
  58. };
  59. struct RowInfo
  60. {
  61. int numItems;
  62. Coord crossSize, lineY, totalLength;
  63. };
  64. const FlexBox& owner;
  65. const Coord parentWidth, parentHeight;
  66. const int numItems;
  67. const bool isRowDirection;
  68. const Coord containerLineLength;
  69. int numberOfRows = 1;
  70. Coord containerCrossLength = 0;
  71. HeapBlock<ItemWithState*> lineItems;
  72. HeapBlock<RowInfo> lineInfo;
  73. Array<ItemWithState> itemStates;
  74. ItemWithState& getItem (int x, int y) const noexcept { return *lineItems[y * numItems + x]; }
  75. static bool isAuto (Coord value) noexcept { return value == FlexItem::autoValue; }
  76. static bool isAssigned (Coord value) noexcept { return value != FlexItem::notAssigned; }
  77. static Coord getValueOrZeroIfAuto (Coord value) noexcept { return isAuto (value) ? Coord() : value; }
  78. //==============================================================================
  79. void createStates()
  80. {
  81. itemStates.ensureStorageAllocated (numItems);
  82. for (auto& item : owner.items)
  83. itemStates.add (item);
  84. itemStates.sort (*this, true);
  85. for (auto& item : itemStates)
  86. {
  87. item.preferredWidth = getPreferredWidth (item);
  88. item.preferredHeight = getPreferredHeight (item);
  89. }
  90. }
  91. void initialiseItems() noexcept
  92. {
  93. if (owner.flexWrap == FlexBox::Wrap::noWrap) // for single-line, all items go in line 1
  94. {
  95. lineInfo[0].numItems = numItems;
  96. int i = 0;
  97. for (auto& item : itemStates)
  98. {
  99. item.resetItemLockedSize();
  100. lineItems[i++] = &item;
  101. }
  102. }
  103. else // if multi-line, group the flexbox items into multiple lines
  104. {
  105. auto currentLength = containerLineLength;
  106. int column = 0, row = 0;
  107. for (auto& item : itemStates)
  108. {
  109. item.resetItemLockedSize();
  110. const auto flexitemLength = getItemLength (item);
  111. if (flexitemLength > currentLength)
  112. {
  113. if (++row >= numItems)
  114. break;
  115. column = 0;
  116. currentLength = containerLineLength;
  117. numberOfRows = jmax (numberOfRows, row + 1);
  118. }
  119. currentLength -= flexitemLength;
  120. lineItems[row * numItems + column] = &item;
  121. ++column;
  122. lineInfo[row].numItems = jmax (lineInfo[row].numItems, column);
  123. }
  124. }
  125. }
  126. void resolveFlexibleLengths() noexcept
  127. {
  128. for (int row = 0; row < numberOfRows; ++row)
  129. {
  130. resetRowItems (row);
  131. for (int maxLoops = numItems; --maxLoops >= 0;)
  132. {
  133. resetUnlockedRowItems (row);
  134. if (layoutRowItems (row))
  135. break;
  136. }
  137. }
  138. }
  139. void resolveAutoMarginsOnMainAxis() noexcept
  140. {
  141. for (int row = 0; row < numberOfRows; ++row)
  142. {
  143. Coord allFlexGrow = 0;
  144. const auto numColumns = lineInfo[row].numItems;
  145. const auto remainingLength = containerLineLength - lineInfo[row].totalLength;
  146. for (int column = 0; column < numColumns; ++column)
  147. {
  148. auto& item = getItem (column, row);
  149. if (isRowDirection)
  150. {
  151. if (isAuto (item.item->margin.left)) ++allFlexGrow;
  152. if (isAuto (item.item->margin.right)) ++allFlexGrow;
  153. }
  154. else
  155. {
  156. if (isAuto (item.item->margin.top)) ++allFlexGrow;
  157. if (isAuto (item.item->margin.bottom)) ++allFlexGrow;
  158. }
  159. }
  160. auto changeUnitWidth = remainingLength / allFlexGrow;
  161. if (changeUnitWidth > 0)
  162. {
  163. for (int column = 0; column < numColumns; ++column)
  164. {
  165. auto& item = getItem (column, row);
  166. if (isRowDirection)
  167. {
  168. if (isAuto (item.item->margin.left)) item.lockedMarginLeft = changeUnitWidth;
  169. if (isAuto (item.item->margin.right)) item.lockedMarginRight = changeUnitWidth;
  170. }
  171. else
  172. {
  173. if (isAuto (item.item->margin.top)) item.lockedMarginTop = changeUnitWidth;
  174. if (isAuto (item.item->margin.bottom)) item.lockedMarginBottom = changeUnitWidth;
  175. }
  176. }
  177. }
  178. }
  179. }
  180. void calculateCrossSizesByLine() noexcept
  181. {
  182. for (int row = 0; row < numberOfRows; ++row)
  183. {
  184. Coord maxSize = 0;
  185. const auto numColumns = lineInfo[row].numItems;
  186. for (int column = 0; column < numColumns; ++column)
  187. {
  188. auto& item = getItem (column, row);
  189. maxSize = jmax (maxSize, isRowDirection ? item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom
  190. : item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight);
  191. }
  192. lineInfo[row].crossSize = maxSize;
  193. }
  194. }
  195. void calculateCrossSizeOfAllItems() noexcept
  196. {
  197. for (int row = 0; row < numberOfRows; ++row)
  198. {
  199. const auto numColumns = lineInfo[row].numItems;
  200. for (int column = 0; column < numColumns; ++column)
  201. {
  202. auto& item = getItem (column, row);
  203. if (isAssigned (item.item->maxHeight) && item.lockedHeight > item.item->maxHeight)
  204. item.lockedHeight = item.item->maxHeight;
  205. if (isAssigned (item.item->maxWidth) && item.lockedWidth > item.item->maxWidth)
  206. item.lockedWidth = item.item->maxWidth;
  207. }
  208. }
  209. }
  210. void alignLinesPerAlignContent() noexcept
  211. {
  212. containerCrossLength = isRowDirection ? parentHeight : parentWidth;
  213. if (owner.alignContent == FlexBox::AlignContent::flexStart)
  214. {
  215. for (int row = 0; row < numberOfRows; ++row)
  216. for (int row2 = row; row2 < numberOfRows; ++row2)
  217. lineInfo[row].lineY = row == 0 ? 0 : lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
  218. }
  219. else if (owner.alignContent == FlexBox::AlignContent::flexEnd)
  220. {
  221. for (int row = 0; row < numberOfRows; ++row)
  222. {
  223. Coord crossHeights = 0;
  224. for (int row2 = row; row2 < numberOfRows; ++row2)
  225. crossHeights += lineInfo[row2].crossSize;
  226. lineInfo[row].lineY = containerCrossLength - crossHeights;
  227. }
  228. }
  229. else
  230. {
  231. Coord totalHeight = 0;
  232. for (int row = 0; row < numberOfRows; ++row)
  233. totalHeight += lineInfo[row].crossSize;
  234. if (owner.alignContent == FlexBox::AlignContent::stretch)
  235. {
  236. const auto difference = jmax (Coord(), (containerCrossLength - totalHeight) / numberOfRows);
  237. for (int row = 0; row < numberOfRows; ++row)
  238. {
  239. lineInfo[row].crossSize += difference;
  240. lineInfo[row].lineY = row == 0 ? 0 : lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
  241. }
  242. }
  243. else if (owner.alignContent == FlexBox::AlignContent::center)
  244. {
  245. const auto additionalength = (containerCrossLength - totalHeight) / 2;
  246. for (int row = 0; row < numberOfRows; ++row)
  247. lineInfo[row].lineY = row == 0 ? additionalength : lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
  248. }
  249. else if (owner.alignContent == FlexBox::AlignContent::spaceBetween)
  250. {
  251. const auto additionalength = numberOfRows <= 1 ? Coord() : jmax (Coord(), (containerCrossLength - totalHeight)
  252. / static_cast<Coord> (numberOfRows - 1));
  253. lineInfo[0].lineY = 0;
  254. for (int row = 1; row < numberOfRows; ++row)
  255. lineInfo[row].lineY += additionalength + lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
  256. }
  257. else if (owner.alignContent == FlexBox::AlignContent::spaceAround)
  258. {
  259. const auto additionalength = numberOfRows <= 1 ? Coord() : jmax (Coord(), (containerCrossLength - totalHeight)
  260. / static_cast<Coord> (2 + (2 * (numberOfRows - 1))));
  261. lineInfo[0].lineY = additionalength;
  262. for (int row = 1; row < numberOfRows; ++row)
  263. lineInfo[row].lineY += (2 * additionalength) + lineInfo[row - 1].lineY + lineInfo[row - 1].crossSize;
  264. }
  265. }
  266. }
  267. void resolveAutoMarginsOnCrossAxis() noexcept
  268. {
  269. for (int row = 0; row < numberOfRows; ++row)
  270. {
  271. const auto numColumns = lineInfo[row].numItems;
  272. const auto crossSizeForLine = lineInfo[row].crossSize;
  273. for (int column = 0; column < numColumns; ++column)
  274. {
  275. auto& item = getItem (column, row);
  276. if (isRowDirection)
  277. {
  278. if (isAuto (item.item->margin.top) && isAuto (item.item->margin.bottom))
  279. item.lockedMarginTop = (crossSizeForLine - item.lockedHeight) / 2;
  280. else if (isAuto (item.item->margin.top))
  281. item.lockedMarginTop = crossSizeForLine - item.lockedHeight - item.item->margin.bottom;
  282. }
  283. else if (isAuto (item.item->margin.left) && isAuto (item.item->margin.right))
  284. {
  285. item.lockedMarginLeft = jmax (Coord(), (crossSizeForLine - item.lockedWidth) / 2);
  286. }
  287. else if (isAuto (item.item->margin.top))
  288. {
  289. item.lockedMarginLeft = jmax (Coord(), crossSizeForLine - item.lockedHeight - item.item->margin.bottom);
  290. }
  291. }
  292. }
  293. }
  294. void alignItemsInCrossAxisInLinesPerAlignItems() noexcept
  295. {
  296. for (int row = 0; row < numberOfRows; ++row)
  297. {
  298. const auto numColumns = lineInfo[row].numItems;
  299. const auto lineSize = lineInfo[row].crossSize;
  300. for (int column = 0; column < numColumns; ++column)
  301. {
  302. auto& item = getItem (column, row);
  303. if (item.item->alignSelf == FlexItem::AlignSelf::autoAlign)
  304. {
  305. if (owner.alignItems == FlexBox::AlignItems::stretch)
  306. {
  307. item.lockedMarginTop = item.item->margin.top;
  308. if (isRowDirection)
  309. item.setHeightChecked (lineSize - item.item->margin.top - item.item->margin.bottom);
  310. }
  311. else if (owner.alignItems == FlexBox::AlignItems::flexStart)
  312. {
  313. item.lockedMarginTop = item.item->margin.top;
  314. }
  315. else if (owner.alignItems == FlexBox::AlignItems::flexEnd)
  316. {
  317. item.lockedMarginTop = lineSize - item.lockedHeight - item.item->margin.bottom;
  318. }
  319. else if (owner.alignItems == FlexBox::AlignItems::center)
  320. {
  321. item.lockedMarginTop = (lineSize - item.lockedHeight - item.item->margin.top - item.item->margin.bottom) / 2;
  322. }
  323. }
  324. }
  325. }
  326. }
  327. void alignLinesPerAlignSelf() noexcept
  328. {
  329. for (int row = 0; row < numberOfRows; ++row)
  330. {
  331. const auto numColumns = lineInfo[row].numItems;
  332. const auto lineSize = lineInfo[row].crossSize;
  333. for (int column = 0; column < numColumns; ++column)
  334. {
  335. auto& item = getItem (column, row);
  336. if (! isAuto (item.item->margin.top))
  337. {
  338. if (item.item->alignSelf == FlexItem::AlignSelf::flexStart)
  339. {
  340. if (isRowDirection)
  341. item.lockedMarginTop = item.item->margin.top;
  342. else
  343. item.lockedMarginLeft = item.item->margin.left;
  344. }
  345. else if (item.item->alignSelf == FlexItem::AlignSelf::flexEnd)
  346. {
  347. if (isRowDirection)
  348. item.lockedMarginTop = lineSize - item.lockedHeight - item.item->margin.bottom;
  349. else
  350. item.lockedMarginLeft = lineSize - item.lockedWidth - item.item->margin.right;
  351. }
  352. else if (item.item->alignSelf == FlexItem::AlignSelf::center)
  353. {
  354. if (isRowDirection)
  355. item.lockedMarginTop = item.item->margin.top + (lineSize - item.lockedHeight - item.item->margin.top - item.item->margin.bottom) / 2;
  356. else
  357. item.lockedMarginLeft = item.item->margin.left + (lineSize - item.lockedWidth - item.item->margin.left - item.item->margin.right) / 2;
  358. }
  359. else if (item.item->alignSelf == FlexItem::AlignSelf::stretch)
  360. {
  361. item.lockedMarginTop = item.item->margin.top;
  362. item.lockedMarginLeft = item.item->margin.left;
  363. if (isRowDirection)
  364. item.setHeightChecked (isAssigned (item.item->height) ? getPreferredHeight (item)
  365. : lineSize - item.item->margin.top - item.item->margin.bottom);
  366. else
  367. item.setWidthChecked (isAssigned (item.item->width) ? getPreferredWidth (item)
  368. : lineSize - item.item->margin.left - item.item->margin.right);
  369. }
  370. }
  371. }
  372. }
  373. }
  374. void alignItemsByJustifyContent() noexcept
  375. {
  376. Coord additionalMarginRight = 0, additionalMarginLeft = 0;
  377. recalculateTotalItemLengthPerLineArray();
  378. for (int row = 0; row < numberOfRows; ++row)
  379. {
  380. const auto numColumns = lineInfo[row].numItems;
  381. Coord x = 0;
  382. if (owner.justifyContent == FlexBox::JustifyContent::flexEnd)
  383. {
  384. x = containerLineLength - lineInfo[row].totalLength;
  385. }
  386. else if (owner.justifyContent == FlexBox::JustifyContent::center)
  387. {
  388. x = (containerLineLength - lineInfo[row].totalLength) / 2;
  389. }
  390. else if (owner.justifyContent == FlexBox::JustifyContent::spaceBetween)
  391. {
  392. additionalMarginRight
  393. = jmax (Coord(), (containerLineLength - lineInfo[row].totalLength) / jmax (1, numColumns - 1));
  394. }
  395. else if (owner.justifyContent == FlexBox::JustifyContent::spaceAround)
  396. {
  397. additionalMarginLeft = additionalMarginRight
  398. = jmax (Coord(), (containerLineLength - lineInfo[row].totalLength) / jmax (1, 2 * numColumns));
  399. }
  400. for (int column = 0; column < numColumns; ++column)
  401. {
  402. auto& item = getItem (column, row);
  403. if (isRowDirection)
  404. {
  405. item.lockedMarginLeft += additionalMarginLeft;
  406. item.lockedMarginRight += additionalMarginRight;
  407. item.item->currentBounds.setPosition ((float) (x + item.lockedMarginLeft), (float) item.lockedMarginTop);
  408. x += item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight;
  409. }
  410. else
  411. {
  412. item.lockedMarginTop += additionalMarginLeft;
  413. item.lockedMarginBottom += additionalMarginRight;
  414. item.item->currentBounds.setPosition ((float) item.lockedMarginLeft, (float) (x + item.lockedMarginTop));
  415. x += item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom;
  416. }
  417. }
  418. }
  419. }
  420. void layoutAllItems() noexcept
  421. {
  422. for (int row = 0; row < numberOfRows; ++row)
  423. {
  424. const auto lineY = lineInfo[row].lineY;
  425. const auto numColumns = lineInfo[row].numItems;
  426. for (int column = 0; column < numColumns; ++column)
  427. {
  428. auto& item = getItem (column, row);
  429. if (isRowDirection)
  430. item.item->currentBounds.setY ((float) (lineY + item.lockedMarginTop));
  431. else
  432. item.item->currentBounds.setX ((float) (lineY + item.lockedMarginLeft));
  433. item.item->currentBounds.setSize ((float) item.lockedWidth,
  434. (float) item.lockedHeight);
  435. }
  436. }
  437. reverseLocations();
  438. reverseWrap();
  439. }
  440. static int compareElements (const ItemWithState& i1, const ItemWithState& i2) noexcept
  441. {
  442. return i1.item->order < i2.item->order ? -1 : (i2.item->order < i1.item->order ? 1 : 0);
  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. item.lockedWidth = getPreferredWidth (item);
  465. item.lockedHeight = getPreferredHeight (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 -= getItemLength (item);
  478. }
  479. else
  480. {
  481. totalItemsLength += getItemLength (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)
  492. changeUnit = difference / totalFlexGrow;
  493. }
  494. else
  495. {
  496. if (totalFlexShrink != 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. {
  518. const auto& item = getItem (column, row);
  519. lineInfo[row].totalLength += isRowDirection ? item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight
  520. : item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom;
  521. }
  522. }
  523. }
  524. void reverseLocations() noexcept
  525. {
  526. if (owner.flexDirection == FlexBox::Direction::rowReverse)
  527. {
  528. for (auto& item : owner.items)
  529. item.currentBounds.setX ((float) (containerLineLength - item.currentBounds.getRight()));
  530. }
  531. else if (owner.flexDirection == FlexBox::Direction::columnReverse)
  532. {
  533. for (auto& item : owner.items)
  534. item.currentBounds.setY ((float) (containerLineLength - item.currentBounds.getBottom()));
  535. }
  536. }
  537. void reverseWrap() noexcept
  538. {
  539. if (owner.flexWrap == FlexBox::Wrap::wrapReverse)
  540. {
  541. if (isRowDirection)
  542. {
  543. for (auto& item : owner.items)
  544. item.currentBounds.setY ((float) (containerCrossLength - item.currentBounds.getBottom()));
  545. }
  546. else
  547. {
  548. for (auto& item : owner.items)
  549. item.currentBounds.setX ((float) (containerCrossLength - item.currentBounds.getRight()));
  550. }
  551. }
  552. }
  553. Coord getItemLength (const ItemWithState& item) const noexcept
  554. {
  555. return isRowDirection ? item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight
  556. : item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom;
  557. }
  558. Coord getItemCrossSize (const ItemWithState& item) const noexcept
  559. {
  560. return isRowDirection ? item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom
  561. : item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight;
  562. }
  563. bool addToItemLength (ItemWithState& item, const Coord length, int row) const noexcept
  564. {
  565. bool ok = false;
  566. if (isRowDirection)
  567. {
  568. const auto prefWidth = getPreferredWidth (item);
  569. if (isAssigned (item.item->maxWidth) && item.item->maxWidth < prefWidth + length)
  570. {
  571. item.lockedWidth = item.item->maxWidth;
  572. item.locked = true;
  573. }
  574. else if (isAssigned (prefWidth) && item.item->minWidth > prefWidth + length)
  575. {
  576. item.lockedWidth = item.item->minWidth;
  577. item.locked = true;
  578. }
  579. else
  580. {
  581. ok = true;
  582. item.lockedWidth = prefWidth + length;
  583. }
  584. lineInfo[row].totalLength += item.lockedWidth + item.lockedMarginLeft + item.lockedMarginRight;
  585. }
  586. else
  587. {
  588. const auto prefHeight = getPreferredHeight (item);
  589. if (isAssigned (item.item->maxHeight) && item.item->maxHeight < prefHeight + length)
  590. {
  591. item.lockedHeight = item.item->maxHeight;
  592. item.locked = true;
  593. }
  594. else if (isAssigned (prefHeight) && item.item->minHeight > prefHeight + length)
  595. {
  596. item.lockedHeight = item.item->minHeight;
  597. item.locked = true;
  598. }
  599. else
  600. {
  601. ok = true;
  602. item.lockedHeight = prefHeight + length;
  603. }
  604. lineInfo[row].totalLength += item.lockedHeight + item.lockedMarginTop + item.lockedMarginBottom;
  605. }
  606. return ok;
  607. }
  608. Coord getPreferredWidth (const ItemWithState& itemWithState) const noexcept
  609. {
  610. const auto& item = *itemWithState.item;
  611. auto preferredWidth = (item.flexBasis > 0 && isRowDirection)
  612. ? item.flexBasis
  613. : (isAssigned (item.width) ? item.width : item.minWidth);
  614. if (isAssigned (item.minWidth) && preferredWidth < item.minWidth) return item.minWidth;
  615. if (isAssigned (item.maxWidth) && preferredWidth > item.maxWidth) return item.maxWidth;
  616. return preferredWidth;
  617. }
  618. Coord getPreferredHeight (const ItemWithState& itemWithState) const noexcept
  619. {
  620. const auto& item = *itemWithState.item;
  621. auto preferredHeight = (item.flexBasis > 0 && ! isRowDirection)
  622. ? item.flexBasis
  623. : (isAssigned (item.height) ? item.height : item.minHeight);
  624. if (isAssigned (item.minHeight) && preferredHeight < item.minHeight) return item.minHeight;
  625. if (isAssigned (item.maxHeight) && preferredHeight > item.maxHeight) return item.maxHeight;
  626. return preferredHeight;
  627. }
  628. };
  629. //==============================================================================
  630. FlexBox::FlexBox() noexcept {}
  631. FlexBox::~FlexBox() noexcept {}
  632. FlexBox::FlexBox (JustifyContent jc) noexcept : justifyContent (jc) {}
  633. FlexBox::FlexBox (Direction d, Wrap w, AlignContent ac, AlignItems ai, JustifyContent jc) noexcept
  634. : flexDirection (d), flexWrap (w), alignContent (ac), alignItems (ai), justifyContent (jc)
  635. {}
  636. void FlexBox::performLayout (Rectangle<float> targetArea)
  637. {
  638. if (! items.isEmpty())
  639. {
  640. FlexBoxLayoutCalculation layout (*this, targetArea.getWidth(), targetArea.getHeight());
  641. layout.createStates();
  642. layout.initialiseItems();
  643. layout.resolveFlexibleLengths();
  644. layout.resolveAutoMarginsOnMainAxis();
  645. layout.calculateCrossSizesByLine();
  646. layout.calculateCrossSizeOfAllItems();
  647. layout.alignLinesPerAlignContent();
  648. layout.resolveAutoMarginsOnCrossAxis();
  649. layout.alignItemsInCrossAxisInLinesPerAlignItems();
  650. layout.alignLinesPerAlignSelf();
  651. layout.alignItemsByJustifyContent();
  652. layout.layoutAllItems();
  653. for (auto& item : items)
  654. {
  655. item.currentBounds += targetArea.getPosition();
  656. if (auto comp = item.associatedComponent)
  657. comp->setBounds (item.currentBounds.getSmallestIntegerContainer());
  658. if (auto box = item.associatedFlexBox)
  659. box->performLayout (item.currentBounds);
  660. }
  661. }
  662. }
  663. void FlexBox::performLayout (Rectangle<int> targetArea)
  664. {
  665. performLayout (targetArea.toFloat());
  666. }
  667. //==============================================================================
  668. FlexItem::FlexItem() noexcept {}
  669. FlexItem::FlexItem (float w, float h) noexcept : currentBounds (w, h), minWidth (w), minHeight (h) {}
  670. FlexItem::FlexItem (float w, float h, Component& c) noexcept : FlexItem (w, h) { associatedComponent = &c; }
  671. FlexItem::FlexItem (float w, float h, FlexBox& fb) noexcept : FlexItem (w, h) { associatedFlexBox = &fb; }
  672. FlexItem::FlexItem (Component& c) noexcept : associatedComponent (&c) {}
  673. FlexItem::FlexItem (FlexBox& fb) noexcept : associatedFlexBox (&fb) {}
  674. FlexItem::Margin::Margin() noexcept : left(), right(), top(), bottom() {}
  675. FlexItem::Margin::Margin (float v) noexcept : left (v), right (v), top (v), bottom (v) {}
  676. //==============================================================================
  677. FlexItem FlexItem::withFlex (float newFlexGrow) const noexcept
  678. {
  679. auto fi = *this;
  680. fi.flexGrow = newFlexGrow;
  681. return fi;
  682. }
  683. FlexItem FlexItem::withFlex (float newFlexGrow, float newFlexShrink) const noexcept
  684. {
  685. auto fi = withFlex (newFlexGrow);
  686. fi.flexShrink = newFlexShrink;
  687. return fi;
  688. }
  689. FlexItem FlexItem::withFlex (float newFlexGrow, float newFlexShrink, float newFlexBasis) const noexcept
  690. {
  691. auto fi = withFlex (newFlexGrow, newFlexShrink);
  692. fi.flexBasis = newFlexBasis;
  693. return fi;
  694. }
  695. FlexItem FlexItem::withWidth (float newWidth) const noexcept { auto fi = *this; fi.width = newWidth; return fi; }
  696. FlexItem FlexItem::withHeight (float newHeight) const noexcept { auto fi = *this; fi.height = newHeight; return fi; }
  697. FlexItem FlexItem::withMargin (Margin m) const noexcept { auto fi = *this; fi.margin = m; return fi; }