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 _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
      throw 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
            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.
99
      throw 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
    switch (axisDirection) {
    case AxisDirection.right:
114
      final BoxConstraints innerConstraints = BoxConstraints.tightFor(height: constraints.maxHeight);
115 116 117
      while (child != null) {
        child.layout(innerConstraints, parentUsesSize: true);
        final ListBodyParentData childParentData = child.parentData;
118
        childParentData.offset = Offset(mainAxisExtent, 0.0);
119 120 121
        mainAxisExtent += child.size.width;
        assert(child.parentData == childParentData);
        child = childParentData.nextSibling;
122
      }
123
      size = constraints.constrain(Size(mainAxisExtent, constraints.maxHeight));
124 125
      break;
    case AxisDirection.left:
126
      final BoxConstraints innerConstraints = BoxConstraints.tightFor(height: constraints.maxHeight);
127 128 129 130 131 132 133 134 135 136 137 138
      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;
139
        childParentData.offset = Offset(mainAxisExtent - position, 0.0);
140 141 142
        assert(child.parentData == childParentData);
        child = childParentData.nextSibling;
      }
143
      size = constraints.constrain(Size(mainAxisExtent, constraints.maxHeight));
144 145
      break;
    case AxisDirection.down:
146
      final BoxConstraints innerConstraints = BoxConstraints.tightFor(width: constraints.maxWidth);
147 148 149
      while (child != null) {
        child.layout(innerConstraints, parentUsesSize: true);
        final ListBodyParentData childParentData = child.parentData;
150
        childParentData.offset = Offset(0.0, mainAxisExtent);
151
        mainAxisExtent += child.size.height;
152 153 154
        assert(child.parentData == childParentData);
        child = childParentData.nextSibling;
      }
155
      size = constraints.constrain(Size(constraints.maxWidth, mainAxisExtent));
156 157
      break;
    case AxisDirection.up:
158
      final BoxConstraints innerConstraints = BoxConstraints.tightFor(width: constraints.maxWidth);
159 160 161 162 163 164 165 166 167 168 169 170
      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;
171
        childParentData.offset = Offset(0.0, mainAxisExtent - position);
172 173 174
        assert(child.parentData == childParentData);
        child = childParentData.nextSibling;
      }
175
      size = constraints.constrain(Size(constraints.maxWidth, mainAxisExtent));
176
      break;
177
    }
178
    assert(size.isFinite);
179 180
  }

181
  @override
182 183
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
184
    properties.add(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
  }

}