shifted_box.dart 44.1 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
// @dart = 2.8

7
import 'dart:math' as math;
8

9 10
import 'package:flutter/foundation.dart';

11
import 'box.dart';
12
import 'debug.dart';
13
import 'debug_overflow_indicator.dart';
14
import 'object.dart';
15
import 'stack.dart' show RelativeRect;
16

17 18
/// Abstract class for one-child-layout render boxes that provide control over
/// the child's position.
19
abstract class RenderShiftedBox extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
20
  /// Initializes the [child] property for subclasses.
21 22 23 24
  RenderShiftedBox(RenderBox child) {
    this.child = child;
  }

25
  @override
26
  double computeMinIntrinsicWidth(double height) {
27
    if (child != null)
28 29
      return child.getMinIntrinsicWidth(height);
    return 0.0;
30 31
  }

32
  @override
33
  double computeMaxIntrinsicWidth(double height) {
34
    if (child != null)
35 36
      return child.getMaxIntrinsicWidth(height);
    return 0.0;
37 38
  }

39
  @override
40
  double computeMinIntrinsicHeight(double width) {
41
    if (child != null)
42 43
      return child.getMinIntrinsicHeight(width);
    return 0.0;
44 45
  }

46
  @override
47
  double computeMaxIntrinsicHeight(double width) {
48
    if (child != null)
49 50
      return child.getMaxIntrinsicHeight(width);
    return 0.0;
51 52
  }

53
  @override
54 55 56
  double computeDistanceToActualBaseline(TextBaseline baseline) {
    double result;
    if (child != null) {
57
      assert(!debugNeedsLayout);
58
      result = child.getDistanceToActualBaseline(baseline);
59
      final BoxParentData childParentData = child.parentData as BoxParentData;
60
      if (result != null)
61
        result += childParentData.offset.dy;
62 63 64 65 66 67
    } else {
      result = super.computeDistanceToActualBaseline(baseline);
    }
    return result;
  }

68
  @override
69
  void paint(PaintingContext context, Offset offset) {
Hixie's avatar
Hixie committed
70
    if (child != null) {
71
      final BoxParentData childParentData = child.parentData as BoxParentData;
Adam Barth's avatar
Adam Barth committed
72
      context.paintChild(child, childParentData.offset + offset);
Hixie's avatar
Hixie committed
73
    }
74 75
  }

76
  @override
77
  bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
78
    if (child != null) {
79
      final BoxParentData childParentData = child.parentData as BoxParentData;
80 81 82 83 84 85 86 87
      return result.addWithPaintOffset(
        offset: childParentData.offset,
        position: position,
        hitTest: (BoxHitTestResult result, Offset transformed) {
          assert(transformed == position - childParentData.offset);
          return child.hitTest(result, position: transformed);
        },
      );
88
    }
Adam Barth's avatar
Adam Barth committed
89
    return false;
90 91 92 93
  }

}

94 95 96 97 98 99
/// Insets its child by the given padding.
///
/// When passing layout constraints to its child, padding shrinks the
/// constraints by the given padding, causing the child to layout at a smaller
/// size. Padding then sizes itself to its child's size, inflated by the
/// padding, effectively creating empty space around the child.
100
class RenderPadding extends RenderShiftedBox {
101 102 103
  /// Creates a render object that insets its child.
  ///
  /// The [padding] argument must not be null and must have non-negative insets.
104
  RenderPadding({
105 106 107
    @required EdgeInsetsGeometry padding,
    TextDirection textDirection,
    RenderBox child,
108 109
  }) : assert(padding != null),
       assert(padding.isNonNegative),
110
       _textDirection = textDirection,
111
       _padding = padding,
Ian Hickson's avatar
Ian Hickson committed
112
       super(child);
113 114 115

  EdgeInsets _resolvedPadding;

Ian Hickson's avatar
Ian Hickson committed
116 117 118 119 120 121 122 123 124 125
  void _resolve() {
    if (_resolvedPadding != null)
      return;
    _resolvedPadding = padding.resolve(textDirection);
    assert(_resolvedPadding.isNonNegative);
  }

  void _markNeedResolution() {
    _resolvedPadding = null;
    markNeedsLayout();
126
  }
127

128
  /// The amount to pad the child in each dimension.
129 130
  ///
  /// If this is set to an [EdgeInsetsDirectional] object, then [textDirection]
131
  /// must not be null.
132 133 134
  EdgeInsetsGeometry get padding => _padding;
  EdgeInsetsGeometry _padding;
  set padding(EdgeInsetsGeometry value) {
135
    assert(value != null);
136
    assert(value.isNonNegative);
137 138 139
    if (_padding == value)
      return;
    _padding = value;
Ian Hickson's avatar
Ian Hickson committed
140
    _markNeedResolution();
141 142 143
  }

  /// The text direction with which to resolve [padding].
Ian Hickson's avatar
Ian Hickson committed
144 145 146
  ///
  /// This may be changed to null, but only after the [padding] has been changed
  /// to a value that does not depend on the direction.
147 148 149 150 151 152
  TextDirection get textDirection => _textDirection;
  TextDirection _textDirection;
  set textDirection(TextDirection value) {
    if (_textDirection == value)
      return;
    _textDirection = value;
Ian Hickson's avatar
Ian Hickson committed
153
    _markNeedResolution();
154 155
  }

156
  @override
157
  double computeMinIntrinsicWidth(double height) {
Ian Hickson's avatar
Ian Hickson committed
158
    _resolve();
159 160
    final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
    final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
161
    if (child != null) // next line relies on double.infinity absorption
162
      return child.getMinIntrinsicWidth(math.max(0.0, height - totalVerticalPadding)) + totalHorizontalPadding;
163
    return totalHorizontalPadding;
164 165
  }

166
  @override
167
  double computeMaxIntrinsicWidth(double height) {
Ian Hickson's avatar
Ian Hickson committed
168
    _resolve();
169 170
    final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
    final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
171
    if (child != null) // next line relies on double.infinity absorption
172
      return child.getMaxIntrinsicWidth(math.max(0.0, height - totalVerticalPadding)) + totalHorizontalPadding;
173
    return totalHorizontalPadding;
174 175
  }

176
  @override
177
  double computeMinIntrinsicHeight(double width) {
Ian Hickson's avatar
Ian Hickson committed
178
    _resolve();
179 180
    final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
    final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
181
    if (child != null) // next line relies on double.infinity absorption
182
      return child.getMinIntrinsicHeight(math.max(0.0, width - totalHorizontalPadding)) + totalVerticalPadding;
183
    return totalVerticalPadding;
184 185
  }

186
  @override
187
  double computeMaxIntrinsicHeight(double width) {
Ian Hickson's avatar
Ian Hickson committed
188
    _resolve();
189 190
    final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
    final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
191
    if (child != null) // next line relies on double.infinity absorption
192
      return child.getMaxIntrinsicHeight(math.max(0.0, width - totalHorizontalPadding)) + totalVerticalPadding;
193
    return totalVerticalPadding;
194 195
  }

196
  @override
197
  void performLayout() {
198
    final BoxConstraints constraints = this.constraints;
Ian Hickson's avatar
Ian Hickson committed
199
    _resolve();
200
    assert(_resolvedPadding != null);
201
    if (child == null) {
202
      size = constraints.constrain(Size(
203
        _resolvedPadding.left + _resolvedPadding.right,
204
        _resolvedPadding.top + _resolvedPadding.bottom,
205 206 207
      ));
      return;
    }
208
    final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding);
209
    child.layout(innerConstraints, parentUsesSize: true);
210
    final BoxParentData childParentData = child.parentData as BoxParentData;
211 212
    childParentData.offset = Offset(_resolvedPadding.left, _resolvedPadding.top);
    size = constraints.constrain(Size(
213
      _resolvedPadding.left + child.size.width + _resolvedPadding.right,
214
      _resolvedPadding.top + child.size.height + _resolvedPadding.bottom,
215 216 217
    ));
  }

218
  @override
219 220 221
  void debugPaintSize(PaintingContext context, Offset offset) {
    super.debugPaintSize(context, offset);
    assert(() {
222
      final Rect outerRect = offset & size;
223
      debugPaintPadding(context.canvas, outerRect, child != null ? _resolvedPadding.deflateRect(outerRect) : null);
224
      return true;
225
    }());
226 227
  }

228
  @override
229 230
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
231 232
    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding));
    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
233
  }
234 235
}

236
/// Abstract class for one-child-layout render boxes that use a
237
/// [AlignmentGeometry] to align their children.
238
abstract class RenderAligningShiftedBox extends RenderShiftedBox {
239 240 241
  /// Initializes member variables for subclasses.
  ///
  /// The [alignment] argument must not be null.
242 243 244
  ///
  /// The [textDirection] must be non-null if the [alignment] is
  /// direction-sensitive.
245
  RenderAligningShiftedBox({
246
    AlignmentGeometry alignment = Alignment.center,
Ian Hickson's avatar
Ian Hickson committed
247
    @required TextDirection textDirection,
248 249
    RenderBox child,
  }) : assert(alignment != null),
250
       _alignment = alignment,
251
       _textDirection = textDirection,
Ian Hickson's avatar
Ian Hickson committed
252
       super(child);
253

254
  /// A constructor to be used only when the extending class also has a mixin.
255
  // TODO(gspencer): Remove this constructor once https://github.com/dart-lang/sdk/issues/31543 is fixed.
256
  @protected
257
  RenderAligningShiftedBox.mixin(AlignmentGeometry alignment, TextDirection textDirection, RenderBox child)
258 259
    : this(alignment: alignment, textDirection: textDirection, child: child);

260
  Alignment _resolvedAlignment;
261

Ian Hickson's avatar
Ian Hickson committed
262 263 264 265 266 267 268 269 270
  void _resolve() {
    if (_resolvedAlignment != null)
      return;
    _resolvedAlignment = alignment.resolve(textDirection);
  }

  void _markNeedResolution() {
    _resolvedAlignment = null;
    markNeedsLayout();
271
  }
272

273 274 275
  /// How to align the child.
  ///
  /// The x and y values of the alignment control the horizontal and vertical
276
  /// alignment, respectively. An x value of -1.0 means that the left edge of
277 278 279
  /// the child is aligned with the left edge of the parent whereas an x value
  /// of 1.0 means that the right edge of the child is aligned with the right
  /// edge of the parent. Other values interpolate (and extrapolate) linearly.
280
  /// For example, a value of 0.0 means that the center of the child is aligned
281
  /// with the center of the parent.
282
  ///
283
  /// If this is set to an [AlignmentDirectional] object, then
284
  /// [textDirection] must not be null.
285 286
  AlignmentGeometry get alignment => _alignment;
  AlignmentGeometry _alignment;
287 288
  /// Sets the alignment to a new value, and triggers a layout update.
  ///
Ian Hickson's avatar
Ian Hickson committed
289
  /// The new alignment must not be null.
290
  set alignment(AlignmentGeometry value) {
291
    assert(value != null);
292
    if (_alignment == value)
293
      return;
294
    _alignment = value;
Ian Hickson's avatar
Ian Hickson committed
295
    _markNeedResolution();
296 297 298
  }

  /// The text direction with which to resolve [alignment].
Ian Hickson's avatar
Ian Hickson committed
299 300 301
  ///
  /// This may be changed to null, but only after [alignment] has been changed
  /// to a value that does not depend on the direction.
302 303 304 305 306 307
  TextDirection get textDirection => _textDirection;
  TextDirection _textDirection;
  set textDirection(TextDirection value) {
    if (_textDirection == value)
      return;
    _textDirection = value;
Ian Hickson's avatar
Ian Hickson committed
308
    _markNeedResolution();
309 310
  }

311 312 313 314 315 316 317 318
  /// Apply the current [alignment] to the [child].
  ///
  /// Subclasses should call this method if they have a child, to have
  /// this class perform the actual alignment. If there is no child,
  /// do not call this method.
  ///
  /// This method must be called after the child has been laid out and
  /// this object's own size has been set.
319
  @protected
320
  void alignChild() {
Ian Hickson's avatar
Ian Hickson committed
321
    _resolve();
322
    assert(child != null);
323
    assert(!child.debugNeedsLayout);
324 325
    assert(child.hasSize);
    assert(hasSize);
Ian Hickson's avatar
Ian Hickson committed
326
    assert(_resolvedAlignment != null);
327 328
    final BoxParentData childParentData = child.parentData as BoxParentData;
    childParentData.offset = _resolvedAlignment.alongOffset(size - child.size as Offset);
329 330 331
  }

  @override
332 333
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
334 335
    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment));
    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
336 337 338
  }
}

339
/// Positions its child using an [AlignmentGeometry].
340 341 342
///
/// For example, to align a box at the bottom right, you would pass this box a
/// tight constraint that is bigger than the child's natural size,
343
/// with an alignment of [Alignment.bottomRight].
344 345 346 347 348 349
///
/// By default, sizes to be as big as possible in both axes. If either axis is
/// unconstrained, then in that direction it will be sized to fit the child's
/// dimensions. Using widthFactor and heightFactor you can force this latter
/// behavior in all cases.
class RenderPositionedBox extends RenderAligningShiftedBox {
350
  /// Creates a render object that positions its child.
351 352 353 354
  RenderPositionedBox({
    RenderBox child,
    double widthFactor,
    double heightFactor,
355
    AlignmentGeometry alignment = Alignment.center,
356
    TextDirection textDirection,
357 358 359
  }) : assert(widthFactor == null || widthFactor >= 0.0),
       assert(heightFactor == null || heightFactor >= 0.0),
       _widthFactor = widthFactor,
360
       _heightFactor = heightFactor,
361
       super(child: child, alignment: alignment, textDirection: textDirection);
362

363
  /// If non-null, sets its width to the child's width multiplied by this factor.
364 365
  ///
  /// Can be both greater and less than 1.0 but must be positive.
366
  double get widthFactor => _widthFactor;
367
  double _widthFactor;
368
  set widthFactor(double value) {
369
    assert(value == null || value >= 0.0);
370
    if (_widthFactor == value)
371
      return;
372
    _widthFactor = value;
373 374 375
    markNeedsLayout();
  }

376
  /// If non-null, sets its height to the child's height multiplied by this factor.
377 378
  ///
  /// Can be both greater and less than 1.0 but must be positive.
379
  double get heightFactor => _heightFactor;
380
  double _heightFactor;
381
  set heightFactor(double value) {
382
    assert(value == null || value >= 0.0);
383 384 385 386 387
    if (_heightFactor == value)
      return;
    _heightFactor = value;
    markNeedsLayout();
  }
388

389
  @override
390
  void performLayout() {
391
    final BoxConstraints constraints = this.constraints;
392 393
    final bool shrinkWrapWidth = _widthFactor != null || constraints.maxWidth == double.infinity;
    final bool shrinkWrapHeight = _heightFactor != null || constraints.maxHeight == double.infinity;
394

395 396
    if (child != null) {
      child.layout(constraints.loosen(), parentUsesSize: true);
397
      size = constraints.constrain(Size(shrinkWrapWidth ? child.size.width * (_widthFactor ?? 1.0) : double.infinity,
398
                                        shrinkWrapHeight ? child.size.height * (_heightFactor ?? 1.0) : double.infinity));
399
      alignChild();
400
    } else {
401
      size = constraints.constrain(Size(shrinkWrapWidth ? 0.0 : double.infinity,
402
                                        shrinkWrapHeight ? 0.0 : double.infinity));
403 404 405
    }
  }

406
  @override
407 408 409 410 411 412
  void debugPaintSize(PaintingContext context, Offset offset) {
    super.debugPaintSize(context, offset);
    assert(() {
      Paint paint;
      if (child != null && !child.size.isEmpty) {
        Path path;
413
        paint = Paint()
414
          ..style = PaintingStyle.stroke
415
          ..strokeWidth = 1.0
416
          ..color = const Color(0xFFFFFF00);
417
        path = Path();
418
        final BoxParentData childParentData = child.parentData as BoxParentData;
419 420
        if (childParentData.offset.dy > 0.0) {
          // vertical alignment arrows
421
          final double headSize = math.min(childParentData.offset.dy * 0.2, 10.0);
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438
          path
            ..moveTo(offset.dx + size.width / 2.0, offset.dy)
            ..relativeLineTo(0.0, childParentData.offset.dy - headSize)
            ..relativeLineTo(headSize, 0.0)
            ..relativeLineTo(-headSize, headSize)
            ..relativeLineTo(-headSize, -headSize)
            ..relativeLineTo(headSize, 0.0)
            ..moveTo(offset.dx + size.width / 2.0, offset.dy + size.height)
            ..relativeLineTo(0.0, -childParentData.offset.dy + headSize)
            ..relativeLineTo(headSize, 0.0)
            ..relativeLineTo(-headSize, -headSize)
            ..relativeLineTo(-headSize, headSize)
            ..relativeLineTo(headSize, 0.0);
          context.canvas.drawPath(path, paint);
        }
        if (childParentData.offset.dx > 0.0) {
          // horizontal alignment arrows
439
          final double headSize = math.min(childParentData.offset.dx * 0.2, 10.0);
440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455
          path
            ..moveTo(offset.dx, offset.dy + size.height / 2.0)
            ..relativeLineTo(childParentData.offset.dx - headSize, 0.0)
            ..relativeLineTo(0.0, headSize)
            ..relativeLineTo(headSize, -headSize)
            ..relativeLineTo(-headSize, -headSize)
            ..relativeLineTo(0.0, headSize)
            ..moveTo(offset.dx + size.width, offset.dy + size.height / 2.0)
            ..relativeLineTo(-childParentData.offset.dx + headSize, 0.0)
            ..relativeLineTo(0.0, headSize)
            ..relativeLineTo(-headSize, -headSize)
            ..relativeLineTo(headSize, -headSize)
            ..relativeLineTo(0.0, headSize);
          context.canvas.drawPath(path, paint);
        }
      } else {
456
        paint = Paint()
457
          ..color = const Color(0x90909090);
458 459 460
        context.canvas.drawRect(offset & size, paint);
      }
      return true;
461
    }());
462 463
  }

464
  @override
465 466
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
467 468
    properties.add(DoubleProperty('widthFactor', _widthFactor, ifNull: 'expand'));
    properties.add(DoubleProperty('heightFactor', _heightFactor, ifNull: 'expand'));
469
  }
470 471
}

472 473 474 475 476 477 478 479 480 481 482 483 484
/// A render object that imposes different constraints on its child than it gets
/// from its parent, possibly allowing the child to overflow the parent.
///
/// A render overflow box proxies most functions in the render box protocol to
/// its child, except that when laying out its child, it passes constraints
/// based on the minWidth, maxWidth, minHeight, and maxHeight fields instead of
/// just passing the parent's constraints in. Specifically, it overrides any of
/// the equivalent fields on the constraints given by the parent with the
/// constraints given by these fields for each such field that is not null. It
/// then sizes itself based on the parent's constraints' maxWidth and maxHeight,
/// ignoring the child's dimensions.
///
/// For example, if you wanted a box to always render 50 pixels high, regardless
485 486 487 488
/// of where it was rendered, you would wrap it in a
/// RenderConstrainedOverflowBox with minHeight and maxHeight set to 50.0.
/// Generally speaking, to avoid confusing behavior around hit testing, a
/// RenderConstrainedOverflowBox should usually be wrapped in a RenderClipRect.
489
///
490
/// The child is positioned according to [alignment]. To position a smaller
491
/// child inside a larger parent, use [RenderPositionedBox] and
492
/// [RenderConstrainedBox] rather than RenderConstrainedOverflowBox.
493 494 495 496 497 498 499 500 501
///
/// See also:
///
///  * [RenderUnconstrainedBox] for a render object that allows its children
///    to render themselves unconstrained, expands to fit them, and considers
///    overflow to be an error.
///  * [RenderSizedOverflowBox], a render object that is a specific size but
///    passes its original constraints through to its child, which it allows to
///    overflow.
502
class RenderConstrainedOverflowBox extends RenderAligningShiftedBox {
503
  /// Creates a render object that lets its child overflow itself.
504
  RenderConstrainedOverflowBox({
505 506 507 508 509
    RenderBox child,
    double minWidth,
    double maxWidth,
    double minHeight,
    double maxHeight,
510
    AlignmentGeometry alignment = Alignment.center,
511
    TextDirection textDirection,
512 513 514 515
  }) : _minWidth = minWidth,
       _maxWidth = maxWidth,
       _minHeight = minHeight,
       _maxHeight = maxHeight,
516
       super(child: child, alignment: alignment, textDirection: textDirection);
517 518 519 520 521

  /// The minimum width constraint to give the child. Set this to null (the
  /// default) to use the constraint from the parent instead.
  double get minWidth => _minWidth;
  double _minWidth;
522
  set minWidth(double value) {
523 524 525 526 527 528 529 530 531 532
    if (_minWidth == value)
      return;
    _minWidth = value;
    markNeedsLayout();
  }

  /// The maximum width constraint to give the child. Set this to null (the
  /// default) to use the constraint from the parent instead.
  double get maxWidth => _maxWidth;
  double _maxWidth;
533
  set maxWidth(double value) {
534 535 536 537 538 539 540 541 542 543
    if (_maxWidth == value)
      return;
    _maxWidth = value;
    markNeedsLayout();
  }

  /// The minimum height constraint to give the child. Set this to null (the
  /// default) to use the constraint from the parent instead.
  double get minHeight => _minHeight;
  double _minHeight;
544
  set minHeight(double value) {
545 546 547 548 549 550 551 552 553 554
    if (_minHeight == value)
      return;
    _minHeight = value;
    markNeedsLayout();
  }

  /// The maximum height constraint to give the child. Set this to null (the
  /// default) to use the constraint from the parent instead.
  double get maxHeight => _maxHeight;
  double _maxHeight;
555
  set maxHeight(double value) {
556 557 558 559 560 561 562
    if (_maxHeight == value)
      return;
    _maxHeight = value;
    markNeedsLayout();
  }

  BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
563
    return BoxConstraints(
564 565 566
      minWidth: _minWidth ?? constraints.minWidth,
      maxWidth: _maxWidth ?? constraints.maxWidth,
      minHeight: _minHeight ?? constraints.minHeight,
567
      maxHeight: _maxHeight ?? constraints.maxHeight,
568 569 570
    );
  }

571
  @override
572 573
  bool get sizedByParent => true;

574
  @override
575 576 577 578
  void performResize() {
    size = constraints.biggest;
  }

579
  @override
580 581 582
  void performLayout() {
    if (child != null) {
      child.layout(_getInnerConstraints(constraints), parentUsesSize: true);
583
      alignChild();
584 585 586
    }
  }

587
  @override
588 589
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
590 591 592 593
    properties.add(DoubleProperty('minWidth', minWidth, ifNull: 'use parent minWidth constraint'));
    properties.add(DoubleProperty('maxWidth', maxWidth, ifNull: 'use parent maxWidth constraint'));
    properties.add(DoubleProperty('minHeight', minHeight, ifNull: 'use parent minHeight constraint'));
    properties.add(DoubleProperty('maxHeight', maxHeight, ifNull: 'use parent maxHeight constraint'));
594 595 596
  }
}

597 598 599 600
/// Renders a box, imposing no constraints on its child, allowing the child to
/// render at its "natural" size.
///
/// This allows a child to render at the size it would render if it were alone
601 602 603 604 605
/// on an infinite canvas with no constraints. This box will then attempt to
/// adopt the same size, within the limits of its own constraints. If it ends
/// up with a different size, it will align the child based on [alignment].
/// If the box cannot expand enough to accommodate the entire child, the
/// child will be clipped.
606 607
///
/// In debug mode, if the child overflows the box, a warning will be printed on
608
/// the console, and black and yellow striped areas will appear where the
609 610 611 612
/// overflow occurs.
///
/// See also:
///
613 614 615
///  * [RenderConstrainedBox], which renders a box which imposes constraints
///    on its child.
///  * [RenderConstrainedOverflowBox], which renders a box that imposes different
616 617 618 619 620 621
///    constraints on its child than it gets from its parent, possibly allowing
///    the child to overflow the parent.
///  * [RenderSizedOverflowBox], a render object that is a specific size but
///    passes its original constraints through to its child, which it allows to
///    overflow.
class RenderUnconstrainedBox extends RenderAligningShiftedBox with DebugOverflowIndicatorMixin {
622 623 624 625
  /// Create a render object that sizes itself to the child but does not
  /// pass the [constraints] down to that child.
  ///
  /// The [alignment] must not be null.
626 627 628
  RenderUnconstrainedBox({
    @required AlignmentGeometry alignment,
    @required TextDirection textDirection,
629
    Axis constrainedAxis,
630
    RenderBox child,
631
    Clip clipBehavior = Clip.none,
632
  }) : assert(alignment != null),
633
       assert(clipBehavior != null),
634
       _constrainedAxis = constrainedAxis,
635
       _clipBehavior = clipBehavior,
636
       super.mixin(alignment, textDirection, child);
637

638 639 640
  /// The axis to retain constraints on, if any.
  ///
  /// If not set, or set to null (the default), neither axis will retain its
641
  /// constraints. If set to [Axis.vertical], then vertical constraints will
642 643 644 645 646 647 648 649 650 651
  /// be retained, and if set to [Axis.horizontal], then horizontal constraints
  /// will be retained.
  Axis get constrainedAxis => _constrainedAxis;
  Axis _constrainedAxis;
  set constrainedAxis(Axis value) {
    if (_constrainedAxis == value)
      return;
    _constrainedAxis = value;
    markNeedsLayout();
  }
652

653 654 655 656
  Rect _overflowContainerRect = Rect.zero;
  Rect _overflowChildRect = Rect.zero;
  bool _isOverflowing = false;

657 658 659 660 661 662 663 664 665 666 667 668 669 670
  /// {@macro flutter.widgets.Clip}
  ///
  /// Defaults to [Clip.none], and must not be null.
  Clip get clipBehavior => _clipBehavior;
  Clip _clipBehavior = Clip.none;
  set clipBehavior(Clip value) {
    assert(value != null);
    if (value != _clipBehavior) {
      _clipBehavior = value;
      markNeedsPaint();
      markNeedsSemanticsUpdate();
    }
  }

671 672
  @override
  void performLayout() {
673
    final BoxConstraints constraints = this.constraints;
674
    if (child != null) {
675 676
      // Let the child lay itself out at it's "natural" size, but if
      // constrainedAxis is non-null, keep any constraints on that axis.
677
      BoxConstraints childConstraints;
678 679 680
      if (constrainedAxis != null) {
        switch (constrainedAxis) {
          case Axis.horizontal:
681
            childConstraints = BoxConstraints(maxWidth: constraints.maxWidth, minWidth: constraints.minWidth);
682 683
            break;
          case Axis.vertical:
684
            childConstraints = BoxConstraints(maxHeight: constraints.maxHeight, minHeight: constraints.minHeight);
685 686 687
            break;
        }
      } else {
688
        childConstraints = const BoxConstraints();
689
      }
690
      child.layout(childConstraints, parentUsesSize: true);
691 692
      size = constraints.constrain(child.size);
      alignChild();
693
      final BoxParentData childParentData = child.parentData as BoxParentData;
694 695 696
      _overflowContainerRect = Offset.zero & size;
      _overflowChildRect = childParentData.offset & child.size;
    } else {
697
      size = constraints.smallest;
698 699 700
      _overflowContainerRect = Rect.zero;
      _overflowChildRect = Rect.zero;
    }
701
    _isOverflowing = RelativeRect.fromRect(_overflowContainerRect, _overflowChildRect).hasInsets;
702 703 704 705 706 707 708 709 710 711 712 713 714 715
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    // There's no point in drawing the child if we're empty, or there is no
    // child.
    if (child == null || size.isEmpty)
      return;

    if (!_isOverflowing) {
      super.paint(context, offset);
      return;
    }

716 717 718 719 720 721
    if (clipBehavior == Clip.none) {
      super.paint(context, offset);
    } else {
      // We have overflow and the clipBehavior isn't none. Clip it.
      context.pushClipRect(needsCompositing, offset, Offset.zero & size, super.paint, clipBehavior: clipBehavior);
    }
722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746

    // Display the overflow indicator.
    assert(() {
      paintOverflowIndicator(context, offset, _overflowContainerRect, _overflowChildRect);
      return true;
    }());
  }

  @override
  Rect describeApproximatePaintClip(RenderObject child) {
    return _isOverflowing ? Offset.zero & size : null;
  }

  @override
  String toStringShort() {
    String header = super.toStringShort();
    if (_isOverflowing)
      header += ' OVERFLOWING';
    return header;
  }
}

/// A render object that is a specific size but passes its original constraints
/// through to its child, which it allows to overflow.
///
747 748 749
/// If the child's resulting size differs from this render object's size, then
/// the child is aligned according to the [alignment] property.
///
750
/// See also:
751
///
752 753 754 755 756 757
///  * [RenderUnconstrainedBox] for a render object that allows its children
///    to render themselves unconstrained, expands to fit them, and considers
///    overflow to be an error.
///  * [RenderConstrainedOverflowBox] for a render object that imposes
///    different constraints on its child than it gets from its parent,
///    possibly allowing the child to overflow the parent.
758
class RenderSizedOverflowBox extends RenderAligningShiftedBox {
759 760
  /// Creates a render box of a given size that lets its child overflow.
  ///
761 762 763 764
  /// The [requestedSize] and [alignment] arguments must not be null.
  ///
  /// The [textDirection] argument must not be null if the [alignment] is
  /// direction-sensitive.
765 766
  RenderSizedOverflowBox({
    RenderBox child,
767
    @required Size requestedSize,
768
    AlignmentGeometry alignment = Alignment.center,
769
    TextDirection textDirection,
770 771
  }) : assert(requestedSize != null),
       _requestedSize = requestedSize,
772
       super(child: child, alignment: alignment, textDirection: textDirection);
773 774 775 776

  /// The size this render box should attempt to be.
  Size get requestedSize => _requestedSize;
  Size _requestedSize;
777
  set requestedSize(Size value) {
778 779 780 781 782 783 784 785
    assert(value != null);
    if (_requestedSize == value)
      return;
    _requestedSize = value;
    markNeedsLayout();
  }

  @override
786
  double computeMinIntrinsicWidth(double height) {
787
    return _requestedSize.width;
788 789 790
  }

  @override
791
  double computeMaxIntrinsicWidth(double height) {
792
    return _requestedSize.width;
793 794 795
  }

  @override
796
  double computeMinIntrinsicHeight(double width) {
797
    return _requestedSize.height;
798 799 800
  }

  @override
801
  double computeMaxIntrinsicHeight(double width) {
802
    return _requestedSize.height;
803 804 805 806 807 808 809 810 811 812 813 814 815
  }

  @override
  double computeDistanceToActualBaseline(TextBaseline baseline) {
    if (child != null)
      return child.getDistanceToActualBaseline(baseline);
    return super.computeDistanceToActualBaseline(baseline);
  }

  @override
  void performLayout() {
    size = constraints.constrain(_requestedSize);
    if (child != null) {
816
      child.layout(constraints, parentUsesSize: true);
817 818 819 820 821 822 823 824 825 826 827 828 829
      alignChild();
    }
  }
}

/// Sizes its child to a fraction of the total available space.
///
/// For both its width and height, this render object imposes a tight
/// constraint on its child that is a multiple (typically less than 1.0) of the
/// maximum constraint it received from its parent on that axis. If the factor
/// for a given axis is null, then the constraints from the parent are just
/// passed through instead.
///
830 831 832
/// It then tries to size itself to the size of its child. Where this is not
/// possible (e.g. if the constraints from the parent are themselves tight), the
/// child is aligned according to [alignment].
833
class RenderFractionallySizedOverflowBox extends RenderAligningShiftedBox {
834 835 836 837
  /// Creates a render box that sizes its child to a fraction of the total available space.
  ///
  /// If non-null, the [widthFactor] and [heightFactor] arguments must be
  /// non-negative.
838 839 840 841 842
  ///
  /// The [alignment] must not be null.
  ///
  /// The [textDirection] must be non-null if the [alignment] is
  /// direction-sensitive.
843 844 845 846
  RenderFractionallySizedOverflowBox({
    RenderBox child,
    double widthFactor,
    double heightFactor,
847
    AlignmentGeometry alignment = Alignment.center,
848
    TextDirection textDirection,
849 850
  }) : _widthFactor = widthFactor,
       _heightFactor = heightFactor,
851
       super(child: child, alignment: alignment, textDirection: textDirection) {
852 853 854 855 856 857 858
    assert(_widthFactor == null || _widthFactor >= 0.0);
    assert(_heightFactor == null || _heightFactor >= 0.0);
  }

  /// If non-null, the factor of the incoming width to use.
  ///
  /// If non-null, the child is given a tight width constraint that is the max
859
  /// incoming width constraint multiplied by this factor. If null, the child is
860
  /// given the incoming width constraints.
861 862
  double get widthFactor => _widthFactor;
  double _widthFactor;
863
  set widthFactor(double value) {
864 865 866 867 868 869 870 871 872 873
    assert(value == null || value >= 0.0);
    if (_widthFactor == value)
      return;
    _widthFactor = value;
    markNeedsLayout();
  }

  /// If non-null, the factor of the incoming height to use.
  ///
  /// If non-null, the child is given a tight height constraint that is the max
874
  /// incoming width constraint multiplied by this factor. If null, the child is
875
  /// given the incoming width constraints.
876 877
  double get heightFactor => _heightFactor;
  double _heightFactor;
878
  set heightFactor(double value) {
879 880 881 882 883 884 885 886 887 888 889
    assert(value == null || value >= 0.0);
    if (_heightFactor == value)
      return;
    _heightFactor = value;
    markNeedsLayout();
  }

  BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
    double minWidth = constraints.minWidth;
    double maxWidth = constraints.maxWidth;
    if (_widthFactor != null) {
890
      final double width = maxWidth * _widthFactor;
891 892 893 894 895 896
      minWidth = width;
      maxWidth = width;
    }
    double minHeight = constraints.minHeight;
    double maxHeight = constraints.maxHeight;
    if (_heightFactor != null) {
897
      final double height = maxHeight * _heightFactor;
898 899 900
      minHeight = height;
      maxHeight = height;
    }
901
    return BoxConstraints(
902 903 904
      minWidth: minWidth,
      maxWidth: maxWidth,
      minHeight: minHeight,
905
      maxHeight: maxHeight,
906 907 908 909
    );
  }

  @override
910
  double computeMinIntrinsicWidth(double height) {
911 912
    double result;
    if (child == null) {
913
      result = super.computeMinIntrinsicWidth(height);
914
    } else { // the following line relies on double.infinity absorption
915 916 917 918
      result = child.getMinIntrinsicWidth(height * (_heightFactor ?? 1.0));
    }
    assert(result.isFinite);
    return result / (_widthFactor ?? 1.0);
919 920 921
  }

  @override
922
  double computeMaxIntrinsicWidth(double height) {
923 924
    double result;
    if (child == null) {
925
      result = super.computeMaxIntrinsicWidth(height);
926
    } else { // the following line relies on double.infinity absorption
927 928 929 930
      result = child.getMaxIntrinsicWidth(height * (_heightFactor ?? 1.0));
    }
    assert(result.isFinite);
    return result / (_widthFactor ?? 1.0);
931 932 933
  }

  @override
934
  double computeMinIntrinsicHeight(double width) {
935 936
    double result;
    if (child == null) {
937
      result = super.computeMinIntrinsicHeight(width);
938
    } else { // the following line relies on double.infinity absorption
939 940 941 942
      result = child.getMinIntrinsicHeight(width * (_widthFactor ?? 1.0));
    }
    assert(result.isFinite);
    return result / (_heightFactor ?? 1.0);
943 944 945
  }

  @override
946
  double computeMaxIntrinsicHeight(double width) {
947 948
    double result;
    if (child == null) {
949
      result = super.computeMaxIntrinsicHeight(width);
950
    } else { // the following line relies on double.infinity absorption
951 952 953 954
      result = child.getMaxIntrinsicHeight(width * (_widthFactor ?? 1.0));
    }
    assert(result.isFinite);
    return result / (_heightFactor ?? 1.0);
955 956 957 958 959 960 961 962 963 964 965 966 967 968
  }

  @override
  void performLayout() {
    if (child != null) {
      child.layout(_getInnerConstraints(constraints), parentUsesSize: true);
      size = constraints.constrain(child.size);
      alignChild();
    } else {
      size = constraints.constrain(_getInnerConstraints(constraints).constrain(Size.zero));
    }
  }

  @override
969 970
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
971 972
    properties.add(DoubleProperty('widthFactor', _widthFactor, ifNull: 'pass-through'));
    properties.add(DoubleProperty('heightFactor', _heightFactor, ifNull: 'pass-through'));
973 974 975
  }
}

Adam Barth's avatar
Adam Barth committed
976
/// A delegate for computing the layout of a render object with a single child.
977 978 979 980 981 982 983 984 985 986 987 988 989 990
///
/// Used by [CustomSingleChildLayout] (in the widgets library) and
/// [RenderCustomSingleChildLayoutBox] (in the rendering library).
///
/// When asked to layout, [CustomSingleChildLayout] first calls [getSize] with
/// its incoming constraints to determine its size. It then calls
/// [getConstraintsForChild] to determine the constraints to apply to the child.
/// After the child completes its layout, [RenderCustomSingleChildLayoutBox]
/// calls [getPositionForChild] to determine the child's position.
///
/// The [shouldRelayout] method is called when a new instance of the class
/// is provided, to check if the new instance actually represents different
/// information.
///
991
/// The most efficient way to trigger a relayout is to supply a `relayout`
992
/// argument to the constructor of the [SingleChildLayoutDelegate]. The custom
993 994 995
/// layout will listen to this value and relayout whenever the Listenable
/// notifies its listeners, such as when an [Animation] ticks. This allows
/// the custom layout to avoid the build phase of the pipeline.
996 997 998 999 1000 1001
///
/// See also:
///
///  * [CustomSingleChildLayout], the widget that uses this delegate.
///  * [RenderCustomSingleChildLayoutBox], render object that uses this
///    delegate.
1002
abstract class SingleChildLayoutDelegate {
1003 1004 1005 1006
  /// Creates a layout delegate.
  ///
  /// The layout will update whenever [relayout] notifies its listeners.
  const SingleChildLayoutDelegate({ Listenable relayout }) : _relayout = relayout;
1007

1008
  final Listenable _relayout;
1009 1010 1011

  /// The size of this object given the incoming constraints.
  ///
1012
  /// Defaults to the biggest size that satisfies the given constraints.
Adam Barth's avatar
Adam Barth committed
1013 1014
  Size getSize(BoxConstraints constraints) => constraints.biggest;

1015 1016 1017 1018 1019 1020 1021
  /// The constraints for the child given the incoming constraints.
  ///
  /// During layout, the child is given the layout constraints returned by this
  /// function. The child is required to pick a size for itself that satisfies
  /// these constraints.
  ///
  /// Defaults to the given constraints.
Adam Barth's avatar
Adam Barth committed
1022 1023
  BoxConstraints getConstraintsForChild(BoxConstraints constraints) => constraints;

1024 1025 1026 1027 1028 1029 1030 1031 1032
  /// The position where the child should be placed.
  ///
  /// The `size` argument is the size of the parent, which might be different
  /// from the value returned by [getSize] if that size doesn't satisfy the
  /// constraints passed to [getSize]. The `childSize` argument is the size of
  /// the child, which will satisfy the constraints returned by
  /// [getConstraintsForChild].
  ///
  /// Defaults to positioning the child in the upper left corner of the parent.
1033
  Offset getPositionForChild(Size size, Size childSize) => Offset.zero;
1034

1035 1036 1037 1038 1039 1040 1041
  /// Called whenever a new instance of the custom layout delegate class is
  /// provided to the [RenderCustomSingleChildLayoutBox] object, or any time
  /// that a new [CustomSingleChildLayout] object is created with a new instance
  /// of the custom layout delegate class (which amounts to the same thing,
  /// because the latter is implemented in terms of the former).
  ///
  /// If the new instance represents different information than the old
1042 1043
  /// instance, then the method should return true, otherwise it should return
  /// false.
1044
  ///
1045
  /// If the method returns false, then the [getSize],
1046 1047 1048 1049
  /// [getConstraintsForChild], and [getPositionForChild] calls might be
  /// optimized away.
  ///
  /// It's possible that the layout methods will get called even if
1050
  /// [shouldRelayout] returns false (e.g. if an ancestor changed its layout).
1051 1052 1053
  /// It's also possible that the layout method will get called
  /// without [shouldRelayout] being called at all (e.g. if the parent changes
  /// size).
1054
  bool shouldRelayout(covariant SingleChildLayoutDelegate oldDelegate);
Adam Barth's avatar
Adam Barth committed
1055 1056
}

1057 1058 1059 1060 1061 1062
/// Defers the layout of its single child to a delegate.
///
/// The delegate can determine the layout constraints for the child and can
/// decide where to position the child. The delegate can also determine the size
/// of the parent, but the size of the parent cannot depend on the size of the
/// child.
1063
class RenderCustomSingleChildLayoutBox extends RenderShiftedBox {
1064
  /// Creates a render box that defers its layout to a delegate.
1065 1066
  ///
  /// The [delegate] argument must not be null.
1067
  RenderCustomSingleChildLayoutBox({
Adam Barth's avatar
Adam Barth committed
1068
    RenderBox child,
1069
    @required SingleChildLayoutDelegate delegate,
1070 1071 1072
  }) : assert(delegate != null),
       _delegate = delegate,
       super(child);
Adam Barth's avatar
Adam Barth committed
1073

1074
  /// A delegate that controls this object's layout.
1075 1076
  SingleChildLayoutDelegate get delegate => _delegate;
  SingleChildLayoutDelegate _delegate;
1077
  set delegate(SingleChildLayoutDelegate newDelegate) {
Adam Barth's avatar
Adam Barth committed
1078 1079 1080
    assert(newDelegate != null);
    if (_delegate == newDelegate)
      return;
1081 1082
    final SingleChildLayoutDelegate oldDelegate = _delegate;
    if (newDelegate.runtimeType != oldDelegate.runtimeType || newDelegate.shouldRelayout(oldDelegate))
1083
      markNeedsLayout();
Adam Barth's avatar
Adam Barth committed
1084
    _delegate = newDelegate;
1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100
    if (attached) {
      oldDelegate?._relayout?.removeListener(markNeedsLayout);
      newDelegate?._relayout?.addListener(markNeedsLayout);
    }
  }

  @override
  void attach(PipelineOwner owner) {
    super.attach(owner);
    _delegate?._relayout?.addListener(markNeedsLayout);
  }

  @override
  void detach() {
    _delegate?._relayout?.removeListener(markNeedsLayout);
    super.detach();
Adam Barth's avatar
Adam Barth committed
1101 1102 1103 1104 1105 1106
  }

  Size _getSize(BoxConstraints constraints) {
    return constraints.constrain(_delegate.getSize(constraints));
  }

1107 1108 1109 1110
  // TODO(ianh): It's a bit dubious to be using the getSize function from the delegate to
  // figure out the intrinsic dimensions. We really should either not support intrinsics,
  // or we should expose intrinsic delegate callbacks and throw if they're not implemented.

1111
  @override
1112
  double computeMinIntrinsicWidth(double height) {
1113
    final double width = _getSize(BoxConstraints.tightForFinite(height: height)).width;
1114 1115 1116
    if (width.isFinite)
      return width;
    return 0.0;
Adam Barth's avatar
Adam Barth committed
1117 1118
  }

1119
  @override
1120
  double computeMaxIntrinsicWidth(double height) {
1121
    final double width = _getSize(BoxConstraints.tightForFinite(height: height)).width;
1122 1123 1124
    if (width.isFinite)
      return width;
    return 0.0;
Adam Barth's avatar
Adam Barth committed
1125 1126
  }

1127
  @override
1128
  double computeMinIntrinsicHeight(double width) {
1129
    final double height = _getSize(BoxConstraints.tightForFinite(width: width)).height;
1130 1131 1132
    if (height.isFinite)
      return height;
    return 0.0;
Adam Barth's avatar
Adam Barth committed
1133 1134
  }

1135
  @override
1136
  double computeMaxIntrinsicHeight(double width) {
1137
    final double height = _getSize(BoxConstraints.tightForFinite(width: width)).height;
1138 1139 1140
    if (height.isFinite)
      return height;
    return 0.0;
Adam Barth's avatar
Adam Barth committed
1141 1142
  }

1143
  @override
Adam Barth's avatar
Adam Barth committed
1144
  void performLayout() {
1145
    size = _getSize(constraints);
Adam Barth's avatar
Adam Barth committed
1146
    if (child != null) {
1147
      final BoxConstraints childConstraints = delegate.getConstraintsForChild(constraints);
1148
      assert(childConstraints.debugAssertIsValid(isAppliedConstraint: true));
1149
      child.layout(childConstraints, parentUsesSize: !childConstraints.isTight);
1150
      final BoxParentData childParentData = child.parentData as BoxParentData;
1151
      childParentData.offset = delegate.getPositionForChild(size, childConstraints.isTight ? childConstraints.smallest : child.size);
Adam Barth's avatar
Adam Barth committed
1152 1153 1154 1155
    }
  }
}

1156 1157 1158
/// Shifts the child down such that the child's baseline (or the
/// bottom of the child, if the child has no baseline) is [baseline]
/// logical pixels below the top of this box, then sizes this box to
1159 1160 1161 1162
/// contain the child.
///
/// If [baseline] is less than the distance from the top of the child
/// to the baseline of the child, then the child will overflow the top
1163
/// of the box. This is typically not desirable, in particular, that
1164 1165 1166 1167 1168 1169 1170 1171
/// part of the child will not be found when doing hit tests, so the
/// user cannot interact with that part of the child.
///
/// This box will be sized so that its bottom is coincident with the
/// bottom of the child. This means if this box shifts the child down,
/// there will be space between the top of this box and the top of the
/// child, but there is never space between the bottom of the child
/// and the bottom of the box.
1172
class RenderBaseline extends RenderShiftedBox {
1173 1174
  /// Creates a [RenderBaseline] object.
  ///
1175
  /// The [baseline] and [baselineType] arguments must not be null.
1176 1177
  RenderBaseline({
    RenderBox child,
1178
    @required double baseline,
1179
    @required TextBaseline baselineType,
1180 1181 1182
  }) : assert(baseline != null),
       assert(baselineType != null),
       _baseline = baseline,
1183
       _baselineType = baselineType,
1184
       super(child);
1185

1186 1187
  /// The number of logical pixels from the top of this box at which to position
  /// the child's baseline.
1188
  double get baseline => _baseline;
1189
  double _baseline;
1190
  set baseline(double value) {
1191 1192 1193 1194 1195 1196 1197
    assert(value != null);
    if (_baseline == value)
      return;
    _baseline = value;
    markNeedsLayout();
  }

1198
  /// The type of baseline to use for positioning the child.
1199
  TextBaseline get baselineType => _baselineType;
1200
  TextBaseline _baselineType;
1201
  set baselineType(TextBaseline value) {
1202 1203 1204 1205 1206 1207 1208
    assert(value != null);
    if (_baselineType == value)
      return;
    _baselineType = value;
    markNeedsLayout();
  }

1209
  @override
1210 1211
  void performLayout() {
    if (child != null) {
1212
      final BoxConstraints constraints = this.constraints;
1213
      child.layout(constraints.loosen(), parentUsesSize: true);
1214
      final double childBaseline = child.getDistanceToBaseline(baselineType);
1215
      final double actualBaseline = baseline;
1216
      final double top = actualBaseline - childBaseline;
1217
      final BoxParentData childParentData = child.parentData as BoxParentData;
1218
      childParentData.offset = Offset(0.0, top);
1219
      final Size childSize = child.size;
1220
      size = constraints.constrain(Size(childSize.width, top + childSize.height));
1221 1222 1223 1224 1225
    } else {
      performResize();
    }
  }

1226
  @override
1227 1228
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1229 1230
    properties.add(DoubleProperty('baseline', baseline));
    properties.add(EnumProperty<TextBaseline>('baselineType', baselineType));
1231
  }
1232
}