Refactored Item Layouts
[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 void SpiralLayout::GetResizeAnimation(Animation& animation, Actor actor, Vector3 size, float durationSeconds) const
401 {
402   if(animation)
403   {
404     animation.AnimateTo( Property( actor, Actor::Property::SIZE ), size );
405   }
406 }
407
408 Degree SpiralLayout::GetScrollDirection() const
409 {
410   Degree scrollDirection(0);
411   const ControlOrientation::Type orientation = GetOrientation();
412
413   if ( orientation == ControlOrientation::Up )
414   {
415     scrollDirection = Degree( -45.0f ); // Allow swiping horizontally & vertically
416   }
417   else if ( orientation == ControlOrientation::Left )
418   {
419     scrollDirection = Degree( 45.0f );
420   }
421   else if ( orientation == ControlOrientation::Down )
422   {
423     scrollDirection = Degree( 180.0f - 45.0f );
424   }
425   else // orientation == ControlOrientation::Right
426   {
427     scrollDirection = Degree( 270.0f - 45.0f );
428   }
429
430   return scrollDirection;
431 }
432
433 void SpiralLayout::ApplyConstraints( Actor& actor, const int itemId, const Vector3& layoutSize, const Actor& itemViewActor )
434 {
435   // This just implements the default behaviour of constraint application.
436   // Custom layouts can override this function to apply their custom constraints.
437   Dali::Toolkit::ItemView itemView = Dali::Toolkit::ItemView::DownCast( itemViewActor );
438   if( itemView )
439   {
440     const ControlOrientation::Type orientation = GetOrientation();
441
442     // Position constraint
443     SpiralPositionConstraint positionConstraint( itemId, GetDefaultSpiralRadiusFunction( layoutSize ), mImpl->mItemSpacingRadians, mImpl->mItemDescent, mImpl->mTopItemAlignment );
444     Constraint constraint;
445     if ( orientation == ControlOrientation::Up )
446     {
447       constraint = Constraint::New< Vector3 >( actor, Actor::Property::POSITION, positionConstraint, &SpiralPositionConstraint::OrientationUp );
448     }
449     else if ( orientation == ControlOrientation::Left )
450     {
451       constraint = Constraint::New< Vector3 >( actor, Actor::Property::POSITION, positionConstraint, &SpiralPositionConstraint::OrientationLeft );
452     }
453     else if ( orientation == ControlOrientation::Down )
454     {
455       constraint = Constraint::New< Vector3 >( actor, Actor::Property::POSITION, positionConstraint, &SpiralPositionConstraint::OrientationDown );
456     }
457     else // orientation == ControlOrientation::Right
458     {
459       constraint = Constraint::New< Vector3 >( actor, Actor::Property::POSITION, positionConstraint, &SpiralPositionConstraint::OrientationRight );
460     }
461     constraint.AddSource( ParentSource( Toolkit::ItemView::Property::LAYOUT_POSITION ) );
462     constraint.AddSource( ParentSource( Actor::Property::SIZE ) );
463     constraint.Apply();
464
465     // Rotation constraint
466     SpiralRotationConstraint rotationConstraint( itemId, mImpl->mItemSpacingRadians );
467     if ( orientation == ControlOrientation::Up )
468     {
469       constraint = Constraint::New< Quaternion >( actor, Actor::Property::ORIENTATION, rotationConstraint, &SpiralRotationConstraint::OrientationUp );
470     }
471     else if ( orientation == ControlOrientation::Left )
472     {
473       constraint = Constraint::New< Quaternion >( actor, Actor::Property::ORIENTATION, rotationConstraint, &SpiralRotationConstraint::OrientationLeft );
474     }
475     else if ( orientation == ControlOrientation::Down )
476     {
477       constraint = Constraint::New< Quaternion >( actor, Actor::Property::ORIENTATION, rotationConstraint, &SpiralRotationConstraint::OrientationDown );
478     }
479     else // orientation == ControlOrientation::Right
480     {
481       constraint = Constraint::New< Quaternion >( actor, Actor::Property::ORIENTATION, rotationConstraint, &SpiralRotationConstraint::OrientationRight );
482     }
483     constraint.AddSource( ParentSource( Toolkit::ItemView::Property::LAYOUT_POSITION ) );
484     constraint.Apply();
485
486     // Color constraint
487     constraint = Constraint::New< Vector4 >( actor, Actor::Property::COLOR, SpiralColorConstraint( itemId, mImpl->mItemSpacingRadians ) );
488     constraint.AddSource( ParentSource( Toolkit::ItemView::Property::LAYOUT_POSITION ) );
489     constraint.SetRemoveAction(Dali::Constraint::Discard);
490     constraint.Apply();
491
492     // Visibility constraint
493     SpiralVisibilityConstraint visibilityConstraint( itemId, mImpl->mItemSpacingRadians, mImpl->mItemDescent, mImpl->mTopItemAlignment );
494     if (IsVertical( orientation ) )
495     {
496       constraint = Constraint::New< bool >( actor, Actor::Property::VISIBLE, visibilityConstraint, &SpiralVisibilityConstraint::Portrait );
497     }
498     else // horizontal
499     {
500       constraint = Constraint::New< bool >( actor, Actor::Property::VISIBLE, visibilityConstraint, &SpiralVisibilityConstraint::Landscape );
501     }
502     constraint.AddSource( ParentSource( Toolkit::ItemView::Property::LAYOUT_POSITION ) );
503     constraint.AddSource( ParentSource( Actor::Property::SIZE ) );
504     constraint.SetRemoveAction(Dali::Constraint::Discard);
505     constraint.Apply();
506   }
507 }
508
509 Vector3 SpiralLayout::GetItemPosition(int itemID, float currentLayoutPosition, const Vector3& layoutSize) const
510 {
511   Vector3 itemPosition = Vector3::ZERO;
512   const ControlOrientation::Type orientation = GetOrientation();
513
514   SpiralPositionConstraint positionConstraint( itemID, GetDefaultSpiralRadiusFunction( layoutSize ), mImpl->mItemSpacingRadians, mImpl->mItemDescent, mImpl->mTopItemAlignment );
515
516   if ( orientation == ControlOrientation::Up )
517   {
518     positionConstraint.OrientationUp( itemPosition, currentLayoutPosition + itemID, layoutSize );
519   }
520   else if ( orientation == ControlOrientation::Left )
521   {
522     positionConstraint.OrientationLeft( itemPosition, currentLayoutPosition + itemID, layoutSize );
523   }
524   else if ( orientation == ControlOrientation::Down )
525   {
526     positionConstraint.OrientationDown( itemPosition, currentLayoutPosition + itemID, layoutSize );
527   }
528   else // orientation == ControlOrientation::Right
529   {
530     positionConstraint.OrientationRight( itemPosition, currentLayoutPosition + itemID, layoutSize );
531   }
532
533   return itemPosition;
534 }
535
536 SpiralLayout::SpiralLayout()
537 : mImpl(NULL)
538 {
539   mImpl = new Impl();
540 }
541
542 float SpiralLayout::GetClosestOnScreenLayoutPosition(int itemID, float currentLayoutPosition, const Vector3& layoutSize)
543 {
544   return GetItemScrollToPosition(itemID);
545 }
546
547 } // namespace Internal
548
549 } // namespace Toolkit
550
551 } // namespace Dali