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 4a6bd09..dca4aab 100644 (file)
@@ -1,19 +1,18 @@
-/*
- * Copyright (c) 2014 Samsung Electronics Co., Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
+//
+// Copyright (c) 2014 Samsung Electronics Co., Ltd.
+//
+// Licensed under the Flora License, Version 1.0 (the License);
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://floralicense.org/license/
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an AS IS BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
 
 // INTERNAL INCLUDES
 #include <dali/public-api/events/mouse-wheel-event.h>
@@ -45,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);
@@ -519,7 +519,7 @@ ScrollView::ScrollView()
   mRotationDelta(0.0f),
   mScrollPreRotation(0.0f),
   mScrollPostRotation(0.0f),
-  mTouchDownReceived(false),
+  mTouchDownTimeoutReached(false),
   mActorAutoSnapEnabled(false),
   mAutoResizeContainerEnabled(false),
   mWrapMode(false),
@@ -541,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);
 }
@@ -1358,21 +1359,32 @@ bool ScrollView::SnapWithVelocity(Vector2 velocity)
   const float orthoAngleRange = FLICK_ORTHO_ANGLE_RANGE * M_PI / 180.0f;
   const float flickSpeedThreshold2 = FLICK_SPEED_THRESHOLD*FLICK_SPEED_THRESHOLD;
 
+  Vector3 positionSnap = mScrollPostPosition;
+
   // Flick logic X Axis
 
   if(mRulerX->IsEnabled())
   {
     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
       {
         biasX = 0.0f, horizontal = Left;
+
+        // This guards against an error where no movement occurs, due to the flick finishing
+        // before the update-thread has advanced mScrollPostPosition past the the previous snap point.
+        positionSnap.x += 1.0f;
       }
       else if((angle >= M_PI-orthoAngleRange) || (angle < -M_PI+orthoAngleRange)) // Swiping West
       {
         biasX = 1.0f, horizontal = Right;
+
+        // This guards against an error where no movement occurs, due to the flick finishing
+        // before the update-thread has advanced mScrollPostPosition past the the previous snap point.
+        positionSnap.x -= 1.0f;
       }
     }
   }
@@ -1383,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
       {
@@ -1407,8 +1420,7 @@ bool ScrollView::SnapWithVelocity(Vector2 velocity)
     alphaFunction = mFlickAlphaFunction;
   }
 
-  // Position Snap ////////////////////////////////////////////////////////////
-  Vector3 positionSnap = mScrollPostPosition;
+  // Calculate next positionSnap ////////////////////////////////////////////////////////////
 
   if(mActorAutoSnapEnabled)
   {
@@ -1867,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)
@@ -1883,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 )
@@ -1917,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 )
@@ -1929,7 +1969,7 @@ bool ScrollView::OnTouchEvent(const TouchEvent& event)
         FinishTransform();
       }
     }
-    mTouchDownReceived = false;
+    mTouchDownTimeoutReached = false;
     mScrollInterrupted = false;
   }
 
@@ -2047,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;
@@ -2132,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;
@@ -2163,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;
     }
 
@@ -2277,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;
 }