Skip to content

Commit cd593da

Browse files
authored
Add clipBehavior to widgets with clipRect (flutter#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
1 parent 5ee5490 commit cd593da

35 files changed

+785
-115
lines changed

dev/integration_tests/flutter_gallery/lib/demo/pesto_demo.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ class _PestoLogoState extends State<PestoLogo> {
224224
child: SizedBox(
225225
width: kLogoWidth,
226226
child: Stack(
227-
overflow: Overflow.visible,
227+
clipBehavior: Clip.none,
228228
children: <Widget>[
229229
Positioned.fromRect(
230230
rect: _imageRectTween.lerp(widget.t),

packages/flutter/lib/src/rendering/editable.dart

+20-3
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
227227
EdgeInsets floatingCursorAddedMargin = const EdgeInsets.fromLTRB(4, 4, 4, 5),
228228
TextRange promptRectRange,
229229
Color promptRectColor,
230+
Clip clipBehavior = Clip.hardEdge,
230231
@required this.textSelectionDelegate,
231232
}) : assert(textAlign != null),
232233
assert(textDirection != null, 'RenderEditable created without a textDirection.'),
@@ -257,6 +258,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
257258
assert(devicePixelRatio != null),
258259
assert(selectionHeightStyle != null),
259260
assert(selectionWidthStyle != null),
261+
assert(clipBehavior != null),
260262
_textPainter = TextPainter(
261263
text: text,
262264
textAlign: textAlign,
@@ -290,7 +292,8 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
290292
_obscureText = obscureText,
291293
_readOnly = readOnly,
292294
_forceLine = forceLine,
293-
_promptRectRange = promptRectRange {
295+
_promptRectRange = promptRectRange,
296+
_clipBehavior = clipBehavior {
294297
assert(_showCursor != null);
295298
assert(!_showCursor.value || cursorColor != null);
296299
this.hasFocus = hasFocus ?? false;
@@ -1241,6 +1244,20 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
12411244

12421245
double get _caretMargin => _kCaretGap + cursorWidth;
12431246

1247+
/// {@macro flutter.widgets.Clip}
1248+
///
1249+
/// Defaults to [Clip.hardEdge], and must not be null.
1250+
Clip get clipBehavior => _clipBehavior;
1251+
Clip _clipBehavior = Clip.hardEdge;
1252+
set clipBehavior(Clip value) {
1253+
assert(value != null);
1254+
if (value != _clipBehavior) {
1255+
_clipBehavior = value;
1256+
markNeedsPaint();
1257+
markNeedsSemanticsUpdate();
1258+
}
1259+
}
1260+
12441261
@override
12451262
void describeSemanticsConfiguration(SemanticsConfiguration config) {
12461263
super.describeSemanticsConfiguration(config);
@@ -2133,8 +2150,8 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
21332150
@override
21342151
void paint(PaintingContext context, Offset offset) {
21352152
_layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
2136-
if (_hasVisualOverflow)
2137-
context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintContents);
2153+
if (_hasVisualOverflow && clipBehavior != Clip.none)
2154+
context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintContents, clipBehavior: clipBehavior);
21382155
else
21392156
_paintContents(context, offset);
21402157
_paintHandleLayers(context, getEndpointsForSelection(selection));

packages/flutter/lib/src/rendering/flex.dart

+24-3
Original file line numberDiff line numberDiff line change
@@ -277,17 +277,20 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
277277
TextDirection textDirection,
278278
VerticalDirection verticalDirection = VerticalDirection.down,
279279
TextBaseline textBaseline,
280+
Clip clipBehavior = Clip.none,
280281
}) : assert(direction != null),
281282
assert(mainAxisAlignment != null),
282283
assert(mainAxisSize != null),
283284
assert(crossAxisAlignment != null),
285+
assert(clipBehavior != null),
284286
_direction = direction,
285287
_mainAxisAlignment = mainAxisAlignment,
286288
_mainAxisSize = mainAxisSize,
287289
_crossAxisAlignment = crossAxisAlignment,
288290
_textDirection = textDirection,
289291
_verticalDirection = verticalDirection,
290-
_textBaseline = textBaseline {
292+
_textBaseline = textBaseline,
293+
_clipBehavior = clipBehavior {
291294
addAll(children);
292295
}
293296

@@ -474,6 +477,20 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
474477
// are treated as not overflowing.
475478
bool get _hasOverflow => _overflow > precisionErrorTolerance;
476479

480+
/// {@macro flutter.widgets.Clip}
481+
///
482+
/// Defaults to [Clip.none], and must not be null.
483+
Clip get clipBehavior => _clipBehavior;
484+
Clip _clipBehavior = Clip.none;
485+
set clipBehavior(Clip value) {
486+
assert(value != null);
487+
if (value != _clipBehavior) {
488+
_clipBehavior = value;
489+
markNeedsPaint();
490+
markNeedsSemanticsUpdate();
491+
}
492+
}
493+
477494
@override
478495
void setupParentData(RenderBox child) {
479496
if (child.parentData is! FlexParentData)
@@ -955,8 +972,12 @@ class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, Fl
955972
if (size.isEmpty)
956973
return;
957974

958-
// We have overflow. Clip it.
959-
context.pushClipRect(needsCompositing, offset, Offset.zero & size, defaultPaint);
975+
if (clipBehavior == Clip.none) {
976+
defaultPaint(context, offset);
977+
} else {
978+
// We have overflow and the clipBehavior isn't none. Clip it.
979+
context.pushClipRect(needsCompositing, offset, Offset.zero & size, defaultPaint, clipBehavior: clipBehavior);
980+
}
960981

961982
assert(() {
962983
// Only set this if it's null to save work. It gets reset to null if the

packages/flutter/lib/src/rendering/list_wheel_viewport.dart

+29-37
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,8 @@ class RenderListWheelViewport
143143
double overAndUnderCenterOpacity = 1,
144144
@required double itemExtent,
145145
double squeeze = 1,
146-
bool clipToSize = true,
147146
bool renderChildrenOutsideViewport = false,
147+
Clip clipBehavior = Clip.none,
148148
List<RenderBox> children,
149149
}) : assert(childManager != null),
150150
assert(offset != null),
@@ -163,11 +163,11 @@ class RenderListWheelViewport
163163
assert(squeeze != null),
164164
assert(squeeze > 0),
165165
assert(itemExtent > 0),
166-
assert(clipToSize != null),
167166
assert(renderChildrenOutsideViewport != null),
167+
assert(clipBehavior != null),
168168
assert(
169-
!renderChildrenOutsideViewport || !clipToSize,
170-
clipToSizeAndRenderChildrenOutsideViewportConflict,
169+
!renderChildrenOutsideViewport || clipBehavior == Clip.none,
170+
clipBehaviorAndRenderChildrenOutsideViewportConflict,
171171
),
172172
_offset = offset,
173173
_diameterRatio = diameterRatio,
@@ -178,8 +178,8 @@ class RenderListWheelViewport
178178
_overAndUnderCenterOpacity = overAndUnderCenterOpacity,
179179
_itemExtent = itemExtent,
180180
_squeeze = squeeze,
181-
_clipToSize = clipToSize,
182-
_renderChildrenOutsideViewport = renderChildrenOutsideViewport {
181+
_renderChildrenOutsideViewport = renderChildrenOutsideViewport,
182+
_clipBehavior = clipBehavior {
183183
addAll(children);
184184
}
185185

@@ -199,10 +199,10 @@ class RenderListWheelViewport
199199
'be clipped in the z-axis and therefore not renderable. Value must be '
200200
'between 0 and 0.01.';
201201

202-
/// An error message to show when [clipToSize] and [renderChildrenOutsideViewport]
202+
/// An error message to show when [clipBehavior] and [renderChildrenOutsideViewport]
203203
/// are set to conflicting values.
204-
static const String clipToSizeAndRenderChildrenOutsideViewportConflict =
205-
'Cannot renderChildrenOutsideViewport and clipToSize since children '
204+
static const String clipBehaviorAndRenderChildrenOutsideViewportConflict =
205+
'Cannot renderChildrenOutsideViewport and clip since children '
206206
'rendered outside will be clipped anyway.';
207207

208208
/// The delegate that manages the children of this object.
@@ -441,46 +441,23 @@ class RenderListWheelViewport
441441
markNeedsSemanticsUpdate();
442442
}
443443

444-
/// {@template flutter.rendering.wheelList.clipToSize}
445-
/// Whether to clip painted children to the inside of this viewport.
446-
///
447-
/// Defaults to [true]. Must not be null.
448-
///
449-
/// If this is false and [renderChildrenOutsideViewport] is false, the
450-
/// first and last children may be painted partly outside of this scroll view.
451-
/// {@endtemplate}
452-
bool get clipToSize => _clipToSize;
453-
bool _clipToSize;
454-
set clipToSize(bool value) {
455-
assert(value != null);
456-
assert(
457-
!renderChildrenOutsideViewport || !clipToSize,
458-
clipToSizeAndRenderChildrenOutsideViewportConflict,
459-
);
460-
if (value == _clipToSize)
461-
return;
462-
_clipToSize = value;
463-
markNeedsPaint();
464-
markNeedsSemanticsUpdate();
465-
}
466-
467444
/// {@template flutter.rendering.wheelList.renderChildrenOutsideViewport}
468445
/// Whether to paint children inside the viewport only.
469446
///
470447
/// If false, every child will be painted. However the [Scrollable] is still
471448
/// the size of the viewport and detects gestures inside only.
472449
///
473-
/// Defaults to [false]. Must not be null. Cannot be true if [clipToSize]
474-
/// is also true since children outside the viewport will be clipped, and
450+
/// Defaults to [false]. Must not be null. Cannot be true if [clipBehavior]
451+
/// is not [Clip.none] since children outside the viewport will be clipped, and
475452
/// therefore cannot render children outside the viewport.
476453
/// {@endtemplate}
477454
bool get renderChildrenOutsideViewport => _renderChildrenOutsideViewport;
478455
bool _renderChildrenOutsideViewport;
479456
set renderChildrenOutsideViewport(bool value) {
480457
assert(value != null);
481458
assert(
482-
!renderChildrenOutsideViewport || !clipToSize,
483-
clipToSizeAndRenderChildrenOutsideViewportConflict,
459+
!renderChildrenOutsideViewport || clipBehavior == Clip.none,
460+
clipBehaviorAndRenderChildrenOutsideViewportConflict,
484461
);
485462
if (value == _renderChildrenOutsideViewport)
486463
return;
@@ -489,6 +466,20 @@ class RenderListWheelViewport
489466
markNeedsSemanticsUpdate();
490467
}
491468

469+
/// {@macro flutter.widgets.Clip}
470+
///
471+
/// Defaults to [Clip.hardEdge], and must not be null.
472+
Clip get clipBehavior => _clipBehavior;
473+
Clip _clipBehavior = Clip.hardEdge;
474+
set clipBehavior(Clip value) {
475+
assert(value != null);
476+
if (value != _clipBehavior) {
477+
_clipBehavior = value;
478+
markNeedsPaint();
479+
markNeedsSemanticsUpdate();
480+
}
481+
}
482+
492483
void _hasScrolled() {
493484
markNeedsLayout();
494485
markNeedsSemanticsUpdate();
@@ -787,12 +778,13 @@ class RenderListWheelViewport
787778
@override
788779
void paint(PaintingContext context, Offset offset) {
789780
if (childCount > 0) {
790-
if (_clipToSize && _shouldClipAtCurrentOffset()) {
781+
if (_shouldClipAtCurrentOffset() && clipBehavior != Clip.none) {
791782
context.pushClipRect(
792783
needsCompositing,
793784
offset,
794785
Offset.zero & size,
795786
_paintVisibleChildren,
787+
clipBehavior: clipBehavior,
796788
);
797789
} else {
798790
_paintVisibleChildren(context, offset);

packages/flutter/lib/src/rendering/proxy_box.dart

+19-2
Original file line numberDiff line numberDiff line change
@@ -2292,11 +2292,14 @@ class RenderFittedBox extends RenderProxyBox {
22922292
AlignmentGeometry alignment = Alignment.center,
22932293
TextDirection textDirection,
22942294
RenderBox child,
2295+
Clip clipBehavior = Clip.none,
22952296
}) : assert(fit != null),
22962297
assert(alignment != null),
2298+
assert(clipBehavior != null),
22972299
_fit = fit,
22982300
_alignment = alignment,
22992301
_textDirection = textDirection,
2302+
_clipBehavior = clipBehavior,
23002303
super(child);
23012304

23022305
Alignment _resolvedAlignment;
@@ -2373,6 +2376,20 @@ class RenderFittedBox extends RenderProxyBox {
23732376
bool _hasVisualOverflow;
23742377
Matrix4 _transform;
23752378

2379+
/// {@macro flutter.widgets.Clip}
2380+
///
2381+
/// Defaults to [Clip.none], and must not be null.
2382+
Clip get clipBehavior => _clipBehavior;
2383+
Clip _clipBehavior = Clip.none;
2384+
set clipBehavior(Clip value) {
2385+
assert(value != null);
2386+
if (value != _clipBehavior) {
2387+
_clipBehavior = value;
2388+
markNeedsPaint();
2389+
markNeedsSemanticsUpdate();
2390+
}
2391+
}
2392+
23762393
void _clearPaintData() {
23772394
_hasVisualOverflow = null;
23782395
_transform = null;
@@ -2418,9 +2435,9 @@ class RenderFittedBox extends RenderProxyBox {
24182435
return;
24192436
_updatePaintData();
24202437
if (child != null) {
2421-
if (_hasVisualOverflow)
2438+
if (_hasVisualOverflow && clipBehavior != Clip.none)
24222439
layer = context.pushClipRect(needsCompositing, offset, Offset.zero & size, _paintChildWithTransform,
2423-
oldLayer: layer is ClipRectLayer ? layer as ClipRectLayer : null);
2440+
oldLayer: layer is ClipRectLayer ? layer as ClipRectLayer : null, clipBehavior: clipBehavior);
24242441
else
24252442
layer = _paintChildWithTransform(context, offset);
24262443
}

packages/flutter/lib/src/rendering/shifted_box.dart

+23-2
Original file line numberDiff line numberDiff line change
@@ -626,8 +626,11 @@ class RenderUnconstrainedBox extends RenderAligningShiftedBox with DebugOverflow
626626
@required TextDirection textDirection,
627627
Axis constrainedAxis,
628628
RenderBox child,
629+
Clip clipBehavior = Clip.none,
629630
}) : assert(alignment != null),
631+
assert(clipBehavior != null),
630632
_constrainedAxis = constrainedAxis,
633+
_clipBehavior = clipBehavior,
631634
super.mixin(alignment, textDirection, child);
632635

633636
/// The axis to retain constraints on, if any.
@@ -649,6 +652,20 @@ class RenderUnconstrainedBox extends RenderAligningShiftedBox with DebugOverflow
649652
Rect _overflowChildRect = Rect.zero;
650653
bool _isOverflowing = false;
651654

655+
/// {@macro flutter.widgets.Clip}
656+
///
657+
/// Defaults to [Clip.none], and must not be null.
658+
Clip get clipBehavior => _clipBehavior;
659+
Clip _clipBehavior = Clip.none;
660+
set clipBehavior(Clip value) {
661+
assert(value != null);
662+
if (value != _clipBehavior) {
663+
_clipBehavior = value;
664+
markNeedsPaint();
665+
markNeedsSemanticsUpdate();
666+
}
667+
}
668+
652669
@override
653670
void performLayout() {
654671
final BoxConstraints constraints = this.constraints;
@@ -694,8 +711,12 @@ class RenderUnconstrainedBox extends RenderAligningShiftedBox with DebugOverflow
694711
return;
695712
}
696713

697-
// We have overflow. Clip it.
698-
context.pushClipRect(needsCompositing, offset, Offset.zero & size, super.paint);
714+
if (clipBehavior == Clip.none) {
715+
super.paint(context, offset);
716+
} else {
717+
// We have overflow and the clipBehavior isn't none. Clip it.
718+
context.pushClipRect(needsCompositing, offset, Offset.zero & size, super.paint, clipBehavior: clipBehavior);
719+
}
699720

700721
// Display the overflow indicator.
701722
assert(() {

0 commit comments

Comments
 (0)