// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:math' as math; import 'package:flutter/foundation.dart'; import 'box.dart'; import 'debug.dart'; import 'debug_overflow_indicator.dart'; import 'layer.dart'; import 'object.dart'; import 'stack.dart' show RelativeRect; /// Signature for a function that transforms a [BoxConstraints] to another /// [BoxConstraints]. /// /// Used by [RenderConstraintsTransformBox] and [ConstraintsTransformBox]. /// Typically the caller requires the returned [BoxConstraints] to be /// [BoxConstraints.isNormalized]. typedef BoxConstraintsTransform = BoxConstraints Function(BoxConstraints); /// Abstract class for one-child-layout render boxes that provide control over /// the child's position. abstract class RenderShiftedBox extends RenderBox with RenderObjectWithChildMixin<RenderBox> { /// Initializes the [child] property for subclasses. RenderShiftedBox(RenderBox? child) { this.child = child; } @override double computeMinIntrinsicWidth(double height) { if (child != null) return child!.getMinIntrinsicWidth(height); return 0.0; } @override double computeMaxIntrinsicWidth(double height) { if (child != null) return child!.getMaxIntrinsicWidth(height); return 0.0; } @override double computeMinIntrinsicHeight(double width) { if (child != null) return child!.getMinIntrinsicHeight(width); return 0.0; } @override double computeMaxIntrinsicHeight(double width) { if (child != null) return child!.getMaxIntrinsicHeight(width); return 0.0; } @override double? computeDistanceToActualBaseline(TextBaseline baseline) { double? result; if (child != null) { assert(!debugNeedsLayout); result = child!.getDistanceToActualBaseline(baseline); final BoxParentData childParentData = child!.parentData! as BoxParentData; if (result != null) result += childParentData.offset.dy; } else { result = super.computeDistanceToActualBaseline(baseline); } return result; } @override void paint(PaintingContext context, Offset offset) { if (child != null) { final BoxParentData childParentData = child!.parentData! as BoxParentData; context.paintChild(child!, childParentData.offset + offset); } } @override bool hitTestChildren(BoxHitTestResult result, { required Offset position }) { if (child != null) { final BoxParentData childParentData = child!.parentData! as BoxParentData; return result.addWithPaintOffset( offset: childParentData.offset, position: position, hitTest: (BoxHitTestResult result, Offset? transformed) { assert(transformed == position - childParentData.offset); return child!.hitTest(result, position: transformed!); }, ); } return false; } } /// 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. class RenderPadding extends RenderShiftedBox { /// Creates a render object that insets its child. /// /// The [padding] argument must not be null and must have non-negative insets. RenderPadding({ required EdgeInsetsGeometry padding, TextDirection? textDirection, RenderBox? child, }) : assert(padding != null), assert(padding.isNonNegative), _textDirection = textDirection, _padding = padding, super(child); EdgeInsets? _resolvedPadding; void _resolve() { if (_resolvedPadding != null) return; _resolvedPadding = padding.resolve(textDirection); assert(_resolvedPadding!.isNonNegative); } void _markNeedResolution() { _resolvedPadding = null; markNeedsLayout(); } /// The amount to pad the child in each dimension. /// /// If this is set to an [EdgeInsetsDirectional] object, then [textDirection] /// must not be null. EdgeInsetsGeometry get padding => _padding; EdgeInsetsGeometry _padding; set padding(EdgeInsetsGeometry value) { assert(value != null); assert(value.isNonNegative); if (_padding == value) return; _padding = value; _markNeedResolution(); } /// The text direction with which to resolve [padding]. /// /// This may be changed to null, but only after the [padding] has been changed /// to a value that does not depend on the direction. TextDirection? get textDirection => _textDirection; TextDirection? _textDirection; set textDirection(TextDirection? value) { if (_textDirection == value) return; _textDirection = value; _markNeedResolution(); } @override double computeMinIntrinsicWidth(double height) { _resolve(); final double totalHorizontalPadding = _resolvedPadding!.left + _resolvedPadding!.right; final double totalVerticalPadding = _resolvedPadding!.top + _resolvedPadding!.bottom; if (child != null) // next line relies on double.infinity absorption return child!.getMinIntrinsicWidth(math.max(0.0, height - totalVerticalPadding)) + totalHorizontalPadding; return totalHorizontalPadding; } @override double computeMaxIntrinsicWidth(double height) { _resolve(); final double totalHorizontalPadding = _resolvedPadding!.left + _resolvedPadding!.right; final double totalVerticalPadding = _resolvedPadding!.top + _resolvedPadding!.bottom; if (child != null) // next line relies on double.infinity absorption return child!.getMaxIntrinsicWidth(math.max(0.0, height - totalVerticalPadding)) + totalHorizontalPadding; return totalHorizontalPadding; } @override double computeMinIntrinsicHeight(double width) { _resolve(); final double totalHorizontalPadding = _resolvedPadding!.left + _resolvedPadding!.right; final double totalVerticalPadding = _resolvedPadding!.top + _resolvedPadding!.bottom; if (child != null) // next line relies on double.infinity absorption return child!.getMinIntrinsicHeight(math.max(0.0, width - totalHorizontalPadding)) + totalVerticalPadding; return totalVerticalPadding; } @override double computeMaxIntrinsicHeight(double width) { _resolve(); final double totalHorizontalPadding = _resolvedPadding!.left + _resolvedPadding!.right; final double totalVerticalPadding = _resolvedPadding!.top + _resolvedPadding!.bottom; if (child != null) // next line relies on double.infinity absorption return child!.getMaxIntrinsicHeight(math.max(0.0, width - totalHorizontalPadding)) + totalVerticalPadding; return totalVerticalPadding; } @override Size computeDryLayout(BoxConstraints constraints) { _resolve(); assert(_resolvedPadding != null); if (child == null) { return constraints.constrain(Size( _resolvedPadding!.left + _resolvedPadding!.right, _resolvedPadding!.top + _resolvedPadding!.bottom, )); } final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding!); final Size childSize = child!.getDryLayout(innerConstraints); return constraints.constrain(Size( _resolvedPadding!.left + childSize.width + _resolvedPadding!.right, _resolvedPadding!.top + childSize.height + _resolvedPadding!.bottom, )); } @override void performLayout() { final BoxConstraints constraints = this.constraints; _resolve(); assert(_resolvedPadding != null); if (child == null) { size = constraints.constrain(Size( _resolvedPadding!.left + _resolvedPadding!.right, _resolvedPadding!.top + _resolvedPadding!.bottom, )); return; } final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding!); child!.layout(innerConstraints, parentUsesSize: true); final BoxParentData childParentData = child!.parentData! as BoxParentData; childParentData.offset = Offset(_resolvedPadding!.left, _resolvedPadding!.top); size = constraints.constrain(Size( _resolvedPadding!.left + child!.size.width + _resolvedPadding!.right, _resolvedPadding!.top + child!.size.height + _resolvedPadding!.bottom, )); } @override void debugPaintSize(PaintingContext context, Offset offset) { super.debugPaintSize(context, offset); assert(() { final Rect outerRect = offset & size; debugPaintPadding(context.canvas, outerRect, child != null ? _resolvedPadding!.deflateRect(outerRect) : null); return true; }()); } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding)); properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null)); } } /// Abstract class for one-child-layout render boxes that use a /// [AlignmentGeometry] to align their children. abstract class RenderAligningShiftedBox extends RenderShiftedBox { /// Initializes member variables for subclasses. /// /// The [alignment] argument must not be null. /// /// The [textDirection] must be non-null if the [alignment] is /// direction-sensitive. RenderAligningShiftedBox({ AlignmentGeometry alignment = Alignment.center, required TextDirection? textDirection, RenderBox? child, }) : assert(alignment != null), _alignment = alignment, _textDirection = textDirection, super(child); /// A constructor to be used only when the extending class also has a mixin. // TODO(gspencer): Remove this constructor once https://github.com/dart-lang/sdk/issues/31543 is fixed. @protected RenderAligningShiftedBox.mixin(AlignmentGeometry alignment, TextDirection? textDirection, RenderBox? child) : this(alignment: alignment, textDirection: textDirection, child: child); Alignment? _resolvedAlignment; void _resolve() { if (_resolvedAlignment != null) return; _resolvedAlignment = alignment.resolve(textDirection); } void _markNeedResolution() { _resolvedAlignment = null; markNeedsLayout(); } /// How to align the child. /// /// The x and y values of the alignment control the horizontal and vertical /// alignment, respectively. An x value of -1.0 means that the left edge of /// 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. /// For example, a value of 0.0 means that the center of the child is aligned /// with the center of the parent. /// /// If this is set to an [AlignmentDirectional] object, then /// [textDirection] must not be null. AlignmentGeometry get alignment => _alignment; AlignmentGeometry _alignment; /// Sets the alignment to a new value, and triggers a layout update. /// /// The new alignment must not be null. set alignment(AlignmentGeometry value) { assert(value != null); if (_alignment == value) return; _alignment = value; _markNeedResolution(); } /// The text direction with which to resolve [alignment]. /// /// This may be changed to null, but only after [alignment] has been changed /// to a value that does not depend on the direction. TextDirection? get textDirection => _textDirection; TextDirection? _textDirection; set textDirection(TextDirection? value) { if (_textDirection == value) return; _textDirection = value; _markNeedResolution(); } /// 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. @protected void alignChild() { _resolve(); assert(child != null); assert(!child!.debugNeedsLayout); assert(child!.hasSize); assert(hasSize); assert(_resolvedAlignment != null); final BoxParentData childParentData = child!.parentData! as BoxParentData; childParentData.offset = _resolvedAlignment!.alongOffset(size - child!.size as Offset); } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment)); properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null)); } } /// Positions its child using an [AlignmentGeometry]. /// /// 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, /// with an alignment of [Alignment.bottomRight]. /// /// 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 { /// Creates a render object that positions its child. RenderPositionedBox({ RenderBox? child, double? widthFactor, double? heightFactor, AlignmentGeometry alignment = Alignment.center, TextDirection? textDirection, }) : assert(widthFactor == null || widthFactor >= 0.0), assert(heightFactor == null || heightFactor >= 0.0), _widthFactor = widthFactor, _heightFactor = heightFactor, super(child: child, alignment: alignment, textDirection: textDirection); /// If non-null, sets its width to the child's width multiplied by this factor. /// /// Can be both greater and less than 1.0 but must be positive. double? get widthFactor => _widthFactor; double? _widthFactor; set widthFactor(double? value) { assert(value == null || value >= 0.0); if (_widthFactor == value) return; _widthFactor = value; markNeedsLayout(); } /// If non-null, sets its height to the child's height multiplied by this factor. /// /// Can be both greater and less than 1.0 but must be positive. double? get heightFactor => _heightFactor; double? _heightFactor; set heightFactor(double? value) { assert(value == null || value >= 0.0); if (_heightFactor == value) return; _heightFactor = value; markNeedsLayout(); } @override Size computeDryLayout(BoxConstraints constraints) { final bool shrinkWrapWidth = _widthFactor != null || constraints.maxWidth == double.infinity; final bool shrinkWrapHeight = _heightFactor != null || constraints.maxHeight == double.infinity; if (child != null) { final Size childSize = child!.getDryLayout(constraints.loosen()); return constraints.constrain(Size( shrinkWrapWidth ? childSize.width * (_widthFactor ?? 1.0) : double.infinity, shrinkWrapHeight ? childSize.height * (_heightFactor ?? 1.0) : double.infinity, )); } return constraints.constrain(Size( shrinkWrapWidth ? 0.0 : double.infinity, shrinkWrapHeight ? 0.0 : double.infinity, )); } @override void performLayout() { final BoxConstraints constraints = this.constraints; final bool shrinkWrapWidth = _widthFactor != null || constraints.maxWidth == double.infinity; final bool shrinkWrapHeight = _heightFactor != null || constraints.maxHeight == double.infinity; if (child != null) { child!.layout(constraints.loosen(), parentUsesSize: true); size = constraints.constrain(Size( shrinkWrapWidth ? child!.size.width * (_widthFactor ?? 1.0) : double.infinity, shrinkWrapHeight ? child!.size.height * (_heightFactor ?? 1.0) : double.infinity, )); alignChild(); } else { size = constraints.constrain(Size( shrinkWrapWidth ? 0.0 : double.infinity, shrinkWrapHeight ? 0.0 : double.infinity, )); } } @override void debugPaintSize(PaintingContext context, Offset offset) { super.debugPaintSize(context, offset); assert(() { final Paint paint; if (child != null && !child!.size.isEmpty) { final Path path; paint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = 1.0 ..color = const Color(0xFFFFFF00); path = Path(); final BoxParentData childParentData = child!.parentData! as BoxParentData; if (childParentData.offset.dy > 0.0) { // vertical alignment arrows final double headSize = math.min(childParentData.offset.dy * 0.2, 10.0); 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 final double headSize = math.min(childParentData.offset.dx * 0.2, 10.0); 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 { paint = Paint() ..color = const Color(0x90909090); context.canvas.drawRect(offset & size, paint); } return true; }()); } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DoubleProperty('widthFactor', _widthFactor, ifNull: 'expand')); properties.add(DoubleProperty('heightFactor', _heightFactor, ifNull: 'expand')); } } /// 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 /// 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. /// /// The child is positioned according to [alignment]. To position a smaller /// child inside a larger parent, use [RenderPositionedBox] and /// [RenderConstrainedBox] rather than RenderConstrainedOverflowBox. /// /// 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. class RenderConstrainedOverflowBox extends RenderAligningShiftedBox { /// Creates a render object that lets its child overflow itself. RenderConstrainedOverflowBox({ RenderBox? child, double? minWidth, double? maxWidth, double? minHeight, double? maxHeight, AlignmentGeometry alignment = Alignment.center, TextDirection? textDirection, }) : _minWidth = minWidth, _maxWidth = maxWidth, _minHeight = minHeight, _maxHeight = maxHeight, super(child: child, alignment: alignment, textDirection: textDirection); /// 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; set minWidth(double? value) { 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; set maxWidth(double? value) { 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; set minHeight(double? value) { 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; set maxHeight(double? value) { if (_maxHeight == value) return; _maxHeight = value; markNeedsLayout(); } BoxConstraints _getInnerConstraints(BoxConstraints constraints) { return BoxConstraints( minWidth: _minWidth ?? constraints.minWidth, maxWidth: _maxWidth ?? constraints.maxWidth, minHeight: _minHeight ?? constraints.minHeight, maxHeight: _maxHeight ?? constraints.maxHeight, ); } @override bool get sizedByParent => true; @override Size computeDryLayout(BoxConstraints constraints) { return constraints.biggest; } @override void performLayout() { if (child != null) { child?.layout(_getInnerConstraints(constraints), parentUsesSize: true); alignChild(); } } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); 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')); } } /// A [RenderBox] that applies an arbitrary transform to its [constraints] /// before sizing its child using the new constraints, treating any overflow as /// error. /// /// This [RenderBox] sizes its child using a [BoxConstraints] created by /// applying [constraintsTransform] to this [RenderBox]'s own [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 if [clipBehavior] is not /// [Clip.none]. /// /// In debug mode, if the child overflows the box, a warning will be printed on /// the console, and black and yellow striped areas will appear where the /// overflow occurs. /// /// When [child] is null, this [RenderBox] takes the smallest possible size and /// never overflows. /// /// This [RenderBox] can be used to ensure some of [child]'s natural dimensions /// are honored, and get an early warning during development otherwise. For /// instance, if [child] requires a minimum height to fully display its content, /// [constraintsTransform] can be set to a function that removes the `maxHeight` /// constraint from the incoming [BoxConstraints], so that if the parent /// [RenderObject] fails to provide enough vertical space, a warning will be /// displayed in debug mode, while still allowing [child] to grow vertically. /// /// See also: /// /// * [ConstraintsTransformBox], the widget that makes use of this /// [RenderObject] and exposes the same functionality. /// * [RenderConstrainedBox], which renders a box which imposes constraints /// on its child. /// * [RenderConstrainedOverflowBox], which renders a box that imposes different /// constraints on its child than it gets from its parent, possibly allowing /// the child to overflow the parent. /// * [RenderUnconstrainedBox] which allows its children to render themselves /// unconstrained, expands to fit them, and considers overflow to be an error. class RenderConstraintsTransformBox extends RenderAligningShiftedBox with DebugOverflowIndicatorMixin { /// Creates a [RenderBox] that sizes itself to the child and modifies the /// [constraints] before passing it down to that child. /// /// The [alignment] and [clipBehavior] must not be null. RenderConstraintsTransformBox({ required AlignmentGeometry alignment, required TextDirection? textDirection, required BoxConstraintsTransform constraintsTransform, RenderBox? child, Clip clipBehavior = Clip.none, }) : assert(alignment != null), assert(clipBehavior != null), assert(constraintsTransform != null), _constraintsTransform = constraintsTransform, _clipBehavior = clipBehavior, super.mixin(alignment, textDirection, child); /// {@macro flutter.widgets.constraintsTransform} BoxConstraintsTransform get constraintsTransform => _constraintsTransform; BoxConstraintsTransform _constraintsTransform; set constraintsTransform(BoxConstraintsTransform value) { if (_constraintsTransform == value) return; _constraintsTransform = value; // The RenderObject only needs layout if the new transform maps the current // `constraints` to a different value, or the render object has never been // laid out before. final bool needsLayout = _childConstraints == null || _childConstraints != value(constraints); if (needsLayout) markNeedsLayout(); } /// {@macro flutter.material.Material.clipBehavior} /// /// Defaults to [Clip.none], and must not be null. Clip get clipBehavior => _clipBehavior; Clip _clipBehavior; set clipBehavior(Clip value) { assert(value != null); if (value != _clipBehavior) { _clipBehavior = value; markNeedsPaint(); markNeedsSemanticsUpdate(); } } @override double computeMinIntrinsicHeight(double width) { return super.computeMinIntrinsicHeight( constraintsTransform(BoxConstraints(maxWidth: width)).maxWidth, ); } @override double computeMaxIntrinsicHeight(double width) { return super.computeMaxIntrinsicHeight( constraintsTransform(BoxConstraints(maxWidth: width)).maxWidth, ); } @override double computeMinIntrinsicWidth(double height) { return super.computeMinIntrinsicWidth( constraintsTransform(BoxConstraints(maxHeight: height)).maxHeight, ); } @override double computeMaxIntrinsicWidth(double height) { return super.computeMaxIntrinsicWidth( constraintsTransform(BoxConstraints(maxHeight: height)).maxHeight, ); } @override Size computeDryLayout(BoxConstraints constraints) { final Size? childSize = child?.getDryLayout(constraintsTransform(constraints)); return childSize == null ? constraints.smallest : constraints.constrain(childSize); } Rect _overflowContainerRect = Rect.zero; Rect _overflowChildRect = Rect.zero; bool _isOverflowing = false; BoxConstraints? _childConstraints; @override void performLayout() { final BoxConstraints constraints = this.constraints; final RenderBox? child = this.child; if (child != null) { final BoxConstraints childConstraints = constraintsTransform(constraints); assert(childConstraints != null); assert(childConstraints.isNormalized, '$childConstraints is not normalized'); _childConstraints = childConstraints; child.layout(childConstraints, parentUsesSize: true); size = constraints.constrain(child.size); alignChild(); final BoxParentData childParentData = child.parentData! as BoxParentData; _overflowContainerRect = Offset.zero & size; _overflowChildRect = childParentData.offset & child.size; } else { size = constraints.smallest; _overflowContainerRect = Rect.zero; _overflowChildRect = Rect.zero; } _isOverflowing = RelativeRect.fromRect(_overflowContainerRect, _overflowChildRect).hasInsets; } @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; } if (clipBehavior == Clip.none) { _clipRectLayer = null; super.paint(context, offset); } else { // We have overflow and the clipBehavior isn't none. Clip it. _clipRectLayer = context.pushClipRect( needsCompositing, offset, Offset.zero & size, super.paint, clipBehavior: clipBehavior, oldLayer:_clipRectLayer, ); } // Display the overflow indicator. assert(() { paintOverflowIndicator(context, offset, _overflowContainerRect, _overflowChildRect); return true; }()); } ClipRectLayer? _clipRectLayer; @override Rect? describeApproximatePaintClip(RenderObject child) { return _isOverflowing ? Offset.zero & size : null; } @override String toStringShort() { String header = super.toStringShort(); if (_isOverflowing) header += ' OVERFLOWING'; return header; } } /// Renders a box, imposing no constraints on its child, allowing the child to /// render at its "natural" size. /// /// The class is deprecated, use [RenderConstraintsTransformBox] instead. /// /// This allows a child to render at the size it would render if it were alone /// 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. /// /// In debug mode, if the child overflows the box, a warning will be printed on /// the console, and black and yellow striped areas will appear where the /// overflow occurs. /// /// See also: /// /// * [RenderConstrainedBox], which renders a box which imposes constraints /// on its child. /// * [RenderConstrainedOverflowBox], which renders a box that imposes different /// 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. /// @Deprecated( 'Use RenderConstraintsTransformBox instead. ' 'This feature was deprecated after v2.1.0-11.0.pre.', ) class RenderUnconstrainedBox extends RenderConstraintsTransformBox { /// Create a render object that sizes itself to the child but does not /// pass the [constraints] down to that child. /// /// The [alignment] and [clipBehavior] must not be null. @Deprecated( 'Use RenderConstraintsTransformBox instead. ' 'This feature was deprecated after v2.1.0-11.0.pre.', ) RenderUnconstrainedBox({ required AlignmentGeometry alignment, required TextDirection? textDirection, Axis? constrainedAxis, RenderBox? child, Clip clipBehavior = Clip.none, }) : assert(alignment != null), assert(clipBehavior != null), _constrainedAxis = constrainedAxis, super( alignment: alignment, textDirection: textDirection, child: child, clipBehavior: clipBehavior, constraintsTransform: _convertAxis(constrainedAxis), ); /// The axis to retain constraints on, if any. /// /// If not set, or set to null (the default), neither axis will retain its /// constraints. If set to [Axis.vertical], then vertical constraints will /// 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; constraintsTransform = _convertAxis(constrainedAxis); } static BoxConstraints _unconstrained(BoxConstraints constraints) => const BoxConstraints(); static BoxConstraints _widthConstrained(BoxConstraints constraints) => constraints.widthConstraints(); static BoxConstraints _heightConstrained(BoxConstraints constraints) => constraints.heightConstraints(); static BoxConstraintsTransform _convertAxis(Axis? constrainedAxis) { if (constrainedAxis == null) { return _unconstrained; } switch (constrainedAxis) { case Axis.horizontal: return _widthConstrained; case Axis.vertical: return _heightConstrained; } } } /// A render object that is a specific size but passes its original constraints /// through to its child, which it allows to overflow. /// /// If the child's resulting size differs from this render object's size, then /// the child is aligned according to the [alignment] property. /// /// 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. /// * [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. class RenderSizedOverflowBox extends RenderAligningShiftedBox { /// Creates a render box of a given size that lets its child overflow. /// /// The [requestedSize] and [alignment] arguments must not be null. /// /// The [textDirection] argument must not be null if the [alignment] is /// direction-sensitive. RenderSizedOverflowBox({ RenderBox? child, required Size requestedSize, AlignmentGeometry alignment = Alignment.center, TextDirection? textDirection, }) : assert(requestedSize != null), _requestedSize = requestedSize, super(child: child, alignment: alignment, textDirection: textDirection); /// The size this render box should attempt to be. Size get requestedSize => _requestedSize; Size _requestedSize; set requestedSize(Size value) { assert(value != null); if (_requestedSize == value) return; _requestedSize = value; markNeedsLayout(); } @override double computeMinIntrinsicWidth(double height) { return _requestedSize.width; } @override double computeMaxIntrinsicWidth(double height) { return _requestedSize.width; } @override double computeMinIntrinsicHeight(double width) { return _requestedSize.height; } @override double computeMaxIntrinsicHeight(double width) { return _requestedSize.height; } @override double? computeDistanceToActualBaseline(TextBaseline baseline) { if (child != null) return child!.getDistanceToActualBaseline(baseline); return super.computeDistanceToActualBaseline(baseline); } @override Size computeDryLayout(BoxConstraints constraints) { return constraints.constrain(_requestedSize); } @override void performLayout() { size = constraints.constrain(_requestedSize); if (child != null) { child!.layout(constraints, parentUsesSize: true); 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. /// /// 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]. class RenderFractionallySizedOverflowBox extends RenderAligningShiftedBox { /// 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. /// /// The [alignment] must not be null. /// /// The [textDirection] must be non-null if the [alignment] is /// direction-sensitive. RenderFractionallySizedOverflowBox({ RenderBox? child, double? widthFactor, double? heightFactor, AlignmentGeometry alignment = Alignment.center, TextDirection? textDirection, }) : _widthFactor = widthFactor, _heightFactor = heightFactor, super(child: child, alignment: alignment, textDirection: textDirection) { 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 /// incoming width constraint multiplied by this factor. If null, the child is /// given the incoming width constraints. double? get widthFactor => _widthFactor; double? _widthFactor; set widthFactor(double? value) { 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 /// incoming width constraint multiplied by this factor. If null, the child is /// given the incoming width constraints. double? get heightFactor => _heightFactor; double? _heightFactor; set heightFactor(double? value) { 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) { final double width = maxWidth * _widthFactor!; minWidth = width; maxWidth = width; } double minHeight = constraints.minHeight; double maxHeight = constraints.maxHeight; if (_heightFactor != null) { final double height = maxHeight * _heightFactor!; minHeight = height; maxHeight = height; } return BoxConstraints( minWidth: minWidth, maxWidth: maxWidth, minHeight: minHeight, maxHeight: maxHeight, ); } @override double computeMinIntrinsicWidth(double height) { final double result; if (child == null) { result = super.computeMinIntrinsicWidth(height); } else { // the following line relies on double.infinity absorption result = child!.getMinIntrinsicWidth(height * (_heightFactor ?? 1.0)); } assert(result.isFinite); return result / (_widthFactor ?? 1.0); } @override double computeMaxIntrinsicWidth(double height) { final double result; if (child == null) { result = super.computeMaxIntrinsicWidth(height); } else { // the following line relies on double.infinity absorption result = child!.getMaxIntrinsicWidth(height * (_heightFactor ?? 1.0)); } assert(result.isFinite); return result / (_widthFactor ?? 1.0); } @override double computeMinIntrinsicHeight(double width) { final double result; if (child == null) { result = super.computeMinIntrinsicHeight(width); } else { // the following line relies on double.infinity absorption result = child!.getMinIntrinsicHeight(width * (_widthFactor ?? 1.0)); } assert(result.isFinite); return result / (_heightFactor ?? 1.0); } @override double computeMaxIntrinsicHeight(double width) { final double result; if (child == null) { result = super.computeMaxIntrinsicHeight(width); } else { // the following line relies on double.infinity absorption result = child!.getMaxIntrinsicHeight(width * (_widthFactor ?? 1.0)); } assert(result.isFinite); return result / (_heightFactor ?? 1.0); } @override Size computeDryLayout(BoxConstraints constraints) { if (child != null) { final Size childSize = child!.getDryLayout(_getInnerConstraints(constraints)); return constraints.constrain(childSize); } return constraints.constrain(_getInnerConstraints(constraints).constrain(Size.zero)); } @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 void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DoubleProperty('widthFactor', _widthFactor, ifNull: 'pass-through')); properties.add(DoubleProperty('heightFactor', _heightFactor, ifNull: 'pass-through')); } } /// A delegate for computing the layout of a render object with a single child. /// /// 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. /// /// The most efficient way to trigger a relayout is to supply a `relayout` /// argument to the constructor of the [SingleChildLayoutDelegate]. The custom /// 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. /// /// See also: /// /// * [CustomSingleChildLayout], the widget that uses this delegate. /// * [RenderCustomSingleChildLayoutBox], render object that uses this /// delegate. abstract class SingleChildLayoutDelegate { /// Creates a layout delegate. /// /// The layout will update whenever [relayout] notifies its listeners. const SingleChildLayoutDelegate({ Listenable? relayout }) : _relayout = relayout; final Listenable? _relayout; /// The size of this object given the incoming constraints. /// /// Defaults to the biggest size that satisfies the given constraints. Size getSize(BoxConstraints constraints) => constraints.biggest; /// 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. BoxConstraints getConstraintsForChild(BoxConstraints constraints) => constraints; /// 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. Offset getPositionForChild(Size size, Size childSize) => Offset.zero; /// 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 /// instance, then the method should return true, otherwise it should return /// false. /// /// If the method returns false, then the [getSize], /// [getConstraintsForChild], and [getPositionForChild] calls might be /// optimized away. /// /// It's possible that the layout methods will get called even if /// [shouldRelayout] returns false (e.g. if an ancestor changed its layout). /// It's also possible that the layout method will get called /// without [shouldRelayout] being called at all (e.g. if the parent changes /// size). bool shouldRelayout(covariant SingleChildLayoutDelegate oldDelegate); } /// 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. class RenderCustomSingleChildLayoutBox extends RenderShiftedBox { /// Creates a render box that defers its layout to a delegate. /// /// The [delegate] argument must not be null. RenderCustomSingleChildLayoutBox({ RenderBox? child, required SingleChildLayoutDelegate delegate, }) : assert(delegate != null), _delegate = delegate, super(child); /// A delegate that controls this object's layout. SingleChildLayoutDelegate get delegate => _delegate; SingleChildLayoutDelegate _delegate; set delegate(SingleChildLayoutDelegate newDelegate) { assert(newDelegate != null); if (_delegate == newDelegate) return; final SingleChildLayoutDelegate oldDelegate = _delegate; if (newDelegate.runtimeType != oldDelegate.runtimeType || newDelegate.shouldRelayout(oldDelegate)) markNeedsLayout(); _delegate = newDelegate; 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(); } Size _getSize(BoxConstraints constraints) { return constraints.constrain(_delegate.getSize(constraints)); } // 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. @override double computeMinIntrinsicWidth(double height) { final double width = _getSize(BoxConstraints.tightForFinite(height: height)).width; if (width.isFinite) return width; return 0.0; } @override double computeMaxIntrinsicWidth(double height) { final double width = _getSize(BoxConstraints.tightForFinite(height: height)).width; if (width.isFinite) return width; return 0.0; } @override double computeMinIntrinsicHeight(double width) { final double height = _getSize(BoxConstraints.tightForFinite(width: width)).height; if (height.isFinite) return height; return 0.0; } @override double computeMaxIntrinsicHeight(double width) { final double height = _getSize(BoxConstraints.tightForFinite(width: width)).height; if (height.isFinite) return height; return 0.0; } @override Size computeDryLayout(BoxConstraints constraints) { return _getSize(constraints); } @override void performLayout() { size = _getSize(constraints); if (child != null) { final BoxConstraints childConstraints = delegate.getConstraintsForChild(constraints); assert(childConstraints.debugAssertIsValid(isAppliedConstraint: true)); child!.layout(childConstraints, parentUsesSize: !childConstraints.isTight); final BoxParentData childParentData = child!.parentData! as BoxParentData; childParentData.offset = delegate.getPositionForChild(size, childConstraints.isTight ? childConstraints.smallest : child!.size); } } } /// 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 /// 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 /// of the box. This is typically not desirable, in particular, that /// 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. class RenderBaseline extends RenderShiftedBox { /// Creates a [RenderBaseline] object. /// /// The [baseline] and [baselineType] arguments must not be null. RenderBaseline({ RenderBox? child, required double baseline, required TextBaseline baselineType, }) : assert(baseline != null), assert(baselineType != null), _baseline = baseline, _baselineType = baselineType, super(child); /// The number of logical pixels from the top of this box at which to position /// the child's baseline. double get baseline => _baseline; double _baseline; set baseline(double value) { assert(value != null); if (_baseline == value) return; _baseline = value; markNeedsLayout(); } /// The type of baseline to use for positioning the child. TextBaseline get baselineType => _baselineType; TextBaseline _baselineType; set baselineType(TextBaseline value) { assert(value != null); if (_baselineType == value) return; _baselineType = value; markNeedsLayout(); } @override Size computeDryLayout(BoxConstraints constraints) { if (child != null) { assert(debugCannotComputeDryLayout( reason: 'Baseline metrics are only available after a full layout.', )); return Size.zero; } return constraints.smallest; } @override void performLayout() { if (child != null) { final BoxConstraints constraints = this.constraints; child!.layout(constraints.loosen(), parentUsesSize: true); final double childBaseline = child!.getDistanceToBaseline(baselineType)!; final double actualBaseline = baseline; final double top = actualBaseline - childBaseline; final BoxParentData childParentData = child!.parentData! as BoxParentData; childParentData.offset = Offset(0.0, top); final Size childSize = child!.size; size = constraints.constrain(Size(childSize.width, top + childSize.height)); } else { size = constraints.smallest; } } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DoubleProperty('baseline', baseline)); properties.add(EnumProperty<TextBaseline>('baselineType', baselineType)); } }