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