From cd593dae1961ca2bc5c045a310f16e6cdffc8294 Mon Sep 17 00:00:00 2001 From: liyuqian Date: Fri, 29 May 2020 15:38:50 -0700 Subject: [PATCH] Add clipBehavior to widgets with clipRect (#55977) * Add clipBehavior to RenderFlex * Add clipBehavior to FittedBox * Add clipBehavior to Flex and FittedBox * Add clipBehavior to UnconstrainedBox * Add clipBehavior to Stack and Wrap * Add clipBehavior to TextEditable * Add clipBehavior to ListWheelScrollView * Add clipBehavior to SingleChildScrollView * Add clipBehavior to RenderViewportBase's widgets Those widgets are NestedScrollView and ShrinkWrappingViewport. * Fix tests * Remove enum Overflow and fix typo * Remove clipToSize * Analyze fix * Remove Mixin and other small fixes * Fix tests and respect Stack's default clipBehavior * Add Overflow back to make it non-breaking * Restore clipBehavior to make it non-breaking * Small fixes * Fix rebase --- .../flutter_gallery/lib/demo/pesto_demo.dart | 2 +- .../flutter/lib/src/rendering/editable.dart | 23 +++++- packages/flutter/lib/src/rendering/flex.dart | 27 ++++++- .../src/rendering/list_wheel_viewport.dart | 66 ++++++++--------- .../flutter/lib/src/rendering/proxy_box.dart | 21 +++++- .../lib/src/rendering/shifted_box.dart | 25 ++++++- packages/flutter/lib/src/rendering/stack.dart | 30 ++++---- .../flutter/lib/src/rendering/viewport.dart | 34 +++++++-- packages/flutter/lib/src/rendering/wrap.dart | 28 ++++++-- .../lib/src/widgets/animated_cross_fade.dart | 2 +- packages/flutter/lib/src/widgets/basic.dart | 70 ++++++++++++++++--- .../lib/src/widgets/editable_text.dart | 12 ++++ .../src/widgets/list_wheel_scroll_view.dart | 50 +++++++------ .../lib/src/widgets/nested_scroll_view.dart | 19 ++++- .../src/widgets/single_child_scroll_view.dart | 38 ++++++++-- .../flutter/lib/src/widgets/viewport.dart | 21 +++++- .../flutter/test/material/dropdown_test.dart | 2 +- packages/flutter/test/rendering/box_test.dart | 27 +++++++ .../flutter/test/rendering/editable_test.dart | 38 ++++++++++ .../flutter/test/rendering/flex_test.dart | 18 +++++ .../test/rendering/proxy_box_test.dart | 19 +++++ .../test/rendering/rendering_tester.dart | 23 ++++++ .../flutter/test/rendering/stack_test.dart | 27 +++++++ .../flutter/test/rendering/wrap_test.dart | 20 ++++++ packages/flutter/test/widgets/basic_test.dart | 9 +++ .../test/widgets/editable_text_test.dart | 42 +++++++++++ .../flutter/test/widgets/fitted_box_test.dart | 12 ++++ packages/flutter/test/widgets/flex_test.dart | 9 +++ .../flutter/test/widgets/framework_test.dart | 4 +- .../widgets/list_wheel_scroll_view_test.dart | 38 ++++++++++ .../test/widgets/nested_scroll_view_test.dart | 51 ++++++++++++++ .../shrink_wrapping_viewport_test.dart | 50 +++++++++++++ .../single_child_scroll_view_test.dart | 22 ++++++ packages/flutter/test/widgets/stack_test.dart | 11 +++ packages/flutter/test/widgets/wrap_test.dart | 10 +++ 35 files changed, 785 insertions(+), 115 deletions(-) create mode 100644 packages/flutter/test/widgets/shrink_wrapping_viewport_test.dart diff --git a/dev/integration_tests/flutter_gallery/lib/demo/pesto_demo.dart b/dev/integration_tests/flutter_gallery/lib/demo/pesto_demo.dart index 328dce7eb9..bac6263ad4 100644 --- a/dev/integration_tests/flutter_gallery/lib/demo/pesto_demo.dart +++ b/dev/integration_tests/flutter_gallery/lib/demo/pesto_demo.dart @@ -224,7 +224,7 @@ class _PestoLogoState extends State { child: SizedBox( width: kLogoWidth, child: Stack( - overflow: Overflow.visible, + clipBehavior: Clip.none, children: [ Positioned.fromRect( rect: _imageRectTween.lerp(widget.t), diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart index 0f31f6ab57..4d5f46bc6a 100644 --- a/packages/flutter/lib/src/rendering/editable.dart +++ b/packages/flutter/lib/src/rendering/editable.dart @@ -227,6 +227,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin { EdgeInsets floatingCursorAddedMargin = const EdgeInsets.fromLTRB(4, 4, 4, 5), TextRange promptRectRange, Color promptRectColor, + Clip clipBehavior = Clip.hardEdge, @required this.textSelectionDelegate, }) : assert(textAlign != null), assert(textDirection != null, 'RenderEditable created without a textDirection.'), @@ -257,6 +258,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin { assert(devicePixelRatio != null), assert(selectionHeightStyle != null), assert(selectionWidthStyle != null), + assert(clipBehavior != null), _textPainter = TextPainter( text: text, textAlign: textAlign, @@ -290,7 +292,8 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin { _obscureText = obscureText, _readOnly = readOnly, _forceLine = forceLine, - _promptRectRange = promptRectRange { + _promptRectRange = promptRectRange, + _clipBehavior = clipBehavior { assert(_showCursor != null); assert(!_showCursor.value || cursorColor != null); this.hasFocus = hasFocus ?? false; @@ -1241,6 +1244,20 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin { double get _caretMargin => _kCaretGap + cursorWidth; + /// {@macro flutter.widgets.Clip} + /// + /// Defaults to [Clip.hardEdge], and must not be null. + Clip get clipBehavior => _clipBehavior; + Clip _clipBehavior = Clip.hardEdge; + set clipBehavior(Clip value) { + assert(value != null); + if (value != _clipBehavior) { + _clipBehavior = value; + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + } + @override void describeSemanticsConfiguration(SemanticsConfiguration config) { super.describeSemanticsConfiguration(config); @@ -2133,8 +2150,8 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin { @override void paint(PaintingContext context, Offset offset) { _layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth); - if (_hasVisualOverflow) - context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintContents); + if (_hasVisualOverflow && clipBehavior != Clip.none) + context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintContents, clipBehavior: clipBehavior); else _paintContents(context, offset); _paintHandleLayers(context, getEndpointsForSelection(selection)); diff --git a/packages/flutter/lib/src/rendering/flex.dart b/packages/flutter/lib/src/rendering/flex.dart index 45cdc722a7..91819f3391 100644 --- a/packages/flutter/lib/src/rendering/flex.dart +++ b/packages/flutter/lib/src/rendering/flex.dart @@ -277,17 +277,20 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin _overflow > precisionErrorTolerance; + /// {@macro flutter.widgets.Clip} + /// + /// Defaults to [Clip.none], and must not be null. + Clip get clipBehavior => _clipBehavior; + Clip _clipBehavior = Clip.none; + set clipBehavior(Clip value) { + assert(value != null); + if (value != _clipBehavior) { + _clipBehavior = value; + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + } + @override void setupParentData(RenderBox child) { if (child.parentData is! FlexParentData) @@ -955,8 +972,12 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin children, }) : assert(childManager != null), assert(offset != null), @@ -163,11 +163,11 @@ class RenderListWheelViewport assert(squeeze != null), assert(squeeze > 0), assert(itemExtent > 0), - assert(clipToSize != null), assert(renderChildrenOutsideViewport != null), + assert(clipBehavior != null), assert( - !renderChildrenOutsideViewport || !clipToSize, - clipToSizeAndRenderChildrenOutsideViewportConflict, + !renderChildrenOutsideViewport || clipBehavior == Clip.none, + clipBehaviorAndRenderChildrenOutsideViewportConflict, ), _offset = offset, _diameterRatio = diameterRatio, @@ -178,8 +178,8 @@ class RenderListWheelViewport _overAndUnderCenterOpacity = overAndUnderCenterOpacity, _itemExtent = itemExtent, _squeeze = squeeze, - _clipToSize = clipToSize, - _renderChildrenOutsideViewport = renderChildrenOutsideViewport { + _renderChildrenOutsideViewport = renderChildrenOutsideViewport, + _clipBehavior = clipBehavior { addAll(children); } @@ -199,10 +199,10 @@ class RenderListWheelViewport 'be clipped in the z-axis and therefore not renderable. Value must be ' 'between 0 and 0.01.'; - /// An error message to show when [clipToSize] and [renderChildrenOutsideViewport] + /// An error message to show when [clipBehavior] and [renderChildrenOutsideViewport] /// are set to conflicting values. - static const String clipToSizeAndRenderChildrenOutsideViewportConflict = - 'Cannot renderChildrenOutsideViewport and clipToSize since children ' + static const String clipBehaviorAndRenderChildrenOutsideViewportConflict = + 'Cannot renderChildrenOutsideViewport and clip since children ' 'rendered outside will be clipped anyway.'; /// The delegate that manages the children of this object. @@ -441,37 +441,14 @@ class RenderListWheelViewport markNeedsSemanticsUpdate(); } - /// {@template flutter.rendering.wheelList.clipToSize} - /// Whether to clip painted children to the inside of this viewport. - /// - /// Defaults to [true]. Must not be null. - /// - /// If this is false and [renderChildrenOutsideViewport] is false, the - /// first and last children may be painted partly outside of this scroll view. - /// {@endtemplate} - bool get clipToSize => _clipToSize; - bool _clipToSize; - set clipToSize(bool value) { - assert(value != null); - assert( - !renderChildrenOutsideViewport || !clipToSize, - clipToSizeAndRenderChildrenOutsideViewportConflict, - ); - if (value == _clipToSize) - return; - _clipToSize = value; - markNeedsPaint(); - markNeedsSemanticsUpdate(); - } - /// {@template flutter.rendering.wheelList.renderChildrenOutsideViewport} /// Whether to paint children inside the viewport only. /// /// If false, every child will be painted. However the [Scrollable] is still /// the size of the viewport and detects gestures inside only. /// - /// Defaults to [false]. Must not be null. Cannot be true if [clipToSize] - /// is also true since children outside the viewport will be clipped, and + /// Defaults to [false]. Must not be null. Cannot be true if [clipBehavior] + /// is not [Clip.none] since children outside the viewport will be clipped, and /// therefore cannot render children outside the viewport. /// {@endtemplate} bool get renderChildrenOutsideViewport => _renderChildrenOutsideViewport; @@ -479,8 +456,8 @@ class RenderListWheelViewport set renderChildrenOutsideViewport(bool value) { assert(value != null); assert( - !renderChildrenOutsideViewport || !clipToSize, - clipToSizeAndRenderChildrenOutsideViewportConflict, + !renderChildrenOutsideViewport || clipBehavior == Clip.none, + clipBehaviorAndRenderChildrenOutsideViewportConflict, ); if (value == _renderChildrenOutsideViewport) return; @@ -489,6 +466,20 @@ class RenderListWheelViewport markNeedsSemanticsUpdate(); } + /// {@macro flutter.widgets.Clip} + /// + /// Defaults to [Clip.hardEdge], and must not be null. + Clip get clipBehavior => _clipBehavior; + Clip _clipBehavior = Clip.hardEdge; + set clipBehavior(Clip value) { + assert(value != null); + if (value != _clipBehavior) { + _clipBehavior = value; + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + } + void _hasScrolled() { markNeedsLayout(); markNeedsSemanticsUpdate(); @@ -787,12 +778,13 @@ class RenderListWheelViewport @override void paint(PaintingContext context, Offset offset) { if (childCount > 0) { - if (_clipToSize && _shouldClipAtCurrentOffset()) { + if (_shouldClipAtCurrentOffset() && clipBehavior != Clip.none) { context.pushClipRect( needsCompositing, offset, Offset.zero & size, _paintVisibleChildren, + clipBehavior: clipBehavior, ); } else { _paintVisibleChildren(context, offset); diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index 0d85ea2c83..1a756a3699 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -2292,11 +2292,14 @@ class RenderFittedBox extends RenderProxyBox { AlignmentGeometry alignment = Alignment.center, TextDirection textDirection, RenderBox child, + Clip clipBehavior = Clip.none, }) : assert(fit != null), assert(alignment != null), + assert(clipBehavior != null), _fit = fit, _alignment = alignment, _textDirection = textDirection, + _clipBehavior = clipBehavior, super(child); Alignment _resolvedAlignment; @@ -2373,6 +2376,20 @@ class RenderFittedBox extends RenderProxyBox { bool _hasVisualOverflow; Matrix4 _transform; + /// {@macro flutter.widgets.Clip} + /// + /// Defaults to [Clip.none], and must not be null. + Clip get clipBehavior => _clipBehavior; + Clip _clipBehavior = Clip.none; + set clipBehavior(Clip value) { + assert(value != null); + if (value != _clipBehavior) { + _clipBehavior = value; + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + } + void _clearPaintData() { _hasVisualOverflow = null; _transform = null; @@ -2418,9 +2435,9 @@ class RenderFittedBox extends RenderProxyBox { return; _updatePaintData(); if (child != null) { - if (_hasVisualOverflow) + if (_hasVisualOverflow && clipBehavior != Clip.none) layer = context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintChildWithTransform, - oldLayer: layer is ClipRectLayer ? layer as ClipRectLayer : null); + oldLayer: layer is ClipRectLayer ? layer as ClipRectLayer : null, clipBehavior: clipBehavior); else layer = _paintChildWithTransform(context, offset); } diff --git a/packages/flutter/lib/src/rendering/shifted_box.dart b/packages/flutter/lib/src/rendering/shifted_box.dart index 832359cdad..a1a9776e71 100644 --- a/packages/flutter/lib/src/rendering/shifted_box.dart +++ b/packages/flutter/lib/src/rendering/shifted_box.dart @@ -626,8 +626,11 @@ class RenderUnconstrainedBox extends RenderAligningShiftedBox with DebugOverflow @required TextDirection textDirection, Axis constrainedAxis, RenderBox child, + Clip clipBehavior = Clip.none, }) : assert(alignment != null), + assert(clipBehavior != null), _constrainedAxis = constrainedAxis, + _clipBehavior = clipBehavior, super.mixin(alignment, textDirection, child); /// The axis to retain constraints on, if any. @@ -649,6 +652,20 @@ class RenderUnconstrainedBox extends RenderAligningShiftedBox with DebugOverflow Rect _overflowChildRect = Rect.zero; bool _isOverflowing = false; + /// {@macro flutter.widgets.Clip} + /// + /// Defaults to [Clip.none], and must not be null. + Clip get clipBehavior => _clipBehavior; + Clip _clipBehavior = Clip.none; + set clipBehavior(Clip value) { + assert(value != null); + if (value != _clipBehavior) { + _clipBehavior = value; + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + } + @override void performLayout() { final BoxConstraints constraints = this.constraints; @@ -694,8 +711,12 @@ class RenderUnconstrainedBox extends RenderAligningShiftedBox with DebugOverflow return; } - // We have overflow. Clip it. - context.pushClipRect(needsCompositing, offset, Offset.zero & size, super.paint); + if (clipBehavior == Clip.none) { + super.paint(context, offset); + } else { + // We have overflow and the clipBehavior isn't none. Clip it. + context.pushClipRect(needsCompositing, offset, Offset.zero & size, super.paint, clipBehavior: clipBehavior); + } // Display the overflow indicator. assert(() { diff --git a/packages/flutter/lib/src/rendering/stack.dart b/packages/flutter/lib/src/rendering/stack.dart index 5eacb7bfdc..5977d221e5 100644 --- a/packages/flutter/lib/src/rendering/stack.dart +++ b/packages/flutter/lib/src/rendering/stack.dart @@ -268,6 +268,8 @@ enum StackFit { passthrough, } +// TODO(liyuqian): Deprecate and remove `Overflow` once its usages are removed from Google. + /// Whether overflowing children should be clipped, or their overflow be /// visible. enum Overflow { @@ -326,14 +328,14 @@ class RenderStack extends RenderBox AlignmentGeometry alignment = AlignmentDirectional.topStart, TextDirection textDirection, StackFit fit = StackFit.loose, - Overflow overflow = Overflow.clip, + Clip clipBehavior = Clip.hardEdge, }) : assert(alignment != null), assert(fit != null), - assert(overflow != null), + assert(clipBehavior != null), _alignment = alignment, _textDirection = textDirection, _fit = fit, - _overflow = overflow { + _clipBehavior = clipBehavior { addAll(children); } @@ -411,17 +413,17 @@ class RenderStack extends RenderBox } } - /// Whether overflowing children should be clipped. See [Overflow]. + /// {@macro flutter.widgets.Clip} /// - /// Some children in a stack might overflow its box. When this flag is set to - /// [Overflow.clip], children cannot paint outside of the stack's box. - Overflow get overflow => _overflow; - Overflow _overflow; - set overflow(Overflow value) { + /// Defaults to [Clip.hardEdge], and must not be null. + Clip get clipBehavior => _clipBehavior; + Clip _clipBehavior = Clip.hardEdge; + set clipBehavior(Clip value) { assert(value != null); - if (_overflow != value) { - _overflow = value; + if (value != _clipBehavior) { + _clipBehavior = value; markNeedsPaint(); + markNeedsSemanticsUpdate(); } } @@ -604,8 +606,8 @@ class RenderStack extends RenderBox @override void paint(PaintingContext context, Offset offset) { - if (_overflow == Overflow.clip && _hasVisualOverflow) { - context.pushClipRect(needsCompositing, offset, Offset.zero & size, paintStack); + if (clipBehavior != Clip.none && _hasVisualOverflow) { + context.pushClipRect(needsCompositing, offset, Offset.zero & size, paintStack, clipBehavior: clipBehavior); } else { paintStack(context, offset); } @@ -620,7 +622,7 @@ class RenderStack extends RenderBox properties.add(DiagnosticsProperty('alignment', alignment)); properties.add(EnumProperty('textDirection', textDirection)); properties.add(EnumProperty('fit', fit)); - properties.add(EnumProperty('overflow', overflow)); + properties.add(EnumProperty('clipBehavior', clipBehavior, defaultValue: Clip.hardEdge)); } } diff --git a/packages/flutter/lib/src/rendering/viewport.dart b/packages/flutter/lib/src/rendering/viewport.dart index 0424305f67..3fcc91a1ee 100644 --- a/packages/flutter/lib/src/rendering/viewport.dart +++ b/packages/flutter/lib/src/rendering/viewport.dart @@ -171,17 +171,20 @@ abstract class RenderViewportBase _clipBehavior; + Clip _clipBehavior = Clip.hardEdge; + set clipBehavior(Clip value) { + assert(value != null); + if (value != _clipBehavior) { + _clipBehavior = value; + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + } + @override void attach(PipelineOwner owner) { super.attach(owner); @@ -574,8 +591,8 @@ abstract class RenderViewportBase= 0.0 && anchor <= 1.0), assert(cacheExtentStyle != CacheExtentStyle.viewport || cacheExtent != null), + assert(clipBehavior != null), _anchor = anchor, _center = center, super( @@ -1147,6 +1166,7 @@ class RenderViewport extends RenderViewportBase children, - }) : super(axisDirection: axisDirection, crossAxisDirection: crossAxisDirection, offset: offset) { + }) : super( + axisDirection: axisDirection, + crossAxisDirection: crossAxisDirection, + offset: offset, + clipBehavior: clipBehavior, + ) { addAll(children); } diff --git a/packages/flutter/lib/src/rendering/wrap.dart b/packages/flutter/lib/src/rendering/wrap.dart index c1bb0d75f9..40359b58ba 100644 --- a/packages/flutter/lib/src/rendering/wrap.dart +++ b/packages/flutter/lib/src/rendering/wrap.dart @@ -101,8 +101,9 @@ class WrapParentData extends ContainerBoxParentData { /// /// The runs themselves are then positioned in the cross axis according to the /// [runSpacing] and [runAlignment]. -class RenderWrap extends RenderBox with ContainerRenderObjectMixin, - RenderBoxContainerDefaultsMixin { +class RenderWrap extends RenderBox + with ContainerRenderObjectMixin, + RenderBoxContainerDefaultsMixin { /// Creates a wrap render object. /// /// By default, the wrap layout is horizontal and both the children and the @@ -117,12 +118,14 @@ class RenderWrap extends RenderBox with ContainerRenderObjectMixin _clipBehavior; + Clip _clipBehavior = Clip.none; + set clipBehavior(Clip value) { + assert(value != null); + if (value != _clipBehavior) { + _clipBehavior = value; + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + } + bool get _debugHasNecessaryDirections { assert(direction != null); assert(alignment != null); @@ -749,8 +767,8 @@ class RenderWrap extends RenderBox with ContainerRenderObjectMixin[ Positioned( key: bottomChildKey, diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index de3530ba81..5c7e915c27 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -42,7 +42,6 @@ export 'package:flutter/rendering.dart' show MainAxisAlignment, MainAxisSize, MultiChildLayoutDelegate, - Overflow, PaintingContext, PointerCancelEvent, PointerCancelEventListener, @@ -1411,9 +1410,11 @@ class FittedBox extends SingleChildRenderObjectWidget { Key key, this.fit = BoxFit.contain, this.alignment = Alignment.center, + this.clipBehavior = Clip.hardEdge, Widget child, }) : assert(fit != null), assert(alignment != null), + assert(clipBehavior != null), super(key: key, child: child); /// How to inscribe the child into the space allocated during layout. @@ -1435,12 +1436,19 @@ class FittedBox extends SingleChildRenderObjectWidget { /// relative to text direction. final AlignmentGeometry alignment; + // TODO(liyuqian): defaults to [Clip.none] once Google references are updated. + /// {@macro flutter.widgets.Clip} + /// + /// Defaults to [Clip.hardEdge]. + final Clip clipBehavior; + @override RenderFittedBox createRenderObject(BuildContext context) { return RenderFittedBox( fit: fit, alignment: alignment, textDirection: Directionality.of(context), + clipBehavior: clipBehavior, ); } @@ -1449,7 +1457,8 @@ class FittedBox extends SingleChildRenderObjectWidget { renderObject ..fit = fit ..alignment = alignment - ..textDirection = Directionality.of(context); + ..textDirection = Directionality.of(context) + ..clipBehavior = clipBehavior; } @override @@ -2231,7 +2240,9 @@ class UnconstrainedBox extends SingleChildRenderObjectWidget { this.textDirection, this.alignment = Alignment.center, this.constrainedAxis, + this.clipBehavior = Clip.hardEdge, }) : assert(alignment != null), + assert(clipBehavior != null), super(key: key, child: child); /// The text direction to use when interpreting the [alignment] if it is an @@ -2257,12 +2268,19 @@ class UnconstrainedBox extends SingleChildRenderObjectWidget { /// will be retained. final Axis constrainedAxis; + // TODO(liyuqian): defaults to [Clip.none] once Google references are updated. + /// {@macro flutter.widgets.Clip} + /// + /// Defaults to [Clip.hardEdge]. + final Clip clipBehavior; + @override void updateRenderObject(BuildContext context, covariant RenderUnconstrainedBox renderObject) { renderObject ..textDirection = textDirection ?? Directionality.of(context) ..alignment = alignment - ..constrainedAxis = constrainedAxis; + ..constrainedAxis = constrainedAxis + ..clipBehavior = clipBehavior; } @override @@ -2270,6 +2288,7 @@ class UnconstrainedBox extends SingleChildRenderObjectWidget { textDirection: textDirection ?? Directionality.of(context), alignment: alignment, constrainedAxis: constrainedAxis, + clipBehavior: clipBehavior, ); @override @@ -3214,8 +3233,10 @@ class Stack extends MultiChildRenderObjectWidget { this.textDirection, this.fit = StackFit.loose, this.overflow = Overflow.clip, + this.clipBehavior = Clip.hardEdge, List children = const [], - }) : super(key: key, children: children); + }) : assert(clipBehavior != null), + super(key: key, children: children); /// How to align the non-positioned and partially-positioned children in the /// stack. @@ -3252,19 +3273,29 @@ class Stack extends MultiChildRenderObjectWidget { /// ([StackFit.expand]). final StackFit fit; + // TODO(liyuqian): Deprecate and remove [overflow] once its usages are removed from Google. + /// Whether overflowing children should be clipped. See [Overflow]. /// /// Some children in a stack might overflow its box. When this flag is set to /// [Overflow.clip], children cannot paint outside of the stack's box. + /// + /// This overrides [clipBehavior] for now due to a staged roll out without + /// breaking Google. We will remove it and only use [clipBehavior] soon. final Overflow overflow; + /// {@macro flutter.widgets.Clip} + /// + /// Defaults to [Clip.hardEdge]. + final Clip clipBehavior; + @override RenderStack createRenderObject(BuildContext context) { return RenderStack( alignment: alignment, textDirection: textDirection ?? Directionality.of(context), fit: fit, - overflow: overflow, + clipBehavior: overflow == Overflow.clip ? Clip.hardEdge : clipBehavior, ); } @@ -3274,7 +3305,7 @@ class Stack extends MultiChildRenderObjectWidget { ..alignment = alignment ..textDirection = textDirection ?? Directionality.of(context) ..fit = fit - ..overflow = overflow; + ..clipBehavior = overflow == Overflow.clip ? Clip.hardEdge : clipBehavior; } @override @@ -3283,7 +3314,7 @@ class Stack extends MultiChildRenderObjectWidget { properties.add(DiagnosticsProperty('alignment', alignment)); properties.add(EnumProperty('textDirection', textDirection, defaultValue: null)); properties.add(EnumProperty('fit', fit)); - properties.add(EnumProperty('overflow', overflow)); + properties.add(EnumProperty('clipBehavior', clipBehavior, defaultValue: Clip.hardEdge)); } } @@ -3809,6 +3840,7 @@ class Flex extends MultiChildRenderObjectWidget { this.textDirection, this.verticalDirection = VerticalDirection.down, this.textBaseline, + this.clipBehavior = Clip.hardEdge, List children = const [], }) : assert(direction != null), assert(mainAxisAlignment != null), @@ -3816,6 +3848,7 @@ class Flex extends MultiChildRenderObjectWidget { assert(crossAxisAlignment != null), assert(verticalDirection != null), assert(crossAxisAlignment != CrossAxisAlignment.baseline || textBaseline != null), + assert(clipBehavior != null), super(key: key, children: children); /// The direction to use as the main axis. @@ -3900,6 +3933,12 @@ class Flex extends MultiChildRenderObjectWidget { /// If aligning items according to their baseline, which baseline to use. final TextBaseline textBaseline; + // TODO(liyuqian): defaults to [Clip.none] once Google references are updated. + /// {@macro flutter.widgets.Clip} + /// + /// Defaults to [Clip.hardEdge]. + final Clip clipBehavior; + bool get _needTextDirection { assert(direction != null); switch (direction) { @@ -3943,6 +3982,7 @@ class Flex extends MultiChildRenderObjectWidget { textDirection: getEffectiveTextDirection(context), verticalDirection: verticalDirection, textBaseline: textBaseline, + clipBehavior: clipBehavior, ); } @@ -3955,7 +3995,8 @@ class Flex extends MultiChildRenderObjectWidget { ..crossAxisAlignment = crossAxisAlignment ..textDirection = getEffectiveTextDirection(context) ..verticalDirection = verticalDirection - ..textBaseline = textBaseline; + ..textBaseline = textBaseline + ..clipBehavior = clipBehavior; } @override @@ -4626,8 +4667,9 @@ class Wrap extends MultiChildRenderObjectWidget { this.crossAxisAlignment = WrapCrossAlignment.start, this.textDirection, this.verticalDirection = VerticalDirection.down, + this.clipBehavior = Clip.hardEdge, List children = const [], - }) : super(key: key, children: children); + }) : assert(clipBehavior != null), super(key: key, children: children); /// The direction to use as the main axis. /// @@ -4761,6 +4803,12 @@ class Wrap extends MultiChildRenderObjectWidget { /// [verticalDirection] must not be null. final VerticalDirection verticalDirection; + // TODO(liyuqian): defaults to [Clip.none] once Google references are updated. + /// {@macro flutter.widgets.Clip} + /// + /// Defaults to [Clip.hardEdge]. + final Clip clipBehavior; + @override RenderWrap createRenderObject(BuildContext context) { return RenderWrap( @@ -4772,6 +4820,7 @@ class Wrap extends MultiChildRenderObjectWidget { crossAxisAlignment: crossAxisAlignment, textDirection: textDirection ?? Directionality.of(context), verticalDirection: verticalDirection, + clipBehavior: clipBehavior, ); } @@ -4785,7 +4834,8 @@ class Wrap extends MultiChildRenderObjectWidget { ..runSpacing = runSpacing ..crossAxisAlignment = crossAxisAlignment ..textDirection = textDirection ?? Directionality.of(context) - ..verticalDirection = verticalDirection; + ..verticalDirection = verticalDirection + ..clipBehavior = clipBehavior; } @override diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index ff29484b99..eae0a2c8da 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart @@ -413,6 +413,7 @@ class EditableText extends StatefulWidget { selectAll: true, ), this.autofillHints, + this.clipBehavior = Clip.hardEdge, }) : assert(controller != null), assert(focusNode != null), assert(obscuringCharacter != null && obscuringCharacter.length == 1), @@ -450,6 +451,7 @@ class EditableText extends StatefulWidget { assert(scrollPadding != null), assert(dragStartBehavior != null), assert(toolbarOptions != null), + assert(clipBehavior != null), _strutStyle = strutStyle, keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline), inputFormatters = maxLines == 1 @@ -1120,6 +1122,11 @@ class EditableText extends StatefulWidget { /// {@endtemplate} final Iterable autofillHints; + /// {@macro flutter.widgets.Clip} + /// + /// Defaults to [Clip.hardEdge]. + final Clip clipBehavior; + @override EditableTextState createState() => EditableTextState(); @@ -2089,6 +2096,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien devicePixelRatio: _devicePixelRatio, promptRectRange: _currentPromptRectRange, promptRectColor: widget.autocorrectionTextRectColor, + clipBehavior: widget.clipBehavior, ), ), ); @@ -2163,6 +2171,7 @@ class _Editable extends LeafRenderObjectWidget { this.devicePixelRatio, this.promptRectRange, this.promptRectColor, + this.clipBehavior, }) : assert(textDirection != null), assert(rendererIgnoresPointer != null), super(key: key); @@ -2208,6 +2217,7 @@ class _Editable extends LeafRenderObjectWidget { final double devicePixelRatio; final TextRange promptRectRange; final Color promptRectColor; + final Clip clipBehavior; @override RenderEditable createRenderObject(BuildContext context) { @@ -2249,6 +2259,7 @@ class _Editable extends LeafRenderObjectWidget { devicePixelRatio: devicePixelRatio, promptRectRange: promptRectRange, promptRectColor: promptRectColor, + clipBehavior: clipBehavior, ); } @@ -2289,6 +2300,7 @@ class _Editable extends LeafRenderObjectWidget { ..devicePixelRatio = devicePixelRatio ..paintCursorAboveText = paintCursorAboveText ..promptRectColor = promptRectColor + ..clipBehavior = clipBehavior ..setPromptRectRange(promptRectRange); } } diff --git a/packages/flutter/lib/src/widgets/list_wheel_scroll_view.dart b/packages/flutter/lib/src/widgets/list_wheel_scroll_view.dart index 40fdc2b0a2..49952a83e2 100644 --- a/packages/flutter/lib/src/widgets/list_wheel_scroll_view.dart +++ b/packages/flutter/lib/src/widgets/list_wheel_scroll_view.dart @@ -579,8 +579,8 @@ class ListWheelScrollView extends StatefulWidget { @required this.itemExtent, this.squeeze = 1.0, this.onSelectedItemChanged, - this.clipToSize = true, this.renderChildrenOutsideViewport = false, + this.clipBehavior = Clip.hardEdge, @required List children, }) : assert(children != null), assert(diameterRatio != null), @@ -595,11 +595,11 @@ class ListWheelScrollView extends StatefulWidget { assert(itemExtent > 0), assert(squeeze != null), assert(squeeze > 0), - assert(clipToSize != null), assert(renderChildrenOutsideViewport != null), + assert(clipBehavior != null), assert( - !renderChildrenOutsideViewport || !clipToSize, - RenderListWheelViewport.clipToSizeAndRenderChildrenOutsideViewportConflict, + !renderChildrenOutsideViewport || clipBehavior == Clip.none, + RenderListWheelViewport.clipBehaviorAndRenderChildrenOutsideViewportConflict, ), childDelegate = ListWheelChildListDelegate(children: children), super(key: key); @@ -619,8 +619,8 @@ class ListWheelScrollView extends StatefulWidget { @required this.itemExtent, this.squeeze = 1.0, this.onSelectedItemChanged, - this.clipToSize = true, this.renderChildrenOutsideViewport = false, + this.clipBehavior = Clip.hardEdge, @required this.childDelegate, }) : assert(childDelegate != null), assert(diameterRatio != null), @@ -635,11 +635,11 @@ class ListWheelScrollView extends StatefulWidget { assert(itemExtent > 0), assert(squeeze != null), assert(squeeze > 0), - assert(clipToSize != null), assert(renderChildrenOutsideViewport != null), + assert(clipBehavior != null), assert( - !renderChildrenOutsideViewport || !clipToSize, - RenderListWheelViewport.clipToSizeAndRenderChildrenOutsideViewportConflict, + !renderChildrenOutsideViewport || clipBehavior == Clip.none, + RenderListWheelViewport.clipBehaviorAndRenderChildrenOutsideViewportConflict, ), super(key: key); @@ -698,15 +698,17 @@ class ListWheelScrollView extends StatefulWidget { /// On optional listener that's called when the centered item changes. final ValueChanged onSelectedItemChanged; - /// {@macro flutter.rendering.wheelList.clipToSize} - final bool clipToSize; - /// {@macro flutter.rendering.wheelList.renderChildrenOutsideViewport} final bool renderChildrenOutsideViewport; /// A delegate that helps lazily instantiating child. final ListWheelChildDelegate childDelegate; + /// {@macro flutter.widgets.Clip} + /// + /// Defaults to [Clip.hardEdge]. + final Clip clipBehavior; + @override _ListWheelScrollViewState createState() => _ListWheelScrollViewState(); } @@ -769,10 +771,10 @@ class _ListWheelScrollViewState extends State { overAndUnderCenterOpacity: widget.overAndUnderCenterOpacity, itemExtent: widget.itemExtent, squeeze: widget.squeeze, - clipToSize: widget.clipToSize, renderChildrenOutsideViewport: widget.renderChildrenOutsideViewport, offset: offset, childDelegate: widget.childDelegate, + clipBehavior: widget.clipBehavior, ); }, ), @@ -950,7 +952,7 @@ class ListWheelViewport extends RenderObjectWidget { /// /// The [itemExtent] argument in pixels must be provided and must be positive. /// - /// The [clipToSize] argument defaults to true and must not be null. + /// The [clipBehavior] argument defaults to [Clip.hardEdge] and must not be null. /// /// The [renderChildrenOutsideViewport] argument defaults to false and must /// not be null. @@ -966,10 +968,10 @@ class ListWheelViewport extends RenderObjectWidget { this.overAndUnderCenterOpacity = 1.0, @required this.itemExtent, this.squeeze = 1.0, - this.clipToSize = true, this.renderChildrenOutsideViewport = false, @required this.offset, @required this.childDelegate, + this.clipBehavior = Clip.hardEdge, }) : assert(childDelegate != null), assert(offset != null), assert(diameterRatio != null), @@ -983,11 +985,11 @@ class ListWheelViewport extends RenderObjectWidget { assert(itemExtent > 0), assert(squeeze != null), assert(squeeze > 0), - assert(clipToSize != null), assert(renderChildrenOutsideViewport != null), + assert(clipBehavior != null), assert( - !renderChildrenOutsideViewport || !clipToSize, - RenderListWheelViewport.clipToSizeAndRenderChildrenOutsideViewportConflict, + !renderChildrenOutsideViewport || clipBehavior == Clip.none, + RenderListWheelViewport.clipBehaviorAndRenderChildrenOutsideViewportConflict, ), super(key: key); @@ -1017,9 +1019,6 @@ class ListWheelViewport extends RenderObjectWidget { /// Defaults to 1. final double squeeze; - /// {@macro flutter.rendering.wheelList.clipToSize} - final bool clipToSize; - /// {@macro flutter.rendering.wheelList.renderChildrenOutsideViewport} final bool renderChildrenOutsideViewport; @@ -1030,6 +1029,11 @@ class ListWheelViewport extends RenderObjectWidget { /// A delegate that lazily instantiates children. final ListWheelChildDelegate childDelegate; + /// {@macro flutter.widgets.Clip} + /// + /// Defaults to [Clip.none]. + final Clip clipBehavior; + @override ListWheelElement createElement() => ListWheelElement(this); @@ -1047,8 +1051,8 @@ class ListWheelViewport extends RenderObjectWidget { overAndUnderCenterOpacity: overAndUnderCenterOpacity, itemExtent: itemExtent, squeeze: squeeze, - clipToSize: clipToSize, renderChildrenOutsideViewport: renderChildrenOutsideViewport, + clipBehavior: clipBehavior, ); } @@ -1064,7 +1068,7 @@ class ListWheelViewport extends RenderObjectWidget { ..overAndUnderCenterOpacity = overAndUnderCenterOpacity ..itemExtent = itemExtent ..squeeze = squeeze - ..clipToSize = clipToSize - ..renderChildrenOutsideViewport = renderChildrenOutsideViewport; + ..renderChildrenOutsideViewport = renderChildrenOutsideViewport + ..clipBehavior = clipBehavior; } } diff --git a/packages/flutter/lib/src/widgets/nested_scroll_view.dart b/packages/flutter/lib/src/widgets/nested_scroll_view.dart index d5523735ae..14f3e76b7d 100644 --- a/packages/flutter/lib/src/widgets/nested_scroll_view.dart +++ b/packages/flutter/lib/src/widgets/nested_scroll_view.dart @@ -193,10 +193,12 @@ class NestedScrollView extends StatefulWidget { @required this.headerSliverBuilder, @required this.body, this.dragStartBehavior = DragStartBehavior.start, + this.clipBehavior = Clip.hardEdge, }) : assert(scrollDirection != null), assert(reverse != null), assert(headerSliverBuilder != null), assert(body != null), + assert(clipBehavior != null), super(key: key); /// An object that can be used to control the position to which the outer @@ -260,6 +262,11 @@ class NestedScrollView extends StatefulWidget { /// {@macro flutter.widgets.scrollable.dragStartBehavior} final DragStartBehavior dragStartBehavior; + /// {@macro flutter.widgets.Clip} + /// + /// Defaults to [Clip.hardEdge]. + final Clip clipBehavior; + /// Returns the [SliverOverlapAbsorberHandle] of the nearest ancestor /// [NestedScrollView]. /// @@ -437,6 +444,7 @@ class NestedScrollViewState extends State { _lastHasScrolledBody, ), handle: _absorberHandle, + clipBehavior: widget.clipBehavior, ); }, ), @@ -452,6 +460,7 @@ class _NestedScrollViewCustomScrollView extends CustomScrollView { @required ScrollController controller, @required List slivers, @required this.handle, + @required this.clipBehavior, DragStartBehavior dragStartBehavior = DragStartBehavior.start, }) : super( scrollDirection: scrollDirection, @@ -463,6 +472,7 @@ class _NestedScrollViewCustomScrollView extends CustomScrollView { ); final SliverOverlapAbsorberHandle handle; + final Clip clipBehavior; @override Widget buildViewport( @@ -477,6 +487,7 @@ class _NestedScrollViewCustomScrollView extends CustomScrollView { offset: offset, slivers: slivers, handle: handle, + clipBehavior: clipBehavior, ); } } @@ -1824,6 +1835,7 @@ class NestedScrollViewViewport extends Viewport { Key center, List slivers = const [], @required this.handle, + Clip clipBehavior = Clip.hardEdge, }) : assert(handle != null), super( key: key, @@ -1833,6 +1845,7 @@ class NestedScrollViewViewport extends Viewport { offset: offset, center: center, slivers: slivers, + clipBehavior: clipBehavior, ); /// The handle to the [SliverOverlapAbsorber] that is feeding this injector. @@ -1849,6 +1862,7 @@ class NestedScrollViewViewport extends Viewport { anchor: anchor, offset: offset, handle: handle, + clipBehavior: clipBehavior, ); } @@ -1862,7 +1876,8 @@ class NestedScrollViewViewport extends Viewport { ) ..anchor = anchor ..offset = offset - ..handle = handle; + ..handle = handle + ..clipBehavior = clipBehavior; } @override @@ -1889,6 +1904,7 @@ class RenderNestedScrollViewViewport extends RenderViewport { List children, RenderSliver center, @required SliverOverlapAbsorberHandle handle, + Clip clipBehavior = Clip.hardEdge, }) : assert(handle != null), _handle = handle, super( @@ -1898,6 +1914,7 @@ class RenderNestedScrollViewViewport extends RenderViewport { anchor: anchor, children: children, center: center, + clipBehavior: clipBehavior, ); /// The object to notify when [markNeedsLayout] is called. diff --git a/packages/flutter/lib/src/widgets/single_child_scroll_view.dart b/packages/flutter/lib/src/widgets/single_child_scroll_view.dart index f4deca6150..0d5b98a550 100644 --- a/packages/flutter/lib/src/widgets/single_child_scroll_view.dart +++ b/packages/flutter/lib/src/widgets/single_child_scroll_view.dart @@ -219,8 +219,10 @@ class SingleChildScrollView extends StatelessWidget { this.controller, this.child, this.dragStartBehavior = DragStartBehavior.start, + this.clipBehavior = Clip.hardEdge, }) : assert(scrollDirection != null), assert(dragStartBehavior != null), + assert(clipBehavior != null), assert(!(controller != null && primary == true), 'Primary ScrollViews obtain their ScrollController via inheritance from a PrimaryScrollController widget. ' 'You cannot both set primary to true and pass an explicit controller.' @@ -290,6 +292,11 @@ class SingleChildScrollView extends StatelessWidget { /// {@macro flutter.widgets.scrollable.dragStartBehavior} final DragStartBehavior dragStartBehavior; + /// {@macro flutter.widgets.Clip} + /// + /// Defaults to [Clip.hardEdge]. + final Clip clipBehavior; + AxisDirection _getDirection(BuildContext context) { return getAxisDirectionFromAxisReverseAndDirectionality(context, scrollDirection, reverse); } @@ -313,6 +320,7 @@ class SingleChildScrollView extends StatelessWidget { axisDirection: axisDirection, offset: offset, child: contents, + clipBehavior: clipBehavior, ); }, ); @@ -328,17 +336,21 @@ class _SingleChildViewport extends SingleChildRenderObjectWidget { this.axisDirection = AxisDirection.down, this.offset, Widget child, + @required this.clipBehavior, }) : assert(axisDirection != null), + assert(clipBehavior != null), super(key: key, child: child); final AxisDirection axisDirection; final ViewportOffset offset; + final Clip clipBehavior; @override _RenderSingleChildViewport createRenderObject(BuildContext context) { return _RenderSingleChildViewport( axisDirection: axisDirection, offset: offset, + clipBehavior: clipBehavior, ); } @@ -347,7 +359,8 @@ class _SingleChildViewport extends SingleChildRenderObjectWidget { // Order dependency: The offset setter reads the axis direction. renderObject ..axisDirection = axisDirection - ..offset = offset; + ..offset = offset + ..clipBehavior = clipBehavior; } } @@ -357,12 +370,15 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix @required ViewportOffset offset, double cacheExtent = RenderAbstractViewport.defaultCacheExtent, RenderBox child, + @required Clip clipBehavior, }) : assert(axisDirection != null), assert(offset != null), assert(cacheExtent != null), + assert(clipBehavior != null), _axisDirection = axisDirection, _offset = offset, - _cacheExtent = cacheExtent { + _cacheExtent = cacheExtent, + _clipBehavior = clipBehavior { this.child = child; } @@ -403,6 +419,20 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix markNeedsLayout(); } + /// {@macro flutter.widgets.Clip} + /// + /// Defaults to [Clip.none], and must not be null. + Clip get clipBehavior => _clipBehavior; + Clip _clipBehavior = Clip.none; + set clipBehavior(Clip value) { + assert(value != null); + if (value != _clipBehavior) { + _clipBehavior = value; + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + } + void _hasScrolled() { markNeedsPaint(); markNeedsSemanticsUpdate(); @@ -548,8 +578,8 @@ class _RenderSingleChildViewport extends RenderBox with RenderObjectWithChildMix context.paintChild(child, offset + paintOffset); } - if (_shouldClipAtPaintOffset(paintOffset)) { - context.pushClipRect(needsCompositing, offset, Offset.zero & size, paintContents); + if (_shouldClipAtPaintOffset(paintOffset) && clipBehavior != Clip.none) { + context.pushClipRect(needsCompositing, offset, Offset.zero & size, paintContents, clipBehavior: clipBehavior); } else { paintContents(context, offset); } diff --git a/packages/flutter/lib/src/widgets/viewport.dart b/packages/flutter/lib/src/widgets/viewport.dart index 2623ad2a01..e478ec364b 100644 --- a/packages/flutter/lib/src/widgets/viewport.dart +++ b/packages/flutter/lib/src/widgets/viewport.dart @@ -58,12 +58,14 @@ class Viewport extends MultiChildRenderObjectWidget { this.center, this.cacheExtent, this.cacheExtentStyle = CacheExtentStyle.pixel, + this.clipBehavior = Clip.hardEdge, List slivers = const [], }) : assert(offset != null), assert(slivers != null), assert(center == null || slivers.where((Widget child) => child.key == center).length == 1), assert(cacheExtentStyle != null), assert(cacheExtentStyle != CacheExtentStyle.viewport || cacheExtent != null), + assert(clipBehavior != null), super(key: key, children: slivers); /// The direction in which the [offset]'s [ViewportOffset.pixels] increases. @@ -118,6 +120,11 @@ class Viewport extends MultiChildRenderObjectWidget { /// {@macro flutter.rendering.viewport.cacheExtentStyle} final CacheExtentStyle cacheExtentStyle; + /// {@macro flutter.widgets.Clip} + /// + /// Defaults to [Clip.none]. + final Clip clipBehavior; + /// Given a [BuildContext] and an [AxisDirection], determine the correct cross /// axis direction. /// @@ -147,6 +154,7 @@ class Viewport extends MultiChildRenderObjectWidget { offset: offset, cacheExtent: cacheExtent, cacheExtentStyle: cacheExtentStyle, + clipBehavior: clipBehavior, ); } @@ -158,7 +166,8 @@ class Viewport extends MultiChildRenderObjectWidget { ..anchor = anchor ..offset = offset ..cacheExtent = cacheExtent - ..cacheExtentStyle = cacheExtentStyle; + ..cacheExtentStyle = cacheExtentStyle + ..clipBehavior = clipBehavior; } @override @@ -263,6 +272,7 @@ class ShrinkWrappingViewport extends MultiChildRenderObjectWidget { this.axisDirection = AxisDirection.down, this.crossAxisDirection, @required this.offset, + this.clipBehavior = Clip.hardEdge, List slivers = const [], }) : assert(offset != null), super(key: key, children: slivers); @@ -295,12 +305,18 @@ class ShrinkWrappingViewport extends MultiChildRenderObjectWidget { /// Typically a [ScrollPosition]. final ViewportOffset offset; + /// {@macro flutter.widgets.Clip} + /// + /// Defaults to [Clip.hardEdge]. + final Clip clipBehavior; + @override RenderShrinkWrappingViewport createRenderObject(BuildContext context) { return RenderShrinkWrappingViewport( axisDirection: axisDirection, crossAxisDirection: crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection), offset: offset, + clipBehavior: clipBehavior, ); } @@ -309,7 +325,8 @@ class ShrinkWrappingViewport extends MultiChildRenderObjectWidget { renderObject ..axisDirection = axisDirection ..crossAxisDirection = crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection) - ..offset = offset; + ..offset = offset + ..clipBehavior = clipBehavior; } @override diff --git a/packages/flutter/test/material/dropdown_test.dart b/packages/flutter/test/material/dropdown_test.dart index 1050e89500..3a180bde25 100644 --- a/packages/flutter/test/material/dropdown_test.dart +++ b/packages/flutter/test/material/dropdown_test.dart @@ -2143,8 +2143,8 @@ void main() { // hard coded 16px margin in the dropdown code, so that // this hint aligns "properly" with the menu. return Stack( + clipBehavior: Clip.none, alignment: Alignment.topCenter, - overflow: Overflow.visible, children: [ PositionedDirectional( width: constraints.maxWidth + hintPaddingOffset, diff --git a/packages/flutter/test/rendering/box_test.dart b/packages/flutter/test/rendering/box_test.dart index bb4acbe8eb..e0dcb784d1 100644 --- a/packages/flutter/test/rendering/box_test.dart +++ b/packages/flutter/test/rendering/box_test.dart @@ -670,6 +670,33 @@ void main() { expect(unconstrained.size.height, equals(100.0), reason: 'constrained height'); }); + test('clipBehavior is respected', () { + const BoxConstraints viewport = BoxConstraints(maxHeight: 100.0, maxWidth: 100.0); + final TestClipPaintingContext context = TestClipPaintingContext(); + + // By default, clipBehavior should be Clip.none + final RenderUnconstrainedBox defaultBox = RenderUnconstrainedBox( + alignment: Alignment.center, + textDirection: TextDirection.ltr, + child: box200x200, + ); + layout(defaultBox, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors); + defaultBox.paint(context, Offset.zero); + expect(context.clipBehavior, equals(Clip.none)); + + for (final Clip clip in Clip.values) { + final RenderUnconstrainedBox box = RenderUnconstrainedBox( + alignment: Alignment.center, + textDirection: TextDirection.ltr, + child: box200x200, + clipBehavior: clip, + ); + layout(box, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors); + box.paint(context, Offset.zero); + expect(context.clipBehavior, equals(clip)); + } + }); + group('hit testing', () { test('BoxHitTestResult wrapping HitTestResult', () { final HitTestEntry entry1 = HitTestEntry(_DummyHitTestTarget()); diff --git a/packages/flutter/test/rendering/editable_test.dart b/packages/flutter/test/rendering/editable_test.dart index 48d793b40c..1614dea98d 100644 --- a/packages/flutter/test/rendering/editable_test.dart +++ b/packages/flutter/test/rendering/editable_test.dart @@ -24,6 +24,44 @@ class FakeEditableTextState with TextSelectionDelegate { } void main() { + test('RenderEditable respects clipBehavior', () { + const BoxConstraints viewport = BoxConstraints(maxHeight: 100.0, maxWidth: 100.0); + final TestClipPaintingContext context = TestClipPaintingContext(); + + final String longString = 'a' * 10000; + + // By default, clipBehavior should be Clip.none + final RenderEditable defaultEditable = RenderEditable( + text: TextSpan(text: longString), + textDirection: TextDirection.ltr, + startHandleLayerLink: LayerLink(), + endHandleLayerLink: LayerLink(), + offset: ViewportOffset.zero(), + textSelectionDelegate: FakeEditableTextState(), + selection: const TextSelection(baseOffset: 0, extentOffset: 0), + ); + layout(defaultEditable, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors); + defaultEditable.paint(context, Offset.zero); + expect(context.clipBehavior, equals(Clip.hardEdge)); + + context.clipBehavior = Clip.none; // Reset as Clip.none won't write into clipBehavior. + for (final Clip clip in Clip.values) { + final RenderEditable editable = RenderEditable( + text: TextSpan(text: longString), + textDirection: TextDirection.ltr, + startHandleLayerLink: LayerLink(), + endHandleLayerLink: LayerLink(), + offset: ViewportOffset.zero(), + textSelectionDelegate: FakeEditableTextState(), + selection: const TextSelection(baseOffset: 0, extentOffset: 0), + clipBehavior: clip, + ); + layout(editable, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors); + editable.paint(context, Offset.zero); + expect(context.clipBehavior, equals(clip)); + } + }); + test('editable intrinsics', () { final TextSelectionDelegate delegate = FakeEditableTextState(); final RenderEditable editable = RenderEditable( diff --git a/packages/flutter/test/rendering/flex_test.dart b/packages/flutter/test/rendering/flex_test.dart index 824aff1051..81ed36c914 100644 --- a/packages/flutter/test/rendering/flex_test.dart +++ b/packages/flutter/test/rendering/flex_test.dart @@ -52,6 +52,24 @@ void main() { FlutterError.onError = oldHandler; }); + test('Clip behavior is respected', () { + const BoxConstraints viewport = BoxConstraints(maxHeight: 100.0, maxWidth: 100.0); + final TestClipPaintingContext context = TestClipPaintingContext(); + + // By default, clipBehavior should be Clip.none + final RenderFlex defaultFlex = RenderFlex(direction: Axis.vertical, children: [box200x200]); + layout(defaultFlex, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors); + defaultFlex.paint(context, Offset.zero); + expect(context.clipBehavior, equals(Clip.none)); + + for (final Clip clip in Clip.values) { + final RenderFlex flex = RenderFlex(direction: Axis.vertical, children: [box200x200], clipBehavior: clip); + layout(flex, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors); + flex.paint(context, Offset.zero); + expect(context.clipBehavior, equals(clip)); + } + }); + test('Vertical Overflow', () { final RenderConstrainedBox flexible = RenderConstrainedBox( additionalConstraints: const BoxConstraints.expand() diff --git a/packages/flutter/test/rendering/proxy_box_test.dart b/packages/flutter/test/rendering/proxy_box_test.dart index 6a49841d6d..1d0d180936 100644 --- a/packages/flutter/test/rendering/proxy_box_test.dart +++ b/packages/flutter/test/rendering/proxy_box_test.dart @@ -431,6 +431,7 @@ void main() { _testLayerReuse(RenderFittedBox( alignment: Alignment.center, fit: BoxFit.cover, + clipBehavior: Clip.hardEdge, // Inject opacity under the clip to force compositing. child: RenderOpacity( opacity: 0.5, @@ -468,6 +469,24 @@ void main() { _testFittedBoxWithClipRectLayer(); }); + test('RenderFittedBox respects clipBehavior', () { + const BoxConstraints viewport = BoxConstraints(maxHeight: 100.0, maxWidth: 100.0); + final TestClipPaintingContext context = TestClipPaintingContext(); + + // By default, clipBehavior should be Clip.none + final RenderFittedBox defaultBox = RenderFittedBox(child: box200x200, fit: BoxFit.none); + layout(defaultBox, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors); + defaultBox.paint(context, Offset.zero); + expect(context.clipBehavior, equals(Clip.none)); + + for (final Clip clip in Clip.values) { + final RenderFittedBox box = RenderFittedBox(child: box200x200, fit: BoxFit.none, clipBehavior: clip); + layout(box, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors); + box.paint(context, Offset.zero); + expect(context.clipBehavior, equals(clip)); + } + }); + test('RenderMouseRegion can change properties when detached', () { renderer.initMouseTracker(MouseTracker( renderer.pointerRouter, diff --git a/packages/flutter/test/rendering/rendering_tester.dart b/packages/flutter/test/rendering/rendering_tester.dart index 6d24df8562..cfd7236e0b 100644 --- a/packages/flutter/test/rendering/rendering_tester.dart +++ b/packages/flutter/test/rendering/rendering_tester.dart @@ -294,3 +294,26 @@ class FakeTicker implements Ticker { return DiagnosticsProperty(name, this, style: DiagnosticsTreeStyle.errorProperty); } } + +class TestClipPaintingContext extends PaintingContext { + TestClipPaintingContext() : super(ContainerLayer(), Rect.zero); + + @override + ClipRectLayer pushClipRect(bool needsCompositing, Offset offset, Rect clipRect, PaintingContextCallback painter, {Clip clipBehavior = Clip.hardEdge, ClipRectLayer oldLayer}) { + this.clipBehavior = clipBehavior; + return null; + } + + Clip clipBehavior = Clip.none; +} + +void expectOverflowedErrors() { + final FlutterErrorDetails errorDetails = renderer.takeFlutterErrorDetails(); + final bool overflowed = errorDetails.toString().contains('overflowed'); + if (!overflowed) { + FlutterError.reportError(errorDetails); + } +} + +RenderConstrainedBox get box200x200 => + RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(height: 200.0, width: 200.0)); diff --git a/packages/flutter/test/rendering/stack_test.dart b/packages/flutter/test/rendering/stack_test.dart index 1243dc3785..9449f0d158 100644 --- a/packages/flutter/test/rendering/stack_test.dart +++ b/packages/flutter/test/rendering/stack_test.dart @@ -61,6 +61,33 @@ void main() { expect(stack.size.height, equals(100.0)); }); + test('Stack respects clipBehavior', () { + const BoxConstraints viewport = BoxConstraints(maxHeight: 100.0, maxWidth: 100.0); + final TestClipPaintingContext context = TestClipPaintingContext(); + + // By default, clipBehavior should be Clip.none + final RenderStack defaultStack = RenderStack(textDirection: TextDirection.ltr, children: [box200x200]); + layout(defaultStack, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors); + defaultStack.paint(context, Offset.zero); + expect(context.clipBehavior, equals(Clip.none)); + + for (final Clip clip in Clip.values) { + final RenderBox child = box200x200; + final RenderStack stack = RenderStack( + textDirection: TextDirection.ltr, + children: [child], + clipBehavior: clip, + ); + { // Make sure that the child is positioned so the stack will consider it as overflowed. + final StackParentData parentData = child.parentData as StackParentData; + parentData.left = parentData.right = 0; + } + layout(stack, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors); + stack.paint(context, Offset.zero); + expect(context.clipBehavior, equals(clip)); + } + }); + group('RenderIndexedStack', () { test('visitChildrenForSemantics only visits displayed child', () { final RenderBox child1 = RenderConstrainedBox( diff --git a/packages/flutter/test/rendering/wrap_test.dart b/packages/flutter/test/rendering/wrap_test.dart index e6dac87e97..7667ff85fb 100644 --- a/packages/flutter/test/rendering/wrap_test.dart +++ b/packages/flutter/test/rendering/wrap_test.dart @@ -5,6 +5,8 @@ import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'rendering_tester.dart'; + void main() { test('Wrap test; toStringDeep', () { final RenderWrap renderWrap = RenderWrap(); @@ -151,4 +153,22 @@ void main() { expect(renderWrap.computeMinIntrinsicWidth(79), 80); expect(renderWrap.computeMinIntrinsicWidth(80), 80); }); + + test('Wrap respects clipBehavior', () { + const BoxConstraints viewport = BoxConstraints(maxHeight: 100.0, maxWidth: 100.0); + final TestClipPaintingContext context = TestClipPaintingContext(); + + // By default, clipBehavior should be Clip.none + final RenderWrap defaultWrap = RenderWrap(textDirection: TextDirection.ltr, children: [box200x200]); + layout(defaultWrap, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors); + defaultWrap.paint(context, Offset.zero); + expect(context.clipBehavior, equals(Clip.none)); + + for (final Clip clip in Clip.values) { + final RenderWrap wrap = RenderWrap(textDirection: TextDirection.ltr, children: [box200x200], clipBehavior: clip); + layout(wrap, constraints: viewport, phase: EnginePhase.composite, onErrors: expectOverflowedErrors); + wrap.paint(context, Offset.zero); + expect(context.clipBehavior, equals(clip)); + } + }); } diff --git a/packages/flutter/test/widgets/basic_test.dart b/packages/flutter/test/widgets/basic_test.dart index f1c502cdc0..0c75008c9c 100644 --- a/packages/flutter/test/widgets/basic_test.dart +++ b/packages/flutter/test/widgets/basic_test.dart @@ -280,6 +280,15 @@ void main() { ); }); + testWidgets('UnconstrainedBox can set and update clipBehavior', (WidgetTester tester) async { + await tester.pumpWidget(const UnconstrainedBox()); + final RenderUnconstrainedBox renderObject = tester.allRenderObjects.whereType().first; + expect(renderObject.clipBehavior, equals(Clip.hardEdge)); + + await tester.pumpWidget(const UnconstrainedBox(clipBehavior: Clip.antiAlias)); + expect(renderObject.clipBehavior, equals(Clip.antiAlias)); + }); + group('ColoredBox', () { _MockCanvas mockCanvas; _MockPaintingContext mockContext; diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart index 05c4c8e79a..7a3e89443b 100644 --- a/packages/flutter/test/widgets/editable_text_test.dart +++ b/packages/flutter/test/widgets/editable_text_test.dart @@ -4761,6 +4761,48 @@ void main() { expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text); }); + + testWidgets('EditableText can set and update clipBehavior', (WidgetTester tester) async { + await tester.pumpWidget(MediaQuery( + data: const MediaQueryData(devicePixelRatio: 1.0), + child: Directionality( + textDirection: TextDirection.ltr, + child: FocusScope( + node: focusScopeNode, + autofocus: true, + child: EditableText( + backgroundCursorColor: Colors.grey, + controller: controller, + focusNode: focusNode, + style: textStyle, + cursorColor: cursorColor, + ), + ), + ), + )); + final RenderEditable renderObject = tester.allRenderObjects.whereType().first; + expect(renderObject.clipBehavior, equals(Clip.hardEdge)); + + await tester.pumpWidget(MediaQuery( + data: const MediaQueryData(devicePixelRatio: 1.0), + child: Directionality( + textDirection: TextDirection.ltr, + child: FocusScope( + node: focusScopeNode, + autofocus: true, + child: EditableText( + backgroundCursorColor: Colors.grey, + controller: controller, + focusNode: focusNode, + style: textStyle, + cursorColor: cursorColor, + clipBehavior: Clip.antiAlias, + ), + ), + ), + )); + expect(renderObject.clipBehavior, equals(Clip.antiAlias)); + }); } class MockTextFormatter extends TextInputFormatter { diff --git a/packages/flutter/test/widgets/fitted_box_test.dart b/packages/flutter/test/widgets/fitted_box_test.dart index 0469a5aa7b..7d47d6edcb 100644 --- a/packages/flutter/test/widgets/fitted_box_test.dart +++ b/packages/flutter/test/widgets/fitted_box_test.dart @@ -369,6 +369,7 @@ void main() { height: 10.0, child: FittedBox( fit: BoxFit.cover, + clipBehavior: Clip.hardEdge, child: SizedBox( width: 10.0, height: 50.0, @@ -391,6 +392,7 @@ void main() { height: 100.0, child: FittedBox( fit: BoxFit.cover, + clipBehavior: Clip.hardEdge, child: SizedBox( width: 50.0, height: 10.0, @@ -418,6 +420,7 @@ void main() { height: b, child: FittedBox( fit: BoxFit.none, + clipBehavior: Clip.hardEdge, child: SizedBox( width: c, height: d, @@ -472,6 +475,15 @@ void main() { await tester.tap(find.byKey(key1)); expect(_pointerDown, isTrue); }); + + testWidgets('Can set and update clipBehavior', (WidgetTester tester) async { + await tester.pumpWidget(FittedBox(fit: BoxFit.none, child: Container())); + final RenderFittedBox renderObject = tester.allRenderObjects.whereType().first; + expect(renderObject.clipBehavior, equals(Clip.hardEdge)); + + await tester.pumpWidget(FittedBox(fit: BoxFit.none, child: Container(), clipBehavior: Clip.antiAlias)); + expect(renderObject.clipBehavior, equals(Clip.antiAlias)); + }); } List getLayers() { diff --git a/packages/flutter/test/widgets/flex_test.dart b/packages/flutter/test/widgets/flex_test.dart index 993dc7e822..a249a7edcb 100644 --- a/packages/flutter/test/widgets/flex_test.dart +++ b/packages/flutter/test/widgets/flex_test.dart @@ -140,4 +140,13 @@ void main() { final String message = tester.takeException().toString(); expect(message, contains('\nSee also:')); }); + + testWidgets('Can set and update clipBehavior', (WidgetTester tester) async { + await tester.pumpWidget(Flex(direction: Axis.vertical)); + final RenderFlex renderObject = tester.allRenderObjects.whereType().first; + expect(renderObject.clipBehavior, equals(Clip.hardEdge)); + + await tester.pumpWidget(Flex(direction: Axis.vertical, clipBehavior: Clip.antiAlias)); + expect(renderObject.clipBehavior, equals(Clip.antiAlias)); + }); } diff --git a/packages/flutter/test/widgets/framework_test.dart b/packages/flutter/test/widgets/framework_test.dart index 68bb488fba..7586da7b25 100644 --- a/packages/flutter/test/widgets/framework_test.dart +++ b/packages/flutter/test/widgets/framework_test.dart @@ -719,7 +719,7 @@ void main() { equalsIgnoringHashCodes( 'Duplicate keys found.\n' 'If multiple keyed nodes exist as children of another node, they must have unique keys.\n' - 'Stack(alignment: AlignmentDirectional.topStart, textDirection: ltr, fit: loose, overflow: clip) has multiple children with key [GlobalKey#00000 problematic].' + 'Stack(alignment: AlignmentDirectional.topStart, textDirection: ltr, fit: loose) has multiple children with key [GlobalKey#00000 problematic].' ), ); }); @@ -889,7 +889,7 @@ void main() { 'The specific parent that did not update after having one or more children forcibly ' 'removed due to GlobalKey reparenting is:\n' '- Stack(alignment: AlignmentDirectional.topStart, textDirection: ltr, fit: loose, ' - 'overflow: clip, renderObject: RenderStack#00000)\n' + 'renderObject: RenderStack#00000)\n' 'A GlobalKey can only be specified on one widget at a time in the widget tree.' ), ); diff --git a/packages/flutter/test/widgets/list_wheel_scroll_view_test.dart b/packages/flutter/test/widgets/list_wheel_scroll_view_test.dart index 4de089f13b..f26e307f47 100644 --- a/packages/flutter/test/widgets/list_wheel_scroll_view_test.dart +++ b/packages/flutter/test/widgets/list_wheel_scroll_view_test.dart @@ -11,6 +11,44 @@ import '../rendering/mock_canvas.dart'; import '../rendering/rendering_tester.dart'; void main() { + testWidgets('ListWheelScrollView respects clipBehavior', (WidgetTester tester) async { + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: ListWheelScrollView( + itemExtent: 2000.0, // huge extent to trigger clip + children: [Container()], + ), + ), + ); + + // 1st, check that the render object has received the default clip behavior. + final RenderListWheelViewport renderObject = tester.allRenderObjects.whereType().first; + expect(renderObject.clipBehavior, equals(Clip.hardEdge)); + + // 2nd, check that the painting context has received the default clip behavior. + final TestClipPaintingContext context = TestClipPaintingContext(); + renderObject.paint(context, Offset.zero); + expect(context.clipBehavior, equals(Clip.hardEdge)); + + // 3rd, pump a new widget to check that the render object can update its clip behavior. + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: ListWheelScrollView( + itemExtent: 2000.0, // huge extent to trigger clip + children: [Container()], + clipBehavior: Clip.antiAlias, + ), + ), + ); + expect(renderObject.clipBehavior, equals(Clip.antiAlias)); + + // 4th, check that a non-default clip behavior can be sent to the painting context. + renderObject.paint(context, Offset.zero); + expect(context.clipBehavior, equals(Clip.antiAlias)); + }); + group('construction check', () { testWidgets('ListWheelScrollView needs positive diameter ratio', (WidgetTester tester) async { try { diff --git a/packages/flutter/test/widgets/nested_scroll_view_test.dart b/packages/flutter/test/widgets/nested_scroll_view_test.dart index c01c311691..e40ea07446 100644 --- a/packages/flutter/test/widgets/nested_scroll_view_test.dart +++ b/packages/flutter/test/widgets/nested_scroll_view_test.dart @@ -8,6 +8,8 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/gestures.dart' show DragStartBehavior; import 'package:flutter/rendering.dart'; +import '../rendering/rendering_tester.dart'; + class _CustomPhysics extends ClampingScrollPhysics { const _CustomPhysics({ ScrollPhysics parent }) : super(parent: parent); @@ -118,6 +120,55 @@ Widget buildTest({ } void main() { + testWidgets('NestedScrollView respects clipBehavior', (WidgetTester tester) async { + Widget build(NestedScrollView nestedScrollView) { + return Localizations( + locale: const Locale('en', 'US'), + delegates: const >[ + DefaultMaterialLocalizations.delegate, + DefaultWidgetsLocalizations.delegate, + ], + child: Directionality( + textDirection: TextDirection.ltr, + child: MediaQuery( + data: const MediaQueryData(), + child: nestedScrollView, + ), + ), + ); + } + + await tester.pumpWidget(build( + NestedScrollView( + headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) => [const SliverAppBar()], + body: Container(height: 2000.0), + ) + )); + + // 1st, check that the render object has received the default clip behavior. + final RenderNestedScrollViewViewport renderObject = tester.allRenderObjects.whereType().first; + expect(renderObject.clipBehavior, equals(Clip.hardEdge)); + + // 2nd, check that the painting context has received the default clip behavior. + final TestClipPaintingContext context = TestClipPaintingContext(); + renderObject.paint(context, Offset.zero); + expect(context.clipBehavior, equals(Clip.hardEdge)); + + // 3rd, pump a new widget to check that the render object can update its clip behavior. + await tester.pumpWidget(build( + NestedScrollView( + headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) => [const SliverAppBar()], + body: Container(height: 2000.0), + clipBehavior: Clip.antiAlias, + ) + )); + expect(renderObject.clipBehavior, equals(Clip.antiAlias)); + + // 4th, check that a non-default clip behavior can be sent to the painting context. + renderObject.paint(context, Offset.zero); + expect(context.clipBehavior, equals(Clip.antiAlias)); + }); + testWidgets('NestedScrollView overscroll and release and hold', (WidgetTester tester) async { await tester.pumpWidget(buildTest()); expect(find.text('aaa2'), findsOneWidget); diff --git a/packages/flutter/test/widgets/shrink_wrapping_viewport_test.dart b/packages/flutter/test/widgets/shrink_wrapping_viewport_test.dart new file mode 100644 index 0000000000..f377e11d9d --- /dev/null +++ b/packages/flutter/test/widgets/shrink_wrapping_viewport_test.dart @@ -0,0 +1,50 @@ +// 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 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../rendering/rendering_tester.dart'; + +void main() { + testWidgets('ShrinkWrappingViewport respects clipBehavior', (WidgetTester tester) async { + Widget build(ShrinkWrappingViewport child) { + return Directionality( + textDirection: TextDirection.ltr, + child: child, + ); + } + + await tester.pumpWidget(build( + ShrinkWrappingViewport( + offset: ViewportOffset.zero(), + slivers: [SliverToBoxAdapter(child: Container(height: 2000.0))], + ) + )); + + // 1st, check that the render object has received the default clip behavior. + final RenderShrinkWrappingViewport renderObject = tester.allRenderObjects.whereType().first; + expect(renderObject.clipBehavior, equals(Clip.hardEdge)); + + // 2nd, check that the painting context has received the default clip behavior. + final TestClipPaintingContext context = TestClipPaintingContext(); + renderObject.paint(context, Offset.zero); + expect(context.clipBehavior, equals(Clip.hardEdge)); + + // 3rd, pump a new widget to check that the render object can update its clip behavior. + await tester.pumpWidget(build( + ShrinkWrappingViewport( + offset: ViewportOffset.zero(), + slivers: [SliverToBoxAdapter(child: Container(height: 2000.0))], + clipBehavior: Clip.antiAlias, + ) + )); + expect(renderObject.clipBehavior, equals(Clip.antiAlias)); + + // 4th, check that a non-default clip behavior can be sent to the painting context. + renderObject.paint(context, Offset.zero); + expect(context.clipBehavior, equals(Clip.antiAlias)); + }); +} diff --git a/packages/flutter/test/widgets/single_child_scroll_view_test.dart b/packages/flutter/test/widgets/single_child_scroll_view_test.dart index be96c1ce86..43dd611c85 100644 --- a/packages/flutter/test/widgets/single_child_scroll_view_test.dart +++ b/packages/flutter/test/widgets/single_child_scroll_view_test.dart @@ -8,6 +8,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; +import '../rendering/rendering_tester.dart'; import 'semantics_tester.dart'; class TestScrollPosition extends ScrollPositionWithSingleContext { @@ -37,6 +38,27 @@ class TestScrollController extends ScrollController { } void main() { + testWidgets('SingleChildScrollView respects clipBehavior', (WidgetTester tester) async { + await tester.pumpWidget(SingleChildScrollView(child: Container(height: 2000.0))); + + // 1st, check that the render object has received the default clip behavior. + final dynamic renderObject = tester.allRenderObjects.where((RenderObject o) => o.runtimeType.toString() == '_RenderSingleChildViewport').first; + expect(renderObject.clipBehavior, equals(Clip.hardEdge)); + + // 2nd, check that the painting context has received the default clip behavior. + final TestClipPaintingContext context = TestClipPaintingContext(); + renderObject.paint(context, Offset.zero); + expect(context.clipBehavior, equals(Clip.hardEdge)); + + // 3rd, pump a new widget to check that the render object can update its clip behavior. + await tester.pumpWidget(SingleChildScrollView(clipBehavior: Clip.antiAlias, child: Container(height: 2000.0))); + expect(renderObject.clipBehavior, equals(Clip.antiAlias)); + + // 4th, check that a non-default clip behavior can be sent to the painting context. + renderObject.paint(context, Offset.zero); + expect(context.clipBehavior, equals(Clip.antiAlias)); + }); + testWidgets('SingleChildScrollView control test', (WidgetTester tester) async { await tester.pumpWidget(SingleChildScrollView( child: Container( diff --git a/packages/flutter/test/widgets/stack_test.dart b/packages/flutter/test/widgets/stack_test.dart index 7447140a9a..878ac4a096 100644 --- a/packages/flutter/test/widgets/stack_test.dart +++ b/packages/flutter/test/widgets/stack_test.dart @@ -376,6 +376,15 @@ void main() { expect(renderBox.size.height, equals(12.0)); }); + testWidgets('Can set and update clipBehavior', (WidgetTester tester) async { + await tester.pumpWidget(Stack(textDirection: TextDirection.ltr)); + final RenderStack renderObject = tester.allRenderObjects.whereType().first; + expect(renderObject.clipBehavior, equals(Clip.hardEdge)); + + await tester.pumpWidget(Stack(textDirection: TextDirection.ltr, clipBehavior: Clip.hardEdge)); + expect(renderObject.clipBehavior, equals(Clip.hardEdge)); + }); + testWidgets('IndexedStack with null index', (WidgetTester tester) async { bool tapped; @@ -412,6 +421,7 @@ void main() { textDirection: TextDirection.ltr, child: Center( child: Stack( + clipBehavior: Clip.hardEdge, children: const [ SizedBox( width: 100.0, @@ -442,6 +452,7 @@ void main() { child: Center( child: Stack( overflow: Overflow.visible, + clipBehavior: Clip.none, children: const [ SizedBox( width: 100.0, diff --git a/packages/flutter/test/widgets/wrap_test.dart b/packages/flutter/test/widgets/wrap_test.dart index 46b7b8339f..d94884dbf2 100644 --- a/packages/flutter/test/widgets/wrap_test.dart +++ b/packages/flutter/test/widgets/wrap_test.dart @@ -733,6 +733,7 @@ void main() { await tester.pumpWidget(Wrap( textDirection: TextDirection.ltr, + clipBehavior: Clip.hardEdge, children: const [ SizedBox(width: 500.0, height: 500.0), SizedBox(width: 500.0, height: 500.0), @@ -895,4 +896,13 @@ void main() { const Offset(0.0, 20.0), ]); }); + + testWidgets('Wrap can set and update clipBehavior', (WidgetTester tester) async { + await tester.pumpWidget(Wrap(textDirection: TextDirection.ltr)); + final RenderWrap renderObject = tester.allRenderObjects.whereType().first; + expect(renderObject.clipBehavior, equals(Clip.hardEdge)); + + await tester.pumpWidget(Wrap(textDirection: TextDirection.ltr, clipBehavior: Clip.antiAlias)); + expect(renderObject.clipBehavior, equals(Clip.antiAlias)); + }); }