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