Unverified Commit 13bfa735 authored by liyuqian's avatar liyuqian Committed by GitHub

Set default clipBehavior to Clip.none and update tests (#20205)

This fixes https://github.com/flutter/flutter/issues/18057
parent ace8abf3
......@@ -29,8 +29,8 @@ import 'theme_data.dart';
class RawMaterialButton extends StatefulWidget {
/// Create a button based on [Semantics], [Material], and [InkWell] widgets.
///
/// The [shape], [elevation], [padding], and [constraints] arguments
/// must not be null.
/// The [shape], [elevation], [padding], [constraints], and [clipBehavior]
/// arguments must not be null.
const RawMaterialButton({
Key key,
@required this.onPressed,
......@@ -46,6 +46,7 @@ class RawMaterialButton extends StatefulWidget {
this.constraints = const BoxConstraints(minWidth: 88.0, minHeight: 36.0),
this.shape = const RoundedRectangleBorder(),
this.animationDuration = kThemeChangeDuration,
this.clipBehavior = Clip.none,
MaterialTapTargetSize materialTapTargetSize,
this.child,
}) : this.materialTapTargetSize = materialTapTargetSize ?? MaterialTapTargetSize.padded,
......@@ -56,6 +57,7 @@ class RawMaterialButton extends StatefulWidget {
assert(padding != null),
assert(constraints != null),
assert(animationDuration != null),
assert(clipBehavior != null),
super(key: key);
/// Called when the button is tapped or otherwise activated.
......@@ -148,6 +150,9 @@ class RawMaterialButton extends StatefulWidget {
/// * [MaterialTapTargetSize], for a description of how this affects tap targets.
final MaterialTapTargetSize materialTapTargetSize;
/// {@macro flutter.widgets.Clip}
final Clip clipBehavior;
@override
_RawMaterialButtonState createState() => new _RawMaterialButtonState();
}
......@@ -177,6 +182,7 @@ class _RawMaterialButtonState extends State<RawMaterialButton> {
color: widget.fillColor,
type: widget.fillColor == null ? MaterialType.transparency : MaterialType.button,
animationDuration: widget.animationDuration,
clipBehavior: widget.clipBehavior,
child: new InkWell(
onHighlightChanged: _handleHighlightChanged,
splashColor: widget.splashColor,
......@@ -245,6 +251,8 @@ class MaterialButton extends StatelessWidget {
/// Rather than creating a material button directly, consider using
/// [FlatButton] or [RaisedButton]. To create a custom Material button
/// consider using [RawMaterialButton].
///
/// The [clipBehavior] argument must not be null.
const MaterialButton({
Key key,
this.colorBrightness,
......@@ -259,9 +267,10 @@ class MaterialButton extends StatelessWidget {
this.height,
this.padding,
this.materialTapTargetSize,
this.clipBehavior = Clip.none,
@required this.onPressed,
this.child
}) : super(key: key);
}) : assert(clipBehavior != null), super(key: key);
/// The theme brightness to use for this button.
///
......@@ -373,6 +382,9 @@ class MaterialButton extends StatelessWidget {
/// * [MaterialTapTargetSize], for a description of how this affects tap targets.
final MaterialTapTargetSize materialTapTargetSize;
/// {@macro flutter.widgets.Clip}
final Clip clipBehavior;
/// Whether the button is enabled or disabled. Buttons are disabled by default. To
/// enable a button, set its [onPressed] property to a non-null value.
bool get enabled => onPressed != null;
......@@ -433,6 +445,7 @@ class MaterialButton extends StatelessWidget {
shape: buttonTheme.shape,
child: child,
materialTapTargetSize: materialTapTargetSize ?? theme.materialTapTargetSize,
clipBehavior: clipBehavior,
);
}
......
......@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' show Clip, defaultClipBehavior; // ignore: deprecated_member_use
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
......@@ -174,7 +172,7 @@ class Material extends StatefulWidget {
this.textStyle,
this.borderRadius,
this.shape,
this.clipBehavior = defaultClipBehavior, // ignore: deprecated_member_use
this.clipBehavior = Clip.none,
this.animationDuration = kThemeChangeDuration,
this.child,
}) : assert(type != null),
......@@ -370,14 +368,16 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin {
}
static Widget _transparentInterior({ShapeBorder shape, Clip clipBehavior, Widget contents}) {
final _ShapeBorderPaint child = new _ShapeBorderPaint(
child: contents,
shape: shape,
);
if (clipBehavior == Clip.none) {
return child;
}
return new ClipPath(
child: new _ShapeBorderPaint(
child: contents,
shape: shape,
),
clipper: new ShapeBorderClipper(
shape: shape,
),
child: child,
clipper: new ShapeBorderClipper(shape: shape),
clipBehavior: clipBehavior,
);
}
......@@ -598,7 +598,7 @@ class _MaterialInterior extends ImplicitlyAnimatedWidget {
Key key,
@required this.child,
@required this.shape,
this.clipBehavior = defaultClipBehavior, // ignore: deprecated_member_use
this.clipBehavior = Clip.none,
@required this.elevation,
@required this.color,
@required this.shadowColor,
......
......@@ -51,7 +51,7 @@ const Duration _kElevationDuration = Duration(milliseconds: 75);
class OutlineButton extends StatefulWidget {
/// Create a filled button.
///
/// The [highlightElevation], and [borderWidth]
/// The [highlightElevation], [borderWidth], and [clipBehavior]
/// arguments must not be null.
const OutlineButton({
Key key,
......@@ -68,8 +68,10 @@ class OutlineButton extends StatefulWidget {
this.highlightedBorderColor,
this.padding,
this.shape,
this.clipBehavior = Clip.none,
this.child,
}) : assert(highlightElevation != null && highlightElevation >= 0.0),
assert(clipBehavior != null),
super(key: key);
/// Create an outline button from a pair of widgets that serve as the button's
......@@ -78,7 +80,8 @@ class OutlineButton extends StatefulWidget {
/// The icon and label are arranged in a row and padded by 12 logical pixels
/// at the start, and 16 at the end, with an 8 pixel gap in between.
///
/// The [highlightElevation], [icon], and [label] must not be null.
/// The [highlightElevation], [icon], [label], and [clipBehavior] must not be
/// null.
OutlineButton.icon({
Key key,
@required this.onPressed,
......@@ -93,11 +96,13 @@ class OutlineButton extends StatefulWidget {
this.disabledBorderColor,
this.highlightedBorderColor,
this.shape,
this.clipBehavior = Clip.none,
@required Widget icon,
@required Widget label,
}) : assert(highlightElevation != null && highlightElevation >= 0.0),
assert(icon != null),
assert(label != null),
assert(clipBehavior != null),
padding = const EdgeInsetsDirectional.only(start: 12.0, end: 16.0),
child = new Row(
mainAxisSize: MainAxisSize.min,
......@@ -223,6 +228,9 @@ class OutlineButton extends StatefulWidget {
/// shape as well.
final ShapeBorder shape;
/// {@macro flutter.widgets.Clip}
final Clip clipBehavior;
/// The button's label.
///
/// Often a [Text] widget in all caps.
......@@ -415,6 +423,7 @@ class _OutlineButtonState extends State<OutlineButton> with SingleTickerProvider
shape: widget.shape ?? buttonTheme.shape,
side: _getOutline(theme, buttonTheme),
),
clipBehavior: widget.clipBehavior,
animationDuration: _kElevationDuration,
child: widget.child,
);
......
......@@ -44,8 +44,8 @@ import 'theme_data.dart';
class RaisedButton extends StatelessWidget {
/// Create a filled button.
///
/// The [elevation], [highlightElevation], and [disabledElevation]
/// arguments must not be null.
/// The [elevation], [highlightElevation], [disabledElevation], and
/// [clipBehavior] arguments must not be null.
const RaisedButton({
Key key,
@required this.onPressed,
......@@ -63,6 +63,7 @@ class RaisedButton extends StatelessWidget {
this.disabledElevation = 0.0,
this.padding,
this.shape,
this.clipBehavior = Clip.none,
this.materialTapTargetSize,
this.animationDuration = kThemeChangeDuration,
this.child,
......@@ -70,6 +71,7 @@ class RaisedButton extends StatelessWidget {
assert(highlightElevation != null),
assert(disabledElevation != null),
assert(animationDuration != null),
assert(clipBehavior != null),
super(key: key);
/// Create a filled button from a pair of widgets that serve as the button's
......@@ -78,8 +80,8 @@ class RaisedButton extends StatelessWidget {
/// The icon and label are arranged in a row and padded by 12 logical pixels
/// at the start, and 16 at the end, with an 8 pixel gap in between.
///
/// The [elevation], [highlightElevation], [disabledElevation], [icon], and
/// [label] arguments must not be null.
/// The [elevation], [highlightElevation], [disabledElevation], [icon],
/// [label], and [clipBehavior] arguments must not be null.
RaisedButton.icon({
Key key,
@required this.onPressed,
......@@ -96,6 +98,7 @@ class RaisedButton extends StatelessWidget {
this.highlightElevation = 8.0,
this.disabledElevation = 0.0,
this.shape,
this.clipBehavior = Clip.none,
this.materialTapTargetSize,
this.animationDuration = kThemeChangeDuration,
@required Widget icon,
......@@ -106,6 +109,7 @@ class RaisedButton extends StatelessWidget {
assert(icon != null),
assert(label != null),
assert(animationDuration != null),
assert(clipBehavior != null),
padding = const EdgeInsetsDirectional.only(start: 12.0, end: 16.0),
child = new Row(
mainAxisSize: MainAxisSize.min,
......@@ -287,6 +291,9 @@ class RaisedButton extends StatelessWidget {
/// shape as well.
final ShapeBorder shape;
/// {@macro flutter.widgets.Clip}
final Clip clipBehavior;
/// Defines the duration of animated changes for [shape] and [elevation].
///
/// The default value is [kThemeChangeDuration].
......@@ -390,6 +397,7 @@ class RaisedButton extends StatelessWidget {
padding: padding ?? buttonTheme.padding,
constraints: buttonTheme.constraints,
shape: shape ?? buttonTheme.shape,
clipBehavior: clipBehavior,
animationDuration: animationDuration,
child: child,
materialTapTargetSize: materialTapTargetSize ?? theme.materialTapTargetSize,
......
......@@ -8,6 +8,7 @@ export 'dart:ui' show
BlendMode,
BlurStyle,
Canvas,
Clip,
Color,
ColorFilter,
FilterQuality,
......
......@@ -5,7 +5,6 @@
import 'dart:async';
import 'dart:collection';
import 'dart:ui' as ui show Image, ImageFilter, Picture, Scene, SceneBuilder;
import 'dart:ui' show Clip, Offset, defaultClipBehavior; // ignore: deprecated_member_use
import 'package:flutter/foundation.dart';
import 'package:flutter/painting.dart';
......@@ -626,7 +625,11 @@ class ClipRectLayer extends ContainerLayer {
/// (as described at [Layer]).
Rect clipRect;
/// {@macro flutter.clipper.clipBehavior}
/// {@template flutter.clipper.clipBehavior}
/// Controls how to clip (default to [Clip.antiAlias]).
///
/// [Clip.none] is not allowed here.
/// {@endtemplate}
Clip get clipBehavior => _clipBehavior;
Clip _clipBehavior;
set clipBehavior(Clip value) {
......@@ -967,7 +970,7 @@ class PhysicalModelLayer extends ContainerLayer {
/// The [clipPath], [elevation], and [color] arguments must not be null.
PhysicalModelLayer({
@required this.clipPath,
this.clipBehavior = defaultClipBehavior, // ignore: deprecated_member_use
this.clipBehavior = Clip.none,
@required this.elevation,
@required this.color,
@required this.shadowColor,
......
......@@ -4,7 +4,6 @@
import 'dart:developer';
import 'dart:ui' as ui show PictureRecorder;
import 'dart:ui' show Clip;
import 'package:flutter/animation.dart';
import 'package:flutter/foundation.dart';
......
......@@ -5,7 +5,6 @@
import 'dart:async';
import 'dart:ui' as ui show ImageFilter, Gradient, Image;
import 'dart:ui' show Clip, defaultClipBehavior; // ignore: deprecated_member_use
import 'package:flutter/animation.dart';
import 'package:flutter/foundation.dart';
......@@ -1118,8 +1117,8 @@ abstract class _RenderCustomClip<T> extends RenderProxyBox {
_RenderCustomClip({
RenderBox child,
CustomClipper<T> clipper,
this.clipBehavior = defaultClipBehavior, // ignore: deprecated_member_use
}) : _clipper = clipper, assert(clipBehavior != null), assert(clipBehavior != Clip.none), super(child);
this.clipBehavior = Clip.antiAlias,
}) : _clipper = clipper, assert(clipBehavior != null), super(child);
/// If non-null, determines which clip to use on the child.
CustomClipper<T> get clipper => _clipper;
......@@ -1162,11 +1161,6 @@ abstract class _RenderCustomClip<T> extends RenderProxyBox {
T get _defaultClip;
T _clip;
/// {@template flutter.clipper.clipBehavior}
/// Controls how to clip (default to [Clip.antiAlias]).
///
/// [Clip.none] is not allowed here.
/// {@endtemplate}
final Clip clipBehavior;
@override
......@@ -1227,6 +1221,8 @@ class RenderClipRect extends _RenderCustomClip<Rect> {
///
/// If [clipper] is null, the clip will match the layout size and position of
/// the child.
///
/// The [clipBehavior] cannot be [Clip.none].
RenderClipRect({
RenderBox child,
CustomClipper<Rect> clipper,
......@@ -1280,12 +1276,14 @@ class RenderClipRRect extends _RenderCustomClip<RRect> {
/// right-angled corners.
///
/// If [clipper] is non-null, then [borderRadius] is ignored.
///
/// The [clipBehavior] cannot be [Clip.none].
RenderClipRRect({
RenderBox child,
BorderRadius borderRadius = BorderRadius.zero,
CustomClipper<RRect> clipper,
Clip clipBehavior = Clip.antiAlias,
}) : _borderRadius = borderRadius, super(child: child, clipper: clipper, clipBehavior: clipBehavior) {
}) : assert(clipBehavior != Clip.none), _borderRadius = borderRadius, super(child: child, clipper: clipper, clipBehavior: clipBehavior) {
assert(_borderRadius != null || clipper != null);
}
......@@ -1350,11 +1348,13 @@ class RenderClipOval extends _RenderCustomClip<Rect> {
///
/// If [clipper] is null, the oval will be inscribed into the layout size and
/// position of the child.
///
/// The [clipBehavior] cannot be [Clip.none].
RenderClipOval({
RenderBox child,
CustomClipper<Rect> clipper,
Clip clipBehavior = Clip.antiAlias,
}) : super(child: child, clipper: clipper, clipBehavior: clipBehavior);
}) : assert(clipBehavior != Clip.none), super(child: child, clipper: clipper, clipBehavior: clipBehavior);
Rect _cachedRect;
Path _cachedPath;
......@@ -1423,11 +1423,13 @@ class RenderClipPath extends _RenderCustomClip<Path> {
/// size and location of the child. However, rather than use this default,
/// consider using a [RenderClipRect], which can achieve the same effect more
/// efficiently.
///
/// The [clipBehavior] cannot be [Clip.none].
RenderClipPath({
RenderBox child,
CustomClipper<Path> clipper,
Clip clipBehavior = Clip.antiAlias,
}) : super(child: child, clipper: clipper, clipBehavior: clipBehavior);
}) : assert(clipBehavior != Clip.none), super(child: child, clipper: clipper, clipBehavior: clipBehavior);
@override
Path get _defaultClip => new Path()..addRect(Offset.zero & size);
......@@ -1475,7 +1477,7 @@ abstract class _RenderPhysicalModelBase<T> extends _RenderCustomClip<T> {
@required double elevation,
@required Color color,
@required Color shadowColor,
Clip clipBehavior = defaultClipBehavior, // ignore: deprecated_member_use
Clip clipBehavior = Clip.none,
CustomClipper<T> clipper,
}) : assert(elevation != null),
assert(color != null),
......@@ -1554,7 +1556,7 @@ class RenderPhysicalModel extends _RenderPhysicalModelBase<RRect> {
RenderPhysicalModel({
RenderBox child,
BoxShape shape = BoxShape.rectangle,
Clip clipBehavior = defaultClipBehavior, // ignore: deprecated_member_use
Clip clipBehavior = Clip.none,
BorderRadius borderRadius,
double elevation = 0.0,
@required Color color,
......@@ -1713,7 +1715,7 @@ class RenderPhysicalShape extends _RenderPhysicalModelBase<Path> {
RenderPhysicalShape({
RenderBox child,
@required CustomClipper<Path> clipper,
Clip clipBehavior = defaultClipBehavior, // ignore: deprecated_member_use
Clip clipBehavior = Clip.none,
double elevation = 0.0,
@required Color color,
Color shadowColor = const Color(0xFF000000),
......
......@@ -3,7 +3,6 @@
// found in the LICENSE file.
import 'dart:ui' as ui show Image, ImageFilter;
import 'dart:ui' show Clip, defaultClipBehavior; // ignore: deprecated_member_use
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
......@@ -692,7 +691,7 @@ class PhysicalModel extends SingleChildRenderObjectWidget {
const PhysicalModel({
Key key,
this.shape = BoxShape.rectangle,
this.clipBehavior = defaultClipBehavior, // ignore: deprecated_member_use
this.clipBehavior = Clip.none,
this.borderRadius,
this.elevation = 0.0,
@required this.color,
......@@ -780,7 +779,7 @@ class PhysicalShape extends SingleChildRenderObjectWidget {
const PhysicalShape({
Key key,
@required this.clipper,
this.clipBehavior = defaultClipBehavior, // ignore: deprecated_member_use
this.clipBehavior = Clip.none,
this.elevation = 0.0,
@required this.color,
this.shadowColor = const Color(0xFF000000),
......
......@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' show Clip, defaultClipBehavior; // ignore: deprecated_member_use
import 'package:flutter/animation.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
......@@ -1183,7 +1181,7 @@ class AnimatedPhysicalModel extends ImplicitlyAnimatedWidget {
Key key,
@required this.child,
@required this.shape,
this.clipBehavior = defaultClipBehavior, // ignore: deprecated_member_use
this.clipBehavior = Clip.none,
this.borderRadius = BorderRadius.zero,
@required this.elevation,
@required this.color,
......
......@@ -179,6 +179,7 @@ void main() {
splashColor: directSplashColor,
highlightColor: directHighlightColor,
onPressed: () { /* to make sure the button is enabled */ },
clipBehavior: Clip.antiAlias,
),
),
);
......@@ -224,6 +225,7 @@ void main() {
child: new Center(
child: new MaterialButton(
onPressed: () { /* to make sure the button is enabled */ },
clipBehavior: Clip.antiAlias,
),
),
);
......@@ -280,6 +282,35 @@ void main() {
await gesture.up();
});
testWidgets('MaterialButton has no clip by default', (WidgetTester tester) async {
final GlobalKey buttonKey = new GlobalKey();
final Widget buttonWidget = new Material(
child: new Center(
child: new MaterialButton(
key: buttonKey,
onPressed: () { /* to make sure the button is enabled */ },
),
),
);
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new Theme(
data: new ThemeData(
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
child: buttonWidget,
),
),
);
expect(
tester.renderObject(find.byKey(buttonKey)),
paintsExactlyCountTimes(#clipPath, 0)
);
});
testWidgets('Disabled MaterialButton has same semantic size as enabled and exposes disabled semantics', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
......
......@@ -171,27 +171,42 @@ void main() {
});
group('Transparency clipping', () {
testWidgets('clips to bounding rect by default', (WidgetTester tester) async {
testWidgets('No clip by default', (WidgetTester tester) async {
final GlobalKey materialKey = new GlobalKey();
await tester.pumpWidget(
new Material(
key: materialKey,
type: MaterialType.transparency,
child: const SizedBox(width: 100.0, height: 100.0),
)
);
expect(find.byKey(materialKey), hasNoImmediateClip);
});
testWidgets('clips to bounding rect by default given Clip.antiAlias', (WidgetTester tester) async {
final GlobalKey materialKey = new GlobalKey();
await tester.pumpWidget(
new Material(
key: materialKey,
type: MaterialType.transparency,
child: const SizedBox(width: 100.0, height: 100.0)
child: const SizedBox(width: 100.0, height: 100.0),
clipBehavior: Clip.antiAlias,
)
);
expect(find.byKey(materialKey), clipsWithBoundingRect);
});
testWidgets('clips to rounded rect when borderRadius provided', (WidgetTester tester) async {
testWidgets('clips to rounded rect when borderRadius provided given Clip.antiAlias', (WidgetTester tester) async {
final GlobalKey materialKey = new GlobalKey();
await tester.pumpWidget(
new Material(
key: materialKey,
type: MaterialType.transparency,
borderRadius: const BorderRadius.all(Radius.circular(10.0)),
child: const SizedBox(width: 100.0, height: 100.0)
child: const SizedBox(width: 100.0, height: 100.0),
clipBehavior: Clip.antiAlias,
)
);
......@@ -203,14 +218,15 @@ void main() {
);
});
testWidgets('clips to shape when provided', (WidgetTester tester) async {
testWidgets('clips to shape when provided given Clip.antiAlias', (WidgetTester tester) async {
final GlobalKey materialKey = new GlobalKey();
await tester.pumpWidget(
new Material(
key: materialKey,
type: MaterialType.transparency,
shape: const StadiumBorder(),
child: const SizedBox(width: 100.0, height: 100.0)
child: const SizedBox(width: 100.0, height: 100.0),
clipBehavior: Clip.antiAlias,
)
);
......
......@@ -60,6 +60,7 @@ void main() {
alignment: Alignment.topLeft,
child: OutlineButton(
shape: const RoundedRectangleBorder(), // default border radius is 0
clipBehavior: Clip.antiAlias,
color: fillColor,
highlightedBorderColor: highlightedBorderColor,
disabledBorderColor: disabledBorderColor,
......@@ -135,6 +136,29 @@ void main() {
..path(color: borderColor, strokeWidth: borderWidth));
});
testWidgets('OutlineButton has no clip by default', (WidgetTester tester) async {
final GlobalKey buttonKey = new GlobalKey();
await tester.pumpWidget(
new Directionality(
textDirection: TextDirection.ltr,
child: new Material(
child: new Center(
child: new OutlineButton(
key: buttonKey,
onPressed: () { },
child: const Text('ABC'),
),
),
),
),
);
expect(
tester.renderObject(find.byKey(buttonKey)),
paintsExactlyCountTimes(#clipPath, 0)
);
});
testWidgets('OutlineButton contributes semantics', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget(
......
......@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' show Clip;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
......
......@@ -431,7 +431,70 @@ void main() {
);
});
testWidgets('PhysicalModel painting', (WidgetTester tester) async {
Center genPhysicalModel(Clip clipBehavior) {
return new Center(
child: new RepaintBoundary(
child: new Container(
color: Colors.white,
child: new Padding(
padding: const EdgeInsets.all(100.0),
child: new SizedBox(
height: 100.0,
width: 100.0,
child: new Transform.rotate(
angle: 1.0, // radians
child: new PhysicalModel(
borderRadius: new BorderRadius.circular(20.0),
color: Colors.red,
clipBehavior: clipBehavior,
child: new Container(
color: Colors.white,
child: new RepaintBoundary(
child: new Center(
child: new Container(
color: Colors.black,
height: 10.0,
width: 10.0,
),
),
),
),
),
),
),
),
),
),
);
}
testWidgets('PhysicalModel painting with Clip.antiAlias', (WidgetTester tester) async {
await tester.pumpWidget(genPhysicalModel(Clip.antiAlias));
await expectLater(
find.byType(RepaintBoundary).first,
matchesGoldenFile('clip.PhysicalModel.antiAlias.png'),
);
});
testWidgets('PhysicalModel painting with Clip.hardEdge', (WidgetTester tester) async {
await tester.pumpWidget(genPhysicalModel(Clip.hardEdge));
await expectLater(
find.byType(RepaintBoundary).first,
matchesGoldenFile('clip.PhysicalModel.hardEdge.png'),
);
});
// There will be bleeding edges on the rect edges, but there shouldn't be any bleeding edges on the
// round corners.
testWidgets('PhysicalModel painting with Clip.antiAliasWithSaveLayer', (WidgetTester tester) async {
await tester.pumpWidget(genPhysicalModel(Clip.antiAliasWithSaveLayer));
await expectLater(
find.byType(RepaintBoundary).first,
matchesGoldenFile('clip.PhysicalModel.antiAliasWithSaveLayer.png'),
);
});
testWidgets('Default PhysicalModel painting', (WidgetTester tester) async {
await tester.pumpWidget(
new Center(
child: new RepaintBoundary(
......@@ -469,7 +532,72 @@ void main() {
);
await expectLater(
find.byType(RepaintBoundary).first,
matchesGoldenFile('clip.PhysicalModel.1.png'),
matchesGoldenFile('clip.PhysicalModel.default.png'),
);
});
Center genPhysicalShape(Clip clipBehavior) {
return new Center(
child: new RepaintBoundary(
child: new Container(
color: Colors.white,
child: new Padding(
padding: const EdgeInsets.all(100.0),
child: new SizedBox(
height: 100.0,
width: 100.0,
child: new Transform.rotate(
angle: 1.0, // radians
child: new PhysicalShape(
clipper: new ShapeBorderClipper(
shape: new BeveledRectangleBorder(
borderRadius: new BorderRadius.circular(20.0),
),
),
clipBehavior: clipBehavior,
color: Colors.red,
child: new Container(
color: Colors.white,
child: new RepaintBoundary(
child: new Center(
child: new Container(
color: Colors.black,
height: 10.0,
width: 10.0,
),
),
),
),
),
),
),
),
),
),
);
}
testWidgets('PhysicalShape painting with Clip.antiAlias', (WidgetTester tester) async {
await tester.pumpWidget(genPhysicalShape(Clip.antiAlias));
await expectLater(
find.byType(RepaintBoundary).first,
matchesGoldenFile('clip.PhysicalShape.antiAlias.png'),
);
});
testWidgets('PhysicalShape painting with Clip.hardEdge', (WidgetTester tester) async {
await tester.pumpWidget(genPhysicalShape(Clip.hardEdge));
await expectLater(
find.byType(RepaintBoundary).first,
matchesGoldenFile('clip.PhysicalShape.hardEdge.png'),
);
});
testWidgets('PhysicalShape painting with Clip.antiAliasWithSaveLayer', (WidgetTester tester) async {
await tester.pumpWidget(genPhysicalShape(Clip.antiAliasWithSaveLayer));
await expectLater(
find.byType(RepaintBoundary).first,
matchesGoldenFile('clip.PhysicalShape.antiAliasWithSaveLayer.png'),
);
});
......@@ -515,7 +643,7 @@ void main() {
);
await expectLater(
find.byType(RepaintBoundary).first,
matchesGoldenFile('clip.PhysicalShape.1.png'),
matchesGoldenFile('clip.PhysicalShape.default.png'),
);
});
}
......@@ -1046,6 +1046,11 @@ class _IsMethodCall extends Matcher {
/// [RenderClipPath].
const Matcher clipsWithBoundingRect = _ClipsWithBoundingRect();
/// Asserts that a [Finder] locates a single object whose root RenderObject is
/// not a [RenderClipRect], [RenderClipRRect], [RenderClipOval], or
/// [RenderClipPath].
const Matcher hasNoImmediateClip = _MatchAnythingExceptClip();
/// Asserts that a [Finder] locates a single object whose root RenderObject
/// is a [RenderClipRRect] with no clipper set, and border radius equals to
/// [borderRadius], or an equivalent [RenderClipPath].
......@@ -1104,7 +1109,53 @@ Matcher rendersOnPhysicalShape({
);
}
abstract class _MatchRenderObject<M extends RenderObject, T extends RenderObject> extends Matcher {
abstract class _FailWithDescriptionMatcher extends Matcher {
const _FailWithDescriptionMatcher();
bool failWithDescription(Map<dynamic, dynamic> matchState, String description) {
matchState['failure'] = description;
return false;
}
@override
Description describeMismatch(
dynamic item,
Description mismatchDescription,
Map<dynamic, dynamic> matchState,
bool verbose
) {
return mismatchDescription.add(matchState['failure']);
}
}
class _MatchAnythingExceptClip extends _FailWithDescriptionMatcher {
const _MatchAnythingExceptClip();
@override
bool matches(covariant Finder finder, Map<dynamic, dynamic> matchState) {
final Iterable<Element> nodes = finder.evaluate();
if (nodes.length != 1)
return failWithDescription(matchState, 'did not have a exactly one child element');
final RenderObject renderObject = nodes.single.renderObject;
switch (renderObject.runtimeType) {
case RenderClipPath:
case RenderClipOval:
case RenderClipRect:
case RenderClipRRect:
return failWithDescription(matchState, 'had a root render object of type: ${renderObject.runtimeType}');
default:
return true;
}
}
@override
Description describe(Description description) {
description.add('does not have a clip as an immediate child');
}
}
abstract class _MatchRenderObject<M extends RenderObject, T extends RenderObject> extends _FailWithDescriptionMatcher {
const _MatchRenderObject();
bool renderObjectMatchesT(Map<dynamic, dynamic> matchState, T renderObject);
......@@ -1125,21 +1176,6 @@ abstract class _MatchRenderObject<M extends RenderObject, T extends RenderObject
return failWithDescription(matchState, 'had a root render object of type: ${renderObject.runtimeType}');
}
bool failWithDescription(Map<dynamic, dynamic> matchState, String description) {
matchState['failure'] = description;
return false;
}
@override
Description describeMismatch(
dynamic item,
Description mismatchDescription,
Map<dynamic, dynamic> matchState,
bool verbose
) {
return mismatchDescription.add(matchState['failure']);
}
}
class _RendersOnPhysicalModel extends _MatchRenderObject<RenderPhysicalShape, RenderPhysicalModel> {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment