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