diff --git a/packages/flutter/lib/rendering.dart b/packages/flutter/lib/rendering.dart index 3931d07eb7..ee4dbb27cd 100644 --- a/packages/flutter/lib/rendering.dart +++ b/packages/flutter/lib/rendering.dart @@ -36,6 +36,7 @@ export 'src/rendering/binding.dart'; export 'src/rendering/box.dart'; export 'src/rendering/custom_layout.dart'; export 'src/rendering/debug.dart'; +export 'src/rendering/debug_overflow_indicator.dart'; export 'src/rendering/editable.dart'; export 'src/rendering/error.dart'; export 'src/rendering/flex.dart'; diff --git a/packages/flutter/lib/src/foundation/basic_types.dart b/packages/flutter/lib/src/foundation/basic_types.dart index 7e0fe50260..489ef8c22c 100644 --- a/packages/flutter/lib/src/foundation/basic_types.dart +++ b/packages/flutter/lib/src/foundation/basic_types.dart @@ -23,6 +23,7 @@ typedef void ValueChanged(T value); /// value, regardless of whether the given value is new or not. /// /// See also: +/// /// * [ValueGetter], the getter equivalent of this signature. /// * [AsyncValueSetter], an asynchronous version of this signature. typedef void ValueSetter(T value); @@ -30,6 +31,7 @@ typedef void ValueSetter(T value); /// Signature for callbacks that are to report a value on demand. /// /// See also: +/// /// * [ValueSetter], the setter equivalent of this signature. /// * [AsyncValueGetter], an asynchronous version of this signature. typedef T ValueGetter(); @@ -41,6 +43,7 @@ typedef Iterable IterableFilter(Iterable input); /// return a [Future] to indicate when their work is complete. /// /// See also: +/// /// * [VoidCallback], a synchronous version of this signature. /// * [AsyncValueGetter], a signature for asynchronous getters. /// * [AsyncValueSetter], a signature for asynchronous setters. @@ -50,6 +53,7 @@ typedef Future AsyncCallback(); /// [Future] that completes when the value has been saved. /// /// See also: +/// /// * [ValueSetter], a synchronous version of this signature. /// * [AsyncValueGetter], the getter equivalent of this signature. typedef Future AsyncValueSetter(T value); @@ -57,6 +61,7 @@ typedef Future AsyncValueSetter(T value); /// Signature for callbacks that are to asynchronously report a value on demand. /// /// See also: +/// /// * [ValueGetter], a synchronous version of this signature. /// * [AsyncValueSetter], the setter equivalent of this signature. typedef Future AsyncValueGetter(); diff --git a/packages/flutter/lib/src/material/text_field.dart b/packages/flutter/lib/src/material/text_field.dart index 6cc38ead01..dcda8431e8 100644 --- a/packages/flutter/lib/src/material/text_field.dart +++ b/packages/flutter/lib/src/material/text_field.dart @@ -232,6 +232,7 @@ class TextField extends StatefulWidget { /// characters. /// /// See also: + /// /// * [LengthLimitingTextInputFormatter] for more information on how it /// counts characters, and how it may differ from the intuitive meaning. final int maxLength; diff --git a/packages/flutter/lib/src/rendering/debug_overflow_indicator.dart b/packages/flutter/lib/src/rendering/debug_overflow_indicator.dart new file mode 100644 index 0000000000..8a6c6bb730 --- /dev/null +++ b/packages/flutter/lib/src/rendering/debug_overflow_indicator.dart @@ -0,0 +1,315 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:math' as math; +import 'dart:ui' as ui; + +import 'package:flutter/painting.dart'; + +import 'object.dart'; +import 'stack.dart'; + +// Describes which side the region data overflows on. +enum _OverflowSide { + left, + top, + bottom, + right, +} + +// Data used by the DebugOverflowIndicator to manage the regions and labels for +// the indicators. +class _OverflowRegionData { + const _OverflowRegionData({ + this.rect, + this.label: '', + this.labelOffset: Offset.zero, + this.rotation: 0.0, + this.side, + }); + + final Rect rect; + final String label; + final Offset labelOffset; + final double rotation; + final _OverflowSide side; +} + +/// An mixin indicator that is drawn when a [RenderObject] overflows its +/// container. +/// +/// This is used by some RenderObjects that are containers to show where, and by +/// how much, their children overflow their containers. These indicators are +/// typically only shown in a debug build (where the call to +/// [paintOverflowIndicator] is surrounded by an assert). +/// +/// This class will also print a debug message to the console when the container +/// overflows. It will print on the first occurrence, and once after each time that +/// [reassemble] is called. +/// +/// ## Sample code +/// +/// ```dart +/// class MyRenderObject extends RenderAligningShiftedBox with DebugOverflowIndicatorMixin { +/// MyRenderObject({ +/// AlignmentGeometry alignment, +/// TextDirection textDirection, +/// RenderBox child, +/// }) : super.mixin(alignment, textDirection, child); +/// +/// Rect _containerRect; +/// Rect _childRect; +/// +/// @override +/// void performLayout() { +/// // ... +/// final BoxParentData childParentData = child.parentData; +/// _containerRect = Offset.zero & size; +/// _childRect = childParentData.offset & child.size; +/// } +/// +/// @override +/// void paint(PaintingContext context, Offset offset) { +/// // Do normal painting here... +/// // ... +/// +/// assert(() { +/// paintOverflowIndicator(context, offset, _containerRect, _childRect); +/// return true; +/// }()); +/// } +/// } +/// ``` +/// +/// See also: +/// +/// * The code for [RenderUnconstrainedBox] and [RenderFlex] for examples of +/// classes that use this indicator mixin. +abstract class DebugOverflowIndicatorMixin extends RenderObject { + // This class is intended to be used as a mixin, and should not be + // extended directly. + factory DebugOverflowIndicatorMixin._() => null; + + static const Color _black = const Color(0xBF000000); + static const Color _yellow = const Color(0xBFFFFF00); + // The fraction of the container that the indicator covers. + static const double _indicatorFraction = 0.1; + static const double _indicatorFontSizePixels = 7.5; + static const double _indicatorLabelPaddingPixels = 1.0; + static const TextStyle _indicatorTextStyle = const TextStyle( + color: const Color(0xFF900000), + fontSize: _indicatorFontSizePixels, + fontWeight: FontWeight.w800, + ); + static final Paint _indicatorPaint = new Paint() + ..shader = new ui.Gradient.linear( + const Offset(0.0, 0.0), + const Offset(10.0, 10.0), + [_black, _yellow, _yellow, _black], + [0.25, 0.25, 0.75, 0.75], + TileMode.repeated, + ); + static final Paint _labelBackgroundPaint = new Paint()..color = const Color(0xFFFFFFFF); + + final List _indicatorLabel = new List.filled( + _OverflowSide.values.length, + new TextPainter(textDirection: TextDirection.ltr), // This label is in English. + ); + + // Set to true to trigger a debug message in the console upon + // the next paint call. Will be reset after each paint. + bool _overflowReportNeeded = true; + + String _formatPixels(double value) { + assert(value > 0.0); + String pixels; + if (value > 10.0) { + pixels = value.toStringAsFixed(0); + } else if (value > 1.0) { + pixels = value.toStringAsFixed(1); + } else { + pixels = value.toStringAsPrecision(3); + } + return pixels; + } + + List<_OverflowRegionData> _calculateOverflowRegions(RelativeRect overflow, Rect containerRect) { + final List<_OverflowRegionData> regions = <_OverflowRegionData>[]; + if (overflow.left > 0.0) { + final Rect markerRect = new Rect.fromLTWH( + 0.0, + 0.0, + containerRect.width * _indicatorFraction, + containerRect.height, + ); + regions.add(new _OverflowRegionData( + rect: markerRect, + label: 'LEFT OVERFLOWED BY ${_formatPixels(overflow.left)} PIXELS', + labelOffset: markerRect.centerLeft + + const Offset(_indicatorFontSizePixels + _indicatorLabelPaddingPixels, 0.0), + rotation: math.PI / 2.0, + side: _OverflowSide.left, + )); + } + if (overflow.right > 0.0) { + final Rect markerRect = new Rect.fromLTWH( + containerRect.width * (1.0 - _indicatorFraction), + 0.0, + containerRect.width * _indicatorFraction, + containerRect.height, + ); + regions.add(new _OverflowRegionData( + rect: markerRect, + label: 'RIGHT OVERFLOWED BY ${_formatPixels(overflow.right)} PIXELS', + labelOffset: markerRect.centerRight - + const Offset(_indicatorFontSizePixels + _indicatorLabelPaddingPixels, 0.0), + rotation: -math.PI / 2.0, + side: _OverflowSide.right, + )); + } + if (overflow.top > 0.0) { + final Rect markerRect = new Rect.fromLTWH( + 0.0, + 0.0, + containerRect.width, + containerRect.height * _indicatorFraction, + ); + regions.add(new _OverflowRegionData( + rect: markerRect, + label: 'TOP OVERFLOWED BY ${_formatPixels(overflow.top)} PIXELS', + labelOffset: markerRect.topCenter + const Offset(0.0, _indicatorLabelPaddingPixels), + rotation: 0.0, + side: _OverflowSide.top, + )); + } + if (overflow.bottom > 0.0) { + final Rect markerRect = new Rect.fromLTWH( + 0.0, + containerRect.height * (1.0 - _indicatorFraction), + containerRect.width, + containerRect.height * _indicatorFraction, + ); + regions.add(new _OverflowRegionData( + rect: markerRect, + label: 'BOTTOM OVERFLOWED BY ${_formatPixels(overflow.bottom)} PIXELS', + labelOffset: markerRect.bottomCenter - + const Offset(0.0, _indicatorFontSizePixels + _indicatorLabelPaddingPixels), + rotation: 0.0, + side: _OverflowSide.bottom, + )); + } + return regions; + } + + void _reportOverflow(RelativeRect overflow, String overflowHints) { + overflowHints ??= 'The edge of the $runtimeType that is ' + 'overflowing has been marked in the rendering with a yellow and black ' + 'striped pattern. This is usually caused by the contents being too big ' + 'for the $runtimeType.\n' + 'This is considered an error condition because it indicates that there ' + 'is content that cannot be seen. If the content is legitimately bigger ' + 'than the available space, consider clipping it with a ClipRect widget ' + 'before putting it in the $runtimeType, or using a scrollable ' + 'container, like a ListView.'; + + final List overflows = []; + if (overflow.left > 0.0) + overflows.add('${_formatPixels(overflow.left)} pixels on the left'); + if (overflow.top > 0.0) + overflows.add('${_formatPixels(overflow.top)} pixels on the top'); + if (overflow.bottom > 0.0) + overflows.add('${_formatPixels(overflow.bottom)} pixels on the bottom'); + if (overflow.right > 0.0) + overflows.add('${_formatPixels(overflow.right)} pixels on the right'); + String overflowText = ''; + assert(overflows.isNotEmpty, + "Somehow $runtimeType didn't actually overflow like it thought it did."); + switch (overflows.length) { + case 1: + overflowText = overflows.first; + break; + case 2: + overflowText = '${overflows.first} and ${overflows.last}'; + break; + default: + overflows[overflows.length - 1] = 'and ${overflows[overflows.length - 1]}'; + overflowText = overflows.join(', '); + } + FlutterError.reportError( + new FlutterErrorDetailsForRendering( + exception: 'A $runtimeType overflowed by $overflowText.', + library: 'rendering library', + context: 'during layout', + renderObject: this, + informationCollector: (StringBuffer information) { + information.writeln(overflowHints); + information.writeln('The specific $runtimeType in question is:'); + information.writeln(' ${toStringShallow(joiner: '\n ')}'); + information.writeln('◢◤' * (FlutterError.wrapWidth ~/ 2)); + }, + ), + ); + } + + /// To be called when the overflow indicators should be painted. + /// + /// Typically only called if there is an overflow, and only from within a + /// debug build. + /// + /// See example code in [DebugOverflowIndicatorMixin] documentation. + void paintOverflowIndicator( + PaintingContext context, + Offset offset, + Rect containerRect, + Rect childRect, { + String overflowHints, + }) { + final RelativeRect overflow = new RelativeRect.fromRect(containerRect, childRect); + + if (overflow.left <= 0.0 && + overflow.right <= 0.0 && + overflow.top <= 0.0 && + overflow.bottom <= 0.0) { + return; + } + + final List<_OverflowRegionData> overflowRegions = _calculateOverflowRegions(overflow, containerRect); + for (_OverflowRegionData region in overflowRegions) { + context.canvas.drawRect(region.rect.shift(offset), _indicatorPaint); + + if (_indicatorLabel[region.side.index].text?.text != region.label) { + _indicatorLabel[region.side.index].text = new TextSpan( + text: region.label, + style: _indicatorTextStyle, + ); + _indicatorLabel[region.side.index].layout(); + } + + final Offset labelOffset = region.labelOffset + offset; + final Offset centerOffset = new Offset(-_indicatorLabel[region.side.index].width / 2.0, 0.0); + final Rect textBackgroundRect = centerOffset & _indicatorLabel[region.side.index].size; + context.canvas.save(); + context.canvas.translate(labelOffset.dx, labelOffset.dy); + context.canvas.rotate(region.rotation); + context.canvas.drawRect(textBackgroundRect, _labelBackgroundPaint); + _indicatorLabel[region.side.index].paint(context.canvas, centerOffset); + context.canvas.restore(); + } + + if (_overflowReportNeeded) { + _overflowReportNeeded = false; + _reportOverflow(overflow, overflowHints); + } + } + + @override + void reassemble() { + super.reassemble(); + // Users expect error messages to be shown again after hot reload. + assert(() { + _overflowReportNeeded = true; + return true; + }()); + } +} diff --git a/packages/flutter/lib/src/rendering/flex.dart b/packages/flutter/lib/src/rendering/flex.dart index 8548dc07a0..629aecb7af 100644 --- a/packages/flutter/lib/src/rendering/flex.dart +++ b/packages/flutter/lib/src/rendering/flex.dart @@ -3,11 +3,11 @@ // found in the LICENSE file. import 'dart:math' as math; -import 'dart:ui' as ui; import 'package:flutter/foundation.dart'; import 'box.dart'; +import 'debug_overflow_indicator.dart'; import 'object.dart'; /// How the child is inscribed into the available space. @@ -262,7 +262,8 @@ typedef double _ChildSizingFunction(RenderBox child, double extent); /// * [Flex], the widget equivalent. /// * [Row] and [Column], direction-specific variants of [Flex]. class RenderFlex extends RenderBox with ContainerRenderObjectMixin, - RenderBoxContainerDefaultsMixin { + RenderBoxContainerDefaultsMixin, + DebugOverflowIndicatorMixin { /// Creates a flex render object. /// /// By default, the flex layout is horizontal and children are aligned to the @@ -467,7 +468,7 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin[_black, _yellow, _yellow, _black], - [0.25, 0.25, 0.75, 0.75], - TileMode.repeated, - ); + // Only set this if it's null to save work. It gets reset to null if the + // _direction changes. + final String debugOverflowHints = + 'The overflowing $runtimeType has an orientation of $_direction.\n' + 'The edge of the $runtimeType that is overflowing has been marked ' + 'in the rendering with a yellow and black striped pattern. This is ' + 'usually caused by the contents being too big for the $runtimeType. ' + 'Consider applying a flex factor (e.g. using an Expanded widget) to ' + 'force the children of the $runtimeType to fit within the available ' + 'space instead of being sized to their natural size.\n' + 'This is considered an error condition because it indicates that there ' + 'is content that cannot be seen. If the content is legitimately bigger ' + 'than the available space, consider clipping it with a ClipRect widget ' + 'before putting it in the flex, or using a scrollable container rather ' + 'than a Flex, like a ListView.'; - String pixels; - if (_overflow > 10.0) { - pixels = _overflow.toStringAsFixed(0); - } else if (_overflow > 1.0) { - pixels = _overflow.toStringAsFixed(1); - } else { - pixels = _overflow.toStringAsPrecision(3); - } - - Rect markerRect; - String label; - Offset labelOffset; - double labelAngle; - switch (direction) { + // Simulate a child rect that overflows by the right amount. This child + // rect is never used for drawing, just for determining the overflow + // location and amount. + Rect overflowChildRect; + switch (_direction) { case Axis.horizontal: - if (textDirection != null) { - final Size markerSize = new Size(size.width * _kMarkerSize, size.height); - switch (textDirection) { - case TextDirection.rtl: - labelAngle = math.PI / 2.0; - markerRect = offset + new Offset(-size.width * _kMarkerSize, 0.0) & markerSize; - labelOffset = markerRect.centerLeft; - break; - case TextDirection.ltr: - labelAngle = -math.PI / 2.0; - markerRect = offset + new Offset(size.width * (1.0 - _kMarkerSize), 0.0) & markerSize; - labelOffset = markerRect.centerRight; - break; - } - } else { - markerRect = (offset & size).deflate(size.shortestSide * _kMarkerSize); - labelOffset = markerRect.center; - labelAngle = 0.0; - } - label = 'ROW OVERFLOWED BY $pixels PIXELS'; + overflowChildRect = new Rect.fromLTWH(0.0, 0.0, size.width + _overflow, 0.0); break; case Axis.vertical: - markerRect = offset + new Offset(0.0, size.height * (1.0 - _kMarkerSize)) & - new Size(size.width, size.height * _kMarkerSize); - label = 'COLUMN OVERFLOWED BY $pixels PIXELS'; + overflowChildRect = new Rect.fromLTWH(0.0, 0.0, 0.0, size.height + _overflow); break; } - context.canvas.drawRect(markerRect, _debugMarkerPaint); - - _debugMarkerLabel ??= new TextPainter() - ..textDirection = TextDirection.ltr; // This label is in English. - _debugMarkerLabel.text = new TextSpan( // This is a no-op if the label hasn't changed. - text: label, - style: _debugMarkerTextStyle, - ); - _debugMarkerLabel.layout(); // This is a no-op if the label hasn't changed. - - switch (direction) { - case Axis.horizontal: - context.canvas.save(); - context.canvas.translate(labelOffset.dx, labelOffset.dy); - context.canvas.rotate(labelAngle); - _debugMarkerLabel.paint(context.canvas, new Offset(-_debugMarkerLabel.width / 2.0, 0.0)); - context.canvas.restore(); - break; - case Axis.vertical: - _debugMarkerLabel.paint(context.canvas, markerRect.bottomCenter - new Offset(_debugMarkerLabel.width / 2.0, 0.0)); - break; - } - - if (_debugReportOverflow) { - _debugReportOverflow = false; - FlutterError.reportError(new FlutterErrorDetailsForRendering( - exception: 'A ${describeEnum(direction)} $runtimeType overflowed by $pixels pixels.', - library: 'rendering library', - context: 'during layout', - renderObject: this, - informationCollector: (StringBuffer information) { - information.writeln( - 'The edge of the $runtimeType that is overflowing has been marked in the rendering ' - 'with a yellow and black striped pattern. This is usually caused by the contents ' - 'being too big for the $runtimeType. Consider applying a flex factor (e.g. using ' - 'an Expanded widget) to force the children of the $runtimeType to fit within the ' - 'available space instead of being sized to their natural size.' - ); - information.writeln( - 'This is considered an error condition because it indicates that there is content ' - 'that cannot be seen. If the content is legitimately bigger than the available ' - 'space, consider clipping it with a ClipRect widget before putting it in the flex, ' - 'or using a scrollable container rather than a Flex, for example using ListView.' - ); - information.writeln('The specific $runtimeType in question is:'); - information.writeln(' ${toStringShallow(joiner: '\n ')}'); - information.writeln('◢◤' * (FlutterError.wrapWidth ~/ 2)); - } - )); - } - + paintOverflowIndicator(context, offset, Offset.zero & size, overflowChildRect, overflowHints: debugOverflowHints); return true; }()); } @@ -1092,5 +995,4 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin('verticalDirection', verticalDirection, defaultValue: null)); description.add(new EnumProperty('textBaseline', textBaseline, defaultValue: null)); } - } diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index 807b1a3319..8b14b3ce1a 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -2970,6 +2970,7 @@ abstract class _SemanticsFragment { /// previously painted [RenderObject]s unreachable for accessibility purposes. /// /// See also: + /// /// * [SemanticsConfiguration.isBlockingSemanticsOfPreviouslyPaintedNodes] /// describes what semantics are dropped in more detail. final bool dropsSemanticsOfPreviousSiblings; diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index 1fb3d5d5e1..a7b07e1fc1 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -27,7 +27,7 @@ export 'package:flutter/gestures.dart' show /// /// A proxy box has a single child and simply mimics all the properties of that /// child by calling through to the child for each function in the render box -/// protocol. For example, a proxy box determines its size by askings its child +/// protocol. For example, a proxy box determines its size by asking its child /// to layout with the same constraints and then matching the size. /// /// A proxy box isn't useful on its own because you might as well just replace diff --git a/packages/flutter/lib/src/rendering/shifted_box.dart b/packages/flutter/lib/src/rendering/shifted_box.dart index 10238b86f6..2641c308e8 100644 --- a/packages/flutter/lib/src/rendering/shifted_box.dart +++ b/packages/flutter/lib/src/rendering/shifted_box.dart @@ -8,7 +8,9 @@ import 'package:flutter/foundation.dart'; import 'box.dart'; import 'debug.dart'; +import 'debug_overflow_indicator.dart'; import 'object.dart'; +import 'stack.dart' show RelativeRect; /// Abstract class for one-child-layout render boxes that provide control over /// the child's position. @@ -236,6 +238,12 @@ abstract class RenderAligningShiftedBox extends RenderShiftedBox { _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/15101 is fixed. + @protected + RenderAligningShiftedBox.mixin(AlignmentGeometry alignment,TextDirection textDirection, RenderBox child) + : this(alignment: alignment, textDirection: textDirection, child: child); + Alignment _resolvedAlignment; void _resolve() { @@ -467,6 +475,16 @@ class RenderPositionedBox extends RenderAligningShiftedBox { /// 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({ @@ -562,8 +580,110 @@ class RenderConstrainedOverflowBox extends RenderAligningShiftedBox { } } -/// A render box that is a specific size but passes its original constraints -/// through to its child, which will probably overflow. +/// Renders a box, imposing no constraints on its child, allowing the child to +/// render at its "natural" size. +/// +/// This allows a child to render at the size it would render if it were alone +/// on an infinite canvas with no constraints. This box will then expand +/// as much as it can within its own constraints and 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 theR +/// overflow occurs. +/// +/// See also: +/// +/// * [RenderConstrainedBox] renders a box which imposes constraints on its +/// child. +/// * [RenderConstrainedOverflowBox], 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. +class RenderUnconstrainedBox extends RenderAligningShiftedBox with DebugOverflowIndicatorMixin { + RenderUnconstrainedBox({ + @required AlignmentGeometry alignment, + @required TextDirection textDirection, + RenderBox child, + }) : assert(alignment != null), + super.mixin(alignment, textDirection, child); + + Rect _overflowContainerRect = Rect.zero; + Rect _overflowChildRect = Rect.zero; + bool _isOverflowing = false; + + @override + void performLayout() { + if (child != null) { + // Let the child lay itself out at it's "natural" size. + child.layout(const BoxConstraints(), parentUsesSize: true); + size = constraints.constrain(child.size); + alignChild(); + final BoxParentData childParentData = child.parentData; + _overflowContainerRect = Offset.zero & size; + _overflowChildRect = childParentData.offset & child.size; + } else { + size = constraints.constrain(Size.zero); + _overflowContainerRect = Rect.zero; + _overflowChildRect = Rect.zero; + } + + final RelativeRect overflow = new RelativeRect.fromRect(_overflowContainerRect, _overflowChildRect); + _isOverflowing = overflow.left > 0.0 || + overflow.right > 0.0 || + overflow.top > 0.0 || + overflow.bottom > 0.0; + } + + @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; + } + + // We have overflow. Clip it. + context.pushClipRect(needsCompositing, offset, Offset.zero & size, super.paint); + + // Display the overflow indicator. + assert(() { + paintOverflowIndicator(context, offset, _overflowContainerRect, _overflowChildRect); + return true; + }()); + } + + @override + Rect describeApproximatePaintClip(RenderObject child) { + return _isOverflowing ? Offset.zero & size : null; + } + + @override + String toStringShort() { + String header = super.toStringShort(); + if (_isOverflowing) + header += ' OVERFLOWING'; + return header; + } +} + +/// A render object that is a specific size but passes its original constraints +/// through to its child, which it allows to overflow. +/// +/// 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. /// diff --git a/packages/flutter/lib/src/rendering/stack.dart b/packages/flutter/lib/src/rendering/stack.dart index 0d72ef0379..73273fa42f 100644 --- a/packages/flutter/lib/src/rendering/stack.dart +++ b/packages/flutter/lib/src/rendering/stack.dart @@ -17,12 +17,14 @@ import 'object.dart'; /// width or height of the rectangle, convert it to a [Rect] using [toRect()] /// (passing the container's own Rect), and then examine that object. /// -/// If you create the RelativeRect with null values, the methods on -/// RelativeRect will not work usefully (or at all). +/// The fields [left], [right], [bottom], and [top] must not be null. @immutable class RelativeRect { /// Creates a RelativeRect with the given values. - const RelativeRect.fromLTRB(this.left, this.top, this.right, this.bottom); + /// + /// The arguments must not be null. + const RelativeRect.fromLTRB(this.left, this.top, this.right, this.bottom) + : assert(left != null && top != null && right != null && bottom != null); /// Creates a RelativeRect from a Rect and a Size. The Rect (first argument) /// and the RelativeRect (the output) are in the coordinate space of the @@ -56,15 +58,23 @@ class RelativeRect { static final RelativeRect fill = const RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0); /// Distance from the left side of the container to the left side of this rectangle. + /// + /// May be negative if the left side of the rectangle is outside of the container. final double left; /// Distance from the top side of the container to the top side of this rectangle. + /// + /// May be negative if the top side of the rectangle is outside of the container. final double top; /// Distance from the right side of the container to the right side of this rectangle. + /// + /// May be negative if the right side of the rectangle is outside of the container. final double right; /// Distance from the bottom side of the container to the bottom side of this rectangle. + /// + /// May be negative if the bottom side of the rectangle is outside of the container. final double bottom; /// Returns a new rectangle object translated by the given offset. diff --git a/packages/flutter/lib/src/semantics/semantics.dart b/packages/flutter/lib/src/semantics/semantics.dart index 8483362e6a..e0a0b3d45b 100644 --- a/packages/flutter/lib/src/semantics/semantics.dart +++ b/packages/flutter/lib/src/semantics/semantics.dart @@ -1157,7 +1157,7 @@ class SemanticsConfiguration { /// The reading direction is given by [textDirection]. /// /// See also: - /// + /// /// * [decreasedValue], describes what [value] will be after performing /// [SemanticsAction.decrease] /// * [increasedValue], describes what [value] will be after performing diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index 180f8bc366..62547e367a 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -684,7 +684,14 @@ class PhysicalModel extends SingleChildRenderObjectWidget { final Color shadowColor; @override - RenderPhysicalModel createRenderObject(BuildContext context) => new RenderPhysicalModel(shape: shape, borderRadius: borderRadius, elevation: elevation, color: color, shadowColor: shadowColor); + RenderPhysicalModel createRenderObject(BuildContext context) { + return new RenderPhysicalModel( + shape: shape, + borderRadius: borderRadius, + elevation: elevation, color: color, + shadowColor: shadowColor, + ); + } @override void updateRenderObject(BuildContext context, RenderPhysicalModel renderObject) { @@ -732,6 +739,7 @@ class PhysicalModel extends SingleChildRenderObjectWidget { /// ``` /// /// See also: +/// /// * [RotatedBox], which rotates the child widget during layout, not just /// during painting. /// * [FittedBox], which sizes and positions its child widget to fit the parent @@ -969,6 +977,7 @@ class CompositedTransformFollower extends SingleChildRenderObjectWidget { /// /// * [Transform], which applies an arbitrary transform to its child widget at /// paint time. +/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). class FittedBox extends SingleChildRenderObjectWidget { /// Creates a widget that scales and positions its child within itself according to [fit]. /// @@ -1026,6 +1035,10 @@ class FittedBox extends SingleChildRenderObjectWidget { /// Hit tests will only be detected inside the bounds of the /// [FractionalTranslation], even if the contents are offset such that /// they overflow. +/// +/// See also: +/// +/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). class FractionalTranslation extends SingleChildRenderObjectWidget { /// Creates a widget that translates its child's painting. /// @@ -1048,7 +1061,12 @@ class FractionalTranslation extends SingleChildRenderObjectWidget { final bool transformHitTests; @override - RenderFractionalTranslation createRenderObject(BuildContext context) => new RenderFractionalTranslation(translation: translation, transformHitTests: transformHitTests); + RenderFractionalTranslation createRenderObject(BuildContext context) { + return new RenderFractionalTranslation( + translation: translation, + transformHitTests: transformHitTests, + ); + } @override void updateRenderObject(BuildContext context, RenderFractionalTranslation renderObject) { @@ -1080,6 +1098,7 @@ class FractionalTranslation extends SingleChildRenderObjectWidget { /// /// * [Transform], which is a paint effect that allows you to apply an /// arbitrary transform to a child. +/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). class RotatedBox extends SingleChildRenderObjectWidget { /// A widget that rotates its child. /// @@ -1112,7 +1131,8 @@ class RotatedBox extends SingleChildRenderObjectWidget { /// /// ## Sample code /// -/// This snippet indents the child (a [Card] with some [Text]) by eight pixels in each direction: +/// This snippet indents the child (a [Card] with some [Text]) by eight pixels +/// in each direction: /// /// ```dart /// new Padding( @@ -1139,13 +1159,12 @@ class RotatedBox extends SingleChildRenderObjectWidget { /// /// In fact, the majority of widgets in Flutter are simply combinations of other /// simpler widgets. Composition, rather than inheritance, is the primary -/// mechansim for building up widgets. +/// mechanism for building up widgets. /// /// See also: /// /// * [EdgeInsets], the class that is used to describe the padding dimensions. -/// * [Center], which positions the child at its natural dimensions, centered -/// in the parent. +/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). class Padding extends SingleChildRenderObjectWidget { /// Creates a widget that insets its child. /// @@ -1203,8 +1222,9 @@ class Padding extends SingleChildRenderObjectWidget { /// a single child. /// * [Center], which is the same as [Align] but with the [alignment] always /// set to [Alignment.center]. -/// * [FractionallySizedBox], which sizes its child based on a fraction of its own -/// size and positions the child according to an [Alignment] value. +/// * [FractionallySizedBox], which sizes its child based on a fraction of its +/// own size and positions the child according to an [Alignment] value. +/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). class Align extends SingleChildRenderObjectWidget { /// Creates an alignment widget. /// @@ -1283,6 +1303,11 @@ class Align extends SingleChildRenderObjectWidget { /// /// * [Align], which lets you arbitrarily position a child within itself, /// rather than just centering it. +/// * [Row], a widget that displays its children in a horizontal array. +/// * [Column], a widget that displays its children in a vertical array. +/// * [Container], a convenience widget that combines common painting, +/// positioning, and sizing widgets. +/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). class Center extends Align { /// Creates a widget that centers its child. const Center({ Key key, double widthFactor, double heightFactor, Widget child }) @@ -1320,7 +1345,9 @@ class CustomSingleChildLayout extends SingleChildRenderObjectWidget { final SingleChildLayoutDelegate delegate; @override - RenderCustomSingleChildLayoutBox createRenderObject(BuildContext context) => new RenderCustomSingleChildLayoutBox(delegate: delegate); + RenderCustomSingleChildLayoutBox createRenderObject(BuildContext context) { + return new RenderCustomSingleChildLayoutBox(delegate: delegate); + } @override void updateRenderObject(BuildContext context, RenderCustomSingleChildLayoutBox renderObject) { @@ -1448,12 +1475,15 @@ class CustomMultiChildLayout extends MultiChildRenderObjectWidget { /// /// * [ConstrainedBox], a more generic version of this class that takes /// arbitrary [BoxConstraints] instead of an explicit width and height. +/// * [UnconstrainedBox], a container that tries to let its child draw without +/// constraints. /// * [FractionallySizedBox], a widget that sizes its child to a fraction of /// the total available space. /// * [AspectRatio], a widget that attempts to fit within the parent's /// constraints while also sizing its child to match a given sapect ratio. /// * [FittedBox], which sizes and positions its child widget to fit the parent /// according to a given [BoxFit] discipline. +/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). class SizedBox extends SingleChildRenderObjectWidget { /// Creates a fixed size box. The [width] and [height] parameters can be null /// to indicate that the size of the box should not be constrained in @@ -1505,7 +1535,9 @@ class SizedBox extends SingleChildRenderObjectWidget { @override void debugFillProperties(DiagnosticPropertiesBuilder description) { super.debugFillProperties(description); - final DiagnosticLevel level = (width == double.INFINITY && height == double.INFINITY) ? DiagnosticLevel.hidden : DiagnosticLevel.info; + final DiagnosticLevel level = (width == double.INFINITY && height == double.INFINITY) + ? DiagnosticLevel.hidden + : DiagnosticLevel.info; description.add(new DoubleProperty('width', width, defaultValue: null, level: level)); description.add(new DoubleProperty('height', height, defaultValue: null, level: level)); } @@ -1534,12 +1566,15 @@ class SizedBox extends SingleChildRenderObjectWidget { /// See also: /// /// * [BoxConstraints], the class that describes constraints. +/// * [UnconstrainedBox], a container that tries to let its child draw without +/// constraints. /// * [SizedBox], which lets you specify tight constraints by explicitly /// specifying the height or width. -/// * [FractionallySizedBox], a widget that sizes its child to a fraction of -/// the total available space. +/// * [FractionallySizedBox], which sizes its child based on a fraction of its +/// own size and positions the child according to an [Alignment] value. /// * [AspectRatio], a widget that attempts to fit within the parent's -/// constraints while also sizing its child to match a given sapect ratio. +/// constraints while also sizing its child to match a given aspect ratio. +/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). class ConstrainedBox extends SingleChildRenderObjectWidget { /// Creates a widget that imposes additional constraints on its child. /// @@ -1556,7 +1591,9 @@ class ConstrainedBox extends SingleChildRenderObjectWidget { final BoxConstraints constraints; @override - RenderConstrainedBox createRenderObject(BuildContext context) => new RenderConstrainedBox(additionalConstraints: constraints); + RenderConstrainedBox createRenderObject(BuildContext context) { + return new RenderConstrainedBox(additionalConstraints: constraints); + } @override void updateRenderObject(BuildContext context, RenderConstrainedBox renderObject) { @@ -1570,15 +1607,87 @@ class ConstrainedBox extends SingleChildRenderObjectWidget { } } +/// A widget that imposes no constraints on its child, allowing it to render +/// at its "natural" size. +/// +/// This allows a child to render at the size it would render if it were alone +/// on an infinite canvas with no constraints. This container will then expand +/// as much as it can within its own constraints and align the child based on +/// [alignment]. If the container cannot expand enough to accommodate the +/// entire child, the child will be clipped. +/// +/// In debug mode, if the child overflows the container, a warning will be +/// printed on the console, and black and yellow striped areas will appear where +/// the overflow occurs. +/// +/// See also: +/// +/// * [ConstrainedBox] for a box which imposes constraints on its child. +/// * [Container], a convenience widget that combines common painting, +/// positioning, and sizing widgets. +/// * [OverflowBox], a widget that imposes different constraints on its child +/// than it gets from its parent, possibly allowing the child to overflow +/// the parent. +class UnconstrainedBox extends SingleChildRenderObjectWidget { + /// Creates a widget that imposes no constraints on its child, allowing it to + /// render at its "natural" size. If the child overflows the parents + /// constraints, a warning will be given in debug mode. + const UnconstrainedBox({ + Key key, + Widget child, + this.textDirection, + this.alignment: Alignment.center, + }) : assert(alignment != null), + super(key: key, child: child); + + /// The text direction to use when interpreting the [alignment] if it is an + /// [AlignmentDirectional]. + final TextDirection textDirection; + + /// The alignment to use when laying out the child. + /// + /// If this is an [AlignmentDirectional], then [textDirection] must not be + /// null. + /// + /// See also: + /// + /// * [Alignment] for non-[Directionality]-aware alignments. + /// * [AlignmentDirectional] for [Directionality]-aware alignments. + final AlignmentGeometry alignment; + + @override + void updateRenderObject(BuildContext context, covariant RenderUnconstrainedBox renderObject) { + renderObject + ..textDirection = textDirection ?? Directionality.of(context) + ..alignment = alignment; + } + + @override + RenderUnconstrainedBox createRenderObject(BuildContext context) => new RenderUnconstrainedBox( + textDirection: textDirection ?? Directionality.of(context), + alignment: alignment, + ); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder description) { + super.debugFillProperties(description); + description.add(new DiagnosticsProperty('alignment', alignment)); + description.add(new DiagnosticsProperty('textDirection', textDirection, defaultValue: null)); + } +} + /// A widget that sizes its child to a fraction of the total available space. /// For more details about the layout algorithm, see /// [RenderFractionallySizedOverflowBox]. /// /// See also: /// -/// * [Align] (which sizes itself based on its child's size and positions -/// the child according to an [Alignment] value) -/// * [OverflowBox] +/// * [Align], which sizes itself based on its child's size and positions +/// the child according to an [Alignment] value. +/// * [OverflowBox], a widget that imposes different constraints on its child +/// than it gets from its parent, possibly allowing the child to overflow the +/// parent. +/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). class FractionallySizedBox extends SingleChildRenderObjectWidget { /// Creates a widget that sizes its child to a fraction of the total available space. /// @@ -1674,6 +1783,7 @@ class FractionallySizedBox extends SingleChildRenderObjectWidget { /// when the incoming constraints are unbounded. /// * [SizedBox], which lets you specify tight constraints by explicitly /// specifying the height or width. +/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). class LimitedBox extends SingleChildRenderObjectWidget { /// Creates a box that limits its size only when it's unconstrained. /// @@ -1722,7 +1832,18 @@ class LimitedBox extends SingleChildRenderObjectWidget { /// A widget that imposes different constraints on its child than it gets /// from its parent, possibly allowing the child to overflow the parent. /// -/// See [RenderConstrainedOverflowBox] for details. +/// See also: +/// +/// * [RenderConstrainedOverflowBox] for details about how [OverflowBox] is +/// rendered. +/// * [SizedOverflowBox], a widget that is a specific size but passes its +/// original constraints through to its child, which may then overflow. +/// * [ConstrainedBox], a widget that imposes additional constraints on its +/// child. +/// * [UnconstrainedBox], a container that tries to let its child draw without +/// constraints. +/// * [SizedBox], a box with a specified size. +/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). class OverflowBox extends SingleChildRenderObjectWidget { /// Creates a widget that lets its child overflow itself. const OverflowBox({ @@ -1797,7 +1918,18 @@ class OverflowBox extends SingleChildRenderObjectWidget { } /// A widget that is a specific size but passes its original constraints -/// through to its child, which will probably overflow. +/// through to its child, which may then overflow. +/// +/// See also: +/// +/// * [OverflowBox], A widget that imposes different constraints on its child +/// than it gets from its parent, possibly allowing the child to overflow the +/// parent. +/// * [ConstrainedBox], a widget that imposes additional constraints on its +/// child. +/// * [UnconstrainedBox], a container that tries to let its child draw without +/// constraints. +/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). class SizedOverflowBox extends SingleChildRenderObjectWidget { /// Creates a widget of a given size that lets its child overflow. /// @@ -1853,6 +1985,10 @@ class SizedOverflowBox extends SingleChildRenderObjectWidget { /// A widget that lays the child out as if it was in the tree, but without painting anything, /// without making the child available for hit testing, and without taking any /// room in the parent. +/// +/// See also: +/// +/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). class Offstage extends SingleChildRenderObjectWidget { /// Creates a widget that visually hides its child. const Offstage({ Key key, this.offstage: true, Widget child }) @@ -1925,6 +2061,16 @@ class _OffstageElement extends SingleChildRenderObjectElement { /// find a feasible size after consulting each constraint, the widget /// will eventually select a size for the child that meets the layout /// constraints but fails to meet the aspect ratio constraints. +/// +/// See also: +/// +/// * [Align], a widget that aligns its child within itself and optionally +/// sizes itself based on the child's size. +/// * [ConstrainedBox], a widget that imposes additional constraints on its +/// child. +/// * [UnconstrainedBox], a container that tries to let its child draw without +/// constraints. +/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). class AspectRatio extends SingleChildRenderObjectWidget { /// Creates a widget with a specific aspect ratio. /// @@ -1972,6 +2118,10 @@ class AspectRatio extends SingleChildRenderObjectWidget { /// pass before the final layout phase. Avoid using it where possible. In the /// worst case, this widget can result in a layout that is O(N²) in the depth of /// the tree. +/// +/// See also: +/// +/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). class IntrinsicWidth extends SingleChildRenderObjectWidget { /// Creates a widget that sizes its child to the child's intrinsic width. /// @@ -1986,7 +2136,9 @@ class IntrinsicWidth extends SingleChildRenderObjectWidget { final double stepHeight; @override - RenderIntrinsicWidth createRenderObject(BuildContext context) => new RenderIntrinsicWidth(stepWidth: stepWidth, stepHeight: stepHeight); + RenderIntrinsicWidth createRenderObject(BuildContext context) { + return new RenderIntrinsicWidth(stepWidth: stepWidth, stepHeight: stepHeight); + } @override void updateRenderObject(BuildContext context, RenderIntrinsicWidth renderObject) { @@ -2006,6 +2158,10 @@ class IntrinsicWidth extends SingleChildRenderObjectWidget { /// pass before the final layout phase. Avoid using it where possible. In the /// worst case, this widget can result in a layout that is O(N²) in the depth of /// the tree. +/// +/// See also: +/// +/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). class IntrinsicHeight extends SingleChildRenderObjectWidget { /// Creates a widget that sizes its child to the child's intrinsic height. /// @@ -2024,6 +2180,13 @@ class IntrinsicHeight extends SingleChildRenderObjectWidget { /// 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 /// is top-aligned instead. +/// +/// See also: +/// +/// * [Align], a widget that aligns its child within itself and optionally +/// sizes itself based on the child's size. +/// * [Center], a widget that centers its child within itself. +/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). class Baseline extends SingleChildRenderObjectWidget { /// Creates a widget that positions its child according to the child's baseline. /// @@ -2045,7 +2208,9 @@ class Baseline extends SingleChildRenderObjectWidget { final TextBaseline baselineType; @override - RenderBaseline createRenderObject(BuildContext context) => new RenderBaseline(baseline: baseline, baselineType: baselineType); + RenderBaseline createRenderObject(BuildContext context) { + return new RenderBaseline(baseline: baseline, baselineType: baselineType); + } @override void updateRenderObject(BuildContext context, RenderBaseline renderObject) { @@ -2285,6 +2450,7 @@ class ListBody extends MultiChildRenderObjectWidget { /// children. /// * [Flow], which provides paint-time control of its children using transform /// matrices. +/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). class Stack extends MultiChildRenderObjectWidget { /// Creates a stack layout widget. /// @@ -2367,7 +2533,10 @@ class Stack extends MultiChildRenderObjectWidget { /// /// If value is null, then nothing is displayed. /// -/// For more details, see [Stack]. +/// See also: +/// +/// * [Stack], for more details about stacks. +/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). class IndexedStack extends Stack { /// Creates a [Stack] widget that paints a single child. /// @@ -2838,6 +3007,7 @@ class PositionedDirectional extends StatelessWidget { /// * [Expanded], to indicate children that should take all the remaining room. /// * [Flexible], to indicate children that should share the remaining room but /// that may be sized smaller (leaving some remaining room unused). +/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). class Flex extends MultiChildRenderObjectWidget { /// Creates a flex layout. /// @@ -3168,6 +3338,7 @@ class Flex extends MultiChildRenderObjectWidget { /// * [Expanded], to indicate children that should take all the remaining room. /// * [Flexible], to indicate children that should share the remaining room but /// that may by sized smaller (leaving some remaining room unused). +/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). class Row extends Flex { /// Creates a horizontal array of children. /// @@ -3359,6 +3530,7 @@ class Row extends Flex { /// * [Expanded], to indicate children that should take all the remaining room. /// * [Flexible], to indicate children that should share the remaining room but /// that may size smaller (leaving some remaining room unused). +/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). class Column extends Flex { /// Creates a vertical array of children. /// @@ -3409,6 +3581,7 @@ class Column extends Flex { /// See also: /// /// * [Expanded], which forces the child to expand to fill the available space. +/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). class Flexible extends ParentDataWidget { /// Creates a widget that controls how a child of a [Row], [Column], or [Flex] /// flexes. @@ -3481,6 +3654,7 @@ class Flexible extends ParentDataWidget { /// See also: /// /// * [Flexible], which does not force the child to fill the available space. +/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). class Expanded extends Flexible { /// Creates a widget that expands a child of a [Row], [Column], or [Flex] /// expand to fill the available space in the main axis. @@ -3539,6 +3713,7 @@ class Expanded extends Flexible { /// /// * [Row], which places children in one line, and gives control over their /// alignment and spacing. +/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). class Wrap extends MultiChildRenderObjectWidget { /// Creates a wrap layout. /// @@ -3769,6 +3944,7 @@ class Wrap extends MultiChildRenderObjectWidget { /// a single child. /// * [CustomMultiChildLayout], which uses a delegate to position multiple /// children. +/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). class Flow extends MultiChildRenderObjectWidget { /// Creates a flow layout. /// @@ -4577,6 +4753,7 @@ class Semantics extends SingleChildRenderObjectWidget { /// or an explicit [textDirection] should be provided. /// /// See also: + /// /// * [SemanticsConfiguration.label] for a description of how this is exposed /// in TalkBack and VoiceOver. final String label; @@ -4587,6 +4764,7 @@ class Semantics extends SingleChildRenderObjectWidget { /// or an explicit [textDirection] should be provided. /// /// See also: + /// /// * [SemanticsConfiguration.value] for a description of how this is exposed /// in TalkBack and VoiceOver. final String value; @@ -4599,6 +4777,7 @@ class Semantics extends SingleChildRenderObjectWidget { /// must be provided. /// /// See also: + /// /// * [SemanticsConfiguration.increasedValue] for a description of how this /// is exposed in TalkBack and VoiceOver. final String increasedValue; @@ -4611,6 +4790,7 @@ class Semantics extends SingleChildRenderObjectWidget { /// must be provided. /// /// See also: + /// /// * [SemanticsConfiguration.decreasedValue] for a description of how this /// is exposed in TalkBack and VoiceOver. final String decreasedValue; @@ -4622,6 +4802,7 @@ class Semantics extends SingleChildRenderObjectWidget { /// or an explicit [textDirection] should be provided. /// /// See also: + /// /// * [SemanticsConfiguration.hint] for a description of how this is exposed /// in TalkBack and VoiceOver. final String hint; diff --git a/packages/flutter/lib/src/widgets/container.dart b/packages/flutter/lib/src/widgets/container.dart index 7f7ac6d248..10b6ae873d 100644 --- a/packages/flutter/lib/src/widgets/container.dart +++ b/packages/flutter/lib/src/widgets/container.dart @@ -224,6 +224,7 @@ class DecoratedBox extends SingleChildRenderObjectWidget { /// * [AnimatedContainer], a variant that smoothly animates the properties when /// they change. /// * [Border], which has a sample which uses [Container] heavily. +/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). class Container extends StatelessWidget { /// Creates a widget that combines common painting, positioning, and sizing widgets. /// diff --git a/packages/flutter/lib/src/widgets/implicit_animations.dart b/packages/flutter/lib/src/widgets/implicit_animations.dart index af0fcce8fc..9487f2e643 100644 --- a/packages/flutter/lib/src/widgets/implicit_animations.dart +++ b/packages/flutter/lib/src/widgets/implicit_animations.dart @@ -41,6 +41,7 @@ class BoxConstraintsTween extends Tween { /// interpolation between decorations. /// /// See also: +/// /// * [Tween] for a discussion on how to use interpolation objects. /// * [ShapeDecoration], [RoundedRectangleBorder], [CircleBorder], and /// [StadiumBorder] for examples of shape borders that can be smoothly @@ -346,6 +347,7 @@ abstract class AnimatedWidgetBaseState exten /// /// * [AnimatedPadding], which is a subset of this widget that only /// supports animating the [padding]. +/// * The [catalog of layout widgets](https://flutter.io/widgets/layout/). class AnimatedContainer extends ImplicitlyAnimatedWidget { /// Creates a container that animates its parameters implicitly. /// diff --git a/packages/flutter/test/material/paginated_data_table_test.dart b/packages/flutter/test/material/paginated_data_table_test.dart index fefed4889c..36fcc937f9 100644 --- a/packages/flutter/test/material/paginated_data_table_test.dart +++ b/packages/flutter/test/material/paginated_data_table_test.dart @@ -152,7 +152,7 @@ void main() { )); // the column overflows because we're forcing it to 600 pixels high - expect(tester.takeException(), contains('A vertical RenderFlex overflowed by')); + expect(tester.takeException(), contains('A RenderFlex overflowed by')); expect(find.text('Gingerbread (0)'), findsOneWidget); expect(find.text('Gingerbread (1)'), findsNothing); diff --git a/packages/flutter/test/rendering/box_test.dart b/packages/flutter/test/rendering/box_test.dart index 5ceee82077..266b7de338 100644 --- a/packages/flutter/test/rendering/box_test.dart +++ b/packages/flutter/test/rendering/box_test.dart @@ -128,4 +128,78 @@ void main() { layout(constraintedBox); expect(coloredBox.parentData?.runtimeType, ParentData); }); + + test('UnconstrainedBox expands to fit children', () { + final RenderUnconstrainedBox unconstrained = new RenderUnconstrainedBox( + textDirection: TextDirection.ltr, + child: new RenderConstrainedBox( + additionalConstraints: const BoxConstraints.tightFor(width: 200.0, height: 200.0), + ), + alignment: Alignment.center, + ); + layout( + unconstrained, + constraints: const BoxConstraints( + minWidth: 200.0, + maxWidth: 200.0, + minHeight: 200.0, + maxHeight: 200.0, + ), + ); + + expect(unconstrained.size.width, equals(200.0), reason: 'unconstrained width'); + expect(unconstrained.size.height, equals(200.0), reason: 'unconstrained height'); + }); + + test('UnconstrainedBox handles vertical overflow', () { + final RenderUnconstrainedBox unconstrained = new RenderUnconstrainedBox( + textDirection: TextDirection.ltr, + child: new RenderConstrainedBox( + additionalConstraints: const BoxConstraints.tightFor(height: 200.0), + ), + alignment: Alignment.center, + ); + final BoxConstraints viewport = const BoxConstraints(maxHeight: 100.0, maxWidth: 100.0); + layout(unconstrained, constraints: viewport); + expect(unconstrained.getMinIntrinsicHeight(100.0), equals(200.0)); + expect(unconstrained.getMaxIntrinsicHeight(100.0), equals(200.0)); + expect(unconstrained.getMinIntrinsicWidth(100.0), equals(0.0)); + expect(unconstrained.getMaxIntrinsicWidth(100.0), equals(0.0)); + }); + + test('UnconstrainedBox handles horizontal overflow', () { + final RenderUnconstrainedBox unconstrained = new RenderUnconstrainedBox( + textDirection: TextDirection.ltr, + child: new RenderConstrainedBox( + additionalConstraints: const BoxConstraints.tightFor(width: 200.0), + ), + alignment: Alignment.center, + ); + final BoxConstraints viewport = const BoxConstraints(maxHeight: 100.0, maxWidth: 100.0); + layout(unconstrained, constraints: viewport); + expect(unconstrained.getMinIntrinsicHeight(100.0), equals(0.0)); + expect(unconstrained.getMaxIntrinsicHeight(100.0), equals(0.0)); + expect(unconstrained.getMinIntrinsicWidth(100.0), equals(200.0)); + expect(unconstrained.getMaxIntrinsicWidth(100.0), equals(200.0)); + }); + + test('UnconstrainedBox.toStringDeep returns useful information', () { + final RenderUnconstrainedBox unconstrained = new RenderUnconstrainedBox( + textDirection: TextDirection.ltr, + alignment: Alignment.center, + ); + expect(unconstrained.alignment, Alignment.center); + expect(unconstrained.textDirection, TextDirection.ltr); + expect(unconstrained, hasAGoodToStringDeep); + expect( + unconstrained.toStringDeep(minLevel: DiagnosticLevel.info), + equalsIgnoringHashCodes( + 'RenderUnconstrainedBox#00000 NEEDS-LAYOUT NEEDS-PAINT DETACHED\n' + ' parentData: MISSING\n' + ' constraints: MISSING\n' + ' size: MISSING\n' + ' alignment: Alignment.center\n' + ' textDirection: ltr\n'), + ); + }); } diff --git a/packages/flutter/test/rendering/debug_overflow_indicator_test.dart b/packages/flutter/test/rendering/debug_overflow_indicator_test.dart new file mode 100644 index 0000000000..e092ea090e --- /dev/null +++ b/packages/flutter/test/rendering/debug_overflow_indicator_test.dart @@ -0,0 +1,68 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/widgets.dart'; + +import '../rendering/mock_canvas.dart'; + +void main() { + testWidgets('overflow indicator is not shown when not overflowing', (WidgetTester tester) async { + await tester.pumpWidget( + const Center( + child: const UnconstrainedBox( + child: const SizedBox(width: 200.0, height: 200.0), + ), + ), + ); + + expect(find.byType(UnconstrainedBox), isNot(paints..rect())); + }); + + testWidgets('overflow indicator is shown when overflowing', (WidgetTester tester) async { + final UnconstrainedBox box = const UnconstrainedBox( + child: const SizedBox(width: 200.0, height: 200.0), + ); + await tester.pumpWidget( + new Center( + child: new SizedBox( + height: 100.0, + child: box, + ), + ), + ); + + expect(tester.takeException(), contains('A RenderUnconstrainedBox overflowed by')); + expect(find.byType(UnconstrainedBox), paints..rect()); + + await tester.pumpWidget( + new Center( + child: new SizedBox( + height: 100.0, + child: box, + ), + ), + ); + + // Doesn't throw the exception a second time, because we didn't reset + // overflowReportNeeded. + expect(tester.takeException(), isNull); + expect(find.byType(UnconstrainedBox), paints..rect()); + }); + + testWidgets('overflow indicator is not shown when constraint size is zero.', (WidgetTester tester) async { + await tester.pumpWidget( + const Center( + child: const SizedBox( + height: 0.0, + child: const UnconstrainedBox( + child: const SizedBox(width: 200.0, height: 200.0), + ), + ), + ), + ); + + expect(find.byType(UnconstrainedBox), isNot(paints..rect())); + }); +}