• 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 <numeric>
18 
19 #include "base/utils/utils.h"
20 #include "core/components_ng/pattern/scrollable/scrollable_properties.h"
21 
22 namespace OHOS::Ace::NG {
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             break;
148         }
149     }
150 }
151 
IsOutOfStart() const152 bool GridLayoutInfo::IsOutOfStart() const
153 {
154     return reachStart_ && Positive(currentOffset_);
155 }
156 
IsOutOfEnd(float mainGap,bool irregular) const157 bool GridLayoutInfo::IsOutOfEnd(float mainGap, bool irregular) const
158 {
159     const bool atOrOutOfStart = reachStart_ && NonNegative(currentOffset_);
160     if (irregular) {
161         return !atOrOutOfStart && Negative(GetDistanceToBottom(lastMainSize_, totalHeightOfItemsInView_, mainGap));
162     }
163     const float endPos = currentOffset_ + totalHeightOfItemsInView_;
164     return !atOrOutOfStart && (endIndex_ == childrenCount_ - 1) &&
165            LessNotEqual(endPos, lastMainSize_ - contentEndPadding_);
166 }
167 
GetCurrentOffsetOfRegularGrid(float mainGap) const168 float GridLayoutInfo::GetCurrentOffsetOfRegularGrid(float mainGap) const
169 {
170     if (lineHeightMap_.empty()) {
171         return 0.0f;
172     }
173     float defaultHeight = GetTotalLineHeight(0.0f) / static_cast<float>(lineHeightMap_.size());
174     if (crossCount_ == 0) {
175         return 0.0f;
176     }
177     auto lines = startIndex_ / crossCount_;
178     float res = 0.0f;
179     for (int i = 0; i < lines; ++i) {
180         auto it = lineHeightMap_.find(i);
181         res += (it != lineHeightMap_.end() ? it->second : defaultHeight) + mainGap;
182     }
183     return res - currentOffset_;
184 }
185 
GetContentOffset(float mainGap) const186 float GridLayoutInfo::GetContentOffset(float mainGap) const
187 {
188     if (lineHeightMap_.empty()) {
189         return 0.0f;
190     }
191     if (!hasBigItem_) {
192         return GetCurrentOffsetOfRegularGrid(mainGap);
193     }
194     // assume lineHeightMap is continuous in range [begin, rbegin].
195     int32_t itemCount = FindItemCount(lineHeightMap_.begin()->first, lineHeightMap_.rbegin()->first);
196     if (itemCount == 0) {
197         return 0.0f;
198     }
199     if (itemCount == childrenCount_ || (lineHeightMap_.begin()->first == 0 && itemCount >= startIndex_)) {
200         return GetStartLineOffset(mainGap);
201     }
202     // begin estimation
203     float heightSum = GetTotalLineHeight(mainGap, false);
204     if (itemCount == 0) {
205         return 0.0f;
206     }
207     auto averageHeight = heightSum / itemCount;
208     return startIndex_ * averageHeight - currentOffset_;
209 }
210 
FindItemCount(int32_t startLine,int32_t endLine) const211 int32_t GridLayoutInfo::FindItemCount(int32_t startLine, int32_t endLine) const
212 {
213     auto firstLine = gridMatrix_.find(startLine);
214     auto lastLine = gridMatrix_.find(endLine);
215     if (firstLine == gridMatrix_.end() || firstLine->second.empty()) {
216         for (auto i = startLine; i <= endLine; ++i) {
217             auto it = gridMatrix_.find(i);
218             if (it != gridMatrix_.end()) {
219                 firstLine = it;
220                 break;
221             }
222         }
223         if (firstLine == gridMatrix_.end() || firstLine->second.empty()) {
224             return 0;
225         }
226     }
227     if (lastLine == gridMatrix_.end() || lastLine->second.empty()) {
228         for (auto i = endLine; i >= startLine; --i) {
229             auto it = gridMatrix_.find(i);
230             if (it != gridMatrix_.end()) {
231                 lastLine = it;
232                 break;
233             }
234         }
235         if (lastLine == gridMatrix_.end() || lastLine->second.empty()) {
236             return 0;
237         }
238     }
239 
240     int32_t minIdx = firstLine->second.begin()->second;
241 
242     int32_t maxIdx = 0;
243     // maxIdx might not be in the last position if hasBigItem_
244     for (const auto& it : lastLine->second) {
245         maxIdx = std::max(maxIdx, it.second);
246     }
247     maxIdx = std::max(maxIdx, FindEndIdx(endLine).itemIdx);
248     return maxIdx - minIdx + 1;
249 }
250 
GetContentHeightOfRegularGrid(float mainGap) const251 float GridLayoutInfo::GetContentHeightOfRegularGrid(float mainGap) const
252 {
253     float res = 0.0f;
254     if (crossCount_ == 0 || lineHeightMap_.empty()) {
255         return res;
256     }
257     float lineHeight = GetTotalLineHeight(0.0f) / static_cast<float>(lineHeightMap_.size());
258     auto lines = (childrenCount_) / crossCount_;
259     for (int i = 0; i < lines; ++i) {
260         auto it = lineHeightMap_.find(i);
261         res += (it != lineHeightMap_.end() ? it->second : lineHeight) + mainGap;
262     }
263     if (childrenCount_ % crossCount_ == 0) {
264         return res - mainGap;
265     }
266     auto lastLine = lineHeightMap_.find(lines);
267     return res + (lastLine != lineHeightMap_.end() ? lastLine->second : lineHeight);
268 }
269 
GetContentHeight(float mainGap) const270 float GridLayoutInfo::GetContentHeight(float mainGap) const
271 {
272     if (!hasBigItem_) {
273         return GetContentHeightOfRegularGrid(mainGap);
274     }
275     if (lineHeightMap_.empty()) {
276         return 0.0f;
277     }
278     float heightSum = GetTotalLineHeight(mainGap, false);
279     // assume lineHeightMap is continuous in range [begin, rbegin]
280     int32_t itemCount = FindItemCount(lineHeightMap_.begin()->first, lineHeightMap_.rbegin()->first);
281     if (itemCount == 0) {
282         return 0.0f;
283     }
284     float averageHeight = heightSum / itemCount;
285 
286     if (itemCount == childrenCount_) {
287         return heightSum - mainGap;
288     }
289     return heightSum + (childrenCount_ - itemCount) * averageHeight;
290 }
291 
GetContentOffset(const GridLayoutOptions & options,float mainGap) const292 float GridLayoutInfo::GetContentOffset(const GridLayoutOptions& options, float mainGap) const
293 {
294     if (startIndex_ == 0) {
295         return -currentOffset_;
296     }
297     if (options.irregularIndexes.empty() || startIndex_ < *(options.irregularIndexes.begin())) {
298         return GetCurrentOffsetOfRegularGrid(mainGap);
299     }
300     if (options.getSizeByIndex) {
301         return GetContentOffset(mainGap);
302     }
303     float prevHeight = GetContentHeight(options, startIndex_, mainGap) + mainGap;
304     return prevHeight - currentOffset_;
305 }
306 
307 namespace {
308 // 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)309 inline float AddLinesInBetween(int32_t prevIdx, int32_t idx, int32_t crossCount, float lineHeight)
310 {
311     if (crossCount == 0) {
312         return 0.0f;
313     }
314     return (idx - prevIdx) > 1 ? ((idx - 2 - prevIdx) / crossCount + 1) * lineHeight : 0.0f;
315 }
316 } // namespace
317 
GetLineHeights(const GridLayoutOptions & options,float mainGap,float & regularHeight,float & irregularHeight) const318 void GridLayoutInfo::GetLineHeights(
319     const GridLayoutOptions& options, float mainGap, float& regularHeight, float& irregularHeight) const
320 {
321     for (const auto& item : lineHeightMap_) {
322         auto line = gridMatrix_.find(item.first);
323         if (line == gridMatrix_.end()) {
324             continue;
325         }
326         if (line->second.empty() || LessOrEqual(item.second, 0.0f)) {
327             continue;
328         }
329         auto lineStart = line->second.begin()->second;
330         if (options.irregularIndexes.find(lineStart) != options.irregularIndexes.end()) {
331             irregularHeight = item.second + mainGap;
332         } else {
333             if (NearZero(regularHeight)) {
334                 regularHeight = item.second + mainGap;
335             }
336         }
337         if (!(NearZero(irregularHeight) || NearZero(regularHeight))) {
338             break;
339         }
340     }
341 }
342 
GetContentHeight(const GridLayoutOptions & options,int32_t endIdx,float mainGap) const343 float GridLayoutInfo::GetContentHeight(const GridLayoutOptions& options, int32_t endIdx, float mainGap) const
344 {
345     if (options.irregularIndexes.empty()) {
346         return GetContentHeightOfRegularGrid(mainGap);
347     }
348     if (options.getSizeByIndex) {
349         return GetContentHeight(mainGap);
350     }
351 
352     float irregularHeight = 0.0f;
353     float regularHeight = 0.0f;
354     GetLineHeights(options, mainGap, regularHeight, irregularHeight);
355     if (NearZero(irregularHeight)) {
356         irregularHeight = lastIrregularMainSize_;
357     }
358     if (NearZero(regularHeight)) {
359         regularHeight = lastRegularMainSize_;
360     }
361 
362     // get line count
363     auto firstIrregularIndex = *(options.irregularIndexes.begin());
364     float totalHeight = AddLinesInBetween(-1, firstIrregularIndex, crossCount_, regularHeight);
365     auto lastIndex = firstIrregularIndex;
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 = 0.0f;
427     float regularHeight = 0.0f;
428     GetLineHeights(options, mainGap, regularHeight, irregularHeight);
429     if (NearZero(irregularHeight)) {
430         irregularHeight = lastIrregularMainSize_;
431     } else {
432         lastIrregularMainSize_ = irregularHeight;
433     }
434     if (NearZero(regularHeight)) {
435         regularHeight = lastRegularMainSize_;
436     } else {
437         lastRegularMainSize_ = regularHeight;
438     }
439 
440     int32_t firstIrregularIndex = *(options.irregularIndexes.begin());
441     float totalHeight = AddLinesInBetween(-1, firstIrregularIndex, crossCount_, regularHeight);
442     int32_t lastIndex = GreatNotEqual(totalHeight, targetContent) ? 0 : firstIrregularIndex;
443     float lastHeight = 0.0f;
444 
445     for (int32_t idx : options.irregularIndexes) {
446         if (GreatOrEqual(totalHeight, targetContent)) {
447             break;
448         }
449         lastHeight = totalHeight;
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     int32_t lines = static_cast<int32_t>(std::floor((targetContent - lastHeight) / regularHeight));
459     currentOffset_ = lastHeight + lines * regularHeight - targetContent;
460     int32_t startIdx = lines * crossCount_ + lastIndex;
461     startIndex_ = std::min(startIdx, childrenCount_ - 1);
462 }
463 
GetCurrentLineHeight() const464 float GridLayoutInfo::GetCurrentLineHeight() const
465 {
466     auto currentLineHeight = lineHeightMap_.find(startMainLineIndex_);
467     auto currentLineMatrix = gridMatrix_.find(startMainLineIndex_);
468     // if current line exist, find it
469     if (currentLineHeight != lineHeightMap_.end() && currentLineMatrix != gridMatrix_.end() &&
470         !currentLineMatrix->second.empty()) {
471         return currentLineHeight->second;
472     }
473 
474     // otherwise return the first line in cache
475     for (const auto& item : lineHeightMap_) {
476         auto line = gridMatrix_.find(item.first);
477         if (line == gridMatrix_.end()) {
478             continue;
479         }
480         if (line->second.empty()) {
481             continue;
482         }
483         return item.second;
484     }
485     return 0.0f;
486 }
487 
FindItemInRange(int32_t target) const488 std::pair<int32_t, int32_t> GridLayoutInfo::FindItemInRange(int32_t target) const
489 {
490     if (gridMatrix_.empty()) {
491         return { -1, -1 };
492     }
493     for (int r = startMainLineIndex_; r <= endMainLineIndex_; ++r) {
494         const auto& row = gridMatrix_.at(r);
495         for (const auto& it : row) {
496             if (it.second == target) {
497                 return { r, it.first };
498             }
499         }
500     }
501     return { -1, -1 };
502 }
503 
504 // Use the index to get the line number where the item is located
GetLineIndexByIndex(int32_t targetIndex,int32_t & targetLineIndex) const505 bool GridLayoutInfo::GetLineIndexByIndex(int32_t targetIndex, int32_t& targetLineIndex) const
506 {
507     for (auto [lineIndex, lineMap] : gridMatrix_) {
508         for (auto [crossIndex, index] : lineMap) {
509             if (index == targetIndex) {
510                 targetLineIndex = lineIndex;
511                 return true;
512             }
513         }
514     }
515     return false;
516 }
517 
518 // get the total height of all rows from zero before the targetLineIndex
GetTotalHeightFromZeroIndex(int32_t targetLineIndex,float mainGap) const519 float GridLayoutInfo::GetTotalHeightFromZeroIndex(int32_t targetLineIndex, float mainGap) const
520 {
521     auto targetPos = 0.f;
522     for (auto [lineIndex, lineHeight] : lineHeightMap_) {
523         if (targetLineIndex > lineIndex) {
524             targetPos += lineHeight + mainGap;
525         } else {
526             break;
527         }
528     }
529     return targetPos;
530 }
531 
TransformAutoScrollAlign(int32_t itemIdx,int32_t height,float mainSize,float mainGap) const532 ScrollAlign GridLayoutInfo::TransformAutoScrollAlign(
533     int32_t itemIdx, int32_t height, float mainSize, float mainGap) const
534 {
535     if (itemIdx >= startIndex_ && itemIdx <= endIndex_) {
536         auto [line, _] = FindItemInRange(itemIdx);
537         float topPos = GetItemTopPos(line, mainGap);
538         float botPos = GetItemBottomPos(line, height, mainGap);
539         if (NonPositive(topPos) && GreatOrEqual(botPos, mainSize)) {
540             // item occupies the whole viewport
541             return ScrollAlign::NONE;
542         }
543         // scrollAlign start / end if the item is not fully in viewport
544         if (Negative(topPos)) {
545             return ScrollAlign::START;
546         }
547         if (GreatNotEqual(botPos, mainSize)) {
548             return ScrollAlign::END;
549         }
550         return ScrollAlign::NONE;
551     }
552     if (itemIdx > endIndex_) {
553         return ScrollAlign::END;
554     }
555     return ScrollAlign::START;
556 }
557 
GetAnimatePosIrregular(int32_t targetIdx,int32_t height,ScrollAlign align,float mainGap) const558 float GridLayoutInfo::GetAnimatePosIrregular(int32_t targetIdx, int32_t height, ScrollAlign align, float mainGap) const
559 {
560     if (targetIdx == LAST_ITEM) {
561         targetIdx = childrenCount_ - 1;
562     }
563     auto it = FindInMatrix(targetIdx);
564     if (it == gridMatrix_.end()) {
565         return -1.0f;
566     }
567     if (align == ScrollAlign::AUTO) {
568         align = TransformAutoScrollAlign(targetIdx, height, lastMainSize_, mainGap);
569     }
570     switch (align) {
571         case ScrollAlign::START:
572             return GetTotalHeightFromZeroIndex(it->first, mainGap);
573         case ScrollAlign::CENTER: {
574             auto [center, offset] = FindItemCenter(it->first, height, mainGap);
575             float res = GetTotalHeightFromZeroIndex(center, mainGap) + offset - lastMainSize_ / 2.0f;
576             return std::max(res, 0.0f);
577         }
578         case ScrollAlign::END: {
579             float res = GetTotalHeightFromZeroIndex(it->first + height, mainGap) - mainGap - lastMainSize_;
580             return std::max(res, 0.0f);
581         }
582         default:
583             return -1.0f;
584     }
585 }
586 
587 // 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)588 bool GridLayoutInfo::GetGridItemAnimatePos(const GridLayoutInfo& currentGridLayoutInfo, int32_t targetIndex,
589     ScrollAlign align, float mainGap, float& targetPos)
590 {
591     int32_t startMainLineIndex = currentGridLayoutInfo.startMainLineIndex_;
592     int32_t endMainLineIndex = currentGridLayoutInfo.endMainLineIndex_;
593     float lastMainSize = currentGridLayoutInfo.lastMainSize_;
594     int32_t targetLineIndex = -1;
595     // Pre-check
596     // Get the line number where the index is located. If targetIndex does not exist, it is returned.
597     CHECK_NULL_RETURN(GetLineIndexByIndex(targetIndex, targetLineIndex), false);
598 
599     // Get the total height of the targetPos from row 0 to targetLineIndex-1.
600     targetPos = GetTotalHeightFromZeroIndex(targetLineIndex, mainGap);
601 
602     // Find the target row and get the altitude information
603     auto targetItem = lineHeightMap_.find(targetLineIndex);
604 
605     // Make sure that the target line has height information
606     CHECK_NULL_RETURN(targetItem != lineHeightMap_.end(), false);
607     auto targetLineHeight = targetItem->second;
608 
609     // Depending on align, calculate where you need to scroll to
610     switch (align) {
611         case ScrollAlign::START:
612         case ScrollAlign::NONE:
613             break;
614         case ScrollAlign::CENTER: {
615             targetPos -= ((lastMainSize - targetLineHeight) * HALF);
616             break;
617         }
618         case ScrollAlign::END: {
619             targetPos -= (lastMainSize - targetLineHeight);
620             break;
621         }
622         case ScrollAlign::AUTO: {
623             GetLineIndexByIndex(currentGridLayoutInfo.startIndex_, startMainLineIndex);
624             GetLineIndexByIndex(currentGridLayoutInfo.endIndex_, endMainLineIndex);
625             auto targetPosBeforeStartIndex = GetTotalHeightFromZeroIndex(startMainLineIndex, mainGap);
626             // targetPos - targetPosBeforeStartIndex:The distance between the top of the startLine row and the top of
627             // the targetLine row
628             // The distance of the targetLine row from the top of the screen
629             auto height2Top = targetPos - targetPosBeforeStartIndex - std::abs(currentGridLayoutInfo.currentOffset_);
630             // The distance of the targetLine row from the bottom of the screen
631             auto height2Bottom = std::abs(currentGridLayoutInfo.currentOffset_) + lastMainSize - targetPos +
632                                  targetPosBeforeStartIndex - targetLineHeight;
633             // This is handled when the targetLine line is the same as the endLine line. As for the period between
634             // startLine and endLine, follow the following process
635             if (GreatOrEqual(height2Top, 0.f) && GreatOrEqual(height2Bottom, 0.f)) {
636                 return false;
637             }
638             // When the row height is greater than the screen height and occupies the entire screen height, do nothing
639             if ((startMainLineIndex == targetLineIndex) && (endMainLineIndex == targetLineIndex)) {
640                 if ((std::abs(currentGridLayoutInfo.currentOffset_) + lastMainSize - targetLineHeight) <= 0) {
641                     return false;
642                 }
643             }
644             if (startMainLineIndex >= targetLineIndex) {
645             } else if (targetLineIndex >= endMainLineIndex) {
646                 targetPos -= (lastMainSize - targetLineHeight);
647             } else {
648                 return false;
649             }
650             break;
651         }
652     }
653     return true;
654 }
655 
656 namespace {
CheckRow(int32_t & maxV,const std::map<int,int> & row,int32_t target)657 bool CheckRow(int32_t& maxV, const std::map<int, int>& row, int32_t target)
658 {
659     for (auto [_, item] : row) {
660         maxV = std::max(maxV, std::abs(item));
661         if (item == target) {
662             return true;
663         }
664     }
665     return false;
666 }
667 
668 using MatIter = decltype(GridLayoutInfo::gridMatrix_)::const_iterator;
669 
SearchInReverse(const decltype(GridLayoutInfo::gridMatrix_) & mat,int32_t target,int32_t crossCnt)670 MatIter SearchInReverse(const decltype(GridLayoutInfo::gridMatrix_)& mat, int32_t target, int32_t crossCnt)
671 {
672     for (auto it = mat.rbegin(); it != mat.rend(); ++it) {
673         int32_t maxV = -1;
674         if (CheckRow(maxV, it->second, target)) {
675             return (++it).base();
676         }
677         if (static_cast<int32_t>(it->second.size()) == crossCnt && maxV < target) {
678             break;
679         }
680     }
681     return mat.end();
682 }
683 } // namespace
684 
FindInMatrix(int32_t index) const685 MatIter GridLayoutInfo::FindInMatrix(int32_t index) const
686 {
687     if (crossCount_ == 0) {
688         return gridMatrix_.end();
689     }
690     if (index == 0) {
691         return gridMatrix_.begin();
692     }
693     size_t count = gridMatrix_.size();
694     size_t step = 0;
695     auto left = gridMatrix_.begin();
696     auto it = left;
697     while (count > 0) {
698         it = left;
699         step = count / 2;
700         std::advance(it, step);
701 
702         // with irregular items, only the max index on each row is guaranteed to be in order.
703         int32_t maxV = -1;
704         if (CheckRow(maxV, it->second, index)) {
705             return it;
706         }
707 
708         if (index <= maxV) {
709             count = step;
710         } else {
711             // index on the right side of current row
712             left = ++it;
713             count -= step + 1;
714         }
715     }
716     /*
717     Fallback to linear to handle this situation:
718         1 | 2 | 3
719         x | 2 | x
720         x | 2 | x
721         x | 2 | x
722     When iterator points to Line 1 ~ 3, Item 3 can never be found.
723     */
724     return SearchInReverse(gridMatrix_, index, crossCount_);
725 }
726 
GetItemPos(int32_t itemIdx) const727 std::pair<int32_t, int32_t> GridLayoutInfo::GetItemPos(int32_t itemIdx) const
728 {
729     auto it = FindInMatrix(itemIdx);
730     if (it == gridMatrix_.end()) {
731         return { -1, -1 };
732     }
733     for (auto col : it->second) {
734         if (col.second == itemIdx) {
735             return { col.first, it->first };
736         }
737     }
738     return { -1, -1 };
739 }
740 
FindEndIdx(int32_t endLine) const741 GridLayoutInfo::EndIndexInfo GridLayoutInfo::FindEndIdx(int32_t endLine) const
742 {
743     if (gridMatrix_.find(endLine) == gridMatrix_.end()) {
744         return {};
745     }
746     for (int32_t rowIdx = endLine; rowIdx >= 0; --rowIdx) {
747         const auto& row = gridMatrix_.at(rowIdx);
748         for (auto it = row.rbegin(); it != row.rend(); ++it) {
749             if (it->second > 0) {
750                 return { .itemIdx = it->second, .y = rowIdx, .x = it->first };
751             }
752         }
753     }
754     return { .itemIdx = 0, .y = 0, .x = 0 };
755 }
756 
ClearMapsToEnd(int32_t idx)757 void GridLayoutInfo::ClearMapsToEnd(int32_t idx)
758 {
759     if (hasMultiLineItem_) {
760         ClearMapsToEndContainsMultiLineItem(idx - 1);
761         return;
762     }
763     auto gridIt = gridMatrix_.lower_bound(idx);
764     gridMatrix_.erase(gridIt, gridMatrix_.end());
765     ClearHeightsToEnd(idx);
766 }
767 
ClearMapsToEndContainsMultiLineItem(int32_t idx)768 void GridLayoutInfo::ClearMapsToEndContainsMultiLineItem(int32_t idx)
769 {
770     int32_t maxIndex = INT_MIN;
771     for (const auto& col : gridMatrix_[idx]) {
772         maxIndex = std::max(maxIndex, col.second);
773     }
774 
775     int targetLine = idx;
776     while (targetLine < gridMatrix_.rbegin()->first) {
777         int32_t minIndex = INT_MAX;
778         for (const auto& col : gridMatrix_[targetLine + 1]) {
779             minIndex = std::min(minIndex, col.second);
780         }
781         if (maxIndex < minIndex) {
782             break;
783         }
784         targetLine++;
785     }
786     gridMatrix_.erase(gridMatrix_.find(targetLine + 1), gridMatrix_.end());
787 
788     auto lineIt = lineHeightMap_.find(targetLine + 1);
789     if (lineIt != lineHeightMap_.end()) {
790         lineHeightMap_.erase(lineIt, lineHeightMap_.end());
791     }
792 }
793 
ClearMapsFromStart(int32_t idx)794 void GridLayoutInfo::ClearMapsFromStart(int32_t idx)
795 {
796     if (hasMultiLineItem_) {
797         ClearMapsFromStartContainsMultiLineItem(idx);
798         return;
799     }
800     auto gridIt = gridMatrix_.lower_bound(idx);
801     gridMatrix_.erase(gridMatrix_.begin(), gridIt);
802     auto lineIt = lineHeightMap_.lower_bound(idx);
803     lineHeightMap_.erase(lineHeightMap_.begin(), lineIt);
804 }
805 
ClearMapsFromStartContainsMultiLineItem(int32_t idx)806 void GridLayoutInfo::ClearMapsFromStartContainsMultiLineItem(int32_t idx)
807 {
808     int32_t minIndex = INT_MAX;
809     for (const auto& col : gridMatrix_[idx]) {
810         minIndex = std::min(minIndex, col.second);
811     }
812 
813     auto iter = gridMatrix_.begin();
814     int targetLine = idx;
815     while (targetLine > iter->first) {
816         int32_t maxIndex = INT_MIN;
817         for (const auto& col : gridMatrix_[targetLine - 1]) {
818             maxIndex = std::max(maxIndex, col.second);
819         }
820         if (maxIndex < minIndex) {
821             break;
822         }
823         targetLine--;
824     }
825     gridMatrix_.erase(gridMatrix_.begin(), gridMatrix_.find(targetLine));
826 
827     auto lineIt = lineHeightMap_.find(targetLine);
828     if (lineIt != lineHeightMap_.end()) {
829         lineHeightMap_.erase(lineHeightMap_.begin(), lineIt);
830     }
831 }
832 
ClearHeightsToEnd(int32_t idx)833 void GridLayoutInfo::ClearHeightsToEnd(int32_t idx)
834 {
835     auto lineIt = lineHeightMap_.lower_bound(idx);
836     lineHeightMap_.erase(lineIt, lineHeightMap_.end());
837 }
838 
ClearMatrixToEnd(int32_t idx,int32_t lineIdx)839 void GridLayoutInfo::ClearMatrixToEnd(int32_t idx, int32_t lineIdx)
840 {
841     auto it = gridMatrix_.find(lineIdx);
842     for (; it != gridMatrix_.end(); ++it) {
843         for (auto itemIt = it->second.begin(); itemIt != it->second.end();) {
844             if (std::abs(itemIt->second) < idx) {
845                 ++itemIt;
846                 continue;
847             }
848             itemIt = it->second.erase(itemIt);
849         }
850         if (it->second.empty()) {
851             break;
852         }
853     }
854     gridMatrix_.erase(it, gridMatrix_.end());
855 }
856 
GetTotalHeightOfItemsInView(float mainGap,bool regular) const857 float GridLayoutInfo::GetTotalHeightOfItemsInView(float mainGap, bool regular) const
858 {
859     float len = 0.0f;
860     auto it = lineHeightMap_.find(startMainLineIndex_);
861     if (!regular) {
862         it = SkipLinesAboveView(mainGap).first;
863     }
864     if (it == lineHeightMap_.end()) {
865         return -mainGap;
866     }
867     if (startMainLineIndex_ > endMainLineIndex_ || it->first > endMainLineIndex_) {
868         return -mainGap;
869     }
870     auto endIt = lineHeightMap_.find(endMainLineIndex_ + 1);
871     for (; it != endIt; ++it) {
872         len += it->second + mainGap;
873     }
874     return len - mainGap;
875 }
876 
SkipLinesAboveView(float mainGap) const877 std::pair<GridLayoutInfo::HeightMapIt, float> GridLayoutInfo::SkipLinesAboveView(float mainGap) const
878 {
879     auto it = lineHeightMap_.find(startMainLineIndex_);
880     float offset = currentOffset_;
881     while (it != lineHeightMap_.end() && Negative(it->second + offset + mainGap)) {
882         offset += it->second + mainGap;
883         ++it;
884     }
885     return { it, offset };
886 }
887 
UpdateStartIndexForExtralOffset(float mainGap,float mainSize)888 void GridLayoutInfo::UpdateStartIndexForExtralOffset(float mainGap, float mainSize)
889 {
890     if (Negative(currentOffset_)) {
891         auto startLineHeight = lineHeightMap_.find(startMainLineIndex_);
892         CHECK_NULL_VOID(startLineHeight != lineHeightMap_.end());
893         auto currentEndOffset = currentOffset_ + startLineHeight->second + mainGap;
894         while (!Positive(currentEndOffset)) {
895             startMainLineIndex_++;
896             startLineHeight = lineHeightMap_.find(startMainLineIndex_);
897             if (startLineHeight == lineHeightMap_.end()) {
898                 startMainLineIndex_--;
899                 break;
900             }
901             currentOffset_ = currentEndOffset;
902             currentEndOffset += (startLineHeight->second + mainGap);
903         }
904     } else if (Positive(currentOffset_)) {
905         auto preLineHeight = lineHeightMap_.find(startMainLineIndex_ - 1);
906         CHECK_NULL_VOID(preLineHeight != lineHeightMap_.end());
907         auto preItemCurrentOffset = currentOffset_ - preLineHeight->second - mainGap;
908         while (Positive(preItemCurrentOffset)) {
909             startMainLineIndex_--;
910             preLineHeight = lineHeightMap_.find(startMainLineIndex_);
911             if (preLineHeight == lineHeightMap_.end()) {
912                 startMainLineIndex_++;
913                 break;
914             }
915             preItemCurrentOffset -= (preLineHeight->second + mainGap);
916             currentOffset_ = preItemCurrentOffset;
917         }
918     }
919     auto startLine = gridMatrix_.find(startMainLineIndex_);
920     CHECK_NULL_VOID(startLine != gridMatrix_.end() && (!startLine->second.empty()));
921     startIndex_ = startLine->second.begin()->second;
922     auto endLineHeight = lineHeightMap_.find(startMainLineIndex_);
923     CHECK_NULL_VOID(endLineHeight != lineHeightMap_.end());
924     endMainLineIndex_ = startMainLineIndex_;
925     auto currentEndOffset = currentOffset_ + endLineHeight->second + mainGap;
926     while (LessNotEqual(currentEndOffset, mainSize)) {
927         endMainLineIndex_++;
928         endLineHeight = lineHeightMap_.find(endMainLineIndex_);
929         if (endLineHeight == lineHeightMap_.end()) {
930             endMainLineIndex_--;
931             break;
932         }
933         currentEndOffset += (endLineHeight->second + mainGap);
934     }
935     auto endLine = gridMatrix_.find(endMainLineIndex_);
936     CHECK_NULL_VOID(endLine != gridMatrix_.end() && (!endLine->second.empty()));
937     endIndex_ = endLine->second.rbegin()->second;
938 }
939 
GetDistanceToBottom(float mainSize,float heightInView,float mainGap) const940 float GridLayoutInfo::GetDistanceToBottom(float mainSize, float heightInView, float mainGap) const
941 {
942     if (lineHeightMap_.empty() || endIndex_ < childrenCount_ - 1 ||
943         endMainLineIndex_ < lineHeightMap_.rbegin()->first) {
944         return Infinity<float>();
945     }
946 
947     float offset = currentOffset_;
948     // currentOffset_ is relative to startMainLine, which might be entirely above viewport
949     auto it = lineHeightMap_.find(startMainLineIndex_);
950     while (it != lineHeightMap_.end() && Negative(offset + it->second + mainGap)) {
951         offset += it->second + mainGap;
952         ++it;
953     }
954     const float bottomPos = offset + heightInView;
955     return bottomPos - mainSize;
956 }
957 
ClearHeightsFromMatrix(int32_t lineIdx)958 void GridLayoutInfo::ClearHeightsFromMatrix(int32_t lineIdx)
959 {
960     auto lineIt = lineHeightMap_.find(lineIdx);
961     if (lineIt == lineHeightMap_.end()) {
962         return;
963     }
964     if (gridMatrix_.find(lineIdx) != gridMatrix_.end()) {
965         lineIt++;
966     }
967     lineHeightMap_.erase(lineIt, lineHeightMap_.end());
968 }
969 
FindStartLineInMatrix(MatIter iter,int32_t index) const970 MatIter GridLayoutInfo::FindStartLineInMatrix(MatIter iter, int32_t index) const
971 {
972     if (iter == gridMatrix_.end() || iter == gridMatrix_.begin()) {
973         return iter;
974     }
975 
976     --iter;
977     int32_t maxValue = 0;
978     while (CheckRow(maxValue, iter->second, index)) {
979         if (iter == gridMatrix_.begin()) {
980             return iter;
981         }
982         --iter;
983     }
984     return ++iter;
985 }
986 
GetHeightInRange(int32_t startLine,int32_t endLine,float mainGap) const987 float GridLayoutInfo::GetHeightInRange(int32_t startLine, int32_t endLine, float mainGap) const
988 {
989     if (endLine <= startLine) {
990         return 0.0f;
991     }
992     auto endIt = lineHeightMap_.lower_bound(endLine);
993     auto it = lineHeightMap_.find(startLine);
994     if (it == lineHeightMap_.end()) {
995         return 0.0f;
996     }
997     float totalHeight = 0.0f;
998     for (; it != lineHeightMap_.end() && it != endIt; ++it) {
999         totalHeight += it->second + mainGap;
1000     }
1001     return totalHeight;
1002 }
1003 
HeightSumSmaller(float other,float mainGap) const1004 bool GridLayoutInfo::HeightSumSmaller(float other, float mainGap) const
1005 {
1006     other += mainGap;
1007     for (const auto& it : lineHeightMap_) {
1008         other -= it.second + mainGap;
1009         if (NonPositive(other)) {
1010             return false;
1011         }
1012     }
1013     return true;
1014 }
1015 
FindItemCenter(int32_t startLine,int32_t lineCnt,float mainGap) const1016 std::pair<int32_t, float> GridLayoutInfo::FindItemCenter(int32_t startLine, int32_t lineCnt, float mainGap) const
1017 {
1018     float halfLen = (GetHeightInRange(startLine, startLine + lineCnt, mainGap) - mainGap) / 2.0f;
1019     auto it = lineHeightMap_.find(startLine);
1020     float len = 0.0f;
1021     while (it != lineHeightMap_.end() && LessNotEqual(len + it->second + mainGap, halfLen)) {
1022         len += it->second + mainGap;
1023         ++it;
1024     }
1025     return { it->first, halfLen - len };
1026 }
1027 
PrepareJumpToBottom()1028 void GridLayoutInfo::PrepareJumpToBottom()
1029 {
1030     if (gridMatrix_.empty() || gridMatrix_.rbegin()->second.empty()) {
1031         TAG_LOGW(ACE_GRID, "Matrix setup is incorrect");
1032         jumpIndex_ = LAST_ITEM;
1033     } else {
1034         jumpIndex_ = std::abs(gridMatrix_.rbegin()->second.begin()->second);
1035     }
1036     scrollAlign_ = ScrollAlign::END;
1037 }
1038 
UpdateDefaultCachedCount()1039 void GridLayoutInfo::UpdateDefaultCachedCount()
1040 {
1041     if (crossCount_ == 0) {
1042         return;
1043     }
1044     static float pageCount = SystemProperties::GetPageCount();
1045     if (pageCount <= 0.0f) {
1046         return;
1047     }
1048     int32_t itemCount = (endIndex_ - startIndex_ + 1) / crossCount_;
1049     if (itemCount <= 0) {
1050         return;
1051     }
1052     constexpr int32_t MAX_DEFAULT_CACHED_COUNT = 16;
1053     int32_t newCachedCount = static_cast<int32_t>(ceil(pageCount * itemCount));
1054     if (newCachedCount > MAX_DEFAULT_CACHED_COUNT) {
1055         TAG_LOGI(ACE_GRID, "Default cachedCount exceed 16");
1056         defCachedCount_ = MAX_DEFAULT_CACHED_COUNT;
1057     } else {
1058         defCachedCount_ = std::max(newCachedCount, defCachedCount_);
1059     }
1060 }
1061 } // namespace OHOS::Ace::NG
1062