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