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/depth-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 unsigned int DEFAULT_NUMBER_OF_COLUMNS = 3;
36 const float DEFAULT_NUMBER_OF_ROWS = 26.0f;
37 const float DEFAULT_ROW_SPACING = 55.0f;
38 const float DEFAULT_BOTTOM_MARGIN_FACTOR = 0.2f;
39 const Radian DEFAULT_TILT_ANGLE(Math::PI * 0.15f);
40 const Radian DEFAULT_ITEM_TILT_ANGLE(-Math::PI * 0.025f);
41 const float DEFAULT_SCROLL_SPEED_FACTOR = 0.02f;
42 const float DEFAULT_MAXIMUM_SWIPE_SPEED = 50.0f;
43 const float DEFAULT_ITEM_FLICK_ANIMATION_DURATION = 0.03f;
45 inline float GetColumnPosition(unsigned int numberOfColumns, unsigned int columnNumber, const Vector3& itemSize, float layoutWidth)
47 // Share the available space between margins & column spacings
48 float availableSpace = std::max(0.0f, (layoutWidth - itemSize.width * numberOfColumns));
50 float leftMargin = availableSpace / numberOfColumns * 0.5f;
52 float columnPosition = leftMargin + itemSize.width * 0.5f + columnNumber * (itemSize.width + availableSpace / numberOfColumns);
54 return columnPosition - layoutWidth * 0.5f;
57 struct DepthPositionConstraint
59 DepthPositionConstraint(unsigned int itemId,
60 unsigned int numberOfColumns,
61 unsigned int columnNumber,
62 const Vector3& itemSize,
65 : mItemSize(itemSize),
67 mNumberOfColumns(numberOfColumns),
68 mColumnNumber(columnNumber),
69 mHeightScale(heightScale),
70 mDepthScale(depthScale)
74 inline void Orientation0(Vector3& current, float layoutPosition, const Vector3& layoutSize)
76 float rowLayoutPositon = layoutPosition - static_cast<float>(mColumnNumber);
78 current.x = GetColumnPosition(mNumberOfColumns, mColumnNumber, mItemSize, layoutSize.width);
79 current.y = rowLayoutPositon * mHeightScale + layoutSize.height * 0.5f - DEFAULT_BOTTOM_MARGIN_FACTOR * layoutSize.height - mItemSize.height * 0.5f;
80 current.z = -rowLayoutPositon * mDepthScale;
83 inline void Orientation90(Vector3& current, float layoutPosition, const Vector3& layoutSize)
85 float rowLayoutPositon = layoutPosition - static_cast<float>(mColumnNumber) + mNumberOfColumns * 0.5f;
87 current.x = rowLayoutPositon * mHeightScale + layoutSize.width * 0.5f - DEFAULT_BOTTOM_MARGIN_FACTOR * layoutSize.width - mItemSize.height * 0.5f;
88 current.y = -GetColumnPosition(mNumberOfColumns, mColumnNumber, mItemSize, layoutSize.height);
89 current.z = -rowLayoutPositon * mDepthScale;
92 inline void Orientation180(Vector3& current, float layoutPosition, const Vector3& layoutSize)
94 float rowLayoutPositon = layoutPosition - static_cast<float>(mColumnNumber);
96 current.x = -GetColumnPosition(mNumberOfColumns, mColumnNumber, mItemSize, layoutSize.width);
97 current.y = -(rowLayoutPositon * mHeightScale + layoutSize.height * 0.5f - DEFAULT_BOTTOM_MARGIN_FACTOR * layoutSize.height - mItemSize.height * 0.5f);
98 current.z = -rowLayoutPositon * mDepthScale;
101 inline void Orientation270(Vector3& current, float layoutPosition, const Vector3& layoutSize)
103 float rowLayoutPositon = layoutPosition - static_cast<float>(mColumnNumber) + mNumberOfColumns * 0.5f;
105 current.x = -(rowLayoutPositon * mHeightScale + layoutSize.width * 0.5f - DEFAULT_BOTTOM_MARGIN_FACTOR * layoutSize.width - mItemSize.height * 0.5f);
106 current.y = GetColumnPosition(mNumberOfColumns, mColumnNumber, mItemSize, layoutSize.height);
107 current.z = -rowLayoutPositon * mDepthScale;
110 void Orientation0(Vector3& current, const PropertyInputContainer& inputs)
112 float layoutPosition = inputs[0]->GetFloat() + static_cast<float>(mItemId);
113 const Vector3& layoutSize = inputs[1]->GetVector3();
114 Orientation0(current, layoutPosition, layoutSize);
117 void Orientation90(Vector3& current, const PropertyInputContainer& inputs)
119 float layoutPosition = inputs[0]->GetFloat() + static_cast<float>(mItemId);
120 const Vector3& layoutSize = inputs[1]->GetVector3();
121 Orientation90(current, layoutPosition, layoutSize);
124 void Orientation180(Vector3& current, const PropertyInputContainer& inputs)
126 float layoutPosition = inputs[0]->GetFloat() + static_cast<float>(mItemId);
127 const Vector3& layoutSize = inputs[1]->GetVector3();
128 Orientation180(current, layoutPosition, layoutSize);
131 void Orientation270(Vector3& current, const PropertyInputContainer& inputs)
133 float layoutPosition = inputs[0]->GetFloat() + static_cast<float>(mItemId);
134 const Vector3& layoutSize = inputs[1]->GetVector3();
135 Orientation270(current, layoutPosition, layoutSize);
139 unsigned int mItemId;
140 unsigned int mNumberOfColumns;
141 unsigned int mColumnNumber;
146 struct DepthRotationConstraint
148 DepthRotationConstraint(Radian angleRadians, ControlOrientation::Type orientation)
149 : mTiltAngle(angleRadians),
152 if(orientation == ControlOrientation::Up)
156 else if(orientation == ControlOrientation::Left)
160 else if(orientation == ControlOrientation::Down)
164 else // orientation == ControlOrientation::Right
170 void operator()(Quaternion& current, const PropertyInputContainer& /* inputs */)
172 current = Quaternion(Radian(mMultiplier * Math::PI), Vector3::ZAXIS) * Quaternion(mTiltAngle, Vector3::XAXIS);
179 struct DepthColorConstraint
181 DepthColorConstraint(unsigned int itemId, unsigned int numberOfColumns, float numberOfRows, unsigned int columnNumber)
183 mNumberOfColumns(numberOfColumns),
184 mNumberOfRows(numberOfRows),
185 mColumnNumber(columnNumber)
189 void operator()(Vector4& current, const Dali::PropertyInputContainer& inputs)
191 float layoutPosition = inputs[0]->GetFloat() + static_cast<float>(mItemId);
192 float row = (layoutPosition - static_cast<float>(mColumnNumber)) / mNumberOfColumns;
194 float darkness(1.0f);
199 darkness = alpha = std::max(0.0f, 1.0f + row);
203 if(row > mNumberOfRows)
209 darkness = 1.0f - (1.0f * (row / mNumberOfRows));
212 if(row > (mNumberOfRows - 1.0f))
214 alpha = std::max(0.0f, 1.0f - (row - (mNumberOfRows - 1.0f)));
218 current.r = current.g = current.b = darkness;
222 unsigned int mItemId;
223 unsigned int mNumberOfColumns;
225 unsigned int mColumnNumber;
228 struct DepthVisibilityConstraint
230 DepthVisibilityConstraint(unsigned int itemId, unsigned int numberOfColumns, float numberOfRows, unsigned int columnNumber)
232 mNumberOfColumns(numberOfColumns),
233 mNumberOfRows(numberOfRows),
234 mColumnNumber(columnNumber)
238 void operator()(bool& current, const Dali::PropertyInputContainer& inputs)
240 float layoutPosition = inputs[0]->GetFloat() + static_cast<float>(mItemId);
241 float row = (layoutPosition - static_cast<float>(mColumnNumber)) / mNumberOfColumns;
243 current = (row > -1.0f) && (row < mNumberOfRows);
246 unsigned int mItemId;
247 unsigned int mNumberOfColumns;
249 unsigned int mColumnNumber;
252 } // unnamed namespace
260 struct DepthLayout::Impl
263 : mNumberOfColumns(DEFAULT_NUMBER_OF_COLUMNS),
264 mNumberOfRows(DEFAULT_NUMBER_OF_ROWS),
265 mRowSpacing(DEFAULT_ROW_SPACING),
266 mTiltAngle(DEFAULT_TILT_ANGLE),
267 mItemTiltAngle(DEFAULT_ITEM_TILT_ANGLE),
268 mScrollSpeedFactor(DEFAULT_SCROLL_SPEED_FACTOR),
269 mMaximumSwipeSpeed(DEFAULT_MAXIMUM_SWIPE_SPEED),
270 mItemFlickAnimationDuration(DEFAULT_ITEM_FLICK_ANIMATION_DURATION)
274 unsigned int mNumberOfColumns;
275 unsigned int mNumberOfRows;
280 Radian mItemTiltAngle;
282 float mScrollSpeedFactor;
283 float mMaximumSwipeSpeed;
284 float mItemFlickAnimationDuration;
287 DepthLayoutPtr DepthLayout::New()
289 return DepthLayoutPtr(new DepthLayout());
292 DepthLayout::~DepthLayout()
297 void DepthLayout::SetNumberOfColumns(unsigned int columns)
299 mImpl->mNumberOfColumns = columns;
302 unsigned int DepthLayout::GetNumberOfColumns() const
304 return mImpl->mNumberOfColumns;
307 void DepthLayout::SetNumberOfRows(unsigned int rows)
309 mImpl->mNumberOfRows = rows;
312 unsigned int DepthLayout::GetNumberOfRows() const
314 return mImpl->mNumberOfRows;
317 void DepthLayout::SetRowSpacing(float spacing)
319 mImpl->mRowSpacing = spacing;
322 float DepthLayout::GetRowSpacing() const
324 return mImpl->mRowSpacing;
327 void DepthLayout::SetTiltAngle(Degree angle)
329 mImpl->mTiltAngle = Degree(Clamp(angle, -45.0f, 45.0f));
332 Degree DepthLayout::GetTiltAngle() const
334 return Degree(mImpl->mTiltAngle);
337 void DepthLayout::SetItemTiltAngle(Degree angle)
339 mImpl->mItemTiltAngle = angle;
342 Degree DepthLayout::GetItemTiltAngle() const
344 return Degree(mImpl->mItemTiltAngle);
347 void DepthLayout::SetScrollSpeedFactor(float scrollSpeed)
349 mImpl->mScrollSpeedFactor = scrollSpeed;
352 void DepthLayout::SetMaximumSwipeSpeed(float speed)
354 mImpl->mMaximumSwipeSpeed = speed;
357 void DepthLayout::SetItemFlickAnimationDuration(float durationSeconds)
359 mImpl->mItemFlickAnimationDuration = durationSeconds;
362 float DepthLayout::GetScrollSpeedFactor() const
364 return mImpl->mScrollSpeedFactor;
367 float DepthLayout::GetMaximumSwipeSpeed() const
369 return mImpl->mMaximumSwipeSpeed;
372 float DepthLayout::GetItemFlickAnimationDuration() const
374 return mImpl->mItemFlickAnimationDuration;
377 float DepthLayout::GetMinimumLayoutPosition(unsigned int numberOfItems, Vector3 layoutSize) const
379 return static_cast<float>(mImpl->mNumberOfColumns) - static_cast<float>(numberOfItems);
382 float DepthLayout::GetClosestAnchorPosition(float layoutPosition) const
384 float rowIndex = static_cast<float>(round(layoutPosition / mImpl->mNumberOfColumns));
385 return rowIndex * static_cast<float>(mImpl->mNumberOfColumns);
388 float DepthLayout::GetItemScrollToPosition(unsigned int itemId) const
390 float rowIndex = static_cast<float>(itemId) / mImpl->mNumberOfColumns;
391 return -rowIndex * static_cast<float>(mImpl->mNumberOfColumns);
394 ItemRange DepthLayout::GetItemsWithinArea(float firstItemPosition, Vector3 layoutSize) const
396 float firstRow = -(firstItemPosition / mImpl->mNumberOfColumns);
397 float lastRow = firstRow + mImpl->mNumberOfRows * 0.5f;
399 unsigned int firstItem = static_cast<unsigned int>(std::max(0.0f, firstRow * mImpl->mNumberOfColumns));
400 unsigned int lastItem = static_cast<unsigned int>(std::max(0.0f, lastRow * mImpl->mNumberOfColumns));
402 return ItemRange(firstItem, lastItem + 1);
405 unsigned int DepthLayout::GetReserveItemCount(Vector3 layoutSize) const
407 float itemsWithinLayout = (layoutSize.depth * mImpl->mNumberOfColumns) / (cosf(mImpl->mTiltAngle) * mImpl->mRowSpacing);
409 return static_cast<unsigned int>(itemsWithinLayout);
412 void DepthLayout::GetDefaultItemSize(unsigned int itemId, const Vector3& layoutSize, Vector3& itemSize) const
415 itemSize.width = itemSize.height = itemSize.depth = (IsVertical(GetOrientation()) ? layoutSize.width : layoutSize.height) / static_cast<float>(mImpl->mNumberOfColumns + 1);
418 Degree DepthLayout::GetScrollDirection() const
420 Degree scrollDirection(0.0f);
421 ControlOrientation::Type orientation = GetOrientation();
423 if(orientation == ControlOrientation::Up)
425 scrollDirection = Degree(180.0f);
427 else if(orientation == ControlOrientation::Left)
429 scrollDirection = Degree(270.0f);
431 else if(orientation == ControlOrientation::Down)
433 scrollDirection = Degree(0.0f);
435 else // orientation == ControlOrientation::Right
437 scrollDirection = Degree(90.0f);
440 return scrollDirection;
443 void DepthLayout::ApplyConstraints(Actor& actor, const int itemId, const Vector3& layoutSize, const Actor& itemViewActor)
445 Dali::Toolkit::ItemView itemView = Dali::Toolkit::ItemView::DownCast(itemViewActor);
449 GetItemSize(itemId, layoutSize, itemSize);
451 ControlOrientation::Type orientation = GetOrientation();
453 // Position constraint
454 Constraint constraint;
455 DepthPositionConstraint depthPositionStruct(itemId,
456 mImpl->mNumberOfColumns,
457 itemId % mImpl->mNumberOfColumns,
459 -sinf(mImpl->mTiltAngle) * mImpl->mRowSpacing,
460 cosf(mImpl->mTiltAngle) * mImpl->mRowSpacing);
461 if(orientation == ControlOrientation::Up)
463 constraint = Constraint::New<Vector3>(actor, Actor::Property::POSITION, depthPositionStruct, &DepthPositionConstraint::Orientation0);
465 else if(orientation == ControlOrientation::Left)
467 constraint = Constraint::New<Vector3>(actor, Actor::Property::POSITION, depthPositionStruct, &DepthPositionConstraint::Orientation90);
469 else if(orientation == ControlOrientation::Down)
471 constraint = Constraint::New<Vector3>(actor, Actor::Property::POSITION, depthPositionStruct, &DepthPositionConstraint::Orientation180);
473 else // orientation == ControlOrientation::Right
475 constraint = Constraint::New<Vector3>(actor, Actor::Property::POSITION, depthPositionStruct, &DepthPositionConstraint::Orientation270);
477 constraint.AddSource(ParentSource(Toolkit::ItemView::Property::LAYOUT_POSITION));
478 constraint.AddSource(ParentSource(Actor::Property::SIZE));
481 // Rotation constraint
482 constraint = Constraint::New<Quaternion>(actor, Actor::Property::ORIENTATION, DepthRotationConstraint(mImpl->mItemTiltAngle, orientation));
486 constraint = Constraint::New<Vector4>(actor, Actor::Property::COLOR, DepthColorConstraint(itemId, mImpl->mNumberOfColumns, mImpl->mNumberOfRows * 0.5f, itemId % mImpl->mNumberOfColumns));
487 constraint.AddSource(ParentSource(Toolkit::ItemView::Property::LAYOUT_POSITION));
488 constraint.SetRemoveAction(Dali::Constraint::DISCARD);
491 // Visibility constraint
492 constraint = Constraint::New<bool>(actor, Actor::Property::VISIBLE, DepthVisibilityConstraint(itemId, mImpl->mNumberOfColumns, mImpl->mNumberOfRows * 0.5f, itemId % mImpl->mNumberOfColumns));
493 constraint.AddSource(ParentSource(Toolkit::ItemView::Property::LAYOUT_POSITION));
494 constraint.SetRemoveAction(Dali::Constraint::DISCARD);
499 void DepthLayout::SetDepthLayoutProperties(const Property::Map& properties)
501 // Set any properties specified for DepthLayout.
502 for(unsigned int idx = 0, mapCount = properties.Count(); idx < mapCount; ++idx)
504 KeyValuePair propertyPair = properties.GetKeyValue(idx);
505 switch(DefaultItemLayoutProperty::Property(propertyPair.first.indexKey))
507 case DefaultItemLayoutProperty::DEPTH_COLUMN_NUMBER:
509 SetNumberOfColumns(propertyPair.second.Get<int>());
512 case DefaultItemLayoutProperty::DEPTH_ROW_NUMBER:
514 SetNumberOfRows(propertyPair.second.Get<int>());
517 case DefaultItemLayoutProperty::DEPTH_ROW_SPACING:
519 SetRowSpacing(propertyPair.second.Get<float>());
522 case DefaultItemLayoutProperty::DEPTH_MAXIMUM_SWIPE_SPEED:
524 SetMaximumSwipeSpeed(propertyPair.second.Get<float>());
527 case DefaultItemLayoutProperty::DEPTH_SCROLL_SPEED_FACTOR:
529 SetScrollSpeedFactor(propertyPair.second.Get<float>());
532 case DefaultItemLayoutProperty::DEPTH_TILT_ANGLE:
534 SetTiltAngle(Degree(Radian(propertyPair.second.Get<float>())));
537 case DefaultItemLayoutProperty::DEPTH_ITEM_TILT_ANGLE:
539 SetItemTiltAngle(Degree(Radian(propertyPair.second.Get<float>())));
542 case DefaultItemLayoutProperty::DEPTH_ITEM_FLICK_ANIMATION_DURATION:
544 SetItemFlickAnimationDuration(propertyPair.second.Get<float>());
555 Vector3 DepthLayout::GetItemPosition(int itemID, float currentLayoutPosition, const Vector3& layoutSize) const
557 Vector3 itemPosition = Vector3::ZERO;
559 const float heightScale = -sinf(mImpl->mTiltAngle) * mImpl->mRowSpacing;
560 const float depthScale = cosf(mImpl->mTiltAngle) * mImpl->mRowSpacing;
563 GetItemSize(itemID, layoutSize, itemSize);
564 DepthPositionConstraint positionFunctor = DepthPositionConstraint(itemID,
565 mImpl->mNumberOfColumns,
566 itemID % mImpl->mNumberOfColumns,
570 ControlOrientation::Type orientation = GetOrientation();
571 if(orientation == ControlOrientation::Up)
573 positionFunctor.Orientation0(itemPosition, currentLayoutPosition + itemID, layoutSize);
575 else if(orientation == ControlOrientation::Left)
577 positionFunctor.Orientation90(itemPosition, currentLayoutPosition + itemID, layoutSize);
579 else if(orientation == ControlOrientation::Down)
581 positionFunctor.Orientation180(itemPosition, currentLayoutPosition + itemID, layoutSize);
583 else // orientation == ControlOrientation::Right
585 positionFunctor.Orientation270(itemPosition, currentLayoutPosition + itemID, layoutSize);
591 DepthLayout::DepthLayout()
597 float DepthLayout::GetClosestOnScreenLayoutPosition(int itemID, float currentLayoutPosition, const Vector3& layoutSize)
599 float scrollTo = currentLayoutPosition;
600 float row = (currentLayoutPosition + itemID - static_cast<float>(itemID % mImpl->mNumberOfColumns)) / mImpl->mNumberOfColumns;
602 // Check whether item is not within viewable area
605 scrollTo = GetItemScrollToPosition(itemID);
607 else if(row > mImpl->mNumberOfRows * 0.5f - 1.0f)
609 scrollTo = GetItemScrollToPosition(itemID) + (mImpl->mNumberOfRows - 1.0f) * 0.5f * mImpl->mNumberOfColumns;
615 int DepthLayout::GetNextFocusItemID(int itemID, int maxItems, Dali::Toolkit::Control::KeyboardFocus::Direction direction, bool loopEnabled)
619 case Toolkit::Control::KeyboardFocus::LEFT:
624 itemID = loopEnabled ? maxItems - 1 : 0;
628 case Toolkit::Control::KeyboardFocus::UP:
630 itemID += mImpl->mNumberOfColumns;
631 if(itemID >= maxItems)
633 itemID = loopEnabled ? 0 : itemID - mImpl->mNumberOfColumns;
637 case Toolkit::Control::KeyboardFocus::RIGHT:
640 if(itemID >= maxItems)
642 itemID = loopEnabled ? 0 : maxItems - 1;
646 case Toolkit::Control::KeyboardFocus::DOWN:
648 itemID -= mImpl->mNumberOfColumns;
651 itemID = loopEnabled ? itemID + maxItems : itemID + mImpl->mNumberOfColumns;
663 } // namespace Internal
665 } // namespace Toolkit