block.dart 8.28 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

10 11
/// Parent data for use with [RenderListBody].
class ListBodyParentData extends ContainerBoxParentDataMixin<RenderBox> { }
12

13
typedef double _ChildSizingFunction(RenderBox child);
14

15 16
/// Displays its children sequentially along a given axis, forcing them to the
/// dimensions of the parent in the other axis.
17
///
18 19 20 21 22 23 24 25 26 27 28 29
/// This layout algorithm arranges its children linearly along the main axis
/// (either horizontally or vertically). In the cross axis, children are
/// stretched to match the box's cross-axis extent. In the main axis, children
/// are given unlimited space and the box expands its main axis to contain all
/// its children. Because [RenderListBody] boxes expand in the main axis, they
/// must be given unlimited space in the main axis, typically by being contained
/// in a viewport with a scrolling direction that matches the box's main axis.
class RenderListBody extends RenderBox
    with ContainerRenderObjectMixin<RenderBox, ListBodyParentData>,
         RenderBoxContainerDefaultsMixin<RenderBox, ListBodyParentData> {
  /// Creates a render object that arranges its children sequentially along a
  /// given axis.
30
  ///
31 32
  /// By default, children are arranged along the vertical axis.
  RenderListBody({
33
    List<RenderBox> children,
34
    Axis mainAxis: Axis.vertical,
35
  }) : _mainAxis = mainAxis {
36 37 38
    addAll(children);
  }

39
  @override
40
  void setupParentData(RenderBox child) {
41 42
    if (child.parentData is! ListBodyParentData)
      child.parentData = new ListBodyParentData();
43 44
  }

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

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

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

81
  @override
82
  void performLayout() {
83 84 85
    assert(() {
      switch (mainAxis) {
        case Axis.horizontal:
86
          if (!constraints.hasBoundedWidth)
87 88 89
            return true;
          break;
        case Axis.vertical:
90
          if (!constraints.hasBoundedHeight)
91 92 93 94
            return true;
          break;
      }
      throw new FlutterError(
95 96 97 98
        'RenderListBody must have unlimited space along its main axis.\n'
        'RenderListBody does not clip or resize its children, so it must be '
        'placed in a parent that does not constrain the main '
        'axis. You probably want to put the RenderListBody inside a '
99 100 101
        'RenderViewport with a matching main axis.'
      );
    });
102 103 104
    assert(() {
      switch (mainAxis) {
        case Axis.horizontal:
105
          if (constraints.hasBoundedHeight)
106 107 108
            return true;
          break;
        case Axis.vertical:
109
          if (constraints.hasBoundedWidth)
110 111 112 113 114 115 116
            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(
117 118 119 120 121
        'RenderListBody must have a bounded constraint for its cross axis.\n'
        'RenderListBody forces its children to expand to fit the RenderListBody\'s container, '
        'so it must be placed in a parent that constrains the cross '
        'axis to a finite dimension. If you are attempting to nest a RenderListBody with '
        'one direction inside one of another direction, you will want to '
122 123 124 125 126
        '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)
      );
    });
127
    final BoxConstraints innerConstraints = _getInnerConstraints(constraints);
128
    double position = 0.0;
129 130 131
    RenderBox child = firstChild;
    while (child != null) {
      child.layout(innerConstraints, parentUsesSize: true);
132
      final ListBodyParentData childParentData = child.parentData;
133 134 135 136 137 138 139 140 141 142
      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
143 144
      assert(child.parentData == childParentData);
      child = childParentData.nextSibling;
145
    }
146 147 148 149 150 151 152 153 154
    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;
    }

155
    assert(size.isFinite);
156 157
  }

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

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

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

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

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

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

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

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

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

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

}