• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2015 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:math' as math;
6
7import 'package:flutter/foundation.dart';
8
9import 'box.dart';
10import 'debug.dart';
11import 'debug_overflow_indicator.dart';
12import 'object.dart';
13import 'stack.dart' show RelativeRect;
14
15/// Abstract class for one-child-layout render boxes that provide control over
16/// the child's position.
17abstract class RenderShiftedBox extends RenderBox with RenderObjectWithChildMixin<RenderBox> {
18  /// Initializes the [child] property for subclasses.
19  RenderShiftedBox(RenderBox child) {
20    this.child = child;
21  }
22
23  @override
24  double computeMinIntrinsicWidth(double height) {
25    if (child != null)
26      return child.getMinIntrinsicWidth(height);
27    return 0.0;
28  }
29
30  @override
31  double computeMaxIntrinsicWidth(double height) {
32    if (child != null)
33      return child.getMaxIntrinsicWidth(height);
34    return 0.0;
35  }
36
37  @override
38  double computeMinIntrinsicHeight(double width) {
39    if (child != null)
40      return child.getMinIntrinsicHeight(width);
41    return 0.0;
42  }
43
44  @override
45  double computeMaxIntrinsicHeight(double width) {
46    if (child != null)
47      return child.getMaxIntrinsicHeight(width);
48    return 0.0;
49  }
50
51  @override
52  double computeDistanceToActualBaseline(TextBaseline baseline) {
53    double result;
54    if (child != null) {
55      assert(!debugNeedsLayout);
56      result = child.getDistanceToActualBaseline(baseline);
57      final BoxParentData childParentData = child.parentData;
58      if (result != null)
59        result += childParentData.offset.dy;
60    } else {
61      result = super.computeDistanceToActualBaseline(baseline);
62    }
63    return result;
64  }
65
66  @override
67  void paint(PaintingContext context, Offset offset) {
68    if (child != null) {
69      final BoxParentData childParentData = child.parentData;
70      context.paintChild(child, childParentData.offset + offset);
71    }
72  }
73
74  @override
75  bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
76    if (child != null) {
77      final BoxParentData childParentData = child.parentData;
78      return result.addWithPaintOffset(
79        offset: childParentData.offset,
80        position: position,
81        hitTest: (BoxHitTestResult result, Offset transformed) {
82          assert(transformed == position - childParentData.offset);
83          return child.hitTest(result, position: transformed);
84        },
85      );
86    }
87    return false;
88  }
89
90}
91
92/// Insets its child by the given padding.
93///
94/// When passing layout constraints to its child, padding shrinks the
95/// constraints by the given padding, causing the child to layout at a smaller
96/// size. Padding then sizes itself to its child's size, inflated by the
97/// padding, effectively creating empty space around the child.
98class RenderPadding extends RenderShiftedBox {
99  /// Creates a render object that insets its child.
100  ///
101  /// The [padding] argument must not be null and must have non-negative insets.
102  RenderPadding({
103    @required EdgeInsetsGeometry padding,
104    TextDirection textDirection,
105    RenderBox child,
106  }) : assert(padding != null),
107       assert(padding.isNonNegative),
108       _textDirection = textDirection,
109       _padding = padding,
110       super(child);
111
112  EdgeInsets _resolvedPadding;
113
114  void _resolve() {
115    if (_resolvedPadding != null)
116      return;
117    _resolvedPadding = padding.resolve(textDirection);
118    assert(_resolvedPadding.isNonNegative);
119  }
120
121  void _markNeedResolution() {
122    _resolvedPadding = null;
123    markNeedsLayout();
124  }
125
126  /// The amount to pad the child in each dimension.
127  ///
128  /// If this is set to an [EdgeInsetsDirectional] object, then [textDirection]
129  /// must not be null.
130  EdgeInsetsGeometry get padding => _padding;
131  EdgeInsetsGeometry _padding;
132  set padding(EdgeInsetsGeometry value) {
133    assert(value != null);
134    assert(value.isNonNegative);
135    if (_padding == value)
136      return;
137    _padding = value;
138    _markNeedResolution();
139  }
140
141  /// The text direction with which to resolve [padding].
142  ///
143  /// This may be changed to null, but only after the [padding] has been changed
144  /// to a value that does not depend on the direction.
145  TextDirection get textDirection => _textDirection;
146  TextDirection _textDirection;
147  set textDirection(TextDirection value) {
148    if (_textDirection == value)
149      return;
150    _textDirection = value;
151    _markNeedResolution();
152  }
153
154  @override
155  double computeMinIntrinsicWidth(double height) {
156    _resolve();
157    final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
158    final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
159    if (child != null) // next line relies on double.infinity absorption
160      return child.getMinIntrinsicWidth(math.max(0.0, height - totalVerticalPadding)) + totalHorizontalPadding;
161    return totalHorizontalPadding;
162  }
163
164  @override
165  double computeMaxIntrinsicWidth(double height) {
166    _resolve();
167    final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
168    final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
169    if (child != null) // next line relies on double.infinity absorption
170      return child.getMaxIntrinsicWidth(math.max(0.0, height - totalVerticalPadding)) + totalHorizontalPadding;
171    return totalHorizontalPadding;
172  }
173
174  @override
175  double computeMinIntrinsicHeight(double width) {
176    _resolve();
177    final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
178    final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
179    if (child != null) // next line relies on double.infinity absorption
180      return child.getMinIntrinsicHeight(math.max(0.0, width - totalHorizontalPadding)) + totalVerticalPadding;
181    return totalVerticalPadding;
182  }
183
184  @override
185  double computeMaxIntrinsicHeight(double width) {
186    _resolve();
187    final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
188    final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
189    if (child != null) // next line relies on double.infinity absorption
190      return child.getMaxIntrinsicHeight(math.max(0.0, width - totalHorizontalPadding)) + totalVerticalPadding;
191    return totalVerticalPadding;
192  }
193
194  @override
195  void performLayout() {
196    _resolve();
197    assert(_resolvedPadding != null);
198    if (child == null) {
199      size = constraints.constrain(Size(
200        _resolvedPadding.left + _resolvedPadding.right,
201        _resolvedPadding.top + _resolvedPadding.bottom,
202      ));
203      return;
204    }
205    final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding);
206    child.layout(innerConstraints, parentUsesSize: true);
207    final BoxParentData childParentData = child.parentData;
208    childParentData.offset = Offset(_resolvedPadding.left, _resolvedPadding.top);
209    size = constraints.constrain(Size(
210      _resolvedPadding.left + child.size.width + _resolvedPadding.right,
211      _resolvedPadding.top + child.size.height + _resolvedPadding.bottom,
212    ));
213  }
214
215  @override
216  void debugPaintSize(PaintingContext context, Offset offset) {
217    super.debugPaintSize(context, offset);
218    assert(() {
219      final Rect outerRect = offset & size;
220      debugPaintPadding(context.canvas, outerRect, child != null ? _resolvedPadding.deflateRect(outerRect) : null);
221      return true;
222    }());
223  }
224
225  @override
226  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
227    super.debugFillProperties(properties);
228    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding));
229    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
230  }
231}
232
233/// Abstract class for one-child-layout render boxes that use a
234/// [AlignmentGeometry] to align their children.
235abstract class RenderAligningShiftedBox extends RenderShiftedBox {
236  /// Initializes member variables for subclasses.
237  ///
238  /// The [alignment] argument must not be null.
239  ///
240  /// The [textDirection] must be non-null if the [alignment] is
241  /// direction-sensitive.
242  RenderAligningShiftedBox({
243    AlignmentGeometry alignment = Alignment.center,
244    @required TextDirection textDirection,
245    RenderBox child,
246  }) : assert(alignment != null),
247       _alignment = alignment,
248       _textDirection = textDirection,
249       super(child);
250
251  /// A constructor to be used only when the extending class also has a mixin.
252  // TODO(gspencer): Remove this constructor once https://github.com/dart-lang/sdk/issues/31543 is fixed.
253  @protected
254  RenderAligningShiftedBox.mixin(AlignmentGeometry alignment, TextDirection textDirection, RenderBox child)
255    : this(alignment: alignment, textDirection: textDirection, child: child);
256
257  Alignment _resolvedAlignment;
258
259  void _resolve() {
260    if (_resolvedAlignment != null)
261      return;
262    _resolvedAlignment = alignment.resolve(textDirection);
263  }
264
265  void _markNeedResolution() {
266    _resolvedAlignment = null;
267    markNeedsLayout();
268  }
269
270  /// How to align the child.
271  ///
272  /// The x and y values of the alignment control the horizontal and vertical
273  /// alignment, respectively. An x value of -1.0 means that the left edge of
274  /// the child is aligned with the left edge of the parent whereas an x value
275  /// of 1.0 means that the right edge of the child is aligned with the right
276  /// edge of the parent. Other values interpolate (and extrapolate) linearly.
277  /// For example, a value of 0.0 means that the center of the child is aligned
278  /// with the center of the parent.
279  ///
280  /// If this is set to an [AlignmentDirectional] object, then
281  /// [textDirection] must not be null.
282  AlignmentGeometry get alignment => _alignment;
283  AlignmentGeometry _alignment;
284  /// Sets the alignment to a new value, and triggers a layout update.
285  ///
286  /// The new alignment must not be null.
287  set alignment(AlignmentGeometry value) {
288    assert(value != null);
289    if (_alignment == value)
290      return;
291    _alignment = value;
292    _markNeedResolution();
293  }
294
295  /// The text direction with which to resolve [alignment].
296  ///
297  /// This may be changed to null, but only after [alignment] has been changed
298  /// to a value that does not depend on the direction.
299  TextDirection get textDirection => _textDirection;
300  TextDirection _textDirection;
301  set textDirection(TextDirection value) {
302    if (_textDirection == value)
303      return;
304    _textDirection = value;
305    _markNeedResolution();
306  }
307
308  /// Apply the current [alignment] to the [child].
309  ///
310  /// Subclasses should call this method if they have a child, to have
311  /// this class perform the actual alignment. If there is no child,
312  /// do not call this method.
313  ///
314  /// This method must be called after the child has been laid out and
315  /// this object's own size has been set.
316  @protected
317  void alignChild() {
318    _resolve();
319    assert(child != null);
320    assert(!child.debugNeedsLayout);
321    assert(child.hasSize);
322    assert(hasSize);
323    assert(_resolvedAlignment != null);
324    final BoxParentData childParentData = child.parentData;
325    childParentData.offset = _resolvedAlignment.alongOffset(size - child.size);
326  }
327
328  @override
329  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
330    super.debugFillProperties(properties);
331    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment));
332    properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
333  }
334}
335
336/// Positions its child using an [AlignmentGeometry].
337///
338/// For example, to align a box at the bottom right, you would pass this box a
339/// tight constraint that is bigger than the child's natural size,
340/// with an alignment of [Alignment.bottomRight].
341///
342/// By default, sizes to be as big as possible in both axes. If either axis is
343/// unconstrained, then in that direction it will be sized to fit the child's
344/// dimensions. Using widthFactor and heightFactor you can force this latter
345/// behavior in all cases.
346class RenderPositionedBox extends RenderAligningShiftedBox {
347  /// Creates a render object that positions its child.
348  RenderPositionedBox({
349    RenderBox child,
350    double widthFactor,
351    double heightFactor,
352    AlignmentGeometry alignment = Alignment.center,
353    TextDirection textDirection,
354  }) : assert(widthFactor == null || widthFactor >= 0.0),
355       assert(heightFactor == null || heightFactor >= 0.0),
356       _widthFactor = widthFactor,
357       _heightFactor = heightFactor,
358       super(child: child, alignment: alignment, textDirection: textDirection);
359
360  /// If non-null, sets its width to the child's width multiplied by this factor.
361  ///
362  /// Can be both greater and less than 1.0 but must be positive.
363  double get widthFactor => _widthFactor;
364  double _widthFactor;
365  set widthFactor(double value) {
366    assert(value == null || value >= 0.0);
367    if (_widthFactor == value)
368      return;
369    _widthFactor = value;
370    markNeedsLayout();
371  }
372
373  /// If non-null, sets its height to the child's height multiplied by this factor.
374  ///
375  /// Can be both greater and less than 1.0 but must be positive.
376  double get heightFactor => _heightFactor;
377  double _heightFactor;
378  set heightFactor(double value) {
379    assert(value == null || value >= 0.0);
380    if (_heightFactor == value)
381      return;
382    _heightFactor = value;
383    markNeedsLayout();
384  }
385
386  @override
387  void performLayout() {
388    final bool shrinkWrapWidth = _widthFactor != null || constraints.maxWidth == double.infinity;
389    final bool shrinkWrapHeight = _heightFactor != null || constraints.maxHeight == double.infinity;
390
391    if (child != null) {
392      child.layout(constraints.loosen(), parentUsesSize: true);
393      size = constraints.constrain(Size(shrinkWrapWidth ? child.size.width * (_widthFactor ?? 1.0) : double.infinity,
394                                            shrinkWrapHeight ? child.size.height * (_heightFactor ?? 1.0) : double.infinity));
395      alignChild();
396    } else {
397      size = constraints.constrain(Size(shrinkWrapWidth ? 0.0 : double.infinity,
398                                            shrinkWrapHeight ? 0.0 : double.infinity));
399    }
400  }
401
402  @override
403  void debugPaintSize(PaintingContext context, Offset offset) {
404    super.debugPaintSize(context, offset);
405    assert(() {
406      Paint paint;
407      if (child != null && !child.size.isEmpty) {
408        Path path;
409        paint = Paint()
410          ..style = PaintingStyle.stroke
411          ..strokeWidth = 1.0
412          ..color = const Color(0xFFFFFF00);
413        path = Path();
414        final BoxParentData childParentData = child.parentData;
415        if (childParentData.offset.dy > 0.0) {
416          // vertical alignment arrows
417          final double headSize = math.min(childParentData.offset.dy * 0.2, 10.0);
418          path
419            ..moveTo(offset.dx + size.width / 2.0, offset.dy)
420            ..relativeLineTo(0.0, childParentData.offset.dy - headSize)
421            ..relativeLineTo(headSize, 0.0)
422            ..relativeLineTo(-headSize, headSize)
423            ..relativeLineTo(-headSize, -headSize)
424            ..relativeLineTo(headSize, 0.0)
425            ..moveTo(offset.dx + size.width / 2.0, offset.dy + size.height)
426            ..relativeLineTo(0.0, -childParentData.offset.dy + headSize)
427            ..relativeLineTo(headSize, 0.0)
428            ..relativeLineTo(-headSize, -headSize)
429            ..relativeLineTo(-headSize, headSize)
430            ..relativeLineTo(headSize, 0.0);
431          context.canvas.drawPath(path, paint);
432        }
433        if (childParentData.offset.dx > 0.0) {
434          // horizontal alignment arrows
435          final double headSize = math.min(childParentData.offset.dx * 0.2, 10.0);
436          path
437            ..moveTo(offset.dx, offset.dy + size.height / 2.0)
438            ..relativeLineTo(childParentData.offset.dx - headSize, 0.0)
439            ..relativeLineTo(0.0, headSize)
440            ..relativeLineTo(headSize, -headSize)
441            ..relativeLineTo(-headSize, -headSize)
442            ..relativeLineTo(0.0, headSize)
443            ..moveTo(offset.dx + size.width, offset.dy + size.height / 2.0)
444            ..relativeLineTo(-childParentData.offset.dx + headSize, 0.0)
445            ..relativeLineTo(0.0, headSize)
446            ..relativeLineTo(-headSize, -headSize)
447            ..relativeLineTo(headSize, -headSize)
448            ..relativeLineTo(0.0, headSize);
449          context.canvas.drawPath(path, paint);
450        }
451      } else {
452        paint = Paint()
453          ..color = const Color(0x90909090);
454        context.canvas.drawRect(offset & size, paint);
455      }
456      return true;
457    }());
458  }
459
460  @override
461  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
462    super.debugFillProperties(properties);
463    properties.add(DoubleProperty('widthFactor', _widthFactor, ifNull: 'expand'));
464    properties.add(DoubleProperty('heightFactor', _heightFactor, ifNull: 'expand'));
465  }
466}
467
468/// A render object that imposes different constraints on its child than it gets
469/// from its parent, possibly allowing the child to overflow the parent.
470///
471/// A render overflow box proxies most functions in the render box protocol to
472/// its child, except that when laying out its child, it passes constraints
473/// based on the minWidth, maxWidth, minHeight, and maxHeight fields instead of
474/// just passing the parent's constraints in. Specifically, it overrides any of
475/// the equivalent fields on the constraints given by the parent with the
476/// constraints given by these fields for each such field that is not null. It
477/// then sizes itself based on the parent's constraints' maxWidth and maxHeight,
478/// ignoring the child's dimensions.
479///
480/// For example, if you wanted a box to always render 50 pixels high, regardless
481/// of where it was rendered, you would wrap it in a
482/// RenderConstrainedOverflowBox with minHeight and maxHeight set to 50.0.
483/// Generally speaking, to avoid confusing behavior around hit testing, a
484/// RenderConstrainedOverflowBox should usually be wrapped in a RenderClipRect.
485///
486/// The child is positioned according to [alignment]. To position a smaller
487/// child inside a larger parent, use [RenderPositionedBox] and
488/// [RenderConstrainedBox] rather than RenderConstrainedOverflowBox.
489///
490/// See also:
491///
492///  * [RenderUnconstrainedBox] for a render object that allows its children
493///    to render themselves unconstrained, expands to fit them, and considers
494///    overflow to be an error.
495///  * [RenderSizedOverflowBox], a render object that is a specific size but
496///    passes its original constraints through to its child, which it allows to
497///    overflow.
498class RenderConstrainedOverflowBox extends RenderAligningShiftedBox {
499  /// Creates a render object that lets its child overflow itself.
500  RenderConstrainedOverflowBox({
501    RenderBox child,
502    double minWidth,
503    double maxWidth,
504    double minHeight,
505    double maxHeight,
506    AlignmentGeometry alignment = Alignment.center,
507    TextDirection textDirection,
508  }) : _minWidth = minWidth,
509       _maxWidth = maxWidth,
510       _minHeight = minHeight,
511       _maxHeight = maxHeight,
512       super(child: child, alignment: alignment, textDirection: textDirection);
513
514  /// The minimum width constraint to give the child. Set this to null (the
515  /// default) to use the constraint from the parent instead.
516  double get minWidth => _minWidth;
517  double _minWidth;
518  set minWidth(double value) {
519    if (_minWidth == value)
520      return;
521    _minWidth = value;
522    markNeedsLayout();
523  }
524
525  /// The maximum width constraint to give the child. Set this to null (the
526  /// default) to use the constraint from the parent instead.
527  double get maxWidth => _maxWidth;
528  double _maxWidth;
529  set maxWidth(double value) {
530    if (_maxWidth == value)
531      return;
532    _maxWidth = value;
533    markNeedsLayout();
534  }
535
536  /// The minimum height constraint to give the child. Set this to null (the
537  /// default) to use the constraint from the parent instead.
538  double get minHeight => _minHeight;
539  double _minHeight;
540  set minHeight(double value) {
541    if (_minHeight == value)
542      return;
543    _minHeight = value;
544    markNeedsLayout();
545  }
546
547  /// The maximum height constraint to give the child. Set this to null (the
548  /// default) to use the constraint from the parent instead.
549  double get maxHeight => _maxHeight;
550  double _maxHeight;
551  set maxHeight(double value) {
552    if (_maxHeight == value)
553      return;
554    _maxHeight = value;
555    markNeedsLayout();
556  }
557
558  BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
559    return BoxConstraints(
560      minWidth: _minWidth ?? constraints.minWidth,
561      maxWidth: _maxWidth ?? constraints.maxWidth,
562      minHeight: _minHeight ?? constraints.minHeight,
563      maxHeight: _maxHeight ?? constraints.maxHeight,
564    );
565  }
566
567  @override
568  bool get sizedByParent => true;
569
570  @override
571  void performResize() {
572    size = constraints.biggest;
573  }
574
575  @override
576  void performLayout() {
577    if (child != null) {
578      child.layout(_getInnerConstraints(constraints), parentUsesSize: true);
579      alignChild();
580    }
581  }
582
583  @override
584  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
585    super.debugFillProperties(properties);
586    properties.add(DoubleProperty('minWidth', minWidth, ifNull: 'use parent minWidth constraint'));
587    properties.add(DoubleProperty('maxWidth', maxWidth, ifNull: 'use parent maxWidth constraint'));
588    properties.add(DoubleProperty('minHeight', minHeight, ifNull: 'use parent minHeight constraint'));
589    properties.add(DoubleProperty('maxHeight', maxHeight, ifNull: 'use parent maxHeight constraint'));
590  }
591}
592
593/// Renders a box, imposing no constraints on its child, allowing the child to
594/// render at its "natural" size.
595///
596/// This allows a child to render at the size it would render if it were alone
597/// on an infinite canvas with no constraints. This box will then attempt to
598/// adopt the same size, within the limits of its own constraints. If it ends
599/// up with a different size, it will align the child based on [alignment].
600/// If the box cannot expand enough to accommodate the entire child, the
601/// child will be clipped.
602///
603/// In debug mode, if the child overflows the box, a warning will be printed on
604/// the console, and black and yellow striped areas will appear where the
605/// overflow occurs.
606///
607/// See also:
608///
609///  * [RenderConstrainedBox], which renders a box which imposes constraints
610///    on its child.
611///  * [RenderConstrainedOverflowBox], which renders a box that imposes different
612///    constraints on its child than it gets from its parent, possibly allowing
613///    the child to overflow the parent.
614///  * [RenderSizedOverflowBox], a render object that is a specific size but
615///    passes its original constraints through to its child, which it allows to
616///    overflow.
617class RenderUnconstrainedBox extends RenderAligningShiftedBox with DebugOverflowIndicatorMixin {
618  /// Create a render object that sizes itself to the child but does not
619  /// pass the [constraints] down to that child.
620  ///
621  /// The [alignment] must not be null.
622  RenderUnconstrainedBox({
623    @required AlignmentGeometry alignment,
624    @required TextDirection textDirection,
625    Axis constrainedAxis,
626    RenderBox child,
627  }) : assert(alignment != null),
628       _constrainedAxis = constrainedAxis,
629       super.mixin(alignment, textDirection, child);
630
631  /// The axis to retain constraints on, if any.
632  ///
633  /// If not set, or set to null (the default), neither axis will retain its
634  /// constraints. If set to [Axis.vertical], then vertical constraints will
635  /// be retained, and if set to [Axis.horizontal], then horizontal constraints
636  /// will be retained.
637  Axis get constrainedAxis => _constrainedAxis;
638  Axis _constrainedAxis;
639  set constrainedAxis(Axis value) {
640    if (_constrainedAxis == value)
641      return;
642    _constrainedAxis = value;
643    markNeedsLayout();
644  }
645
646  Rect _overflowContainerRect = Rect.zero;
647  Rect _overflowChildRect = Rect.zero;
648  bool _isOverflowing = false;
649
650  @override
651  void performLayout() {
652    if (child != null) {
653      // Let the child lay itself out at it's "natural" size, but if
654      // constrainedAxis is non-null, keep any constraints on that axis.
655      BoxConstraints childConstraints;
656      if (constrainedAxis != null) {
657        switch (constrainedAxis) {
658          case Axis.horizontal:
659            childConstraints = BoxConstraints(maxWidth: constraints.maxWidth, minWidth: constraints.minWidth);
660            break;
661          case Axis.vertical:
662            childConstraints = BoxConstraints(maxHeight: constraints.maxHeight, minHeight: constraints.minHeight);
663            break;
664        }
665      } else {
666        childConstraints = const BoxConstraints();
667      }
668      child.layout(childConstraints, parentUsesSize: true);
669      size = constraints.constrain(child.size);
670      alignChild();
671      final BoxParentData childParentData = child.parentData;
672      _overflowContainerRect = Offset.zero & size;
673      _overflowChildRect = childParentData.offset & child.size;
674    } else {
675      size = constraints.smallest;
676      _overflowContainerRect = Rect.zero;
677      _overflowChildRect = Rect.zero;
678    }
679    _isOverflowing = RelativeRect.fromRect(_overflowContainerRect, _overflowChildRect).hasInsets;
680  }
681
682  @override
683  void paint(PaintingContext context, Offset offset) {
684    // There's no point in drawing the child if we're empty, or there is no
685    // child.
686    if (child == null || size.isEmpty)
687      return;
688
689    if (!_isOverflowing) {
690      super.paint(context, offset);
691      return;
692    }
693
694    // We have overflow. Clip it.
695    context.pushClipRect(needsCompositing, offset, Offset.zero & size, super.paint);
696
697    // Display the overflow indicator.
698    assert(() {
699      paintOverflowIndicator(context, offset, _overflowContainerRect, _overflowChildRect);
700      return true;
701    }());
702  }
703
704  @override
705  Rect describeApproximatePaintClip(RenderObject child) {
706    return _isOverflowing ? Offset.zero & size : null;
707  }
708
709  @override
710  String toStringShort() {
711    String header = super.toStringShort();
712    if (_isOverflowing)
713      header += ' OVERFLOWING';
714    return header;
715  }
716}
717
718/// A render object that is a specific size but passes its original constraints
719/// through to its child, which it allows to overflow.
720///
721/// If the child's resulting size differs from this render object's size, then
722/// the child is aligned according to the [alignment] property.
723///
724/// See also:
725///
726///  * [RenderUnconstrainedBox] for a render object that allows its children
727///    to render themselves unconstrained, expands to fit them, and considers
728///    overflow to be an error.
729///  * [RenderConstrainedOverflowBox] for a render object that imposes
730///    different constraints on its child than it gets from its parent,
731///    possibly allowing the child to overflow the parent.
732class RenderSizedOverflowBox extends RenderAligningShiftedBox {
733  /// Creates a render box of a given size that lets its child overflow.
734  ///
735  /// The [requestedSize] and [alignment] arguments must not be null.
736  ///
737  /// The [textDirection] argument must not be null if the [alignment] is
738  /// direction-sensitive.
739  RenderSizedOverflowBox({
740    RenderBox child,
741    @required Size requestedSize,
742    AlignmentGeometry alignment = Alignment.center,
743    TextDirection textDirection,
744  }) : assert(requestedSize != null),
745       _requestedSize = requestedSize,
746       super(child: child, alignment: alignment, textDirection: textDirection);
747
748  /// The size this render box should attempt to be.
749  Size get requestedSize => _requestedSize;
750  Size _requestedSize;
751  set requestedSize(Size value) {
752    assert(value != null);
753    if (_requestedSize == value)
754      return;
755    _requestedSize = value;
756    markNeedsLayout();
757  }
758
759  @override
760  double computeMinIntrinsicWidth(double height) {
761    return _requestedSize.width;
762  }
763
764  @override
765  double computeMaxIntrinsicWidth(double height) {
766    return _requestedSize.width;
767  }
768
769  @override
770  double computeMinIntrinsicHeight(double width) {
771    return _requestedSize.height;
772  }
773
774  @override
775  double computeMaxIntrinsicHeight(double width) {
776    return _requestedSize.height;
777  }
778
779  @override
780  double computeDistanceToActualBaseline(TextBaseline baseline) {
781    if (child != null)
782      return child.getDistanceToActualBaseline(baseline);
783    return super.computeDistanceToActualBaseline(baseline);
784  }
785
786  @override
787  void performLayout() {
788    size = constraints.constrain(_requestedSize);
789    if (child != null) {
790      child.layout(constraints, parentUsesSize: true);
791      alignChild();
792    }
793  }
794}
795
796/// Sizes its child to a fraction of the total available space.
797///
798/// For both its width and height, this render object imposes a tight
799/// constraint on its child that is a multiple (typically less than 1.0) of the
800/// maximum constraint it received from its parent on that axis. If the factor
801/// for a given axis is null, then the constraints from the parent are just
802/// passed through instead.
803///
804/// It then tries to size itself to the size of its child. Where this is not
805/// possible (e.g. if the constraints from the parent are themselves tight), the
806/// child is aligned according to [alignment].
807class RenderFractionallySizedOverflowBox extends RenderAligningShiftedBox {
808  /// Creates a render box that sizes its child to a fraction of the total available space.
809  ///
810  /// If non-null, the [widthFactor] and [heightFactor] arguments must be
811  /// non-negative.
812  ///
813  /// The [alignment] must not be null.
814  ///
815  /// The [textDirection] must be non-null if the [alignment] is
816  /// direction-sensitive.
817  RenderFractionallySizedOverflowBox({
818    RenderBox child,
819    double widthFactor,
820    double heightFactor,
821    AlignmentGeometry alignment = Alignment.center,
822    TextDirection textDirection,
823  }) : _widthFactor = widthFactor,
824       _heightFactor = heightFactor,
825       super(child: child, alignment: alignment, textDirection: textDirection) {
826    assert(_widthFactor == null || _widthFactor >= 0.0);
827    assert(_heightFactor == null || _heightFactor >= 0.0);
828  }
829
830  /// If non-null, the factor of the incoming width to use.
831  ///
832  /// If non-null, the child is given a tight width constraint that is the max
833  /// incoming width constraint multiplied by this factor. If null, the child is
834  /// given the incoming width constraints.
835  double get widthFactor => _widthFactor;
836  double _widthFactor;
837  set widthFactor(double value) {
838    assert(value == null || value >= 0.0);
839    if (_widthFactor == value)
840      return;
841    _widthFactor = value;
842    markNeedsLayout();
843  }
844
845  /// If non-null, the factor of the incoming height to use.
846  ///
847  /// If non-null, the child is given a tight height constraint that is the max
848  /// incoming width constraint multiplied by this factor. If null, the child is
849  /// given the incoming width constraints.
850  double get heightFactor => _heightFactor;
851  double _heightFactor;
852  set heightFactor(double value) {
853    assert(value == null || value >= 0.0);
854    if (_heightFactor == value)
855      return;
856    _heightFactor = value;
857    markNeedsLayout();
858  }
859
860  BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
861    double minWidth = constraints.minWidth;
862    double maxWidth = constraints.maxWidth;
863    if (_widthFactor != null) {
864      final double width = maxWidth * _widthFactor;
865      minWidth = width;
866      maxWidth = width;
867    }
868    double minHeight = constraints.minHeight;
869    double maxHeight = constraints.maxHeight;
870    if (_heightFactor != null) {
871      final double height = maxHeight * _heightFactor;
872      minHeight = height;
873      maxHeight = height;
874    }
875    return BoxConstraints(
876      minWidth: minWidth,
877      maxWidth: maxWidth,
878      minHeight: minHeight,
879      maxHeight: maxHeight,
880    );
881  }
882
883  @override
884  double computeMinIntrinsicWidth(double height) {
885    double result;
886    if (child == null) {
887      result = super.computeMinIntrinsicWidth(height);
888    } else { // the following line relies on double.infinity absorption
889      result = child.getMinIntrinsicWidth(height * (_heightFactor ?? 1.0));
890    }
891    assert(result.isFinite);
892    return result / (_widthFactor ?? 1.0);
893  }
894
895  @override
896  double computeMaxIntrinsicWidth(double height) {
897    double result;
898    if (child == null) {
899      result = super.computeMaxIntrinsicWidth(height);
900    } else { // the following line relies on double.infinity absorption
901      result = child.getMaxIntrinsicWidth(height * (_heightFactor ?? 1.0));
902    }
903    assert(result.isFinite);
904    return result / (_widthFactor ?? 1.0);
905  }
906
907  @override
908  double computeMinIntrinsicHeight(double width) {
909    double result;
910    if (child == null) {
911      result = super.computeMinIntrinsicHeight(width);
912    } else { // the following line relies on double.infinity absorption
913      result = child.getMinIntrinsicHeight(width * (_widthFactor ?? 1.0));
914    }
915    assert(result.isFinite);
916    return result / (_heightFactor ?? 1.0);
917  }
918
919  @override
920  double computeMaxIntrinsicHeight(double width) {
921    double result;
922    if (child == null) {
923      result = super.computeMaxIntrinsicHeight(width);
924    } else { // the following line relies on double.infinity absorption
925      result = child.getMaxIntrinsicHeight(width * (_widthFactor ?? 1.0));
926    }
927    assert(result.isFinite);
928    return result / (_heightFactor ?? 1.0);
929  }
930
931  @override
932  void performLayout() {
933    if (child != null) {
934      child.layout(_getInnerConstraints(constraints), parentUsesSize: true);
935      size = constraints.constrain(child.size);
936      alignChild();
937    } else {
938      size = constraints.constrain(_getInnerConstraints(constraints).constrain(Size.zero));
939    }
940  }
941
942  @override
943  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
944    super.debugFillProperties(properties);
945    properties.add(DoubleProperty('widthFactor', _widthFactor, ifNull: 'pass-through'));
946    properties.add(DoubleProperty('heightFactor', _heightFactor, ifNull: 'pass-through'));
947  }
948}
949
950/// A delegate for computing the layout of a render object with a single child.
951///
952/// Used by [CustomSingleChildLayout] (in the widgets library) and
953/// [RenderCustomSingleChildLayoutBox] (in the rendering library).
954///
955/// When asked to layout, [CustomSingleChildLayout] first calls [getSize] with
956/// its incoming constraints to determine its size. It then calls
957/// [getConstraintsForChild] to determine the constraints to apply to the child.
958/// After the child completes its layout, [RenderCustomSingleChildLayoutBox]
959/// calls [getPositionForChild] to determine the child's position.
960///
961/// The [shouldRelayout] method is called when a new instance of the class
962/// is provided, to check if the new instance actually represents different
963/// information.
964///
965/// The most efficient way to trigger a relayout is to supply a relayout
966/// argument to the constructor of the [SingleChildLayoutDelegate]. The custom
967/// object will listen to this value and relayout whenever the animation
968/// ticks, avoiding both the build phase of the pipeline.
969///
970/// See also:
971///
972///  * [CustomSingleChildLayout], the widget that uses this delegate.
973///  * [RenderCustomSingleChildLayoutBox], render object that uses this
974///    delegate.
975abstract class SingleChildLayoutDelegate {
976  /// Creates a layout delegate.
977  ///
978  /// The layout will update whenever [relayout] notifies its listeners.
979  const SingleChildLayoutDelegate({ Listenable relayout }) : _relayout = relayout;
980
981  final Listenable _relayout;
982
983  /// The size of this object given the incoming constraints.
984  ///
985  /// Defaults to the biggest size that satisfies the given constraints.
986  Size getSize(BoxConstraints constraints) => constraints.biggest;
987
988  /// The constraints for the child given the incoming constraints.
989  ///
990  /// During layout, the child is given the layout constraints returned by this
991  /// function. The child is required to pick a size for itself that satisfies
992  /// these constraints.
993  ///
994  /// Defaults to the given constraints.
995  BoxConstraints getConstraintsForChild(BoxConstraints constraints) => constraints;
996
997  /// The position where the child should be placed.
998  ///
999  /// The `size` argument is the size of the parent, which might be different
1000  /// from the value returned by [getSize] if that size doesn't satisfy the
1001  /// constraints passed to [getSize]. The `childSize` argument is the size of
1002  /// the child, which will satisfy the constraints returned by
1003  /// [getConstraintsForChild].
1004  ///
1005  /// Defaults to positioning the child in the upper left corner of the parent.
1006  Offset getPositionForChild(Size size, Size childSize) => Offset.zero;
1007
1008  /// Called whenever a new instance of the custom layout delegate class is
1009  /// provided to the [RenderCustomSingleChildLayoutBox] object, or any time
1010  /// that a new [CustomSingleChildLayout] object is created with a new instance
1011  /// of the custom layout delegate class (which amounts to the same thing,
1012  /// because the latter is implemented in terms of the former).
1013  ///
1014  /// If the new instance represents different information than the old
1015  /// instance, then the method should return true, otherwise it should return
1016  /// false.
1017  ///
1018  /// If the method returns false, then the [getSize],
1019  /// [getConstraintsForChild], and [getPositionForChild] calls might be
1020  /// optimized away.
1021  ///
1022  /// It's possible that the layout methods will get called even if
1023  /// [shouldRelayout] returns false (e.g. if an ancestor changed its layout).
1024  /// It's also possible that the layout method will get called
1025  /// without [shouldRelayout] being called at all (e.g. if the parent changes
1026  /// size).
1027  bool shouldRelayout(covariant SingleChildLayoutDelegate oldDelegate);
1028}
1029
1030/// Defers the layout of its single child to a delegate.
1031///
1032/// The delegate can determine the layout constraints for the child and can
1033/// decide where to position the child. The delegate can also determine the size
1034/// of the parent, but the size of the parent cannot depend on the size of the
1035/// child.
1036class RenderCustomSingleChildLayoutBox extends RenderShiftedBox {
1037  /// Creates a render box that defers its layout to a delegate.
1038  ///
1039  /// The [delegate] argument must not be null.
1040  RenderCustomSingleChildLayoutBox({
1041    RenderBox child,
1042    @required SingleChildLayoutDelegate delegate,
1043  }) : assert(delegate != null),
1044       _delegate = delegate,
1045       super(child);
1046
1047  /// A delegate that controls this object's layout.
1048  SingleChildLayoutDelegate get delegate => _delegate;
1049  SingleChildLayoutDelegate _delegate;
1050  set delegate(SingleChildLayoutDelegate newDelegate) {
1051    assert(newDelegate != null);
1052    if (_delegate == newDelegate)
1053      return;
1054    final SingleChildLayoutDelegate oldDelegate = _delegate;
1055    if (newDelegate.runtimeType != oldDelegate.runtimeType || newDelegate.shouldRelayout(oldDelegate))
1056      markNeedsLayout();
1057    _delegate = newDelegate;
1058    if (attached) {
1059      oldDelegate?._relayout?.removeListener(markNeedsLayout);
1060      newDelegate?._relayout?.addListener(markNeedsLayout);
1061    }
1062  }
1063
1064  @override
1065  void attach(PipelineOwner owner) {
1066    super.attach(owner);
1067    _delegate?._relayout?.addListener(markNeedsLayout);
1068  }
1069
1070  @override
1071  void detach() {
1072    _delegate?._relayout?.removeListener(markNeedsLayout);
1073    super.detach();
1074  }
1075
1076  Size _getSize(BoxConstraints constraints) {
1077    return constraints.constrain(_delegate.getSize(constraints));
1078  }
1079
1080  // TODO(ianh): It's a bit dubious to be using the getSize function from the delegate to
1081  // figure out the intrinsic dimensions. We really should either not support intrinsics,
1082  // or we should expose intrinsic delegate callbacks and throw if they're not implemented.
1083
1084  @override
1085  double computeMinIntrinsicWidth(double height) {
1086    final double width = _getSize(BoxConstraints.tightForFinite(height: height)).width;
1087    if (width.isFinite)
1088      return width;
1089    return 0.0;
1090  }
1091
1092  @override
1093  double computeMaxIntrinsicWidth(double height) {
1094    final double width = _getSize(BoxConstraints.tightForFinite(height: height)).width;
1095    if (width.isFinite)
1096      return width;
1097    return 0.0;
1098  }
1099
1100  @override
1101  double computeMinIntrinsicHeight(double width) {
1102    final double height = _getSize(BoxConstraints.tightForFinite(width: width)).height;
1103    if (height.isFinite)
1104      return height;
1105    return 0.0;
1106  }
1107
1108  @override
1109  double computeMaxIntrinsicHeight(double width) {
1110    final double height = _getSize(BoxConstraints.tightForFinite(width: width)).height;
1111    if (height.isFinite)
1112      return height;
1113    return 0.0;
1114  }
1115
1116  @override
1117  void performLayout() {
1118    size = _getSize(constraints);
1119    if (child != null) {
1120      final BoxConstraints childConstraints = delegate.getConstraintsForChild(constraints);
1121      assert(childConstraints.debugAssertIsValid(isAppliedConstraint: true));
1122      child.layout(childConstraints, parentUsesSize: !childConstraints.isTight);
1123      final BoxParentData childParentData = child.parentData;
1124      childParentData.offset = delegate.getPositionForChild(size, childConstraints.isTight ? childConstraints.smallest : child.size);
1125    }
1126  }
1127}
1128
1129/// Shifts the child down such that the child's baseline (or the
1130/// bottom of the child, if the child has no baseline) is [baseline]
1131/// logical pixels below the top of this box, then sizes this box to
1132/// contain the child.
1133///
1134/// If [baseline] is less than the distance from the top of the child
1135/// to the baseline of the child, then the child will overflow the top
1136/// of the box. This is typically not desirable, in particular, that
1137/// part of the child will not be found when doing hit tests, so the
1138/// user cannot interact with that part of the child.
1139///
1140/// This box will be sized so that its bottom is coincident with the
1141/// bottom of the child. This means if this box shifts the child down,
1142/// there will be space between the top of this box and the top of the
1143/// child, but there is never space between the bottom of the child
1144/// and the bottom of the box.
1145class RenderBaseline extends RenderShiftedBox {
1146  /// Creates a [RenderBaseline] object.
1147  ///
1148  /// The [baseline] and [baselineType] arguments must not be null.
1149  RenderBaseline({
1150    RenderBox child,
1151    @required double baseline,
1152    @required TextBaseline baselineType,
1153  }) : assert(baseline != null),
1154       assert(baselineType != null),
1155       _baseline = baseline,
1156       _baselineType = baselineType,
1157       super(child);
1158
1159  /// The number of logical pixels from the top of this box at which to position
1160  /// the child's baseline.
1161  double get baseline => _baseline;
1162  double _baseline;
1163  set baseline(double value) {
1164    assert(value != null);
1165    if (_baseline == value)
1166      return;
1167    _baseline = value;
1168    markNeedsLayout();
1169  }
1170
1171  /// The type of baseline to use for positioning the child.
1172  TextBaseline get baselineType => _baselineType;
1173  TextBaseline _baselineType;
1174  set baselineType(TextBaseline value) {
1175    assert(value != null);
1176    if (_baselineType == value)
1177      return;
1178    _baselineType = value;
1179    markNeedsLayout();
1180  }
1181
1182  @override
1183  void performLayout() {
1184    if (child != null) {
1185      child.layout(constraints.loosen(), parentUsesSize: true);
1186      final double childBaseline = child.getDistanceToBaseline(baselineType);
1187      final double actualBaseline = baseline;
1188      final double top = actualBaseline - childBaseline;
1189      final BoxParentData childParentData = child.parentData;
1190      childParentData.offset = Offset(0.0, top);
1191      final Size childSize = child.size;
1192      size = constraints.constrain(Size(childSize.width, top + childSize.height));
1193    } else {
1194      performResize();
1195    }
1196  }
1197
1198  @override
1199  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
1200    super.debugFillProperties(properties);
1201    properties.add(DoubleProperty('baseline', baseline));
1202    properties.add(EnumProperty<TextBaseline>('baselineType', baselineType));
1203  }
1204}
1205