From 999fc7afa629ca47c0489294276be6d1be399df2 Mon Sep 17 00:00:00 2001 From: Eric Seidel Date: Fri, 27 Feb 2015 16:46:19 -0800 Subject: [PATCH] Add support for scroll and fling gestures to SkyShell. This is not complete gesture support by far, but it's a start. MojoShell and Chrome use a C++ GestureDetector, this code attempts to use the Android (Java) GestureDetector instead. We probably should not be sending gesturetap until we've decided it's not a scroll, etc, but this implementation does not go that far. I had to fix a bug whereby we were assuming the InputEvent.time_stamp was in TimeDelta's internal format, which was wrong. When we get time_stamp from Android its in ms since boot. R=abarth@chromium.org Review URL: https://codereview.chromium.org/969493002 --- engine/src/flutter/shell/BUILD.gn | 3 +- .../domokit/sky/shell/GestureProvider.java | 126 ++++++++++++++++++ .../org/domokit/sky/shell/PlatformView.java | 15 ++- .../flutter/shell/ui/input_event_converter.cc | 64 ++++++++- 4 files changed, 199 insertions(+), 9 deletions(-) create mode 100644 engine/src/flutter/shell/org/domokit/sky/shell/GestureProvider.java diff --git a/engine/src/flutter/shell/BUILD.gn b/engine/src/flutter/shell/BUILD.gn index 061cffcafac..d583784cf03 100644 --- a/engine/src/flutter/shell/BUILD.gn +++ b/engine/src/flutter/shell/BUILD.gn @@ -72,11 +72,12 @@ shared_library("sky_shell") { android_library("java") { java_files = [ + "org/domokit/sky/shell/GestureProvider.java", "org/domokit/sky/shell/JavaServiceProvider.java", "org/domokit/sky/shell/PlatformView.java", - "org/domokit/sky/shell/SkyMain.java", "org/domokit/sky/shell/SkyActivity.java", "org/domokit/sky/shell/SkyApplication.java", + "org/domokit/sky/shell/SkyMain.java", ] deps = [ diff --git a/engine/src/flutter/shell/org/domokit/sky/shell/GestureProvider.java b/engine/src/flutter/shell/org/domokit/sky/shell/GestureProvider.java new file mode 100644 index 00000000000..af286e9670a --- /dev/null +++ b/engine/src/flutter/shell/org/domokit/sky/shell/GestureProvider.java @@ -0,0 +1,126 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.domokit.sky.shell; + +import android.content.Context; +import android.view.GestureDetector; +import android.view.MotionEvent; + +import org.chromium.mojom.sky.EventType; +import org.chromium.mojom.sky.GestureData; +import org.chromium.mojom.sky.InputEvent; + +/** + * Knows how to drive a GestureDetector to turn MotionEvents into Sky's + * InputEvents. Seems like this should not be needed. That there must exist + * some Android class to do most of this work for us? + */ +public class GestureProvider implements GestureDetector.OnGestureListener { + private static final String TAG = "GestureProvider"; + + /** + * Callback interface + */ + public interface OnGestureListener { + void onGestureEvent(InputEvent e); + } + + private OnGestureListener mListener; + private GestureDetector mDetector; + private boolean mScrolling; + private boolean mFlinging; + + public GestureProvider(Context context, OnGestureListener listener) { + mListener = listener; + mDetector = new GestureDetector(context, this); + } + + private InputEvent createGestureEvent(MotionEvent event) { + GestureData gestureData = new GestureData(); + gestureData.x = event.getX(); + gestureData.y = event.getY(); + InputEvent inputEvent = new InputEvent(); + inputEvent.timeStamp = event.getEventTime(); + inputEvent.gestureData = gestureData; + return inputEvent; + } + + public void onTouchEvent(MotionEvent event) { + // TODO(eseidel): I am not confident that these stops are correct. + int maskedAction = event.getActionMasked(); + if (mScrolling && maskedAction == MotionEvent.ACTION_UP) { + mScrolling = false; + InputEvent inputEvent = createGestureEvent(event); + inputEvent.type = EventType.GESTURE_SCROLL_END; + mListener.onGestureEvent(inputEvent); + } + + if (mFlinging && maskedAction == MotionEvent.ACTION_DOWN) { + mFlinging = false; + InputEvent inputEvent = createGestureEvent(event); + inputEvent.type = EventType.GESTURE_FLING_CANCEL; + mListener.onGestureEvent(inputEvent); + } + + mDetector.onTouchEvent(event); + } + + @Override + public boolean onDown(MotionEvent event) { + return true; + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, + float velocityX, float velocityY) { + + mFlinging = true; + + // Use the first event as a scroll start (for the target hit-test) + InputEvent inputEvent = createGestureEvent(e1); + inputEvent.gestureData.velocityX = velocityX; + inputEvent.gestureData.velocityY = velocityY; + inputEvent.type = EventType.GESTURE_FLING_START; + + mListener.onGestureEvent(inputEvent); + + return true; + } + + @Override + public void onLongPress(MotionEvent event) { + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, + float distanceY) { + // Use the first event as a scroll start (for the target hit-test) + InputEvent inputEvent = createGestureEvent(e1); + inputEvent.gestureData.dx = distanceX; + inputEvent.gestureData.dy = -distanceY; + + // If we haven't started scrolling, send a scroll_begin. + if (!mScrolling) { + mScrolling = true; + inputEvent.type = EventType.GESTURE_SCROLL_BEGIN; + mListener.onGestureEvent(inputEvent); + } + + inputEvent.type = EventType.GESTURE_SCROLL_UPDATE; + mListener.onGestureEvent(inputEvent); + return true; + } + + @Override + public void onShowPress(MotionEvent event) { + } + + @Override + public boolean onSingleTapUp(MotionEvent event) { + return true; + } + + +} \ No newline at end of file diff --git a/engine/src/flutter/shell/org/domokit/sky/shell/PlatformView.java b/engine/src/flutter/shell/org/domokit/sky/shell/PlatformView.java index e24cbdf44da..07af2b46745 100644 --- a/engine/src/flutter/shell/org/domokit/sky/shell/PlatformView.java +++ b/engine/src/flutter/shell/org/domokit/sky/shell/PlatformView.java @@ -26,10 +26,14 @@ import org.chromium.mojom.sky.ViewportObserver; * A view containing Sky */ @JNINamespace("sky::shell") -public class PlatformView extends SurfaceView { +public class PlatformView extends SurfaceView + implements GestureProvider.OnGestureListener { + private static final String TAG = "PlatformView"; + private long mNativePlatformView; private ViewportObserver.Proxy mViewportObserver; private final SurfaceHolder.Callback mSurfaceCallback; + private GestureProvider mGestureProvider; public PlatformView(Context context) { super(context); @@ -62,6 +66,8 @@ public class PlatformView extends SurfaceView { } }; getHolder().addCallback(mSurfaceCallback); + + mGestureProvider = new GestureProvider(context, this); } @Override @@ -116,6 +122,8 @@ public class PlatformView extends SurfaceView { @Override public boolean onTouchEvent(MotionEvent event) { + mGestureProvider.onTouchEvent(event); + int maskedAction = event.getActionMasked(); // ACTION_UP, ACTION_POINTER_UP, ACTION_DOWN, and ACTION_POINTER_DOWN // only apply to a single pointer, other events apply to all pointers. @@ -135,6 +143,11 @@ public class PlatformView extends SurfaceView { return true; } + @Override + public void onGestureEvent(InputEvent event) { + mViewportObserver.onInputEvent(event); + } + public void loadUrl(String url) { mViewportObserver.loadUrl(url); } diff --git a/engine/src/flutter/shell/ui/input_event_converter.cc b/engine/src/flutter/shell/ui/input_event_converter.cc index 9fcf49c3d23..baa0ee5acb8 100644 --- a/engine/src/flutter/shell/ui/input_event_converter.cc +++ b/engine/src/flutter/shell/ui/input_event_converter.cc @@ -15,8 +15,7 @@ scoped_ptr BuildWebPointerEvent( const InputEventPtr& event, float device_pixel_ratio) { scoped_ptr web_event(new blink::WebPointerEvent); - web_event->timeStampMS = - base::TimeDelta::FromInternalValue(event->time_stamp).InMillisecondsF(); + web_event->timeStampMS = event->time_stamp; switch (event->type) { case EVENT_TYPE_POINTER_DOWN: @@ -47,15 +46,66 @@ scoped_ptr BuildWebPointerEvent( return web_event.Pass(); } +scoped_ptr BuildWebGestureEvent( + const InputEventPtr& event, float device_pixel_ratio) { + scoped_ptr web_event(new blink::WebGestureEvent); + + web_event->timeStampMS = event->time_stamp; + + switch (event->type) { + case EVENT_TYPE_GESTURE_SCROLL_BEGIN: + web_event->type = blink::WebInputEvent::GestureScrollBegin; + break; + case EVENT_TYPE_GESTURE_SCROLL_END: + web_event->type = blink::WebInputEvent::GestureScrollEnd; + break; + case EVENT_TYPE_GESTURE_SCROLL_UPDATE: + web_event->type = blink::WebInputEvent::GestureScrollUpdate; + web_event->data.scrollUpdate.deltaX = + event->gesture_data->dx / device_pixel_ratio; + web_event->data.scrollUpdate.deltaY = + event->gesture_data->dy / device_pixel_ratio; + break; + case EVENT_TYPE_GESTURE_FLING_START: + web_event->type = blink::WebInputEvent::GestureFlingStart; + web_event->data.flingStart.velocityX = + event->gesture_data->velocityX / device_pixel_ratio; + web_event->data.flingStart.velocityY = + event->gesture_data->velocityY / device_pixel_ratio; + break; + case EVENT_TYPE_GESTURE_FLING_CANCEL: + web_event->type = blink::WebInputEvent::GestureFlingCancel; + break; + default: + break; + } + + if (event->gesture_data) { + web_event->x = event->gesture_data->x / device_pixel_ratio; + web_event->y = event->gesture_data->y / device_pixel_ratio; + } + + return web_event.Pass(); +} + } // namespace scoped_ptr ConvertEvent(const InputEventPtr& event, float device_pixel_ratio) { - if (event->type == EVENT_TYPE_POINTER_DOWN || - event->type == EVENT_TYPE_POINTER_UP || - event->type == EVENT_TYPE_POINTER_MOVE || - event->type == EVENT_TYPE_POINTER_CANCEL) { - return BuildWebPointerEvent(event, device_pixel_ratio); + switch (event->type) { + case EVENT_TYPE_POINTER_DOWN: + case EVENT_TYPE_POINTER_UP: + case EVENT_TYPE_POINTER_MOVE: + case EVENT_TYPE_POINTER_CANCEL: + return BuildWebPointerEvent(event, device_pixel_ratio); + case EVENT_TYPE_GESTURE_SCROLL_BEGIN: + case EVENT_TYPE_GESTURE_SCROLL_UPDATE: + case EVENT_TYPE_GESTURE_SCROLL_END: + case EVENT_TYPE_GESTURE_FLING_START: + case EVENT_TYPE_GESTURE_FLING_CANCEL: + return BuildWebGestureEvent(event, device_pixel_ratio); + case EVENT_TYPE_UNKNOWN: + NOTIMPLEMENTED() << "ConvertEvent received unexpected EVENT_TYPE_UNKNOWN"; } return scoped_ptr();