diff --git a/sky/packages/sky/lib/src/rendering/proxy_box.dart b/sky/packages/sky/lib/src/rendering/proxy_box.dart index f795ca4c33f..27e3fbc92a8 100644 --- a/sky/packages/sky/lib/src/rendering/proxy_box.dart +++ b/sky/packages/sky/lib/src/rendering/proxy_box.dart @@ -977,6 +977,12 @@ class RenderCustomPaint extends RenderProxyBox { typedef void PointerEventListener(PointerInputEvent e); +enum HitTestBehavior { + deferToChild, + opaque, + translucent, +} + /// Invokes the callbacks in response to pointer events. class RenderPointerListener extends RenderProxyBox { RenderPointerListener({ @@ -984,6 +990,7 @@ class RenderPointerListener extends RenderProxyBox { this.onPointerMove, this.onPointerUp, this.onPointerCancel, + this.behavior: HitTestBehavior.deferToChild, RenderBox child }) : super(child); @@ -991,6 +998,20 @@ class RenderPointerListener extends RenderProxyBox { PointerEventListener onPointerMove; PointerEventListener onPointerUp; PointerEventListener onPointerCancel; + HitTestBehavior behavior; + + bool hitTest(HitTestResult result, { Point position }) { + bool hitTarget = false; + if (position.x >= 0.0 && position.x < size.width && + position.y >= 0.0 && position.y < size.height) { + hitTarget = hitTestChildren(result, position: position) || hitTestSelf(position); + if (hitTarget || behavior == HitTestBehavior.translucent) + result.add(new BoxHitTestEntry(this, position)); + } + return hitTarget; + } + + bool hitTestSelf(Point position) => behavior == HitTestBehavior.opaque; void handleEvent(InputEvent event, HitTestEntry entry) { if (onPointerDown != null && event.type == 'pointerdown') @@ -1017,6 +1038,8 @@ class RenderPointerListener extends RenderProxyBox { if (listeners.isEmpty) listeners.add(''); settings.add('listeners: ${listeners.join(", ")}'); + if (behavior != HitTestBehavior.deferToChild) + settings.add('behavior: $behavior'); } } diff --git a/sky/packages/sky/lib/src/widgets/basic.dart b/sky/packages/sky/lib/src/widgets/basic.dart index 2363797126a..5b3f84e4aa8 100644 --- a/sky/packages/sky/lib/src/widgets/basic.dart +++ b/sky/packages/sky/lib/src/widgets/basic.dart @@ -31,6 +31,7 @@ export 'package:flutter/rendering.dart' show FontWeight, FractionalOffset, Gradient, + HitTestBehavior, ImageFit, ImageRepeat, InputEvent, @@ -1229,19 +1230,24 @@ class Listener extends OneChildRenderObjectWidget { this.onPointerDown, this.onPointerMove, this.onPointerUp, - this.onPointerCancel - }) : super(key: key, child: child); + this.onPointerCancel, + this.behavior: HitTestBehavior.deferToChild + }) : super(key: key, child: child) { + assert(behavior != null); + } final PointerEventListener onPointerDown; final PointerEventListener onPointerMove; final PointerEventListener onPointerUp; final PointerEventListener onPointerCancel; + final HitTestBehavior behavior; RenderPointerListener createRenderObject() => new RenderPointerListener( onPointerDown: onPointerDown, onPointerMove: onPointerMove, onPointerUp: onPointerUp, - onPointerCancel: onPointerCancel + onPointerCancel: onPointerCancel, + behavior: behavior ); void updateRenderObject(RenderPointerListener renderObject, Listener oldWidget) { @@ -1249,6 +1255,7 @@ class Listener extends OneChildRenderObjectWidget { renderObject.onPointerMove = onPointerMove; renderObject.onPointerUp = onPointerUp; renderObject.onPointerCancel = onPointerCancel; + renderObject.behavior = behavior; } } diff --git a/sky/packages/sky/lib/src/widgets/gesture_detector.dart b/sky/packages/sky/lib/src/widgets/gesture_detector.dart index 97d2f3d2312..5c9bd1d0992 100644 --- a/sky/packages/sky/lib/src/widgets/gesture_detector.dart +++ b/sky/packages/sky/lib/src/widgets/gesture_detector.dart @@ -48,7 +48,8 @@ class GestureDetector extends StatefulComponent { this.onPanEnd, this.onScaleStart, this.onScaleUpdate, - this.onScaleEnd + this.onScaleEnd, + this.behavior }) : super(key: key); final Widget child; @@ -77,6 +78,8 @@ class GestureDetector extends StatefulComponent { final GestureScaleUpdateCallback onScaleUpdate; final GestureScaleEndCallback onScaleEnd; + final HitTestBehavior behavior; + _GestureDetectorState createState() => new _GestureDetectorState(); } @@ -224,9 +227,14 @@ class _GestureDetectorState extends State { _scale.addPointer(event); } + HitTestBehavior get _defaultBehavior { + return config.child == null ? HitTestBehavior.translucent : HitTestBehavior.deferToChild; + } + Widget build(BuildContext context) { return new Listener( onPointerDown: _handlePointerDown, + behavior: config.behavior ?? _defaultBehavior, child: config.child ); } diff --git a/sky/unit/test/widget/gesture_detector_test.dart b/sky/unit/test/widget/gesture_detector_test.dart index 38488fcc8e3..030af3c5e66 100644 --- a/sky/unit/test/widget/gesture_detector_test.dart +++ b/sky/unit/test/widget/gesture_detector_test.dart @@ -134,4 +134,69 @@ void main() { expect(didEndPan, isTrue); }); }); + + test('Translucent', () { + testWidgets((WidgetTester tester) { + bool didReceivePointerDown; + bool didTap; + + void pumpWidgetTree(HitTestBehavior behavior) { + tester.pumpWidget( + new Stack([ + new Listener( + onPointerDown: (_) { + didReceivePointerDown = true; + }, + child: new Container( + width: 100.0, + height: 100.0, + decoration: const BoxDecoration( + backgroundColor: const Color(0xFF00FF00) + ) + ) + ), + new Container( + width: 100.0, + height: 100.0, + child: new GestureDetector( + onTap: () { + didTap = true; + }, + behavior: behavior + ) + ) + ]) + ); + } + + didReceivePointerDown = false; + didTap = false; + pumpWidgetTree(null); + tester.tapAt(new Point(10.0, 10.0)); + expect(didReceivePointerDown, isTrue); + expect(didTap, isTrue); + + didReceivePointerDown = false; + didTap = false; + pumpWidgetTree(HitTestBehavior.deferToChild); + tester.tapAt(new Point(10.0, 10.0)); + expect(didReceivePointerDown, isTrue); + expect(didTap, isFalse); + + didReceivePointerDown = false; + didTap = false; + pumpWidgetTree(HitTestBehavior.opaque); + tester.tapAt(new Point(10.0, 10.0)); + expect(didReceivePointerDown, isFalse); + expect(didTap, isTrue); + + didReceivePointerDown = false; + didTap = false; + pumpWidgetTree(HitTestBehavior.translucent); + tester.tapAt(new Point(10.0, 10.0)); + expect(didReceivePointerDown, isTrue); + expect(didTap, isTrue); + + }); + }); }