OLD | NEW |
1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 part of layout; | 5 part of layout; |
6 | 6 |
7 /** | 7 /** |
8 * Implements a grid-based layout system based on: | 8 * Implements a grid-based layout system based on: |
9 * [http://dev.w3.org/csswg/css3-grid-align/] | 9 * [http://dev.w3.org/csswg/css3-grid-align/] |
10 * | 10 * |
(...skipping 25 matching lines...) Expand all Loading... |
36 // - The CSS parsing is not 100% complete, see the parser TODOs. | 36 // - The CSS parsing is not 100% complete, see the parser TODOs. |
37 // - We don't implement error recovery for invalid combinations of CSS | 37 // - We don't implement error recovery for invalid combinations of CSS |
38 // properties, or invalid CSS property values. Instead we throw an error. | 38 // properties, or invalid CSS property values. Instead we throw an error. |
39 // | 39 // |
40 // TODO(jmesserly): high level performance optimizations we could do: | 40 // TODO(jmesserly): high level performance optimizations we could do: |
41 // - Optimize for the common case of spanCount = 1 | 41 // - Optimize for the common case of spanCount = 1 |
42 // - Optimize for the vbox/hbox case (1 row or 1 column) | 42 // - Optimize for the vbox/hbox case (1 row or 1 column) |
43 // - Optimize for the case of no content sized tracks | 43 // - Optimize for the case of no content sized tracks |
44 // - Optimize for the "incremental update" cases | 44 // - Optimize for the "incremental update" cases |
45 class GridLayout extends ViewLayout { | 45 class GridLayout extends ViewLayout { |
46 | |
47 /** Configuration parameters defined in CSS. */ | 46 /** Configuration parameters defined in CSS. */ |
48 final GridTrackList rows; | 47 final GridTrackList rows; |
49 final GridTrackList columns; | 48 final GridTrackList columns; |
50 final GridTemplate template; | 49 final GridTemplate template; |
51 | 50 |
52 /** The default sizing for new rows. */ | 51 /** The default sizing for new rows. */ |
53 final TrackSizing rowSizing; | 52 final TrackSizing rowSizing; |
54 | 53 |
55 /** The default sizing for new columns. */ | 54 /** The default sizing for new columns. */ |
56 final TrackSizing columnSizing; | 55 final TrackSizing columnSizing; |
57 | 56 |
58 /** | 57 /** |
59 * This stores the grid's size during a layout. | 58 * This stores the grid's size during a layout. |
60 * Used for rows/columns with % or fr units. | 59 * Used for rows/columns with % or fr units. |
61 */ | 60 */ |
62 int _gridWidth, _gridHeight; | 61 int _gridWidth, _gridHeight; |
63 | 62 |
64 /** | 63 /** |
65 * During a layout, this stores all row/column size information. | 64 * During a layout, this stores all row/column size information. |
66 * Because grid-items can implicitly specify their own rows/columns, we can't | 65 * Because grid-items can implicitly specify their own rows/columns, we can't |
67 * compute this until we know the set of items. | 66 * compute this until we know the set of items. |
68 */ | 67 */ |
69 List<GridTrack> _rowTracks, _columnTracks; | 68 List<GridTrack> _rowTracks, _columnTracks; |
70 | 69 |
71 /** During a layout, tracks which dimension we're processing. */ | 70 /** During a layout, tracks which dimension we're processing. */ |
72 Dimension _dimension; | 71 Dimension _dimension; |
73 | 72 |
74 GridLayout(Positionable view) | 73 GridLayout(Positionable view) |
75 : super(view), | 74 : super(view), |
76 rows = _GridTrackParser.parse(view.customStyle['grid-rows']), | 75 rows = _GridTrackParser.parse(view.customStyle['grid-rows']), |
77 columns = _GridTrackParser.parse(view.customStyle['grid-columns']), | 76 columns = _GridTrackParser.parse(view.customStyle['grid-columns']), |
78 template = _GridTemplateParser.parse(view.customStyle['grid-template']), | 77 template = _GridTemplateParser.parse(view.customStyle['grid-template']), |
79 | 78 rowSizing = _GridTrackParser |
80 rowSizing = _GridTrackParser.parseTrackSizing( | 79 .parseTrackSizing(view.customStyle['grid-row-sizing']), |
81 view.customStyle['grid-row-sizing']), | 80 columnSizing = _GridTrackParser |
82 | 81 .parseTrackSizing(view.customStyle['grid-column-sizing']) { |
83 columnSizing = _GridTrackParser.parseTrackSizing( | |
84 view.customStyle['grid-column-sizing']) { | |
85 | |
86 _rowTracks = rows != null ? rows.tracks : new List<GridTrack>(); | 82 _rowTracks = rows != null ? rows.tracks : new List<GridTrack>(); |
87 _columnTracks = columns != null ? columns.tracks : new List<GridTrack>(); | 83 _columnTracks = columns != null ? columns.tracks : new List<GridTrack>(); |
88 } | 84 } |
89 | 85 |
90 | |
91 int get currentWidth => _gridWidth; | 86 int get currentWidth => _gridWidth; |
92 int get currentHeight => _gridHeight; | 87 int get currentHeight => _gridHeight; |
93 | 88 |
94 void cacheExistingBrowserLayout() { | 89 void cacheExistingBrowserLayout() { |
95 // We don't need to do anything as we don't rely on the _cachedViewRect | 90 // We don't need to do anything as we don't rely on the _cachedViewRect |
96 // when the grid layout is used. | 91 // when the grid layout is used. |
97 } | 92 } |
98 | 93 |
99 // TODO(jacobr): cleanup this method so that it returns a Future | 94 // TODO(jacobr): cleanup this method so that it returns a Future |
100 // rather than taking a Completer as an argument. | 95 // rather than taking a Completer as an argument. |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
145 /** | 140 /** |
146 * This is the core Grid Track sizing algorithm. It is run for Grid columns | 141 * This is the core Grid Track sizing algorithm. It is run for Grid columns |
147 * and Grid rows. The goal of the function is to ensure: | 142 * and Grid rows. The goal of the function is to ensure: |
148 * 1. That each Grid Track satisfies its minSizing | 143 * 1. That each Grid Track satisfies its minSizing |
149 * 2. That each Grid Track grows from the breadth which satisfied its | 144 * 2. That each Grid Track grows from the breadth which satisfied its |
150 * minSizing to a breadth which satifies its | 145 * minSizing to a breadth which satifies its |
151 * maxSizing, subject to RemainingSpace. | 146 * maxSizing, subject to RemainingSpace. |
152 */ | 147 */ |
153 // Note: spec does not correctly doc all the parameters to this function. | 148 // Note: spec does not correctly doc all the parameters to this function. |
154 void _computeUsedBreadthOfTracks(List<GridTrack> tracks) { | 149 void _computeUsedBreadthOfTracks(List<GridTrack> tracks) { |
155 | |
156 // TODO(jmesserly): as a performance optimization we could cache this | 150 // TODO(jmesserly): as a performance optimization we could cache this |
157 final items = view.childViews | 151 final items = view.childViews.map((view_) => view_.layout).toList(); |
158 .map((view_) => view_.layout) | |
159 .toList(); | |
160 CollectionUtils.sortBy(items, (item) => _getSpanCount(item)); | 152 CollectionUtils.sortBy(items, (item) => _getSpanCount(item)); |
161 | 153 |
162 // 1. Initialize per Grid Track variables | 154 // 1. Initialize per Grid Track variables |
163 for (final t in tracks) { | 155 for (final t in tracks) { |
164 // percentage or length sizing functions will return a value | 156 // percentage or length sizing functions will return a value |
165 // min-content, max-content, or a fraction will be set to 0 | 157 // min-content, max-content, or a fraction will be set to 0 |
166 t.usedBreadth = t.minSizing.resolveLength(_getGridContentSize()); | 158 t.usedBreadth = t.minSizing.resolveLength(_getGridContentSize()); |
167 t.maxBreadth = t.maxSizing.resolveLength(_getGridContentSize()); | 159 t.maxBreadth = t.maxSizing.resolveLength(_getGridContentSize()); |
168 t.updatedBreadth = 0; | 160 t.updatedBreadth = 0; |
169 } | 161 } |
(...skipping 15 matching lines...) Expand all Loading... |
185 | 177 |
186 // 4. Resolve content-based MaxTrackSizingFunctions | 178 // 4. Resolve content-based MaxTrackSizingFunctions |
187 _distributeSpaceBySpanCount(items, ContentSizeMode.MIN, MAX_BREADTH); | 179 _distributeSpaceBySpanCount(items, ContentSizeMode.MIN, MAX_BREADTH); |
188 | 180 |
189 _distributeSpaceBySpanCount(items, ContentSizeMode.MAX, MAX_BREADTH); | 181 _distributeSpaceBySpanCount(items, ContentSizeMode.MAX, MAX_BREADTH); |
190 | 182 |
191 // 5. Grow all Grid Tracks in GridTracks from their usedBreadth up to their | 183 // 5. Grow all Grid Tracks in GridTracks from their usedBreadth up to their |
192 // maxBreadth value until RemainingSpace is exhausted. | 184 // maxBreadth value until RemainingSpace is exhausted. |
193 // Note: it's not spec'd what to pass as the accumulator, but usedBreadth | 185 // Note: it's not spec'd what to pass as the accumulator, but usedBreadth |
194 // seems right. | 186 // seems right. |
195 _distributeSpaceToTracks(tracks, _getRemainingSpace(tracks), | 187 _distributeSpaceToTracks( |
196 USED_BREADTH, false); | 188 tracks, _getRemainingSpace(tracks), USED_BREADTH, false); |
197 | 189 |
198 // Spec wording is confusing about which direction this assignment happens, | 190 // Spec wording is confusing about which direction this assignment happens, |
199 // but this is the way that makes sense. | 191 // but this is the way that makes sense. |
200 for (final t in tracks) { | 192 for (final t in tracks) { |
201 t.usedBreadth = t.updatedBreadth; | 193 t.usedBreadth = t.updatedBreadth; |
202 } | 194 } |
203 | 195 |
204 // 6. Grow all Grid Tracks having a fraction as their maxSizing | 196 // 6. Grow all Grid Tracks having a fraction as their maxSizing |
205 final tempBreadth = _calcNormalizedFractionBreadth(tracks); | 197 final tempBreadth = _calcNormalizedFractionBreadth(tracks); |
206 for (final t in tracks) { | 198 for (final t in tracks) { |
207 t.usedBreadth = Math.max(t.usedBreadth, | 199 t.usedBreadth = |
208 tempBreadth * t.maxSizing.fractionValue); | 200 Math.max(t.usedBreadth, tempBreadth * t.maxSizing.fractionValue); |
209 } | 201 } |
210 | 202 |
211 _computeTrackPositions(tracks); | 203 _computeTrackPositions(tracks); |
212 } | 204 } |
213 | 205 |
214 /** | 206 /** |
215 * Final steps to finish positioning tracks. Takes the track size and uses | 207 * Final steps to finish positioning tracks. Takes the track size and uses |
216 * it to get start and end positions. Also rounds the positions to integers. | 208 * it to get start and end positions. Also rounds the positions to integers. |
217 */ | 209 */ |
218 void _computeTrackPositions(List<GridTrack> tracks) { | 210 void _computeTrackPositions(List<GridTrack> tracks) { |
(...skipping 29 matching lines...) Expand all Loading... |
248 | 240 |
249 /** | 241 /** |
250 * This method computes a '1fr' value, referred to as the | 242 * This method computes a '1fr' value, referred to as the |
251 * tempBreadth, for a set of Grid Tracks. The value computed | 243 * tempBreadth, for a set of Grid Tracks. The value computed |
252 * will ensure that when the tempBreadth is multiplied by the | 244 * will ensure that when the tempBreadth is multiplied by the |
253 * fractions associated with tracks, that the UsedBreadths of tracks | 245 * fractions associated with tracks, that the UsedBreadths of tracks |
254 * will increase by an amount equal to the maximum of zero and the specified | 246 * will increase by an amount equal to the maximum of zero and the specified |
255 * freeSpace less the sum of the current UsedBreadths. | 247 * freeSpace less the sum of the current UsedBreadths. |
256 */ | 248 */ |
257 num _calcNormalizedFractionBreadth(List<GridTrack> tracks) { | 249 num _calcNormalizedFractionBreadth(List<GridTrack> tracks) { |
258 | |
259 final fractionTracks = tracks.where((t) => t.maxSizing.isFraction).toList(); | 250 final fractionTracks = tracks.where((t) => t.maxSizing.isFraction).toList(); |
260 | 251 |
261 // Note: the spec has various bugs in this function, such as mismatched | 252 // Note: the spec has various bugs in this function, such as mismatched |
262 // identifiers and names that aren't defined. For the most part it's | 253 // identifiers and names that aren't defined. For the most part it's |
263 // possible to figure out the meaning. It's also a bit confused about | 254 // possible to figure out the meaning. It's also a bit confused about |
264 // how to compute spaceNeededFromFractionTracks, but that should just be the | 255 // how to compute spaceNeededFromFractionTracks, but that should just be the |
265 // set to the remaining free space after usedBreadth is accounted for. | 256 // set to the remaining free space after usedBreadth is accounted for. |
266 | 257 |
267 // We use the tempBreadth field to store the normalized fraction breadth | 258 // We use the tempBreadth field to store the normalized fraction breadth |
268 for (final t in fractionTracks) { | 259 for (final t in fractionTracks) { |
(...skipping 19 matching lines...) Expand all Loading... |
288 return spaceNeededFromFractionTracks / accumulatedFractions; | 279 return spaceNeededFromFractionTracks / accumulatedFractions; |
289 } | 280 } |
290 | 281 |
291 /** | 282 /** |
292 * Ensures that for each Grid Track in tracks, a value will be | 283 * Ensures that for each Grid Track in tracks, a value will be |
293 * computed, updatedBreadth, that represents the Grid Track's share of | 284 * computed, updatedBreadth, that represents the Grid Track's share of |
294 * freeSpace. | 285 * freeSpace. |
295 */ | 286 */ |
296 void _distributeSpaceToTracks(List<GridTrack> tracks, num freeSpace, | 287 void _distributeSpaceToTracks(List<GridTrack> tracks, num freeSpace, |
297 _BreadthAccumulator breadth, bool ignoreMaxBreadth) { | 288 _BreadthAccumulator breadth, bool ignoreMaxBreadth) { |
298 | |
299 // TODO(jmesserly): in some cases it would be safe to sort the passed in | 289 // TODO(jmesserly): in some cases it would be safe to sort the passed in |
300 // list in place. Not always though. | 290 // list in place. Not always though. |
301 tracks = CollectionUtils.orderBy(tracks, | 291 tracks = CollectionUtils.orderBy( |
302 (t) => t.maxBreadth - breadth.getSize(t)); | 292 tracks, (t) => t.maxBreadth - breadth.getSize(t)); |
303 | 293 |
304 // Give each Grid Track an equal share of the space, but without exceeding | 294 // Give each Grid Track an equal share of the space, but without exceeding |
305 // their maxBreadth values. Because there are different MaxBreadths | 295 // their maxBreadth values. Because there are different MaxBreadths |
306 // assigned to the different Grid Tracks, this can result in uneven growth. | 296 // assigned to the different Grid Tracks, this can result in uneven growth. |
307 for (int i = 0; i < tracks.length; i++) { | 297 for (int i = 0; i < tracks.length; i++) { |
308 num share = freeSpace / (tracks.length - i); | 298 num share = freeSpace / (tracks.length - i); |
309 share = Math.min(share, tracks[i].maxBreadth); | 299 share = Math.min(share, tracks[i].maxBreadth); |
310 tracks[i].tempBreadth = share; | 300 tracks[i].tempBreadth = share; |
311 freeSpace -= share; | 301 freeSpace -= share; |
312 } | 302 } |
(...skipping 21 matching lines...) Expand all Loading... |
334 /** | 324 /** |
335 * This function prioritizes the distribution of space driven by Grid Items | 325 * This function prioritizes the distribution of space driven by Grid Items |
336 * in content-sized Grid Tracks by the Grid Item's spanCount. That is, Grid | 326 * in content-sized Grid Tracks by the Grid Item's spanCount. That is, Grid |
337 * Items having a lower spanCount have an opportunity to increase the size of | 327 * Items having a lower spanCount have an opportunity to increase the size of |
338 * the Grid Tracks they cover before those with larger SpanCounts. | 328 * the Grid Tracks they cover before those with larger SpanCounts. |
339 * | 329 * |
340 * Note: items are assumed to be already sorted in increasing span count | 330 * Note: items are assumed to be already sorted in increasing span count |
341 */ | 331 */ |
342 void _distributeSpaceBySpanCount(List<ViewLayout> items, | 332 void _distributeSpaceBySpanCount(List<ViewLayout> items, |
343 ContentSizeMode sizeMode, _BreadthAccumulator breadth) { | 333 ContentSizeMode sizeMode, _BreadthAccumulator breadth) { |
344 | 334 items = items |
345 items = items.where((item) => | 335 .where((item) => |
346 _hasContentSizedTracks(_getTracks(item), sizeMode, breadth)).toList(); | 336 _hasContentSizedTracks(_getTracks(item), sizeMode, breadth)) |
| 337 .toList(); |
347 | 338 |
348 var tracks = []; | 339 var tracks = []; |
349 | 340 |
350 for (int i = 0; i < items.length; i++) { | 341 for (int i = 0; i < items.length; i++) { |
351 final item = items[i]; | 342 final item = items[i]; |
352 | 343 |
353 final itemTargetSize = item.measureContent(this, _dimension, sizeMode); | 344 final itemTargetSize = item.measureContent(this, _dimension, sizeMode); |
354 | 345 |
355 final spannedTracks = _getTracks(item); | 346 final spannedTracks = _getTracks(item); |
356 _distributeSpaceToTracks(spannedTracks, itemTargetSize, breadth, true); | 347 _distributeSpaceToTracks(spannedTracks, itemTargetSize, breadth, true); |
357 | 348 |
358 // Remember that we need to update the sizes on these tracks | 349 // Remember that we need to update the sizes on these tracks |
359 tracks.addAll(spannedTracks); | 350 tracks.addAll(spannedTracks); |
360 | 351 |
361 // Each time we transition to a new spanCount, update any modified tracks | 352 // Each time we transition to a new spanCount, update any modified tracks |
362 bool spanCountFinished = false; | 353 bool spanCountFinished = false; |
363 if (i + 1 == items.length) { | 354 if (i + 1 == items.length) { |
364 spanCountFinished = true; | 355 spanCountFinished = true; |
365 } else if (_getSpanCount(item) != _getSpanCount(items[i + 1])) { | 356 } else if (_getSpanCount(item) != _getSpanCount(items[i + 1])) { |
366 spanCountFinished = true; | 357 spanCountFinished = true; |
367 } | 358 } |
368 | 359 |
369 if (spanCountFinished) { | 360 if (spanCountFinished) { |
370 for (final t in tracks) { | 361 for (final t in tracks) { |
371 breadth.setSize(t, | 362 breadth.setSize(t, Math.max(breadth.getSize(t), t.updatedBreadth)); |
372 Math.max(breadth.getSize(t), t.updatedBreadth)); | |
373 } | 363 } |
374 tracks = []; | 364 tracks = []; |
375 } | 365 } |
376 } | 366 } |
377 } | 367 } |
378 | 368 |
379 /** | 369 /** |
380 * Returns true if we have an appropriate content sized dimension, and don't | 370 * Returns true if we have an appropriate content sized dimension, and don't |
381 * cross a fractional track. | 371 * cross a fractional track. |
382 */ | 372 */ |
383 static bool _hasContentSizedTracks(Iterable<GridTrack> tracks, | 373 static bool _hasContentSizedTracks(Iterable<GridTrack> tracks, |
384 ContentSizeMode sizeMode, _BreadthAccumulator breadth) { | 374 ContentSizeMode sizeMode, _BreadthAccumulator breadth) { |
385 | |
386 for (final t in tracks) { | 375 for (final t in tracks) { |
387 final fn = breadth.getSizingFunction(t); | 376 final fn = breadth.getSizingFunction(t); |
388 if (sizeMode == ContentSizeMode.MAX && fn.isMaxContentSized || | 377 if (sizeMode == ContentSizeMode.MAX && fn.isMaxContentSized || |
389 sizeMode == ContentSizeMode.MIN && fn.isContentSized) { | 378 sizeMode == ContentSizeMode.MIN && fn.isContentSized) { |
390 | |
391 // Make sure we don't cross a fractional track | 379 // Make sure we don't cross a fractional track |
392 return tracks.length == 1 || !tracks.any((t_) => t_.isFractional); | 380 return tracks.length == 1 || !tracks.any((t_) => t_.isFractional); |
393 } | 381 } |
394 } | 382 } |
395 return false; | 383 return false; |
396 } | 384 } |
397 | 385 |
398 /** Ensures that the numbered track exists. */ | 386 /** Ensures that the numbered track exists. */ |
399 void _ensureTrack(List<GridTrack> tracks, TrackSizing sizing, | 387 void _ensureTrack( |
400 int start, int span) { | 388 List<GridTrack> tracks, TrackSizing sizing, int start, int span) { |
401 // Start is 1-based. Make it 0-based. | 389 // Start is 1-based. Make it 0-based. |
402 start -= 1; | 390 start -= 1; |
403 | 391 |
404 // Grow the list if needed | 392 // Grow the list if needed |
405 int length = start + span; | 393 int length = start + span; |
406 int first = Math.min(start, tracks.length); | 394 int first = Math.min(start, tracks.length); |
407 tracks.length = Math.max(tracks.length, length); | 395 tracks.length = Math.max(tracks.length, length); |
408 | 396 |
409 // Fill in tracks | 397 // Fill in tracks |
410 for (int i = first; i < length; i++) { | 398 for (int i = first; i < length; i++) { |
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
508 | 496 |
509 final result = new List<GridTrack>(span); | 497 final result = new List<GridTrack>(span); |
510 for (int i = 0; i < span; i++) { | 498 for (int i = 0; i < span; i++) { |
511 result[i] = tracks[start + i]; | 499 result[i] = tracks[start + i]; |
512 } | 500 } |
513 return result; | 501 return result; |
514 } | 502 } |
515 | 503 |
516 int _getSpanCount(ViewLayout item) { | 504 int _getSpanCount(ViewLayout item) { |
517 GridLayoutParams childLayout = item.layoutParams; | 505 GridLayoutParams childLayout = item.layoutParams; |
518 return (_dimension == Dimension.WIDTH ? | 506 return (_dimension == Dimension.WIDTH |
519 childLayout.columnSpan : childLayout.rowSpan); | 507 ? childLayout.columnSpan |
| 508 : childLayout.rowSpan); |
520 } | 509 } |
521 } | 510 } |
OLD | NEW |