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