OLD | NEW |
| (Empty) |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 import 'dart:math' as math; | |
6 import 'box.dart'; | |
7 import 'object.dart'; | |
8 | |
9 class FlexBoxParentData extends BoxParentData with ContainerParentDataMixin<Rend
erBox> { | |
10 int flex; | |
11 | |
12 void merge(FlexBoxParentData other) { | |
13 if (other.flex != null) | |
14 flex = other.flex; | |
15 super.merge(other); | |
16 } | |
17 | |
18 String toString() => '${super.toString()}; flex=$flex'; | |
19 } | |
20 | |
21 enum FlexDirection { horizontal, vertical } | |
22 | |
23 enum FlexJustifyContent { | |
24 flexStart, | |
25 flexEnd, | |
26 center, | |
27 spaceBetween, | |
28 spaceAround, | |
29 } | |
30 | |
31 enum FlexAlignItems { | |
32 flexStart, | |
33 flexEnd, | |
34 center, | |
35 } | |
36 | |
37 typedef double _ChildSizingFunction(RenderBox child, BoxConstraints constraints)
; | |
38 | |
39 class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
exBoxParentData>, | |
40 RenderBoxContainerDefaultsMixin<RenderBo
x, FlexBoxParentData> { | |
41 // lays out RenderBox children using flexible layout | |
42 | |
43 RenderFlex({ | |
44 FlexDirection direction: FlexDirection.horizontal, | |
45 FlexJustifyContent justifyContent: FlexJustifyContent.flexStart, | |
46 FlexAlignItems alignItems: FlexAlignItems.center | |
47 }) : _direction = direction, _justifyContent = justifyContent, _alignItems = a
lignItems; | |
48 | |
49 FlexDirection _direction; | |
50 FlexDirection get direction => _direction; | |
51 void set direction (FlexDirection value) { | |
52 if (_direction != value) { | |
53 _direction = value; | |
54 markNeedsLayout(); | |
55 } | |
56 } | |
57 | |
58 FlexJustifyContent _justifyContent; | |
59 FlexJustifyContent get justifyContent => _justifyContent; | |
60 void set justifyContent (FlexJustifyContent value) { | |
61 if (_justifyContent != value) { | |
62 _justifyContent = value; | |
63 markNeedsLayout(); | |
64 } | |
65 } | |
66 | |
67 FlexAlignItems _alignItems; | |
68 FlexAlignItems get alignItems => _alignItems; | |
69 void set alignItems (FlexAlignItems value) { | |
70 if (_alignItems != value) { | |
71 _alignItems = value; | |
72 markNeedsLayout(); | |
73 } | |
74 } | |
75 | |
76 void setParentData(RenderBox child) { | |
77 if (child.parentData is! FlexBoxParentData) | |
78 child.parentData = new FlexBoxParentData(); | |
79 } | |
80 | |
81 double _getIntrinsicSize({ BoxConstraints constraints, | |
82 FlexDirection sizingDirection, | |
83 _ChildSizingFunction childSize }) { | |
84 // http://www.w3.org/TR/2015/WD-css-flexbox-1-20150514/#intrinsic-sizes | |
85 if (_direction == sizingDirection) { | |
86 // INTRINSIC MAIN SIZE | |
87 // Intrinsic main size is the smallest size the flex container can take | |
88 // while maintaining the min/max-content contributions of its flex items. | |
89 BoxConstraints childConstraints; | |
90 switch(_direction) { | |
91 case FlexDirection.horizontal: | |
92 childConstraints = new BoxConstraints(maxHeight: constraints.maxHeight
); | |
93 break; | |
94 case FlexDirection.vertical: | |
95 childConstraints = new BoxConstraints(maxWidth: constraints.maxWidth); | |
96 break; | |
97 } | |
98 | |
99 double totalFlex = 0.0; | |
100 double inflexibleSpace = 0.0; | |
101 double maxFlexFractionSoFar = 0.0; | |
102 RenderBox child = firstChild; | |
103 while (child != null) { | |
104 int flex = _getFlex(child); | |
105 totalFlex += flex; | |
106 if (flex > 0) { | |
107 double flexFraction = childSize(child, childConstraints) / _getFlex(ch
ild); | |
108 maxFlexFractionSoFar = math.max(maxFlexFractionSoFar, flexFraction); | |
109 } else { | |
110 inflexibleSpace += childSize(child, childConstraints); | |
111 } | |
112 child = child.parentData.nextSibling; | |
113 } | |
114 double mainSize = maxFlexFractionSoFar * totalFlex + inflexibleSpace; | |
115 | |
116 // Ensure that we don't violate the given constraints with our result | |
117 switch(_direction) { | |
118 case FlexDirection.horizontal: | |
119 return constraints.constrainWidth(mainSize); | |
120 case FlexDirection.vertical: | |
121 return constraints.constrainHeight(mainSize); | |
122 } | |
123 } else { | |
124 // INTRINSIC CROSS SIZE | |
125 // The spec wants us to perform layout into the given available main-axis | |
126 // space and return the cross size. That's too expensive, so instead we | |
127 // size inflexible children according to their max intrinsic size in the | |
128 // main direction and use those constraints to determine their max | |
129 // intrinsic size in the cross direction. We don't care if the caller | |
130 // asked for max or min -- the answer is always computed using the | |
131 // max size in the main direction. | |
132 | |
133 double availableMainSpace; | |
134 BoxConstraints childConstraints; | |
135 switch(_direction) { | |
136 case FlexDirection.horizontal: | |
137 childConstraints = new BoxConstraints(maxWidth: constraints.maxWidth); | |
138 availableMainSpace = constraints.maxWidth; | |
139 break; | |
140 case FlexDirection.vertical: | |
141 childConstraints = new BoxConstraints(maxHeight: constraints.maxHeight
); | |
142 availableMainSpace = constraints.maxHeight; | |
143 break; | |
144 } | |
145 | |
146 // Get inflexible space using the max in the main direction | |
147 int totalFlex = 0; | |
148 double inflexibleSpace = 0.0; | |
149 double maxCrossSize = 0.0; | |
150 RenderBox child = firstChild; | |
151 while (child != null) { | |
152 int flex = _getFlex(child); | |
153 totalFlex += flex; | |
154 double mainSize; | |
155 double crossSize; | |
156 if (flex == 0) { | |
157 switch (_direction) { | |
158 case FlexDirection.horizontal: | |
159 mainSize = child.getMaxIntrinsicWidth(childConstraints); | |
160 BoxConstraints widthConstraints = | |
161 new BoxConstraints(minWidth: mainSize, maxWidth: mainSize); | |
162 crossSize = child.getMaxIntrinsicHeight(widthConstraints); | |
163 break; | |
164 case FlexDirection.vertical: | |
165 mainSize = child.getMaxIntrinsicHeight(childConstraints); | |
166 BoxConstraints heightConstraints = | |
167 new BoxConstraints(minWidth: mainSize, maxWidth: mainSize); | |
168 crossSize = child.getMaxIntrinsicWidth(heightConstraints); | |
169 break; | |
170 } | |
171 inflexibleSpace += mainSize; | |
172 maxCrossSize = math.max(maxCrossSize, crossSize); | |
173 } | |
174 child = child.parentData.nextSibling; | |
175 } | |
176 | |
177 // Determine the spacePerFlex by allocating the remaining available space | |
178 double spacePerFlex = (availableMainSpace - inflexibleSpace) / totalFlex; | |
179 | |
180 // Size remaining items, find the maximum cross size | |
181 child = firstChild; | |
182 while (child != null) { | |
183 int flex = _getFlex(child); | |
184 if (flex > 0) { | |
185 double childMainSize = spacePerFlex * flex; | |
186 double crossSize; | |
187 switch (_direction) { | |
188 case FlexDirection.horizontal: | |
189 BoxConstraints childConstraints = | |
190 new BoxConstraints(minWidth: childMainSize, maxWidth: childMainS
ize); | |
191 crossSize = child.getMaxIntrinsicHeight(childConstraints); | |
192 break; | |
193 case FlexDirection.vertical: | |
194 BoxConstraints childConstraints = | |
195 new BoxConstraints(minHeight: childMainSize, maxHeight: childMai
nSize); | |
196 crossSize = child.getMaxIntrinsicWidth(childConstraints); | |
197 break; | |
198 } | |
199 maxCrossSize = math.max(maxCrossSize, crossSize); | |
200 } | |
201 child = child.parentData.nextSibling; | |
202 } | |
203 | |
204 // Ensure that we don't violate the given constraints with our result | |
205 switch(_direction) { | |
206 case FlexDirection.horizontal: | |
207 return constraints.constrainHeight(maxCrossSize); | |
208 case FlexDirection.vertical: | |
209 return constraints.constrainWidth(maxCrossSize); | |
210 } | |
211 } | |
212 } | |
213 | |
214 double getMinIntrinsicWidth(BoxConstraints constraints) { | |
215 return _getIntrinsicSize( | |
216 constraints: constraints, | |
217 sizingDirection: FlexDirection.horizontal, | |
218 childSize: (c, innerConstraints) => c.getMinIntrinsicWidth(innerConstraint
s) | |
219 ); | |
220 } | |
221 | |
222 double getMaxIntrinsicWidth(BoxConstraints constraints) { | |
223 return _getIntrinsicSize( | |
224 constraints: constraints, | |
225 sizingDirection: FlexDirection.horizontal, | |
226 childSize: (c, innerConstraints) => c.getMaxIntrinsicWidth(innerConstraint
s) | |
227 ); | |
228 } | |
229 | |
230 double getMinIntrinsicHeight(BoxConstraints constraints) { | |
231 return _getIntrinsicSize( | |
232 constraints: constraints, | |
233 sizingDirection: FlexDirection.vertical, | |
234 childSize: (c, innerConstraints) => c.getMinIntrinsicHeight(innerConstrain
ts) | |
235 ); | |
236 } | |
237 | |
238 double getMaxIntrinsicHeight(BoxConstraints constraints) { | |
239 return _getIntrinsicSize( | |
240 constraints: constraints, | |
241 sizingDirection: FlexDirection.vertical, | |
242 childSize: (c, innerConstraints) => c.getMaxIntrinsicHeight(innerConstrain
ts)); | |
243 } | |
244 | |
245 int _getFlex(RenderBox child) { | |
246 assert(child.parentData is FlexBoxParentData); | |
247 return child.parentData.flex != null ? child.parentData.flex : 0; | |
248 } | |
249 | |
250 double _getCrossSize(RenderBox child) { | |
251 return (_direction == FlexDirection.horizontal) ? child.size.height : child.
size.width; | |
252 } | |
253 | |
254 double _getMainSize(RenderBox child) { | |
255 return (_direction == FlexDirection.horizontal) ? child.size.width : child.s
ize.height; | |
256 } | |
257 | |
258 void performLayout() { | |
259 // Based on http://www.w3.org/TR/css-flexbox-1/ Section 9.7 Resolving Flexib
le Lengths | |
260 // Steps 1-3. Determine used flex factor, size inflexible items, calculate f
ree space | |
261 int totalFlex = 0; | |
262 int totalChildren = 0; | |
263 assert(constraints != null); | |
264 final double mainSize = (_direction == FlexDirection.horizontal) ? constrain
ts.maxWidth : constraints.maxHeight; | |
265 double crossSize = 0.0; // This will be determined after laying out the chi
ldren | |
266 double freeSpace = mainSize; | |
267 RenderBox child = firstChild; | |
268 while (child != null) { | |
269 totalChildren++; | |
270 int flex = _getFlex(child); | |
271 if (flex > 0) { | |
272 totalFlex += child.parentData.flex; | |
273 } else { | |
274 BoxConstraints innerConstraints = new BoxConstraints(maxHeight: constrai
nts.maxHeight, | |
275 maxWidth: constrain
ts.maxWidth); | |
276 child.layout(innerConstraints, parentUsesSize: true); | |
277 freeSpace -= _getMainSize(child); | |
278 crossSize = math.max(crossSize, _getCrossSize(child)); | |
279 } | |
280 child = child.parentData.nextSibling; | |
281 } | |
282 | |
283 // Steps 4-5. Distribute remaining space to flexible children. | |
284 double spacePerFlex = totalFlex > 0 ? (freeSpace / totalFlex) : 0.0; | |
285 double usedSpace = 0.0; | |
286 child = firstChild; | |
287 while (child != null) { | |
288 int flex = _getFlex(child); | |
289 if (flex > 0) { | |
290 double spaceForChild = spacePerFlex * flex; | |
291 BoxConstraints innerConstraints; | |
292 switch (_direction) { | |
293 case FlexDirection.horizontal: | |
294 innerConstraints = new BoxConstraints(maxHeight: constraints.maxHeig
ht, | |
295 minWidth: spaceForChild, | |
296 maxWidth: spaceForChild); | |
297 break; | |
298 case FlexDirection.vertical: | |
299 innerConstraints = new BoxConstraints(minHeight: spaceForChild, | |
300 maxHeight: spaceForChild, | |
301 maxWidth: constraints.maxWidth
); | |
302 break; | |
303 } | |
304 child.layout(innerConstraints, parentUsesSize: true); | |
305 usedSpace += _getMainSize(child); | |
306 crossSize = math.max(crossSize, _getCrossSize(child)); | |
307 } | |
308 child = child.parentData.nextSibling; | |
309 } | |
310 | |
311 // Section 8.2: Axis Alignment using the justify-content property | |
312 double remainingSpace = math.max(0.0, freeSpace - usedSpace); | |
313 double leadingSpace; | |
314 double betweenSpace; | |
315 child = firstChild; | |
316 switch (_justifyContent) { | |
317 case FlexJustifyContent.flexStart: | |
318 leadingSpace = 0.0; | |
319 betweenSpace = 0.0; | |
320 break; | |
321 case FlexJustifyContent.flexEnd: | |
322 leadingSpace = remainingSpace; | |
323 betweenSpace = 0.0; | |
324 break; | |
325 case FlexJustifyContent.center: | |
326 leadingSpace = remainingSpace / 2.0; | |
327 betweenSpace = 0.0; | |
328 break; | |
329 case FlexJustifyContent.spaceBetween: | |
330 leadingSpace = 0.0; | |
331 betweenSpace = totalChildren > 1 ? remainingSpace / (totalChildren - 1)
: 0.0; | |
332 break; | |
333 case FlexJustifyContent.spaceAround: | |
334 betweenSpace = totalChildren > 0 ? remainingSpace / totalChildren : 0.0; | |
335 leadingSpace = betweenSpace / 2.0; | |
336 break; | |
337 } | |
338 | |
339 switch (_direction) { | |
340 case FlexDirection.horizontal: | |
341 size = constraints.constrain(new Size(mainSize, crossSize)); | |
342 crossSize = size.height; | |
343 break; | |
344 case FlexDirection.vertical: | |
345 size = constraints.constrain(new Size(crossSize, mainSize)); | |
346 crossSize = size.width; | |
347 break; | |
348 } | |
349 | |
350 // Position elements. For now, center the flex items in the cross direction | |
351 double childMainPosition = leadingSpace; | |
352 child = firstChild; | |
353 while (child != null) { | |
354 double childCrossPosition; | |
355 switch (_alignItems) { | |
356 case FlexAlignItems.flexStart: | |
357 childCrossPosition = 0.0; | |
358 break; | |
359 case FlexAlignItems.flexEnd: | |
360 childCrossPosition = crossSize - _getCrossSize(child); | |
361 break; | |
362 case FlexAlignItems.center: | |
363 childCrossPosition = crossSize / 2.0 - _getCrossSize(child) / 2.0; | |
364 break; | |
365 } | |
366 switch (_direction) { | |
367 case FlexDirection.horizontal: | |
368 child.parentData.position = new Point(childMainPosition, childCrossPos
ition); | |
369 break; | |
370 case FlexDirection.vertical: | |
371 child.parentData.position = new Point(childCrossPosition, childMainPos
ition); | |
372 break; | |
373 } | |
374 childMainPosition += _getMainSize(child) + betweenSpace; | |
375 child = child.parentData.nextSibling; | |
376 } | |
377 } | |
378 | |
379 void hitTestChildren(HitTestResult result, { Point position }) { | |
380 defaultHitTestChildren(result, position: position); | |
381 } | |
382 | |
383 void paint(RenderObjectDisplayList canvas) { | |
384 defaultPaint(canvas); | |
385 } | |
386 } | |
OLD | NEW |