Change overshoot to have constant speed as a property
[platform/core/uifw/dali-toolkit.git] / base / dali-toolkit / internal / controls / scrollable / item-view / item-view-impl.cpp
index 7aca7ff..6d4d4b8 100644 (file)
 
 // EXTERNAL INCLUDES
 #include <algorithm>
+#include <dali/public-api/animation/constraints.h>
+#include <dali/public-api/common/set-wrapper.h>
+#include <dali/public-api/common/stage.h>
+#include <dali/public-api/events/mouse-wheel-event.h>
+#include <dali/public-api/events/touch-event.h>
+#include <dali/public-api/object/type-registry.h>
 
 // INTERNAL INCLUDES
-#include <dali/public-api/events/mouse-wheel-event.h>
 #include <dali-toolkit/public-api/controls/scrollable/item-view/item-factory.h>
 #include <dali-toolkit/internal/controls/scrollable/scroll-connector-impl.h>
 #include <dali-toolkit/internal/controls/scrollable/bouncing-effect-actor.h>
 
-using namespace std;
+using std::string;
+using std::set;
 using namespace Dali;
 
 namespace // unnamed namespace
@@ -53,8 +59,6 @@ const float MILLISECONDS_PER_SECONDS = 1000.0f;
 const Vector2 OVERSHOOT_BOUNCE_ACTOR_DEFAULT_SIZE( 720.0f, 42.0f );
 const float OVERSHOOT_BOUNCE_ACTOR_RESIZE_THRESHOLD = 180.0f;
 const Vector4 OVERSHOOT_OVERLAY_NINE_PATCH_BORDER(0.0f, 0.0f, 1.0f, 12.0f);
-const float MAXIMUM_OVERSHOOT_HEIGHT = 36.0f;  // 36 pixels
-const float DEFAULT_OVERSHOOT_ANIMATION_DURATION = 0.5f;  // 0.5 second
 const float DEFAULT_KEYBOARD_FOCUS_SCROLL_DURATION = 0.2f;
 
 const string LAYOUT_POSITION_PROPERTY_NAME( "item-view-layout-position" );
@@ -331,6 +335,7 @@ ItemView::ItemView(ItemFactory& factory)
   mRefreshOrderHint(true/*Refresh item 0 first*/),
   mMinimumSwipeSpeed(DEFAULT_MINIMUM_SWIPE_SPEED),
   mMinimumSwipeDistance(DEFAULT_MINIMUM_SWIPE_DISTANCE),
+  mMouseWheelScrollDistanceStep(0.0f),
   mScrollDistance(0.0f),
   mScrollSpeed(0.0f),
   mTotalPanDisplacement(Vector2::ZERO),
@@ -338,6 +343,9 @@ ItemView::ItemView(ItemFactory& factory)
   mIsFlicking(false),
   mGestureState(Gesture::Clear),
   mAddingItems(false),
+  mPropertyPosition(Property::INVALID_INDEX),
+  mPropertyMinimumLayoutPosition(Property::INVALID_INDEX),
+  mPropertyScrollSpeed(Property::INVALID_INDEX),
   mRefreshEnabled(true),
   mItemsParentOrigin( ParentOrigin::CENTER),
   mItemsAnchorPoint( AnchorPoint::CENTER)
@@ -620,7 +628,7 @@ float ItemView::GetAnchoringDuration() const
 
 void ItemView::SetRefreshInterval(float intervalLayoutPositions)
 {
-  if(mRefreshIntervalLayoutPositions != intervalLayoutPositions)
+  if( !Equals(mRefreshIntervalLayoutPositions, intervalLayoutPositions) )
   {
     mRefreshIntervalLayoutPositions = intervalLayoutPositions;
 
@@ -676,37 +684,58 @@ void ItemView::InsertItem( Item newItem, float durationSeconds )
 {
   mAddingItems = true;
 
-  SetupActor( newItem, durationSeconds );
-  Self().Add( newItem.second );
+  Actor displacedActor;
+  ItemPoolIter afterDisplacedIter = mItemPool.end();
 
   ItemPoolIter foundIter = mItemPool.find( newItem.first );
   if( mItemPool.end() != foundIter )
   {
-    Actor moveMe = foundIter->second;
+    SetupActor( newItem, durationSeconds );
+    Self().Add( newItem.second );
+
+    displacedActor = foundIter->second;
     foundIter->second = newItem.second;
 
+    afterDisplacedIter = ++foundIter;
+  }
+  else
+  {
+    // Inserting before the existing item range?
+    ItemPoolIter iter = mItemPool.begin();
+    if( iter != mItemPool.end() &&
+        iter->first > newItem.first )
+    {
+      displacedActor = iter->second;
+      mItemPool.erase( iter++ ); // iter is still valid after the erase
+
+      afterDisplacedIter = iter;
+    }
+  }
+
+  if( displacedActor )
+  {
     // Move the existing actors to make room
-    for( ItemPoolIter iter = ++foundIter; mItemPool.end() != iter; ++iter )
+    for( ItemPoolIter iter = afterDisplacedIter; mItemPool.end() != iter; ++iter )
     {
       Actor temp = iter->second;
-      iter->second = moveMe;
-      moveMe = temp;
+      iter->second = displacedActor;
+      displacedActor = temp;
 
       iter->second.RemoveConstraints();
-      mActiveLayout->ApplyConstraints(iter->second, iter->first, durationSeconds, mScrollPositionObject, Self() );
+      mActiveLayout->ApplyConstraints( iter->second, iter->first, durationSeconds, mScrollPositionObject, Self() );
     }
 
     // Create last item
-    ItemId lastId = mItemPool.rbegin()->first;
-    Item lastItem( lastId + 1, moveMe );
-    mItemPool.insert( lastItem );
+    ItemPool::reverse_iterator lastIter = mItemPool.rbegin();
+    if ( lastIter != mItemPool.rend() )
+    {
+      ItemId lastId = lastIter->first;
+      Item lastItem( lastId + 1, displacedActor );
+      mItemPool.insert( lastItem );
 
-    lastItem.second.RemoveConstraints();
-    mActiveLayout->ApplyConstraints(lastItem.second, lastItem.first, durationSeconds, mScrollPositionObject, Self() );
-  }
-  else
-  {
-    mItemPool.insert( newItem );
+      lastItem.second.RemoveConstraints();
+      mActiveLayout->ApplyConstraints( lastItem.second, lastItem.first, durationSeconds, mScrollPositionObject, Self() );
+    }
   }
 
   CalculateDomainSize(Self().GetCurrentSize());
@@ -719,18 +748,16 @@ void ItemView::InsertItems( const ItemContainer& newItems, float durationSeconds
   mAddingItems = true;
 
   // Insert from lowest id to highest
-  set<Item> sortedItems;
+  std::set<Item> sortedItems;
   for( ConstItemIter iter = newItems.begin(); newItems.end() != iter; ++iter )
   {
     sortedItems.insert( *iter );
   }
 
-  for( set<Item>::iterator iter = sortedItems.begin(); sortedItems.end() != iter; ++iter )
+  for( std::set<Item>::iterator iter = sortedItems.begin(); sortedItems.end() != iter; ++iter )
   {
     Self().Add( iter->second );
 
-    cout << "inserting item: " << iter->first << endl;
-
     ItemPoolIter foundIter = mItemPool.find( iter->first );
     if( mItemPool.end() != foundIter )
     {
@@ -778,16 +805,18 @@ void ItemView::InsertItems( const ItemContainer& newItems, float durationSeconds
 
 void ItemView::RemoveItem( unsigned int itemId, float durationSeconds )
 {
-  bool actorRemoved = RemoveActor( itemId );
-  if( actorRemoved )
+  bool actorsReordered = RemoveActor( itemId );
+  if( actorsReordered )
   {
     ReapplyAllConstraints( durationSeconds );
+
+    OnItemsRemoved();
   }
 }
 
 void ItemView::RemoveItems( const ItemIdContainer& itemIds, float durationSeconds )
 {
-  bool actorRemoved( false );
+  bool actorsReordered( false );
 
   // Remove from highest id to lowest
   set<ItemId> sortedItems;
@@ -800,27 +829,44 @@ void ItemView::RemoveItems( const ItemIdContainer& itemIds, float durationSecond
   {
     if( RemoveActor( *iter ) )
     {
-      actorRemoved = true;
+      actorsReordered = true;
     }
   }
 
-  if( actorRemoved )
+  if( actorsReordered )
   {
     ReapplyAllConstraints( durationSeconds );
+
+    OnItemsRemoved();
   }
 }
 
 bool ItemView::RemoveActor(unsigned int itemId)
 {
-  bool removed( false );
-
-  const ItemPoolIter removeIter = mItemPool.find( itemId );
+  bool reordered( false );
 
+  ItemPoolIter removeIter = mItemPool.find( itemId );
   if( removeIter != mItemPool.end() )
   {
     ReleaseActor(itemId, removeIter->second);
+  }
+  else
+  {
+    // Removing before the existing item range?
+    ItemPoolIter iter = mItemPool.begin();
+    if( iter != mItemPool.end() &&
+        iter->first > itemId )
+    {
+      // In order to decrement the first visible item ID
+      mItemPool.insert( Item(iter->first - 1, Actor()) );
+
+      removeIter = mItemPool.begin();
+    }
+  }
 
-    removed = true;
+  if( removeIter != mItemPool.end() )
+  {
+    reordered = true;
 
     // Adjust the remaining item IDs, for example if item 2 is removed:
     //   Initial actors:     After insert:
@@ -842,7 +888,7 @@ bool ItemView::RemoveActor(unsigned int itemId)
     }
   }
 
-  return removed;
+  return reordered;
 }
 
 void ItemView::ReplaceItem( Item replacementItem, float durationSeconds )
@@ -898,7 +944,7 @@ void ItemView::RemoveActorsOutsideRange( ItemRange range )
 
 void ItemView::AddActorsWithinRange( ItemRange range, float durationSeconds )
 {
-  range.end = min(mItemFactory.GetNumberOfItems(), range.end);
+  range.end = std::min(mItemFactory.GetNumberOfItems(), range.end);
 
   // The order of addition depends on the scroll direction.
   if (mRefreshOrderHint)
@@ -1091,22 +1137,33 @@ void ItemView::ReapplyAllConstraints( float durationSeconds )
     actor.RemoveConstraints();
     mActiveLayout->ApplyConstraints(actor, id, durationSeconds, mScrollPositionObject, Self());
   }
+}
 
+void ItemView::OnItemsRemoved()
+{
   CalculateDomainSize(Self().GetCurrentSize());
+
+  // Adjust scroll-position after an item is removed
+  if( mActiveLayout )
+  {
+    float firstItemScrollPosition = ClampFirstItemPosition(GetCurrentLayoutPosition(0), Self().GetCurrentSize(), *mActiveLayout);
+
+    mScrollPositionObject.SetProperty( ScrollConnector::SCROLL_POSITION, firstItemScrollPosition );
+  }
 }
 
 float ItemView::ClampFirstItemPosition(float targetPosition, const Vector3& targetSize, ItemLayout& layout)
 {
   Actor self = Self();
   float minLayoutPosition = layout.GetMinimumLayoutPosition(mItemFactory.GetNumberOfItems(), targetSize);
-  float clamppedPosition = min(0.0f, max(minLayoutPosition, targetPosition));
+  float clamppedPosition = std::min(0.0f, std::max(minLayoutPosition, targetPosition));
   mScrollOvershoot = targetPosition - clamppedPosition;
   self.SetProperty(mPropertyMinimumLayoutPosition, minLayoutPosition);
 
   return clamppedPosition;
 }
 
-void ItemView::OnPan(PanGesture gesture)
+void ItemView::OnPan( const PanGesture& gesture )
 {
   Actor self = Self();
   const Vector3 layoutSize = Self().GetCurrentSize();
@@ -1146,7 +1203,7 @@ void ItemView::OnPan(PanGesture gesture)
 
         RemoveAnimation(mScrollAnimation);
 
-        float flickAnimationDuration = Clamp( mActiveLayout->GetItemFlickAnimationDuration() * max(1.0f, fabsf(firstItemScrollPosition - GetCurrentLayoutPosition(0)))
+        float flickAnimationDuration = Clamp( mActiveLayout->GetItemFlickAnimationDuration() * std::max(1.0f, fabsf(firstItemScrollPosition - GetCurrentLayoutPosition(0)))
                                        , DEFAULT_MINIMUM_SWIPE_DURATION, DEFAULT_MAXIMUM_SWIPE_DURATION);
 
         mScrollAnimation = Animation::New(flickAnimationDuration);
@@ -1197,23 +1254,18 @@ void ItemView::OnPan(PanGesture gesture)
 
       float firstItemScrollPosition = ClampFirstItemPosition(layoutPositionDelta, layoutSize, *mActiveLayout);
 
+      float currentOvershoot = mScrollPositionObject.GetProperty<float>(ScrollConnector::OVERSHOOT);
+
       mScrollPositionObject.SetProperty( ScrollConnector::SCROLL_POSITION, firstItemScrollPosition );
       self.SetProperty(mPropertyPosition, GetScrollPosition(firstItemScrollPosition, layoutSize));
 
-      mTotalPanDisplacement += gesture.displacement;
-      mScrollOvershoot = layoutPositionDelta - firstItemScrollPosition;
-      if( mScrollOvershoot > Math::MACHINE_EPSILON_1 )
-      {
-        AnimateScrollOvershoot(1.0f);
-      }
-      else if( mScrollOvershoot < -Math::MACHINE_EPSILON_1 )
-      {
-        AnimateScrollOvershoot(-1.0f);
-      }
-      else
+      if( (firstItemScrollPosition >= 0.0f && currentOvershoot < 1.0f) || (firstItemScrollPosition <= mActiveLayout->GetMinimumLayoutPosition(mItemFactory.GetNumberOfItems(), layoutSize) && currentOvershoot > -1.0f) )
       {
-        AnimateScrollOvershoot(0.0f);
+        mTotalPanDisplacement += gesture.displacement;
       }
+
+      mScrollOvershoot = CalculateScrollOvershoot();
+      mScrollPositionObject.SetProperty( ScrollConnector::OVERSHOOT, mScrollOvershoot );
     }
     break;
 
@@ -1541,7 +1593,6 @@ void ItemView::SetOvershootEnabled( bool enable )
                                         OvershootOverlayVisibilityConstraint() );
     mOvershootOverlay.ApplyConstraint(constraint);
 
-    Actor self = Self();
     constraint = Constraint::New<float>( effectOvershootPropertyIndex,
                                          Source( mScrollPositionObject, ScrollConnector::OVERSHOOT ),
                                          EqualToConstraint() );
@@ -1570,11 +1621,11 @@ float ItemView::CalculateScrollOvershoot()
     float positionDelta = GetCurrentLayoutPosition(0) + scrollDistance;
     float minLayoutPosition = mActiveLayout->GetMinimumLayoutPosition(mItemFactory.GetNumberOfItems(), Self().GetCurrentSize());
     self.SetProperty(mPropertyMinimumLayoutPosition, minLayoutPosition);
-    float clamppedPosition = min(0.0f, max(minLayoutPosition, positionDelta));
+    float clamppedPosition = std::min(0.0f, std::max(minLayoutPosition, positionDelta));
     overshoot = positionDelta - clamppedPosition;
   }
 
-  return overshoot;
+  return overshoot > 0.0f ? std::min(overshoot, 1.0f) : std::max(overshoot, -1.0f);
 }
 
 void ItemView::AnimateScrollOvershoot(float overshootAmount, bool animateBack)
@@ -1590,17 +1641,23 @@ void ItemView::AnimateScrollOvershoot(float overshootAmount, bool animateBack)
     return;
   }
 
-  Actor self = Self();
-  float currentOvershoot = mScrollPositionObject.GetProperty<float>(ScrollConnector::OVERSHOOT);
-  float duration = DEFAULT_OVERSHOOT_ANIMATION_DURATION * (animatingOn ? (1.0f - fabsf(currentOvershoot)) : fabsf(currentOvershoot));
+  if(mOvershootAnimationSpeed > Math::MACHINE_EPSILON_0)
+  {
+    float currentOvershoot = mScrollPositionObject.GetProperty<float>(ScrollConnector::OVERSHOOT);
+    float duration = mOvershootOverlay.GetCurrentSize().height * (animatingOn ? (1.0f - fabsf(currentOvershoot)) : fabsf(currentOvershoot)) / mOvershootAnimationSpeed;
 
-  RemoveAnimation(mScrollOvershootAnimation);
-  mScrollOvershootAnimation = Animation::New(duration);
-  mScrollOvershootAnimation.FinishedSignal().Connect(this, &ItemView::OnOvershootOnFinished);
-  mScrollOvershootAnimation.AnimateTo( Property(mScrollPositionObject, ScrollConnector::OVERSHOOT), overshootAmount, TimePeriod(0.0f, duration) );
-  mScrollOvershootAnimation.Play();
+    RemoveAnimation(mScrollOvershootAnimation);
+    mScrollOvershootAnimation = Animation::New(duration);
+    mScrollOvershootAnimation.FinishedSignal().Connect(this, &ItemView::OnOvershootOnFinished);
+    mScrollOvershootAnimation.AnimateTo( Property(mScrollPositionObject, ScrollConnector::OVERSHOOT), overshootAmount, TimePeriod(0.0f, duration) );
+    mScrollOvershootAnimation.Play();
 
-  mAnimatingOvershootOn = animatingOn;
+    mAnimatingOvershootOn = animatingOn;
+  }
+  else
+  {
+    mScrollPositionObject.SetProperty( ScrollConnector::OVERSHOOT, overshootAmount );
+  }
 }
 
 void ItemView::SetItemsParentOrigin( const Vector3& parentOrigin )
@@ -1639,8 +1696,16 @@ Vector3 ItemView::GetItemsAnchorPoint() const
 
 void ItemView::GetItemsRange(ItemRange& range)
 {
-  range.begin = mItemPool.begin()->first;
-  range.end = mItemPool.rbegin()->first + 1;
+  if( !mItemPool.empty() )
+  {
+    range.begin = mItemPool.begin()->first;
+    range.end = mItemPool.rbegin()->first + 1;
+  }
+  else
+  {
+    range.begin = 0;
+    range.end = 0;
+  }
 }
 
 void ItemView::OnScrollPositionChanged( float position )