ScrollView: Avoid animating in wrong direction during fast flick
[platform/core/uifw/dali-toolkit.git] / base / dali-toolkit / internal / controls / scrollable / scroll-view / scroll-view-impl.cpp
index b11b64c..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),
@@ -540,7 +541,8 @@ ScrollView::ScrollView()
   mAxisAutoLockGradient(Toolkit::ScrollView::DEFAULT_AXIS_AUTO_LOCK_GRADIENT),
   mFrictionCoefficient(Toolkit::ScrollView::DEFAULT_FRICTION_COEFFICIENT),
   mFlickSpeedCoefficient(Toolkit::ScrollView::DEFAULT_FLICK_SPEED_COEFFICIENT),
-  mMaxFlickSpeed(Toolkit::ScrollView::DEFAULT_MAX_FLICK_SPEED)
+  mMaxFlickSpeed(Toolkit::ScrollView::DEFAULT_MAX_FLICK_SPEED),
+  mInAccessibilityPan(false)
 {
   SetRequiresMouseWheelEvents(true);
 }
@@ -1365,7 +1367,8 @@ bool ScrollView::SnapWithVelocity(Vector2 velocity)
   {
     horizontal = All;
 
-    if(speed2 > flickSpeedThreshold2) // exceeds flick threshold
+    if( speed2 > flickSpeedThreshold2 || // exceeds flick threshold
+        mInAccessibilityPan ) // With AccessibilityPan its easier to move between snap positions
     {
       if((angle >= -orthoAngleRange) && (angle < orthoAngleRange)) // Swiping East
       {
@@ -1392,7 +1395,8 @@ bool ScrollView::SnapWithVelocity(Vector2 velocity)
   {
     vertical = All;
 
-    if(speed2 > flickSpeedThreshold2) // exceeds flick threshold
+    if( speed2 > flickSpeedThreshold2 || // exceeds flick threshold
+        mInAccessibilityPan ) // With AccessibilityPan its easier to move between snap positions
     {
       if((angle >= M_PI_2-orthoAngleRange) && (angle < M_PI_2+orthoAngleRange)) // Swiping South
       {
@@ -1875,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)
@@ -1891,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 )
@@ -1925,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 )
@@ -1937,7 +1969,7 @@ bool ScrollView::OnTouchEvent(const TouchEvent& event)
         FinishTransform();
       }
     }
-    mTouchDownReceived = false;
+    mTouchDownTimeoutReached = false;
     mScrollInterrupted = false;
   }
 
@@ -2055,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;
@@ -2140,17 +2173,23 @@ void ScrollView::OnPan(PanGesture gesture)
       self.SetProperty( mPropertyScrollStartPagePosition, GetCurrentScrollPosition() );
 
       //  Update property: X & Y = Position (only when in panning mode - in snapping mode, X & Y are animated).
-      Constraint constraint = Constraint::New<float>( mPropertyX,
-                                           LocalSource( mPropertyPosition ),
-                                           Source( self, mPropertyPanning ),
-                                           InternalXConstraint );
-      mScrollMainInternalXConstraint = self.ApplyConstraint(constraint);
+      if( ! mScrollMainInternalXConstraint )
+      {
+        Constraint constraint = Constraint::New<float>( mPropertyX,
+                                                        LocalSource( mPropertyPosition ),
+                                                        Source( self, mPropertyPanning ),
+                                                        InternalXConstraint );
+        mScrollMainInternalXConstraint = self.ApplyConstraint( constraint );
+      }
+      if( ! mScrollMainInternalYConstraint )
+      {
+        Constraint constraint = Constraint::New<float>( mPropertyY,
+                                                        LocalSource( mPropertyPosition ),
+                                                        Source( self, mPropertyPanning ),
+                                                        InternalYConstraint );
+        mScrollMainInternalYConstraint = self.ApplyConstraint( constraint );
+      }
 
-      constraint = Constraint::New<float>( mPropertyY,
-                                           LocalSource( mPropertyPosition ),
-                                           Source( self, mPropertyPanning ),
-                                           InternalYConstraint );
-      mScrollMainInternalYConstraint = self.ApplyConstraint(constraint);
       // When panning we want to make sure overshoot values are affected by pre position and post position
       SetOvershootConstraintsEnabled(true);
       break;
@@ -2171,6 +2210,8 @@ void ScrollView::OnPan(PanGesture gesture)
       // Remove X & Y position constraints as they are not required when we are not panning.
       self.RemoveConstraint(mScrollMainInternalXConstraint);
       self.RemoveConstraint(mScrollMainInternalYConstraint);
+      mScrollMainInternalXConstraint.Reset();
+      mScrollMainInternalYConstraint.Reset();
       break;
     }
 
@@ -2285,7 +2326,11 @@ Vector3 ScrollView::GetOvershoot(Vector3& position) const
 
 bool ScrollView::OnAccessibilityPan(PanGesture gesture)
 {
+  // Keep track of whether this is an AccessibilityPan
+  mInAccessibilityPan = true;
   OnPan(gesture);
+  mInAccessibilityPan = false;
+
   return true;
 }