1 /*
2  * Copyright (c) 2021 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "core/components/list/grid_layout_manager.h"
17 
18 #include "core/components/scroll/render_multi_child_scroll.h"
19 
20 namespace {
21 
22 #ifndef WEARABLE_PRODUCT
23 constexpr int32_t MIN_COUNT_OF_ADJUST_SLIP = 50;
24 constexpr double SLIP_FACTOR = 250.0;
25 #else
26 constexpr int32_t MIN_COUNT_OF_ADJUST_SLIP = 30;
27 constexpr double SLIP_FACTOR = 250.0;
28 #endif
29 
30 } // namespace
31 
32 namespace OHOS::Ace {
33 
GridLayoutManager(RenderList & renderList)34 GridLayoutManager::GridLayoutManager(RenderList& renderList) : renderList_(renderList) {}
35 
Update()36 void GridLayoutManager::Update()
37 {
38     direction_ = renderList_.GetDirection();
39     maxCount_ = renderList_.GetMaxCount();
40     cachedCount_ = renderList_.GetCachedCount();
41     gridWidth_ = renderList_.GetWidth();
42     gridHeight_ = renderList_.GetHeight();
43     columnCount_ = renderList_.GetColumnCount();
44     columnExtent_ = renderList_.GetColumnExtent();
45     itemExtent_ = renderList_.GetItemExtent();
46     if (direction_ == FlexDirection::COLUMN || direction_ == FlexDirection::COLUMN_REVERSE) {
47         isVertical_ = true;
48     } else {
49         isVertical_ = false;
50     }
51     updateFlag_ = true;
52     beginIndex_ = renderList_.GetBeginIndex();
53     endIndex_ = renderList_.GetEndIndex();
54     repeatedLength_ = renderList_.GetRepeatedLength();
55     indexOffset_ = renderList_.GetIndexOffset();
56     length_ = renderList_.GetLength();
57     rightToLeft_ = renderList_.GetRightToLeft();
58     crossAxisAlign_ = renderList_.GetFlexAlign();
59     renderList_.MarkNeedLayout();
60 }
61 
MakeInnerLayoutParam(int32_t columnSpan) const62 LayoutParam GridLayoutManager::MakeInnerLayoutParam(int32_t columnSpan) const
63 {
64     LayoutParam innerLayout;
65     if (itemExtent_.IsValid()) {
66         double extent = itemExtent_.Value();
67         if (itemExtent_.Unit() == DimensionUnit::PERCENT) {
68             extent *= renderList_.GetMainSize(viewPort_);
69         } else {
70             extent = renderList_.NormalizeToPx(itemExtent_);
71         }
72         if (crossAxisAlign_ == FlexAlign::STRETCH) {
73             if (isVertical_) {
74                 innerLayout.SetMinSize(Size(gridLen_ * columnSpan, extent));
75                 innerLayout.SetMaxSize(Size(gridLen_ * columnSpan, extent));
76             } else {
77                 innerLayout.SetMinSize(Size(extent, gridLen_ * columnSpan));
78                 innerLayout.SetMaxSize(Size(extent, gridLen_ * columnSpan));
79             }
80         } else {
81             if (isVertical_) {
82                 innerLayout.SetMaxSize(Size(gridLen_ * columnSpan, extent));
83             } else {
84                 innerLayout.SetMaxSize(Size(extent, gridLen_ * columnSpan));
85             }
86         }
87     } else {
88         if (crossAxisAlign_ == FlexAlign::STRETCH) {
89             if (isVertical_) {
90                 innerLayout.SetMinSize(Size(gridLen_ * columnSpan, innerLayout.GetMinSize().Height()));
91                 innerLayout.SetMaxSize(Size(gridLen_ * columnSpan, innerLayout.GetMaxSize().Height()));
92             } else {
93                 innerLayout.SetMinSize(Size(innerLayout.GetMinSize().Width(), gridLen_ * columnSpan));
94                 innerLayout.SetMaxSize(Size(innerLayout.GetMaxSize().Width(), gridLen_ * columnSpan));
95             }
96         } else {
97             if (isVertical_) {
98                 innerLayout.SetMaxSize(Size(gridLen_ * columnSpan, innerLayout.GetMaxSize().Height()));
99             } else {
100                 innerLayout.SetMaxSize(Size(innerLayout.GetMaxSize().Width(), gridLen_ * columnSpan));
101             }
102         }
103     }
104     return innerLayout;
105 }
106 
SetChildPosition(const RefPtr<RenderNode> & child,double mainSize,int32_t gridPos,int32_t columnSpan)107 void GridLayoutManager::SetChildPosition(
108     const RefPtr<RenderNode>& child, double mainSize, int32_t gridPos, int32_t columnSpan)
109 {
110     double crossAxis = 0.0;
111     FlexAlign align = crossAxisAlign_;
112     if (rightToLeft_) {
113         if (align == FlexAlign::FLEX_END) {
114             align = FlexAlign::FLEX_START;
115         } else if (align == FlexAlign::FLEX_START) {
116             align = FlexAlign::FLEX_END;
117         }
118     }
119     switch (align) {
120         case FlexAlign::FLEX_END:
121             crossAxis = gridLen_ * columnSpan - renderList_.GetCrossSize(child->GetLayoutSize());
122             break;
123         case FlexAlign::CENTER:
124             crossAxis = (gridLen_ * columnSpan - renderList_.GetCrossSize(child->GetLayoutSize())) / 2.0;
125             break;
126         case FlexAlign::STRETCH:
127         case FlexAlign::FLEX_START:
128         default:
129             break;
130     }
131     crossAxis += gridPos * gridLen_;
132     if (isVertical_) {
133         if (rightToLeft_) {
134             crossAxis = crossSize_ - crossAxis - gridLen_ * columnSpan;
135         }
136         if (IsColReverse()) {
137             mainSize = mainSize_ - renderList_.GetMainSize(child->GetLayoutSize()) - mainSize;
138         }
139         child->SetPosition(Offset(crossAxis, mainSize) + position_);
140     } else {
141         if (rightToLeft_ || IsRowReverse()) {
142             mainSize = mainSize_ - renderList_.GetMainSize(child->GetLayoutSize()) - mainSize;
143         }
144         child->SetPosition(Offset(mainSize, crossAxis) + position_);
145     }
146 }
147 
CalculateCachedRange(int32_t viewBegin,int32_t viewEnd,int32_t cachedCount,int32_t & cachedBegin,int32_t & cachedEnd)148 void GridLayoutManager::CalculateCachedRange(
149     int32_t viewBegin, int32_t viewEnd, int32_t cachedCount, int32_t& cachedBegin, int32_t& cachedEnd)
150 {
151     cachedBegin = (viewBegin - cachedCount > 0) ? (viewBegin - cachedCount) : 0;
152     if (length_ != LIST_LENGTH_INFINITE) {
153         cachedEnd = (viewEnd + cachedCount > length_) ? length_ : viewEnd + cachedCount;
154     } else {
155         cachedEnd = viewEnd + cachedCount;
156     }
157 }
158 
RequestMoreItemsIfNeeded(int32_t viewBegin,int32_t viewEnd)159 void GridLayoutManager::RequestMoreItemsIfNeeded(int32_t viewBegin, int32_t viewEnd)
160 {
161     int32_t cachedBegin;
162     int32_t cachedEnd;
163     if (beginIndex_ == LIST_PARAM_INVAID || endIndex_ == LIST_PARAM_INVAID) {
164         return;
165     }
166 
167     if (viewBegin > viewEnd) {
168         std::swap(viewBegin, viewEnd);
169     }
170 
171     CalculateCachedRange(viewBegin, viewEnd, cachedCount_, cachedBegin, cachedEnd);
172     if (CalculateRepeatedIndex(cachedBegin) < beginIndex_ || CalculateRepeatedIndex(cachedEnd) > endIndex_) {
173         int32_t requestBegin;
174         int32_t requestEnd;
175         CalculateCachedRange(viewBegin, viewEnd, 2 * cachedCount_, requestBegin, requestEnd); // 2: double cache
176         renderList_.RequestMoreItems(CalculateRepeatedIndex(requestBegin), CalculateRepeatedIndex(requestEnd));
177     }
178 }
179 
CalculateAxisSize()180 void GridLayoutManager::CalculateAxisSize()
181 {
182     // Not first time layout after update, no need to initial.
183     if (!updateFlag_ && !renderList_.IsLayoutChanged()) {
184         return;
185     }
186 
187     if (isVertical_) {
188         mainSize_ = ((gridHeight_ > 0.0) && (gridHeight_ < renderList_.GetLayoutParam().GetMaxSize().Height()))
189                         ? gridHeight_
190                         : renderList_.GetLayoutParam().GetMaxSize().Height();
191         crossSize_ = ((gridWidth_ > 0.0) && (gridWidth_ < renderList_.GetLayoutParam().GetMaxSize().Width()))
192                          ? gridWidth_
193                          : renderList_.GetLayoutParam().GetMaxSize().Width();
194     } else {
195         mainSize_ = ((gridWidth_ > 0.0) && (gridWidth_ < renderList_.GetLayoutParam().GetMaxSize().Width()))
196                         ? gridWidth_
197                         : renderList_.GetLayoutParam().GetMaxSize().Width();
198         crossSize_ = ((gridHeight_ > 0.0) && (gridHeight_ < renderList_.GetLayoutParam().GetMaxSize().Height()))
199                          ? gridHeight_
200                          : renderList_.GetLayoutParam().GetMaxSize().Height();
201     }
202 
203     // Initialize the columnCount, default is 1
204     if (columnCount_ == 0) {
205         if (columnExtent_ > 0) {
206             columnCount_ = crossSize_ / columnExtent_;
207             if (columnCount_ * columnExtent_ < crossSize_) {
208                 ++columnCount_;
209             }
210         } else {
211             columnCount_ = 1;
212         }
213     }
214 
215     // Initialize the column length
216     if (NearEqual(crossSize_, Size::INFINITE_SIZE)) {
217         crossSize_ = renderList_.GetCrossSize(viewPort_);
218     }
219     gridLen_ = crossSize_ / columnCount_;
220 }
221 
RefreshLayout()222 void GridLayoutManager::RefreshLayout()
223 {
224     if (!needRefresh_) {
225         return;
226     }
227     needRefresh_ = false;
228     auto items = renderList_.GetItems();
229     auto iter = items.begin();
230     if (iter != items.end()) {
231         auto firstItem = RenderListItem::GetRenderListItem(iter->second);
232         if (firstItem && firstItem->GetOperation() != LIST_ITEM_OP_NONE) {
233             LOGI("First item changed, recycle all child and layout again.");
234             renderList_.RecycleAllChild();
235             renderList_.RefreshOffset(0.0);
236             itemGrid_.clear();
237             itemPosition_.clear();
238             itemGroupsExpand_.clear();
239             itemGroupsFocusIndex_.clear();
240         } else {
241             std::vector<int32_t> needRemoveItems;
242             std::map<int32_t, RefPtr<RenderNode>> newItems;
243             int32_t rmCount = 0;
244             while (iter != items.end()) {
245                 auto item = RenderListItem::GetRenderListItem(iter->second);
246                 if (item) {
247                     if (item->GetOperation() == LIST_ITEM_OP_REMOVE) {
248                         LOGI("This item[%{public}d] removed, notify element to recycle.", item->GetIndex());
249                         needRemoveItems.emplace_back(iter->first);
250                         itemGrid_.erase(item->GetIndex());
251                         itemPosition_.erase(item->GetIndex());
252                         itemGroupsExpand_.erase(item->GetIndex());
253                         itemGroupsFocusIndex_.erase(item->GetIndex());
254                         ++rmCount;
255                     } else {
256                         if (rmCount > 0) {
257                             itemGrid_.erase(item->GetIndex());
258                             itemPosition_.erase(item->GetIndex());
259                             itemGroupsExpand_.erase(item->GetIndex());
260                             itemGroupsFocusIndex_.erase(item->GetIndex());
261                         }
262                         item->SetIndex(item->GetIndex() - rmCount);
263                         newItems.emplace(std::make_pair(item->GetIndex(), iter->second));
264                     }
265                 }
266                 iter++;
267             }
268             renderList_.RecycleByItems(needRemoveItems);
269             renderList_.ResetItems(newItems);
270         }
271     }
272     renderList_.OnRefreshed();
273 }
274 
PerformLayout()275 void GridLayoutManager::PerformLayout()
276 {
277     CalculateAxisSize();
278     RefreshLayout();
279     int32_t itemIndex = GetIndexByPosition(head_);
280     int32_t firstIndex = itemIndex;
281     renderList_.RecycleHead(itemIndex - 1); // Recycle head items.
282     double curMainSize = GetItemPosition(itemIndex);
283     auto itemChild = renderList_.GetChildByIndex(itemIndex);
284     while (!itemChild && curMainSize < tail_ && CheckItemPosition(itemIndex)) {
285         itemChild = renderList_.GetChildByIndex(itemIndex);
286         curMainSize = GetItemPosition(itemIndex);
287         ++itemIndex;
288     }
289     int32_t curGrid = GetItemGrid(itemIndex);
290     double childMainSize = 0.0;
291     while (itemChild) {
292         auto listItem = RenderListItem::GetRenderListItem(itemChild);
293         if (!listItem) {
294             LOGE("Get render list item failed index: %{public}d", itemIndex);
295             return;
296         }
297         int32_t span = std::min(listItem->GetColumnSpan(), columnCount_ - curGrid);
298         LayoutParam innerLayout = MakeInnerLayoutParam(span);
299         itemChild->Layout(innerLayout);
300         SetChildPosition(itemChild, curMainSize, curGrid, span);
301         itemGrid_[itemIndex] = curGrid;
302         itemPosition_[itemIndex] = curMainSize;
303         childMainSize = std::max(renderList_.GetMainSize(itemChild->GetLayoutSize()), childMainSize);
304         curGrid += span;
305         if (curGrid >= columnCount_) {
306             curGrid = 0;
307             curMainSize += childMainSize;
308             childMainSize = 0.0;
309         }
310         if (curMainSize >= tail_) {
311             break;
312         }
313         itemChild = renderList_.GetChildByIndex(++itemIndex);
314     }
315     curMainSize += childMainSize;
316     RequestMoreItemsIfNeeded(firstIndex, itemIndex);
317     renderList_.RecycleTail(itemIndex + 1); // Recycle tail items.
318     Size layoutSize = renderList_.MakeValue<Size>(curMainSize, renderList_.GetCrossSize(viewPort_));
319     renderList_.SetLayoutSize(layoutSize);
320     ShowItemFocusAnimation();
321     updateFlag_ = false;
322     if (itemCountOfPage_ != (itemIndex - firstIndex)) {
323         itemCountOfPage_ = itemIndex - firstIndex;
324 
325         if (itemCountOfPage_ > MIN_COUNT_OF_ADJUST_SLIP) {
326             slipFactor_ = itemCountOfPage_ * SLIP_FACTOR;
327         }
328     }
329 }
330 
focusMove(KeyDirection direction)331 int32_t GridLayoutManager::focusMove(KeyDirection direction)
332 {
333     int32_t index = focusMove_;
334     int32_t curGrid = 0;
335     int32_t curSpan = 1;
336     auto curFocus = renderList_.GetItemByIndex(index);
337     if (curFocus) {
338         curGrid = GetItemGrid(index);
339         curSpan = curFocus->GetColumnSpan();
340     }
341     switch (direction) {
342         case KeyDirection::UP:
343         case KeyDirection::DOWN: {
344             auto next = renderList_.GetItemByIndex(direction == KeyDirection::UP ? --index : ++index);
345             while (next) {
346                 int32_t nextGrid = GetItemGrid(index);
347                 int32_t nextSpan = next->GetColumnSpan();
348                 if (nextGrid == curGrid || (nextGrid < curGrid && nextGrid + nextSpan > curGrid)) {
349                     return index;
350                 }
351                 next = renderList_.GetItemByIndex(direction == KeyDirection::UP ? --index : ++index);
352             }
353             break;
354         }
355         case KeyDirection::LEFT:
356             if (curGrid != 0) {
357                 auto next = renderList_.GetItemByIndex(--index);
358                 if (next && GetItemGrid(index) < curGrid) {
359                     return index;
360                 }
361             }
362             break;
363         case KeyDirection::RIGHT:
364             if (curGrid + curSpan < columnCount_) {
365                 auto next = renderList_.GetItemByIndex(++index);
366                 if (next && GetItemGrid(index) > curGrid) {
367                     return index;
368                 }
369             }
370             break;
371         default:
372             break;
373     }
374     return -1;
375 }
376 
LayoutToItem(int32_t toIndex)377 void GridLayoutManager::LayoutToItem(int32_t toIndex)
378 {
379     int32_t curHeadIndex = renderList_.GetCurrentMinIndex();
380     int32_t curTailIndex = renderList_.GetCurrentMaxIndex();
381     double curMainSize = GetItemPosition(curTailIndex);
382     int32_t curGrid = GetItemGrid(curTailIndex);
383     auto itemChild = renderList_.GetChildByIndex(curTailIndex);
384     double childMainSize = 0.0;
385     while (itemChild) {
386         auto listItem = RenderListItem::GetRenderListItem(itemChild);
387         if (!listItem) {
388             LOGE("Get render list item failed index: %{public}d", curTailIndex);
389             return;
390         }
391         int32_t span = std::min(listItem->GetColumnSpan(), columnCount_ - curGrid);
392         LayoutParam innerLayout = MakeInnerLayoutParam(span);
393         itemChild->Layout(innerLayout);
394         SetChildPosition(itemChild, curMainSize, curGrid, span);
395         itemGrid_[curTailIndex] = curGrid;
396         itemPosition_[curTailIndex] = curMainSize;
397         childMainSize = std::max(renderList_.GetMainSize(itemChild->GetLayoutSize()), childMainSize);
398         curGrid += span;
399         if (curGrid >= columnCount_) {
400             curGrid = 0;
401             curMainSize += childMainSize;
402             childMainSize = 0.0;
403         }
404         if (curTailIndex >= toIndex) {
405             break;
406         }
407         itemChild = renderList_.GetChildByIndex(++curTailIndex);
408         renderList_.RecycleHead(curHeadIndex++);
409     }
410     curMainSize += childMainSize;
411     Size layoutSize = renderList_.MakeValue<Size>(curMainSize, renderList_.GetCrossSize(viewPort_));
412     renderList_.SetLayoutSize(layoutSize);
413 }
414 
LayoutMore(double incDistance)415 void GridLayoutManager::LayoutMore(double incDistance)
416 {
417     // Use to load about one page size, so not need to recycle child.
418     int32_t curTailIndex = renderList_.GetCurrentMaxIndex();
419     double curMainSize = GetItemPosition(curTailIndex);
420     int32_t curGrid = GetItemGrid(curTailIndex);
421     auto itemChild = renderList_.GetChildByIndex(curTailIndex);
422     double childMainSize = 0.0;
423     double incMainSize = 0.0;
424     while (itemChild) {
425         auto listItem = RenderListItem::GetRenderListItem(itemChild);
426         if (!listItem) {
427             LOGE("Get render list item failed index: %{public}d", curTailIndex);
428             return;
429         }
430         int32_t span = std::min(listItem->GetColumnSpan(), columnCount_ - curGrid);
431         LayoutParam innerLayout = MakeInnerLayoutParam(span);
432         itemChild->Layout(innerLayout);
433         SetChildPosition(itemChild, curMainSize, curGrid, span);
434         itemGrid_[curTailIndex] = curGrid;
435         itemPosition_[curTailIndex] = curMainSize;
436         childMainSize = std::max(renderList_.GetMainSize(itemChild->GetLayoutSize()), childMainSize);
437         curGrid += span;
438         if (curGrid >= columnCount_) {
439             curGrid = 0;
440             curMainSize += childMainSize;
441             incMainSize += childMainSize;
442             childMainSize = 0.0;
443         }
444         if (incMainSize >= incDistance) {
445             break;
446         }
447         itemChild = renderList_.GetChildByIndex(++curTailIndex);
448     }
449     curMainSize += childMainSize;
450     Size layoutSize = renderList_.MakeValue<Size>(curMainSize, renderList_.GetCrossSize(viewPort_));
451     renderList_.SetLayoutSize(layoutSize);
452 }
453 
MoveItemToViewPort(double position)454 void GridLayoutManager::MoveItemToViewPort(double position)
455 {
456     RefPtr<RenderNode> item = renderList_.GetChildByPosition(position);
457     if (!item) {
458         LOGE("[ListFocus]MoveItemToViewPort, Get item failed.");
459         return;
460     }
461     Size itemSize = item->GetLayoutSize();
462     double size = (direction_ == FlexDirection::ROW || direction_ == FlexDirection::ROW_REVERSE) ? itemSize.Width()
463                                                                                                  : itemSize.Height();
464 
465     // jump to this item using position
466     RefPtr<RenderNode> parentNode = renderList_.GetParent().Upgrade();
467     RefPtr<RenderMultiChildScroll> scroll = AceType::DynamicCast<RenderMultiChildScroll>(parentNode);
468     if (!scroll) {
469         LOGE("[ListFocus]MoveItemToViewPort, Get Parent failed.");
470         return;
471     }
472     double focusEffectWidth = size * (TV_ITEM_SCALE - DEFAULT_SCALE) * HALF_ITEM_SIZE;
473     double animationOffset = scroll->NormalizeToPx(FOCUS_BOUNDARY);
474     scroll->MoveItemToViewPort(position, size, animationOffset + focusEffectWidth);
475 }
476 
ShowItemFocusAnimation()477 void GridLayoutManager::ShowItemFocusAnimation()
478 {
479     RefPtr<RenderListItem> focusItem;
480     for (const auto& item : renderList_.GetItems()) {
481         if (!item.second || item.second->GetChildren().empty()) {
482             continue;
483         }
484 
485         focusItem = RenderListItem::GetRenderListItem(item.second);
486         if (!focusItem) {
487             break;
488         } else {
489             if (focusItem->IsFocused()) {
490                 focusItem->ShowFocusAnimation(true, Rect(renderList_.GetGlobalOffset(), viewPort_));
491             }
492         }
493     }
494 }
495 
496 } // namespace OHOS::Ace
497