ScrollView: Avoid animating in wrong direction during fast flick
authorPaul Wisbey <p.wisbey@samsung.com>
Tue, 3 Jun 2014 10:42:21 +0000 (19:42 +0900)
committerAdeel Kazmi <adeel.kazmi@samsung.com>
Wed, 11 Jun 2014 07:57:20 +0000 (08:57 +0100)
[problem]      When flicking pages quickly, it sometimes jumps backwards
[cause]        Incorrect (infinite) velocity calculation in ScrollView::OnTouchEvent()
[solution]     Don't interrupt snap-animation immediately during TouchPoint::Down i.e. allow
time for pan gesture to occur.

base/dali-toolkit/internal/controls/scrollable/scroll-view/scroll-view-impl.cpp
base/dali-toolkit/internal/controls/scrollable/scroll-view/scroll-view-impl.h

index 6cd1a37..dca4aab 100644 (file)
@@ -44,7 +44,8 @@ const float AUTOLOCK_AXIS_MINIMUM_DISTANCE2 = 100.0f;
 const float FLICK_ORTHO_ANGLE_RANGE = 60.0f;                                              ///< degrees. (if >45, then supports diagonal flicking)
 const unsigned int MAXIMUM_NUMBER_OF_VALUES = 5;                                          ///< Number of values to use for weighted pan calculation.
 const Vector2 DEFAULT_MOUSE_WHEEL_SCROLL_DISTANCE_STEP_PROPORTION = Vector2(0.17f, 0.1f); ///< The step of horizontal scroll distance in the proportion of stage size for each mouse wheel event received.
-const unsigned long MINIMUM_TIME_BETWEEN_DOWN_AND_UP_FOR_RESET( 150u );
+const unsigned long MINIMUM_TIME_BETWEEN_DOWN_AND_UP_FOR_RESET( 150u );                   ///< Determines whether velocity is reset after tap interrupts snap animation
+const float TOUCH_DOWN_TIMER_INTERVAL = 100.0f;                                           ///< After this time interval with touch-down, the snap animation will be interrupted (if no gesture has started).
 
 // predefined effect values
 const Vector3 ANGLE_CAROUSEL_ROTATE(Math::PI * 0.5f, Math::PI * 0.5f, 0.0f);
@@ -518,7 +519,7 @@ ScrollView::ScrollView()
   mRotationDelta(0.0f),
   mScrollPreRotation(0.0f),
   mScrollPostRotation(0.0f),
-  mTouchDownReceived(false),
+  mTouchDownTimeoutReached(false),
   mActorAutoSnapEnabled(false),
   mAutoResizeContainerEnabled(false),
   mWrapMode(false),
@@ -1878,6 +1879,49 @@ void ScrollView::OnChildRemove(Actor& child)
   UnbindActor(child);
 }
 
+void ScrollView::StartTouchDownTimer()
+{
+  if ( !mTouchDownTimer )
+  {
+    mTouchDownTimer = Timer::New( TOUCH_DOWN_TIMER_INTERVAL );
+    mTouchDownTimer.TickSignal().Connect( this, &ScrollView::OnTouchDownTimeout );
+  }
+
+  mTouchDownTimer.Start();
+}
+
+void ScrollView::StopTouchDownTimer()
+{
+  if ( mTouchDownTimer )
+  {
+    mTouchDownTimer.Stop();
+  }
+}
+
+bool ScrollView::OnTouchDownTimeout()
+{
+  mTouchDownTimeoutReached = true;
+
+  if( mSnapAnimation || mSnapXAnimation || mSnapYAnimation || mSnapOvershootAnimation )
+  {
+    mScrollInterrupted = true;
+    StopAnimation();
+  }
+
+  if(mScrolling) // are we interrupting a current scroll?
+  {
+    // reset domain offset as scrolling from original plane.
+    mDomainOffset = Vector3::ZERO;
+    Self().SetProperty(mPropertyDomainOffset, Vector3::ZERO);
+
+    mScrolling = false;
+    Vector3 currentScrollPosition = GetCurrentScrollPosition();
+    mScrollCompletedSignalV2.Emit( currentScrollPosition );
+  }
+
+  return false;
+}
+
 bool ScrollView::OnTouchEvent(const TouchEvent& event)
 {
   if(!mSensitive)
@@ -1894,33 +1938,23 @@ bool ScrollView::OnTouchEvent(const TouchEvent& event)
 
   if (event.GetPoint(0).state == TouchPoint::Down)
   {
-    mTouchDownTime = event.time;
-    mTouchDownReceived = true;
-    mTouchDownPosition = event.GetPoint(0).local;
-
-    if( mSnapAnimation || mSnapXAnimation || mSnapYAnimation || mSnapOvershootAnimation )
-    {
-      mScrollInterrupted = true;
-      StopAnimation();
-    }
-
-    if(mScrolling) // are we interrupting a current scroll?
+    if(mGestureStackDepth==0)
     {
-      // reset domain offset as scrolling from original plane.
-      mDomainOffset = Vector3::ZERO;
-      Self().SetProperty(mPropertyDomainOffset, Vector3::ZERO);
+      mTouchDownTime = event.time;
 
-      mScrolling = false;
-      Vector3 currentScrollPosition = GetCurrentScrollPosition();
-      mScrollCompletedSignalV2.Emit( currentScrollPosition );
+      // This allows time for a pan-gesture to start, to avoid breaking snap-animation behavior with fast flicks.
+      // If touch-down does not become a pan (after timeout interval), then snap-animation can be interrupted.
+      StartTouchDownTimer();
     }
   }
   else if(event.GetPoint(0).state == TouchPoint::Up)
   {
+    StopTouchDownTimer();
+
     // if the user touches and releases without enough movement to go
     // into a gesture state, then we should snap to nearest point.
     // otherwise our scroll could be stopped (interrupted) half way through an animation.
-    if(mGestureStackDepth==0 && mTouchDownReceived)
+    if(mGestureStackDepth==0 && mTouchDownTimeoutReached)
     {
       unsigned timeDelta( event.time - mTouchDownTime );
       if ( timeDelta >= MINIMUM_TIME_BETWEEN_DOWN_AND_UP_FOR_RESET )
@@ -1928,11 +1962,6 @@ bool ScrollView::OnTouchEvent(const TouchEvent& event)
         // Reset the velocity only if down was received a while ago
         mLastVelocity = Vector2( 0.0f, 0.0f );
       }
-      else
-      {
-        Vector2 positionDelta( mTouchDownPosition - event.GetPoint(0).local );
-        mLastVelocity = positionDelta / timeDelta;
-      }
 
       // Only finish the transform if scrolling was interrupted on down or if we are scrolling
       if ( mSnapAnimation || mSnapXAnimation || mSnapYAnimation || mSnapOvershootAnimation || mScrollInterrupted || mScrolling )
@@ -1940,7 +1969,7 @@ bool ScrollView::OnTouchEvent(const TouchEvent& event)
         FinishTransform();
       }
     }
-    mTouchDownReceived = false;
+    mTouchDownTimeoutReached = false;
     mScrollInterrupted = false;
   }
 
@@ -2058,6 +2087,7 @@ void ScrollView::GestureStarted()
   // we continue and combine the effects of the gesture instead of reseting.
   if(mGestureStackDepth++==0)
   {
+    StopTouchDownTimer();
     StopAnimation();
     mPanDelta = Vector3::ZERO;
     mScaleDelta = Vector3::ONE;
index 4c3dc8e..8f2be58 100644 (file)
@@ -556,6 +556,21 @@ private: // private overriden functions from CustomActorImpl and Controls
 private:
 
   /**
+   * Start a timer which calls OnTouchDownTimeout()
+   */
+  void StartTouchDownTimer();
+
+  /**
+   * Stop a timer which calls OnTouchDownTimeout()
+   */
+  void StopTouchDownTimer();
+
+  /**
+   * Helper to detect when touch-point has been down (outside of pan gesture)
+   */
+  bool OnTouchDownTimeout();
+
+  /**
    * Called whenever a snap animation has completed
    * @param[in] source the Animation instance that has completed.
    */
@@ -806,7 +821,6 @@ private:
   bool mScrolling;                      ///< Flag indicating whether the scroll view is being scrolled (by user or animation)
   bool mScrollInterrupted;              ///< Flag set for when a down event interrupts a scroll
   unsigned long mTouchDownTime;         ///< The touch down time
-  Vector2 mTouchDownPosition;           ///< The touch down position
 
   bool mSensitive;                      ///< Scroll Sensitivity Flag.
 
@@ -834,7 +848,7 @@ private:
   RulerPtr mRulerScaleX;
   RulerPtr mRulerScaleY;
   RulerPtr mRulerRotation;
-  bool mTouchDownReceived;
+  bool mTouchDownTimeoutReached;
   bool mActorAutoSnapEnabled;           ///< Whether to automatically snap to closest actor.
   bool mAutoResizeContainerEnabled;     ///< Whether to automatically resize container (affects RulerDomain's on X/Y axes)
   bool mWrapMode;                       ///< Whether to wrap contents based on container size.
@@ -851,6 +865,7 @@ private:
   Vector2 mLastVelocity;                ///< Record the last velocity from PanGesture (Finish event doesn't have correct velocity)
   LockAxis mLockAxis;
 
+  Timer mTouchDownTimer;                ///< Used to interrupt snap-animation. This cannot be done in OnTouchEvent without breaking fast flick behavior.
   Timer mOvershootRefreshTimer;
   Timer mRefreshTimer;                  ///< Refresh timer is used to provide the Application developer with updates as animations run.
   int mRefreshIntervalMilliseconds;     ///< Refresh timer interval.