Commit 50626e9b authored by Ian Hickson's avatar Ian Hickson

ClipPath (#3695)

* ClipPath

* Add a test for ClipOval and ClipPath
parent 961f5cd2
......@@ -857,7 +857,13 @@ abstract class _RenderCustomClip<T> extends RenderProxyBox {
}
T get _defaultClip;
T get _clip => _clipper?.getClip(size) ?? _defaultClip;
T _clip;
@override
void performLayout() {
super.performLayout();
_clip = _clipper?.getClip(size) ?? _defaultClip;
}
@override
Rect describeApproximatePaintClip(RenderObject child) => _clipper?.getApproximateClipRect(size) ?? Point.origin & size;
......@@ -994,6 +1000,40 @@ class RenderClipOval extends _RenderCustomClip<Rect> {
}
}
/// Clips its child using a path.
///
/// Takes a delegate whose primary method returns a path that should
/// be used to prevent the child from painting outside the path.
///
/// Clipping to a path is expensive. Certain shapes have more
/// optimized render objects:
///
/// * To clip to a rectangle, consider [RenderClipRect].
/// * To clip to an oval or circle, consider [RenderClipOval].
/// * To clip to a rounded rectangle, consider [RenderClipRRect].
class RenderClipPath extends _RenderCustomClip<Path> {
RenderClipPath({
RenderBox child,
CustomClipper<Path> clipper
}) : super(child: child, clipper: clipper);
@override
Path get _defaultClip => new Path()..addRect(Point.origin & size);
@override
bool hitTest(HitTestResult result, { Point position }) {
if (_clip == null || !_clip.contains(position))
return false;
return super.hitTest(result, position: position);
}
@override
void paint(PaintingContext context, Offset offset) {
if (child != null)
context.pushClipPath(needsCompositing, offset, Point.origin & size, _clip, super.paint);
}
}
/// Where to paint a box decoration.
enum DecorationPosition {
/// Paint the box decoration behind the children.
......
......@@ -293,6 +293,14 @@ class ClipOval extends SingleChildRenderObjectWidget {
ClipOval({ Key key, this.clipper, Widget child }) : super(key: key, child: child);
/// If non-null, determines which clip to use.
///
/// The delegate returns a rectangle that describes the axis-aligned
/// bounding box of the oval. The oval's axes will themselves also
/// be axis-aligned.
///
/// If the [clipper] delegate is null, then the oval uses the
/// widget's bounding box (the layout dimensions of the render
/// object) instead.
final CustomClipper<Rect> clipper;
@override
......@@ -309,6 +317,42 @@ class ClipOval extends SingleChildRenderObjectWidget {
}
}
/// Clips its child using a path.
///
/// Invokes a callback on a delegate whenever the widget is to be
/// painted. The callback returns a path and the widget prevents the
/// child from painting outside the path.
///
/// Clipping to a path is expensive. Certain shapes have more
/// optimized widgets:
///
/// * To clip to a rectangle, consider [ClipRect].
/// * To clip to an oval or circle, consider [ClipOval].
/// * To clip to a rounded rectangle, consider [ClipRRect].
class ClipPath extends SingleChildRenderObjectWidget {
ClipPath({ Key key, this.clipper, Widget child }) : super(key: key, child: child);
/// If non-null, determines which clip to use.
///
/// The default clip, which is used if this property is null, is the
/// bounding box rectangle of the widget. [ClipRect] is a more
/// efficient way of obtaining that effect.
final CustomClipper<Path> clipper;
@override
RenderClipPath createRenderObject(BuildContext context) => new RenderClipPath(clipper: clipper);
@override
void updateRenderObject(BuildContext context, RenderClipPath renderObject) {
renderObject.clipper = clipper;
}
@override
void didUnmountRenderObject(RenderClipPath renderObject) {
renderObject.clipper = null;
}
}
// POSITIONING AND SIZING NODES
......
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
import 'package:test/test.dart';
final List<String> log = <String>[];
class PathClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
log.add('getClip');
return new Path()
..addRect(new Rect.fromLTWH(50.0, 50.0, 100.0, 100.0));
}
@override
bool shouldRepaint(PathClipper oldWidget) => false;
}
void main() {
testWidgets('ClipPath', (WidgetTester tester) {
tester.pumpWidget(
new ClipPath(
clipper: new PathClipper(),
child: new GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () { log.add('tap'); },
child: new Container()
)
)
);
expect(log, equals(['getClip']));
tester.tapAt(new Point(10.0, 10.0));
expect(log, equals(['getClip']));
log.clear();
tester.tapAt(new Point(100.0, 100.0));
expect(log, equals(['tap']));
log.clear();
});
testWidgets('ClipOval', (WidgetTester tester) {
tester.pumpWidget(
new ClipOval(
child: new GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () { log.add('tap'); },
child: new Container()
)
)
);
expect(log, equals([]));
tester.tapAt(new Point(10.0, 10.0));
expect(log, equals([]));
log.clear();
tester.tapAt(new Point(400.0, 300.0));
expect(log, equals(['tap']));
log.clear();
});
}
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