80eda13e5250c474ebf7242c6089d8ec187eba42
[platform/core/uifw/dali-toolkit.git] / base / dali-toolkit / public-api / controls / scrollable / item-view / spiral-layout.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/public-api/controls/scrollable/item-view/spiral-layout.h>
20
21 // EXTERNAL INCLUDES
22 #include <algorithm>
23 #include <dali/public-api/animation/animation.h>
24
25 using namespace Dali;
26 using namespace Dali::Toolkit;
27 using namespace std;
28
29 namespace // unnamed namespace
30 {
31
32 const float DEFAULT_ITEMS_PER_SPIRAL_TURN = 9.5f;
33 const float DEFAULT_ITEM_SPACING_RADIANS = Math::PI*2.0f/DEFAULT_ITEMS_PER_SPIRAL_TURN;
34
35 const float DEFAULT_REVOLUTION_DISTANCE = 190.0f;
36 const float DEFAULT_ITEM_DESCENT = DEFAULT_REVOLUTION_DISTANCE / DEFAULT_ITEMS_PER_SPIRAL_TURN;
37
38 const float DEFAULT_TOP_ITEM_ALIGNMENT = -0.125f;
39
40 const float DEFAULT_SCROLL_SPEED_FACTOR = 0.01f;
41 const float DEFAULT_MAXIMUM_SWIPE_SPEED = 30.0f;
42 const float DEFAULT_ITEM_FLICK_ANIMATION_DURATION = 0.1f;
43
44 struct DefaultItemSizeFunction
45 {
46   Vector3 operator()(const Vector3& layoutSize)
47   {
48     float width = layoutSize.width * 0.25f;
49
50     // 4x3 aspect ratio
51     return Vector3(width, (width/4)*3, (width/4)*3);
52   }
53 };
54
55 struct DefaultSpiralRadiusFunction
56 {
57   float operator()(const Vector3& layoutSize)
58   {
59     return layoutSize.width*0.4f;
60   }
61 };
62
63 struct SpiralPositionConstraintUp
64 {
65   SpiralPositionConstraintUp(SpiralLayout::SpiralRadiusFunction spiralRadius, float itemSpacingRadians, float itemDescent, float topItemAlignment)
66   : mSpiralRadius(spiralRadius),
67     mItemSpacingRadians(itemSpacingRadians),
68     mItemDescent(itemDescent),
69     mTopItemAlignment(topItemAlignment)
70   {
71   }
72
73   Vector3 operator()(const Vector3& current, const float& layoutPosition, const float& scrollSpeed, const Vector3& layoutSize)
74   {
75     float spiralRadius = mSpiralRadius(layoutSize);
76
77     float angle = -Math::PI*0.5f + mItemSpacingRadians * layoutPosition;
78
79     return Vector3( -spiralRadius * cosf(angle),
80                     (mItemDescent * layoutPosition) + layoutSize.height*mTopItemAlignment,
81                     -spiralRadius * sinf(angle) );
82   }
83
84   SpiralLayout::SpiralRadiusFunction mSpiralRadius;
85   float mItemSpacingRadians;
86   float mItemDescent;
87   float mTopItemAlignment;
88 };
89
90 struct SpiralPositionConstraintLeft
91 {
92   SpiralPositionConstraintLeft(SpiralLayout::SpiralRadiusFunction spiralRadius, float itemSpacingRadians, float itemDescent, float topItemAlignment)
93   : mSpiralRadius(spiralRadius),
94     mItemSpacingRadians(itemSpacingRadians),
95     mItemDescent(itemDescent),
96     mTopItemAlignment(topItemAlignment)
97   {
98   }
99
100   Vector3 operator()(const Vector3& current, const float& layoutPosition, const float& scrollSpeed, const Vector3& layoutSize)
101   {
102     float spiralRadius = mSpiralRadius(layoutSize);
103
104     float angle = Math::PI*0.5f + mItemSpacingRadians * layoutPosition;
105
106     return Vector3(  (mItemDescent * layoutPosition) + layoutSize.width*mTopItemAlignment,
107                      -spiralRadius * cosf(angle),
108                       spiralRadius * sinf(angle) );
109   }
110
111   SpiralLayout::SpiralRadiusFunction mSpiralRadius;
112   float mItemSpacingRadians;
113   float mItemDescent;
114   float mTopItemAlignment;
115 };
116
117 struct SpiralPositionConstraintDown
118 {
119   SpiralPositionConstraintDown(SpiralLayout::SpiralRadiusFunction spiralRadius, float itemSpacingRadians, float itemDescent, float topItemAlignment)
120   : mSpiralRadius(spiralRadius),
121     mItemSpacingRadians(itemSpacingRadians),
122     mItemDescent(itemDescent),
123     mTopItemAlignment(topItemAlignment)
124   {
125   }
126
127   Vector3 operator()(const Vector3& current, const float& layoutPosition, const float& scrollSpeed, const Vector3& layoutSize)
128   {
129     float spiralRadius = mSpiralRadius(layoutSize);
130
131     float angle = Math::PI*0.5f + mItemSpacingRadians * layoutPosition;
132
133     return Vector3(  -spiralRadius * cosf(angle),
134                     (-mItemDescent * layoutPosition) - layoutSize.height*mTopItemAlignment,
135                       spiralRadius * sinf(angle) );
136   }
137
138   SpiralLayout::SpiralRadiusFunction mSpiralRadius;
139   float mItemSpacingRadians;
140   float mItemDescent;
141   float mTopItemAlignment;
142 };
143
144 struct SpiralPositionConstraintRight
145 {
146   SpiralPositionConstraintRight(SpiralLayout::SpiralRadiusFunction spiralRadius, float itemSpacingRadians, float itemDescent, float topItemAlignment)
147   : mSpiralRadius(spiralRadius),
148     mItemSpacingRadians(itemSpacingRadians),
149     mItemDescent(itemDescent),
150     mTopItemAlignment(topItemAlignment)
151   {
152   }
153
154   Vector3 operator()(const Vector3& current, const float& layoutPosition, const float& scrollSpeed, const Vector3& layoutSize)
155   {
156     float spiralRadius = mSpiralRadius(layoutSize);
157
158     float angle = -Math::PI*0.5f + mItemSpacingRadians * layoutPosition;
159
160     return Vector3( (-mItemDescent * layoutPosition) - layoutSize.width*mTopItemAlignment,
161                      -spiralRadius * cosf(angle),
162                      -spiralRadius * sinf(angle) );
163   }
164
165   SpiralLayout::SpiralRadiusFunction mSpiralRadius;
166   float mItemSpacingRadians;
167   float mItemDescent;
168   float mTopItemAlignment;
169 };
170
171 struct SpiralRotationConstraintUp
172 {
173   SpiralRotationConstraintUp(float itemSpacingRadians)
174   : mItemSpacingRadians(itemSpacingRadians)
175   {
176   }
177
178   Quaternion operator()(const Quaternion& current, const float& layoutPosition, const float& scrollSpeed, const Vector3& layoutSize)
179   {
180     float angle = -mItemSpacingRadians * layoutPosition;
181
182     return Quaternion(angle, Vector3::YAXIS);
183   }
184
185   float mItemSpacingRadians;
186 };
187
188 struct SpiralRotationConstraintLeft
189 {
190   SpiralRotationConstraintLeft(float itemSpacingRadians)
191   : mItemSpacingRadians(itemSpacingRadians)
192   {
193   }
194
195   Quaternion operator()(const Quaternion& current, const float& layoutPosition, const float& scrollSpeed, const Vector3& layoutSize)
196   {
197     float angle = -mItemSpacingRadians * layoutPosition;
198
199     return Quaternion(-Math::PI*0.5f, Vector3::ZAXIS) * Quaternion(angle, Vector3::YAXIS);
200   }
201
202   float mItemSpacingRadians;
203 };
204
205 struct SpiralRotationConstraintDown
206 {
207   SpiralRotationConstraintDown(float itemSpacingRadians)
208   : mItemSpacingRadians(itemSpacingRadians)
209   {
210   }
211
212   Quaternion operator()(const Quaternion& current, const float& layoutPosition, const float& scrollSpeed, const Vector3& layoutSize)
213   {
214     float angle = -mItemSpacingRadians * layoutPosition;
215
216     return Quaternion(-Math::PI, Vector3::ZAXIS) * Quaternion(angle, Vector3::YAXIS);
217   }
218
219   float mItemSpacingRadians;
220 };
221
222 struct SpiralRotationConstraintRight
223 {
224   SpiralRotationConstraintRight(float itemSpacingRadians)
225   : mItemSpacingRadians(itemSpacingRadians)
226   {
227   }
228
229   Quaternion operator()(const Quaternion& current, const float& layoutPosition, const float& scrollSpeed, const Vector3& layoutSize)
230   {
231     float angle = -mItemSpacingRadians * layoutPosition;
232
233     return Quaternion(-Math::PI*1.5f, Vector3::ZAXIS) * Quaternion(angle, Vector3::YAXIS);
234   }
235
236   float mItemSpacingRadians;
237 };
238
239 struct SpiralColorConstraint
240 {
241   SpiralColorConstraint(float itemSpacingRadians)
242   : mItemSpacingRadians(itemSpacingRadians)
243   {
244   }
245
246   Vector4 operator()(const Vector4& current, const float& layoutPosition, const float& scrollSpeed, const Vector3& layoutSize)
247   {
248     Degree angle = Radian(mItemSpacingRadians * fabsf(layoutPosition));
249     angle = (float)((int)angle % 360);
250
251     float progress = angle / 360.0f;
252     progress = (progress > 0.5f) ? 2.0f*(1.0f - progress) : progress*2.0f;
253
254     float darkness(1.0f);
255     {
256       const float startMarker = 0.10f; // The progress at which darkening starts
257       const float endMarker   = 0.35f; // The progress at which darkening ends
258       const float minDarkness = 0.15f; // The darkness at end marker
259
260       if (progress > endMarker)
261       {
262         darkness = minDarkness;
263       }
264       else if (progress > startMarker)
265       {
266         darkness = 1.0f - ( (1.0f - minDarkness) * ((progress-startMarker) / (endMarker-startMarker)) );
267       }
268     }
269
270     return Vector4( darkness, darkness, darkness, current.a );
271   }
272
273   float mItemSpacingRadians;
274 };
275
276 struct SpiralVisibilityConstraintPortrait
277 {
278   SpiralVisibilityConstraintPortrait(float itemSpacingRadians, float itemDescent, float topItemAlignment)
279   : mItemSpacingRadians(itemSpacingRadians),
280     mItemDescent(itemDescent),
281     mTopItemAlignment(topItemAlignment)
282   {
283   }
284
285   bool operator()(const bool& current, const float& layoutPosition, const float& scrollSpeed, const Vector3& layoutSize)
286   {
287     float itemsCachedBeforeTopItem = layoutSize.height*(mTopItemAlignment+0.5f) / mItemDescent;
288     return (layoutPosition >= -itemsCachedBeforeTopItem - 1.0f && layoutPosition <= (layoutSize.height / mItemDescent) + 1.0f);
289   }
290
291   float mItemSpacingRadians;
292   float mItemDescent;
293   float mTopItemAlignment;
294 };
295
296 struct SpiralVisibilityConstraintLandscape
297 {
298   SpiralVisibilityConstraintLandscape(float itemSpacingRadians, float itemDescent, float topItemAlignment)
299   : mItemSpacingRadians(itemSpacingRadians),
300     mItemDescent(itemDescent),
301     mTopItemAlignment(topItemAlignment)
302   {
303   }
304
305   bool operator()(const bool& current, const float& layoutPosition, const float& scrollSpeed, const Vector3& layoutSize)
306   {
307     float itemsCachedBeforeTopItem = layoutSize.width*(mTopItemAlignment+0.5f) / mItemDescent;
308     return (layoutPosition >= -itemsCachedBeforeTopItem - 1.0f && layoutPosition <= (layoutSize.width / mItemDescent) + 1.0f);
309   }
310
311   float mItemSpacingRadians;
312   float mItemDescent;
313   float mTopItemAlignment;
314 };
315
316 } // unnamed namespace
317
318 namespace Dali
319 {
320
321 namespace Toolkit
322 {
323
324 struct SpiralLayout::Impl
325 {
326   Impl()
327   : mItemSizeFunction(DefaultItemSizeFunction()),
328     mSpiralRadiusFunction(DefaultSpiralRadiusFunction()),
329     mItemSpacingRadians(DEFAULT_ITEM_SPACING_RADIANS),
330     mRevolutionDistance(DEFAULT_REVOLUTION_DISTANCE),
331     mItemDescent(DEFAULT_ITEM_DESCENT),
332     mTopItemAlignment(DEFAULT_TOP_ITEM_ALIGNMENT),
333     mScrollSpeedFactor(DEFAULT_SCROLL_SPEED_FACTOR),
334     mMaximumSwipeSpeed(DEFAULT_MAXIMUM_SWIPE_SPEED),
335     mItemFlickAnimationDuration(DEFAULT_ITEM_FLICK_ANIMATION_DURATION)
336   {
337   }
338
339   ItemSizeFunction     mItemSizeFunction;
340   SpiralRadiusFunction mSpiralRadiusFunction;
341
342   float mItemSpacingRadians;
343   float mRevolutionDistance;
344   float mItemDescent;
345   float mTopItemAlignment;
346   float mScrollSpeedFactor;
347   float mMaximumSwipeSpeed;
348   float mItemFlickAnimationDuration;
349 };
350
351 SpiralLayoutPtr SpiralLayout::New()
352 {
353   return SpiralLayoutPtr(new SpiralLayout());
354 }
355
356 SpiralLayout::~SpiralLayout()
357 {
358   delete mImpl;
359 }
360
361 void SpiralLayout::SetItemSizeFunction(ItemSizeFunction function)
362 {
363   mImpl->mItemSizeFunction = function;
364 }
365
366 SpiralLayout::ItemSizeFunction SpiralLayout::GetItemSizeFunction() const
367 {
368   return mImpl->mItemSizeFunction;
369 }
370
371 void SpiralLayout::SetItemSpacing(Radian itemSpacing)
372 {
373   mImpl->mItemSpacingRadians = itemSpacing;
374
375   float itemsPerSpiral = max(1.0f, (2.0f*(float)Math::PI) / mImpl->mItemSpacingRadians);
376   mImpl->mItemDescent = mImpl->mRevolutionDistance / itemsPerSpiral;
377 }
378
379 Radian SpiralLayout::GetItemSpacing() const
380 {
381   return Radian( mImpl->mItemSpacingRadians );
382 }
383
384 void SpiralLayout::SetRevolutionDistance(float distance)
385 {
386   mImpl->mRevolutionDistance = distance;
387
388   float itemsPerSpiral = max(1.0f, (2.0f*(float)Math::PI) / mImpl->mItemSpacingRadians);
389   mImpl->mItemDescent = mImpl->mRevolutionDistance / itemsPerSpiral;
390 }
391
392 float SpiralLayout::GetRevolutionDistance() const
393 {
394   return mImpl->mRevolutionDistance;
395 }
396
397 void SpiralLayout::SetSpiralRadiusFunction(SpiralRadiusFunction function)
398 {
399   mImpl->mSpiralRadiusFunction = function;
400 }
401
402 SpiralLayout::SpiralRadiusFunction SpiralLayout::GetSpiralRadiusFunction() const
403 {
404   return mImpl->mSpiralRadiusFunction;
405 }
406
407 void SpiralLayout::SetTopItemAlignment(float alignment)
408 {
409   mImpl->mTopItemAlignment = alignment;
410 }
411
412 float SpiralLayout::GetTopItemAlignment() const
413 {
414   return mImpl->mTopItemAlignment;
415 }
416
417 void SpiralLayout::SetScrollSpeedFactor(float scrollSpeed)
418 {
419   mImpl->mScrollSpeedFactor = scrollSpeed;
420 }
421
422 void SpiralLayout::SetMaximumSwipeSpeed(float speed)
423 {
424   mImpl->mMaximumSwipeSpeed = speed;
425 }
426
427 void SpiralLayout::SetItemFlickAnimationDuration(float durationSeconds)
428 {
429   mImpl->mItemFlickAnimationDuration = durationSeconds;
430 }
431
432 float SpiralLayout::GetScrollSpeedFactor() const
433 {
434   return mImpl->mScrollSpeedFactor;
435 }
436
437 float SpiralLayout::GetMaximumSwipeSpeed() const
438 {
439   return mImpl->mMaximumSwipeSpeed;
440 }
441
442 float SpiralLayout::GetItemFlickAnimationDuration() const
443 {
444   return mImpl->mItemFlickAnimationDuration;
445 }
446
447 float SpiralLayout::GetMinimumLayoutPosition(unsigned int numberOfItems, Vector3 layoutSize) const
448 {
449   return 1.0f - static_cast<float>(numberOfItems);
450 }
451
452 float SpiralLayout::GetClosestAnchorPosition(float layoutPosition) const
453 {
454   return round(layoutPosition);
455 }
456
457 float SpiralLayout::GetItemScrollToPosition(unsigned int itemId) const
458 {
459   return -(static_cast<float>(itemId));
460 }
461
462 ItemRange SpiralLayout::GetItemsWithinArea(float firstItemPosition, Vector3 layoutSize) const
463 {
464   float layoutHeight = IsHorizontal(mOrientation) ? layoutSize.width : layoutSize.height;
465   float itemsPerSpiral = layoutHeight / mImpl->mItemDescent;
466   float itemsCachedBeforeTopItem = layoutHeight * (mImpl->mTopItemAlignment + 0.5f) / mImpl->mItemDescent;
467   float itemsViewable = min(itemsPerSpiral, itemsPerSpiral - itemsCachedBeforeTopItem - firstItemPosition + 1.0f);
468
469   unsigned int firstItem = static_cast<unsigned int>(max(0.0f, -firstItemPosition - itemsCachedBeforeTopItem - 1.0f));
470   unsigned int lastItem  = static_cast<unsigned int>(max(0.0f, firstItem + itemsViewable));
471
472   return ItemRange(firstItem, lastItem+1);
473 }
474
475 unsigned int SpiralLayout::GetReserveItemCount(Vector3 layoutSize) const
476 {
477   float layoutHeight = IsHorizontal(mOrientation) ? layoutSize.width : layoutSize.height;
478   return static_cast<unsigned int>(layoutHeight / mImpl->mItemDescent);
479 }
480
481 bool SpiralLayout::GetItemSize(unsigned int itemId, Vector3 layoutSize, Vector3& itemSize) const
482 {
483   // Note: itemId is not checked, since every item has the same size
484
485   itemSize = mImpl->mItemSizeFunction(layoutSize);
486   return true;
487 }
488
489 void SpiralLayout::GetResizeAnimation(Animation& animation, Actor actor, Vector3 size, float durationSeconds) const
490 {
491   if(animation)
492   {
493     animation.Resize(actor, size);
494   }
495 }
496
497 bool SpiralLayout::GetPositionConstraint(unsigned int itemId, ItemLayout::Vector3Function& constraint) const
498 {
499   if (mOrientation == ControlOrientation::Up)
500   {
501     constraint = SpiralPositionConstraintUp(mImpl->mSpiralRadiusFunction, mImpl->mItemSpacingRadians, mImpl->mItemDescent, mImpl->mTopItemAlignment);
502   }
503   else if (mOrientation == ControlOrientation::Left)
504   {
505     constraint = SpiralPositionConstraintLeft(mImpl->mSpiralRadiusFunction, mImpl->mItemSpacingRadians, mImpl->mItemDescent, mImpl->mTopItemAlignment);
506   }
507   else if (mOrientation == ControlOrientation::Down)
508   {
509     constraint = SpiralPositionConstraintDown(mImpl->mSpiralRadiusFunction, mImpl->mItemSpacingRadians, mImpl->mItemDescent, mImpl->mTopItemAlignment);
510   }
511   else // mOrientation == ControlOrientation::Right
512   {
513     constraint = SpiralPositionConstraintRight(mImpl->mSpiralRadiusFunction, mImpl->mItemSpacingRadians, mImpl->mItemDescent, mImpl->mTopItemAlignment);
514   }
515
516   return true;
517 }
518
519 bool SpiralLayout::GetRotationConstraint(unsigned int itemId, ItemLayout::QuaternionFunction& constraint) const
520 {
521   if (mOrientation == ControlOrientation::Up)
522   {
523     constraint = SpiralRotationConstraintUp(mImpl->mItemSpacingRadians);
524   }
525   else if (mOrientation == ControlOrientation::Left)
526   {
527     constraint = SpiralRotationConstraintLeft(mImpl->mItemSpacingRadians);
528   }
529   else if (mOrientation == ControlOrientation::Down)
530   {
531     constraint = SpiralRotationConstraintDown(mImpl->mItemSpacingRadians);
532   }
533   else // mOrientation == ControlOrientation::Right
534   {
535     constraint = SpiralRotationConstraintRight(mImpl->mItemSpacingRadians);
536   }
537
538   return true;
539 }
540
541 bool SpiralLayout::GetScaleConstraint(unsigned int itemId, ItemLayout::Vector3Function& constraint) const
542 {
543   return false; // No scaling
544 }
545
546 bool SpiralLayout::GetColorConstraint(unsigned int itemId, ItemLayout::Vector4Function& constraint) const
547 {
548   constraint = SpiralColorConstraint(mImpl->mItemSpacingRadians);
549   return true;
550 }
551
552 bool SpiralLayout::GetVisibilityConstraint(unsigned int itemId, ItemLayout::BoolFunction& constraint) const
553 {
554   if (IsVertical(mOrientation))
555   {
556     constraint = SpiralVisibilityConstraintPortrait(mImpl->mItemSpacingRadians, mImpl->mItemDescent, mImpl->mTopItemAlignment);
557   }
558   else // horizontal
559   {
560     constraint = SpiralVisibilityConstraintLandscape(mImpl->mItemSpacingRadians, mImpl->mItemDescent, mImpl->mTopItemAlignment);
561   }
562
563   return true;
564 }
565
566 Degree SpiralLayout::GetScrollDirection() const
567 {
568   Degree scrollDirection(0);
569
570   if (mOrientation == ControlOrientation::Up)
571   {
572     scrollDirection = 0.0f - 45.0f; // Allow swiping horizontally & vertically
573   }
574   else if (mOrientation == ControlOrientation::Left)
575   {
576     scrollDirection = 90.0f - 45.0f;
577   }
578   else if (mOrientation == ControlOrientation::Down)
579   {
580     scrollDirection = 180.0f - 45.0f;
581   }
582   else // mOrientation == ControlOrientation::Right
583   {
584     scrollDirection = 270.0f - 45.0f;
585   }
586
587   return scrollDirection;
588 }
589
590 SpiralLayout::SpiralLayout()
591 : mImpl(NULL)
592 {
593   mImpl = new Impl();
594 }
595
596 float SpiralLayout::GetClosestOnScreenLayoutPosition(int itemID, float currentLayoutPosition, const Vector3& layoutSize)
597 {
598   return GetItemScrollToPosition(itemID);
599 }
600
601 } // namespace Toolkit
602
603 } // namespace Dali