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