Fixed issue that ItemView overshoot appears before it reaches the end of the list
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / controls / scrollable / item-view / spiral-layout.cpp
1 /*
2  * Copyright (c) 2015 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/spiral-layout.h>
20
21 // EXTERNAL INCLUDES
22 #include <algorithm>
23 #include <dali/public-api/animation/animation.h>
24 #include <dali/public-api/animation/constraint.h>
25
26 // INTERNAL INCLUDES
27 #include <dali-toolkit/public-api/controls/scrollable/item-view/item-view.h>
28
29 using namespace Dali;
30 using namespace Dali::Toolkit;
31
32 namespace // unnamed namespace
33 {
34
35 const float DEFAULT_ITEMS_PER_SPIRAL_TURN = 9.5f;
36 const float DEFAULT_ITEM_SPACING_RADIANS = Math::PI*2.0f/DEFAULT_ITEMS_PER_SPIRAL_TURN;
37
38 const float DEFAULT_REVOLUTION_DISTANCE = 190.0f;
39 const float DEFAULT_ITEM_DESCENT = DEFAULT_REVOLUTION_DISTANCE / DEFAULT_ITEMS_PER_SPIRAL_TURN;
40
41 const float DEFAULT_TOP_ITEM_ALIGNMENT = -0.125f;
42
43 const float DEFAULT_SCROLL_SPEED_FACTOR = 0.01f;
44 const float DEFAULT_MAXIMUM_SWIPE_SPEED = 30.0f;
45 const float DEFAULT_ITEM_FLICK_ANIMATION_DURATION = 0.1f;
46
47 float GetDefaultSpiralRadiusFunction(const Vector3& layoutSize)
48 {
49   return layoutSize.width*0.4f;
50 }
51
52 struct SpiralPositionConstraint
53 {
54   SpiralPositionConstraint( unsigned int itemId, float spiralRadius, float itemSpacingRadians, float itemDescent, float topItemAlignment )
55   : mItemId( itemId ),
56     mSpiralRadius( spiralRadius ),
57     mItemSpacingRadians( itemSpacingRadians ),
58     mItemDescent( itemDescent ),
59     mTopItemAlignment( topItemAlignment )
60   {
61   }
62
63   inline void OrientationUp( Vector3& current, float layoutPosition, const Vector3& layoutSize )
64   {
65     float angle = -Math::PI * 0.5f + mItemSpacingRadians * layoutPosition;
66
67     current.x = -mSpiralRadius * cosf( angle );
68     current.y = ( mItemDescent * layoutPosition ) + layoutSize.height * mTopItemAlignment;
69     current.z = -mSpiralRadius * sinf( angle );
70   }
71
72   inline void OrientationLeft( Vector3& current, float layoutPosition, const Vector3& layoutSize )
73   {
74     float angle = Math::PI * 0.5f + mItemSpacingRadians * layoutPosition;
75
76     current.x = ( mItemDescent * layoutPosition ) + layoutSize.width * mTopItemAlignment;
77     current.y = -mSpiralRadius * cosf( angle );
78     current.z = mSpiralRadius * sinf( angle );
79   }
80
81   inline void OrientationDown( Vector3& current, float layoutPosition, const Vector3& layoutSize )
82   {
83     float angle = Math::PI * 0.5f + mItemSpacingRadians * layoutPosition;
84
85     current.x = -mSpiralRadius * cosf( angle );
86     current.y = ( -mItemDescent * layoutPosition ) - layoutSize.height * mTopItemAlignment;
87     current.z = mSpiralRadius * sinf(angle);
88   }
89
90   inline void OrientationRight( Vector3& current, float layoutPosition, const Vector3& layoutSize )
91   {
92     float angle = -Math::PI*0.5f + mItemSpacingRadians * layoutPosition;
93
94     current.x = (-mItemDescent * layoutPosition) - layoutSize.width * mTopItemAlignment;
95     current.y = -mSpiralRadius * cosf( angle );
96     current.z = -mSpiralRadius * sinf( angle );
97   }
98
99   void OrientationUp( Vector3& current, const PropertyInputContainer& inputs )
100   {
101     float layoutPosition = inputs[0]->GetFloat() + static_cast< float >( mItemId );
102     const Vector3& layoutSize = inputs[1]->GetVector3();
103     OrientationUp( current, layoutPosition, layoutSize );
104   }
105
106   void OrientationLeft( Vector3& current, const PropertyInputContainer& inputs )
107   {
108     float layoutPosition = inputs[0]->GetFloat() + static_cast< float >( mItemId );
109     const Vector3& layoutSize = inputs[1]->GetVector3();
110     OrientationLeft( current, layoutPosition, layoutSize );
111   }
112
113   void OrientationDown( Vector3& current, const PropertyInputContainer& inputs )
114   {
115     float layoutPosition = inputs[0]->GetFloat() + static_cast< float >( mItemId );
116     const Vector3& layoutSize = inputs[1]->GetVector3();
117     OrientationDown( current, layoutPosition, layoutSize );
118   }
119
120   void OrientationRight( Vector3& current, const PropertyInputContainer& inputs )
121   {
122     float layoutPosition = inputs[0]->GetFloat() + static_cast< float >( mItemId );
123     const Vector3& layoutSize = inputs[1]->GetVector3();
124     OrientationRight( current, layoutPosition, layoutSize );
125   }
126
127   unsigned int mItemId;
128   float mSpiralRadius;
129   float mItemSpacingRadians;
130   float mItemDescent;
131   float mTopItemAlignment;
132 };
133
134 struct SpiralRotationConstraint
135 {
136   SpiralRotationConstraint( unsigned int itemId, float itemSpacingRadians )
137   : mItemId( itemId ),
138     mItemSpacingRadians( itemSpacingRadians )
139   {
140   }
141
142   void OrientationUp( Quaternion& current, const PropertyInputContainer& inputs )
143   {
144     float layoutPosition = inputs[0]->GetFloat() + static_cast< float >( mItemId );
145     float angle = -mItemSpacingRadians * layoutPosition;
146
147     current = Quaternion( Radian( angle ), Vector3::YAXIS);
148   }
149
150   void OrientationLeft( Quaternion& current, const PropertyInputContainer& inputs )
151   {
152     float layoutPosition = inputs[0]->GetFloat() + static_cast< float >( mItemId );
153     float angle = -mItemSpacingRadians * layoutPosition;
154
155     current = Quaternion( Radian( -Math::PI * 0.5f ), Vector3::ZAXIS ) * Quaternion( Radian( angle ), Vector3::YAXIS );
156   }
157
158   void OrientationDown( Quaternion& current, const PropertyInputContainer& inputs )
159   {
160     float layoutPosition = inputs[0]->GetFloat() + static_cast< float >( mItemId );
161     float angle = -mItemSpacingRadians * layoutPosition;
162
163     current = Quaternion( Radian( -Math::PI ), Vector3::ZAXIS) * Quaternion( Radian( angle ), Vector3::YAXIS );
164   }
165
166   void OrientationRight( Quaternion& current, const PropertyInputContainer& inputs )
167   {
168     float layoutPosition = inputs[0]->GetFloat() + static_cast< float >( mItemId );
169     float angle = -mItemSpacingRadians * layoutPosition;
170
171     current = Quaternion( Radian( -Math::PI * 1.5f ), Vector3::ZAXIS) * Quaternion( Radian( angle ), Vector3::YAXIS );
172   }
173
174   unsigned int mItemId;
175   float mItemSpacingRadians;
176 };
177
178 struct SpiralColorConstraint
179 {
180   SpiralColorConstraint( unsigned int itemId, float itemSpacingRadians )
181   : mItemId( itemId ),
182     mItemSpacingRadians( itemSpacingRadians )
183   {
184   }
185
186   void operator()( Vector4& current, const PropertyInputContainer& inputs )
187   {
188     float layoutPosition = inputs[0]->GetFloat() + static_cast< float >( mItemId );
189     Radian angle( mItemSpacingRadians * fabsf( layoutPosition ) / Dali::ANGLE_360 );
190
191     float progress = angle - floorf( angle ); // take fractional bit only to get between 0.0 - 1.0
192     progress = (progress > 0.5f) ? 2.0f*(1.0f - progress) : progress*2.0f;
193
194     float darkness(1.0f);
195     {
196       const float startMarker = 0.10f; // The progress at which darkening starts
197       const float endMarker   = 0.35f; // The progress at which darkening ends
198       const float minDarkness = 0.15f; // The darkness at end marker
199
200       if (progress > endMarker)
201       {
202         darkness = minDarkness;
203       }
204       else if (progress > startMarker)
205       {
206         darkness = 1.0f - ( (1.0f - minDarkness) * ((progress-startMarker) / (endMarker-startMarker)) );
207       }
208     }
209
210     current.r = current.g = current.b = darkness;
211   }
212
213   unsigned int mItemId;
214   float mItemSpacingRadians;
215 };
216
217 struct SpiralVisibilityConstraint
218 {
219   SpiralVisibilityConstraint( unsigned int itemId, float itemSpacingRadians, float itemDescent, float topItemAlignment )
220   : mItemId( itemId ),
221     mItemSpacingRadians( itemSpacingRadians ),
222     mItemDescent( itemDescent ),
223     mTopItemAlignment( topItemAlignment )
224   {
225   }
226
227   void Portrait( bool& current, const PropertyInputContainer& inputs )
228   {
229     float layoutPosition = inputs[0]->GetFloat() + static_cast< float >( mItemId );
230     const Vector3& layoutSize = inputs[1]->GetVector3();
231     float itemsCachedBeforeTopItem = layoutSize.height*(mTopItemAlignment+0.5f) / mItemDescent;
232     current = ( layoutPosition >= -itemsCachedBeforeTopItem - 1.0f && layoutPosition <= ( layoutSize.height / mItemDescent ) + 1.0f );
233   }
234
235   void Landscape( bool& current, const PropertyInputContainer& inputs )
236   {
237     float layoutPosition = inputs[0]->GetFloat() + static_cast< float >( mItemId );
238     const Vector3& layoutSize = inputs[1]->GetVector3();
239     float itemsCachedBeforeTopItem = layoutSize.width*(mTopItemAlignment+0.5f) / mItemDescent;
240     current = ( layoutPosition >= -itemsCachedBeforeTopItem - 1.0f && layoutPosition <= ( layoutSize.width / mItemDescent ) + 1.0f );
241   }
242
243   unsigned int mItemId;
244   float mItemSpacingRadians;
245   float mItemDescent;
246   float mTopItemAlignment;
247 };
248
249 } // unnamed namespace
250
251 namespace Dali
252 {
253
254 namespace Toolkit
255 {
256
257 namespace Internal
258 {
259
260 struct SpiralLayout::Impl
261 {
262   Impl()
263   : mItemSpacingRadians(DEFAULT_ITEM_SPACING_RADIANS),
264     mRevolutionDistance(DEFAULT_REVOLUTION_DISTANCE),
265     mItemDescent(DEFAULT_ITEM_DESCENT),
266     mTopItemAlignment(DEFAULT_TOP_ITEM_ALIGNMENT),
267     mScrollSpeedFactor(DEFAULT_SCROLL_SPEED_FACTOR),
268     mMaximumSwipeSpeed(DEFAULT_MAXIMUM_SWIPE_SPEED),
269     mItemFlickAnimationDuration(DEFAULT_ITEM_FLICK_ANIMATION_DURATION)
270   {
271   }
272
273   float mItemSpacingRadians;
274   float mRevolutionDistance;
275   float mItemDescent;
276   float mTopItemAlignment;
277   float mScrollSpeedFactor;
278   float mMaximumSwipeSpeed;
279   float mItemFlickAnimationDuration;
280 };
281
282 SpiralLayoutPtr SpiralLayout::New()
283 {
284   return SpiralLayoutPtr(new SpiralLayout());
285 }
286
287 SpiralLayout::~SpiralLayout()
288 {
289   delete mImpl;
290 }
291
292 void SpiralLayout::SetItemSpacing(Radian itemSpacing)
293 {
294   mImpl->mItemSpacingRadians = itemSpacing;
295
296   float itemsPerSpiral = std::max(1.0f, (2.0f*(float)Math::PI) / mImpl->mItemSpacingRadians);
297   mImpl->mItemDescent = mImpl->mRevolutionDistance / itemsPerSpiral;
298 }
299
300 Radian SpiralLayout::GetItemSpacing() const
301 {
302   return Radian( mImpl->mItemSpacingRadians );
303 }
304
305 void SpiralLayout::SetRevolutionDistance(float distance)
306 {
307   mImpl->mRevolutionDistance = distance;
308
309   float itemsPerSpiral = std::max(1.0f, (2.0f*(float)Math::PI) / mImpl->mItemSpacingRadians);
310   mImpl->mItemDescent = mImpl->mRevolutionDistance / itemsPerSpiral;
311 }
312
313 float SpiralLayout::GetRevolutionDistance() const
314 {
315   return mImpl->mRevolutionDistance;
316 }
317
318 void SpiralLayout::SetTopItemAlignment(float alignment)
319 {
320   mImpl->mTopItemAlignment = alignment;
321 }
322
323 float SpiralLayout::GetTopItemAlignment() const
324 {
325   return mImpl->mTopItemAlignment;
326 }
327
328 void SpiralLayout::SetScrollSpeedFactor(float scrollSpeed)
329 {
330   mImpl->mScrollSpeedFactor = scrollSpeed;
331 }
332
333 void SpiralLayout::SetMaximumSwipeSpeed(float speed)
334 {
335   mImpl->mMaximumSwipeSpeed = speed;
336 }
337
338 void SpiralLayout::SetItemFlickAnimationDuration(float durationSeconds)
339 {
340   mImpl->mItemFlickAnimationDuration = durationSeconds;
341 }
342
343 float SpiralLayout::GetScrollSpeedFactor() const
344 {
345   return mImpl->mScrollSpeedFactor;
346 }
347
348 float SpiralLayout::GetMaximumSwipeSpeed() const
349 {
350   return mImpl->mMaximumSwipeSpeed;
351 }
352
353 float SpiralLayout::GetItemFlickAnimationDuration() const
354 {
355   return mImpl->mItemFlickAnimationDuration;
356 }
357
358 float SpiralLayout::GetMinimumLayoutPosition(unsigned int numberOfItems, Vector3 layoutSize) const
359 {
360   return 1.0f - static_cast<float>(numberOfItems);
361 }
362
363 float SpiralLayout::GetClosestAnchorPosition(float layoutPosition) const
364 {
365   return round(layoutPosition);
366 }
367
368 float SpiralLayout::GetItemScrollToPosition(unsigned int itemId) const
369 {
370   return -(static_cast<float>(itemId));
371 }
372
373 ItemRange SpiralLayout::GetItemsWithinArea(float firstItemPosition, Vector3 layoutSize) const
374 {
375   float layoutHeight = IsHorizontal( GetOrientation() ) ? layoutSize.width : layoutSize.height;
376   float itemsPerSpiral = layoutHeight / mImpl->mItemDescent;
377   float itemsCachedBeforeTopItem = layoutHeight * (mImpl->mTopItemAlignment + 0.5f) / mImpl->mItemDescent;
378   float itemsViewable = std::min(itemsPerSpiral, itemsPerSpiral - itemsCachedBeforeTopItem - firstItemPosition + 1.0f);
379
380   unsigned int firstItem = static_cast<unsigned int>(std::max(0.0f, -firstItemPosition - itemsCachedBeforeTopItem - 1.0f));
381   unsigned int lastItem  = static_cast<unsigned int>(std::max(0.0f, firstItem + itemsViewable));
382
383   return ItemRange(firstItem, lastItem+1);
384 }
385
386 unsigned int SpiralLayout::GetReserveItemCount(Vector3 layoutSize) const
387 {
388   float layoutHeight = IsHorizontal( GetOrientation() ) ? layoutSize.width : layoutSize.height;
389   return static_cast<unsigned int>(layoutHeight / mImpl->mItemDescent);
390 }
391
392 void SpiralLayout::GetDefaultItemSize( unsigned int itemId, const Vector3& layoutSize, Vector3& itemSize ) const
393 {
394   itemSize.width = layoutSize.width * 0.25f;
395
396   // 4x3 aspect ratio
397   itemSize.height = itemSize.depth = ( itemSize.width / 4.0f ) * 3.0f;
398 }
399
400 Degree SpiralLayout::GetScrollDirection() const
401 {
402   Degree scrollDirection(0);
403   const ControlOrientation::Type orientation = GetOrientation();
404
405   if ( orientation == ControlOrientation::Up )
406   {
407     scrollDirection = Degree( -45.0f ); // Allow swiping horizontally & vertically
408   }
409   else if ( orientation == ControlOrientation::Left )
410   {
411     scrollDirection = Degree( 45.0f );
412   }
413   else if ( orientation == ControlOrientation::Down )
414   {
415     scrollDirection = Degree( 180.0f - 45.0f );
416   }
417   else // orientation == ControlOrientation::Right
418   {
419     scrollDirection = Degree( 270.0f - 45.0f );
420   }
421
422   return scrollDirection;
423 }
424
425 void SpiralLayout::ApplyConstraints( Actor& actor, const int itemId, const Vector3& layoutSize, const Actor& itemViewActor )
426 {
427   // This just implements the default behaviour of constraint application.
428   // Custom layouts can override this function to apply their custom constraints.
429   Dali::Toolkit::ItemView itemView = Dali::Toolkit::ItemView::DownCast( itemViewActor );
430   if( itemView )
431   {
432     const ControlOrientation::Type orientation = GetOrientation();
433
434     // Position constraint
435     SpiralPositionConstraint positionConstraint( itemId, GetDefaultSpiralRadiusFunction( layoutSize ), mImpl->mItemSpacingRadians, mImpl->mItemDescent, mImpl->mTopItemAlignment );
436     Constraint constraint;
437     if ( orientation == ControlOrientation::Up )
438     {
439       constraint = Constraint::New< Vector3 >( actor, Actor::Property::POSITION, positionConstraint, &SpiralPositionConstraint::OrientationUp );
440     }
441     else if ( orientation == ControlOrientation::Left )
442     {
443       constraint = Constraint::New< Vector3 >( actor, Actor::Property::POSITION, positionConstraint, &SpiralPositionConstraint::OrientationLeft );
444     }
445     else if ( orientation == ControlOrientation::Down )
446     {
447       constraint = Constraint::New< Vector3 >( actor, Actor::Property::POSITION, positionConstraint, &SpiralPositionConstraint::OrientationDown );
448     }
449     else // orientation == ControlOrientation::Right
450     {
451       constraint = Constraint::New< Vector3 >( actor, Actor::Property::POSITION, positionConstraint, &SpiralPositionConstraint::OrientationRight );
452     }
453     constraint.AddSource( ParentSource( Toolkit::ItemView::Property::LAYOUT_POSITION ) );
454     constraint.AddSource( ParentSource( Actor::Property::SIZE ) );
455     constraint.Apply();
456
457     // Rotation constraint
458     SpiralRotationConstraint rotationConstraint( itemId, mImpl->mItemSpacingRadians );
459     if ( orientation == ControlOrientation::Up )
460     {
461       constraint = Constraint::New< Quaternion >( actor, Actor::Property::ORIENTATION, rotationConstraint, &SpiralRotationConstraint::OrientationUp );
462     }
463     else if ( orientation == ControlOrientation::Left )
464     {
465       constraint = Constraint::New< Quaternion >( actor, Actor::Property::ORIENTATION, rotationConstraint, &SpiralRotationConstraint::OrientationLeft );
466     }
467     else if ( orientation == ControlOrientation::Down )
468     {
469       constraint = Constraint::New< Quaternion >( actor, Actor::Property::ORIENTATION, rotationConstraint, &SpiralRotationConstraint::OrientationDown );
470     }
471     else // orientation == ControlOrientation::Right
472     {
473       constraint = Constraint::New< Quaternion >( actor, Actor::Property::ORIENTATION, rotationConstraint, &SpiralRotationConstraint::OrientationRight );
474     }
475     constraint.AddSource( ParentSource( Toolkit::ItemView::Property::LAYOUT_POSITION ) );
476     constraint.Apply();
477
478     // Color constraint
479     constraint = Constraint::New< Vector4 >( actor, Actor::Property::COLOR, SpiralColorConstraint( itemId, mImpl->mItemSpacingRadians ) );
480     constraint.AddSource( ParentSource( Toolkit::ItemView::Property::LAYOUT_POSITION ) );
481     constraint.SetRemoveAction(Dali::Constraint::Discard);
482     constraint.Apply();
483
484     // Visibility constraint
485     SpiralVisibilityConstraint visibilityConstraint( itemId, mImpl->mItemSpacingRadians, mImpl->mItemDescent, mImpl->mTopItemAlignment );
486     if (IsVertical( orientation ) )
487     {
488       constraint = Constraint::New< bool >( actor, Actor::Property::VISIBLE, visibilityConstraint, &SpiralVisibilityConstraint::Portrait );
489     }
490     else // horizontal
491     {
492       constraint = Constraint::New< bool >( actor, Actor::Property::VISIBLE, visibilityConstraint, &SpiralVisibilityConstraint::Landscape );
493     }
494     constraint.AddSource( ParentSource( Toolkit::ItemView::Property::LAYOUT_POSITION ) );
495     constraint.AddSource( ParentSource( Actor::Property::SIZE ) );
496     constraint.SetRemoveAction(Dali::Constraint::Discard);
497     constraint.Apply();
498   }
499 }
500
501 Vector3 SpiralLayout::GetItemPosition(int itemID, float currentLayoutPosition, const Vector3& layoutSize) const
502 {
503   Vector3 itemPosition = Vector3::ZERO;
504   const ControlOrientation::Type orientation = GetOrientation();
505
506   SpiralPositionConstraint positionConstraint( itemID, GetDefaultSpiralRadiusFunction( layoutSize ), mImpl->mItemSpacingRadians, mImpl->mItemDescent, mImpl->mTopItemAlignment );
507
508   if ( orientation == ControlOrientation::Up )
509   {
510     positionConstraint.OrientationUp( itemPosition, currentLayoutPosition + itemID, layoutSize );
511   }
512   else if ( orientation == ControlOrientation::Left )
513   {
514     positionConstraint.OrientationLeft( itemPosition, currentLayoutPosition + itemID, layoutSize );
515   }
516   else if ( orientation == ControlOrientation::Down )
517   {
518     positionConstraint.OrientationDown( itemPosition, currentLayoutPosition + itemID, layoutSize );
519   }
520   else // orientation == ControlOrientation::Right
521   {
522     positionConstraint.OrientationRight( itemPosition, currentLayoutPosition + itemID, layoutSize );
523   }
524
525   return itemPosition;
526 }
527
528 SpiralLayout::SpiralLayout()
529 : mImpl(NULL)
530 {
531   mImpl = new Impl();
532 }
533
534 float SpiralLayout::GetClosestOnScreenLayoutPosition(int itemID, float currentLayoutPosition, const Vector3& layoutSize)
535 {
536   return GetItemScrollToPosition(itemID);
537 }
538
539 } // namespace Internal
540
541 } // namespace Toolkit
542
543 } // namespace Dali