diff --git a/packages/flutter/lib/src/rendering/layer.dart b/packages/flutter/lib/src/rendering/layer.dart index e1bf4ce947..a9bde83422 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 5ba14ef9a3..d09624d455 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 3cadc72b8c..356685d07b 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 }