4 * Copyright, Moxiecode Systems AB
5 * Released under LGPL License.
7 * License: http://www.tinymce.com/license
8 * Contributing: http://www.tinymce.com/contributing
12 * This layout manager works similar to the CSS flex box.
14 * @setting {String} direction row|row-reverse|column|column-reverse
15 * @setting {Number} flex A positive-number to flex by.
16 * @setting {String} align start|end|center|stretch
17 * @setting {String} pack start|end|justify
19 * @class tinymce.ui.FlexLayout
20 * @extends tinymce.ui.AbsoluteLayout
22 define("tinymce/ui/FlexLayout", [
23 "tinymce/ui/AbsoluteLayout"
24 ], function(AbsoluteLayout) {
27 return AbsoluteLayout.extend({
29 * Recalculates the positions of the controls in the specified container.
32 * @param {tinymce.ui.Container} container Container instance to recalc.
34 recalc: function(container) {
35 // A ton of variables, needs to be in the same scope for performance
36 var i, l, items, contLayoutRect, contPaddingBox, contSettings, align, pack, spacing, totalFlex, availableSpace, direction;
37 var ctrl, ctrlLayoutRect, ctrlSettings, flex, maxSizeItems = [], size, maxSize, ratio, rect, pos, maxAlignEndPos;
38 var sizeName, minSizeName, posName, maxSizeName, beforeName, innerSizeName, afterName, deltaSizeName, contentSizeName;
39 var alignAxisName, alignInnerSizeName, alignSizeName, alignMinSizeName, alignMaxSizeName, alignBeforeName, alignAfterName;
40 var alignDeltaSizeName, alignContentSizeName;
41 var max = Math.max, min = Math.min;
43 // Get container items, properties and settings
44 items = container.items().filter(':visible');
45 contLayoutRect = container.layoutRect();
46 contPaddingBox = container._paddingBox;
47 contSettings = container.settings;
48 direction = contSettings.direction;
49 align = contSettings.align;
50 pack = contSettings.pack;
51 spacing = contSettings.spacing || 0;
53 if (direction == "row-reversed" || direction == "column-reverse") {
54 items = items.set(items.toArray().reverse());
55 direction = direction.split('-')[0];
58 // Setup axis variable name for row/column direction since the calculations is the same
59 if (direction == "column") {
64 innerSizeName = "innerH";
67 deltaSizeName = "deltaH";
68 contentSizeName = "contentH";
70 alignBeforeName = "left";
73 alignInnerSizeName = "innerW";
74 alignMinSizeName = "minW";
75 alignMaxSizeName = "maxW";
76 alignAfterName = "right";
77 alignDeltaSizeName = "deltaW";
78 alignContentSizeName = "contentW";
84 innerSizeName = "innerW";
87 deltaSizeName = "deltaW";
88 contentSizeName = "contentW";
90 alignBeforeName = "top";
93 alignInnerSizeName = "innerH";
94 alignMinSizeName = "minH";
95 alignMaxSizeName = "maxH";
96 alignAfterName = "bottom";
97 alignDeltaSizeName = "deltaH";
98 alignContentSizeName = "contentH";
101 // Figure out total flex, availableSpace and collect any max size elements
102 availableSpace = contLayoutRect[innerSizeName] - contPaddingBox[beforeName] - contPaddingBox[beforeName];
103 maxAlignEndPos = totalFlex = 0;
104 for (i = 0, l = items.length; i < l; i++) {
106 ctrlLayoutRect = ctrl.layoutRect();
107 ctrlSettings = ctrl.settings;
108 flex = ctrlSettings.flex;
109 availableSpace -= (i < l - 1 ? spacing : 0);
114 // Flexed item has a max size then we need to check if we will hit that size
115 if (ctrlLayoutRect[maxSizeName]) {
116 maxSizeItems.push(ctrl);
119 ctrlLayoutRect.flex = flex;
122 availableSpace -= ctrlLayoutRect[minSizeName];
124 // Calculate the align end position to be used to check for overflow/underflow
125 size = contPaddingBox[alignBeforeName] + ctrlLayoutRect[alignMinSizeName] + contPaddingBox[alignAfterName];
126 if (size > maxAlignEndPos) {
127 maxAlignEndPos = size;
131 // Calculate minW/minH
133 if (availableSpace < 0) {
134 rect[minSizeName] = contLayoutRect[minSizeName] - availableSpace + contLayoutRect[deltaSizeName];
136 rect[minSizeName] = contLayoutRect[innerSizeName] - availableSpace + contLayoutRect[deltaSizeName];
139 rect[alignMinSizeName] = maxAlignEndPos + contLayoutRect[alignDeltaSizeName];
141 rect[contentSizeName] = contLayoutRect[innerSizeName] - availableSpace;
142 rect[alignContentSizeName] = maxAlignEndPos;
143 rect.minW = min(rect.minW, contLayoutRect.maxW);
144 rect.minH = min(rect.minH, contLayoutRect.maxH);
145 rect.minW = max(rect.minW, contLayoutRect.startMinWidth);
146 rect.minH = max(rect.minH, contLayoutRect.startMinHeight);
148 // Resize container container if minSize was changed
149 if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) {
153 container.layoutRect(rect);
154 this.recalc(container);
156 // Forced recalc for example if items are hidden/shown
157 if (container._lastRect === null) {
158 var parentCtrl = container.parent();
160 parentCtrl._lastRect = null;
168 // Handle max size elements, check if they will become to wide with current options
169 ratio = availableSpace / totalFlex;
170 for (i = 0, l = maxSizeItems.length; i < l; i++) {
171 ctrl = maxSizeItems[i];
172 ctrlLayoutRect = ctrl.layoutRect();
173 maxSize = ctrlLayoutRect[maxSizeName];
174 size = ctrlLayoutRect[minSizeName] + Math.ceil(ctrlLayoutRect.flex * ratio);
176 if (size > maxSize) {
177 availableSpace -= (ctrlLayoutRect[maxSizeName] - ctrlLayoutRect[minSizeName]);
178 totalFlex -= ctrlLayoutRect.flex;
179 ctrlLayoutRect.flex = 0;
180 ctrlLayoutRect.maxFlexSize = maxSize;
182 ctrlLayoutRect.maxFlexSize = 0;
186 // Setup new ratio, target layout rect, start position
187 ratio = availableSpace / totalFlex;
188 pos = contPaddingBox[beforeName];
191 // Handle pack setting moves the start position to end, center
192 if (totalFlex === 0) {
194 pos = availableSpace + contPaddingBox[beforeName];
195 } else if (pack == "center") {
197 (contLayoutRect[innerSizeName] / 2) - ((contLayoutRect[innerSizeName] - availableSpace) / 2)
198 ) + contPaddingBox[beforeName];
201 pos = contPaddingBox[beforeName];
203 } else if (pack == "justify") {
204 pos = contPaddingBox[beforeName];
205 spacing = Math.floor(availableSpace / (items.length - 1));
209 // Default aligning (start) the other ones needs to be calculated while doing the layout
210 rect[alignAxisName] = contPaddingBox[alignBeforeName];
212 // Start laying out controls
213 for (i = 0, l = items.length; i < l; i++) {
215 ctrlLayoutRect = ctrl.layoutRect();
216 size = ctrlLayoutRect.maxFlexSize || ctrlLayoutRect[minSizeName];
218 // Align the control on the other axis
219 if (align === "center") {
220 rect[alignAxisName] = Math.round((contLayoutRect[alignInnerSizeName] / 2) - (ctrlLayoutRect[alignSizeName] / 2));
221 } else if (align === "stretch") {
222 rect[alignSizeName] = max(
223 ctrlLayoutRect[alignMinSizeName] || 0,
224 contLayoutRect[alignInnerSizeName] - contPaddingBox[alignBeforeName] - contPaddingBox[alignAfterName]
226 rect[alignAxisName] = contPaddingBox[alignBeforeName];
227 } else if (align === "end") {
228 rect[alignAxisName] = contLayoutRect[alignInnerSizeName] - ctrlLayoutRect[alignSizeName] - contPaddingBox.top;
231 // Calculate new size based on flex
232 if (ctrlLayoutRect.flex > 0) {
233 size += Math.ceil(ctrlLayoutRect.flex * ratio);
236 rect[sizeName] = size;
238 ctrl.layoutRect(rect);
240 // Recalculate containers
246 pos += size + spacing;