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