grid.dart 4.97 KB
Newer Older
1 2 3 4
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'box.dart';
import 'object.dart';
7

8
class _GridMetrics {
9 10
  // Grid is width-in, height-out.  We fill the max width and adjust height
  // accordingly.
11
  factory _GridMetrics({ double width, int childCount, double maxChildExtent }) {
12 13
    assert(width != null);
    assert(childCount != null);
14 15 16 17 18
    assert(maxChildExtent != null);
    double childExtent = maxChildExtent;
    int childrenPerRow = (width / childExtent).floor();
    // If the child extent divides evenly into the width use that, otherwise + 1
    if (width / childExtent != childrenPerRow.toDouble()) childrenPerRow += 1;
19 20 21 22 23 24 25 26 27 28 29 30 31
    double totalPadding = 0.0;
    if (childrenPerRow * childExtent > width) {
      // TODO(eseidel): We should snap to pixel bounderies.
      childExtent = width / childrenPerRow;
    } else {
      totalPadding = width - (childrenPerRow * childExtent);
    }
    double childPadding = totalPadding / (childrenPerRow + 1.0);
    int rowCount = (childCount / childrenPerRow).ceil();

    double height = childPadding * (rowCount + 1) + (childExtent * rowCount);
    Size childSize = new Size(childExtent, childExtent);
    Size size = new Size(width, height);
32
    return new _GridMetrics._(size, childSize, childrenPerRow, childPadding, rowCount);
33 34
  }

35
  const _GridMetrics._(this.size, this.childSize, this.childrenPerRow, this.childPadding, this.rowCount);
36 37 38 39 40 41 42 43

  final Size size;
  final Size childSize;
  final int childrenPerRow; // aka columnCount
  final double childPadding;
  final int rowCount;
}

44
/// Parent data for use with [RenderGrid]
Hixie's avatar
Hixie committed
45
class GridParentData extends ContainerBoxParentDataMixin<RenderBox> {}
46 47 48 49 50 51 52

/// Implements the grid layout algorithm
///
/// In grid layout, children are arranged into rows and collumns in on a two
/// dimensional grid. The grid determines how many children will be placed in
/// each row by making the children as wide as possible while still respecting
/// the given [maxChildExtent].
53 54
class RenderGrid extends RenderBox with ContainerRenderObjectMixin<RenderBox, GridParentData>,
                                        RenderBoxContainerDefaultsMixin<RenderBox, GridParentData> {
55
  RenderGrid({ List<RenderBox> children, double maxChildExtent }) {
56 57 58 59 60 61 62
    addAll(children);
    _maxChildExtent = maxChildExtent;
  }

  double _maxChildExtent;
  bool _hasVisualOverflow = false;

63 64 65 66 67 68 69 70
  double get maxChildExtent => _maxChildExtent;
  void set maxChildExtent (double value) {
    if (_maxChildExtent != value) {
      _maxChildExtent = value;
      markNeedsLayout();
    }
  }

71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
  void setupParentData(RenderBox child) {
    if (child.parentData is! GridParentData)
      child.parentData = new GridParentData();
  }

  double getMinIntrinsicWidth(BoxConstraints constraints) {
    // We can render at any width.
    return constraints.constrainWidth(0.0);
  }

  double getMaxIntrinsicWidth(BoxConstraints constraints) {
    double maxWidth = childCount * _maxChildExtent;
    return constraints.constrainWidth(maxWidth);
  }

  double getMinIntrinsicHeight(BoxConstraints constraints) {
    double desiredHeight = _computeMetrics().size.height;
    return constraints.constrainHeight(desiredHeight);
  }

  double getMaxIntrinsicHeight(BoxConstraints constraints) {
    return getMinIntrinsicHeight(constraints);
  }

  double computeDistanceToActualBaseline(TextBaseline baseline) {
    return defaultComputeDistanceToHighestActualBaseline(baseline);
  }

99 100
  _GridMetrics _computeMetrics() {
    return new _GridMetrics(
101 102
      width: constraints.maxWidth,
      childCount: childCount,
103
      maxChildExtent: _maxChildExtent
104 105 106 107 108 109
    );
  }

  void performLayout() {
    // We could shrink-wrap our contents when infinite, but for now we don't.
    assert(constraints.maxWidth < double.INFINITY);
110
    _GridMetrics metrics = _computeMetrics();
111 112 113 114 115 116 117 118 119 120 121 122
    size = constraints.constrain(metrics.size);
    if (constraints.maxHeight < size.height)
      _hasVisualOverflow = true;

    int row = 0;
    int column = 0;
    RenderBox child = firstChild;
    while (child != null) {
      child.layout(new BoxConstraints.tight(metrics.childSize));

      double x = (column + 1) * metrics.childPadding + (column * metrics.childSize.width);
      double y = (row + 1) * metrics.childPadding + (row * metrics.childSize.height);
Hixie's avatar
Hixie committed
123 124
      final GridParentData childParentData = child.parentData;
      childParentData.position = new Point(x, y);
125 126 127 128 129 130

      column += 1;
      if (column >= metrics.childrenPerRow) {
        row += 1;
        column = 0;
      }
Hixie's avatar
Hixie committed
131 132 133

      assert(child.parentData == childParentData);
      child = childParentData.nextSibling;
134 135 136
    }
  }

Adam Barth's avatar
Adam Barth committed
137 138
  bool hitTestChildren(HitTestResult result, { Point position }) {
    return defaultHitTestChildren(result, position: position);
139 140 141
  }

  void paint(PaintingContext context, Offset offset) {
142 143 144
    if (_hasVisualOverflow)
      context.pushClipRect(needsCompositing, offset, Point.origin & size, defaultPaint);
    else
145 146 147
      defaultPaint(context, offset);
  }
}