Bug 1111333 - Enable double-tap-drag to zoom. r=kats
authorDennis Ek <contact@dennisek.se>
Tue, 23 May 2017 19:53:06 +0200
changeset 360226 f5a0a69abe4b17622c5e804db3dc0e564e48c622
parent 360225 bb439487185f4ce5cdc0183db4b6776beb75b954
child 360227 cbae2c2731e49a6ce8458e07da2ccaa1ae34ceeb
push id90612
push userkgupta@mozilla.com
push dateTue, 23 May 2017 20:04:39 +0000
treeherdermozilla-inbound@f5a0a69abe4b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats
bugs1111333
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1111333 - Enable double-tap-drag to zoom. r=kats MozReview-Commit-ID: KpwVNZZdLPd
AUTHORS
gfx/layers/apz/src/GestureEventListener.cpp
gfx/layers/apz/src/GestureEventListener.h
--- a/AUTHORS
+++ b/AUTHORS
@@ -285,16 +285,17 @@ David Rajchenbach-Teller <dteller@mozill
 David Savage
 David S. Miller <davem@redhat.com>
 David Woodhouse <dwmw2@infradead.org>
 David Zbarsky <dzbarsky@gmail.com>
 Dean Tessman <dean_tessman@hotmail.com>
 <deneen@alum.bucknell.edu>
 Denis Antrushin <adu@sparc.spb.su>
 Denis Issoupov <denis@macadamian.com>
+Dennis Ek <contact@dennisek.se>
 Dennis Handly
 Derrick Rice <derrick.rice@gmail.com>
 <desale@netscape.com>
 diablohn
 Diane Trout <diane@ghic.org>
 Dietrich Ayala <dietrich@mozilla.com>
 Digital Creations 2, Inc
 Disruptive Innovations
--- a/gfx/layers/apz/src/GestureEventListener.cpp
+++ b/gfx/layers/apz/src/GestureEventListener.cpp
@@ -32,16 +32,22 @@ static const uint32_t MAX_TAP_TIME = 300
 /**
  * Amount of span or focus change needed to take us from the GESTURE_WAITING_PINCH
  * state to the GESTURE_PINCH state. This is measured as either a change in distance
  * between the fingers used to compute the span ratio, or the a change in
  * position of the focus point between the two fingers.
  */
 static const float PINCH_START_THRESHOLD = 35.0f;
 
+/**
+ * Determines how fast a one touch pinch zooms in and out. The greater the
+ * value, the faster it zooms.
+ */
+static const float ONE_TOUCH_PINCH_SPEED = 0.005f;
+
 static bool sLongTapEnabled = true;
 
 ParentLayerPoint GetCurrentFocus(const MultiTouchInput& aEvent)
 {
   const ParentLayerPoint& firstTouch = aEvent.mTouches[0].mLocalScreenPoint;
   const ParentLayerPoint& secondTouch = aEvent.mTouches[1].mLocalScreenPoint;
   return (firstTouch + secondTouch) / 2;
 }
@@ -49,16 +55,23 @@ ParentLayerPoint GetCurrentFocus(const M
 ParentLayerCoord GetCurrentSpan(const MultiTouchInput& aEvent)
 {
   const ParentLayerPoint& firstTouch = aEvent.mTouches[0].mLocalScreenPoint;
   const ParentLayerPoint& secondTouch = aEvent.mTouches[1].mLocalScreenPoint;
   ParentLayerPoint delta = secondTouch - firstTouch;
   return delta.Length();
 }
 
+ParentLayerCoord GestureEventListener::GetYSpanFromStartPoint()
+{
+  const ParentLayerPoint start = mTouchStartPosition;
+  const ParentLayerPoint& current = mTouches[0].mLocalScreenPoint;
+  return current.y - start.y;
+}
+
 TapGestureInput CreateTapEvent(const MultiTouchInput& aTouch, TapGestureInput::TapGestureType aType)
 {
   return TapGestureInput(aType,
                          aTouch.mTime,
                          aTouch.mTimeStamp,
                          aTouch.mTouches[0].mScreenPoint,
                          aTouch.modifiers);
 }
@@ -88,38 +101,41 @@ nsEventStatus GestureEventListener::Hand
 
   // Cache the current event since it may become the single or long tap that we
   // send.
   mLastTouchInput = aEvent;
 
   switch (aEvent.mType) {
   case MultiTouchInput::MULTITOUCH_START:
     mTouches.Clear();
+    // Cache every touch.
     for (size_t i = 0; i < aEvent.mTouches.Length(); i++) {
       mTouches.AppendElement(aEvent.mTouches[i]);
     }
 
     if (aEvent.mTouches.Length() == 1) {
       rv = HandleInputTouchSingleStart();
     } else {
       rv = HandleInputTouchMultiStart();
     }
     break;
   case MultiTouchInput::MULTITOUCH_MOVE:
+    // Update the screen points of the cached touches.
     for (size_t i = 0; i < aEvent.mTouches.Length(); i++) {
       for (size_t j = 0; j < mTouches.Length(); j++) {
         if (aEvent.mTouches[i].mIdentifier == mTouches[j].mIdentifier) {
           mTouches[j].mScreenPoint = aEvent.mTouches[i].mScreenPoint;
           mTouches[j].mLocalScreenPoint = aEvent.mTouches[i].mLocalScreenPoint;
         }
       }
     }
     rv = HandleInputTouchMove();
     break;
   case MultiTouchInput::MULTITOUCH_END:
+    // Remove the cache of the touch that ended.
     for (size_t i = 0; i < aEvent.mTouches.Length(); i++) {
       for (size_t j = 0; j < mTouches.Length(); j++) {
         if (aEvent.mTouches[i].mIdentifier == mTouches[j].mIdentifier) {
           mTouches.RemoveElementAt(j);
           break;
         }
       }
     }
@@ -249,28 +265,55 @@ nsEventStatus GestureEventListener::Hand
     if (MoveDistanceIsLarge()) {
       // So that we don't fire a long-tap-up if the user moves around after a
       // long-tap
       SetState(GESTURE_NONE);
     }
     break;
 
   case GESTURE_FIRST_SINGLE_TOUCH_DOWN:
-  case GESTURE_FIRST_SINGLE_TOUCH_MAX_TAP_DOWN:
-  case GESTURE_SECOND_SINGLE_TOUCH_DOWN: {
+  case GESTURE_FIRST_SINGLE_TOUCH_MAX_TAP_DOWN: {
     // If we move too much, bail out of the tap.
     if (MoveDistanceIsLarge()) {
       CancelLongTapTimeoutTask();
       CancelMaxTapTimeoutTask();
       mSingleTapSent = Nothing();
       SetState(GESTURE_NONE);
     }
     break;
   }
 
+  // The user has performed a double tap, but not lifted her finger.
+  case GESTURE_SECOND_SINGLE_TOUCH_DOWN: {
+    // If touch has moved noticeably (within MAX_TAP_TIME), change state.
+    if (MoveDistanceIsLarge()) {
+      CancelLongTapTimeoutTask();
+      CancelMaxTapTimeoutTask();
+      mSingleTapSent = Nothing();
+      SetState(GESTURE_ONE_TOUCH_PINCH);
+
+      ParentLayerCoord currentSpan = 1.0f;
+      ParentLayerPoint currentFocus = mTouchStartPosition;
+
+      PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_START,
+                                   mLastTouchInput.mTime,
+                                   mLastTouchInput.mTimeStamp,
+                                   currentFocus,
+                                   currentSpan,
+                                   currentSpan,
+                                   mLastTouchInput.modifiers);
+
+      rv = mAsyncPanZoomController->HandleGestureEvent(pinchEvent);
+
+      mPreviousSpan = currentSpan;
+      mPreviousFocus = currentFocus;
+    }
+    break;
+  }
+
   case GESTURE_MULTI_TOUCH_DOWN: {
     if (mLastTouchInput.mTouches.Length() < 2) {
       NS_WARNING("Wrong input: less than 2 moving points in GESTURE_MULTI_TOUCH_DOWN state");
       break;
     }
 
     ParentLayerCoord currentSpan = GetCurrentSpan(mLastTouchInput);
     ParentLayerPoint currentFocus = GetCurrentFocus(mLastTouchInput);
@@ -319,16 +362,40 @@ nsEventStatus GestureEventListener::Hand
                                  mLastTouchInput.modifiers);
 
     rv = mAsyncPanZoomController->HandleGestureEvent(pinchEvent);
     mPreviousSpan = currentSpan;
 
     break;
   }
 
+  case GESTURE_ONE_TOUCH_PINCH: {
+    ParentLayerCoord currentSpan = GetYSpanFromStartPoint();
+    float effectiveSpan = 1.0f + (fabsf(currentSpan.value) * ONE_TOUCH_PINCH_SPEED);
+    ParentLayerPoint currentFocus = mTouchStartPosition;
+
+    // Invert zoom.
+    if (currentSpan.value < 0) {
+      effectiveSpan = 1.0f / effectiveSpan;
+    }
+
+    PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_SCALE,
+                                 mLastTouchInput.mTime,
+                                 mLastTouchInput.mTimeStamp,
+                                 currentFocus,
+                                 effectiveSpan,
+                                 mPreviousSpan,
+                                 mLastTouchInput.modifiers);
+
+    rv = mAsyncPanZoomController->HandleGestureEvent(pinchEvent);
+    mPreviousSpan = effectiveSpan;
+
+    break;
+  }
+
   default:
     NS_WARNING("Unhandled state upon touch move");
     SetState(GESTURE_NONE);
     break;
   }
 
   return rv;
 }
@@ -406,16 +473,33 @@ nsEventStatus GestureEventListener::Hand
                                    mLastTouchInput.modifiers);
       mAsyncPanZoomController->HandleGestureEvent(pinchEvent);
     }
 
     rv = nsEventStatus_eConsumeNoDefault;
 
     break;
 
+  case GESTURE_ONE_TOUCH_PINCH: {
+    SetState(GESTURE_NONE);
+    ParentLayerPoint point(-1, -1);
+    PinchGestureInput pinchEvent(PinchGestureInput::PINCHGESTURE_END,
+                                 mLastTouchInput.mTime,
+                                 mLastTouchInput.mTimeStamp,
+                                 point,
+                                 1.0f,
+                                 1.0f,
+                                 mLastTouchInput.modifiers);
+    mAsyncPanZoomController->HandleGestureEvent(pinchEvent);
+
+    rv = nsEventStatus_eConsumeNoDefault;
+
+    break;
+  }
+
   default:
     NS_WARNING("Unhandled state upon touch end");
     SetState(GESTURE_NONE);
     break;
   }
 
   return rv;
 }
--- a/gfx/layers/apz/src/GestureEventListener.h
+++ b/gfx/layers/apz/src/GestureEventListener.h
@@ -100,34 +100,40 @@ private:
     // After having gotten into this state we clear the timer for MAX_TAP_TIME.
     // Allowed next states: GESTURE_SECOND_SINGLE_TOUCH_DOWN, GESTURE_NONE,
     //                      GESTURE_MULTI_TOUCH_DOWN.
     GESTURE_FIRST_SINGLE_TOUCH_UP,
 
     // A user put down her finger again right after a single tap thus the
     // gesture can't be a single tap, but rather a double tap. But we're
     // still not sure about that until the user lifts her finger again.
-    // Allowed next states: GESTURE_MULTI_TOUCH_DOWN, GESTURE_NONE.
+    // Allowed next states: GESTURE_MULTI_TOUCH_DOWN, GESTURE_ONE_TOUCH_PINCH,
+    //                      GESTURE_NONE.
     GESTURE_SECOND_SINGLE_TOUCH_DOWN,
 
     // A long touch has happened, but the user still keeps her finger down.
     // We'll trigger a "long tap up" event when the finger is up.
     // Allowed next states: GESTURE_NONE, GESTURE_MULTI_TOUCH_DOWN.
     GESTURE_LONG_TOUCH_DOWN,
 
     // We have detected that two or more fingers are on the screen, but there
     // hasn't been enough movement yet to make us start actually zooming the
     // screen.
     // Allowed next states: GESTURE_PINCH, GESTURE_NONE
     GESTURE_MULTI_TOUCH_DOWN,
 
     // There are two or more fingers on the screen, and the user has already
     // pinched enough for us to start zooming the screen.
     // Allowed next states: GESTURE_NONE
-    GESTURE_PINCH
+    GESTURE_PINCH,
+
+    // The user has double tapped, but not lifted her finger, and moved her
+    // finger more than PINCH_START_THRESHOLD.
+    // Allowed next states: GESTURE_NONE.
+    GESTURE_ONE_TOUCH_PINCH
   };
 
   /**
    * These HandleInput* functions comprise input alphabet of the GEL
    * finite-state machine triggering state transitions.
    */
   nsEventStatus HandleInputTouchSingleStart();
   nsEventStatus HandleInputTouchMultiStart();
@@ -137,16 +143,22 @@ private:
   void HandleInputTimeoutLongTap();
   void HandleInputTimeoutMaxTap(bool aDuringFastFling);
 
   void TriggerSingleTapConfirmedEvent();
 
   bool MoveDistanceIsLarge();
 
   /**
+   * Returns current vertical span, counting from the where the user first put
+   * her finger down.
+   */
+  ParentLayerCoord GetYSpanFromStartPoint();
+
+  /**
    * Do actual state transition and reset substates.
    */
   void SetState(GestureState aState);
 
   RefPtr<AsyncPanZoomController> mAsyncPanZoomController;
 
   /**
    * Array containing all active touches. When a touch happens it, gets added to