Fix for ItemView OVERSHOOT_SIZE
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / controls / scrollable / item-view / item-view-impl.cpp
1 /*
2  * Copyright (c) 2014 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali-toolkit/internal/controls/scrollable/item-view/item-view-impl.h>
20
21 // EXTERNAL INCLUDES
22 #include <cstring> // for strcmp
23 #include <algorithm>
24 #include <dali/public-api/animation/constraint.h>
25 #include <dali/public-api/animation/constraints.h>
26 #include <dali/devel-api/common/set-wrapper.h>
27 #include <dali/public-api/common/stage.h>
28 #include <dali/public-api/events/wheel-event.h>
29 #include <dali/public-api/events/touch-data.h>
30 #include <dali/public-api/object/type-registry.h>
31 #include <dali/public-api/object/type-registry-helper.h>
32
33 // INTERNAL INCLUDES
34 #include <dali-toolkit/public-api/controls/scroll-bar/scroll-bar.h>
35 #include <dali-toolkit/public-api/controls/scrollable/item-view/item-factory.h>
36 #include <dali-toolkit/internal/controls/scrollable/bouncing-effect-actor.h>
37
38 using std::string;
39 using std::set;
40 using namespace Dali;
41
42 namespace // Unnamed namespace
43 {
44
45 const float DEFAULT_MINIMUM_SWIPE_SPEED = 1.0f;
46 const float DEFAULT_MINIMUM_SWIPE_DISTANCE = 3.0f;
47 const float DEFAULT_WHEEL_SCROLL_DISTANCE_STEP_PROPORTION = 0.1f;
48
49 const float DEFAULT_MINIMUM_SWIPE_DURATION = 0.45f;
50 const float DEFAULT_MAXIMUM_SWIPE_DURATION = 2.6f;
51
52 const float DEFAULT_REFRESH_INTERVAL_LAYOUT_POSITIONS = 20.0f; // 1 updates per 20 items
53 const int WHEEL_EVENT_FINISHED_TIME_OUT = 500;  // 0.5 second
54
55 const float DEFAULT_ANCHORING_DURATION = 1.0f;  // 1 second
56
57 const float MILLISECONDS_PER_SECONDS = 1000.0f;
58
59 const float OVERSHOOT_BOUNCE_ACTOR_RESIZE_THRESHOLD = 180.0f;
60 const Vector4 OVERSHOOT_OVERLAY_NINE_PATCH_BORDER(0.0f, 0.0f, 1.0f, 12.0f);
61 const float DEFAULT_KEYBOARD_FOCUS_SCROLL_DURATION = 0.2f;
62
63 const unsigned int OVERSHOOT_SIZE_CONSTRAINT_TAG(42);
64
65 /**
66  * Local helper to convert pan distance (in actor coordinates) to the layout-specific scrolling direction
67  */
68 float CalculateScrollDistance(Vector2 panDistance, Toolkit::ItemLayout& layout)
69 {
70   Radian scrollDirection(layout.GetScrollDirection());
71
72   float cosTheta = cosf(scrollDirection);
73   float sinTheta = sinf(scrollDirection);
74
75   return panDistance.x * sinTheta + panDistance.y * cosTheta;
76 }
77
78 // Overshoot overlay constraints
79 struct OvershootOverlaySizeConstraint
80 {
81   OvershootOverlaySizeConstraint( float height )
82   : mOvershootHeight( height )
83   {
84   }
85
86   void operator()( Vector3& current, const PropertyInputContainer& inputs )
87   {
88     const Vector2& parentScrollDirection = inputs[0]->GetVector2();
89     const Toolkit::ControlOrientation::Type& layoutOrientation = static_cast<Toolkit::ControlOrientation::Type>(inputs[1]->GetInteger());
90     const Vector3& parentSize = inputs[2]->GetVector3();
91
92     if(Toolkit::IsVertical(layoutOrientation))
93     {
94       current.width = fabsf(parentScrollDirection.y) > Math::MACHINE_EPSILON_1 ? parentSize.x : parentSize.y;
95     }
96     else
97     {
98       current.width = fabsf(parentScrollDirection.x) > Math::MACHINE_EPSILON_1 ? parentSize.y : parentSize.x;
99     }
100
101     current.height = ( current.width > OVERSHOOT_BOUNCE_ACTOR_RESIZE_THRESHOLD ) ? mOvershootHeight : mOvershootHeight*0.5f;
102   }
103
104   float mOvershootHeight;
105 };
106
107 void OvershootOverlayRotationConstraint( Quaternion& current, const PropertyInputContainer& inputs )
108 {
109   const Vector2& parentScrollDirection = inputs[0]->GetVector2();
110   const Toolkit::ControlOrientation::Type& layoutOrientation = static_cast<Toolkit::ControlOrientation::Type>(inputs[1]->GetInteger());
111   const float parentOvershoot = inputs[2]->GetFloat();
112
113   float multiplier = 0;
114   if(Toolkit::IsVertical(layoutOrientation))
115   {
116     if(fabsf(parentScrollDirection.y) <= Math::MACHINE_EPSILON_1)
117     {
118       if( (layoutOrientation == Toolkit::ControlOrientation::Up && parentOvershoot < Math::MACHINE_EPSILON_0)
119           || (layoutOrientation == Toolkit::ControlOrientation::Down && parentOvershoot > Math::MACHINE_EPSILON_0) )
120       {
121         multiplier = 0.5f;
122       }
123       else
124       {
125         multiplier = 1.5f;
126       }
127     }
128     else if( (parentOvershoot > Math::MACHINE_EPSILON_0 && parentScrollDirection.y > Math::MACHINE_EPSILON_0)
129           || (parentOvershoot < Math::MACHINE_EPSILON_0 && parentScrollDirection.y < Math::MACHINE_EPSILON_0) )
130     {
131       multiplier = 0.0f;
132     }
133     else
134     {
135       multiplier = 1.0f;
136     }
137   }
138   else
139   {
140     if(fabsf(parentScrollDirection.x) <= Math::MACHINE_EPSILON_1)
141     {
142       if( (layoutOrientation == Toolkit::ControlOrientation::Left && parentOvershoot > Math::MACHINE_EPSILON_0)
143           ||(layoutOrientation == Toolkit::ControlOrientation::Right && parentOvershoot < Math::MACHINE_EPSILON_0) )
144       {
145         multiplier = 1.0f;
146       }
147       else
148       {
149         multiplier = 0.0f;
150       }
151     }
152     else if( (parentOvershoot > Math::MACHINE_EPSILON_0 && parentScrollDirection.x > Math::MACHINE_EPSILON_0)
153           || (parentOvershoot < Math::MACHINE_EPSILON_0 && parentScrollDirection.x < Math::MACHINE_EPSILON_0) )
154     {
155       multiplier = 1.5f;
156     }
157     else
158     {
159       multiplier = 0.5f;
160     }
161   }
162
163   current = Quaternion( Radian( multiplier * Math::PI ), Vector3::ZAXIS );
164 }
165
166 void OvershootOverlayPositionConstraint( Vector3& current, const PropertyInputContainer& inputs )
167 {
168   const Vector3& parentSize = inputs[0]->GetVector3();
169   const Vector2& parentScrollDirection = inputs[1]->GetVector2();
170   const Toolkit::ControlOrientation::Type& layoutOrientation = static_cast<Toolkit::ControlOrientation::Type>(inputs[2]->GetInteger());
171   const float parentOvershoot = inputs[3]->GetFloat();
172
173   Vector3 relativeOffset;
174
175   if(Toolkit::IsVertical(layoutOrientation))
176   {
177     if(fabsf(parentScrollDirection.y) <= Math::MACHINE_EPSILON_1)
178     {
179       if( (layoutOrientation == Toolkit::ControlOrientation::Up && parentOvershoot < Math::MACHINE_EPSILON_0)
180           || (layoutOrientation == Toolkit::ControlOrientation::Down && parentOvershoot > Math::MACHINE_EPSILON_0) )
181       {
182         relativeOffset = Vector3(1.0f, 0.0f, 0.0f);
183       }
184       else
185       {
186         relativeOffset =Vector3(0.0f, 1.0f, 0.0f);
187       }
188     }
189     else if( (parentOvershoot > Math::MACHINE_EPSILON_0 && parentScrollDirection.y > Math::MACHINE_EPSILON_0)
190           || (parentOvershoot < Math::MACHINE_EPSILON_0 && parentScrollDirection.y < Math::MACHINE_EPSILON_0) )
191     {
192       relativeOffset = Vector3(0.0f, 0.0f, 0.0f);
193     }
194     else
195     {
196       relativeOffset = Vector3(1.0f, 1.0f, 0.0f);
197     }
198   }
199   else
200   {
201     if(fabsf(parentScrollDirection.x) <= Math::MACHINE_EPSILON_1)
202     {
203       if( (layoutOrientation == Toolkit::ControlOrientation::Left && parentOvershoot < Math::MACHINE_EPSILON_0)
204           || (layoutOrientation == Toolkit::ControlOrientation::Right && parentOvershoot > Math::MACHINE_EPSILON_0) )
205       {
206         relativeOffset = Vector3(0.0f, 0.0f, 0.0f);
207       }
208       else
209       {
210         relativeOffset = Vector3(1.0f, 1.0f, 0.0f);
211       }
212     }
213     else if( (parentOvershoot > Math::MACHINE_EPSILON_0 && parentScrollDirection.x > Math::MACHINE_EPSILON_0)
214           || (parentOvershoot < Math::MACHINE_EPSILON_0 && parentScrollDirection.x < Math::MACHINE_EPSILON_0) )
215     {
216       relativeOffset = Vector3(0.0f, 1.0f, 0.0f);
217     }
218     else
219     {
220       relativeOffset = Vector3(1.0f, 0.0f, 0.0f);
221     }
222   }
223
224   current = relativeOffset * parentSize;
225 }
226
227 void OvershootOverlayVisibilityConstraint( bool& current, const PropertyInputContainer& inputs )
228 {
229   current = inputs[0]->GetBoolean();
230 }
231
232 } // unnamed namespace
233
234 namespace Dali
235 {
236
237 namespace Toolkit
238 {
239
240 namespace Internal
241 {
242
243 namespace // unnamed namespace
244 {
245
246 //Type registration
247
248 DALI_TYPE_REGISTRATION_BEGIN( Toolkit::ItemView, Toolkit::Scrollable, NULL)
249
250 DALI_PROPERTY_REGISTRATION( Toolkit, ItemView, "minimumSwipeSpeed",          FLOAT,     MINIMUM_SWIPE_SPEED          )
251 DALI_PROPERTY_REGISTRATION( Toolkit, ItemView, "minimumSwipeDistance",       FLOAT,     MINIMUM_SWIPE_DISTANCE       )
252 DALI_PROPERTY_REGISTRATION( Toolkit, ItemView, "wheelScrollDistanceStep",    FLOAT,     WHEEL_SCROLL_DISTANCE_STEP   )
253 DALI_PROPERTY_REGISTRATION( Toolkit, ItemView, "snapToItemEnabled",          BOOLEAN,   SNAP_TO_ITEM_ENABLED         )
254 DALI_PROPERTY_REGISTRATION( Toolkit, ItemView, "refreshInterval",            FLOAT,     REFRESH_INTERVAL             )
255
256 DALI_ANIMATABLE_PROPERTY_REGISTRATION( Toolkit, ItemView, "layoutPosition",      FLOAT,    LAYOUT_POSITION)
257 DALI_ANIMATABLE_PROPERTY_REGISTRATION( Toolkit, ItemView, "scrollSpeed",         FLOAT,    SCROLL_SPEED)
258 DALI_ANIMATABLE_PROPERTY_REGISTRATION( Toolkit, ItemView, "overshoot",           FLOAT,    OVERSHOOT)
259 DALI_ANIMATABLE_PROPERTY_REGISTRATION( Toolkit, ItemView, "scrollDirection",     VECTOR2,  SCROLL_DIRECTION)
260 DALI_ANIMATABLE_PROPERTY_REGISTRATION( Toolkit, ItemView, "layoutOrientation",   INTEGER,  LAYOUT_ORIENTATION)
261 DALI_ANIMATABLE_PROPERTY_REGISTRATION( Toolkit, ItemView, "scrollContentSize",   FLOAT,    SCROLL_CONTENT_SIZE)
262
263 DALI_SIGNAL_REGISTRATION(              Toolkit, ItemView, "layoutActivated",     LAYOUT_ACTIVATED_SIGNAL )
264
265 DALI_ACTION_REGISTRATION(              Toolkit, ItemView, "stopScrolling",       ACTION_STOP_SCROLLING   )
266
267 DALI_ACTION_REGISTRATION(              Toolkit, ItemView, "enableRefresh",       ACTION_ENABLE_REFRESH   )
268 DALI_ACTION_REGISTRATION(              Toolkit, ItemView, "disableRefresh",      ACTION_DISABLE_REFRESH  )
269
270 DALI_TYPE_REGISTRATION_END()
271
272 bool FindById( const ItemContainer& items, ItemId id )
273 {
274   for( ConstItemIter iter = items.begin(); items.end() != iter; ++iter )
275   {
276     if( iter->first == id )
277     {
278       return true;
279     }
280   }
281
282   return false;
283 }
284
285 /**
286   * Helper to apply size constraint to mOvershootOverlay
287   * @param[in] overshootOverlay The overshootOverlay actor
288   * @param[in] The required height
289   */
290 void ApplyOvershootSizeConstraint( Actor overshootOverlay, float height )
291 {
292   Constraint constraint = Constraint::New<Vector3>( overshootOverlay, Actor::Property::SIZE, OvershootOverlaySizeConstraint( height ) );
293   constraint.AddSource( ParentSource( Dali::Toolkit::ItemView::Property::SCROLL_DIRECTION ) );
294   constraint.AddSource( ParentSource( Dali::Toolkit::ItemView::Property::LAYOUT_ORIENTATION ) );
295   constraint.AddSource( ParentSource( Dali::Actor::Property::SIZE ) );
296   constraint.SetTag( OVERSHOOT_SIZE_CONSTRAINT_TAG );
297   constraint.Apply();
298 }
299
300 } // unnamed namespace
301
302 Dali::Toolkit::ItemView ItemView::New(ItemFactory& factory)
303 {
304   // Create the implementation
305   ItemViewPtr itemView(new ItemView(factory));
306
307   // Pass ownership to CustomActor via derived handle
308   Dali::Toolkit::ItemView handle(*itemView);
309
310   // Second-phase init of the implementation
311   // This can only be done after the CustomActor connection has been made...
312   itemView->Initialize();
313
314   return handle;
315 }
316
317 ItemView::ItemView(ItemFactory& factory)
318 : Scrollable( ControlBehaviour( DISABLE_SIZE_NEGOTIATION | DISABLE_STYLE_CHANGE_SIGNALS | REQUIRES_WHEEL_EVENTS | REQUIRES_KEYBOARD_NAVIGATION_SUPPORT ) ),
319   mItemFactory(factory),
320   mItemsParentOrigin(ParentOrigin::CENTER),
321   mItemsAnchorPoint(AnchorPoint::CENTER),
322   mTotalPanDisplacement(Vector2::ZERO),
323   mActiveLayout(NULL),
324   mAnchoringDuration(DEFAULT_ANCHORING_DURATION),
325   mRefreshIntervalLayoutPositions(0.0f),
326   mMinimumSwipeSpeed(DEFAULT_MINIMUM_SWIPE_SPEED),
327   mMinimumSwipeDistance(DEFAULT_MINIMUM_SWIPE_DISTANCE),
328   mWheelScrollDistanceStep(0.0f),
329   mScrollDistance(0.0f),
330   mScrollSpeed(0.0f),
331   mScrollOvershoot(0.0f),
332   mGestureState(Gesture::Clear),
333   mAnimatingOvershootOn(false),
334   mAnimateOvershootOff(false),
335   mAnchoringEnabled(false),
336   mRefreshOrderHint(true/*Refresh item 0 first*/),
337   mIsFlicking(false),
338   mAddingItems(false),
339   mRefreshEnabled(true),
340   mRefreshNotificationEnabled(true),
341   mInAnimation(false)
342 {
343 }
344
345 void ItemView::OnInitialize()
346 {
347   Actor self = Self();
348
349   Vector2 stageSize = Stage::GetCurrent().GetSize();
350   mWheelScrollDistanceStep = stageSize.y * DEFAULT_WHEEL_SCROLL_DISTANCE_STEP_PROPORTION;
351
352   self.TouchSignal().Connect( this, &ItemView::OnTouch );
353   EnableGestureDetection(Gesture::Type(Gesture::Pan));
354
355   mWheelEventFinishedTimer = Timer::New( WHEEL_EVENT_FINISHED_TIME_OUT );
356   mWheelEventFinishedTimer.TickSignal().Connect( this, &ItemView::OnWheelEventFinished );
357
358   SetRefreshInterval(DEFAULT_REFRESH_INTERVAL_LAYOUT_POSITIONS);
359 }
360
361 ItemView::~ItemView()
362 {
363 }
364
365 unsigned int ItemView::GetLayoutCount() const
366 {
367   return mLayouts.size();
368 }
369
370 void ItemView::AddLayout(ItemLayout& layout)
371 {
372   mLayouts.push_back(ItemLayoutPtr(&layout));
373 }
374
375 void ItemView::RemoveLayout(unsigned int layoutIndex)
376 {
377   DALI_ASSERT_ALWAYS(layoutIndex < mLayouts.size());
378
379   if (mActiveLayout == mLayouts[layoutIndex].Get())
380   {
381     mActiveLayout = NULL;
382   }
383
384   mLayouts.erase(mLayouts.begin() + layoutIndex);
385 }
386
387 ItemLayoutPtr ItemView::GetLayout(unsigned int layoutIndex) const
388 {
389   return mLayouts[layoutIndex];
390 }
391
392 ItemLayoutPtr ItemView::GetActiveLayout() const
393 {
394   return ItemLayoutPtr(mActiveLayout);
395 }
396
397 float ItemView::GetCurrentLayoutPosition(unsigned int itemId) const
398 {
399   return Self().GetProperty<float>( Toolkit::ItemView::Property::LAYOUT_POSITION ) + static_cast<float>( itemId );
400 }
401
402 void ItemView::ActivateLayout(unsigned int layoutIndex, const Vector3& targetSize, float durationSeconds)
403 {
404   DALI_ASSERT_ALWAYS(layoutIndex < mLayouts.size());
405
406   mRefreshEnabled = false;
407
408   Actor self = Self();
409
410   // The ItemView size should match the active layout size
411   self.SetSize(targetSize);
412   mActiveLayoutTargetSize = targetSize;
413
414   // Switch to the new layout
415   mActiveLayout = mLayouts[layoutIndex].Get();
416
417   // Move the items to the new layout positions...
418
419   for (ConstItemPoolIter iter = mItemPool.begin(); iter != mItemPool.end(); ++iter)
420   {
421     unsigned int itemId = iter->first;
422     Actor actor = iter->second;
423
424     // Remove constraints from previous layout
425     actor.RemoveConstraints();
426
427     Vector3 size;
428     mActiveLayout->GetItemSize( itemId, targetSize, size );
429     actor.SetSize( size.GetVectorXY() );
430
431     mActiveLayout->ApplyConstraints(actor, itemId, targetSize, Self() );
432   }
433
434   // Refresh the new layout
435   ItemRange range = GetItemRange(*mActiveLayout, targetSize, GetCurrentLayoutPosition(0), false/* don't reserve extra*/);
436   AddActorsWithinRange( range, targetSize );
437
438   // Scroll to an appropriate layout position
439
440   bool scrollAnimationNeeded(false);
441   float firstItemScrollPosition(0.0f);
442
443   float current = GetCurrentLayoutPosition(0);
444   float minimum = ClampFirstItemPosition(current, targetSize, *mActiveLayout);
445
446   if (current < minimum)
447   {
448     scrollAnimationNeeded = true;
449     firstItemScrollPosition = minimum;
450   }
451   else if (mAnchoringEnabled)
452   {
453     scrollAnimationNeeded = true;
454     firstItemScrollPosition = mActiveLayout->GetClosestAnchorPosition(current);
455   }
456
457   if (scrollAnimationNeeded)
458   {
459     RemoveAnimation(mScrollAnimation);
460     mScrollAnimation = Animation::New(durationSeconds);
461     mScrollAnimation.AnimateTo( Property(self, Toolkit::ItemView::Property::LAYOUT_POSITION), firstItemScrollPosition, AlphaFunction::EASE_OUT );
462     mScrollAnimation.FinishedSignal().Connect(this, &ItemView::OnLayoutActivationScrollFinished);
463     mScrollAnimation.Play();
464   }
465   else
466   {
467     // Emit the layout activated signal
468     mLayoutActivatedSignal.Emit();
469   }
470
471   AnimateScrollOvershoot(0.0f);
472   mScrollOvershoot = 0.0f;
473
474   Radian scrollDirection(mActiveLayout->GetScrollDirection());
475   self.SetProperty(Toolkit::ItemView::Property::SCROLL_DIRECTION, Vector2(sinf(scrollDirection), cosf(scrollDirection)));
476   self.SetProperty(Toolkit::ItemView::Property::LAYOUT_ORIENTATION, static_cast<int>(mActiveLayout->GetOrientation()));
477   self.SetProperty(Toolkit::ItemView::Property::SCROLL_SPEED, mScrollSpeed);
478
479   CalculateDomainSize(targetSize);
480 }
481
482 void ItemView::DeactivateCurrentLayout()
483 {
484   if (mActiveLayout)
485   {
486     for (ConstItemPoolIter iter = mItemPool.begin(); iter != mItemPool.end(); ++iter)
487     {
488       Actor actor = iter->second;
489       actor.RemoveConstraints();
490     }
491
492     mActiveLayout = NULL;
493   }
494 }
495
496 void ItemView::OnRefreshNotification(PropertyNotification& source)
497 {
498   if( mRefreshNotificationEnabled )
499   {
500     // Cancel scroll animation to prevent any fighting of setting the scroll position property by scroll bar during fast scroll.
501     if(!mRefreshEnabled && mScrollAnimation)
502     {
503       RemoveAnimation(mScrollAnimation);
504     }
505
506     // Only cache extra items when it is not a fast scroll
507     DoRefresh(GetCurrentLayoutPosition(0), mRefreshEnabled || mScrollAnimation);
508   }
509 }
510
511 void ItemView::Refresh()
512 {
513   for (ItemPoolIter iter = mItemPool.begin(); iter != mItemPool.end(); ++iter )
514   {
515     ReleaseActor( iter->first, iter->second );
516   }
517   mItemPool.clear();
518
519   DoRefresh(GetCurrentLayoutPosition(0), true);
520 }
521
522 void ItemView::DoRefresh(float currentLayoutPosition, bool cacheExtra)
523 {
524   if (mActiveLayout)
525   {
526     ItemRange range = GetItemRange(*mActiveLayout, mActiveLayoutTargetSize, currentLayoutPosition, cacheExtra/*reserve extra*/);
527     RemoveActorsOutsideRange( range );
528     AddActorsWithinRange( range, Self().GetCurrentSize() );
529
530     mScrollUpdatedSignal.Emit( Vector2(0.0f, currentLayoutPosition) );
531   }
532 }
533
534 void ItemView::SetMinimumSwipeSpeed(float speed)
535 {
536   mMinimumSwipeSpeed = speed;
537 }
538
539 float ItemView::GetMinimumSwipeSpeed() const
540 {
541   return mMinimumSwipeSpeed;
542 }
543
544 void ItemView::SetMinimumSwipeDistance(float distance)
545 {
546   mMinimumSwipeDistance = distance;
547 }
548
549 float ItemView::GetMinimumSwipeDistance() const
550 {
551   return mMinimumSwipeDistance;
552 }
553
554 void ItemView::SetWheelScrollDistanceStep(float step)
555 {
556   mWheelScrollDistanceStep = step;
557 }
558
559 float ItemView::GetWheelScrollDistanceStep() const
560 {
561   return mWheelScrollDistanceStep;
562 }
563
564 void ItemView::SetAnchoring(bool enabled)
565 {
566   mAnchoringEnabled = enabled;
567 }
568
569 bool ItemView::GetAnchoring() const
570 {
571   return mAnchoringEnabled;
572 }
573
574 void ItemView::SetAnchoringDuration(float durationSeconds)
575 {
576   mAnchoringDuration = durationSeconds;
577 }
578
579 float ItemView::GetAnchoringDuration() const
580 {
581   return mAnchoringDuration;
582 }
583
584 void ItemView::SetRefreshInterval(float intervalLayoutPositions)
585 {
586   if( !Equals(mRefreshIntervalLayoutPositions, intervalLayoutPositions) )
587   {
588     mRefreshIntervalLayoutPositions = intervalLayoutPositions;
589
590     Actor self = Self();
591     if(mRefreshNotification)
592     {
593       self.RemovePropertyNotification(mRefreshNotification);
594     }
595     mRefreshNotification = self.AddPropertyNotification( Toolkit::ItemView::Property::LAYOUT_POSITION, StepCondition(mRefreshIntervalLayoutPositions, 0.0f) );
596     mRefreshNotification.NotifySignal().Connect( this, &ItemView::OnRefreshNotification );
597   }
598 }
599
600 float ItemView::GetRefreshInterval() const
601 {
602   return mRefreshIntervalLayoutPositions;
603 }
604
605 void ItemView::SetRefreshEnabled(bool enabled)
606 {
607   mRefreshEnabled = enabled;
608 }
609
610 Actor ItemView::GetItem(unsigned int itemId) const
611 {
612   Actor actor;
613
614   ConstItemPoolIter iter = mItemPool.find( itemId );
615   if( iter != mItemPool.end() )
616   {
617     actor = iter->second;
618   }
619
620   return actor;
621 }
622
623 unsigned int ItemView::GetItemId( Actor actor ) const
624 {
625   unsigned int itemId( 0 );
626
627   for ( ConstItemPoolIter iter = mItemPool.begin(); iter != mItemPool.end(); ++iter )
628   {
629     if( iter->second == actor )
630     {
631       itemId = iter->first;
632       break;
633     }
634   }
635
636   return itemId;
637 }
638
639 void ItemView::InsertItem( Item newItem, float durationSeconds )
640 {
641   mAddingItems = true;
642   Vector3 layoutSize = Self().GetCurrentSize();
643
644   Actor displacedActor;
645   ItemPoolIter afterDisplacedIter = mItemPool.end();
646
647   ItemPoolIter foundIter = mItemPool.find( newItem.first );
648   if( mItemPool.end() != foundIter )
649   {
650     SetupActor( newItem, layoutSize );
651     Self().Add( newItem.second );
652
653     displacedActor = foundIter->second;
654     foundIter->second = newItem.second;
655
656     afterDisplacedIter = ++foundIter;
657   }
658   else
659   {
660     // Inserting before the existing item range?
661     ItemPoolIter iter = mItemPool.begin();
662     if( iter != mItemPool.end() &&
663         iter->first > newItem.first )
664     {
665       displacedActor = iter->second;
666       mItemPool.erase( iter++ ); // iter is still valid after the erase
667
668       afterDisplacedIter = iter;
669     }
670   }
671
672   if( displacedActor )
673   {
674     // Move the existing actors to make room
675     for( ItemPoolIter iter = afterDisplacedIter; mItemPool.end() != iter; ++iter )
676     {
677       Actor temp = iter->second;
678       iter->second = displacedActor;
679       displacedActor = temp;
680
681       iter->second.RemoveConstraints();
682       mActiveLayout->ApplyConstraints( iter->second, iter->first, layoutSize, Self() );
683     }
684
685     // Create last item
686     ItemPool::reverse_iterator lastIter = mItemPool.rbegin();
687     if ( lastIter != mItemPool.rend() )
688     {
689       ItemId lastId = lastIter->first;
690       Item lastItem( lastId + 1, displacedActor );
691       mItemPool.insert( lastItem );
692
693       lastItem.second.RemoveConstraints();
694       mActiveLayout->ApplyConstraints( lastItem.second, lastItem.first, layoutSize, Self() );
695     }
696   }
697
698   CalculateDomainSize( layoutSize );
699
700   mAddingItems = false;
701 }
702
703 void ItemView::InsertItems( const ItemContainer& newItems, float durationSeconds )
704 {
705   mAddingItems = true;
706   Vector3 layoutSize = Self().GetCurrentSize();
707
708   // Insert from lowest id to highest
709   std::set<Item> sortedItems;
710   for( ConstItemIter iter = newItems.begin(); newItems.end() != iter; ++iter )
711   {
712     sortedItems.insert( *iter );
713   }
714
715   for( std::set<Item>::iterator iter = sortedItems.begin(); sortedItems.end() != iter; ++iter )
716   {
717     Self().Add( iter->second );
718
719     ItemPoolIter foundIter = mItemPool.find( iter->first );
720     if( mItemPool.end() != foundIter )
721     {
722       Actor moveMe = foundIter->second;
723       foundIter->second = iter->second;
724
725       // Move the existing actors to make room
726       for( ItemPoolIter iter = ++foundIter; mItemPool.end() != iter; ++iter )
727       {
728         Actor temp = iter->second;
729         iter->second = moveMe;
730         moveMe = temp;
731       }
732
733       // Create last item
734       ItemId lastId = mItemPool.rbegin()->first;
735       Item lastItem( lastId + 1, moveMe );
736       mItemPool.insert( lastItem );
737     }
738     else
739     {
740       mItemPool.insert( *iter );
741     }
742   }
743
744   // Relayout everything
745   for (ItemPoolIter iter = mItemPool.begin(); iter != mItemPool.end(); ++iter)
746   {
747     // If newly inserted
748     if( FindById( newItems, iter->first ) )
749     {
750       SetupActor( *iter, layoutSize );
751     }
752     else
753     {
754       iter->second.RemoveConstraints();
755       mActiveLayout->ApplyConstraints( iter->second, iter->first, layoutSize, Self() );
756     }
757   }
758
759   CalculateDomainSize( layoutSize );
760
761   mAddingItems = false;
762 }
763
764 void ItemView::RemoveItem( unsigned int itemId, float durationSeconds )
765 {
766   bool actorsReordered = RemoveActor( itemId );
767   if( actorsReordered )
768   {
769     ReapplyAllConstraints();
770
771     OnItemsRemoved();
772   }
773 }
774
775 void ItemView::RemoveItems( const ItemIdContainer& itemIds, float durationSeconds )
776 {
777   bool actorsReordered( false );
778
779   // Remove from highest id to lowest
780   set<ItemId> sortedItems;
781   for( ConstItemIdIter iter = itemIds.begin(); itemIds.end() != iter; ++iter )
782   {
783     sortedItems.insert( *iter );
784   }
785
786   for( set<ItemId>::reverse_iterator iter = sortedItems.rbegin(); sortedItems.rend() != iter; ++iter )
787   {
788     if( RemoveActor( *iter ) )
789     {
790       actorsReordered = true;
791     }
792   }
793
794   if( actorsReordered )
795   {
796     ReapplyAllConstraints();
797
798     OnItemsRemoved();
799   }
800 }
801
802 bool ItemView::RemoveActor(unsigned int itemId)
803 {
804   bool reordered( false );
805
806   ItemPoolIter removeIter = mItemPool.find( itemId );
807   if( removeIter != mItemPool.end() )
808   {
809     ReleaseActor(itemId, removeIter->second);
810   }
811   else
812   {
813     // Removing before the existing item range?
814     ItemPoolIter iter = mItemPool.begin();
815     if( iter != mItemPool.end() &&
816         iter->first > itemId )
817     {
818       // In order to decrement the first visible item ID
819       mItemPool.insert( Item(iter->first - 1, Actor()) );
820
821       removeIter = mItemPool.begin();
822     }
823   }
824
825   if( removeIter != mItemPool.end() )
826   {
827     reordered = true;
828
829     // Adjust the remaining item IDs, for example if item 2 is removed:
830     //   Initial actors:     After insert:
831     //     ID 1 - ActorA       ID 1 - ActorA
832     //     ID 2 - ActorB       ID 2 - ActorC (previously ID 3)
833     //     ID 3 - ActorC       ID 3 - ActorB (previously ID 4)
834     //     ID 4 - ActorD
835     for (ItemPoolIter iter = removeIter; iter != mItemPool.end(); ++iter)
836     {
837       if( iter->first < mItemPool.rbegin()->first )
838       {
839         iter->second = mItemPool[ iter->first + 1 ];
840       }
841       else
842       {
843         mItemPool.erase( iter );
844         break;
845       }
846     }
847   }
848
849   return reordered;
850 }
851
852 void ItemView::ReplaceItem( Item replacementItem, float durationSeconds )
853 {
854   mAddingItems = true;
855   Vector3 layoutSize = Self().GetCurrentSize();
856
857   SetupActor( replacementItem, layoutSize );
858   Self().Add( replacementItem.second );
859
860   const ItemPoolIter iter = mItemPool.find( replacementItem.first );
861   if( mItemPool.end() != iter )
862   {
863     ReleaseActor(iter->first, iter->second);
864     iter->second = replacementItem.second;
865   }
866   else
867   {
868     mItemPool.insert( replacementItem );
869   }
870
871   CalculateDomainSize( layoutSize );
872
873   mAddingItems = false;
874 }
875
876 void ItemView::ReplaceItems( const ItemContainer& replacementItems, float durationSeconds )
877 {
878   for( ConstItemIter iter = replacementItems.begin(); replacementItems.end() != iter; ++iter )
879   {
880     ReplaceItem( *iter, durationSeconds );
881   }
882 }
883
884 void ItemView::RemoveActorsOutsideRange( ItemRange range )
885 {
886   // Remove unwanted actors from the ItemView & ItemPool
887   for (ItemPoolIter iter = mItemPool.begin(); iter != mItemPool.end(); )
888   {
889     unsigned int current = iter->first;
890
891     if( ! range.Within( current ) )
892     {
893       ReleaseActor(iter->first, iter->second);
894
895       mItemPool.erase( iter++ ); // erase invalidates the return value of post-increment; iter remains valid
896     }
897     else
898     {
899       ++iter;
900     }
901   }
902 }
903
904 void ItemView::AddActorsWithinRange( ItemRange range, const Vector3& layoutSize )
905 {
906   range.end = std::min(mItemFactory.GetNumberOfItems(), range.end);
907
908   // The order of addition depends on the scroll direction.
909   if (mRefreshOrderHint)
910   {
911     for (unsigned int itemId = range.begin; itemId < range.end; ++itemId)
912     {
913       AddNewActor( itemId, layoutSize );
914     }
915   }
916   else
917   {
918     for (unsigned int itemId = range.end; itemId > range.begin; --itemId)
919     {
920       AddNewActor( itemId-1, layoutSize );
921     }
922   }
923
924   // Total number of items may change dynamically.
925   // Always recalculate the domain size to reflect that.
926   CalculateDomainSize(Self().GetCurrentSize());
927 }
928
929 void ItemView::AddNewActor( unsigned int itemId, const Vector3& layoutSize )
930 {
931   mAddingItems = true;
932
933   if( mItemPool.end() == mItemPool.find( itemId ) )
934   {
935     Actor actor = mItemFactory.NewItem( itemId );
936
937     if( actor )
938     {
939       Item newItem( itemId, actor );
940
941       mItemPool.insert( newItem );
942
943       SetupActor( newItem, layoutSize );
944       Self().Add( actor );
945     }
946   }
947
948   mAddingItems = false;
949 }
950
951 void ItemView::SetupActor( Item item, const Vector3& layoutSize )
952 {
953   item.second.SetParentOrigin( mItemsParentOrigin );
954   item.second.SetAnchorPoint( mItemsAnchorPoint );
955
956   if( mActiveLayout )
957   {
958     Vector3 size;
959     mActiveLayout->GetItemSize( item.first, mActiveLayoutTargetSize, size );
960     item.second.SetSize( size.GetVectorXY() );
961
962     mActiveLayout->ApplyConstraints( item.second, item.first, layoutSize, Self() );
963   }
964 }
965
966 void ItemView::ReleaseActor( ItemId item, Actor actor )
967 {
968   Self().Remove( actor );
969   mItemFactory.ItemReleased(item, actor);
970 }
971
972 ItemRange ItemView::GetItemRange(ItemLayout& layout, const Vector3& layoutSize, float layoutPosition, bool reserveExtra)
973 {
974   unsigned int itemCount = mItemFactory.GetNumberOfItems();
975
976   ItemRange available(0u, itemCount);
977
978   ItemRange range = layout.GetItemsWithinArea( layoutPosition, layoutSize );
979
980   if (reserveExtra)
981   {
982     // Add the reserve items for scrolling
983     unsigned int extra = layout.GetReserveItemCount(layoutSize);
984     range.begin = (range.begin >= extra) ? (range.begin - extra) : 0u;
985     range.end += extra;
986   }
987
988   return range.Intersection(available);
989 }
990
991 void ItemView::OnChildAdd(Actor& child)
992 {
993   if(!mAddingItems)
994   {
995     // We don't want to do this downcast check for any item added by ItemView itself.
996     Dali::Toolkit::ScrollBar scrollBar = Dali::Toolkit::ScrollBar::DownCast(child);
997     if(scrollBar)
998     {
999       scrollBar.SetScrollPropertySource(Self(),
1000                                         Toolkit::ItemView::Property::LAYOUT_POSITION,
1001                                         Toolkit::Scrollable::Property::SCROLL_POSITION_MIN_Y,
1002                                         Toolkit::Scrollable::Property::SCROLL_POSITION_MAX_Y,
1003                                         Toolkit::ItemView::Property::SCROLL_CONTENT_SIZE);
1004     }
1005   }
1006 }
1007
1008 bool ItemView::OnWheelEvent(const WheelEvent& event)
1009 {
1010   // Respond the wheel event to scroll
1011   if (mActiveLayout)
1012   {
1013     Actor self = Self();
1014     const Vector3 layoutSize = Self().GetCurrentSize();
1015     float layoutPositionDelta = GetCurrentLayoutPosition(0) - (event.z * mWheelScrollDistanceStep * mActiveLayout->GetScrollSpeedFactor());
1016     float firstItemScrollPosition = ClampFirstItemPosition(layoutPositionDelta, layoutSize, *mActiveLayout);
1017
1018     self.SetProperty(Toolkit::ItemView::Property::LAYOUT_POSITION, firstItemScrollPosition );
1019
1020     mScrollStartedSignal.Emit(GetCurrentScrollPosition());
1021     mRefreshEnabled = true;
1022   }
1023
1024   if (mWheelEventFinishedTimer.IsRunning())
1025   {
1026     mWheelEventFinishedTimer.Stop();
1027   }
1028
1029   mWheelEventFinishedTimer.Start();
1030
1031   return true;
1032 }
1033
1034 bool ItemView::OnWheelEventFinished()
1035 {
1036   if (mActiveLayout)
1037   {
1038     RemoveAnimation(mScrollAnimation);
1039
1040     // No more wheel events coming. Do the anchoring if enabled.
1041     mScrollAnimation = DoAnchoring();
1042     if (mScrollAnimation)
1043     {
1044       mScrollAnimation.FinishedSignal().Connect(this, &ItemView::OnScrollFinished);
1045       mScrollAnimation.Play();
1046     }
1047     else
1048     {
1049       mScrollOvershoot = 0.0f;
1050       AnimateScrollOvershoot(0.0f);
1051
1052       mScrollCompletedSignal.Emit(GetCurrentScrollPosition());
1053     }
1054   }
1055
1056   return false;
1057 }
1058
1059 void ItemView::ReapplyAllConstraints()
1060 {
1061   Vector3 layoutSize = Self().GetCurrentSize();
1062
1063   for (ConstItemPoolIter iter = mItemPool.begin(); iter != mItemPool.end(); ++iter)
1064   {
1065     unsigned int id = iter->first;
1066     Actor actor = iter->second;
1067
1068     actor.RemoveConstraints();
1069     mActiveLayout->ApplyConstraints(actor, id, layoutSize, Self());
1070   }
1071 }
1072
1073 void ItemView::OnItemsRemoved()
1074 {
1075   CalculateDomainSize(Self().GetCurrentSize());
1076
1077   // Adjust scroll-position after an item is removed
1078   if( mActiveLayout )
1079   {
1080     float firstItemScrollPosition = ClampFirstItemPosition(GetCurrentLayoutPosition(0), Self().GetCurrentSize(), *mActiveLayout);
1081     Self().SetProperty( Toolkit::ItemView::Property::LAYOUT_POSITION, firstItemScrollPosition );
1082   }
1083 }
1084
1085 float ItemView::ClampFirstItemPosition( float targetPosition, const Vector3& targetSize, ItemLayout& layout, bool updateOvershoot )
1086 {
1087   Actor self = Self();
1088   float minLayoutPosition = layout.GetMinimumLayoutPosition(mItemFactory.GetNumberOfItems(), targetSize);
1089   float clamppedPosition = std::min(0.0f, std::max(minLayoutPosition, targetPosition));
1090   self.SetProperty(Toolkit::Scrollable::Property::SCROLL_POSITION_MAX, Vector2(0.0f, -minLayoutPosition));
1091
1092   if( updateOvershoot )
1093   {
1094     mScrollOvershoot = targetPosition - clamppedPosition;
1095   }
1096
1097   return clamppedPosition;
1098 }
1099
1100 bool ItemView::OnTouch( Actor actor, const TouchData& touch )
1101 {
1102   // Ignore events with multiple-touch points
1103   if (touch.GetPointCount() != 1)
1104   {
1105     return false;
1106   }
1107
1108   if ( touch.GetState( 0 ) == PointState::DOWN )
1109   {
1110     // Cancel ongoing scrolling etc.
1111     mGestureState = Gesture::Clear;
1112
1113     mScrollDistance = 0.0f;
1114     mScrollSpeed = 0.0f;
1115     Self().SetProperty(Toolkit::ItemView::Property::SCROLL_SPEED, mScrollSpeed);
1116
1117     mScrollOvershoot = 0.0f;
1118     AnimateScrollOvershoot(0.0f);
1119
1120     if(mScrollAnimation)
1121     {
1122       mScrollCompletedSignal.Emit(GetCurrentScrollPosition());
1123     }
1124
1125     RemoveAnimation(mScrollAnimation);
1126   }
1127
1128   return true; // consume since we're potentially scrolling
1129 }
1130
1131 void ItemView::OnPan( const PanGesture& gesture )
1132 {
1133   Actor self = Self();
1134   const Vector3 layoutSize = Self().GetCurrentSize();
1135
1136   RemoveAnimation(mScrollAnimation);
1137
1138   // Short-circuit if there is no active layout
1139   if (!mActiveLayout)
1140   {
1141     mGestureState = Gesture::Clear;
1142     return;
1143   }
1144
1145   mGestureState = gesture.state;
1146
1147   switch (mGestureState)
1148   {
1149     case Gesture::Finished:
1150     {
1151       // Swipe Detection
1152       if (fabsf(mScrollDistance) > mMinimumSwipeDistance &&
1153           mScrollSpeed > mMinimumSwipeSpeed)
1154       {
1155         float direction = (mScrollDistance < 0.0f) ? -1.0f : 1.0f;
1156
1157         mRefreshOrderHint = true;
1158
1159         float currentLayoutPosition = GetCurrentLayoutPosition(0);
1160         float firstItemScrollPosition = ClampFirstItemPosition(currentLayoutPosition + mScrollSpeed * direction,
1161                                                                layoutSize,
1162                                                                *mActiveLayout);
1163
1164         if (mAnchoringEnabled)
1165         {
1166           firstItemScrollPosition = mActiveLayout->GetClosestAnchorPosition(firstItemScrollPosition);
1167         }
1168
1169         RemoveAnimation(mScrollAnimation);
1170
1171         float flickAnimationDuration = Clamp( mActiveLayout->GetItemFlickAnimationDuration() * std::max(1.0f, fabsf(firstItemScrollPosition - GetCurrentLayoutPosition(0)))
1172                                        , DEFAULT_MINIMUM_SWIPE_DURATION, DEFAULT_MAXIMUM_SWIPE_DURATION);
1173
1174         mScrollAnimation = Animation::New(flickAnimationDuration);
1175         mScrollAnimation.AnimateTo( Property(self, Toolkit::ItemView::Property::LAYOUT_POSITION ), firstItemScrollPosition, AlphaFunction::EASE_OUT );
1176         mScrollAnimation.AnimateTo( Property(self, Toolkit::ItemView::Property::SCROLL_SPEED), 0.0f, AlphaFunction::EASE_OUT );
1177
1178         mIsFlicking = true;
1179         // Check whether it has already scrolled to the end
1180         if(fabs(currentLayoutPosition - firstItemScrollPosition) > Math::MACHINE_EPSILON_0)
1181         {
1182           AnimateScrollOvershoot(0.0f);
1183         }
1184       }
1185
1186       // Anchoring may be triggered when there was no swipe
1187       if (!mScrollAnimation)
1188       {
1189         mScrollAnimation = DoAnchoring();
1190       }
1191
1192       // Reset the overshoot if no scroll animation.
1193       if (!mScrollAnimation)
1194       {
1195         mScrollCompletedSignal.Emit(GetCurrentScrollPosition());
1196
1197         AnimateScrollOvershoot(0.0f, false);
1198       }
1199     }
1200     break;
1201
1202     case Gesture::Started: // Fall through
1203     {
1204       mTotalPanDisplacement = Vector2::ZERO;
1205       mScrollStartedSignal.Emit(GetCurrentScrollPosition());
1206       mRefreshEnabled = true;
1207     }
1208
1209     case Gesture::Continuing:
1210     {
1211       mScrollDistance = CalculateScrollDistance(gesture.displacement, *mActiveLayout);
1212       mScrollSpeed = Clamp((gesture.GetSpeed() * gesture.GetSpeed() * mActiveLayout->GetFlickSpeedFactor() * MILLISECONDS_PER_SECONDS), 0.0f, mActiveLayout->GetMaximumSwipeSpeed());
1213
1214       // Refresh order depends on the direction of the scroll; negative is towards the last item.
1215       mRefreshOrderHint = mScrollDistance < 0.0f;
1216
1217       float layoutPositionDelta = GetCurrentLayoutPosition(0) + (mScrollDistance * mActiveLayout->GetScrollSpeedFactor());
1218
1219       float firstItemScrollPosition = ClampFirstItemPosition(layoutPositionDelta, layoutSize, *mActiveLayout);
1220
1221       float currentOvershoot = self.GetProperty<float>(Toolkit::ItemView::Property::OVERSHOOT);
1222
1223       self.SetProperty(Toolkit::ItemView::Property::LAYOUT_POSITION, firstItemScrollPosition );
1224
1225       if( ( firstItemScrollPosition >= 0.0f &&
1226             currentOvershoot < 1.0f ) ||
1227           ( firstItemScrollPosition <= mActiveLayout->GetMinimumLayoutPosition(mItemFactory.GetNumberOfItems(), layoutSize) &&
1228             currentOvershoot > -1.0f ) )
1229       {
1230         mTotalPanDisplacement += gesture.displacement;
1231       }
1232
1233       mScrollOvershoot = CalculateScrollOvershoot();
1234
1235       // If the view is moved in a direction against the overshoot indicator, then the indicator should be animated off.
1236       // First make sure we are not in an animation, otherwise a previously started
1237       // off-animation will be overwritten as the user continues scrolling.
1238       if( !mInAnimation )
1239       {
1240         // Check if the movement is against the current overshoot amount (if we are currently displaying the indicator).
1241         if( ( ( mScrollOvershoot > Math::MACHINE_EPSILON_0 ) && ( mScrollDistance < -Math::MACHINE_EPSILON_0 ) ) ||
1242           ( ( mScrollOvershoot < Math::MACHINE_EPSILON_0 ) && ( mScrollDistance > Math::MACHINE_EPSILON_0 ) ) )
1243         {
1244           // The user has moved against the indicator direction.
1245           // First, we reset the total displacement. This means the overshoot amount will become zero the next frame,
1246           // and if the user starts dragging in the overshoot direction again, the indicator will appear once more.
1247           mTotalPanDisplacement = Vector2::ZERO;
1248           // Animate the overshoot indicator off.
1249           AnimateScrollOvershoot( 0.0f, false );
1250         }
1251         else
1252         {
1253           // Only set the property directly if we are not animating the overshoot away,
1254           // as otherwise this will overwrite the animation generated value.
1255           self.SetProperty( Toolkit::ItemView::Property::OVERSHOOT, mScrollOvershoot );
1256         }
1257       }
1258     }
1259     break;
1260
1261     case Gesture::Cancelled:
1262     {
1263       mScrollAnimation = DoAnchoring();
1264     }
1265     break;
1266
1267     default:
1268       break;
1269   }
1270
1271   if (mScrollAnimation)
1272   {
1273     mScrollAnimation.FinishedSignal().Connect(this, &ItemView::OnScrollFinished);
1274     mScrollAnimation.Play();
1275   }
1276 }
1277
1278 bool ItemView::OnAccessibilityPan(PanGesture gesture)
1279 {
1280   OnPan(gesture);
1281   return true;
1282 }
1283
1284 Actor ItemView::GetNextKeyboardFocusableActor(Actor actor, Toolkit::Control::KeyboardFocus::Direction direction, bool loopEnabled)
1285 {
1286   Actor nextFocusActor;
1287   if(mActiveLayout)
1288   {
1289     int nextItemID = 0;
1290     if(!actor || actor == this->Self())
1291     {
1292       nextFocusActor = GetItem(nextItemID);
1293     }
1294     else if(actor && actor.GetParent() == this->Self())
1295     {
1296       int itemID = GetItemId(actor);
1297       nextItemID = mActiveLayout->GetNextFocusItemID(itemID, mItemFactory.GetNumberOfItems(), direction, loopEnabled);
1298       nextFocusActor = GetItem(nextItemID);
1299       if(nextFocusActor == actor)
1300       {
1301         // need to pass NULL actor back to focus manager
1302         nextFocusActor.Reset();
1303         return nextFocusActor;
1304       }
1305     }
1306     float layoutPosition = mActiveLayout->GetClosestAnchorPosition( GetCurrentLayoutPosition(0) );
1307     Vector3 layoutSize = Self().GetCurrentSize();
1308     if(!nextFocusActor)
1309     {
1310       // likely the current item is not buffered, so not in our item pool, probably best to get first viewable item
1311       ItemRange viewableItems = mActiveLayout->GetItemsWithinArea(layoutPosition, layoutSize);
1312       nextItemID = viewableItems.begin;
1313       nextFocusActor = GetItem(nextItemID);
1314     }
1315   }
1316   return nextFocusActor;
1317 }
1318
1319 void ItemView::OnKeyboardFocusChangeCommitted(Actor commitedFocusableActor)
1320 {
1321   // only in this function if our chosen focus actor was actually used
1322   if(commitedFocusableActor)
1323   {
1324     int nextItemID = GetItemId(commitedFocusableActor);
1325     float layoutPosition = GetCurrentLayoutPosition(0);
1326     Vector3 layoutSize = Self().GetCurrentSize();
1327
1328     float scrollTo = mActiveLayout->GetClosestOnScreenLayoutPosition(nextItemID, layoutPosition, layoutSize);
1329     ScrollTo(Vector2(0.0f, scrollTo), DEFAULT_KEYBOARD_FOCUS_SCROLL_DURATION);
1330   }
1331 }
1332
1333 Animation ItemView::DoAnchoring()
1334 {
1335   Animation anchoringAnimation;
1336   Actor self = Self();
1337
1338   if (mActiveLayout && mAnchoringEnabled)
1339   {
1340     float anchorPosition = mActiveLayout->GetClosestAnchorPosition( GetCurrentLayoutPosition(0) );
1341
1342     anchoringAnimation = Animation::New(mAnchoringDuration);
1343     anchoringAnimation.AnimateTo( Property(self, Toolkit::ItemView::Property::LAYOUT_POSITION), anchorPosition, AlphaFunction::EASE_OUT );
1344     anchoringAnimation.AnimateTo( Property(self, Toolkit::ItemView::Property::SCROLL_SPEED), 0.0f, AlphaFunction::EASE_OUT );
1345     if(!mIsFlicking)
1346     {
1347       AnimateScrollOvershoot(0.0f);
1348     }
1349   }
1350
1351   return anchoringAnimation;
1352 }
1353
1354 void ItemView::OnScrollFinished(Animation& source)
1355 {
1356   Actor self = Self();
1357
1358   RemoveAnimation(mScrollAnimation); // mScrollAnimation is used to query whether we're scrolling
1359
1360   mScrollCompletedSignal.Emit(GetCurrentScrollPosition());
1361
1362   if(mIsFlicking && fabsf(mScrollOvershoot) > Math::MACHINE_EPSILON_1)
1363   {
1364     AnimateScrollOvershoot( mScrollOvershoot > 0.0f ? 1.0f : -1.0f, true);
1365   }
1366   else
1367   {
1368     // Reset the overshoot
1369     AnimateScrollOvershoot( 0.0f );
1370   }
1371   mIsFlicking = false;
1372
1373   mScrollOvershoot = 0.0f;
1374 }
1375
1376 void ItemView::OnLayoutActivationScrollFinished(Animation& source)
1377 {
1378   RemoveAnimation(mScrollAnimation);
1379   mRefreshEnabled = true;
1380   DoRefresh(GetCurrentLayoutPosition(0), true);
1381
1382   // Emit the layout activated signal
1383   mLayoutActivatedSignal.Emit();
1384 }
1385
1386 void ItemView::OnOvershootOnFinished(Animation& animation)
1387 {
1388   mAnimatingOvershootOn = false;
1389   mScrollOvershootAnimation.FinishedSignal().Disconnect(this, &ItemView::OnOvershootOnFinished);
1390   RemoveAnimation(mScrollOvershootAnimation);
1391   if(mAnimateOvershootOff)
1392   {
1393     AnimateScrollOvershoot(0.0f);
1394   }
1395   mInAnimation = false;
1396 }
1397
1398 void ItemView::ScrollToItem(unsigned int itemId, float durationSeconds)
1399 {
1400   Actor self = Self();
1401   const Vector3 layoutSize = Self().GetCurrentSize();
1402   float firstItemScrollPosition = ClampFirstItemPosition(mActiveLayout->GetItemScrollToPosition(itemId), layoutSize, *mActiveLayout);
1403
1404   if(durationSeconds > 0.0f)
1405   {
1406     RemoveAnimation(mScrollAnimation);
1407     mScrollAnimation = Animation::New(durationSeconds);
1408     mScrollAnimation.AnimateTo( Property(self, Toolkit::ItemView::Property::LAYOUT_POSITION), firstItemScrollPosition, mScrollToAlphaFunction );
1409     mScrollAnimation.FinishedSignal().Connect(this, &ItemView::OnScrollFinished);
1410     mScrollAnimation.Play();
1411   }
1412   else
1413   {
1414     self.SetProperty( Toolkit::ItemView::Property::LAYOUT_POSITION, firstItemScrollPosition );
1415     AnimateScrollOvershoot(0.0f);
1416   }
1417
1418   mScrollStartedSignal.Emit(GetCurrentScrollPosition());
1419   mRefreshEnabled = true;
1420 }
1421
1422 void ItemView::RemoveAnimation(Animation& animation)
1423 {
1424   if(animation)
1425   {
1426     // Cease animating, and reset handle.
1427     animation.Clear();
1428     animation.Reset();
1429   }
1430 }
1431
1432 void ItemView::CalculateDomainSize(const Vector3& layoutSize)
1433 {
1434   Actor self = Self();
1435
1436   Vector3 firstItemPosition(Vector3::ZERO);
1437   Vector3 lastItemPosition(Vector3::ZERO);
1438
1439   if(mActiveLayout)
1440   {
1441     firstItemPosition = mActiveLayout->GetItemPosition( 0,0,layoutSize );
1442
1443     float minLayoutPosition = mActiveLayout->GetMinimumLayoutPosition(mItemFactory.GetNumberOfItems(), layoutSize);
1444     lastItemPosition = mActiveLayout->GetItemPosition( fabs(minLayoutPosition),fabs(minLayoutPosition),layoutSize );
1445
1446     float domainSize;
1447
1448     if(IsHorizontal(mActiveLayout->GetOrientation()))
1449     {
1450       domainSize = fabs(firstItemPosition.x - lastItemPosition.x);
1451     }
1452     else
1453     {
1454       domainSize = fabs(firstItemPosition.y - lastItemPosition.y);
1455     }
1456
1457     self.SetProperty(Toolkit::Scrollable::Property::SCROLL_POSITION_MIN, Vector2::ZERO);
1458     self.SetProperty(Toolkit::Scrollable::Property::SCROLL_POSITION_MAX, Vector2(0.0f, -minLayoutPosition));
1459
1460     self.SetProperty(Toolkit::ItemView::Property::SCROLL_CONTENT_SIZE, domainSize);
1461
1462     bool isLayoutScrollable = IsLayoutScrollable(layoutSize);
1463     self.SetProperty(Toolkit::Scrollable::Property::CAN_SCROLL_VERTICAL, isLayoutScrollable);
1464     self.SetProperty(Toolkit::Scrollable::Property::CAN_SCROLL_HORIZONTAL, false);
1465   }
1466 }
1467
1468 bool ItemView::IsLayoutScrollable(const Vector3& layoutSize)
1469 {
1470   Actor self = Self();
1471
1472   float currentLayoutPosition = ClampFirstItemPosition( GetCurrentLayoutPosition(0), layoutSize, *mActiveLayout, false );
1473   float forwardClampedPosition = ClampFirstItemPosition( currentLayoutPosition + 1.0, layoutSize, *mActiveLayout, false );
1474   float backwardClampedPosition = ClampFirstItemPosition( currentLayoutPosition - 1.0, layoutSize, *mActiveLayout, false );
1475
1476   return (fabs(forwardClampedPosition - backwardClampedPosition) > Math::MACHINE_EPSILON_0);
1477 }
1478
1479 float ItemView::GetScrollPosition(float layoutPosition, const Vector3& layoutSize) const
1480 {
1481   Vector3 firstItemPosition( mActiveLayout->GetItemPosition(0, layoutPosition, layoutSize ) );
1482   return IsHorizontal(mActiveLayout->GetOrientation()) ? firstItemPosition.x: firstItemPosition.y;
1483 }
1484
1485 Vector2 ItemView::GetCurrentScrollPosition() const
1486 {
1487   return Vector2(0.0f, GetScrollPosition(GetCurrentLayoutPosition(0), Self().GetCurrentSize()));
1488 }
1489
1490 void ItemView::AddOverlay(Actor actor)
1491 {
1492   actor.SetDrawMode( DrawMode::OVERLAY_2D );
1493   Self().Add(actor);
1494 }
1495
1496 void ItemView::RemoveOverlay(Actor actor)
1497 {
1498   Self().Remove(actor);
1499 }
1500
1501 void ItemView::ScrollTo(const Vector2& position, float duration)
1502 {
1503   Actor self = Self();
1504   const Vector3 layoutSize = Self().GetCurrentSize();
1505
1506   float firstItemScrollPosition = ClampFirstItemPosition(position.y, layoutSize, *mActiveLayout);
1507
1508   if(duration > 0.0f)
1509   {
1510     RemoveAnimation(mScrollAnimation);
1511     mScrollAnimation = Animation::New(duration);
1512     mScrollAnimation.AnimateTo( Property(self, Toolkit::ItemView::Property::LAYOUT_POSITION), firstItemScrollPosition, mScrollToAlphaFunction );
1513     mScrollAnimation.FinishedSignal().Connect(this, &ItemView::OnScrollFinished);
1514     mScrollAnimation.Play();
1515   }
1516   else
1517   {
1518     self.SetProperty( Toolkit::ItemView::Property::LAYOUT_POSITION, firstItemScrollPosition );
1519     AnimateScrollOvershoot(0.0f);
1520   }
1521
1522   mScrollStartedSignal.Emit(GetCurrentScrollPosition());
1523   mRefreshEnabled = true;
1524 }
1525
1526 void ItemView::SetOvershootSize( const Vector2& size )
1527 {
1528   mOvershootSize = size;
1529
1530   if( mOvershootOverlay )
1531   {
1532     // Remove old & add new size constraint
1533     mOvershootOverlay.RemoveConstraints( OVERSHOOT_SIZE_CONSTRAINT_TAG );
1534     ApplyOvershootSizeConstraint( mOvershootOverlay, mOvershootSize.height );
1535   }
1536 }
1537
1538 void ItemView::SetOvershootEffectColor( const Vector4& color )
1539 {
1540   mOvershootEffectColor = color;
1541   if( mOvershootOverlay )
1542   {
1543     mOvershootOverlay.SetColor( color );
1544   }
1545 }
1546
1547 void ItemView::EnableScrollOvershoot( bool enable )
1548 {
1549   Actor self = Self();
1550   if( enable )
1551   {
1552     if( !mOvershootOverlay )
1553     {
1554       Property::Index effectOvershootPropertyIndex = Property::INVALID_INDEX;
1555       mOvershootOverlay = CreateBouncingEffectActor( effectOvershootPropertyIndex );
1556       mOvershootOverlay.SetColor(mOvershootEffectColor);
1557       mOvershootOverlay.SetParentOrigin(ParentOrigin::TOP_LEFT);
1558       mOvershootOverlay.SetAnchorPoint(AnchorPoint::TOP_LEFT);
1559       mOvershootOverlay.SetDrawMode( DrawMode::OVERLAY_2D );
1560       self.Add(mOvershootOverlay);
1561
1562       ApplyOvershootSizeConstraint( mOvershootOverlay, mOvershootSize.height );
1563
1564       Constraint constraint = Constraint::New<Quaternion>( mOvershootOverlay, Actor::Property::ORIENTATION, OvershootOverlayRotationConstraint );
1565       constraint.AddSource( ParentSource( Toolkit::ItemView::Property::SCROLL_DIRECTION ) );
1566       constraint.AddSource( ParentSource( Toolkit::ItemView::Property::LAYOUT_ORIENTATION ) );
1567       constraint.AddSource( ParentSource( Toolkit::ItemView::Property::OVERSHOOT ) );
1568       constraint.Apply();
1569
1570       constraint = Constraint::New<Vector3>( mOvershootOverlay, Actor::Property::POSITION, OvershootOverlayPositionConstraint );
1571       constraint.AddSource( ParentSource( Actor::Property::SIZE ) );
1572       constraint.AddSource( ParentSource( Toolkit::ItemView::Property::SCROLL_DIRECTION ) );
1573       constraint.AddSource( ParentSource( Toolkit::ItemView::Property::LAYOUT_ORIENTATION ) );
1574       constraint.AddSource( ParentSource( Toolkit::ItemView::Property::OVERSHOOT ) );
1575       constraint.Apply();
1576
1577       constraint = Constraint::New<bool>( mOvershootOverlay, Actor::Property::VISIBLE, OvershootOverlayVisibilityConstraint );
1578       constraint.AddSource( ParentSource( Toolkit::Scrollable::Property::CAN_SCROLL_VERTICAL ) );
1579       constraint.Apply();
1580
1581       constraint = Constraint::New<float>( mOvershootOverlay, effectOvershootPropertyIndex, EqualToConstraint() );
1582       constraint.AddSource( ParentSource( Toolkit::ItemView::Property::OVERSHOOT ) );
1583       constraint.Apply();
1584     }
1585   }
1586   else
1587   {
1588     if( mOvershootOverlay )
1589     {
1590       self.Remove(mOvershootOverlay);
1591       mOvershootOverlay.Reset();
1592     }
1593   }
1594 }
1595
1596 float ItemView::CalculateScrollOvershoot()
1597 {
1598   float overshoot = 0.0f;
1599
1600   if(mActiveLayout)
1601   {
1602     // The overshoot must be calculated from the accumulated pan gesture displacement
1603     // since the pan gesture starts.
1604     Actor self = Self();
1605     float scrollDistance = CalculateScrollDistance(mTotalPanDisplacement, *mActiveLayout) * mActiveLayout->GetScrollSpeedFactor();
1606     float positionDelta = GetCurrentLayoutPosition(0) + scrollDistance;
1607     float minLayoutPosition = mActiveLayout->GetMinimumLayoutPosition(mItemFactory.GetNumberOfItems(), Self().GetCurrentSize());
1608     self.SetProperty(Toolkit::Scrollable::Property::SCROLL_POSITION_MAX, Vector2(0.0f, -minLayoutPosition));
1609     float clamppedPosition = std::min(0.0f, std::max(minLayoutPosition, positionDelta));
1610     overshoot = positionDelta - clamppedPosition;
1611   }
1612
1613   return overshoot > 0.0f ? std::min(overshoot, 1.0f) : std::max(overshoot, -1.0f);
1614 }
1615
1616 void ItemView::AnimateScrollOvershoot(float overshootAmount, bool animateBack)
1617 {
1618   bool animatingOn = fabsf(overshootAmount) > Math::MACHINE_EPSILON_1;
1619
1620   // make sure we animate back if needed
1621   mAnimateOvershootOff = animateBack || (!animatingOn && mAnimatingOvershootOn);
1622
1623   if( mAnimatingOvershootOn )
1624   {
1625     // animating on, do not allow animate off
1626     return;
1627   }
1628
1629   Actor self = Self();
1630
1631   if(mOvershootAnimationSpeed > Math::MACHINE_EPSILON_0)
1632   {
1633     float currentOvershoot = self.GetProperty<float>(Toolkit::ItemView::Property::OVERSHOOT);
1634     float duration = 0.0f;
1635
1636     if (mOvershootOverlay)
1637     {
1638       duration = mOvershootOverlay.GetCurrentSize().height * (animatingOn ? (1.0f - fabsf(currentOvershoot)) : fabsf(currentOvershoot)) / mOvershootAnimationSpeed;
1639     }
1640
1641     // Mark the animation as in progress to prevent manual property sets overwriting it.
1642     mInAnimation = true;
1643     mAnimatingOvershootOn = animatingOn;
1644     RemoveAnimation(mScrollOvershootAnimation);
1645     mScrollOvershootAnimation = Animation::New(duration);
1646     mScrollOvershootAnimation.FinishedSignal().Connect(this, &ItemView::OnOvershootOnFinished);
1647     mScrollOvershootAnimation.AnimateTo( Property(self, Toolkit::ItemView::Property::OVERSHOOT), overshootAmount, TimePeriod(0.0f, duration) );
1648     mScrollOvershootAnimation.Play();
1649   }
1650   else
1651   {
1652     self.SetProperty( Toolkit::ItemView::Property::OVERSHOOT, overshootAmount );
1653   }
1654 }
1655
1656 void ItemView::SetItemsParentOrigin( const Vector3& parentOrigin )
1657 {
1658   if( parentOrigin != mItemsParentOrigin )
1659   {
1660     mItemsParentOrigin = parentOrigin;
1661     for (ItemPoolIter iter = mItemPool.begin(); iter != mItemPool.end(); ++iter)
1662     {
1663       iter->second.SetParentOrigin(parentOrigin);
1664     }
1665   }
1666 }
1667
1668 Vector3 ItemView::GetItemsParentOrigin() const
1669 {
1670   return mItemsParentOrigin;
1671 }
1672
1673 void ItemView::SetItemsAnchorPoint( const Vector3& anchorPoint )
1674 {
1675   if( anchorPoint != mItemsAnchorPoint )
1676   {
1677     mItemsAnchorPoint = anchorPoint;
1678     for (ItemPoolIter iter = mItemPool.begin(); iter != mItemPool.end(); ++iter)
1679     {
1680       iter->second.SetAnchorPoint(anchorPoint);
1681     }
1682   }
1683 }
1684
1685 Vector3 ItemView::GetItemsAnchorPoint() const
1686 {
1687   return mItemsAnchorPoint;
1688 }
1689
1690 void ItemView::GetItemsRange(ItemRange& range)
1691 {
1692   if( !mItemPool.empty() )
1693   {
1694     range.begin = mItemPool.begin()->first;
1695     range.end = mItemPool.rbegin()->first + 1;
1696   }
1697   else
1698   {
1699     range.begin = 0;
1700     range.end = 0;
1701   }
1702 }
1703
1704 bool ItemView::DoConnectSignal( BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor )
1705 {
1706   Dali::BaseHandle handle( object );
1707
1708   bool connected( true );
1709   Toolkit::ItemView itemView = Toolkit::ItemView::DownCast( handle );
1710
1711   if( 0 == strcmp( signalName.c_str(), LAYOUT_ACTIVATED_SIGNAL ) )
1712   {
1713     itemView.LayoutActivatedSignal().Connect( tracker, functor );
1714   }
1715   else
1716   {
1717     // signalName does not match any signal
1718     connected = false;
1719   }
1720
1721   return connected;
1722 }
1723
1724 void ItemView::SetProperty( BaseObject* object, Property::Index index, const Property::Value& value )
1725 {
1726   Toolkit::ItemView itemView = Toolkit::ItemView::DownCast( Dali::BaseHandle( object ) );
1727
1728   if( itemView )
1729   {
1730     ItemView& itemViewImpl( GetImpl( itemView ) );
1731     switch( index )
1732     {
1733       case Toolkit::ItemView::Property::MINIMUM_SWIPE_SPEED:
1734       {
1735         itemViewImpl.SetMinimumSwipeSpeed( value.Get<float>() );
1736         break;
1737       }
1738       case Toolkit::ItemView::Property::MINIMUM_SWIPE_DISTANCE:
1739       {
1740         itemViewImpl.SetMinimumSwipeDistance( value.Get<float>() );
1741         break;
1742       }
1743       case Toolkit::ItemView::Property::WHEEL_SCROLL_DISTANCE_STEP:
1744       {
1745         itemViewImpl.SetWheelScrollDistanceStep( value.Get<float>() );
1746         break;
1747       }
1748       case Toolkit::ItemView::Property::SNAP_TO_ITEM_ENABLED:
1749       {
1750         itemViewImpl.SetAnchoring( value.Get<bool>() );
1751         break;
1752       }
1753       case Toolkit::ItemView::Property::REFRESH_INTERVAL:
1754       {
1755         itemViewImpl.SetRefreshInterval( value.Get<float>() );
1756         break;
1757       }
1758     }
1759   }
1760 }
1761
1762 Property::Value ItemView::GetProperty( BaseObject* object, Property::Index index )
1763 {
1764   Property::Value value;
1765
1766   Toolkit::ItemView itemView = Toolkit::ItemView::DownCast( Dali::BaseHandle( object ) );
1767
1768   if( itemView )
1769   {
1770     ItemView& itemViewImpl( GetImpl( itemView ) );
1771     switch( index )
1772     {
1773       case Toolkit::ItemView::Property::MINIMUM_SWIPE_SPEED:
1774       {
1775         value = itemViewImpl.GetMinimumSwipeSpeed();
1776         break;
1777       }
1778       case Toolkit::ItemView::Property::MINIMUM_SWIPE_DISTANCE:
1779       {
1780         value = itemViewImpl.GetMinimumSwipeDistance();
1781         break;
1782       }
1783       case Toolkit::ItemView::Property::WHEEL_SCROLL_DISTANCE_STEP:
1784       {
1785         value = itemViewImpl.GetWheelScrollDistanceStep();
1786         break;
1787       }
1788       case Toolkit::ItemView::Property::SNAP_TO_ITEM_ENABLED:
1789       {
1790         value = itemViewImpl.GetAnchoring();
1791         break;
1792       }
1793       case Toolkit::ItemView::Property::REFRESH_INTERVAL:
1794       {
1795         value = itemViewImpl.GetRefreshInterval();
1796         break;
1797       }
1798     }
1799   }
1800
1801   return value;
1802 }
1803
1804 bool ItemView::DoAction( BaseObject* object, const std::string& actionName, const Property::Map& attributes )
1805 {
1806   Dali::BaseHandle handle( object );
1807
1808   Toolkit::ItemView itemView = Toolkit::ItemView::DownCast( handle );
1809
1810   DALI_ASSERT_ALWAYS( itemView );
1811
1812   if( 0 == strcmp( actionName.c_str(), ACTION_STOP_SCROLLING ) )
1813   {
1814     GetImpl( itemView ).DoStopScrolling();
1815   }
1816   else if ( 0 == strcmp( actionName.c_str(), ACTION_ENABLE_REFRESH ) )
1817   {
1818     GetImpl( itemView ).SetRefreshNotificationEnabled( true );
1819   }
1820   else if ( 0 == strcmp( actionName.c_str(), ACTION_DISABLE_REFRESH ) )
1821   {
1822     GetImpl( itemView ).SetRefreshNotificationEnabled( false );
1823   }
1824
1825   return true;
1826 }
1827
1828 void ItemView::DoStopScrolling()
1829 {
1830   if( mScrollAnimation )
1831   {
1832     mScrollAnimation.Stop();
1833     mScrollAnimation.Reset();
1834   }
1835 }
1836
1837 void ItemView::SetRefreshNotificationEnabled( bool enabled )
1838 {
1839   mRefreshNotificationEnabled = enabled;
1840 }
1841
1842 } // namespace Internal
1843
1844 } // namespace Toolkit
1845
1846 } // namespace Dali