Commit 2fc5a759 authored by Adam Barth's avatar Adam Barth

Add the ability to have translucent gesture detectors

A translucent gesture detector still listens for gestures but also lets the
content visually behind the detector receive events.
parent 7bafe54a
...@@ -977,6 +977,12 @@ class RenderCustomPaint extends RenderProxyBox { ...@@ -977,6 +977,12 @@ class RenderCustomPaint extends RenderProxyBox {
typedef void PointerEventListener(PointerInputEvent e); typedef void PointerEventListener(PointerInputEvent e);
enum HitTestBehavior {
deferToChild,
opaque,
translucent,
}
/// Invokes the callbacks in response to pointer events. /// Invokes the callbacks in response to pointer events.
class RenderPointerListener extends RenderProxyBox { class RenderPointerListener extends RenderProxyBox {
RenderPointerListener({ RenderPointerListener({
...@@ -984,6 +990,7 @@ class RenderPointerListener extends RenderProxyBox { ...@@ -984,6 +990,7 @@ class RenderPointerListener extends RenderProxyBox {
this.onPointerMove, this.onPointerMove,
this.onPointerUp, this.onPointerUp,
this.onPointerCancel, this.onPointerCancel,
this.behavior: HitTestBehavior.deferToChild,
RenderBox child RenderBox child
}) : super(child); }) : super(child);
...@@ -991,6 +998,20 @@ class RenderPointerListener extends RenderProxyBox { ...@@ -991,6 +998,20 @@ class RenderPointerListener extends RenderProxyBox {
PointerEventListener onPointerMove; PointerEventListener onPointerMove;
PointerEventListener onPointerUp; PointerEventListener onPointerUp;
PointerEventListener onPointerCancel; 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) { void handleEvent(InputEvent event, HitTestEntry entry) {
if (onPointerDown != null && event.type == 'pointerdown') if (onPointerDown != null && event.type == 'pointerdown')
...@@ -1017,6 +1038,8 @@ class RenderPointerListener extends RenderProxyBox { ...@@ -1017,6 +1038,8 @@ class RenderPointerListener extends RenderProxyBox {
if (listeners.isEmpty) if (listeners.isEmpty)
listeners.add('<none>'); listeners.add('<none>');
settings.add('listeners: ${listeners.join(", ")}'); settings.add('listeners: ${listeners.join(", ")}');
if (behavior != HitTestBehavior.deferToChild)
settings.add('behavior: $behavior');
} }
} }
......
...@@ -31,6 +31,7 @@ export 'package:flutter/rendering.dart' show ...@@ -31,6 +31,7 @@ export 'package:flutter/rendering.dart' show
FontWeight, FontWeight,
FractionalOffset, FractionalOffset,
Gradient, Gradient,
HitTestBehavior,
ImageFit, ImageFit,
ImageRepeat, ImageRepeat,
InputEvent, InputEvent,
...@@ -1229,19 +1230,24 @@ class Listener extends OneChildRenderObjectWidget { ...@@ -1229,19 +1230,24 @@ class Listener extends OneChildRenderObjectWidget {
this.onPointerDown, this.onPointerDown,
this.onPointerMove, this.onPointerMove,
this.onPointerUp, this.onPointerUp,
this.onPointerCancel this.onPointerCancel,
}) : super(key: key, child: child); this.behavior: HitTestBehavior.deferToChild
}) : super(key: key, child: child) {
assert(behavior != null);
}
final PointerEventListener onPointerDown; final PointerEventListener onPointerDown;
final PointerEventListener onPointerMove; final PointerEventListener onPointerMove;
final PointerEventListener onPointerUp; final PointerEventListener onPointerUp;
final PointerEventListener onPointerCancel; final PointerEventListener onPointerCancel;
final HitTestBehavior behavior;
RenderPointerListener createRenderObject() => new RenderPointerListener( RenderPointerListener createRenderObject() => new RenderPointerListener(
onPointerDown: onPointerDown, onPointerDown: onPointerDown,
onPointerMove: onPointerMove, onPointerMove: onPointerMove,
onPointerUp: onPointerUp, onPointerUp: onPointerUp,
onPointerCancel: onPointerCancel onPointerCancel: onPointerCancel,
behavior: behavior
); );
void updateRenderObject(RenderPointerListener renderObject, Listener oldWidget) { void updateRenderObject(RenderPointerListener renderObject, Listener oldWidget) {
...@@ -1249,6 +1255,7 @@ class Listener extends OneChildRenderObjectWidget { ...@@ -1249,6 +1255,7 @@ class Listener extends OneChildRenderObjectWidget {
renderObject.onPointerMove = onPointerMove; renderObject.onPointerMove = onPointerMove;
renderObject.onPointerUp = onPointerUp; renderObject.onPointerUp = onPointerUp;
renderObject.onPointerCancel = onPointerCancel; renderObject.onPointerCancel = onPointerCancel;
renderObject.behavior = behavior;
} }
} }
......
...@@ -48,7 +48,8 @@ class GestureDetector extends StatefulComponent { ...@@ -48,7 +48,8 @@ class GestureDetector extends StatefulComponent {
this.onPanEnd, this.onPanEnd,
this.onScaleStart, this.onScaleStart,
this.onScaleUpdate, this.onScaleUpdate,
this.onScaleEnd this.onScaleEnd,
this.behavior
}) : super(key: key); }) : super(key: key);
final Widget child; final Widget child;
...@@ -77,6 +78,8 @@ class GestureDetector extends StatefulComponent { ...@@ -77,6 +78,8 @@ class GestureDetector extends StatefulComponent {
final GestureScaleUpdateCallback onScaleUpdate; final GestureScaleUpdateCallback onScaleUpdate;
final GestureScaleEndCallback onScaleEnd; final GestureScaleEndCallback onScaleEnd;
final HitTestBehavior behavior;
_GestureDetectorState createState() => new _GestureDetectorState(); _GestureDetectorState createState() => new _GestureDetectorState();
} }
...@@ -224,9 +227,14 @@ class _GestureDetectorState extends State<GestureDetector> { ...@@ -224,9 +227,14 @@ class _GestureDetectorState extends State<GestureDetector> {
_scale.addPointer(event); _scale.addPointer(event);
} }
HitTestBehavior get _defaultBehavior {
return config.child == null ? HitTestBehavior.translucent : HitTestBehavior.deferToChild;
}
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Listener( return new Listener(
onPointerDown: _handlePointerDown, onPointerDown: _handlePointerDown,
behavior: config.behavior ?? _defaultBehavior,
child: config.child child: config.child
); );
} }
......
...@@ -134,4 +134,69 @@ void main() { ...@@ -134,4 +134,69 @@ void main() {
expect(didEndPan, isTrue); 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);
});
});
} }
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