2 * Copyright (c) 2021 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include <dali-toolkit/internal/controls/scrollable/item-view/spiral-layout.h>
22 #include <dali/public-api/animation/animation.h>
23 #include <dali/public-api/animation/constraint.h>
27 #include <dali-toolkit/public-api/controls/scrollable/item-view/default-item-layout-property.h>
28 #include <dali-toolkit/public-api/controls/scrollable/item-view/item-view.h>
31 using namespace Dali::Toolkit;
33 namespace // unnamed namespace
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;
38 const float DEFAULT_REVOLUTION_DISTANCE = 190.0f;
39 const float DEFAULT_ITEM_DESCENT = DEFAULT_REVOLUTION_DISTANCE / DEFAULT_ITEMS_PER_SPIRAL_TURN;
41 const float DEFAULT_TOP_ITEM_ALIGNMENT = -0.125f;
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;
47 float GetDefaultSpiralRadiusFunction(const Vector3& layoutSize)
49 return layoutSize.width * 0.4f;
52 struct SpiralPositionConstraint
54 SpiralPositionConstraint(unsigned int itemId, float spiralRadius, float itemSpacingRadians, float itemDescent, float topItemAlignment)
56 mSpiralRadius(spiralRadius),
57 mItemSpacingRadians(itemSpacingRadians),
58 mItemDescent(itemDescent),
59 mTopItemAlignment(topItemAlignment)
63 inline void OrientationUp(Vector3& current, float layoutPosition, const Vector3& layoutSize)
65 float angle = -Math::PI * 0.5f + mItemSpacingRadians * layoutPosition;
67 current.x = -mSpiralRadius * cosf(angle);
68 current.y = (mItemDescent * layoutPosition) + layoutSize.height * mTopItemAlignment;
69 current.z = -mSpiralRadius * sinf(angle);
72 inline void OrientationLeft(Vector3& current, float layoutPosition, const Vector3& layoutSize)
74 float angle = Math::PI * 0.5f + mItemSpacingRadians * layoutPosition;
76 current.x = (mItemDescent * layoutPosition) + layoutSize.width * mTopItemAlignment;
77 current.y = -mSpiralRadius * cosf(angle);
78 current.z = mSpiralRadius * sinf(angle);
81 inline void OrientationDown(Vector3& current, float layoutPosition, const Vector3& layoutSize)
83 float angle = Math::PI * 0.5f + mItemSpacingRadians * layoutPosition;
85 current.x = -mSpiralRadius * cosf(angle);
86 current.y = (-mItemDescent * layoutPosition) - layoutSize.height * mTopItemAlignment;
87 current.z = mSpiralRadius * sinf(angle);
90 inline void OrientationRight(Vector3& current, float layoutPosition, const Vector3& layoutSize)
92 float angle = -Math::PI * 0.5f + mItemSpacingRadians * layoutPosition;
94 current.x = (-mItemDescent * layoutPosition) - layoutSize.width * mTopItemAlignment;
95 current.y = -mSpiralRadius * cosf(angle);
96 current.z = -mSpiralRadius * sinf(angle);
99 void OrientationUp(Vector3& current, const PropertyInputContainer& inputs)
101 float layoutPosition = inputs[0]->GetFloat() + static_cast<float>(mItemId);
102 const Vector3& layoutSize = inputs[1]->GetVector3();
103 OrientationUp(current, layoutPosition, layoutSize);
106 void OrientationLeft(Vector3& current, const PropertyInputContainer& inputs)
108 float layoutPosition = inputs[0]->GetFloat() + static_cast<float>(mItemId);
109 const Vector3& layoutSize = inputs[1]->GetVector3();
110 OrientationLeft(current, layoutPosition, layoutSize);
113 void OrientationDown(Vector3& current, const PropertyInputContainer& inputs)
115 float layoutPosition = inputs[0]->GetFloat() + static_cast<float>(mItemId);
116 const Vector3& layoutSize = inputs[1]->GetVector3();
117 OrientationDown(current, layoutPosition, layoutSize);
120 void OrientationRight(Vector3& current, const PropertyInputContainer& inputs)
122 float layoutPosition = inputs[0]->GetFloat() + static_cast<float>(mItemId);
123 const Vector3& layoutSize = inputs[1]->GetVector3();
124 OrientationRight(current, layoutPosition, layoutSize);
127 unsigned int mItemId;
129 float mItemSpacingRadians;
131 float mTopItemAlignment;
134 struct SpiralRotationConstraint
136 SpiralRotationConstraint(unsigned int itemId, float itemSpacingRadians)
138 mItemSpacingRadians(itemSpacingRadians)
142 void OrientationUp(Quaternion& current, const PropertyInputContainer& inputs)
144 float layoutPosition = inputs[0]->GetFloat() + static_cast<float>(mItemId);
145 float angle = -mItemSpacingRadians * layoutPosition;
147 current = Quaternion(Radian(angle), Vector3::YAXIS);
150 void OrientationLeft(Quaternion& current, const PropertyInputContainer& inputs)
152 float layoutPosition = inputs[0]->GetFloat() + static_cast<float>(mItemId);
153 float angle = -mItemSpacingRadians * layoutPosition;
155 current = Quaternion(Radian(-Math::PI * 0.5f), Vector3::ZAXIS) * Quaternion(Radian(angle), Vector3::YAXIS);
158 void OrientationDown(Quaternion& current, const PropertyInputContainer& inputs)
160 float layoutPosition = inputs[0]->GetFloat() + static_cast<float>(mItemId);
161 float angle = -mItemSpacingRadians * layoutPosition;
163 current = Quaternion(Radian(-Math::PI), Vector3::ZAXIS) * Quaternion(Radian(angle), Vector3::YAXIS);
166 void OrientationRight(Quaternion& current, const PropertyInputContainer& inputs)
168 float layoutPosition = inputs[0]->GetFloat() + static_cast<float>(mItemId);
169 float angle = -mItemSpacingRadians * layoutPosition;
171 current = Quaternion(Radian(-Math::PI * 1.5f), Vector3::ZAXIS) * Quaternion(Radian(angle), Vector3::YAXIS);
174 unsigned int mItemId;
175 float mItemSpacingRadians;
178 struct SpiralColorConstraint
180 SpiralColorConstraint(unsigned int itemId, float itemSpacingRadians)
182 mItemSpacingRadians(itemSpacingRadians)
186 void operator()(Vector4& current, const PropertyInputContainer& inputs)
188 float layoutPosition = inputs[0]->GetFloat() + static_cast<float>(mItemId);
189 Radian angle(mItemSpacingRadians * fabsf(layoutPosition) / Dali::ANGLE_360);
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;
194 float darkness(1.0f);
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
200 if(progress > endMarker)
202 darkness = minDarkness;
204 else if(progress > startMarker)
206 darkness = 1.0f - ((1.0f - minDarkness) * ((progress - startMarker) / (endMarker - startMarker)));
210 current.r = current.g = current.b = darkness;
213 unsigned int mItemId;
214 float mItemSpacingRadians;
217 struct SpiralVisibilityConstraint
219 SpiralVisibilityConstraint(unsigned int itemId, float itemSpacingRadians, float itemDescent, float topItemAlignment)
221 mItemSpacingRadians(itemSpacingRadians),
222 mItemDescent(itemDescent),
223 mTopItemAlignment(topItemAlignment)
227 void Portrait(bool& current, const PropertyInputContainer& inputs)
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);
235 void Landscape(bool& current, const PropertyInputContainer& inputs)
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);
243 unsigned int mItemId;
244 float mItemSpacingRadians;
246 float mTopItemAlignment;
249 } // unnamed namespace
257 struct SpiralLayout::Impl
260 : mItemSpacingRadians(DEFAULT_ITEM_SPACING_RADIANS),
261 mRevolutionDistance(DEFAULT_REVOLUTION_DISTANCE),
262 mItemDescent(DEFAULT_ITEM_DESCENT),
263 mTopItemAlignment(DEFAULT_TOP_ITEM_ALIGNMENT),
264 mScrollSpeedFactor(DEFAULT_SCROLL_SPEED_FACTOR),
265 mMaximumSwipeSpeed(DEFAULT_MAXIMUM_SWIPE_SPEED),
266 mItemFlickAnimationDuration(DEFAULT_ITEM_FLICK_ANIMATION_DURATION)
270 float mItemSpacingRadians;
271 float mRevolutionDistance;
273 float mTopItemAlignment;
274 float mScrollSpeedFactor;
275 float mMaximumSwipeSpeed;
276 float mItemFlickAnimationDuration;
279 SpiralLayoutPtr SpiralLayout::New()
281 return SpiralLayoutPtr(new SpiralLayout());
284 SpiralLayout::~SpiralLayout()
289 void SpiralLayout::SetItemSpacing(Radian itemSpacing)
291 mImpl->mItemSpacingRadians = itemSpacing;
293 float itemsPerSpiral = std::max(1.0f, (2.0f * (float)Math::PI) / mImpl->mItemSpacingRadians);
294 mImpl->mItemDescent = mImpl->mRevolutionDistance / itemsPerSpiral;
297 Radian SpiralLayout::GetItemSpacing() const
299 return Radian(mImpl->mItemSpacingRadians);
302 void SpiralLayout::SetRevolutionDistance(float distance)
304 mImpl->mRevolutionDistance = distance;
306 float itemsPerSpiral = std::max(1.0f, (2.0f * (float)Math::PI) / mImpl->mItemSpacingRadians);
307 mImpl->mItemDescent = mImpl->mRevolutionDistance / itemsPerSpiral;
310 float SpiralLayout::GetRevolutionDistance() const
312 return mImpl->mRevolutionDistance;
315 void SpiralLayout::SetTopItemAlignment(float alignment)
317 mImpl->mTopItemAlignment = alignment;
320 float SpiralLayout::GetTopItemAlignment() const
322 return mImpl->mTopItemAlignment;
325 void SpiralLayout::SetScrollSpeedFactor(float scrollSpeed)
327 mImpl->mScrollSpeedFactor = scrollSpeed;
330 void SpiralLayout::SetMaximumSwipeSpeed(float speed)
332 mImpl->mMaximumSwipeSpeed = speed;
335 void SpiralLayout::SetItemFlickAnimationDuration(float durationSeconds)
337 mImpl->mItemFlickAnimationDuration = durationSeconds;
340 float SpiralLayout::GetScrollSpeedFactor() const
342 return mImpl->mScrollSpeedFactor;
345 float SpiralLayout::GetMaximumSwipeSpeed() const
347 return mImpl->mMaximumSwipeSpeed;
350 float SpiralLayout::GetItemFlickAnimationDuration() const
352 return mImpl->mItemFlickAnimationDuration;
355 float SpiralLayout::GetMinimumLayoutPosition(unsigned int numberOfItems, Vector3 layoutSize) const
357 return 1.0f - static_cast<float>(numberOfItems);
360 float SpiralLayout::GetClosestAnchorPosition(float layoutPosition) const
362 return round(layoutPosition);
365 float SpiralLayout::GetItemScrollToPosition(unsigned int itemId) const
367 return -(static_cast<float>(itemId));
370 ItemRange SpiralLayout::GetItemsWithinArea(float firstItemPosition, Vector3 layoutSize) const
372 float layoutHeight = IsHorizontal(GetOrientation()) ? layoutSize.width : layoutSize.height;
373 float itemsPerSpiral = layoutHeight / mImpl->mItemDescent;
374 float itemsCachedBeforeTopItem = layoutHeight * (mImpl->mTopItemAlignment + 0.5f) / mImpl->mItemDescent;
375 float itemsViewable = std::min(itemsPerSpiral, itemsPerSpiral - itemsCachedBeforeTopItem - firstItemPosition + 1.0f);
377 unsigned int firstItem = static_cast<unsigned int>(std::max(0.0f, -firstItemPosition - itemsCachedBeforeTopItem - 1.0f));
378 unsigned int lastItem = static_cast<unsigned int>(std::max(0.0f, firstItem + itemsViewable));
380 return ItemRange(firstItem, lastItem + 1);
383 unsigned int SpiralLayout::GetReserveItemCount(Vector3 layoutSize) const
385 float layoutHeight = IsHorizontal(GetOrientation()) ? layoutSize.width : layoutSize.height;
386 return static_cast<unsigned int>(layoutHeight / mImpl->mItemDescent);
389 void SpiralLayout::GetDefaultItemSize(unsigned int itemId, const Vector3& layoutSize, Vector3& itemSize) const
391 itemSize.width = layoutSize.width * 0.25f;
394 itemSize.height = itemSize.depth = (itemSize.width / 4.0f) * 3.0f;
397 Degree SpiralLayout::GetScrollDirection() const
399 Degree scrollDirection(0);
400 const ControlOrientation::Type orientation = GetOrientation();
402 if(orientation == ControlOrientation::Up)
404 scrollDirection = Degree(-45.0f); // Allow swiping horizontally & vertically
406 else if(orientation == ControlOrientation::Left)
408 scrollDirection = Degree(45.0f);
410 else if(orientation == ControlOrientation::Down)
412 scrollDirection = Degree(180.0f - 45.0f);
414 else // orientation == ControlOrientation::Right
416 scrollDirection = Degree(270.0f - 45.0f);
419 return scrollDirection;
422 void SpiralLayout::ApplyConstraints(Actor& actor, const int itemId, const Vector3& layoutSize, const Actor& itemViewActor)
424 // This just implements the default behaviour of constraint application.
425 // Custom layouts can override this function to apply their custom constraints.
426 Dali::Toolkit::ItemView itemView = Dali::Toolkit::ItemView::DownCast(itemViewActor);
429 const ControlOrientation::Type orientation = GetOrientation();
431 // Position constraint
432 SpiralPositionConstraint positionConstraint(itemId, GetDefaultSpiralRadiusFunction(layoutSize), mImpl->mItemSpacingRadians, mImpl->mItemDescent, mImpl->mTopItemAlignment);
433 Constraint constraint;
434 if(orientation == ControlOrientation::Up)
436 constraint = Constraint::New<Vector3>(actor, Actor::Property::POSITION, positionConstraint, &SpiralPositionConstraint::OrientationUp);
438 else if(orientation == ControlOrientation::Left)
440 constraint = Constraint::New<Vector3>(actor, Actor::Property::POSITION, positionConstraint, &SpiralPositionConstraint::OrientationLeft);
442 else if(orientation == ControlOrientation::Down)
444 constraint = Constraint::New<Vector3>(actor, Actor::Property::POSITION, positionConstraint, &SpiralPositionConstraint::OrientationDown);
446 else // orientation == ControlOrientation::Right
448 constraint = Constraint::New<Vector3>(actor, Actor::Property::POSITION, positionConstraint, &SpiralPositionConstraint::OrientationRight);
450 constraint.AddSource(ParentSource(Toolkit::ItemView::Property::LAYOUT_POSITION));
451 constraint.AddSource(ParentSource(Actor::Property::SIZE));
454 // Rotation constraint
455 SpiralRotationConstraint rotationConstraint(itemId, mImpl->mItemSpacingRadians);
456 if(orientation == ControlOrientation::Up)
458 constraint = Constraint::New<Quaternion>(actor, Actor::Property::ORIENTATION, rotationConstraint, &SpiralRotationConstraint::OrientationUp);
460 else if(orientation == ControlOrientation::Left)
462 constraint = Constraint::New<Quaternion>(actor, Actor::Property::ORIENTATION, rotationConstraint, &SpiralRotationConstraint::OrientationLeft);
464 else if(orientation == ControlOrientation::Down)
466 constraint = Constraint::New<Quaternion>(actor, Actor::Property::ORIENTATION, rotationConstraint, &SpiralRotationConstraint::OrientationDown);
468 else // orientation == ControlOrientation::Right
470 constraint = Constraint::New<Quaternion>(actor, Actor::Property::ORIENTATION, rotationConstraint, &SpiralRotationConstraint::OrientationRight);
472 constraint.AddSource(ParentSource(Toolkit::ItemView::Property::LAYOUT_POSITION));
476 constraint = Constraint::New<Vector4>(actor, Actor::Property::COLOR, SpiralColorConstraint(itemId, mImpl->mItemSpacingRadians));
477 constraint.AddSource(ParentSource(Toolkit::ItemView::Property::LAYOUT_POSITION));
478 constraint.SetRemoveAction(Dali::Constraint::DISCARD);
481 // Visibility constraint
482 SpiralVisibilityConstraint visibilityConstraint(itemId, mImpl->mItemSpacingRadians, mImpl->mItemDescent, mImpl->mTopItemAlignment);
483 if(IsVertical(orientation))
485 constraint = Constraint::New<bool>(actor, Actor::Property::VISIBLE, visibilityConstraint, &SpiralVisibilityConstraint::Portrait);
489 constraint = Constraint::New<bool>(actor, Actor::Property::VISIBLE, visibilityConstraint, &SpiralVisibilityConstraint::Landscape);
491 constraint.AddSource(ParentSource(Toolkit::ItemView::Property::LAYOUT_POSITION));
492 constraint.AddSource(ParentSource(Actor::Property::SIZE));
493 constraint.SetRemoveAction(Dali::Constraint::DISCARD);
498 void SpiralLayout::SetSpiralLayoutProperties(const Property::Map& properties)
500 // Set any properties specified for SpiralLayout.
501 for(unsigned int idx = 0, mapCount = properties.Count(); idx < mapCount; ++idx)
503 KeyValuePair propertyPair = properties.GetKeyValue(idx);
504 switch(DefaultItemLayoutProperty::Property(propertyPair.first.indexKey))
506 case DefaultItemLayoutProperty::SPIRAL_ITEM_SPACING:
508 SetItemSpacing(Radian(propertyPair.second.Get<float>()));
511 case DefaultItemLayoutProperty::SPIRAL_MAXIMUM_SWIPE_SPEED:
513 SetMaximumSwipeSpeed(propertyPair.second.Get<float>());
516 case DefaultItemLayoutProperty::SPIRAL_TOP_ITEM_ALIGNMENT:
518 SetTopItemAlignment(propertyPair.second.Get<float>());
521 case DefaultItemLayoutProperty::SPIRAL_SCROLL_SPEED_FACTOR:
523 SetScrollSpeedFactor(propertyPair.second.Get<float>());
526 case DefaultItemLayoutProperty::SPIRAL_REVOLUTION_DISTANCE:
528 SetRevolutionDistance(propertyPair.second.Get<float>());
531 case DefaultItemLayoutProperty::SPIRAL_ITEM_FLICK_ANIMATION_DURATION:
533 SetItemFlickAnimationDuration(propertyPair.second.Get<float>());
544 Vector3 SpiralLayout::GetItemPosition(int itemID, float currentLayoutPosition, const Vector3& layoutSize) const
546 Vector3 itemPosition = Vector3::ZERO;
547 const ControlOrientation::Type orientation = GetOrientation();
549 SpiralPositionConstraint positionConstraint(itemID, GetDefaultSpiralRadiusFunction(layoutSize), mImpl->mItemSpacingRadians, mImpl->mItemDescent, mImpl->mTopItemAlignment);
551 if(orientation == ControlOrientation::Up)
553 positionConstraint.OrientationUp(itemPosition, currentLayoutPosition + itemID, layoutSize);
555 else if(orientation == ControlOrientation::Left)
557 positionConstraint.OrientationLeft(itemPosition, currentLayoutPosition + itemID, layoutSize);
559 else if(orientation == ControlOrientation::Down)
561 positionConstraint.OrientationDown(itemPosition, currentLayoutPosition + itemID, layoutSize);
563 else //orientation == ControlOrientation::Right
565 positionConstraint.OrientationRight(itemPosition, currentLayoutPosition + itemID, layoutSize);
571 SpiralLayout::SpiralLayout()
577 float SpiralLayout::GetClosestOnScreenLayoutPosition(int itemID, float currentLayoutPosition, const Vector3& layoutSize)
579 return GetItemScrollToPosition(itemID);
582 } // namespace Internal
584 } // namespace Toolkit