FlexLayout.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. /**
  2. * FlexLayout.js
  3. *
  4. * Copyright, Moxiecode Systems AB
  5. * Released under LGPL License.
  6. *
  7. * License: http://www.tinymce.com/license
  8. * Contributing: http://www.tinymce.com/contributing
  9. */
  10. /**
  11. * This layout manager works similar to the CSS flex box.
  12. *
  13. * @setting {String} direction row|row-reverse|column|column-reverse
  14. * @setting {Number} flex A positive-number to flex by.
  15. * @setting {String} align start|end|center|stretch
  16. * @setting {String} pack start|end|justify
  17. *
  18. * @class tinymce.ui.FlexLayout
  19. * @extends tinymce.ui.AbsoluteLayout
  20. */
  21. define("tinymce/ui/FlexLayout", [
  22. "tinymce/ui/AbsoluteLayout"
  23. ], function(AbsoluteLayout) {
  24. "use strict";
  25. return AbsoluteLayout.extend({
  26. /**
  27. * Recalculates the positions of the controls in the specified container.
  28. *
  29. * @method recalc
  30. * @param {tinymce.ui.Container} container Container instance to recalc.
  31. */
  32. recalc: function(container) {
  33. // A ton of variables, needs to be in the same scope for performance
  34. var i, l, items, contLayoutRect, contPaddingBox, contSettings, align, pack, spacing, totalFlex, availableSpace, direction;
  35. var ctrl, ctrlLayoutRect, ctrlSettings, flex, maxSizeItems = [], size, maxSize, ratio, rect, pos, maxAlignEndPos;
  36. var sizeName, minSizeName, posName, maxSizeName, beforeName, innerSizeName, deltaSizeName, contentSizeName;
  37. var alignAxisName, alignInnerSizeName, alignSizeName, alignMinSizeName, alignBeforeName, alignAfterName;
  38. var alignDeltaSizeName, alignContentSizeName;
  39. var max = Math.max, min = Math.min;
  40. // Get container items, properties and settings
  41. items = container.items().filter(':visible');
  42. contLayoutRect = container.layoutRect();
  43. contPaddingBox = container._paddingBox;
  44. contSettings = container.settings;
  45. direction = container.isRtl() ? (contSettings.direction || 'row-reversed') : contSettings.direction;
  46. align = contSettings.align;
  47. pack = container.isRtl() ? (contSettings.pack || 'end') : contSettings.pack;
  48. spacing = contSettings.spacing || 0;
  49. if (direction == "row-reversed" || direction == "column-reverse") {
  50. items = items.set(items.toArray().reverse());
  51. direction = direction.split('-')[0];
  52. }
  53. // Setup axis variable name for row/column direction since the calculations is the same
  54. if (direction == "column") {
  55. posName = "y";
  56. sizeName = "h";
  57. minSizeName = "minH";
  58. maxSizeName = "maxH";
  59. innerSizeName = "innerH";
  60. beforeName = 'top';
  61. deltaSizeName = "deltaH";
  62. contentSizeName = "contentH";
  63. alignBeforeName = "left";
  64. alignSizeName = "w";
  65. alignAxisName = "x";
  66. alignInnerSizeName = "innerW";
  67. alignMinSizeName = "minW";
  68. alignAfterName = "right";
  69. alignDeltaSizeName = "deltaW";
  70. alignContentSizeName = "contentW";
  71. } else {
  72. posName = "x";
  73. sizeName = "w";
  74. minSizeName = "minW";
  75. maxSizeName = "maxW";
  76. innerSizeName = "innerW";
  77. beforeName = 'left';
  78. deltaSizeName = "deltaW";
  79. contentSizeName = "contentW";
  80. alignBeforeName = "top";
  81. alignSizeName = "h";
  82. alignAxisName = "y";
  83. alignInnerSizeName = "innerH";
  84. alignMinSizeName = "minH";
  85. alignAfterName = "bottom";
  86. alignDeltaSizeName = "deltaH";
  87. alignContentSizeName = "contentH";
  88. }
  89. // Figure out total flex, availableSpace and collect any max size elements
  90. availableSpace = contLayoutRect[innerSizeName] - contPaddingBox[beforeName] - contPaddingBox[beforeName];
  91. maxAlignEndPos = totalFlex = 0;
  92. for (i = 0, l = items.length; i < l; i++) {
  93. ctrl = items[i];
  94. ctrlLayoutRect = ctrl.layoutRect();
  95. ctrlSettings = ctrl.settings;
  96. flex = ctrlSettings.flex;
  97. availableSpace -= (i < l - 1 ? spacing : 0);
  98. if (flex > 0) {
  99. totalFlex += flex;
  100. // Flexed item has a max size then we need to check if we will hit that size
  101. if (ctrlLayoutRect[maxSizeName]) {
  102. maxSizeItems.push(ctrl);
  103. }
  104. ctrlLayoutRect.flex = flex;
  105. }
  106. availableSpace -= ctrlLayoutRect[minSizeName];
  107. // Calculate the align end position to be used to check for overflow/underflow
  108. size = contPaddingBox[alignBeforeName] + ctrlLayoutRect[alignMinSizeName] + contPaddingBox[alignAfterName];
  109. if (size > maxAlignEndPos) {
  110. maxAlignEndPos = size;
  111. }
  112. }
  113. // Calculate minW/minH
  114. rect = {};
  115. if (availableSpace < 0) {
  116. rect[minSizeName] = contLayoutRect[minSizeName] - availableSpace + contLayoutRect[deltaSizeName];
  117. } else {
  118. rect[minSizeName] = contLayoutRect[innerSizeName] - availableSpace + contLayoutRect[deltaSizeName];
  119. }
  120. rect[alignMinSizeName] = maxAlignEndPos + contLayoutRect[alignDeltaSizeName];
  121. rect[contentSizeName] = contLayoutRect[innerSizeName] - availableSpace;
  122. rect[alignContentSizeName] = maxAlignEndPos;
  123. rect.minW = min(rect.minW, contLayoutRect.maxW);
  124. rect.minH = min(rect.minH, contLayoutRect.maxH);
  125. rect.minW = max(rect.minW, contLayoutRect.startMinWidth);
  126. rect.minH = max(rect.minH, contLayoutRect.startMinHeight);
  127. // Resize container container if minSize was changed
  128. if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) {
  129. rect.w = rect.minW;
  130. rect.h = rect.minH;
  131. container.layoutRect(rect);
  132. this.recalc(container);
  133. // Forced recalc for example if items are hidden/shown
  134. if (container._lastRect === null) {
  135. var parentCtrl = container.parent();
  136. if (parentCtrl) {
  137. parentCtrl._lastRect = null;
  138. parentCtrl.recalc();
  139. }
  140. }
  141. return;
  142. }
  143. // Handle max size elements, check if they will become to wide with current options
  144. ratio = availableSpace / totalFlex;
  145. for (i = 0, l = maxSizeItems.length; i < l; i++) {
  146. ctrl = maxSizeItems[i];
  147. ctrlLayoutRect = ctrl.layoutRect();
  148. maxSize = ctrlLayoutRect[maxSizeName];
  149. size = ctrlLayoutRect[minSizeName] + ctrlLayoutRect.flex * ratio;
  150. if (size > maxSize) {
  151. availableSpace -= (ctrlLayoutRect[maxSizeName] - ctrlLayoutRect[minSizeName]);
  152. totalFlex -= ctrlLayoutRect.flex;
  153. ctrlLayoutRect.flex = 0;
  154. ctrlLayoutRect.maxFlexSize = maxSize;
  155. } else {
  156. ctrlLayoutRect.maxFlexSize = 0;
  157. }
  158. }
  159. // Setup new ratio, target layout rect, start position
  160. ratio = availableSpace / totalFlex;
  161. pos = contPaddingBox[beforeName];
  162. rect = {};
  163. // Handle pack setting moves the start position to end, center
  164. if (totalFlex === 0) {
  165. if (pack == "end") {
  166. pos = availableSpace + contPaddingBox[beforeName];
  167. } else if (pack == "center") {
  168. pos = Math.round(
  169. (contLayoutRect[innerSizeName] / 2) - ((contLayoutRect[innerSizeName] - availableSpace) / 2)
  170. ) + contPaddingBox[beforeName];
  171. if (pos < 0) {
  172. pos = contPaddingBox[beforeName];
  173. }
  174. } else if (pack == "justify") {
  175. pos = contPaddingBox[beforeName];
  176. spacing = Math.floor(availableSpace / (items.length - 1));
  177. }
  178. }
  179. // Default aligning (start) the other ones needs to be calculated while doing the layout
  180. rect[alignAxisName] = contPaddingBox[alignBeforeName];
  181. // Start laying out controls
  182. for (i = 0, l = items.length; i < l; i++) {
  183. ctrl = items[i];
  184. ctrlLayoutRect = ctrl.layoutRect();
  185. size = ctrlLayoutRect.maxFlexSize || ctrlLayoutRect[minSizeName];
  186. // Align the control on the other axis
  187. if (align === "center") {
  188. rect[alignAxisName] = Math.round((contLayoutRect[alignInnerSizeName] / 2) - (ctrlLayoutRect[alignSizeName] / 2));
  189. } else if (align === "stretch") {
  190. rect[alignSizeName] = max(
  191. ctrlLayoutRect[alignMinSizeName] || 0,
  192. contLayoutRect[alignInnerSizeName] - contPaddingBox[alignBeforeName] - contPaddingBox[alignAfterName]
  193. );
  194. rect[alignAxisName] = contPaddingBox[alignBeforeName];
  195. } else if (align === "end") {
  196. rect[alignAxisName] = contLayoutRect[alignInnerSizeName] - ctrlLayoutRect[alignSizeName] - contPaddingBox.top;
  197. }
  198. // Calculate new size based on flex
  199. if (ctrlLayoutRect.flex > 0) {
  200. size += ctrlLayoutRect.flex * ratio;
  201. }
  202. rect[sizeName] = size;
  203. rect[posName] = pos;
  204. ctrl.layoutRect(rect);
  205. // Recalculate containers
  206. if (ctrl.recalc) {
  207. ctrl.recalc();
  208. }
  209. // Move x/y position
  210. pos += size + spacing;
  211. }
  212. }
  213. });
  214. });