• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2023-2025 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 #include "core/components_ng/pattern/grid/grid_layout_info.h"
16 
17 #include "core/components_ng/pattern/scrollable/scrollable_properties.h"
18 
19 namespace OHOS::Ace::NG {
20 namespace {
21 const int32_t MAX_CUMULATIVE_LINES = 100;
22 }
GetItemIndexByPosition(int32_t position)23 int32_t GridLayoutInfo::GetItemIndexByPosition(int32_t position)
24 {
25     auto iter = positionItemIndexMap_.find(position);
26     if (iter != positionItemIndexMap_.end()) {
27         return iter->second;
28     }
29     return position;
30 }
31 
GetPositionByItemIndex(int32_t itemIndex)32 int32_t GridLayoutInfo::GetPositionByItemIndex(int32_t itemIndex)
33 {
34     auto position = itemIndex;
35     auto find = std::find_if(positionItemIndexMap_.begin(), positionItemIndexMap_.end(),
36         [itemIndex](const std::pair<int32_t, int32_t>& item) { return item.second == itemIndex; });
37     if (find != positionItemIndexMap_.end()) {
38         position = find->first;
39     }
40 
41     return position;
42 }
43 
GetOriginalIndex() const44 int32_t GridLayoutInfo::GetOriginalIndex() const
45 {
46     return currentMovingItemPosition_;
47 }
48 
ClearDragState()49 void GridLayoutInfo::ClearDragState()
50 {
51     positionItemIndexMap_.clear();
52     currentMovingItemPosition_ = -1;
53     currentRect_.Reset();
54 }
55 
MoveItemsBack(int32_t from,int32_t to,int32_t itemIndex)56 void GridLayoutInfo::MoveItemsBack(int32_t from, int32_t to, int32_t itemIndex)
57 {
58     auto lastItemIndex = itemIndex;
59     if (crossCount_ == 0) {
60         return;
61     }
62     for (int32_t i = from; i <= to; ++i) {
63         int32_t mainIndex = (i - startIndex_) / crossCount_ + startMainLineIndex_;
64         int32_t crossIndex = (i - startIndex_) % crossCount_;
65         if (i == from) {
66             gridMatrix_[mainIndex][crossIndex] = itemIndex;
67         } else {
68             auto index = GetItemIndexByPosition(i - 1);
69             gridMatrix_[mainIndex][crossIndex] = index;
70             positionItemIndexMap_[i - 1] = lastItemIndex;
71             lastItemIndex = index;
72         }
73     }
74     positionItemIndexMap_[from] = itemIndex;
75     positionItemIndexMap_[to] = lastItemIndex;
76     currentMovingItemPosition_ = from;
77 }
78 
MoveItemsForward(int32_t from,int32_t to,int32_t itemIndex)79 void GridLayoutInfo::MoveItemsForward(int32_t from, int32_t to, int32_t itemIndex)
80 {
81     if (crossCount_ == 0) {
82         return;
83     }
84     for (int32_t i = from; i <= to; ++i) {
85         int32_t mainIndex = (i - startIndex_) / crossCount_ + startMainLineIndex_;
86         int32_t crossIndex = (i - startIndex_) % crossCount_;
87         if (i == to) {
88             gridMatrix_[mainIndex][crossIndex] = itemIndex;
89         } else {
90             auto index = GetItemIndexByPosition(i + 1);
91             gridMatrix_[mainIndex][crossIndex] = index;
92             positionItemIndexMap_[i] = index;
93         }
94     }
95     positionItemIndexMap_[to] = itemIndex;
96     currentMovingItemPosition_ = to;
97 }
98 
SwapItems(int32_t itemIndex,int32_t insertIndex)99 void GridLayoutInfo::SwapItems(int32_t itemIndex, int32_t insertIndex)
100 {
101     currentMovingItemPosition_ = currentMovingItemPosition_ == -1 ? itemIndex : currentMovingItemPosition_;
102     auto insertPosition = insertIndex;
103     // drag from another grid
104     if (itemIndex == -1) {
105         if (currentMovingItemPosition_ == -1) {
106             MoveItemsBack(insertPosition, childrenCount_, itemIndex);
107             return;
108         }
109     } else {
110         insertPosition = GetPositionByItemIndex(insertIndex);
111     }
112 
113     if (currentMovingItemPosition_ > insertPosition) {
114         MoveItemsBack(insertPosition, currentMovingItemPosition_, itemIndex);
115         return;
116     }
117 
118     if (insertPosition > currentMovingItemPosition_) {
119         MoveItemsForward(currentMovingItemPosition_, insertPosition, itemIndex);
120     }
121 }
122 
UpdateEndLine(float mainSize,float mainGap)123 void GridLayoutInfo::UpdateEndLine(float mainSize, float mainGap)
124 {
125     if (mainSize >= lastMainSize_) {
126         return;
127     }
128     for (auto i = startMainLineIndex_; i < endMainLineIndex_; ++i) {
129         mainSize -= (lineHeightMap_[i] + mainGap);
130         if (LessOrEqual(mainSize + mainGap, 0)) {
131             endMainLineIndex_ = i;
132             break;
133         }
134     }
135 }
136 
UpdateEndIndex(float overScrollOffset,float mainSize,float mainGap)137 void GridLayoutInfo::UpdateEndIndex(float overScrollOffset, float mainSize, float mainGap)
138 {
139     auto remainSize = mainSize - overScrollOffset;
140     for (auto i = startMainLineIndex_; i < endMainLineIndex_; ++i) {
141         remainSize -= (lineHeightMap_[i] + mainGap);
142         if (LessOrEqual(remainSize + mainGap, 0)) {
143             auto endLine = gridMatrix_.find(i);
144             CHECK_NULL_VOID(endLine != gridMatrix_.end());
145             CHECK_NULL_VOID(!endLine->second.empty());
146             endIndex_ = endLine->second.rbegin()->second;
147             endMainLineIndex_ = i;
148             break;
149         }
150     }
151 }
152 
IsOutOfStart() const153 bool GridLayoutInfo::IsOutOfStart() const
154 {
155     return reachStart_ && Positive(currentOffset_);
156 }
157 
IsOutOfEnd(float mainGap,bool irregular) const158 bool GridLayoutInfo::IsOutOfEnd(float mainGap, bool irregular) const
159 {
160     const bool atOrOutOfStart = reachStart_ && NonNegative(currentOffset_);
161     if (irregular) {
162         return !atOrOutOfStart &&
163                Negative(GetDistanceToBottom(lastMainSize_ - contentEndPadding_, totalHeightOfItemsInView_, mainGap));
164     }
165     const float endPos = currentOffset_ + totalHeightOfItemsInView_;
166     return !atOrOutOfStart && (endIndex_ == childrenCount_ - 1) &&
167            LessNotEqualCustomPrecision(endPos, lastMainSize_ - contentEndPadding_, -0.01f);
168 }
169 
GetCurrentOffsetOfRegularGrid(float mainGap) const170 float GridLayoutInfo::GetCurrentOffsetOfRegularGrid(float mainGap) const
171 {
172     if (lineHeightMap_.empty()) {
173         return 0.0f;
174     }
175     float defaultHeight = GetTotalLineHeight(0.0f) / static_cast<float>(lineHeightMap_.size());
176     if (crossCount_ == 0) {
177         return 0.0f;
178     }
179     auto lines = startIndex_ / crossCount_;
180     float res = 0.0f;
181     for (int i = 0; i < lines; ++i) {
182         auto it = lineHeightMap_.find(i);
183         res += (it != lineHeightMap_.end() ? it->second : defaultHeight) + mainGap;
184     }
185     return res - currentOffset_;
186 }
187 
GetContentOffset(float mainGap) const188 float GridLayoutInfo::GetContentOffset(float mainGap) const
189 {
190     if (lineHeightMap_.empty()) {
191         return 0.0f;
192     }
193     if (!hasBigItem_) {
194         return GetCurrentOffsetOfRegularGrid(mainGap);
195     }
196     // assume lineHeightMap is continuous in range [begin, rbegin].
197     int32_t itemCount = FindItemCount(lineHeightMap_.begin()->first, lineHeightMap_.rbegin()->first);
198     if (itemCount == 0) {
199         return 0.0f;
200     }
201     if (itemCount == childrenCount_ || (lineHeightMap_.begin()->first == 0 && itemCount >= startIndex_)) {
202         return GetStartLineOffset(mainGap);
203     }
204     // begin estimation
205     float heightSum = GetTotalLineHeight(mainGap, false);
206     if (itemCount == 0) {
207         return 0.0f;
208     }
209     auto averageHeight = heightSum / itemCount;
210     return startIndex_ * averageHeight - currentOffset_;
211 }
212 
FindItemCount(int32_t startLine,int32_t endLine) const213 int32_t GridLayoutInfo::FindItemCount(int32_t startLine, int32_t endLine) const
214 {
215     auto firstLine = gridMatrix_.find(startLine);
216     auto lastLine = gridMatrix_.find(endLine);
217     if (firstLine == gridMatrix_.end() || firstLine->second.empty()) {
218         for (auto i = startLine; i <= endLine; ++i) {
219             auto it = gridMatrix_.find(i);
220             if (it != gridMatrix_.end()) {
221                 firstLine = it;
222                 break;
223             }
224         }
225         if (firstLine == gridMatrix_.end() || firstLine->second.empty()) {
226             return 0;
227         }
228     }
229     if (lastLine == gridMatrix_.end() || lastLine->second.empty()) {
230         for (auto i = endLine; i >= startLine; --i) {
231             auto it = gridMatrix_.find(i);
232             if (it != gridMatrix_.end()) {
233                 lastLine = it;
234                 break;
235             }
236         }
237         if (lastLine == gridMatrix_.end() || lastLine->second.empty()) {
238             return 0;
239         }
240     }
241 
242     int32_t minIdx = firstLine->second.begin()->second;
243 
244     int32_t maxIdx = 0;
245     // maxIdx might not be in the last position if hasBigItem_
246     for (const auto& it : lastLine->second) {
247         maxIdx = std::max(maxIdx, it.second);
248     }
249     maxIdx = std::max(maxIdx, FindEndIdx(endLine).itemIdx);
250     return maxIdx - minIdx + 1;
251 }
252 
GetContentHeightOfRegularGrid(float mainGap) const253 float GridLayoutInfo::GetContentHeightOfRegularGrid(float mainGap) const
254 {
255     float res = 0.0f;
256     if (crossCount_ == 0 || lineHeightMap_.empty()) {
257         return res;
258     }
259     auto childrenCount = childrenCount_ + repeatDifference_;
260     float lineHeight = GetTotalLineHeight(0.0f) / static_cast<float>(lineHeightMap_.size());
261     auto lines = (childrenCount) / crossCount_;
262     for (int i = 0; i < lines; ++i) {
263         auto it = lineHeightMap_.find(i);
264         res += (it != lineHeightMap_.end() ? it->second : lineHeight) + mainGap;
265     }
266     if (childrenCount % crossCount_ == 0) {
267         return res - mainGap;
268     }
269     auto lastLine = lineHeightMap_.find(lines);
270     return res + (lastLine != lineHeightMap_.end() ? lastLine->second : lineHeight);
271 }
272 
GetContentHeight(float mainGap) const273 float GridLayoutInfo::GetContentHeight(float mainGap) const
274 {
275     if (!hasBigItem_) {
276         return GetContentHeightOfRegularGrid(mainGap);
277     }
278     if (lineHeightMap_.empty()) {
279         return 0.0f;
280     }
281     float heightSum = GetTotalLineHeight(mainGap, false);
282     // assume lineHeightMap is continuous in range [begin, rbegin]
283     int32_t itemCount = FindItemCount(lineHeightMap_.begin()->first, lineHeightMap_.rbegin()->first);
284     if (itemCount == 0) {
285         return 0.0f;
286     }
287     float averageHeight = heightSum / itemCount;
288 
289     auto childrenCount = childrenCount_ + repeatDifference_;
290     if (itemCount == childrenCount) {
291         return heightSum - mainGap;
292     }
293     return heightSum + (childrenCount - itemCount) * averageHeight;
294 }
295 
GetContentOffset(const GridLayoutOptions & options,float mainGap) const296 float GridLayoutInfo::GetContentOffset(const GridLayoutOptions& options, float mainGap) const
297 {
298     if (startIndex_ == 0) {
299         return -currentOffset_;
300     }
301     if (options.irregularIndexes.empty() || startIndex_ < *(options.irregularIndexes.begin())) {
302         return GetCurrentOffsetOfRegularGrid(mainGap);
303     }
304     if (options.getSizeByIndex) {
305         return GetContentOffset(mainGap);
306     }
307     float prevHeight = GetContentHeight(options, startIndex_, mainGap) + mainGap;
308     return prevHeight - currentOffset_;
309 }
310 
311 namespace {
312 // prevIdx and idx are indices of two irregular items that take up a whole line
AddLinesInBetween(int32_t prevIdx,int32_t idx,int32_t crossCount,float lineHeight)313 inline float AddLinesInBetween(int32_t prevIdx, int32_t idx, int32_t crossCount, float lineHeight)
314 {
315     if (crossCount == 0) {
316         return 0.0f;
317     }
318     return (idx - prevIdx) > 1 ? ((idx - 2 - prevIdx) / crossCount + 1) * lineHeight : 0.0f;
319 }
320 } // namespace
321 
GetLineHeights(const GridLayoutOptions & options,float mainGap,float & regularHeight,float & irregularHeight) const322 void GridLayoutInfo::GetLineHeights(
323     const GridLayoutOptions& options, float mainGap, float& regularHeight, float& irregularHeight) const
324 {
325     for (const auto& item : lineHeightMap_) {
326         auto line = gridMatrix_.find(item.first);
327         if (line == gridMatrix_.end()) {
328             continue;
329         }
330         if (line->second.empty() || LessOrEqual(item.second, 0.0f)) {
331             continue;
332         }
333         auto lineStart = line->second.begin()->second;
334         if (options.irregularIndexes.find(lineStart) != options.irregularIndexes.end()) {
335             irregularHeight = item.second + mainGap;
336         } else {
337             if (NonPositive(regularHeight)) {
338                 regularHeight = item.second + mainGap;
339             }
340         }
341         if (Positive(irregularHeight) && Positive(regularHeight)) {
342             break;
343         }
344     }
345 }
346 
GetContentHeight(const GridLayoutOptions & options,int32_t endIdx,float mainGap) const347 float GridLayoutInfo::GetContentHeight(const GridLayoutOptions& options, int32_t endIdx, float mainGap) const
348 {
349     if (options.irregularIndexes.empty()) {
350         return GetContentHeightOfRegularGrid(mainGap);
351     }
352     if (options.getSizeByIndex) {
353         return GetContentHeight(mainGap);
354     }
355 
356     float irregularHeight = -1.0f;
357     float regularHeight = -1.0f;
358     GetLineHeights(options, mainGap, regularHeight, irregularHeight);
359     if (Negative(irregularHeight) && Positive(lastIrregularMainSize_)) {
360         irregularHeight = lastIrregularMainSize_;
361     }
362     if (Negative(regularHeight) && Positive(lastRegularMainSize_)) {
363         regularHeight = lastRegularMainSize_;
364     }
365     if (Negative(irregularHeight)) {
366         irregularHeight = regularHeight;
367     }
368     if (Negative(regularHeight)) {
369         regularHeight = irregularHeight;
370     }
371     // get line count
372     float totalHeight = 0;
373     auto lastIndex = -1;
374     for (int32_t idx : options.irregularIndexes) {
375         if (idx >= endIdx) {
376             break;
377         }
378         totalHeight += irregularHeight;
379         totalHeight += AddLinesInBetween(lastIndex, idx, crossCount_, regularHeight);
380         lastIndex = idx;
381     }
382 
383     totalHeight += AddLinesInBetween(lastIndex, endIdx, crossCount_, regularHeight);
384     totalHeight -= mainGap;
385     return totalHeight;
386 }
387 
GetIrregularOffset(float mainGap) const388 float GridLayoutInfo::GetIrregularOffset(float mainGap) const
389 {
390     // need to calculate total line height before startMainLine_
391     // gridMatrix ready up to endLine, so lineCnt is known.
392     // get sum of existing lines
393     // use average to estimate unknown lines
394     if (lineHeightMap_.empty() || childrenCount_ == 0) {
395         return 0.0f;
396     }
397 
398     auto it = lineHeightMap_.lower_bound(startMainLineIndex_);
399     auto knownLineCnt = static_cast<float>(std::distance(lineHeightMap_.begin(), it));
400     float knownHeight = GetHeightInRange(lineHeightMap_.begin()->first, startMainLineIndex_, 0.0f);
401     float avgHeight = synced_ ? avgLineHeight_ : GetTotalLineHeight(0.0f) / static_cast<float>(lineHeightMap_.size());
402 
403     auto startLine = static_cast<float>(startMainLineIndex_);
404     float estTotal = knownHeight + avgHeight * (startLine - knownLineCnt);
405     return estTotal + startLine * mainGap - currentOffset_;
406 }
407 
GetIrregularHeight(float mainGap) const408 float GridLayoutInfo::GetIrregularHeight(float mainGap) const
409 {
410     // count current number of lines
411     // estimate total number of lines based on {known item / total item}
412     if (lineHeightMap_.empty() || childrenCount_ == 0) {
413         return 0.0f;
414     }
415     auto childrenCount = childrenCount_ + repeatDifference_;
416     int32_t lastKnownLine = lineHeightMap_.rbegin()->first;
417     float itemRatio = static_cast<float>(FindEndIdx(lastKnownLine).itemIdx + 1) / static_cast<float>(childrenCount);
418     float estTotalLines = std::round(static_cast<float>(lastKnownLine + 1) / itemRatio);
419 
420     auto knownLineCnt = static_cast<float>(lineHeightMap_.size());
421     float knownHeight = synced_ ? avgLineHeight_ * knownLineCnt : GetTotalLineHeight(0.0f);
422     float avgHeight = synced_ ? avgLineHeight_ : knownHeight / knownLineCnt;
423     return knownHeight + (estTotalLines - knownLineCnt) * avgHeight + (estTotalLines - 1) * mainGap;
424 }
425 
SkipStartIndexByOffset(const GridLayoutOptions & options,float mainGap)426 void GridLayoutInfo::SkipStartIndexByOffset(const GridLayoutOptions& options, float mainGap)
427 {
428     float targetContent = currentHeight_ - (currentOffset_ - prevOffset_);
429     if (LessOrEqual(targetContent, 0.0)) {
430         currentOffset_ = 0.0f;
431         startIndex_ = 0;
432         return;
433     }
434 
435     float irregularHeight = -1.0f;
436     float regularHeight = -1.0f;
437     GetLineHeights(options, mainGap, regularHeight, irregularHeight);
438     if (Negative(irregularHeight) && Positive(lastIrregularMainSize_)) {
439         irregularHeight = lastIrregularMainSize_;
440     } else {
441         lastIrregularMainSize_ = irregularHeight;
442     }
443     if (Negative(regularHeight) && Positive(lastRegularMainSize_)) {
444         regularHeight = lastRegularMainSize_;
445     } else {
446         lastRegularMainSize_ = regularHeight;
447     }
448     if (Negative(irregularHeight)) {
449         irregularHeight = regularHeight;
450     }
451 
452     float totalHeight = 0;
453     int32_t lastIndex = -1;
454 
455     for (int32_t idx : options.irregularIndexes) {
456         if (GreatOrEqual(totalHeight, targetContent)) {
457             break;
458         }
459         float height = AddLinesInBetween(lastIndex, idx, crossCount_, regularHeight);
460         if (GreatOrEqual(totalHeight + height, targetContent)) {
461             break;
462         }
463         totalHeight += height;
464         totalHeight += irregularHeight;
465         lastIndex = idx;
466     }
467     if (NonPositive(regularHeight)) {
468         startIndex_ = std::min(lastIndex, childrenCount_ - 1);
469         currentOffset_ = targetContent - totalHeight;
470         return;
471     }
472     int32_t lines = static_cast<int32_t>(std::floor((targetContent - totalHeight) / regularHeight));
473     currentOffset_ = totalHeight + lines * regularHeight - targetContent;
474     int32_t startIdx = lines * crossCount_ + lastIndex + 1;
475     startIndex_ = std::min(startIdx, childrenCount_ - 1);
476 }
477 
GetCurrentLineHeight() const478 float GridLayoutInfo::GetCurrentLineHeight() const
479 {
480     auto currentLineHeight = lineHeightMap_.find(startMainLineIndex_);
481     auto currentLineMatrix = gridMatrix_.find(startMainLineIndex_);
482     // if current line exist, find it
483     if (currentLineHeight != lineHeightMap_.end() && currentLineMatrix != gridMatrix_.end() &&
484         !currentLineMatrix->second.empty()) {
485         return currentLineHeight->second;
486     }
487 
488     // otherwise return the first line in cache
489     for (const auto& item : lineHeightMap_) {
490         auto line = gridMatrix_.find(item.first);
491         if (line == gridMatrix_.end()) {
492             continue;
493         }
494         if (line->second.empty()) {
495             continue;
496         }
497         return item.second;
498     }
499     return 0.0f;
500 }
501 
FindItemInRange(int32_t target) const502 std::pair<int32_t, int32_t> GridLayoutInfo::FindItemInRange(int32_t target) const
503 {
504     if (gridMatrix_.empty()) {
505         return { -1, -1 };
506     }
507     auto end = gridMatrix_.upper_bound(endMainLineIndex_);
508     for (auto row = gridMatrix_.lower_bound(startMainLineIndex_); row != end; ++row) {
509         for (const auto& cell : row->second) {
510             if (cell.second == target) {
511                 return { row->first, cell.first };
512             }
513         }
514     }
515     return { -1, -1 };
516 }
517 
518 // Use the index to get the line number where the item is located
GetLineIndexByIndex(int32_t targetIndex,int32_t & targetLineIndex) const519 bool GridLayoutInfo::GetLineIndexByIndex(int32_t targetIndex, int32_t& targetLineIndex) const
520 {
521     for (auto [lineIndex, lineMap] : gridMatrix_) {
522         for (auto [crossIndex, index] : lineMap) {
523             if (index == targetIndex) {
524                 targetLineIndex = lineIndex;
525                 return true;
526             }
527         }
528     }
529     return false;
530 }
531 
532 // get the total height of all rows from zero before the targetLineIndex
GetTotalHeightFromZeroIndex(int32_t targetLineIndex,float mainGap) const533 float GridLayoutInfo::GetTotalHeightFromZeroIndex(int32_t targetLineIndex, float mainGap) const
534 {
535     auto targetPos = 0.f;
536     for (auto [lineIndex, lineHeight] : lineHeightMap_) {
537         if (targetLineIndex > lineIndex) {
538             targetPos += lineHeight + mainGap;
539         } else {
540             break;
541         }
542     }
543     return targetPos;
544 }
545 
TransformAutoScrollAlign(int32_t itemIdx,int32_t height,float mainSize,float mainGap) const546 ScrollAlign GridLayoutInfo::TransformAutoScrollAlign(
547     int32_t itemIdx, int32_t height, float mainSize, float mainGap) const
548 {
549     if (itemIdx >= startIndex_ && itemIdx <= endIndex_) {
550         auto [line, _] = FindItemInRange(itemIdx);
551         float topPos = GetItemTopPos(line, mainGap);
552         float botPos = GetItemBottomPos(line, height, mainGap);
553         if (NonPositive(topPos) && GreatOrEqual(botPos, mainSize)) {
554             // item occupies the whole viewport
555             return ScrollAlign::NONE;
556         }
557         // scrollAlign start / end if the item is not fully in viewport
558         if (Negative(topPos)) {
559             return ScrollAlign::START;
560         }
561         if (GreatNotEqual(botPos, mainSize)) {
562             return ScrollAlign::END;
563         }
564         return ScrollAlign::NONE;
565     }
566     if (itemIdx > endIndex_) {
567         return ScrollAlign::END;
568     }
569     return ScrollAlign::START;
570 }
571 
GetAnimatePosIrregular(int32_t targetIdx,int32_t height,ScrollAlign align,float mainGap) const572 float GridLayoutInfo::GetAnimatePosIrregular(int32_t targetIdx, int32_t height, ScrollAlign align, float mainGap) const
573 {
574     if (targetIdx == LAST_ITEM) {
575         targetIdx = childrenCount_ - 1;
576     }
577     auto it = FindInMatrix(targetIdx);
578     if (it == gridMatrix_.end()) {
579         return -1.0f;
580     }
581     if (align == ScrollAlign::AUTO) {
582         align = TransformAutoScrollAlign(targetIdx, height, lastMainSize_, mainGap);
583     }
584     switch (align) {
585         case ScrollAlign::START:
586             return GetTotalHeightFromZeroIndex(it->first, mainGap);
587         case ScrollAlign::CENTER: {
588             auto [center, offset] = FindItemCenter(it->first, height, mainGap);
589             float res = GetTotalHeightFromZeroIndex(center, mainGap) + offset - lastMainSize_ / 2.0f;
590             return std::max(res, 0.0f);
591         }
592         case ScrollAlign::END: {
593             float res = GetTotalHeightFromZeroIndex(it->first + height, mainGap) - mainGap - lastMainSize_;
594             return std::max(res, 0.0f);
595         }
596         default:
597             return -1.0f;
598     }
599 }
600 
601 // Based on the index from zero and align, gets the position to scroll to
GetGridItemAnimatePos(const GridLayoutInfo & currentGridLayoutInfo,int32_t targetIndex,ScrollAlign align,float mainGap,float & targetPos)602 bool GridLayoutInfo::GetGridItemAnimatePos(const GridLayoutInfo& currentGridLayoutInfo, int32_t targetIndex,
603     ScrollAlign align, float mainGap, float& targetPos)
604 {
605     int32_t startMainLineIndex = currentGridLayoutInfo.startMainLineIndex_;
606     int32_t endMainLineIndex = currentGridLayoutInfo.endMainLineIndex_;
607     float lastMainSize = currentGridLayoutInfo.lastMainSize_;
608     int32_t targetLineIndex = -1;
609     // Pre-check
610     // Get the line number where the index is located. If targetIndex does not exist, it is returned.
611     CHECK_NULL_RETURN(GetLineIndexByIndex(targetIndex, targetLineIndex), false);
612 
613     // Get the total height of the targetPos from row 0 to targetLineIndex-1.
614     targetPos = GetTotalHeightFromZeroIndex(targetLineIndex, mainGap);
615 
616     // Find the target row and get the altitude information
617     auto targetItem = lineHeightMap_.find(targetLineIndex);
618 
619     // Make sure that the target line has height information
620     CHECK_NULL_RETURN(targetItem != lineHeightMap_.end(), false);
621     auto targetLineHeight = targetItem->second;
622 
623     // Depending on align, calculate where you need to scroll to
624     switch (align) {
625         case ScrollAlign::START:
626         case ScrollAlign::NONE:
627             break;
628         case ScrollAlign::CENTER: {
629             targetPos -= ((lastMainSize - targetLineHeight) * HALF);
630             break;
631         }
632         case ScrollAlign::END: {
633             targetPos -= (lastMainSize - targetLineHeight);
634             break;
635         }
636         case ScrollAlign::AUTO: {
637             GetLineIndexByIndex(currentGridLayoutInfo.startIndex_, startMainLineIndex);
638             GetLineIndexByIndex(currentGridLayoutInfo.endIndex_, endMainLineIndex);
639             auto targetPosBeforeStartIndex = GetTotalHeightFromZeroIndex(startMainLineIndex, mainGap);
640             // targetPos - targetPosBeforeStartIndex:The distance between the top of the startLine row and the top of
641             // the targetLine row
642             // The distance of the targetLine row from the top of the screen
643             auto height2Top = targetPos - targetPosBeforeStartIndex - std::abs(currentGridLayoutInfo.currentOffset_);
644             // The distance of the targetLine row from the bottom of the screen
645             auto height2Bottom = std::abs(currentGridLayoutInfo.currentOffset_) + lastMainSize - targetPos +
646                                  targetPosBeforeStartIndex - targetLineHeight;
647             // This is handled when the targetLine line is the same as the endLine line. As for the period between
648             // startLine and endLine, follow the following process
649             if (GreatOrEqual(height2Top, 0.f) && GreatOrEqual(height2Bottom, 0.f)) {
650                 return false;
651             }
652             // When the row height is greater than the screen height and occupies the entire screen height, do nothing
653             if ((startMainLineIndex == targetLineIndex) && (endMainLineIndex == targetLineIndex)) {
654                 if ((std::abs(currentGridLayoutInfo.currentOffset_) + lastMainSize - targetLineHeight) <= 0) {
655                     return false;
656                 }
657             }
658             if (startMainLineIndex >= targetLineIndex) {
659             } else if (targetLineIndex >= endMainLineIndex) {
660                 targetPos -= (lastMainSize - targetLineHeight);
661             } else {
662                 return false;
663             }
664             break;
665         }
666     }
667     return true;
668 }
669 
670 namespace {
CheckRow(int32_t & maxV,const std::map<int,int> & row,int32_t target)671 bool CheckRow(int32_t& maxV, const std::map<int, int>& row, int32_t target)
672 {
673     for (auto [_, item] : row) {
674         maxV = std::max(maxV, std::abs(item));
675         if (item == target) {
676             return true;
677         }
678     }
679     return false;
680 }
681 
682 using MatIter = decltype(GridLayoutInfo::gridMatrix_)::const_iterator;
683 
SearchInReverse(const decltype(GridLayoutInfo::gridMatrix_) & mat,int32_t target,int32_t crossCnt)684 MatIter SearchInReverse(const decltype(GridLayoutInfo::gridMatrix_)& mat, int32_t target, int32_t crossCnt)
685 {
686     for (auto it = mat.rbegin(); it != mat.rend(); ++it) {
687         int32_t maxV = -1;
688         if (CheckRow(maxV, it->second, target)) {
689             return (++it).base();
690         }
691         if (static_cast<int32_t>(it->second.size()) == crossCnt && maxV < target) {
692             break;
693         }
694     }
695     return mat.end();
696 }
697 } // namespace
698 
FindInMatrix(int32_t index) const699 MatIter GridLayoutInfo::FindInMatrix(int32_t index) const
700 {
701     if (crossCount_ == 0) {
702         return gridMatrix_.end();
703     }
704     if (index == 0) {
705         return gridMatrix_.begin();
706     }
707     size_t count = gridMatrix_.size();
708     size_t step = 0;
709     auto left = gridMatrix_.begin();
710     auto it = left;
711     while (count > 0) {
712         it = left;
713         step = count / 2;
714         std::advance(it, step);
715 
716         // with irregular items, only the max index on each row is guaranteed to be in order.
717         int32_t maxV = -1;
718         if (CheckRow(maxV, it->second, index)) {
719             return it;
720         }
721 
722         if (index <= maxV) {
723             count = step;
724         } else {
725             // index on the right side of current row
726             left = ++it;
727             count -= step + 1;
728         }
729     }
730     /*
731     Fallback to linear to handle this situation:
732         1 | 2 | 3
733         x | 2 | x
734         x | 2 | x
735         x | 2 | x
736     When iterator points to Line 1 ~ 3, Item 3 can never be found.
737     */
738     return SearchInReverse(gridMatrix_, index, crossCount_);
739 }
740 
GetItemPos(int32_t itemIdx) const741 std::pair<int32_t, int32_t> GridLayoutInfo::GetItemPos(int32_t itemIdx) const
742 {
743     auto it = FindInMatrix(itemIdx);
744     if (it == gridMatrix_.end()) {
745         return { -1, -1 };
746     }
747     for (auto col : it->second) {
748         if (col.second == itemIdx) {
749             return { col.first, it->first };
750         }
751     }
752     return { -1, -1 };
753 }
754 
FindEndIdx(int32_t endLine) const755 GridLayoutInfo::EndIndexInfo GridLayoutInfo::FindEndIdx(int32_t endLine) const
756 {
757     auto it = gridMatrix_.find(endLine);
758     if (it == gridMatrix_.end()) {
759         return {};
760     }
761 
762     // Create reverse iterator starting from endLine position
763     for (auto rIt = std::make_reverse_iterator(++it); rIt != gridMatrix_.rend(); ++rIt) {
764         const auto& row = rIt->second;
765         // Search backwards in the row for first positive index
766         for (auto cell = row.rbegin(); cell != row.rend(); ++cell) {
767             if (cell->second > 0) {
768                 return { .itemIdx = cell->second, .y = rIt->first, .x = cell->first };
769             }
770         }
771     }
772     return { .itemIdx = 0, .y = 0, .x = 0 };
773 }
774 
ClearMapsToEnd(int32_t idx)775 void GridLayoutInfo::ClearMapsToEnd(int32_t idx)
776 {
777     auto gridIt = gridMatrix_.lower_bound(idx);
778     gridMatrix_.erase(gridIt, gridMatrix_.end());
779     ClearHeightsToEnd(idx);
780 }
781 
ClearMapsFromStart(int32_t idx)782 void GridLayoutInfo::ClearMapsFromStart(int32_t idx)
783 {
784     auto gridIt = gridMatrix_.lower_bound(idx);
785     gridMatrix_.erase(gridMatrix_.begin(), gridIt);
786     auto lineIt = lineHeightMap_.lower_bound(idx);
787     lineHeightMap_.erase(lineHeightMap_.begin(), lineIt);
788 }
789 
ClearHeightsToEnd(int32_t idx)790 void GridLayoutInfo::ClearHeightsToEnd(int32_t idx)
791 {
792     auto lineIt = lineHeightMap_.lower_bound(idx);
793     lineHeightMap_.erase(lineIt, lineHeightMap_.end());
794 }
795 
ClearMatrixToEnd(int32_t idx,int32_t lineIdx)796 void GridLayoutInfo::ClearMatrixToEnd(int32_t idx, int32_t lineIdx)
797 {
798     auto it = gridMatrix_.find(lineIdx);
799     for (; it != gridMatrix_.end(); ++it) {
800         for (auto itemIt = it->second.begin(); itemIt != it->second.end();) {
801             if (std::abs(itemIt->second) < idx) {
802                 ++itemIt;
803                 continue;
804             }
805             itemIt = it->second.erase(itemIt);
806         }
807         if (it->second.empty()) {
808             break;
809         }
810     }
811     gridMatrix_.erase(it, gridMatrix_.end());
812 }
813 
GetTotalHeightOfItemsInView(float mainGap,bool prune) const814 float GridLayoutInfo::GetTotalHeightOfItemsInView(float mainGap, bool prune) const
815 {
816     if (!prune) {
817         return GetHeightInRange(startMainLineIndex_, endMainLineIndex_ + 1, mainGap) - mainGap;
818     }
819     auto it = SkipLinesAboveView(mainGap).first;
820     if (it == lineHeightMap_.end() || it->first > endMainLineIndex_) {
821         return -mainGap;
822     }
823     auto endIt = lineHeightMap_.upper_bound(endMainLineIndex_);
824     float len = 0.0f;
825     for (; it != endIt; ++it) {
826         len += it->second + mainGap;
827     }
828     return len - mainGap;
829 }
830 
SkipLinesAboveView(float mainGap) const831 std::pair<GridLayoutInfo::HeightMapIt, float> GridLayoutInfo::SkipLinesAboveView(float mainGap) const
832 {
833     auto it = lineHeightMap_.find(startMainLineIndex_);
834     float offset = currentOffset_;
835     while (it != lineHeightMap_.end() && Negative(it->second + offset + mainGap)) {
836         offset += it->second + mainGap;
837         ++it;
838     }
839     return { it, offset };
840 }
841 
UpdateStartIndexForExtralOffset(float mainGap,float mainSize)842 void GridLayoutInfo::UpdateStartIndexForExtralOffset(float mainGap, float mainSize)
843 {
844     if (Negative(currentOffset_)) {
845         auto startLineHeight = lineHeightMap_.find(startMainLineIndex_);
846         CHECK_NULL_VOID(startLineHeight != lineHeightMap_.end());
847         auto currentEndOffset = currentOffset_ + startLineHeight->second + mainGap;
848         while (!Positive(currentEndOffset)) {
849             startMainLineIndex_++;
850             startLineHeight = lineHeightMap_.find(startMainLineIndex_);
851             if (startLineHeight == lineHeightMap_.end()) {
852                 startMainLineIndex_--;
853                 break;
854             }
855             currentOffset_ = currentEndOffset;
856             currentEndOffset += (startLineHeight->second + mainGap);
857         }
858     } else if (Positive(currentOffset_)) {
859         auto preLineHeight = lineHeightMap_.find(startMainLineIndex_ - 1);
860         CHECK_NULL_VOID(preLineHeight != lineHeightMap_.end());
861         auto preItemCurrentOffset = currentOffset_ - preLineHeight->second - mainGap;
862         while (Positive(preItemCurrentOffset)) {
863             startMainLineIndex_--;
864             preLineHeight = lineHeightMap_.find(startMainLineIndex_);
865             if (preLineHeight == lineHeightMap_.end()) {
866                 startMainLineIndex_++;
867                 break;
868             }
869             preItemCurrentOffset -= (preLineHeight->second + mainGap);
870             currentOffset_ = preItemCurrentOffset;
871         }
872     }
873     auto startLine = gridMatrix_.find(startMainLineIndex_);
874     CHECK_NULL_VOID(startLine != gridMatrix_.end() && (!startLine->second.empty()));
875     startIndex_ = startLine->second.begin()->second;
876     auto endLineHeight = lineHeightMap_.find(startMainLineIndex_);
877     CHECK_NULL_VOID(endLineHeight != lineHeightMap_.end());
878     endMainLineIndex_ = startMainLineIndex_;
879     auto currentEndOffset = currentOffset_ + endLineHeight->second + mainGap;
880     while (LessNotEqual(currentEndOffset, mainSize)) {
881         endMainLineIndex_++;
882         endLineHeight = lineHeightMap_.find(endMainLineIndex_);
883         if (endLineHeight == lineHeightMap_.end()) {
884             endMainLineIndex_--;
885             break;
886         }
887         currentEndOffset += (endLineHeight->second + mainGap);
888     }
889     auto endLine = gridMatrix_.find(endMainLineIndex_);
890     CHECK_NULL_VOID(endLine != gridMatrix_.end() && (!endLine->second.empty()));
891     endIndex_ = endLine->second.rbegin()->second;
892 }
893 
GetDistanceToBottom(float mainSize,float heightInView,float mainGap) const894 float GridLayoutInfo::GetDistanceToBottom(float mainSize, float heightInView, float mainGap) const
895 {
896     if (lineHeightMap_.empty() || endIndex_ < childrenCount_ - 1 ||
897         endMainLineIndex_ < lineHeightMap_.rbegin()->first) {
898         return Infinity<float>();
899     }
900 
901     float offset = currentOffset_;
902     // currentOffset_ is relative to startMainLine, which might be entirely above viewport
903     auto it = lineHeightMap_.find(startMainLineIndex_);
904     while (it != lineHeightMap_.end() && Negative(offset + it->second + mainGap)) {
905         offset += it->second + mainGap;
906         ++it;
907     }
908     const float bottomPos = offset + heightInView;
909     return bottomPos - mainSize;
910 }
911 
ClearHeightsFromMatrix(int32_t lineIdx)912 void GridLayoutInfo::ClearHeightsFromMatrix(int32_t lineIdx)
913 {
914     auto lineIt = lineHeightMap_.find(lineIdx);
915     if (lineIt == lineHeightMap_.end()) {
916         return;
917     }
918     if (gridMatrix_.find(lineIdx) != gridMatrix_.end()) {
919         lineIt++;
920     }
921     lineHeightMap_.erase(lineIt, lineHeightMap_.end());
922 }
923 
FindStartLineInMatrix(MatIter iter,int32_t index) const924 MatIter GridLayoutInfo::FindStartLineInMatrix(MatIter iter, int32_t index) const
925 {
926     if (iter == gridMatrix_.end() || iter == gridMatrix_.begin()) {
927         return iter;
928     }
929 
930     --iter;
931     int32_t maxValue = 0;
932     while (CheckRow(maxValue, iter->second, index)) {
933         if (iter == gridMatrix_.begin()) {
934             return iter;
935         }
936         --iter;
937     }
938     return ++iter;
939 }
940 
GetHeightInRange(int32_t startLine,int32_t endLine,float mainGap) const941 float GridLayoutInfo::GetHeightInRange(int32_t startLine, int32_t endLine, float mainGap) const
942 {
943     if (endLine <= startLine) {
944         return 0.0f;
945     }
946     float totalHeight = 0.0f;
947     auto endIt = lineHeightMap_.lower_bound(endLine);
948     for (auto it = lineHeightMap_.lower_bound(startLine); it != endIt; ++it) {
949         totalHeight += it->second + mainGap;
950     }
951     return totalHeight;
952 }
953 
HeightSumSmaller(float other,float mainGap) const954 bool GridLayoutInfo::HeightSumSmaller(float other, float mainGap) const
955 {
956     other += mainGap;
957     for (const auto& it : lineHeightMap_) {
958         other -= it.second + mainGap;
959         if (NonPositive(other)) {
960             return false;
961         }
962     }
963     return true;
964 }
965 
FindItemCenter(int32_t startLine,int32_t lineCnt,float mainGap) const966 std::pair<int32_t, float> GridLayoutInfo::FindItemCenter(int32_t startLine, int32_t lineCnt, float mainGap) const
967 {
968     float halfLen = (GetHeightInRange(startLine, startLine + lineCnt, mainGap) - mainGap) / 2.0f;
969     auto it = lineHeightMap_.find(startLine);
970     float len = 0.0f;
971     while (it != lineHeightMap_.end() && LessNotEqual(len + it->second + mainGap, halfLen)) {
972         len += it->second + mainGap;
973         ++it;
974     }
975     return { it->first, halfLen - len };
976 }
977 
PrepareJumpToBottom()978 void GridLayoutInfo::PrepareJumpToBottom()
979 {
980     if (gridMatrix_.empty() || gridMatrix_.rbegin()->second.empty()) {
981         TAG_LOGW(ACE_GRID, "Matrix setup is incorrect");
982         jumpIndex_ = LAST_ITEM;
983     } else {
984         jumpIndex_ = std::abs(gridMatrix_.rbegin()->second.begin()->second);
985     }
986     scrollAlign_ = ScrollAlign::END;
987 }
988 
UpdateDefaultCachedCount()989 void GridLayoutInfo::UpdateDefaultCachedCount()
990 {
991     if (crossCount_ == 0) {
992         return;
993     }
994     thread_local float pageCount = SystemProperties::GetPageCount();
995     if (pageCount <= 0.0f) {
996         return;
997     }
998     int32_t itemCount = (endIndex_ - startIndex_ + 1) / crossCount_;
999     if (itemCount <= 0) {
1000         return;
1001     }
1002     constexpr int32_t MAX_DEFAULT_CACHED_COUNT = 16;
1003     int32_t newCachedCount = static_cast<int32_t>(ceil(pageCount * itemCount));
1004     if (newCachedCount > MAX_DEFAULT_CACHED_COUNT) {
1005         TAG_LOGI(ACE_GRID, "Default cachedCount exceed 16");
1006         defCachedCount_ = MAX_DEFAULT_CACHED_COUNT;
1007     } else {
1008         defCachedCount_ = std::max(newCachedCount, defCachedCount_);
1009     }
1010 }
1011 
FindInMatrixByMainIndexAndCrossIndex(int32_t mainIndex,int32_t crossIndex) const1012 int32_t GridLayoutInfo::FindInMatrixByMainIndexAndCrossIndex(int32_t mainIndex, int32_t crossIndex) const
1013 {
1014     if (gridMatrix_.count(mainIndex) > 0 && gridMatrix_.at(mainIndex).count(crossIndex) > 0) {
1015         return gridMatrix_.at(mainIndex).at(crossIndex);
1016     }
1017     return -1;
1018 }
1019 
PrintMatrix()1020 void GridLayoutInfo::PrintMatrix()
1021 {
1022     TAG_LOGI(ACE_GRID, "-----------start print gridMatrix------------");
1023     std::string res = std::string("");
1024     for (auto item : gridMatrix_) {
1025         res.append(std::to_string(item.first));
1026         res.append(": ");
1027         for (auto index : item.second) {
1028             res.append("[")
1029                 .append(std::to_string(index.first))
1030                 .append(",")
1031                 .append(std::to_string(index.second))
1032                 .append("] ");
1033         }
1034         TAG_LOGI(ACE_GRID, "%{public}s", res.c_str());
1035         res.clear();
1036     }
1037     TAG_LOGI(ACE_GRID, "-----------end print gridMatrix------------");
1038 }
1039 
PrintLineHeight()1040 void GridLayoutInfo::PrintLineHeight()
1041 {
1042     TAG_LOGI(ACE_GRID, "-----------start print lineHeightMap------------");
1043     for (auto item : lineHeightMap_) {
1044         TAG_LOGI(ACE_GRID, "%{public}d : %{public}f", item.first, item.second);
1045     }
1046     TAG_LOGI(ACE_GRID, "-----------end print lineHeightMap------------");
1047 }
1048 
CheckGridMatrix(int32_t cachedCount)1049 bool GridLayoutInfo::CheckGridMatrix(int32_t cachedCount)
1050 {
1051     auto endRow = gridMatrix_.upper_bound(endMainLineIndex_);
1052     while (endRow != gridMatrix_.end()) {
1053         if (endRow->first > endMainLineIndex_ + cachedCount) {
1054             break;
1055         }
1056         for (const auto& cell : endRow->second) {
1057             if (cell.second < endIndex_) {
1058                 TAG_LOGW(AceLogTag::ACE_GRID,
1059                     "check grid matrix failed, index %{public}d is less than endIndex %{public}d", cell.second,
1060                     endIndex_);
1061                 PrintMatrix();
1062                 return false;
1063             }
1064         }
1065         ++endRow;
1066     }
1067 
1068     auto startRow = gridMatrix_.lower_bound(startMainLineIndex_);
1069     if (startRow == gridMatrix_.end()) {
1070         return true;
1071     }
1072     while (startRow != gridMatrix_.begin()) {
1073         --startRow;
1074         if (startRow->first < startMainLineIndex_ - cachedCount) {
1075             break;
1076         }
1077         for (const auto& cell : startRow->second) {
1078             if (cell.second > startIndex_) {
1079                 TAG_LOGW(AceLogTag::ACE_GRID,
1080                     "check grid matrix failed, index %{public}d is greater than startIndex %{public}d", cell.second,
1081                     startIndex_);
1082                 PrintMatrix();
1083                 return false;
1084             }
1085         }
1086     }
1087     return true;
1088 }
1089 
IsAllItemsMeasured() const1090 bool GridLayoutInfo::IsAllItemsMeasured() const
1091 {
1092     if (gridMatrix_.empty()) {
1093         return false;
1094     }
1095     auto allItemsMeasured = false;
1096     auto firstLine = gridMatrix_.begin();
1097     if (firstLine->first == 0) {
1098         if (firstLine->second.empty()) {
1099             return false;
1100         }
1101         auto firstItem = firstLine->second.begin();
1102         allItemsMeasured |= firstItem->second == 0;
1103     }
1104     if (allItemsMeasured) {
1105         auto lastLine = gridMatrix_.rbegin();
1106         if (lastLine->second.empty() || lastLine->first - firstLine->first > MAX_CUMULATIVE_LINES) {
1107             return false;
1108         }
1109         auto lastItem = lastLine->second.rbegin();
1110         allItemsMeasured &= lastItem->second == GetChildrenCount() - 1;
1111     }
1112     return allItemsMeasured;
1113 }
1114 
ToString() const1115 std::string GridLayoutInfo::ToString() const
1116 {
1117     return "startMainLine = " + std::to_string(startMainLineIndex_) + ", offset = " + std::to_string(currentOffset_) +
1118            ", endMainLine = " + std::to_string(endMainLineIndex_) + ", startIndex = " + std::to_string(startIndex_) +
1119            ", endIndex = " + std::to_string(endIndex_) + ", jumpIndex = " + std::to_string(jumpIndex_) +
1120            ", gridMatrix size = " + std::to_string(gridMatrix_.size()) +
1121            ", lineHeightMap size = " + std::to_string(lineHeightMap_.size());
1122 }
1123 } // namespace OHOS::Ace::NG
1124