1 /*
2  * Copyright (c) 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 #ifndef FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERNS_LIST_LIST_POSITION_MAP_H
17 #define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERNS_LIST_LIST_POSITION_MAP_H
18 
19 
20 #include <cstddef>
21 #include <cstdint>
22 #include <functional>
23 #include <optional>
24 #include <tuple>
25 #include <queue>
26 
27 #include "base/geometry/dimension.h"
28 #include "base/memory/referenced.h"
29 #include "base/utils/utils.h"
30 #include "core/components_ng/base/ui_node.h"
31 #include "core/components_ng/syntax/lazy_for_each_node.h"
32 #include "core/components_ng/syntax/repeat_virtual_scroll_node.h"
33 #include "core/components_ng/pattern/list/list_children_main_size.h"
34 #include "core/components_ng/pattern/list/list_item_group_pattern.h"
35 #include "core/components_ng/property/measure_property.h"
36 
37 
38 namespace OHOS::Ace::NG {
39 class ListItemGroupPattern;
40 namespace {
41 
42 struct PositionInfo {
43     float mainPos;
44     float mainSize;
45 };
46 
47 enum class ListPosMapUpdate {
48     NO_CHANGE = 0,
49     UPDATE_ALL_SIZE,
50     RE_CALCULATE,
51 };
52 }
53 
54 class ListPositionMap : public virtual AceType {
55     DECLARE_ACE_TYPE(ListPositionMap, AceType)
56 public:
57     ListPositionMap() = default;
58     ~ListPositionMap() override = default;
59 
UpdatePos(int32_t index,PositionInfo posInfo)60     void UpdatePos(int32_t index, PositionInfo posInfo)
61     {
62         posMap_[index] = { posInfo.mainPos, posInfo.mainSize };
63     }
64 
UpdatePosWithCheck(int32_t index,PositionInfo posInfo)65     void UpdatePosWithCheck(int32_t index, PositionInfo posInfo)
66     {
67         auto iter = posMap_.find(index);
68         if (iter == posMap_.end()) {
69             posMap_[index] = posInfo;
70             return;
71         }
72         if (LessNotEqual(iter->second.mainSize, posInfo.mainSize)) {
73             iter->second.mainSize = posInfo.mainSize;
74         }
75     }
76 
ClearPosMap()77     void ClearPosMap()
78     {
79         posMap_.clear();
80     }
81 
MarkDirty(ListChangeFlag flag)82     void MarkDirty(ListChangeFlag flag)
83     {
84         dirty_ = dirty_ | flag;
85     }
86 
ClearDirty()87     void ClearDirty()
88     {
89         dirty_ = LIST_NO_CHANGE;
90     }
91 
GetTotalHeight()92     float GetTotalHeight() const
93     {
94         return totalHeight_;
95     }
96 
GetPrevTotalHeight()97     float GetPrevTotalHeight() const
98     {
99         return prevTotalHeight_;
100     }
101 
CheckPosMapUpdateRule()102     ListPosMapUpdate CheckPosMapUpdateRule()
103     {
104         ListPosMapUpdate flag;
105         if (dirty_ == LIST_NO_CHANGE) {
106             flag = ListPosMapUpdate::NO_CHANGE;
107         } else if (0 == (dirty_ & (LIST_UPDATE_CHILD_SIZE | LIST_UPDATE_LANES | LIST_GROUP_UPDATE_HEADER_FOOTER))) {
108             flag = ListPosMapUpdate::UPDATE_ALL_SIZE;
109         } else {
110             flag = ListPosMapUpdate::RE_CALCULATE;
111         }
112         return flag;
113     }
114 
UpdatePosMapStart(float delta,float & listCurrentPos,float space,int32_t startIndex,float startPos,bool groupAtStart)115     void UpdatePosMapStart(float delta, float& listCurrentPos, float space,
116         int32_t startIndex, float startPos, bool groupAtStart)
117     {
118         listCurrentPos += delta;
119         auto it = posMap_.find(startIndex);
120         if (it == posMap_.begin() || it == posMap_.end()) {
121             return;
122         }
123         startPos = it->second.mainPos;
124         it--;
125         float prevPos = it->second.mainPos + it->second.mainSize + space;
126         int32_t prevIndex = it->first;
127         if (prevIndex + 1 >= startIndex && groupAtStart) {
128             if (NearEqual(prevPos, startPos)) {
129                 return;
130             }
131         } else {
132             if (LessNotEqual(prevPos, startPos)) {
133                 return;
134             }
135         }
136         listCurrentPos += prevPos - startPos;
137     }
138 
UpdatePosMapEnd(int32_t prevEndIndex,float space,bool groupAtEnd)139     void UpdatePosMapEnd(int32_t prevEndIndex, float space, bool groupAtEnd)
140     {
141         auto it = posMap_.find(prevEndIndex);
142         if (it == posMap_.end()) {
143             return;
144         }
145         float prevPos = it->second.mainPos + it->second.mainSize + space;
146         it++;
147         if (it == posMap_.end()) {
148             return;
149         }
150         if (prevEndIndex + 1 >= it->first && groupAtEnd) {
151             if (NearEqual(prevPos, it->second.mainPos)) {
152                 return;
153             }
154         } else {
155             if (LessNotEqual(prevPos, it->second.mainPos)) {
156                 return;
157             }
158         }
159         float delta = prevPos - it->second.mainPos;
160         while (it != posMap_.end()) {
161             it->second.mainPos += delta;
162             it++;
163         }
164     }
165 
CalculateUINode(RefPtr<UINode> node)166     void CalculateUINode(RefPtr<UINode> node)
167     {
168         CHECK_NULL_VOID(node);
169         auto children = node->GetChildren();
170         for (const auto& child : children) {
171             if (AceType::InstanceOf<FrameNode>(child)) {
172                 auto frameNode = AceType::DynamicCast<FrameNode>(child);
173                 CalculateFrameNode(frameNode);
174             } else if (AceType::InstanceOf<LazyForEachNode>(child) ||
175                 AceType::InstanceOf<RepeatVirtualScrollNode>(child)) {
176                 // Rules: only one type node(ListItem or ListItemGroup) can exist in LazyForEach.
177                 CalculateLazyForEachNode(child);
178             } else {
179                 CalculateUINode(child);
180             }
181         }
182     }
183 
GetLazyForEachChildIsGroup(RefPtr<UINode> node)184     std::optional<bool> GetLazyForEachChildIsGroup(RefPtr<UINode> node)
185     {
186         std::optional<bool> isGroup;
187         auto children = node->GetChildren();
188         if (children.size() > 0) {
189             auto child = children.begin();
190             while (child != children.end() && !((*child)->GetFrameChildByIndex(0, false))) {
191                 child++;
192             }
193             auto frameNode = AceType::DynamicCast<FrameNode>((*child)->GetFrameChildByIndex(0, false));
194             if (frameNode) {
195                 isGroup = frameNode->GetHostTag() == V2::LIST_ITEM_GROUP_ETS_TAG;
196             }
197         }
198         if (!(isGroup.has_value())) {
199             auto listNode = node->GetParentFrameNode();
200             CHECK_NULL_RETURN(listNode, isGroup);
201             auto wrapper = listNode->GetOrCreateChildByIndex(curIndex_);
202             CHECK_NULL_RETURN(wrapper, isGroup);
203             isGroup = wrapper->GetHostTag() == V2::LIST_ITEM_GROUP_ETS_TAG;
204         }
205         return isGroup;
206     }
207 
CalculateLazyForEachNode(RefPtr<UINode> node)208     void CalculateLazyForEachNode(RefPtr<UINode> node)
209     {
210         int32_t count = node->FrameCount();
211         if (count <= 0) {
212             return;
213         }
214         std::optional<bool> isGroup = GetLazyForEachChildIsGroup(node);
215         if (!(isGroup.has_value())) {
216             TAG_LOGW(AceLogTag::ACE_LIST,
217                 "ListPositionMap Conflict: LazyForEach FrameCount > 0, but get child type failed.");
218             isGroup = false;
219         }
220         while (count--) {
221             isGroup.value() ? CalculateGroupNode() : CalculateListItemNode();
222         }
223         return;
224     }
225 
CalculateFrameNode(RefPtr<FrameNode> frameNode)226     void CalculateFrameNode(RefPtr<FrameNode> frameNode)
227     {
228         CHECK_NULL_VOID(frameNode);
229         auto listItemGroupPatten = frameNode->GetPattern<ListItemGroupPattern>();
230         if (listItemGroupPatten) {
231             CalculateGroupNode();
232         } else {
233             CalculateListItemNode();
234         }
235     }
236 
CalculateListItemNode()237     void CalculateListItemNode()
238     {
239         curRowHeight_ = std::max(curRowHeight_, childrenSize_->GetChildSize(curIndex_));
240         curLine_++;
241         if (curLine_ == lanes_ || curIndex_ == totalItemCount_ - 1) {
242             while (curLine_) {
243                 curLine_--;
244                 posMap_[curIndex_ - curLine_] = { totalHeight_, curRowHeight_ };
245             }
246             totalHeight_ += (curRowHeight_ + space_);
247             curRowHeight_ = 0.0f;
248         }
249         curIndex_++;
250     }
251 
CalculateGroupNode()252     void CalculateGroupNode()
253     {
254         if (curLine_ > 0) {
255             while (curLine_) {
256                 curLine_--;
257                 posMap_[curIndex_ - 1 - curLine_] = { totalHeight_, curRowHeight_ };
258             }
259             totalHeight_ += (curRowHeight_ + space_);
260             curRowHeight_ = 0.0f;
261         }
262         curRowHeight_ = childrenSize_->GetChildSize(curIndex_);
263         posMap_[curIndex_] = { totalHeight_, curRowHeight_ };
264         totalHeight_ += (curRowHeight_ + space_);
265         curLine_ = 0;
266         curRowHeight_ = 0.0f;
267         curIndex_++;
268     }
269 
PosMapRecalculate(LayoutWrapper * layoutWrapper)270     void PosMapRecalculate(LayoutWrapper* layoutWrapper)
271     {
272         curIndex_ = 0;
273         curLine_ = 0;
274         totalHeight_ = 0.0f;
275         curRowHeight_ = 0.0f;
276         if (lanes_ == 1) {
277             for (int32_t index = 0; index < totalItemCount_; index++) {
278                 curRowHeight_ = childrenSize_->GetChildSize(index);
279                 posMap_[index] = { totalHeight_, curRowHeight_ };
280                 totalHeight_ += (curRowHeight_ + space_);
281             }
282             totalHeight_ -= space_;
283         } else {
284             auto listNode = layoutWrapper->GetHostNode();
285             CHECK_NULL_VOID(listNode);
286             CalculateUINode(listNode);
287             totalHeight_ -= space_;
288         }
289         TAG_LOGI(AceLogTag::ACE_LIST, "List PosMapRecalculate totalHeight:%{public}f prevTotalHeight:%{public}f, "
290             "lanes:%{public}d, space:%{public}f, totalItemCount:%{public}d",
291             totalHeight_, prevTotalHeight_, lanes_, space_, totalItemCount_);
292     }
293 
GroupPosMapRecalculate()294     void GroupPosMapRecalculate()
295     {
296         curIndex_ = 0;
297         totalHeight_ = headerSize_;
298         curRowHeight_ = 0.0f;
299         curLine_ = 0;
300 
301         for (int32_t index = 0; index < totalItemCount_;) {
302             while (curLine_ < lanes_) {
303                 curRowHeight_ = std::max(curRowHeight_, childrenSize_->GetChildSize(index + curLine_));
304                 curLine_++;
305             }
306             curLine_ = 0;
307             int32_t curRowEndIndex = std::min(index + lanes_ - 1, totalItemCount_ - 1);
308             while (index <= curRowEndIndex) {
309                 posMap_[index++] = { totalHeight_, curRowHeight_ };
310             }
311             totalHeight_ += (curRowHeight_ + space_);
312             curRowHeight_ = 0.0f;
313         }
314         totalHeight_ = totalHeight_ - space_ + footerSize_;
315     }
316 
PosMapUpdateAllSize()317     void PosMapUpdateAllSize()
318     {
319         float curPos = 0.0f;
320         for (int32_t index = 0; index < totalItemCount_; index++) {
321             posMap_[index] = { curPos, curRowHeight_ };
322         }
323     }
324 
UpdatePosMap(LayoutWrapper * layoutWrapper,int32_t lanes,float space,RefPtr<ListChildrenMainSize> & childrenSize)325     void UpdatePosMap(LayoutWrapper* layoutWrapper, int32_t lanes, float space,
326         RefPtr<ListChildrenMainSize>& childrenSize)
327     {
328         childrenSize_ = childrenSize;
329         if (totalItemCount_ != layoutWrapper->GetTotalChildCount()) {
330             dirty_ |= LIST_UPDATE_ITEM_COUNT;
331             totalItemCount_ = layoutWrapper->GetTotalChildCount();
332         }
333         if (lanes != lanes_) {
334             dirty_ |= LIST_UPDATE_LANES;
335             lanes_ = lanes;
336         }
337         if (!NearEqual(space, space_)) {
338             dirty_ |= LIST_UPDATE_SPACE;
339             space_ = space;
340         }
341         switch (CheckPosMapUpdateRule()) {
342             case ListPosMapUpdate::NO_CHANGE:
343                 break;
344             case ListPosMapUpdate::UPDATE_ALL_SIZE:
345                 PosMapRecalculate(layoutWrapper);
346                 break;
347             case ListPosMapUpdate::RE_CALCULATE:
348             default:
349                 PosMapRecalculate(layoutWrapper);
350         }
351         ClearDirty();
352     }
353 
UpdateGroupPosMap(int32_t totalCount,int32_t lanes,float space,RefPtr<ListChildrenMainSize> & childrenSize,float headerSize,float footerSize)354     void UpdateGroupPosMap(int32_t totalCount, int32_t lanes, float space,
355         RefPtr<ListChildrenMainSize>& childrenSize, float headerSize, float footerSize)
356     {
357         childrenSize_ = childrenSize;
358         prevTotalHeight_ = totalHeight_;
359         if (totalCount != totalItemCount_) {
360             dirty_ |= LIST_UPDATE_ITEM_COUNT;
361             totalItemCount_ = totalCount;
362         }
363         if (lanes != lanes_) {
364             dirty_ |= LIST_UPDATE_LANES;
365             lanes_ = lanes;
366         }
367         if (!NearEqual(space, space_)) {
368             dirty_ |= LIST_UPDATE_SPACE;
369             space_ = space;
370         }
371         if (!NearEqual(headerSize, headerSize_) || !NearEqual(footerSize, footerSize_)) {
372             dirty_ |= LIST_GROUP_UPDATE_HEADER_FOOTER;
373             headerSize_ = headerSize;
374             footerSize_ = footerSize;
375         }
376         if (totalItemCount_ <= 0) {
377             totalHeight_ = 0;
378             ClearPosMap();
379             ClearDirty();
380             return;
381         }
382         switch (CheckPosMapUpdateRule()) {
383             case ListPosMapUpdate::NO_CHANGE:
384                 break;
385             case ListPosMapUpdate::UPDATE_ALL_SIZE:
386                 GroupPosMapRecalculate();
387                 break;
388             case ListPosMapUpdate::RE_CALCULATE:
389             default:
390                 GroupPosMapRecalculate();
391         }
392         ClearDirty();
393     }
394 
UpdateTotalCount(int32_t totalCount)395     void UpdateTotalCount(int32_t totalCount)
396     {
397         totalItemCount_ = totalCount;
398         auto iter = posMap_.lower_bound(totalCount);
399         posMap_.erase(iter, posMap_.end());
400     }
401 
402     float GetPos(int32_t index, float offset = 0.0f)
403     {
404         return posMap_[index].mainPos - offset;
405     }
406 
GetGroupLayoutOffset(int32_t startIndex,float startPos)407     float GetGroupLayoutOffset(int32_t startIndex, float startPos)
408     {
409         return posMap_[startIndex].mainPos - startPos;
410     }
411 
GetPositionInfo(int32_t index)412     PositionInfo GetPositionInfo(int32_t index) const
413     {
414         auto it = posMap_.find(index);
415         if (it == posMap_.end()) {
416             return { -1.0f, -1.0f };
417         }
418         return it->second;
419     }
420 
GetStartIndexAndPos()421     std::pair<int32_t, float> GetStartIndexAndPos() const
422     {
423         if (posMap_.empty()) {
424             return { -1, 0.0f };
425         }
426         const auto& start = posMap_.begin();
427         return { start->first, start->second.mainPos };
428     }
429 
GetEndIndexAndPos()430     std::pair<int32_t, float> GetEndIndexAndPos() const
431     {
432         if (posMap_.empty()) {
433             return { -1, 0.0f };
434         }
435         const auto& end = posMap_.rbegin();
436         return { end->first, end->second.mainPos + end->second.mainSize };
437     }
438 
OptimizeBeforeMeasure(int32_t & beginIndex,float & beginPos,const float offset,const float contentSize)439     void OptimizeBeforeMeasure(int32_t& beginIndex, float& beginPos, const float offset, const float contentSize)
440     {
441         if (NearZero(offset) || GreatOrEqual(contentSize, totalHeight_)) {
442             return;
443         }
444         float chainOffset = chainOffsetFunc_ ? chainOffsetFunc_(beginIndex) : 0.0f;
445         if (Positive(offset)) {
446             float criticalPos = offset;
447             std::pair<int32_t, float> rowInfo = GetRowEndIndexAndHeight(beginIndex);
448             while (!NearEqual(posMap_[beginIndex].mainPos + rowInfo.second, totalHeight_) &&
449                 LessNotEqual(beginPos + rowInfo.second + chainOffset, criticalPos)) {
450                 beginIndex = rowInfo.first + 1;
451                 beginPos += (rowInfo.second + space_);
452                 rowInfo = GetRowEndIndexAndHeight(beginIndex);
453                 chainOffset = chainOffsetFunc_ ? chainOffsetFunc_(beginIndex) : 0.0f;
454             }
455         } else {
456             float criticalPos = offset + contentSize;
457             std::pair<int32_t, float> rowInfo = GetRowEndIndexAndHeight(beginIndex);
458             while (Positive(posMap_[beginIndex].mainPos) &&
459                 GreatNotEqual(beginPos - rowInfo.second + chainOffset, criticalPos)) {
460                 beginIndex = GetRowStartIndex(beginIndex) - 1;
461                 beginPos -= (rowInfo.second + space_);
462                 rowInfo = GetRowEndIndexAndHeight(beginIndex);
463                 chainOffset = chainOffsetFunc_ ? chainOffsetFunc_(beginIndex) : 0.0f;
464             }
465         }
466     }
467 
SetChainOffsetCallback(std::function<float (int32_t)> func)468     void SetChainOffsetCallback(std::function<float(int32_t)> func)
469     {
470         chainOffsetFunc_ = std::move(func);
471     }
472 
GetRowStartIndex(const int32_t input)473     int32_t GetRowStartIndex(const int32_t input)
474     {
475         int32_t startIndex = input;
476         while (startIndex > 0 && NearEqual(posMap_[startIndex].mainPos, posMap_[startIndex - 1].mainPos)) {
477             startIndex--;
478         }
479         return startIndex;
480     }
481 
GetRowEndIndex(const int32_t input)482     int32_t GetRowEndIndex(const int32_t input)
483     {
484         std::pair<int32_t, float> rowInfo = GetRowEndIndexAndHeight(input);
485         return rowInfo.first;
486     }
487 
GetRowHeight(int32_t input)488     float GetRowHeight(int32_t input)
489     {
490         std::pair<int32_t, float> rowInfo = GetRowEndIndexAndHeight(input);
491         return rowInfo.second;
492     }
493 
GetRowEndIndexAndHeight(const int32_t input)494     std::pair<int32_t, float> GetRowEndIndexAndHeight(const int32_t input)
495     {
496         int32_t endIndex = input;
497         float rowHeight = 0.0f;
498         while (endIndex < (totalItemCount_ - 1) &&
499             NearEqual(posMap_[endIndex].mainPos, posMap_[endIndex + 1].mainPos)) {
500             endIndex++;
501         }
502         if (endIndex == totalItemCount_ - 1) {
503             rowHeight = totalHeight_ - posMap_[endIndex].mainPos - footerSize_;
504         } else {
505             rowHeight = posMap_[endIndex + 1].mainPos  - posMap_[endIndex].mainPos - space_;
506         }
507         return { endIndex, rowHeight };
508     }
509 private:
510     std::map<int32_t, PositionInfo> posMap_;
511     RefPtr<ListChildrenMainSize> childrenSize_;
512     std::function<float(int32_t)> chainOffsetFunc_;
513     ListChangeFlag dirty_ = LIST_NO_CHANGE;
514     int32_t totalItemCount_ = 0;
515     int32_t lanes_ = -1;
516     int32_t curLine_ = 0;
517     int32_t curIndex_ = 0;
518     float totalHeight_ = 0.0f;
519     float prevTotalHeight_ = 0.0f;
520     float curRowHeight_ = 0.0f;
521     float space_ = 0.0f;
522     float headerSize_ = 0.0f;
523     float footerSize_ = 0.0f;
524 };
525 
526 } // namespace OHOS::Ace::NG
527 #endif // FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERNS_LIST_LIST_POSITION_MAP_H