list_body.dart 10.1 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
/// Parent data for use with [RenderListBody].
11
class ListBodyParentData extends ContainerBoxParentData<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 35 36
    AxisDirection axisDirection: AxisDirection.down,
  }) : assert(axisDirection != null),
       _axisDirection = axisDirection {
37 38 39
    addAll(children);
  }

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

46 47 48 49
  /// The direction in which the children are laid out.
  ///
  /// For example, if the [axisDirection] is [AxisDirection.down], each child
  /// will be laid out below the next, vertically.
50 51 52 53 54 55 56 57
  AxisDirection get axisDirection => _axisDirection;
  AxisDirection _axisDirection;
  set axisDirection(AxisDirection value) {
    assert(value != null);
    if (_axisDirection == value)
      return;
    _axisDirection = value;
    markNeedsLayout();
58 59
  }

60 61
  /// The axis (horizontal or vertical) corresponding to the current
  /// [axisDirection].
62
  Axis get mainAxis => axisDirectionToAxis(axisDirection);
63

64
  @override
65
  void performLayout() {
66 67 68
    assert(() {
      switch (mainAxis) {
        case Axis.horizontal:
69
          if (!constraints.hasBoundedWidth)
70 71 72
            return true;
          break;
        case Axis.vertical:
73
          if (!constraints.hasBoundedHeight)
74 75 76 77
            return true;
          break;
      }
      throw new FlutterError(
78 79 80 81
        '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 '
82 83
        'RenderViewport with a matching main axis.'
      );
84
    }());
85 86 87
    assert(() {
      switch (mainAxis) {
        case Axis.horizontal:
88
          if (constraints.hasBoundedHeight)
89 90 91
            return true;
          break;
        case Axis.vertical:
92
          if (constraints.hasBoundedWidth)
93 94 95 96 97 98 99
            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(
100 101 102 103 104
        '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 '
105 106 107 108
        '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)
      );
109
    }());
110
    double mainAxisExtent = 0.0;
111
    RenderBox child = firstChild;
112 113 114 115 116 117 118 119 120 121
    switch (axisDirection) {
    case AxisDirection.right:
      final BoxConstraints innerConstraints = new BoxConstraints.tightFor(height: constraints.maxHeight);
      while (child != null) {
        child.layout(innerConstraints, parentUsesSize: true);
        final ListBodyParentData childParentData = child.parentData;
        childParentData.offset = new Offset(mainAxisExtent, 0.0);
        mainAxisExtent += child.size.width;
        assert(child.parentData == childParentData);
        child = childParentData.nextSibling;
122
      }
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
      size = constraints.constrain(new Size(mainAxisExtent, constraints.maxHeight));
      break;
    case AxisDirection.left:
      final BoxConstraints innerConstraints = new BoxConstraints.tightFor(height: constraints.maxHeight);
      while (child != null) {
        child.layout(innerConstraints, parentUsesSize: true);
        final ListBodyParentData childParentData = child.parentData;
        mainAxisExtent += child.size.width;
        assert(child.parentData == childParentData);
        child = childParentData.nextSibling;
      }
      double position = 0.0;
      child = firstChild;
      while (child != null) {
        final ListBodyParentData childParentData = child.parentData;
        position += child.size.width;
        childParentData.offset = new Offset(mainAxisExtent - position, 0.0);
        assert(child.parentData == childParentData);
        child = childParentData.nextSibling;
      }
      size = constraints.constrain(new Size(mainAxisExtent, constraints.maxHeight));
      break;
    case AxisDirection.down:
      final BoxConstraints innerConstraints = new BoxConstraints.tightFor(width: constraints.maxWidth);
      while (child != null) {
        child.layout(innerConstraints, parentUsesSize: true);
        final ListBodyParentData childParentData = child.parentData;
        childParentData.offset = new Offset(0.0, mainAxisExtent);
          mainAxisExtent += child.size.height;
        assert(child.parentData == childParentData);
        child = childParentData.nextSibling;
      }
      size = constraints.constrain(new Size(constraints.maxWidth, mainAxisExtent));
      break;
    case AxisDirection.up:
      final BoxConstraints innerConstraints = new BoxConstraints.tightFor(width: constraints.maxWidth);
      while (child != null) {
        child.layout(innerConstraints, parentUsesSize: true);
        final ListBodyParentData childParentData = child.parentData;
        mainAxisExtent += child.size.height;
        assert(child.parentData == childParentData);
        child = childParentData.nextSibling;
      }
      double position = 0.0;
      child = firstChild;
      while (child != null) {
        final ListBodyParentData childParentData = child.parentData;
        position += child.size.height;
        childParentData.offset = new Offset(0.0, mainAxisExtent - position);
        assert(child.parentData == childParentData);
        child = childParentData.nextSibling;
      }
      size = constraints.constrain(new Size(constraints.maxWidth, mainAxisExtent));
      break;
177
    }
178
    assert(size.isFinite);
179 180
  }

181
  @override
182
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
183
    super.debugFillProperties(description);
184
    description.add(new EnumProperty<AxisDirection>('axisDirection', axisDirection));
185
  }
186

187
  double _getIntrinsicCrossAxis(_ChildSizingFunction childSize) {
188
    double extent = 0.0;
189 190
    RenderBox child = firstChild;
    while (child != null) {
191
      extent = math.max(extent, childSize(child));
192
      final ListBodyParentData childParentData = child.parentData;
Hixie's avatar
Hixie committed
193
      child = childParentData.nextSibling;
194
    }
195
    return extent;
196 197
  }

198
  double _getIntrinsicMainAxis(_ChildSizingFunction childSize) {
199
    double extent = 0.0;
200 201
    RenderBox child = firstChild;
    while (child != null) {
202
      extent += childSize(child);
203
      final ListBodyParentData childParentData = child.parentData;
Hixie's avatar
Hixie committed
204
      child = childParentData.nextSibling;
205
    }
206
    return extent;
207 208
  }

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

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

233
  @override
234
  double computeMinIntrinsicHeight(double width) {
235
    assert(mainAxis != null);
236 237
    switch (mainAxis) {
      case Axis.horizontal:
238
        return _getIntrinsicMainAxis((RenderBox child) => child.getMinIntrinsicHeight(width));
239
      case Axis.vertical:
240
        return _getIntrinsicCrossAxis((RenderBox child) => child.getMinIntrinsicHeight(width));
241
    }
242
    return null;
243 244
  }

245
  @override
246
  double computeMaxIntrinsicHeight(double width) {
247
    assert(mainAxis != null);
248 249
    switch (mainAxis) {
      case Axis.horizontal:
250
        return _getIntrinsicMainAxis((RenderBox child) => child.getMaxIntrinsicHeight(width));
251
      case Axis.vertical:
252
        return _getIntrinsicCrossAxis((RenderBox child) => child.getMaxIntrinsicHeight(width));
253
    }
254
    return null;
255 256
  }

257
  @override
258 259 260 261
  double computeDistanceToActualBaseline(TextBaseline baseline) {
    return defaultComputeDistanceToFirstActualBaseline(baseline);
  }

262
  @override
263 264
  void paint(PaintingContext context, Offset offset) {
    defaultPaint(context, offset);
265 266
  }

267
  @override
268
  bool hitTestChildren(HitTestResult result, { Offset position }) {
Adam Barth's avatar
Adam Barth committed
269
    return defaultHitTestChildren(result, position: position);
270 271 272
  }

}