Commit f68cdacd authored by Sahand Akbarzadeh's avatar Sahand Akbarzadeh Committed by Flutter GitHub Bot

Add clip behaviour to Container (#44971)

parent c95dafc4
...@@ -213,6 +213,21 @@ class BoxDecoration extends Decoration { ...@@ -213,6 +213,21 @@ class BoxDecoration extends Decoration {
@override @override
EdgeInsetsGeometry get padding => border?.dimensions; EdgeInsetsGeometry get padding => border?.dimensions;
@override
Path getClipPath(Rect rect, TextDirection textDirection) {
Path clipPath;
switch (shape) {
case BoxShape.circle:
clipPath = Path()..addOval(rect);
break;
case BoxShape.rectangle:
if (borderRadius != null)
clipPath = Path()..addRRect(borderRadius.resolve(textDirection).toRRect(rect));
break;
}
return clipPath;
}
/// Returns a new box decoration that is scaled by the given factor. /// Returns a new box decoration that is scaled by the given factor.
BoxDecoration scale(double factor) { BoxDecoration scale(double factor) {
return BoxDecoration( return BoxDecoration(
......
...@@ -165,6 +165,9 @@ abstract class Decoration extends Diagnosticable { ...@@ -165,6 +165,9 @@ abstract class Decoration extends Diagnosticable {
/// omitted if there is no chance that the painter will change (for example, /// omitted if there is no chance that the painter will change (for example,
/// if it is a [BoxDecoration] with definitely no [DecorationImage]). /// if it is a [BoxDecoration] with definitely no [DecorationImage]).
BoxPainter createBoxPainter([ VoidCallback onChanged ]); BoxPainter createBoxPainter([ VoidCallback onChanged ]);
/// Returns a closed [Path] that describes the outer edge of this decoration.
Path getClipPath(Rect rect, TextDirection textDirection) => null;
} }
/// A stateful class that can paint a particular [Decoration]. /// A stateful class that can paint a particular [Decoration].
......
...@@ -122,6 +122,11 @@ class ShapeDecoration extends Decoration { ...@@ -122,6 +122,11 @@ class ShapeDecoration extends Decoration {
); );
} }
@override
Path getClipPath(Rect rect, TextDirection textDirection) {
return shape.getOuterPath(rect, textDirection: textDirection);
}
/// The color to fill in the background of the shape. /// The color to fill in the background of the shape.
/// ///
/// The color is under the [image]. /// The color is under the [image].
......
...@@ -312,10 +312,12 @@ class Container extends StatelessWidget { ...@@ -312,10 +312,12 @@ class Container extends StatelessWidget {
this.margin, this.margin,
this.transform, this.transform,
this.child, this.child,
this.clipBehavior = Clip.none,
}) : assert(margin == null || margin.isNonNegative), }) : assert(margin == null || margin.isNonNegative),
assert(padding == null || padding.isNonNegative), assert(padding == null || padding.isNonNegative),
assert(decoration == null || decoration.debugAssertIsValid()), assert(decoration == null || decoration.debugAssertIsValid()),
assert(constraints == null || constraints.debugAssertIsValid()), assert(constraints == null || constraints.debugAssertIsValid()),
assert(clipBehavior != null),
assert(color == null || decoration == null, assert(color == null || decoration == null,
'Cannot provide both a color and a decoration\n' 'Cannot provide both a color and a decoration\n'
'The color argument is just a shorthand for "decoration: new BoxDecoration(color: color)".' 'The color argument is just a shorthand for "decoration: new BoxDecoration(color: color)".'
...@@ -388,6 +390,11 @@ class Container extends StatelessWidget { ...@@ -388,6 +390,11 @@ class Container extends StatelessWidget {
/// The transformation matrix to apply before painting the container. /// The transformation matrix to apply before painting the container.
final Matrix4 transform; final Matrix4 transform;
/// The clip behavior when [Container.decoration] has a clipPath.
///
/// Defaults to [Clip.none].
final Clip clipBehavior;
EdgeInsetsGeometry get _paddingIncludingDecoration { EdgeInsetsGeometry get _paddingIncludingDecoration {
if (decoration == null || decoration.padding == null) if (decoration == null || decoration.padding == null)
return padding; return padding;
...@@ -436,6 +443,17 @@ class Container extends StatelessWidget { ...@@ -436,6 +443,17 @@ class Container extends StatelessWidget {
if (transform != null) if (transform != null)
current = Transform(transform: transform, child: current); current = Transform(transform: transform, child: current);
if (clipBehavior != Clip.none) {
current = ClipPath(
clipper: _DecorationClipper(
textDirection: Directionality.of(context),
decoration: decoration
),
clipBehavior: clipBehavior,
child: current,
);
}
return current; return current;
} }
...@@ -444,6 +462,7 @@ class Container extends StatelessWidget { ...@@ -444,6 +462,7 @@ class Container extends StatelessWidget {
super.debugFillProperties(properties); super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, showName: false, defaultValue: null)); properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, showName: false, defaultValue: null));
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null)); properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
properties.add(DiagnosticsProperty<Clip>('clipBehavior', clipBehavior, defaultValue: Clip.none));
properties.add(DiagnosticsProperty<Decoration>('bg', decoration, defaultValue: null)); properties.add(DiagnosticsProperty<Decoration>('bg', decoration, defaultValue: null));
properties.add(DiagnosticsProperty<Decoration>('fg', foregroundDecoration, defaultValue: null)); properties.add(DiagnosticsProperty<Decoration>('fg', foregroundDecoration, defaultValue: null));
properties.add(DiagnosticsProperty<BoxConstraints>('constraints', constraints, defaultValue: null)); properties.add(DiagnosticsProperty<BoxConstraints>('constraints', constraints, defaultValue: null));
...@@ -451,3 +470,26 @@ class Container extends StatelessWidget { ...@@ -451,3 +470,26 @@ class Container extends StatelessWidget {
properties.add(ObjectFlagProperty<Matrix4>.has('transform', transform)); properties.add(ObjectFlagProperty<Matrix4>.has('transform', transform));
} }
} }
/// A clipper that uses [Decoration.getClipPath] to clip.
class _DecorationClipper extends CustomClipper<Path> {
_DecorationClipper({
TextDirection textDirection,
@required this.decoration
}) : assert (decoration != null),
textDirection = textDirection ?? TextDirection.ltr;
final TextDirection textDirection;
final Decoration decoration;
@override
Path getClip(Size size) {
return decoration.getClipPath(Offset.zero & size, textDirection);
}
@override
bool shouldReclip(_DecorationClipper oldClipper) {
return oldClipper.decoration != decoration
|| oldClipper.textDirection != textDirection;
}
}
...@@ -68,4 +68,18 @@ void main() { ...@@ -68,4 +68,18 @@ void main() {
paints..rect(rect: Offset.zero & size), paints..rect(rect: Offset.zero & size),
); );
}); });
test('BoxDecoration.getClipPath', () {
const double radius = 10;
final BoxDecoration decoration = BoxDecoration(
borderRadius: BorderRadius.circular(radius),
);
const Rect rect = Rect.fromLTWH(0.0, 0.0, 100.0, 20.0);
final Path clipPath = decoration.getClipPath(rect, TextDirection.ltr);
final Matcher isLookLikeExpectedPath = isPathThat(
includes: const <Offset>[ Offset(30.0, 10.0), Offset(50.0, 10.0), ],
excludes: const <Offset>[ Offset(1.0, 1.0), Offset(99.0, 19.0), ],
);
expect(clipPath, isLookLikeExpectedPath);
});
} }
...@@ -98,6 +98,17 @@ void main() { ...@@ -98,6 +98,17 @@ void main() {
); );
expect(log, isEmpty); expect(log, isEmpty);
}); });
test('ShapeDecoration.getClipPath', () {
const ShapeDecoration decoration = ShapeDecoration(shape: CircleBorder(side: BorderSide.none));
const Rect rect = Rect.fromLTWH(0.0, 0.0, 100.0, 20.0);
final Path clipPath = decoration.getClipPath(rect, TextDirection.ltr);
final Matcher isLookLikeExpectedPath = isPathThat(
includes: const <Offset>[ Offset(50.0, 10.0), ],
excludes: const <Offset>[ Offset(1.0, 1.0), Offset(30.0, 10.0), Offset(99.0, 19.0), ],
);
expect(clipPath, isLookLikeExpectedPath);
});
} }
class TestImageProvider extends ImageProvider<TestImageProvider> { class TestImageProvider extends ImageProvider<TestImageProvider> {
......
...@@ -498,6 +498,38 @@ void main() { ...@@ -498,6 +498,38 @@ void main() {
), ),
); );
}); });
testWidgets('giving clipBehaviour Clip.None, will not add a ClipPath to the tree', (WidgetTester tester) async {
await tester.pumpWidget(Container(
clipBehavior: Clip.none,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(1),
),
child: const SizedBox(),
));
expect(
find.byType(ClipPath),
findsNothing,
);
});
testWidgets('giving clipBehaviour not a Clip.None, will add a ClipPath to the tree', (WidgetTester tester) async {
final Container container = Container(
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(1),
),
child: const SizedBox(),
);
await tester.pumpWidget(container);
expect(
find.byType(ClipPath),
findsOneWidget,
);
});
} }
class _MockPaintingContext extends Mock implements PaintingContext {} class _MockPaintingContext extends Mock implements PaintingContext {}
......
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