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