2 * Copyright (c) 2015 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/grid-layout.h>
23 #include <dali/public-api/animation/animation.h>
24 #include <dali/public-api/animation/constraint.h>
27 #include <dali-toolkit/public-api/controls/scrollable/item-view/item-view.h>
30 using namespace Dali::Toolkit;
32 namespace // unnamed namespace
35 const unsigned int DEFAULT_NUMBER_OF_COLUMNS = 4;
36 const float DEFAULT_TOP_MARGIN = 95.0f;
37 const float DEFAULT_BOTTOM_MARGIN = 20.0f;
38 const float DEFAULT_SIDE_MARGIN = 20.0f;
39 const float DEFAULT_COLUMN_SPACING = 20.0f;
40 const float DEFAULT_ROW_SPACING = 20.0f;
41 const float DEFAULT_SCROLL_SPEED_FACTOR = 0.03f;
42 const float DEFAULT_MAXIMUM_SWIPE_SPEED = 100.0f;
43 const float DEFAULT_ITEM_FLICK_ANIMATION_DURATION = 0.015f;
45 struct GridPositionConstraint
47 GridPositionConstraint(
49 const unsigned int columnIndex,
50 const unsigned int numberOfColumns,
51 const float rowSpacing,
52 const float columnSpacing,
53 const float topMargin,
54 const float sideMargin,
55 const Vector3& itemSize,
57 : mItemSize( itemSize ),
59 mColumnIndex( columnIndex ),
60 mNumberOfColumns( numberOfColumns ),
61 mRowSpacing( rowSpacing ),
62 mColumnSpacing( columnSpacing ),
63 mTopMargin( topMargin ),
64 mSideMargin( sideMargin ),
69 inline void Orientation0( Vector3& current, float layoutPosition, const Vector3& layoutSize )
71 current.x = mSideMargin + ( mColumnIndex * ( mItemSize.x + mColumnSpacing ) ) + mItemSize.x * 0.5f - layoutSize.x * 0.5f;
72 current.y = ( ( mItemSize.y + mRowSpacing ) * ( layoutPosition - mColumnIndex) ) / mNumberOfColumns - layoutSize.height * 0.5f + mItemSize.y * 0.5f + mTopMargin;
73 current.z = mColumnIndex * mZGap;
76 inline void Orientation90( Vector3& current, float layoutPosition, const Vector3& layoutSize )
78 current.x = ( ( mItemSize.y + mRowSpacing ) * ( layoutPosition - mColumnIndex ) ) / mNumberOfColumns - layoutSize.width * 0.5f + mItemSize.y * 0.5f + mTopMargin;
79 current.y = -( mSideMargin + ( mColumnIndex * ( mItemSize.x + mColumnSpacing ) ) + mItemSize.x * 0.5f - layoutSize.y * 0.5f );
80 current.z = mColumnIndex * mZGap;
83 inline void Orientation180( Vector3& current, float layoutPosition, const Vector3& layoutSize )
85 current.x = -(mSideMargin + (mColumnIndex * (mItemSize.x + mColumnSpacing)) + mItemSize.x * 0.5f - layoutSize.x * 0.5f);
86 current.y = -( ( ( mItemSize.y + mRowSpacing ) * ( layoutPosition - mColumnIndex ) ) / mNumberOfColumns - layoutSize.height * 0.5f + mItemSize.y * 0.5f + mTopMargin );
87 current.z = mColumnIndex * mZGap;
90 inline void Orientation270( Vector3& current, float layoutPosition, const Vector3& layoutSize )
92 current.x = -( ( ( mItemSize.y + mRowSpacing ) * ( layoutPosition - mColumnIndex ) ) / mNumberOfColumns - layoutSize.width * 0.5f + mItemSize.y * 0.5f + mTopMargin );
93 current.y = mSideMargin + ( mColumnIndex * ( mItemSize.x + mColumnSpacing ) ) + mItemSize.x * 0.5f - layoutSize.y * 0.5f;
94 current.z = mColumnIndex * mZGap;
97 void Orientation0( Vector3& current, const PropertyInputContainer& inputs )
99 float layoutPosition = inputs[0]->GetFloat() + static_cast< float >( mItemId );
100 const Vector3& layoutSize = inputs[1]->GetVector3();
101 Orientation0( current, layoutPosition, layoutSize );
104 void Orientation90( Vector3& current, const PropertyInputContainer& inputs )
106 float layoutPosition = inputs[0]->GetFloat() + static_cast< float >( mItemId );
107 const Vector3& layoutSize = inputs[1]->GetVector3();
108 Orientation90( current, layoutPosition, layoutSize );
111 void Orientation180( Vector3& current, const PropertyInputContainer& inputs )
113 float layoutPosition = inputs[0]->GetFloat() + static_cast< float >( mItemId );
114 const Vector3& layoutSize = inputs[1]->GetVector3();
115 Orientation180( current, layoutPosition, layoutSize );
118 void Orientation270( Vector3& current, const PropertyInputContainer& inputs )
120 float layoutPosition = inputs[0]->GetFloat() + static_cast< float >( mItemId );
121 const Vector3& layoutSize = inputs[1]->GetVector3();
122 Orientation270( current, layoutPosition, layoutSize );
128 unsigned int mItemId;
129 unsigned int mColumnIndex;
130 unsigned int mNumberOfColumns;
132 float mColumnSpacing;
138 void GridRotationConstraint0( Quaternion& current, const PropertyInputContainer& /* inputs */ )
140 current = Quaternion( Radian( 0.0f ), Vector3::ZAXIS );
143 void GridRotationConstraint90( Quaternion& current, const PropertyInputContainer& /* inputs */ )
145 current = Quaternion( Radian( 1.5f * Math::PI ), Vector3::ZAXIS );
148 void GridRotationConstraint180( Quaternion& current, const PropertyInputContainer& /* inputs */ )
150 current = Quaternion( Radian( Math::PI ), Vector3::ZAXIS );
153 void GridRotationConstraint270( Quaternion& current, const PropertyInputContainer& /* inputs */ )
155 current = Quaternion( Radian( 0.5f * Math::PI ), Vector3::ZAXIS );
158 void GridColorConstraint( Vector4& current, const PropertyInputContainer& /* inputs */ )
160 current.r = current.g = current.b = 1.0f;
163 struct GridVisibilityConstraint
165 GridVisibilityConstraint(
167 const unsigned int columnIndex,
168 const unsigned int numberOfColumns,
169 const float rowSpacing,
170 const float columnSpacing,
171 const float sideMargin,
172 const Vector3& itemSize )
173 : mItemSize( itemSize ),
175 mColumnIndex( columnIndex ),
176 mNumberOfColumns( numberOfColumns ),
177 mRowSpacing( rowSpacing ),
178 mColumnSpacing( columnSpacing ),
179 mSideMargin( sideMargin )
183 void Portrait( bool& current, const PropertyInputContainer& inputs )
185 float layoutPosition = inputs[0]->GetFloat() + static_cast< float >( mItemId );
186 const Vector3& layoutSize = inputs[1]->GetVector3();
188 float row = ( layoutPosition - static_cast< float >( mColumnIndex ) ) / mNumberOfColumns;
189 int rowsPerPage = ceil( layoutSize.height / ( mItemSize.y + mRowSpacing ) );
191 current = ( row > -2.0f ) && ( row < rowsPerPage );
194 void Landscape( bool& current, const PropertyInputContainer& inputs )
196 float layoutPosition = inputs[0]->GetFloat() + static_cast< float >( mItemId );
197 const Vector3& layoutSize = inputs[1]->GetVector3();
199 float row = ( layoutPosition - static_cast< float >( mColumnIndex ) ) / mNumberOfColumns;
200 int rowsPerPage = ceil( layoutSize.width / ( mItemSize.y + mRowSpacing ) );
202 current = ( row > -2.0f ) && ( row < rowsPerPage );
208 unsigned int mItemId;
209 unsigned int mColumnIndex;
210 unsigned int mNumberOfColumns;
212 float mColumnSpacing;
216 } // unnamed namespace
227 struct GridLayout::Impl
230 : mNumberOfColumns(DEFAULT_NUMBER_OF_COLUMNS),
231 mRowSpacing(DEFAULT_ROW_SPACING),
232 mColumnSpacing(DEFAULT_COLUMN_SPACING),
233 mTopMargin(DEFAULT_TOP_MARGIN),
234 mBottomMargin(DEFAULT_BOTTOM_MARGIN),
235 mSideMargin(DEFAULT_SIDE_MARGIN),
237 mScrollSpeedFactor(DEFAULT_SCROLL_SPEED_FACTOR),
238 mMaximumSwipeSpeed(DEFAULT_MAXIMUM_SWIPE_SPEED),
239 mItemFlickAnimationDuration(DEFAULT_ITEM_FLICK_ANIMATION_DURATION)
243 unsigned int mNumberOfColumns;
245 float mColumnSpacing;
251 float mScrollSpeedFactor;
252 float mMaximumSwipeSpeed;
253 float mItemFlickAnimationDuration;
256 GridLayoutPtr GridLayout::New()
258 return GridLayoutPtr(new GridLayout());
261 GridLayout::~GridLayout()
266 void GridLayout::SetNumberOfColumns(unsigned int columns)
268 mImpl->mNumberOfColumns = columns;
271 unsigned int GridLayout::GetNumberOfColumns() const
273 return mImpl->mNumberOfColumns;
276 void GridLayout::SetRowSpacing(float spacing)
278 mImpl->mRowSpacing = spacing;
281 float GridLayout::GetRowSpacing() const
283 return mImpl->mRowSpacing;
286 void GridLayout::SetColumnSpacing(float spacing)
288 mImpl->mColumnSpacing = spacing;
291 float GridLayout::GetColumnSpacing() const
293 return mImpl->mColumnSpacing;
296 void GridLayout::SetTopMargin(float margin)
298 mImpl->mTopMargin = margin;
301 float GridLayout::GetTopMargin() const
303 return mImpl->mTopMargin;
306 void GridLayout::SetBottomMargin(float margin)
308 mImpl->mBottomMargin = margin;
311 float GridLayout::GetBottomMargin() const
313 return mImpl->mBottomMargin;
316 void GridLayout::SetSideMargin(float margin)
318 mImpl->mSideMargin = margin;
321 float GridLayout::GetSideMargin() const
323 return mImpl->mSideMargin;
326 void GridLayout::SetZGap(float gap)
331 float GridLayout::GetZGap() const
336 void GridLayout::SetScrollSpeedFactor(float scrollSpeed)
338 mImpl->mScrollSpeedFactor = scrollSpeed;
341 void GridLayout::SetMaximumSwipeSpeed(float speed)
343 mImpl->mMaximumSwipeSpeed = speed;
346 void GridLayout::SetItemFlickAnimationDuration(float durationSeconds)
348 mImpl->mItemFlickAnimationDuration = durationSeconds;
351 float GridLayout::GetScrollSpeedFactor() const
353 return mImpl->mScrollSpeedFactor;
356 float GridLayout::GetMaximumSwipeSpeed() const
358 return mImpl->mMaximumSwipeSpeed;
361 float GridLayout::GetItemFlickAnimationDuration() const
363 return mImpl->mItemFlickAnimationDuration;
366 float GridLayout::GetMinimumLayoutPosition(unsigned int numberOfItems, Vector3 layoutSize) const
368 float layoutHeight = IsHorizontal( GetOrientation() ) ? layoutSize.width : layoutSize.height;
371 GetItemSize( 0, layoutSize, itemSize );
373 unsigned int itemsLastRow = numberOfItems % mImpl->mNumberOfColumns;
374 if (itemsLastRow == 0)
376 itemsLastRow = mImpl->mNumberOfColumns;
379 float rowsLastPage = (layoutHeight - mImpl->mBottomMargin - mImpl->mTopMargin + mImpl->mRowSpacing) / (itemSize.y + mImpl->mRowSpacing);
380 float itemsLastPage = (rowsLastPage - 1.0f) * static_cast<float>(mImpl->mNumberOfColumns) + static_cast<float>(itemsLastRow);
382 return itemsLastPage - static_cast<float>(numberOfItems);
385 float GridLayout::GetClosestAnchorPosition(float layoutPosition) const
387 float rowIndex = static_cast<float>(round(layoutPosition / mImpl->mNumberOfColumns));
388 return rowIndex * static_cast<float>(mImpl->mNumberOfColumns);
391 float GridLayout::GetItemScrollToPosition(unsigned int itemId) const
393 float rowIndex = static_cast<float>(itemId / mImpl->mNumberOfColumns);
394 return -rowIndex * static_cast<float>(mImpl->mNumberOfColumns);
397 ItemRange GridLayout::GetItemsWithinArea(float firstItemPosition, Vector3 layoutSize) const
399 float layoutHeight = IsHorizontal( GetOrientation() ) ? layoutSize.width : layoutSize.height;
402 GetItemSize( 0, layoutSize, itemSize );
404 int itemsPerPage = mImpl->mNumberOfColumns * ceil(layoutHeight / (itemSize.y + mImpl->mRowSpacing));
405 int firstVisibleItem = -(static_cast<int>(firstItemPosition / mImpl->mNumberOfColumns)) * mImpl->mNumberOfColumns;
407 int firstItemIndex = std::max(0, firstVisibleItem - static_cast<int>(mImpl->mNumberOfColumns));
408 int lastItemIndex = std::max(0, firstVisibleItem + itemsPerPage);
410 return ItemRange(firstItemIndex, lastItemIndex);
413 float GridLayout::GetClosestOnScreenLayoutPosition(int itemID, float currentLayoutPosition, const Vector3& layoutSize)
415 Vector3 itemPosition = GetItemPosition( itemID, currentLayoutPosition, layoutSize );
417 ControlOrientation::Type orientation = GetOrientation();
419 GetItemSize(itemID, layoutSize, itemSize);
420 Vector3 onScreenArea = ( layoutSize - ( IsVertical( orientation ) ? itemSize : Vector3( itemSize.y, itemSize.x, itemSize.z ) ) ) * 0.5f;
421 if (itemPosition.x < -onScreenArea.x
422 || itemPosition.x > onScreenArea.x
423 || itemPosition.y < -onScreenArea.y
424 || itemPosition.y > onScreenArea.y)
426 // item not within viewable area
427 float rowHeight = itemSize.y + mImpl->mRowSpacing;
428 Vector3 firstItemPosition = GetItemPosition( itemID, 0.0f, layoutSize );
430 switch( orientation )
432 case ControlOrientation::Up:
434 if(itemPosition.y > onScreenArea.y)
436 offset = ((layoutSize.y - rowHeight) * 0.5f) - firstItemPosition.y;
440 offset = ((-layoutSize.y + rowHeight) * 0.5f) - firstItemPosition.y;
444 case ControlOrientation::Down:
446 if(itemPosition.y < -onScreenArea.y)
448 offset = ((layoutSize.y - rowHeight) * 0.5f) - firstItemPosition.y;
452 offset = ((-layoutSize.y + rowHeight) * 0.5f) - firstItemPosition.y;
456 case ControlOrientation::Left:
458 if(itemPosition.x > onScreenArea.x)
460 offset = ((layoutSize.x - rowHeight) * 0.5f) - firstItemPosition.x;
464 offset = ((-layoutSize.x + rowHeight) * 0.5f) - firstItemPosition.x;
468 case ControlOrientation::Right:
470 if(itemPosition.x < -onScreenArea.x)
472 offset = ((layoutSize.x - rowHeight) * 0.5f) - firstItemPosition.x;
476 offset = ((-layoutSize.x + rowHeight) * 0.5f) - firstItemPosition.x;
481 // work out number of rows from first item position to an item aligned to bottom of screen
482 float rowDiff = offset / rowHeight;
483 float layoutPositionOffset = rowDiff * mImpl->mNumberOfColumns;
484 float scrollTo = GetItemScrollToPosition(itemID) + layoutPositionOffset;
487 return currentLayoutPosition;
490 unsigned int GridLayout::GetReserveItemCount(Vector3 layoutSize) const
492 float layoutHeight = IsHorizontal( GetOrientation() ) ? layoutSize.width : layoutSize.height;
495 GetItemSize( 0, layoutSize, itemSize );
496 int itemsPerPage = mImpl->mNumberOfColumns * ceil(layoutHeight / (itemSize.y + mImpl->mRowSpacing));
500 void GridLayout::GetDefaultItemSize( unsigned int itemId, const Vector3& layoutSize, Vector3& itemSize ) const
502 float layoutWidth = IsHorizontal( GetOrientation() ) ? layoutSize.height : layoutSize.width;
503 itemSize.width = ( layoutWidth - mImpl->mSideMargin * 2.0f - mImpl->mColumnSpacing * static_cast<float>( mImpl->mNumberOfColumns - 1 ) ) / static_cast<float>( mImpl->mNumberOfColumns );
506 itemSize.height = itemSize.depth = itemSize.width * 0.75f;
509 Degree GridLayout::GetScrollDirection() const
511 Degree scrollDirection(0.0f);
512 ControlOrientation::Type orientation = GetOrientation();
514 if ( orientation == ControlOrientation::Up )
516 scrollDirection = Degree( 0.0f );
518 else if ( orientation == ControlOrientation::Left )
520 scrollDirection = Degree( 90.0f );
522 else if ( orientation == ControlOrientation::Down )
524 scrollDirection = Degree( 180.0f );
526 else // orientation == ControlOrientation::Right
528 scrollDirection = Degree( 270.0f );
531 return scrollDirection;
534 void GridLayout::ApplyConstraints( Actor& actor, const int itemId, const Vector3& layoutSize, const Actor& itemViewActor )
536 // This just implements the default behaviour of constraint application.
537 // Custom layouts can override this function to apply their custom constraints.
538 Dali::Toolkit::ItemView itemView = Dali::Toolkit::ItemView::DownCast( itemViewActor );
542 GetItemSize( itemId, layoutSize, itemSize );
543 const unsigned int columnIndex = itemId % mImpl->mNumberOfColumns;
544 const ControlOrientation::Type orientation = GetOrientation();
546 // Position constraint
547 GridPositionConstraint positionConstraint( itemId,
549 mImpl->mNumberOfColumns,
551 mImpl->mColumnSpacing,
556 Constraint constraint;
557 if ( orientation == ControlOrientation::Up )
559 constraint = Constraint::New< Vector3 >( actor, Actor::Property::POSITION, positionConstraint, &GridPositionConstraint::Orientation0 );
561 else if ( orientation == ControlOrientation::Left )
563 constraint = Constraint::New< Vector3 >( actor, Actor::Property::POSITION, positionConstraint, &GridPositionConstraint::Orientation90 );
565 else if ( orientation == ControlOrientation::Down )
567 constraint = Constraint::New< Vector3 >( actor, Actor::Property::POSITION, positionConstraint, &GridPositionConstraint::Orientation180 );
569 else // orientation == ControlOrientation::Right
571 constraint = Constraint::New< Vector3 >( actor, Actor::Property::POSITION, positionConstraint, &GridPositionConstraint::Orientation270 );
573 constraint.AddSource( ParentSource( Toolkit::ItemView::Property::LAYOUT_POSITION ) );
574 constraint.AddSource( ParentSource( Actor::Property::SIZE ) );
577 // Rotation constraint
578 if ( orientation == ControlOrientation::Up )
580 constraint = Constraint::New< Quaternion >( actor, Actor::Property::ORIENTATION, &GridRotationConstraint0 );
582 else if ( orientation == ControlOrientation::Left )
584 constraint = Constraint::New< Quaternion >( actor, Actor::Property::ORIENTATION, &GridRotationConstraint90 );
586 else if ( orientation == ControlOrientation::Down )
588 constraint = Constraint::New< Quaternion >( actor, Actor::Property::ORIENTATION, &GridRotationConstraint180 );
590 else // orientation == ControlOrientation::Right
592 constraint = Constraint::New< Quaternion >( actor, Actor::Property::ORIENTATION, &GridRotationConstraint270 );
597 constraint = Constraint::New< Vector4 >( actor, Actor::Property::COLOR, &GridColorConstraint );
598 constraint.SetRemoveAction( Dali::Constraint::Discard );
601 // Visibility constraint
602 GridVisibilityConstraint visibilityConstraint( itemId,
604 mImpl->mNumberOfColumns,
606 mImpl->mColumnSpacing,
609 if ( IsVertical( orientation ) )
611 constraint = Constraint::New<bool>( actor, Actor::Property::VISIBLE, visibilityConstraint, &GridVisibilityConstraint::Portrait );
615 constraint = Constraint::New<bool>( actor, Actor::Property::VISIBLE, visibilityConstraint, &GridVisibilityConstraint::Landscape );
617 constraint.AddSource( ParentSource( Toolkit::ItemView::Property::LAYOUT_POSITION ) );
618 constraint.AddSource( ParentSource( Actor::Property::SIZE ) );
619 constraint.SetRemoveAction( Dali::Constraint::Discard );
624 Vector3 GridLayout::GetItemPosition(int itemID, float currentLayoutPosition, const Vector3& layoutSize) const
626 Vector3 itemPosition = Vector3::ZERO;
627 const unsigned int columnIndex = itemID % mImpl->mNumberOfColumns;
628 const ControlOrientation::Type orientation = GetOrientation();
630 GetItemSize( itemID, layoutSize, itemSize );
632 GridPositionConstraint positionConstraintStruct( itemID,
634 mImpl->mNumberOfColumns,
636 mImpl->mColumnSpacing,
642 if ( orientation == ControlOrientation::Up )
644 positionConstraintStruct.Orientation0( itemPosition, currentLayoutPosition + itemID, layoutSize );
646 else if ( orientation == ControlOrientation::Left )
648 positionConstraintStruct.Orientation90( itemPosition, currentLayoutPosition + itemID, layoutSize );
650 else if ( orientation == ControlOrientation::Down )
652 positionConstraintStruct.Orientation180( itemPosition, currentLayoutPosition + itemID, layoutSize );
654 else // orientation == ControlOrientation::Right
656 positionConstraintStruct.Orientation270( itemPosition, currentLayoutPosition + itemID, layoutSize );
662 int GridLayout::GetNextFocusItemID(int itemID, int maxItems, Dali::Toolkit::Control::KeyboardFocus::Direction direction, bool loopEnabled)
666 case Toolkit::Control::KeyboardFocus::LEFT:
671 itemID = loopEnabled ? maxItems - 1 : 0;
675 case Toolkit::Control::KeyboardFocus::UP:
677 itemID -= mImpl->mNumberOfColumns;
680 itemID = loopEnabled ? itemID + maxItems : itemID + mImpl->mNumberOfColumns;
684 case Toolkit::Control::KeyboardFocus::RIGHT:
687 if( itemID >= maxItems )
689 itemID = loopEnabled ? 0 : maxItems - 1;
693 case Toolkit::Control::KeyboardFocus::DOWN:
695 itemID += mImpl->mNumberOfColumns;
696 if( itemID >= maxItems )
698 itemID = loopEnabled ? 0 : itemID - mImpl->mNumberOfColumns;
706 GridLayout::GridLayout()
712 } // namespace Internal
714 } // namespace Toolkit