list_body.dart 10.6 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 _ChildSizingFunction = double Function(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
    AxisDirection axisDirection = AxisDirection.down,
35 36
  }) : assert(axisDirection != null),
       _axisDirection = axisDirection {
37 38 39
    addAll(children);
  }

40
  @override
41
  void setupParentData(RenderBox child) {
42
    if (child.parentData is! ListBodyParentData)
43
      child.parentData = 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
            return true;
          break;
      }
77 78 79 80 81 82 83 84 85 86 87 88
      throw FlutterError.fromParts(<DiagnosticsNode>[
        ErrorSummary('RenderListBody must have unlimited space along its main axis.'),
        ErrorDescription(
          'RenderListBody does not clip or resize its children, so it must be '
          'placed in a parent that does not constrain the main '
          'axis.'
        ),
        ErrorHint(
          'You probably want to put the RenderListBody inside a '
          'RenderViewport with a matching main axis.'
        )
      ]);
89
    }());
90 91 92
    assert(() {
      switch (mainAxis) {
        case Axis.horizontal:
93
          if (constraints.hasBoundedHeight)
94 95 96
            return true;
          break;
        case Axis.vertical:
97
          if (constraints.hasBoundedWidth)
98 99 100 101 102 103
            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.
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
      throw FlutterError.fromParts(<DiagnosticsNode>[
        ErrorSummary('RenderListBody must have a bounded constraint for its cross axis.'),
        ErrorDescription(
          '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.'
        ),
        // TODO(jacobr): this hint is a great candidate to promote to being an
        // automated quick fix in the future.
        ErrorHint(
          'If you are attempting to nest a RenderListBody with '
          'one direction inside one 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)
        )
      ]);
121
    }());
122
    double mainAxisExtent = 0.0;
123
    RenderBox child = firstChild;
124
    switch (axisDirection) {
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 177 178 179 180 181 182 183 184 185 186 187 188
      case AxisDirection.right:
        final BoxConstraints innerConstraints = BoxConstraints.tightFor(height: constraints.maxHeight);
        while (child != null) {
          child.layout(innerConstraints, parentUsesSize: true);
          final ListBodyParentData childParentData = child.parentData;
          childParentData.offset = Offset(mainAxisExtent, 0.0);
          mainAxisExtent += child.size.width;
          assert(child.parentData == childParentData);
          child = childParentData.nextSibling;
        }
        size = constraints.constrain(Size(mainAxisExtent, constraints.maxHeight));
        break;
      case AxisDirection.left:
        final BoxConstraints innerConstraints = 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 = Offset(mainAxisExtent - position, 0.0);
          assert(child.parentData == childParentData);
          child = childParentData.nextSibling;
        }
        size = constraints.constrain(Size(mainAxisExtent, constraints.maxHeight));
        break;
      case AxisDirection.down:
        final BoxConstraints innerConstraints = BoxConstraints.tightFor(width: constraints.maxWidth);
        while (child != null) {
          child.layout(innerConstraints, parentUsesSize: true);
          final ListBodyParentData childParentData = child.parentData;
          childParentData.offset = Offset(0.0, mainAxisExtent);
          mainAxisExtent += child.size.height;
          assert(child.parentData == childParentData);
          child = childParentData.nextSibling;
        }
        size = constraints.constrain(Size(constraints.maxWidth, mainAxisExtent));
        break;
      case AxisDirection.up:
        final BoxConstraints innerConstraints = 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 = Offset(0.0, mainAxisExtent - position);
          assert(child.parentData == childParentData);
          child = childParentData.nextSibling;
        }
        size = constraints.constrain(Size(constraints.maxWidth, mainAxisExtent));
        break;
189
    }
190
    assert(size.isFinite);
191 192
  }

193
  @override
194 195
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
196
    properties.add(EnumProperty<AxisDirection>('axisDirection', axisDirection));
197
  }
198

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

210
  double _getIntrinsicMainAxis(_ChildSizingFunction childSize) {
211
    double extent = 0.0;
212 213
    RenderBox child = firstChild;
    while (child != null) {
214
      extent += childSize(child);
215
      final ListBodyParentData childParentData = child.parentData;
Hixie's avatar
Hixie committed
216
      child = childParentData.nextSibling;
217
    }
218
    return extent;
219 220
  }

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

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

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

257
  @override
258
  double computeMaxIntrinsicHeight(double width) {
259
    assert(mainAxis != null);
260 261
    switch (mainAxis) {
      case Axis.horizontal:
262
        return _getIntrinsicMainAxis((RenderBox child) => child.getMaxIntrinsicHeight(width));
263
      case Axis.vertical:
264
        return _getIntrinsicCrossAxis((RenderBox child) => child.getMaxIntrinsicHeight(width));
265
    }
266
    return null;
267 268
  }

269
  @override
270 271 272 273
  double computeDistanceToActualBaseline(TextBaseline baseline) {
    return defaultComputeDistanceToFirstActualBaseline(baseline);
  }

274
  @override
275 276
  void paint(PaintingContext context, Offset offset) {
    defaultPaint(context, offset);
277 278
  }

279
  @override
280
  bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
Adam Barth's avatar
Adam Barth committed
281
    return defaultHitTestChildren(result, position: position);
282 283 284
  }

}