diff options
author | lambroslambrou@chromium.org <lambroslambrou@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-27 23:20:52 +0000 |
---|---|---|
committer | lambroslambrou@chromium.org <lambroslambrou@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-27 23:20:52 +0000 |
commit | caaa849e2e0cc9caf9a2fe938e43515c35bfa679 (patch) | |
tree | 6b5beb54119aa71deddb962dd3983f50c5fe56ae /remoting/android | |
parent | f4acd55ec469865ec005791e54d46171880e80a0 (diff) | |
download | chromium_src-caaa849e2e0cc9caf9a2fe938e43515c35bfa679.zip chromium_src-caaa849e2e0cc9caf9a2fe938e43515c35bfa679.tar.gz chromium_src-caaa849e2e0cc9caf9a2fe938e43515c35bfa679.tar.bz2 |
Add 2-finger swipe gesture for mouse-wheel in Chromoting Android client.
Since a two-finger gesture is also used for scaling, this adds some code
to disambiguate the two gestures.
R=jamiewalch@chromium.org
Review URL: https://codereview.chromium.org/89553002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@237641 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting/android')
-rw-r--r-- | remoting/android/java/src/org/chromium/chromoting/SwipePinchDetector.java | 161 | ||||
-rw-r--r-- | remoting/android/java/src/org/chromium/chromoting/TrackingInputHandler.java | 23 |
2 files changed, 181 insertions, 3 deletions
diff --git a/remoting/android/java/src/org/chromium/chromoting/SwipePinchDetector.java b/remoting/android/java/src/org/chromium/chromoting/SwipePinchDetector.java new file mode 100644 index 0000000..8f3b17f5 --- /dev/null +++ b/remoting/android/java/src/org/chromium/chromoting/SwipePinchDetector.java @@ -0,0 +1,161 @@ +// Copyright 2013 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.chromium.chromoting; + +import android.content.Context; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +/** + * Helper class for disambiguating whether to treat a two-finger gesture as a swipe or a pinch. + * Initially, the status will be unknown, until the fingers have moved sufficiently far to + * determine the intent. + */ +public class SwipePinchDetector { + /** Current state of the gesture. */ + private enum State { + UNKNOWN, + SWIPE, + PINCH + } + private State mState = State.UNKNOWN; + + /** Initial coordinates of the two pointers in the current gesture. */ + private float mFirstX0; + private float mFirstY0; + private float mFirstX1; + private float mFirstY1; + + /** + * The initial coordinates above are valid when this flag is set. Used to determine whether a + * MotionEvent's pointer coordinates are the first ones of the gesture. + */ + private boolean mInGesture = false; + + /** + * Threshold squared-distance, in pixels, to use for motion-detection. + */ + private int mTouchSlopSquare; + + private void reset() { + mState = State.UNKNOWN; + mInGesture = false; + } + + /** Construct a new detector, using the context to determine movement thresholds. */ + public SwipePinchDetector(Context context) { + ViewConfiguration config = ViewConfiguration.get(context); + int touchSlop = config.getScaledTouchSlop(); + mTouchSlopSquare = touchSlop * touchSlop; + } + + /** Returns whether a swipe is in progress. */ + public boolean isSwiping() { + return mState == State.SWIPE; + } + + /** Returns whether a pinch is in progress. */ + public boolean isPinching() { + return mState == State.PINCH; + } + + /** + * Analyzes the touch event to determine whether the user is swiping or pinching. Only + * motion events with 2 pointers are considered here. Once the gesture is determined to be a + * swipe or a pinch, further 2-finger motion-events will be ignored. When a different event is + * passed in (motion event with != 2 pointers, or some other event type), this object will + * revert back to the original UNKNOWN state. + */ + public void onTouchEvent(MotionEvent event) { + if (event.getPointerCount() != 2) { + reset(); + return; + } + + // Only MOVE or DOWN events are considered - all other events should finish any current + // gesture and reset the detector. In addition, a DOWN event should reset the detector, + // since it signals the start of the gesture. If the events are consistent, a DOWN event + // will occur at the start of the gesture, but this implementation tries to cope in case + // the first event is MOVE rather than DOWN. + int action = event.getActionMasked(); + if (action != MotionEvent.ACTION_MOVE) { + reset(); + if (action != MotionEvent.ACTION_POINTER_DOWN) { + return; + } + } + + // If the gesture is known, there is no need for further processing - the state should + // remain the same until the gesture is complete, as tested above. + if (mState != State.UNKNOWN) { + return; + } + + float currentX0 = event.getX(0); + float currentY0 = event.getY(0); + float currentX1 = event.getX(1); + float currentY1 = event.getY(1); + if (!mInGesture) { + // This is the first event of the gesture, so store the pointer coordinates. + mFirstX0 = currentX0; + mFirstY0 = currentY0; + mFirstX1 = currentX1; + mFirstY1 = currentY1; + mInGesture = true; + return; + } + + float deltaX0 = currentX0 - mFirstX0; + float deltaY0 = currentY0 - mFirstY0; + float deltaX1 = currentX1 - mFirstX1; + float deltaY1 = currentY1 - mFirstY1; + + float squaredDistance0 = deltaX0 * deltaX0 + deltaY0 * deltaY0; + float squaredDistance1 = deltaX1 * deltaX1 + deltaY1 * deltaY1; + + + // If both fingers have moved beyond the touch-slop, it is safe to recognize the gesture. + // However, one finger might be held stationary whilst the other finger is moved a long + // distance. In this case, it is preferable to trigger a PINCH. This should be detected + // soon enough to avoid triggering a sudden large change in the zoom level, but not so + // soon that SWIPE never gets triggered. + + // Threshold level for triggering the PINCH gesture if one finger is stationary. This + // cannot be equal to the touch-slop, because in that case, SWIPE would rarely be detected. + // One finger would usually leave the touch-slop radius slightly before the other finger, + // triggering a PINCH as described above. A larger radius gives an opportunity for + // SWIPE to be detected. Doubling the radius is an arbitrary choice that works well. + int pinchThresholdSquare = 4 * mTouchSlopSquare; + + boolean finger0Moved = squaredDistance0 > mTouchSlopSquare; + boolean finger1Moved = squaredDistance1 > mTouchSlopSquare; + + if (!finger0Moved && !finger1Moved) { + return; + } + + if (finger0Moved && !finger1Moved) { + if (squaredDistance0 > pinchThresholdSquare) { + mState = State.PINCH; + } + return; + } + + if (!finger0Moved && finger1Moved) { + if (squaredDistance1 > pinchThresholdSquare) { + mState = State.PINCH; + } + return; + } + + // Both fingers have moved, so determine SWIPE/PINCH status. If the fingers have moved in + // the same direction, this is a SWIPE, otherwise it's a PINCH. This can be measured by + // taking the scalar product of the direction vectors. This product is positive if the + // vectors are pointing in the same direction, and negative if they're in opposite + // directions. + float scalarProduct = deltaX0 * deltaX1 + deltaY0 * deltaY1; + mState = (scalarProduct > 0) ? State.SWIPE : State.PINCH; + } +} diff --git a/remoting/android/java/src/org/chromium/chromoting/TrackingInputHandler.java b/remoting/android/java/src/org/chromium/chromoting/TrackingInputHandler.java index f26b929..b1cd3e4 100644 --- a/remoting/android/java/src/org/chromium/chromoting/TrackingInputHandler.java +++ b/remoting/android/java/src/org/chromium/chromoting/TrackingInputHandler.java @@ -38,6 +38,9 @@ public class TrackingInputHandler implements TouchInputHandler { /** Used to calculate the physics for flinging the cursor. */ private Scroller mFlingScroller; + /** Used to disambiguate a 2-finger gesture as a swipe or a pinch. */ + private SwipePinchDetector mSwipePinchDetector; + /** * The current cursor position is stored here as floats, so that the desktop image can be * positioned with sub-pixel accuracy, to give a smoother panning animation at high zoom levels. @@ -93,8 +96,8 @@ public class TrackingInputHandler implements TouchInputHandler { mZoomer = new ScaleGestureDetector(context, listener); mTapDetector = new TapGestureDetector(context, listener); - mFlingScroller = new Scroller(context); + mSwipePinchDetector = new SwipePinchDetector(context); mCursorPosition = new PointF(); @@ -251,6 +254,7 @@ public class TrackingInputHandler implements TouchInputHandler { boolean handled = mScroller.onTouchEvent(event); handled |= mZoomer.onTouchEvent(event); handled |= mTapDetector.onTouchEvent(event); + mSwipePinchDetector.onTouchEvent(event); switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: @@ -318,7 +322,8 @@ public class TrackingInputHandler implements TouchInputHandler { */ @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - if (e2.getPointerCount() == 3 && !mSwipeCompleted) { + int pointerCount = e2.getPointerCount(); + if (pointerCount == 3 && !mSwipeCompleted) { // Note that distance values are reversed. For example, dragging a finger in the // direction of increasing Y coordinate (downwards) results in distanceY being // negative. @@ -326,7 +331,15 @@ public class TrackingInputHandler implements TouchInputHandler { return onSwipe(); } - if (e2.getPointerCount() != 1 || mSuppressCursorMovement) { + if (pointerCount == 2 && mSwipePinchDetector.isSwiping()) { + mViewer.injectMouseWheelDeltaEvent(-(int)distanceX, -(int)distanceY); + + // Prevent the cursor being moved or flung by the gesture. + mSuppressCursorMovement = true; + return true; + } + + if (pointerCount != 1 || mSuppressCursorMovement) { return false; } @@ -373,6 +386,10 @@ public class TrackingInputHandler implements TouchInputHandler { /** Called when the user is in the process of pinch-zooming. */ @Override public boolean onScale(ScaleGestureDetector detector) { + if (!mSwipePinchDetector.isPinching()) { + return false; + } + if (Math.abs(detector.getScaleFactor() - 1) < MIN_ZOOM_DELTA) { return false; } |