Unverified Commit fa0c49d7 authored by Chris Bracken's avatar Chris Bracken Committed by GitHub

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.
parent 86dd664f
......@@ -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<S>(AnnotationResult<S> 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<S>(
annotation: typedValue,
localPosition: localPosition,
));
return true;
}
return false;
}
}
......
......@@ -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();
}
}
......@@ -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
}
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