From fa0c49d7755f6a960bb464496cbb3f89451318cc Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Thu, 5 Dec 2019 13:31:50 -0800 Subject: [PATCH] Dispatch hover events to PlatformViewController (#46124) This adds support to PlatformViewLayer for handling hover events. Prior to this, PlatformViewLayers only supported events forwarded by the gesture recognizers associated with the PlatformViewRenderBox. Hover events don't participate in gesture recognition and as such are dropped in GestureBinding. That said, hover event processing in platform views is expected for desktop and other platforms with hover event support. This adds support for passing an optional MouseTrackerAnnotation to PlatformViewLayer. PlatformViewRenderBox populates this with a mouse tracker annotation that forwards hover events to PlatformViewController.dispatchPointerEvent() for handling by users. --- packages/flutter/lib/src/rendering/layer.dart | 32 +++++++++++++++++++ .../lib/src/rendering/platform_view.dart | 30 ++++++++++++++++- .../test/rendering/platform_view_test.dart | 15 ++++++++- 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/packages/flutter/lib/src/rendering/layer.dart b/packages/flutter/lib/src/rendering/layer.dart index e1bf4ce947f..a9bde834220 100644 --- a/packages/flutter/lib/src/rendering/layer.dart +++ b/packages/flutter/lib/src/rendering/layer.dart @@ -624,6 +624,7 @@ class PlatformViewLayer extends Layer { PlatformViewLayer({ @required this.rect, @required this.viewId, + this.hoverAnnotation, }) : assert(rect != null), assert(viewId != null); @@ -635,6 +636,25 @@ class PlatformViewLayer extends Layer { /// A UIView with this identifier must have been created by [PlatformViewsServices.initUiKitView]. final int viewId; + /// [MouseTrackerAnnotation] that handles mouse events for this layer. + /// + /// If [hoverAnnotation] is non-null, [PlatformViewLayer] will annotate the + /// region of this platform view such that annotation callbacks will receive + /// mouse events, including mouse enter, exit, and hover, but not including + /// mouse down, move, and up. The layer will be treated as opaque during an + /// annotation search, which will prevent layers behind it from receiving + /// these events. + /// + /// By default, [hoverAnnotation] is null, and [PlatformViewLayer] will not + /// receive mouse events, and will therefore appear translucent during the + /// annotation search. + /// + /// See also: + /// + /// * [MouseRegion], which explains more about the mouse events and opacity + /// during annotation search. + final MouseTrackerAnnotation hoverAnnotation; + @override void addToScene(ui.SceneBuilder builder, [ Offset layerOffset = Offset.zero ]) { final Rect shiftedRect = layerOffset == Offset.zero ? rect : rect.shift(layerOffset); @@ -649,6 +669,18 @@ class PlatformViewLayer extends Layer { @override @protected bool findAnnotations(AnnotationResult result, Offset localPosition, { @required bool onlyFirst }) { + if (hoverAnnotation == null || !rect.contains(localPosition)) { + return false; + } + if (MouseTrackerAnnotation == S) { + final Object untypedValue = hoverAnnotation; + final S typedValue = untypedValue; + result.add(AnnotationEntry( + annotation: typedValue, + localPosition: localPosition, + )); + return true; + } return false; } } diff --git a/packages/flutter/lib/src/rendering/platform_view.dart b/packages/flutter/lib/src/rendering/platform_view.dart index 5ba14ef9a30..d09624d455c 100644 --- a/packages/flutter/lib/src/rendering/platform_view.dart +++ b/packages/flutter/lib/src/rendering/platform_view.dart @@ -10,6 +10,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/semantics.dart'; import 'package:flutter/services.dart'; +import 'binding.dart'; import 'box.dart'; import 'layer.dart'; import 'object.dart'; @@ -758,7 +759,8 @@ class PlatformViewRenderBox extends RenderBox with _PlatformViewGestureMixin { assert(_controller.viewId != null); context.addLayer(PlatformViewLayer( rect: offset & size, - viewId: _controller.viewId)); + viewId: _controller.viewId, + hoverAnnotation: _hoverAnnotation)); } @override @@ -778,6 +780,18 @@ mixin _PlatformViewGestureMixin on RenderBox { // any newly arriving events there's nothing we need to invalidate. PlatformViewHitTestBehavior hitTestBehavior; + /// [MouseTrackerAnnotation] associated with the platform view layer. + /// + /// Gesture recognizers don't receive hover events due to the performance + /// cost associated with hit testing a sequence of potentially thousands of + /// events -- move events only hit-test the down event, then cache the result + /// and apply it to all subsequent move events, but there is no down event + /// for a hover. To support native hover gesture handling by platform views, + /// we attach/detach this layer annotation as necessary. + MouseTrackerAnnotation _hoverAnnotation; + + _HandlePointerEvent _handlePointerEvent; + /// {@macro flutter.rendering.platformView.updateGestureRecognizers} /// /// Any active gesture arena the `PlatformView` participates in is rejected when the @@ -793,6 +807,7 @@ mixin _PlatformViewGestureMixin on RenderBox { } _gestureRecognizer?.dispose(); _gestureRecognizer = _PlatformViewGestureRecognizer(handlePointerEvent, gestureRecognizers); + _handlePointerEvent = handlePointerEvent; } _PlatformViewGestureRecognizer _gestureRecognizer; @@ -816,9 +831,22 @@ mixin _PlatformViewGestureMixin on RenderBox { } } + @override + void attach(PipelineOwner owner) { + super.attach(owner); + assert(_hoverAnnotation == null); + _hoverAnnotation = MouseTrackerAnnotation(onHover: (PointerHoverEvent event) { + if (_handlePointerEvent != null) + _handlePointerEvent(event); + }); + RendererBinding.instance.mouseTracker.attachAnnotation(_hoverAnnotation); + } + @override void detach() { _gestureRecognizer.reset(); + RendererBinding.instance.mouseTracker.detachAnnotation(_hoverAnnotation); + _hoverAnnotation = null; super.detach(); } } diff --git a/packages/flutter/test/rendering/platform_view_test.dart b/packages/flutter/test/rendering/platform_view_test.dart index 3cadc72b8c0..356685d07bf 100644 --- a/packages/flutter/test/rendering/platform_view_test.dart +++ b/packages/flutter/test/rendering/platform_view_test.dart @@ -7,6 +7,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/material.dart'; +import '../gestures/gesture_tester.dart'; import '../services/fake_platform_views.dart'; import 'rendering_tester.dart'; @@ -15,7 +16,7 @@ void main() { group('PlatformViewRenderBox', () { FakePlatformViewController fakePlatformViewController; PlatformViewRenderBox platformViewRenderBox; - setUp((){ + setUp(() { fakePlatformViewController = FakePlatformViewController(0); platformViewRenderBox = PlatformViewRenderBox( controller: fakePlatformViewController, @@ -68,5 +69,17 @@ void main() { semanticsHandle.dispose(); }); + + testGesture('hover events are dispatched via PlatformViewController.dispatchPointerEvent', (GestureTester tester) { + layout(platformViewRenderBox); + pumpFrame(phase: EnginePhase.flushSemantics); + + final TestPointer pointer = TestPointer(1, PointerDeviceKind.mouse); + tester.route(pointer.addPointer()); + tester.route(pointer.hover(const Offset(10, 10))); + + expect(fakePlatformViewController.dispatchedPointerEvents, isNotEmpty); + }); + }, skip: isBrowser); // TODO(yjbanov): fails on Web with obscured stack trace: https://github.com/flutter/flutter/issues/42770 }