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 
16 #include "core/components_ng/pattern/waterflow/layout/top_down/water_flow_layout_info.h"
17 
18 #include <algorithm>
19 
20 #include "core/components_ng/property/measure_property.h"
21 
22 constexpr float HALF = 0.5f;
23 
24 namespace OHOS::Ace::NG {
GetCrossIndex(int32_t itemIndex) const25 int32_t WaterFlowLayoutInfo::GetCrossIndex(int32_t itemIndex) const
26 {
27     if (static_cast<size_t>(itemIndex) < itemInfos_.size()) {
28         return itemInfos_[itemIndex].crossIdx;
29     }
30     for (const auto& crossItems : items_[GetSegment(itemIndex)]) {
31         auto iter = crossItems.second.find(itemIndex);
32         if (iter != crossItems.second.end()) {
33             return crossItems.first;
34         }
35     }
36     return -1;
37 }
38 
UpdateStartIndex()39 void WaterFlowLayoutInfo::UpdateStartIndex()
40 {
41     if (childrenCount_ == 0) {
42         return;
43     }
44     if (!itemInfos_.empty()) {
45         // don't use in new segmented layout
46         return;
47     }
48     auto mainHeight = GetMaxMainHeight();
49     // need more items for currentOffset_
50     if (LessOrEqual(currentOffset_ + mainHeight, 0.0f)) {
51         return;
52     }
53 
54     int32_t tempStartIndex = -1;
55     for (const auto& crossItems : items_[GetSegment(tempStartIndex)]) {
56         for (const auto& iter : crossItems.second) {
57             if (GreatNotEqual(iter.second.first + iter.second.second + currentOffset_, 0.0f)) {
58                 tempStartIndex = tempStartIndex != -1 ? std::min(tempStartIndex, iter.first) : iter.first;
59                 break;
60             }
61             // FlowItem that have not been loaded at the beginning of each cross need to be selected as startIndex_ for
62             // the ClearCache later.
63             if (NearZero(iter.second.first + iter.second.second) && NearZero(currentOffset_)) {
64                 tempStartIndex = tempStartIndex != -1 ? std::min(tempStartIndex, iter.first) : iter.first;
65                 break;
66             }
67         }
68     }
69     startIndex_ = tempStartIndex == -1 ? 0 : tempStartIndex;
70 }
71 
GetEndIndexByOffset(float offset) const72 int32_t WaterFlowLayoutInfo::GetEndIndexByOffset(float offset) const
73 {
74     int32_t endIndex = 0;
75     bool found = false;
76     for (const auto& crossItems : items_[GetSegment(endIndex)]) {
77         for (const auto& iter : crossItems.second) {
78             if (GreatNotEqual(iter.second.first + iter.second.second + offset, 0)) {
79                 endIndex = std::max(endIndex, iter.first);
80                 found = true;
81                 break;
82             }
83         }
84     }
85     return found ? endIndex : -1;
86 }
87 
GetMaxMainHeight() const88 float WaterFlowLayoutInfo::GetMaxMainHeight() const
89 {
90     if (!endPosArray_.empty()) {
91         const auto& margin = margins_.back();
92         return endPosArray_.back().first + (axis_ == Axis::VERTICAL ? margin.bottom : margin.right).value_or(0.0f);
93     }
94     if (items_.empty()) {
95         return 0.0f;
96     }
97     float result = 0.0f;
98     for (const auto& crossItems : *items_.rbegin()) {
99         if (crossItems.second.empty()) {
100             continue;
101         }
102         auto lastItem = crossItems.second.rbegin();
103         auto crossMainHeight = lastItem->second.first + lastItem->second.second;
104         if (NearEqual(result, 0.0f)) {
105             result = crossMainHeight;
106         }
107         if (LessNotEqual(result, crossMainHeight)) {
108             result = crossMainHeight;
109         }
110     }
111     return result;
112 }
113 
GetContentHeight() const114 float WaterFlowLayoutInfo::GetContentHeight() const
115 {
116     return NearZero(maxHeight_) ? GetMaxMainHeight() : maxHeight_;
117 }
118 
GetMainHeight(int32_t crossIndex,int32_t itemIndex) const119 float WaterFlowLayoutInfo::GetMainHeight(int32_t crossIndex, int32_t itemIndex) const
120 {
121     if (static_cast<size_t>(itemIndex) < itemInfos_.size() && itemInfos_[itemIndex].crossIdx == crossIndex) {
122         return itemInfos_[itemIndex].mainOffset + itemInfos_[itemIndex].mainSize;
123     }
124     auto seg = GetSegment(itemIndex);
125     float result = segmentStartPos_[seg];
126 
127     auto cross = items_[seg].find(crossIndex);
128     if (cross == items_[seg].end()) {
129         return result;
130     }
131     auto item = cross->second.find(itemIndex);
132     if (item == cross->second.end()) {
133         return result;
134     }
135     result = item->second.first + item->second.second;
136     return result;
137 }
138 
GetStartMainPos(int32_t crossIndex,int32_t itemIndex) const139 float WaterFlowLayoutInfo::GetStartMainPos(int32_t crossIndex, int32_t itemIndex) const
140 {
141     if (static_cast<size_t>(itemIndex) < itemInfos_.size() && itemInfos_[itemIndex].crossIdx == crossIndex) {
142         return itemInfos_[itemIndex].mainOffset;
143     }
144     float result = 0.0f;
145     auto cross = items_[GetSegment(itemIndex)].find(crossIndex);
146     if (cross == items_[GetSegment(itemIndex)].end()) {
147         return result;
148     }
149     auto item = cross->second.find(itemIndex);
150     if (item == cross->second.end()) {
151         return result;
152     }
153     result = item->second.first;
154     return result;
155 }
156 
GetOverScrolledDelta(float delta) const157 OverScrollOffset WaterFlowLayoutInfo::GetOverScrolledDelta(float delta) const
158 {
159     OverScrollOffset offset = { 0, 0 };
160     if (startIndex_ == 0) {
161         auto startPos = currentOffset_;
162         auto newStartPos = startPos + delta;
163         if (startPos > 0 && newStartPos > 0) {
164             offset.start = delta;
165         }
166         if (startPos > 0 && newStartPos <= 0) {
167             offset.start = -startPos;
168         }
169         if (startPos <= 0 && newStartPos > 0) {
170             offset.start = newStartPos;
171         }
172     }
173     if (itemEnd_) {
174         auto endPos = currentOffset_ + maxHeight_;
175         if (GreatNotEqual(lastMainSize_, currentOffset_ + maxHeight_)) {
176             endPos = currentOffset_ + lastMainSize_;
177         }
178         auto newEndPos = endPos + delta;
179         if (endPos < lastMainSize_ && newEndPos < lastMainSize_) {
180             offset.end = delta;
181         }
182         if (endPos < lastMainSize_ && newEndPos >= lastMainSize_) {
183             offset.end = lastMainSize_ - endPos;
184         }
185         if (endPos >= lastMainSize_ && newEndPos < lastMainSize_) {
186             offset.end = newEndPos - lastMainSize_;
187         }
188     }
189     return offset;
190 }
191 
IsAllCrossReachEnd(float mainSize) const192 bool WaterFlowLayoutInfo::IsAllCrossReachEnd(float mainSize) const
193 {
194     bool result = true;
195     for (const auto& crossItems : *items_.rbegin()) {
196         if (crossItems.second.empty()) {
197             result = false;
198             break;
199         }
200         auto lastItem = crossItems.second.rbegin();
201         auto lastOffset = lastItem->second.first + lastItem->second.second;
202         if (LessNotEqual(lastOffset + currentOffset_, mainSize)) {
203             result = false;
204             break;
205         }
206     }
207     return result;
208 }
209 
GetCrossIndexForNextItem(int32_t segmentIdx) const210 FlowItemIndex WaterFlowLayoutInfo::GetCrossIndexForNextItem(int32_t segmentIdx) const
211 {
212     FlowItemIndex position = { 0, -1 };
213     auto minHeight = -1.0f;
214     auto crossSize = static_cast<int32_t>(items_[segmentIdx].size());
215     for (int32_t i = 0; i < crossSize; ++i) {
216         const auto& crossItems = items_[segmentIdx].at(i);
217         if (crossItems.empty()) {
218             position.crossIndex = i;
219             position.lastItemIndex = -1;
220             break;
221         }
222         auto lastItem = crossItems.rbegin();
223         auto lastOffset = lastItem->second.first + lastItem->second.second;
224         if (NearEqual(minHeight, -1.0f)) {
225             minHeight = lastOffset;
226             position.crossIndex = i;
227             position.lastItemIndex = lastItem->first;
228         }
229         if (LessNotEqual(lastOffset, minHeight)) {
230             position.crossIndex = i;
231             position.lastItemIndex = lastItem->first;
232             minHeight = lastOffset;
233             // first item height in this cross is 0
234             if (NearZero(minHeight)) {
235                 break;
236             }
237         }
238     }
239 
240     return position;
241 }
242 
Reset()243 void WaterFlowLayoutInfo::Reset()
244 {
245     itemEnd_ = false;
246     itemStart_ = false;
247     offsetEnd_ = false;
248     maxHeight_ = 0.0f;
249 
250     jumpIndex_ = EMPTY_JUMP_INDEX;
251 
252     startIndex_ = 0;
253     endIndex_ = -1;
254     targetIndex_.reset();
255     items_ = { ItemMap() };
256     itemInfos_.clear();
257     endPosArray_.clear();
258     segmentTails_.clear();
259     margins_.clear();
260     segmentStartPos_ = { 0.0f };
261     segmentCache_.clear();
262 }
263 
Reset(int32_t resetFrom)264 void WaterFlowLayoutInfo::Reset(int32_t resetFrom)
265 {
266     TAG_LOGI(AceLogTag::ACE_WATERFLOW, "reset. updateIdx:%{public}d,endIndex:%{public}d", resetFrom, endIndex_);
267     int32_t itemCount = 0;
268     for (const auto& item : items_[0]) {
269         itemCount += static_cast<int32_t>(item.second.size());
270     }
271     if (resetFrom >= itemCount) {
272         return;
273     }
274     maxHeight_ = 0.0f;
275     ClearCacheAfterIndex(resetFrom - 1);
276     startIndex_ = std::max(resetFrom - 1, 0);
277 }
278 
GetCrossCount() const279 int32_t WaterFlowLayoutInfo::GetCrossCount() const
280 {
281     return static_cast<int32_t>(items_[0].size());
282 }
283 
GetMainCount() const284 int32_t WaterFlowLayoutInfo::GetMainCount() const
285 {
286     int32_t maxMainCount = 0;
287     for (const auto& crossItems : items_[0]) {
288         if (crossItems.second.empty()) {
289             continue;
290         }
291         auto mainCount = static_cast<int32_t>(std::count_if(crossItems.second.begin(), crossItems.second.end(),
292             [start = startIndex_, end = endIndex_](const std::pair<const int, std::pair<float, float>>& crossItem) {
293                 return crossItem.first >= start && crossItem.first <= end;
294             }));
295         maxMainCount = std::max(maxMainCount, mainCount);
296     }
297     return maxMainCount;
298 }
299 
ClearCacheAfterIndex(int32_t currentIndex)300 void WaterFlowLayoutInfo::ClearCacheAfterIndex(int32_t currentIndex)
301 {
302     size_t segment = static_cast<size_t>(GetSegment(currentIndex));
303     for (auto& crossItems : items_[segment]) {
304         if (crossItems.second.empty()) {
305             continue;
306         }
307         auto clearFrom = std::find_if(crossItems.second.begin(), crossItems.second.end(),
308             [currentIndex](const std::pair<const int, std::pair<float, float>>& crossItem) {
309                 return crossItem.first > currentIndex;
310             });
311         crossItems.second.erase(clearFrom, crossItems.second.end());
312     }
313     for (size_t i = segment + 1; i < items_.size(); ++i) {
314         for (auto& col : items_[i]) {
315             col.second.clear();
316         }
317     }
318 
319     if (static_cast<size_t>(currentIndex + 1) < itemInfos_.size()) {
320         itemInfos_.resize(currentIndex + 1);
321     }
322 
323     auto it = std::upper_bound(endPosArray_.begin(), endPosArray_.end(), currentIndex,
324         [](int32_t index, const std::pair<float, int32_t>& pos) { return index < pos.second; });
325     endPosArray_.erase(it, endPosArray_.end());
326 
327     if (segment + 1 < segmentStartPos_.size()) {
328         segmentStartPos_.resize(segment + 1);
329         if (currentIndex == segmentTails_[segment]) {
330             SetNextSegmentStartPos(currentIndex);
331         }
332     }
333 }
334 
ReachStart(float prevOffset,bool firstLayout) const335 bool WaterFlowLayoutInfo::ReachStart(float prevOffset, bool firstLayout) const
336 {
337     auto scrollUpToReachTop = (LessNotEqual(prevOffset, 0.0) || firstLayout) && GreatOrEqual(currentOffset_, 0.0);
338     auto scrollDownToReachTop = GreatNotEqual(prevOffset, 0.0) && LessOrEqual(currentOffset_, 0.0);
339     return scrollUpToReachTop || scrollDownToReachTop;
340 }
341 
ReachEnd(float prevOffset,bool firstLayout) const342 bool WaterFlowLayoutInfo::ReachEnd(float prevOffset, bool firstLayout) const
343 {
344     if (!offsetEnd_) {
345         return false;
346     }
347     float minOffset = lastMainSize_ - maxHeight_;
348     auto scrollDownToReachEnd =
349         (GreatNotEqual(prevOffset, minOffset) || firstLayout) && LessOrEqual(currentOffset_, minOffset);
350     auto scrollUpToReachEnd = LessNotEqual(prevOffset, minOffset) && GreatOrEqual(currentOffset_, minOffset);
351     return scrollDownToReachEnd || scrollUpToReachEnd;
352 }
353 
FastSolveStartIndex() const354 int32_t WaterFlowLayoutInfo::FastSolveStartIndex() const
355 {
356     if (NearZero(currentOffset_) && !endPosArray_.empty() && NearZero(endPosArray_[0].first)) {
357         return endPosArray_[0].second;
358     }
359     auto it = std::upper_bound(endPosArray_.begin(), endPosArray_.end(), -currentOffset_,
360         [](float value, const std::pair<float, int32_t>& info) { return LessNotEqual(value, info.first); });
361     if (it == endPosArray_.end()) {
362         return std::max(static_cast<int32_t>(itemInfos_.size()) - 1, 0);
363     }
364     return it->second;
365 }
366 
FastSolveEndIndex(float mainSize) const367 int32_t WaterFlowLayoutInfo::FastSolveEndIndex(float mainSize) const
368 {
369     if (itemInfos_.empty()) {
370         return -1;
371     }
372 
373     const float endBound = mainSize - currentOffset_;
374     auto it = std::lower_bound(itemInfos_.begin(), itemInfos_.end(), endBound,
375         [](const ItemInfo& info, float value) { return LessNotEqual(info.mainOffset, value); });
376 
377     // The last flowItem with the height of 0 should be regarded as endIndex_ when reach end.
378     while (it != itemInfos_.end() && NearZero(it->mainSize) && NearEqual(it->mainOffset, endBound)) {
379         ++it;
380     }
381     int32_t res = std::distance(itemInfos_.begin(), it) - 1;
382     return std::max(res, 0);
383 }
384 
RecordItem(int32_t idx,const FlowItemPosition & pos,float height)385 void WaterFlowLayoutInfo::RecordItem(int32_t idx, const FlowItemPosition& pos, float height)
386 {
387     if (itemInfos_.size() != static_cast<size_t>(idx)) {
388         return;
389     }
390     items_[GetSegment(idx)][pos.crossIndex][idx] = { pos.startMainPos, height };
391     itemInfos_.emplace_back(pos.crossIndex, pos.startMainPos, height);
392     if (endPosArray_.empty() || LessNotEqual(endPosArray_.back().first, pos.startMainPos + height)) {
393         endPosArray_.emplace_back(pos.startMainPos + height, idx);
394     }
395 
396     if (idx == segmentTails_[GetSegment(idx)]) {
397         SetNextSegmentStartPos(idx);
398     }
399 }
400 
SetNextSegmentStartPos(int32_t itemIdx)401 void WaterFlowLayoutInfo::SetNextSegmentStartPos(int32_t itemIdx)
402 {
403     auto segment = static_cast<size_t>(GetSegment(itemIdx));
404     if (segmentStartPos_.size() > segment + 1) {
405         return;
406     }
407     if (segmentStartPos_.size() <= segment || margins_.size() <= segment + 1) {
408         return;
409     }
410 
411     float nextStartPos = endPosArray_.empty() ? segmentStartPos_[segment] : endPosArray_.back().first;
412     while (segment < segmentTails_.size() - 1 && itemIdx == segmentTails_[segment]) {
413         // use while loop to skip empty segments
414         if (axis_ == Axis::VERTICAL) {
415             nextStartPos += margins_[segment].bottom.value_or(0.0f) + margins_[segment + 1].top.value_or(0.0f);
416         } else {
417             nextStartPos += margins_[segment].right.value_or(0.0f) + margins_[segment + 1].left.value_or(0.0f);
418         }
419         segmentStartPos_.push_back(nextStartPos);
420         ++segment;
421     }
422 }
423 
Sync(float mainSize,bool overScroll)424 void WaterFlowLayoutInfo::Sync(float mainSize, bool overScroll)
425 {
426     // adjust offset when it can't overScroll at top
427     if (!overScroll) {
428         currentOffset_ = std::min(currentOffset_, 0.0f);
429     }
430     endIndex_ = FastSolveEndIndex(mainSize);
431 
432     maxHeight_ = GetMaxMainHeight();
433 
434     itemStart_ = GreatOrEqual(currentOffset_, 0.0f);
435     itemEnd_ = endIndex_ >= 0 && endIndex_ == childrenCount_ - 1;
436     offsetEnd_ = itemEnd_ && GreatOrEqual(mainSize - currentOffset_, maxHeight_);
437     // adjust offset when it can't overScroll at bottom
438     if (offsetEnd_ && !overScroll) {
439         currentOffset_ = std::min(-maxHeight_ + mainSize, 0.0f);
440     }
441 
442     startIndex_ = FastSolveStartIndex();
443 }
444 
InitSegments(const std::vector<WaterFlowSections::Section> & sections,int32_t start)445 void WaterFlowLayoutInfo::InitSegments(const std::vector<WaterFlowSections::Section>& sections, int32_t start)
446 {
447     size_t n = sections.size();
448     if (n == 0) {
449         Reset();
450         currentOffset_ = 0.0f;
451         return;
452     }
453     segmentTails_ = { sections[0].itemsCount - 1 };
454     for (size_t i = 1; i < n; ++i) {
455         segmentTails_.push_back(segmentTails_[i - 1] + sections[i].itemsCount);
456     }
457 
458     segmentCache_.clear();
459     if (static_cast<size_t>(start) < segmentStartPos_.size()) {
460         segmentStartPos_.resize(start);
461         // startPos of next segment can only be determined after margins_ is reinitialized.
462     }
463 
464     int32_t lastValidItem = (start > 0) ? segmentTails_[start - 1] : -1;
465     if (static_cast<size_t>(lastValidItem + 1) < itemInfos_.size()) {
466         itemInfos_.resize(lastValidItem + 1);
467     }
468 
469     auto it = std::upper_bound(endPosArray_.begin(), endPosArray_.end(), lastValidItem,
470         [](int32_t index, const std::pair<float, int32_t>& pos) { return index < pos.second; });
471     endPosArray_.erase(it, endPosArray_.end());
472     items_.resize(n);
473     for (size_t i = static_cast<size_t>(start); i < n; ++i) {
474         items_[i].clear();
475         for (int32_t j = 0; j < sections[i].crossCount; ++j) {
476             items_[i][j] = {};
477         }
478     }
479 
480     margins_.clear(); // to be initialized during layout
481 }
482 
PrepareSegmentStartPos()483 void WaterFlowLayoutInfo::PrepareSegmentStartPos()
484 {
485     if (segmentStartPos_.size() <= 1) {
486         ResetSegmentStartPos();
487     }
488     int32_t lastItem = static_cast<int32_t>(itemInfos_.size()) - 1;
489     if (GetSegment(lastItem) >= static_cast<int32_t>(segmentTails_.size())) {
490         TAG_LOGW(AceLogTag::ACE_WATERFLOW, "Section data not initialized before layout");
491         return;
492     }
493     if (segmentTails_[GetSegment(lastItem)] == lastItem) {
494         SetNextSegmentStartPos(static_cast<int32_t>(itemInfos_.size()) - 1);
495     }
496 }
497 
ResetSegmentStartPos()498 void WaterFlowLayoutInfo::ResetSegmentStartPos()
499 {
500     if (margins_.empty()) {
501         segmentStartPos_ = { 0.0f };
502     } else {
503         segmentStartPos_ = { (axis_ == Axis::VERTICAL ? margins_[0].top : margins_[0].left).value_or(0.0f) };
504     }
505 }
506 
PrintWaterFlowItems() const507 void WaterFlowLayoutInfo::PrintWaterFlowItems() const
508 {
509     for (const auto& [key1, map1] : items_[0]) {
510         std::stringstream ss;
511         ss << key1 << ": {";
512         for (const auto& [key2, pair] : map1) {
513             ss << key2 << ": (" << pair.first << ", " << pair.second << ")";
514             if (&pair != &map1.rbegin()->second) {
515                 ss << ", ";
516             }
517         }
518         ss << "}";
519         LOGI("%{public}s", ss.str().c_str());
520     }
521 }
522 
JumpToTargetAlign(const std::pair<float,float> & item) const523 float WaterFlowLayoutInfo::JumpToTargetAlign(const std::pair<float, float>& item) const
524 {
525     float targetPosition = 0.0f;
526     ScrollAlign align = align_;
527     switch (align) {
528         case ScrollAlign::START:
529             targetPosition = -item.first;
530             break;
531         case ScrollAlign::END:
532             targetPosition = lastMainSize_ - (item.first + item.second);
533             break;
534         case ScrollAlign::AUTO:
535             if (currentOffset_ + item.first < 0) {
536                 targetPosition = -item.first;
537             } else if (currentOffset_ + item.first + item.second > lastMainSize_) {
538                 targetPosition = lastMainSize_ - (item.first + item.second);
539             } else {
540                 targetPosition = currentOffset_;
541             }
542             break;
543         case ScrollAlign::CENTER:
544             targetPosition = -item.first + (lastMainSize_ - item.second) * HALF;
545             break;
546         default:
547             break;
548     }
549     return targetPosition;
550 }
551 
JumpTo(const std::pair<float,float> & item)552 void WaterFlowLayoutInfo::JumpTo(const std::pair<float, float>& item)
553 {
554     currentOffset_ = JumpToTargetAlign(item);
555     if (extraOffset_.has_value()) {
556         currentOffset_ += extraOffset_.value();
557         extraOffset_.reset();
558     }
559     align_ = ScrollAlign::START;
560     jumpIndex_ = EMPTY_JUMP_INDEX;
561 }
562 
UpdateOffset(float delta)563 void WaterFlowLayoutInfo::UpdateOffset(float delta)
564 {
565     currentOffset_ += delta;
566 }
567 
CalcTargetPosition(int32_t idx,int32_t crossIdx) const568 float WaterFlowLayoutInfo::CalcTargetPosition(int32_t idx, int32_t crossIdx) const
569 {
570     return -JumpToTargetAlign(items_[GetSegment(idx)].at(crossIdx).at(idx));
571 }
572 
OutOfBounds() const573 bool WaterFlowLayoutInfo::OutOfBounds() const
574 {
575     bool outOfStart = itemStart_ && Positive(currentOffset_);
576     bool outOfEnd = offsetEnd_ && LessNotEqual(currentOffset_ + maxHeight_, lastMainSize_);
577     // not outOfEnd when content size < mainSize but currentOffset_ == 0
578     if (LessNotEqual(maxHeight_, lastMainSize_)) {
579         outOfEnd &= Negative(currentOffset_);
580     }
581     return outOfStart || outOfEnd;
582 }
583 
CalcOverScroll(float mainSize,float delta) const584 float WaterFlowLayoutInfo::CalcOverScroll(float mainSize, float delta) const
585 {
586     float res = 0;
587     if (itemStart_) {
588         res = currentOffset_ + delta;
589     }
590     if (offsetEnd_) {
591         res = mainSize - (GetMaxMainHeight() + currentOffset_ - delta);
592     }
593     return res;
594 }
595 
EstimateContentHeight() const596 float WaterFlowLayoutInfo::EstimateContentHeight() const
597 {
598     auto childCount = 0;
599     if (!itemInfos_.empty()) {
600         // in segmented layout
601         childCount = static_cast<int32_t>(itemInfos_.size());
602     } else if (maxHeight_) {
603         // in original layout, already reach end.
604         return maxHeight_;
605     } else {
606         // in original layout
607         for (const auto& item : items_[0]) {
608             childCount += static_cast<int32_t>(item.second.size());
609         }
610     }
611     if (childCount == 0) {
612         return 0;
613     }
614     auto estimateHeight = GetMaxMainHeight() / childCount * childrenCount_;
615     return estimateHeight;
616 }
617 
GetLastItem() const618 int32_t WaterFlowLayoutInfo::GetLastItem() const
619 {
620     int32_t res = -1;
621     if (items_.empty()) {
622         return res;
623     }
624     for (auto&& map : items_[0]) {
625         if (map.second.empty()) {
626             continue;
627         }
628         res = std::max(res, map.second.rbegin()->first);
629     }
630     return res;
631 }
632 } // namespace OHOS::Ace::NG
633