From c8912dd14c5bba0c167526aa225ce8befa5ea03b 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 --- framework/sky-scrollable.sky | 4 +- services/viewport/input_event.mojom | 15 +++ shell/BUILD.gn | 3 +- .../domokit/sky/shell/GestureProvider.java | 126 ++++++++++++++++++ shell/org/domokit/sky/shell/PlatformView.java | 15 ++- shell/ui/input_event_converter.cc | 64 ++++++++- 6 files changed, 217 insertions(+), 10 deletions(-) create mode 100644 shell/org/domokit/sky/shell/GestureProvider.java diff --git a/framework/sky-scrollable.sky b/framework/sky-scrollable.sky index 7f5e60d21f..323639e55b 100644 --- a/framework/sky-scrollable.sky +++ b/framework/sky-scrollable.sky @@ -94,8 +94,10 @@ class SkyScrollable extends SkyElement { double newScrollOffset = math.max(0.0, math.min(scrollRange, value)); if (newScrollOffset == _scrollOffset) return; + // TODO(eseidel): We should scroll in device pixels instead of logical + // pixels, but to do that correctly we need to use a device pixel unit. _scrollOffset = newScrollOffset; - String transform = 'translateY(${(-_scrollOffset).toStringAsFixed(2)}px)'; + String transform = 'translateY(${(-_scrollOffset).toInt()}px)'; _scrollable.style['transform'] = transform; double topPercent = newScrollOffset / innerHeight * 100.0; diff --git a/services/viewport/input_event.mojom b/services/viewport/input_event.mojom index 21cf84c760..3e8461959f 100644 --- a/services/viewport/input_event.mojom +++ b/services/viewport/input_event.mojom @@ -10,6 +10,11 @@ enum EventType { POINTER_UP, POINTER_MOVE, POINTER_CANCEL, + GESTURE_SCROLL_BEGIN, + GESTURE_SCROLL_UPDATE, + GESTURE_SCROLL_END, + GESTURE_FLING_START, + GESTURE_FLING_CANCEL, }; enum PointerKind { @@ -36,8 +41,18 @@ struct PointerData { float tilt; }; +struct GestureData { + float x; + float y; + float dx; + float dy; + float velocityX; + float velocityY; +}; + struct InputEvent { EventType type; int64 time_stamp; PointerData? pointer_data; + GestureData? gesture_data; }; diff --git a/shell/BUILD.gn b/shell/BUILD.gn index 061cffcafa..d583784cf0 100644 --- a/shell/BUILD.gn +++ b/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/shell/org/domokit/sky/shell/GestureProvider.java b/shell/org/domokit/sky/shell/GestureProvider.java new file mode 100644 index 0000000000..af286e9670 --- /dev/null +++ b/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/shell/org/domokit/sky/shell/PlatformView.java b/shell/org/domokit/sky/shell/PlatformView.java index e24cbdf44d..07af2b4674 100644 --- a/shell/org/domokit/sky/shell/PlatformView.java +++ b/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/shell/ui/input_event_converter.cc b/shell/ui/input_event_converter.cc index 9fcf49c3d2..baa0ee5acb 100644 --- a/shell/ui/input_event_converter.cc +++ b/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(); -- GitLab