Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(14)

Side by Side Diff: client/layout/GridLayout.dart

Issue 9382027: Move client/{base, observable, layout, touch, util, view} to samples/ui_lib . (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: '' Created 8 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « client/base/base.dart ('k') | client/layout/GridLayoutParams.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 /**
6 * Implements a grid-based layout system based on:
7 * [http://dev.w3.org/csswg/css3-grid-align/]
8 *
9 * This layout is designed to support animations and work on browsers that
10 * don't support grid natively. As such, we implement it on top of absolute
11 * positioning.
12 */
13 // TODO(jmesserly): the DOM integration still needs work:
14 // - The grid assumes it is absolutely positioned in its container.
15 // Becasue of that, the grid doesn't work right unless it has at least one
16 // fractional size in each dimension. In other words, only "top down" grids
17 // work at the moment, because the grid can't determine its own size.
18 // The core algorithm supports computing min breadth; the issue is about how
19 // to integrate it into our View layer.
20 // - Unless a child element is "display: inline-block" we can't get its
21 // horizontal content size.
22 // - Once we set an element's size to "position: absolute", we lose the
23 // ability to get its original content size. If the width or height gets
24 // set to something other than the content size, we can't recover the
25 // original content size.
26 // - There's some rounding to ints when we want to set the positions of our
27 // tracks. I don't think we necessarily need to do that.
28 //
29 // TODO(jmesserly): Some features of the spec are unimplemented:
30 // - grid-flow & items that have row and column set to 'auto'.
31 // - Grid writing modes (right to left languages, etc)
32 // - We don't do a second calculation pass if min content size of a grid-item
33 // changes due to column width.
34 // - The CSS parsing is not 100% complete, see the parser TODOs.
35 // - We don't implement error recovery for invalid combinations of CSS
36 // properties, or invalid CSS property values. Instead we throw an error.
37 //
38 // TODO(jmesserly): high level performance optimizations we could do:
39 // - Optimize for the common case of spanCount = 1
40 // - Optimize for the vbox/hbox case (1 row or 1 column)
41 // - Optimize for the case of no content sized tracks
42 // - Optimize for the "incremental update" cases
43 class GridLayout extends ViewLayout {
44
45 /** Configuration parameters defined in CSS. */
46 final GridTrackList rows;
47 final GridTrackList columns;
48 final GridTemplate template;
49
50 /** The default sizing for new rows. */
51 final TrackSizing rowSizing;
52
53 /** The default sizing for new columns. */
54 final TrackSizing columnSizing;
55
56 /**
57 * This stores the grid's size during a layout.
58 * Used for rows/columns with % or fr units.
59 */
60 int _gridWidth, _gridHeight;
61
62 /**
63 * During a layout, this stores all row/column size information.
64 * Because grid-items can implicitly specify their own rows/columns, we can't
65 * compute this until we know the set of items.
66 */
67 List<GridTrack> _rowTracks, _columnTracks;
68
69 /** During a layout, tracks which dimension we're processing. */
70 Dimension _dimension;
71
72 GridLayout(Positionable view)
73 : super(view),
74 rows = _GridTrackParser.parse(view.customStyle['grid-rows']),
75 columns = _GridTrackParser.parse(view.customStyle['grid-columns']),
76 template = _GridTemplateParser.parse(view.customStyle['grid-template']),
77
78 rowSizing = _GridTrackParser.parseTrackSizing(
79 view.customStyle['grid-row-sizing']),
80
81 columnSizing = _GridTrackParser.parseTrackSizing(
82 view.customStyle['grid-column-sizing']) {
83
84 _rowTracks = rows != null ? rows.tracks : new List<GridTrack>();
85 _columnTracks = columns != null ? columns.tracks : new List<GridTrack>();
86 }
87
88
89 int get currentWidth() => _gridWidth;
90 int get currentHeight() => _gridHeight;
91
92 void cacheExistingBrowserLayout() {
93 // We don't need to do anything as we don't rely on the _cachedViewRect
94 // when the grid layout is used.
95 }
96
97 // TODO(jacobr): cleanup this method so that it returns a Future
98 // rather than taking a Completer as an argument.
99 /** The main entry point for layout computation. */
100 void measureLayout(Future<Size> size, Completer<bool> changed) {
101 _ensureAllTracks();
102 window.requestLayoutFrame(() {
103 _gridWidth = size.value.width;
104 _gridHeight = size.value.height;
105
106 if (_rowTracks.length > 0 && _columnTracks.length > 0) {
107 _measureTracks();
108 _setBoundsOfChildren();
109 if (changed != null) {
110 changed.complete(true);
111 }
112 }
113 });
114 }
115
116 /**
117 * The top level measurement function.
118 * [http://dev.w3.org/csswg/css3-grid-align/#calculating-size-of-grid-tracks]
119 */
120 void _measureTracks() {
121 // Resolve logical width, then height. Width comes first so we can use
122 // the width when determining the content-sized height.
123 try {
124 _dimension = Dimension.WIDTH;
125 _computeUsedBreadthOfTracks(_columnTracks);
126 _dimension = Dimension.HEIGHT;
127 _computeUsedBreadthOfTracks(_rowTracks);
128 } finally {
129 _dimension = null;
130 }
131
132 // TODO(jmesserly): we're supposed to detect a min-content size change
133 // due to our computed width and trigger a new layout.
134 // How do we implement that?
135 }
136
137 num _getRemainingSpace(List<GridTrack> tracks) {
138 num remaining = _getGridContentSize();
139 remaining -= CollectionUtils.sum(tracks, (t) => t.usedBreadth);
140 return Math.max(0, remaining);
141 }
142
143 /**
144 * This is the core Grid Track sizing algorithm. It is run for Grid columns
145 * and Grid rows. The goal of the function is to ensure:
146 * 1. That each Grid Track satisfies its minSizing
147 * 2. That each Grid Track grows from the breadth which satisfied its
148 * minSizing to a breadth which satifies its
149 * maxSizing, subject to RemainingSpace.
150 */
151 // Note: spec does not correctly doc all the parameters to this function.
152 void _computeUsedBreadthOfTracks(List<GridTrack> tracks) {
153
154 // TODO(jmesserly): as a performance optimization we could cache this
155 final items = CollectionUtils.map(view.childViews, (view_) => view_.layout);
156 CollectionUtils.sortBy(items, (item) => _getSpanCount(item));
157
158 // 1. Initialize per Grid Track variables
159 for (final t in tracks) {
160 // percentage or length sizing functions will return a value
161 // min-content, max-content, or a fraction will be set to 0
162 t.usedBreadth = t.minSizing.resolveLength(_getGridContentSize());
163 t.maxBreadth = t.maxSizing.resolveLength(_getGridContentSize());
164 t.updatedBreadth = 0;
165 }
166
167 // 2. Resolve content-based MinTrackSizingFunctions
168 final USED_BREADTH = const _UsedBreadthAccumulator();
169 final MAX_BREADTH = const _MaxBreadthAccumulator();
170
171 _distributeSpaceBySpanCount(items, ContentSizeMode.MIN, USED_BREADTH);
172
173 _distributeSpaceBySpanCount(items, ContentSizeMode.MAX, USED_BREADTH);
174
175 // 3. Ensure that maxBreadth is as big as usedBreadth for each track
176 for (final t in tracks) {
177 if (t.maxBreadth < t.usedBreadth) {
178 t.maxBreadth = t.usedBreadth;
179 }
180 }
181
182 // 4. Resolve content-based MaxTrackSizingFunctions
183 _distributeSpaceBySpanCount(items, ContentSizeMode.MIN, MAX_BREADTH);
184
185 _distributeSpaceBySpanCount(items, ContentSizeMode.MAX, MAX_BREADTH);
186
187 // 5. Grow all Grid Tracks in GridTracks from their usedBreadth up to their
188 // maxBreadth value until RemainingSpace is exhausted.
189 // Note: it's not spec'd what to pass as the accumulator, but usedBreadth
190 // seems right.
191 _distributeSpaceToTracks(tracks, _getRemainingSpace(tracks),
192 USED_BREADTH, false);
193
194 // Spec wording is confusing about which direction this assignment happens,
195 // but this is the way that makes sense.
196 for (final t in tracks) {
197 t.usedBreadth = t.updatedBreadth;
198 }
199
200 // 6. Grow all Grid Tracks having a fraction as their maxSizing
201 final tempBreadth = _calcNormalizedFractionBreadth(tracks);
202 for (final t in tracks) {
203 t.usedBreadth = Math.max(t.usedBreadth,
204 tempBreadth * t.maxSizing.fractionValue);
205 }
206
207 _computeTrackPositions(tracks);
208 }
209
210 /**
211 * Final steps to finish positioning tracks. Takes the track size and uses
212 * it to get start and end positions. Also rounds the positions to integers.
213 */
214 void _computeTrackPositions(List<GridTrack> tracks) {
215 // Compute start positions of tracks, as well as the final position
216
217 num position = 0;
218 for (final t in tracks) {
219 t.start = position;
220 position += t.usedBreadth;
221 }
222
223 // Now, go through and round each position to an integer. Then
224 // compute the sizes based on those integers.
225 num finalPosition = position;
226
227 for (int i = 0; i < tracks.length; i++) {
228 int startEdge = tracks[i].start;
229 int endEdge;
230 if (i < tracks.length - 1) {
231 endEdge = tracks[i + 1].start.round().toInt();
232 tracks[i + 1].start = endEdge;
233 } else {
234 endEdge = finalPosition.round().toInt();
235 }
236 int breadth = endEdge - startEdge;
237
238 // check that we're not off by >= 1px.
239 assert((endEdge - startEdge - tracks[i].usedBreadth).abs() < 1);
240
241 tracks[i].usedBreadth = breadth;
242 }
243 }
244
245 /**
246 * This method computes a '1fr' value, referred to as the
247 * tempBreadth, for a set of Grid Tracks. The value computed
248 * will ensure that when the tempBreadth is multiplied by the
249 * fractions associated with tracks, that the UsedBreadths of tracks
250 * will increase by an amount equal to the maximum of zero and the specified
251 * freeSpace less the sum of the current UsedBreadths.
252 */
253 num _calcNormalizedFractionBreadth(List<GridTrack> tracks) {
254
255 final fractionTracks = tracks.filter((t) => t.maxSizing.isFraction);
256
257 // Note: the spec has various bugs in this function, such as mismatched
258 // identifiers and names that aren't defined. For the most part it's
259 // possible to figure out the meaning. It's also a bit confused about
260 // how to compute spaceNeededFromFractionTracks, but that should just be the
261 // set to the remaining free space after usedBreadth is accounted for.
262
263 // We use the tempBreadth field to store the normalized fraction breadth
264 for (final t in fractionTracks) {
265 t.tempBreadth = t.usedBreadth / t.maxSizing.fractionValue;
266 }
267
268 CollectionUtils.sortBy(fractionTracks, (t) => t.tempBreadth);
269
270 num spaceNeededFromFractionTracks = _getRemainingSpace(tracks);
271 num currentBandFractionBreadth = 0;
272 num accumulatedFractions = 0;
273 for (final t in fractionTracks) {
274 if (t.tempBreadth != currentBandFractionBreadth) {
275 if (t.tempBreadth * accumulatedFractions >
276 spaceNeededFromFractionTracks) {
277 break;
278 }
279 currentBandFractionBreadth = t.tempBreadth;
280 }
281 accumulatedFractions += t.maxSizing.fractionValue;
282 spaceNeededFromFractionTracks += t.usedBreadth;
283 }
284 return spaceNeededFromFractionTracks / accumulatedFractions;
285 }
286
287 /**
288 * Ensures that for each Grid Track in tracks, a value will be
289 * computed, updatedBreadth, that represents the Grid Track's share of
290 * freeSpace.
291 */
292 void _distributeSpaceToTracks(List<GridTrack> tracks, num freeSpace,
293 _BreadthAccumulator breadth, bool ignoreMaxBreadth) {
294
295 // TODO(jmesserly): in some cases it would be safe to sort the passed in
296 // list in place. Not always though.
297 tracks = CollectionUtils.orderBy(tracks,
298 (t) => t.maxBreadth - breadth.getSize(t));
299
300 // Give each Grid Track an equal share of the space, but without exceeding
301 // their maxBreadth values. Because there are different MaxBreadths
302 // assigned to the different Grid Tracks, this can result in uneven growth.
303 for (int i = 0; i < tracks.length; i++) {
304 num share = freeSpace / (tracks.length - i);
305 share = Math.min(share, tracks[i].maxBreadth);
306 tracks[i].tempBreadth = share;
307 freeSpace -= share;
308 }
309
310 // If the first loop completed having grown every Grid Track to its
311 // maxBreadth, and there is still freeSpace, then divide that space
312 // evenly and assign it to each Grid Track without regard for its
313 // maxBreadth. This phase of growth will always be even, but only occurs
314 // when the ignoreMaxBreadth flag is true.
315 if (freeSpace > 0 && ignoreMaxBreadth) {
316 for (int i = 0; i < tracks.length; i++) {
317 final share = freeSpace / (tracks.length - i);
318 tracks[i].tempBreadth += share;
319 freeSpace -= share;
320 }
321 }
322
323 // Note: the spec has us updating all grid tracks, not just the passed in
324 // tracks, but I think that's a spec bug.
325 for (final t in tracks) {
326 t.updatedBreadth = Math.max(t.updatedBreadth, t.tempBreadth);
327 }
328 }
329
330 /**
331 * This function prioritizes the distribution of space driven by Grid Items
332 * in content-sized Grid Tracks by the Grid Item's spanCount. That is, Grid
333 * Items having a lower spanCount have an opportunity to increase the size of
334 * the Grid Tracks they cover before those with larger SpanCounts.
335 *
336 * Note: items are assumed to be already sorted in increasing span count
337 */
338 void _distributeSpaceBySpanCount(List<ViewLayout> items,
339 ContentSizeMode sizeMode, _BreadthAccumulator breadth) {
340
341 items = items.filter((item) =>
342 _hasContentSizedTracks(_getTracks(item), sizeMode, breadth));
343
344 var tracks = [];
345
346 for (int i = 0; i < items.length; i++) {
347 final item = items[i];
348
349 final itemTargetSize = item.measureContent(this, _dimension, sizeMode);
350
351 final spannedTracks = _getTracks(item);
352 _distributeSpaceToTracks(spannedTracks, itemTargetSize, breadth, true);
353
354 // Remember that we need to update the sizes on these tracks
355 tracks.addAll(spannedTracks);
356
357 // Each time we transition to a new spanCount, update any modified tracks
358 bool spanCountFinished = false;
359 if (i + 1 == items.length) {
360 spanCountFinished = true;
361 } else if (_getSpanCount(item) != _getSpanCount(items[i + 1])) {
362 spanCountFinished = true;
363 }
364
365 if (spanCountFinished) {
366 for (final t in tracks) {
367 breadth.setSize(t,
368 Math.max(breadth.getSize(t), t.updatedBreadth));
369 }
370 tracks = [];
371 }
372 }
373 }
374
375 /**
376 * Returns true if we have an appropriate content sized dimension, and don't
377 * cross a fractional track.
378 */
379 static bool _hasContentSizedTracks(Collection<GridTrack> tracks,
380 ContentSizeMode sizeMode, _BreadthAccumulator breadth) {
381
382 for (final t in tracks) {
383 final fn = breadth.getSizingFunction(t);
384 if (sizeMode == ContentSizeMode.MAX && fn.isMaxContentSized ||
385 sizeMode == ContentSizeMode.MIN && fn.isContentSized) {
386
387 // Make sure we don't cross a fractional track
388 return tracks.length == 1 || !tracks.some((t_) => t_.isFractional);
389 }
390 }
391 return false;
392 }
393
394 /** Ensures that the numbered track exists. */
395 void _ensureTrack(List<GridTrack> tracks, TrackSizing sizing,
396 int start, int span) {
397 // Start is 1-based. Make it 0-based.
398 start -= 1;
399
400 // Grow the list if needed
401 int length = start + span;
402 int first = Math.min(start, tracks.length);
403 tracks.length = Math.max(tracks.length, length);
404
405 // Fill in tracks
406 for (int i = first; i < length; i++) {
407 if (tracks[i] == null) {
408 tracks[i] = new GridTrack(sizing);
409 }
410 }
411 }
412
413 /**
414 * Scans children creating GridLayoutParams as needed, and creates all of the
415 * rows and columns that we will need.
416 *
417 * Note: this can potentially create new rows/columns, so this needs to be
418 * run before the track sizing algorithm.
419 */
420 void _ensureAllTracks() {
421 final items = CollectionUtils.map(view.childViews, (view_) => view_.layout);
422
423 for (final child in items) {
424 if (child.layoutParams == null) {
425 final p = new GridLayoutParams(child.view, this);
426 _ensureTrack(_rowTracks, rowSizing, p.row, p.rowSpan);
427 _ensureTrack(_columnTracks, columnSizing, p.column, p.columnSpan);
428 child.layoutParams = p;
429 }
430 child.cacheExistingBrowserLayout();
431 }
432 }
433
434 /**
435 * Given the track sizes that were computed, position children in the grid.
436 */
437 void _setBoundsOfChildren() {
438 final items = CollectionUtils.map(view.childViews, (view_) => view_.layout);
439
440 for (final item in items) {
441 GridLayoutParams childLayout = item.layoutParams;
442 var xPos = _getTrackLocationX(childLayout);
443 var yPos = _getTrackLocationY(childLayout);
444
445 int left = xPos.start, width = xPos.length;
446 int top = yPos.start, height = yPos.length;
447
448 // Somewhat counterintuitively (at least to me):
449 // grid-col-align is the horizontal alignment
450 // grid-row-align is the vertical alignment
451 xPos = childLayout.columnAlign.align(xPos, item.currentWidth);
452 yPos = childLayout.rowAlign.align(yPos, item.currentHeight);
453
454 item.setBounds(xPos.start, yPos.start, xPos.length, yPos.length);
455 }
456 }
457
458 num _getGridContentSize() {
459 switch (_dimension) {
460 case Dimension.WIDTH:
461 return _gridWidth;
462 case Dimension.HEIGHT:
463 return _gridHeight;
464 }
465 }
466
467 _GridLocation _getTrackLocationX(GridLayoutParams childLayout) {
468 int start = childLayout.column - 1;
469 int end = start + childLayout.columnSpan - 1;
470
471 start = _columnTracks[start].start;
472 end = _columnTracks[end].end;
473
474 return new _GridLocation(start, end - start);
475 }
476
477 _GridLocation _getTrackLocationY(GridLayoutParams childLayout) {
478 int start = childLayout.row - 1;
479 int end = start + childLayout.rowSpan - 1;
480
481 start = _rowTracks[start].start;
482 end = _rowTracks[end].end;
483
484 return new _GridLocation(start, end - start);
485 }
486
487 /** Gets the tracks that this item crosses. */
488 // TODO(jmesserly): might be better to return an iterable
489 List<GridTrack> _getTracks(ViewLayout item) {
490 GridLayoutParams childLayout = item.layoutParams;
491
492 int start, span;
493 List<GridTrack> tracks;
494 switch (_dimension) {
495 case Dimension.WIDTH:
496 start = childLayout.column - 1;
497 span = childLayout.columnSpan;
498 tracks = _columnTracks;
499 break;
500 case Dimension.HEIGHT:
501 start = childLayout.row - 1;
502 span = childLayout.rowSpan;
503 tracks = _rowTracks;
504 }
505
506 assert(start >= 0 && span >= 1);
507
508 final result = new List<GridTrack>(span);
509 for (int i = 0; i < span; i++) {
510 result[i] = tracks[start + i];
511 }
512 return result;
513 }
514
515 int _getSpanCount(ViewLayout item) {
516 GridLayoutParams childLayout = item.layoutParams;
517 return (_dimension == Dimension.WIDTH ?
518 childLayout.columnSpan : childLayout.rowSpan);
519 }
520 }
OLDNEW
« no previous file with comments | « client/base/base.dart ('k') | client/layout/GridLayoutParams.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698