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