block.dart 8.08 KB
Newer Older
1 2 3 4 5 6
// 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.

import 'dart:math' as math;

7 8
import 'box.dart';
import 'object.dart';
9

Florian Loitsch's avatar
Florian Loitsch committed
10
/// Parent data for use with [RenderBlockBase].
Hixie's avatar
Hixie committed
11
class BlockParentData extends ContainerBoxParentDataMixin<RenderBox> { }
12

13
typedef double _ChildSizingFunction(RenderBox child);
14

Florian Loitsch's avatar
Florian Loitsch committed
15
/// Implements the block layout algorithm.
16 17 18 19 20 21 22 23
///
/// In block layout, children are arranged linearly along the main axis (either
/// horizontally or vertically). In the cross axis, children are stretched to
/// match the block's cross-axis extent. In the main axis, children are given
/// unlimited space and the block expands its main axis to contain all its
/// children. Because blocks expand in the main axis, blocks must be given
/// unlimited space in the main axis, typically by being contained in a
/// viewport with a scrolling direction that matches the block's main axis.
24
class RenderBlock extends RenderBox
25
    with ContainerRenderObjectMixin<RenderBox, BlockParentData>,
26
         RenderBoxContainerDefaultsMixin<RenderBox, BlockParentData> {
27 28 29
  /// Creates a block render object.
  ///
  /// By default, the block positions children along the vertical axis.
30
  RenderBlock({
31
    List<RenderBox> children,
32 33
    Axis mainAxis: Axis.vertical
  }) : _mainAxis = mainAxis {
34 35 36
    addAll(children);
  }

37
  @override
38 39 40 41 42
  void setupParentData(RenderBox child) {
    if (child.parentData is! BlockParentData)
      child.parentData = new BlockParentData();
  }

Florian Loitsch's avatar
Florian Loitsch committed
43
  /// The direction to use as the main axis.
44 45
  Axis get mainAxis => _mainAxis;
  Axis _mainAxis;
46
  set mainAxis (Axis value) {
47 48
    if (_mainAxis != value) {
      _mainAxis = value;
49 50 51 52 53
      markNeedsLayout();
    }
  }

  BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
54
    assert(_mainAxis != null);
55 56
    switch (_mainAxis) {
      case Axis.horizontal:
57
        return new BoxConstraints.tightFor(height: constraints.maxHeight);
58
      case Axis.vertical:
59
        return new BoxConstraints.tightFor(width: constraints.maxWidth);
60
    }
61
    return null;
62 63
  }

64 65 66
  double get _mainAxisExtent {
    RenderBox child = lastChild;
    if (child == null)
67
      return 0.0;
68
    BoxParentData parentData = child.parentData;
69
    assert(mainAxis != null);
70 71 72 73 74 75
    switch (mainAxis) {
      case Axis.horizontal:
        return parentData.offset.dx + child.size.width;
      case Axis.vertical:
        return parentData.offset.dy + child.size.height;
    }
76
    return null;
77 78
  }

79
  @override
80
  void performLayout() {
81 82 83
    assert(() {
      switch (mainAxis) {
        case Axis.horizontal:
84
          if (!constraints.hasBoundedWidth)
85 86 87
            return true;
          break;
        case Axis.vertical:
88
          if (!constraints.hasBoundedHeight)
89 90 91 92 93 94 95 96 97 98 99
            return true;
          break;
      }
      throw new FlutterError(
        'RenderBlock must have unlimited space along its main axis.\n'
        'RenderBlock does not clip or resize its children, so it must be '
        'placed in a parent that does not constrain the block\'s main '
        'axis. You probably want to put the RenderBlock inside a '
        'RenderViewport with a matching main axis.'
      );
    });
100 101 102
    assert(() {
      switch (mainAxis) {
        case Axis.horizontal:
103
          if (constraints.hasBoundedHeight)
104 105 106
            return true;
          break;
        case Axis.vertical:
107
          if (constraints.hasBoundedWidth)
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
            return true;
          break;
      }
      // TODO(ianh): Detect if we're actually nested blocks and say something
      // more specific to the exact situation in that case, and don't mention
      // nesting blocks in the negative case.
      throw new FlutterError(
        'RenderBlock must have a bounded constraint for its cross axis.\n'
        'RenderBlock forces its children to expand to fit the block\'s container, '
        'so it must be placed in a parent that does constrain the block\'s cross '
        'axis to a finite dimension. If you are attempting to nest a block with '
        'one direction inside a block of another direction, you will want to '
        'wrap the inner one inside a box that fixes the dimension in that direction, '
        'for example, a RenderIntrinsicWidth or RenderIntrinsicHeight object. '
        'This is relatively expensive, however.' // (that's why we don't do it automatically)
      );
    });
125 126
    BoxConstraints innerConstraints = _getInnerConstraints(constraints);
    double position = 0.0;
127 128 129
    RenderBox child = firstChild;
    while (child != null) {
      child.layout(innerConstraints, parentUsesSize: true);
Hixie's avatar
Hixie committed
130
      final BlockParentData childParentData = child.parentData;
131 132 133 134 135 136 137 138 139 140
      switch (mainAxis) {
        case Axis.horizontal:
          childParentData.offset = new Offset(position, 0.0);
          position += child.size.width;
          break;
        case Axis.vertical:
          childParentData.offset = new Offset(0.0, position);
          position += child.size.height;
          break;
      }
Hixie's avatar
Hixie committed
141 142
      assert(child.parentData == childParentData);
      child = childParentData.nextSibling;
143
    }
144 145 146 147 148 149 150 151 152
    switch (mainAxis) {
      case Axis.horizontal:
        size = constraints.constrain(new Size(_mainAxisExtent, constraints.maxHeight));
        break;
      case Axis.vertical:
        size = constraints.constrain(new Size(constraints.maxWidth, _mainAxisExtent));
        break;
    }

153
    assert(!size.isInfinite);
154 155
  }

156
  @override
157 158
  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
159
    description.add('mainAxis: $mainAxis');
160
  }
161

162
  double _getIntrinsicCrossAxis(_ChildSizingFunction childSize) {
163
    double extent = 0.0;
164 165
    RenderBox child = firstChild;
    while (child != null) {
166
      extent = math.max(extent, childSize(child));
Hixie's avatar
Hixie committed
167 168
      final BlockParentData childParentData = child.parentData;
      child = childParentData.nextSibling;
169
    }
170
    return extent;
171 172
  }

173
  double _getIntrinsicMainAxis(_ChildSizingFunction childSize) {
174
    double extent = 0.0;
175 176
    RenderBox child = firstChild;
    while (child != null) {
177
      extent += childSize(child);
Hixie's avatar
Hixie committed
178 179
      final BlockParentData childParentData = child.parentData;
      child = childParentData.nextSibling;
180
    }
181
    return extent;
182 183
  }

184
  @override
185
  double computeMinIntrinsicWidth(double height) {
186
    assert(mainAxis != null);
187 188
    switch (mainAxis) {
      case Axis.horizontal:
189
        return _getIntrinsicMainAxis((RenderBox child) => child.getMinIntrinsicWidth(height));
190
      case Axis.vertical:
191
        return _getIntrinsicCrossAxis((RenderBox child) => child.getMinIntrinsicWidth(height));
192
    }
193
    return null;
194 195
  }

196
  @override
197
  double computeMaxIntrinsicWidth(double height) {
198
    assert(mainAxis != null);
199 200
    switch (mainAxis) {
      case Axis.horizontal:
201
        return _getIntrinsicMainAxis((RenderBox child) => child.getMaxIntrinsicWidth(height));
202
      case Axis.vertical:
203
        return _getIntrinsicCrossAxis((RenderBox child) => child.getMaxIntrinsicWidth(height));
204
    }
205
    return null;
206 207
  }

208
  @override
209
  double computeMinIntrinsicHeight(double width) {
210
    assert(mainAxis != null);
211 212
    switch (mainAxis) {
      case Axis.horizontal:
213
        return _getIntrinsicMainAxis((RenderBox child) => child.getMinIntrinsicHeight(width));
214
      case Axis.vertical:
215
        return _getIntrinsicCrossAxis((RenderBox child) => child.getMinIntrinsicHeight(width));
216
    }
217
    return null;
218 219
  }

220
  @override
221
  double computeMaxIntrinsicHeight(double width) {
222
    assert(mainAxis != null);
223 224
    switch (mainAxis) {
      case Axis.horizontal:
225
        return _getIntrinsicMainAxis((RenderBox child) => child.getMaxIntrinsicHeight(width));
226
      case Axis.vertical:
227
        return _getIntrinsicCrossAxis((RenderBox child) => child.getMaxIntrinsicHeight(width));
228
    }
229
    return null;
230 231
  }

232
  @override
233 234 235 236
  double computeDistanceToActualBaseline(TextBaseline baseline) {
    return defaultComputeDistanceToFirstActualBaseline(baseline);
  }

237
  @override
238 239
  void paint(PaintingContext context, Offset offset) {
    defaultPaint(context, offset);
240 241
  }

242
  @override
Adam Barth's avatar
Adam Barth committed
243 244
  bool hitTestChildren(HitTestResult result, { Point position }) {
    return defaultHitTestChildren(result, position: position);
245 246 247
  }

}