1 /*
2  * Copyright (c) 2023 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_HEIGHT_OFFSET_CALCULATOR_H
17 #define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERNS_LIST_LIST_HEIGHT_OFFSET_CALCULATOR_H
18 
19 #include "core/components_ng/base/ui_node.h"
20 #include "core/components_ng/syntax/lazy_for_each_node.h"
21 #include "core/components_ng/syntax/repeat_virtual_scroll_node.h"
22 #include "core/components_ng/pattern/list/list_item_group_pattern.h"
23 #include "core/components_ng/pattern/list/list_item_pattern.h"
24 #include "core/components_ng/pattern/list/list_layout_algorithm.h"
25 
26 namespace OHOS::Ace::NG {
27 namespace {
28 constexpr float DEFAULT_ITEM_HEIGHT = 64.f;
29 }
30 class ListHeightOffsetCalculator {
31 public:
ListHeightOffsetCalculator(const ListLayoutAlgorithm::PositionMap & itemPosition,float space,int32_t lanes,Axis axis)32     ListHeightOffsetCalculator(const ListLayoutAlgorithm::PositionMap& itemPosition, float space,
33         int32_t lanes, Axis axis) : axis_(axis), spaceWidth_(space), lanes_(lanes), itemPosition_(itemPosition)
34     {
35         if (!itemPosition.empty()) {
36             targetPos_ = { itemPosition.begin()->second.startPos, itemPosition.begin()->second.endPos };
37             startIndex_ = itemPosition.begin()->first;
38             endIndex_ = itemPosition.rbegin()->first;
39             float itemsSize = itemPosition.rbegin()->second.endPos - itemPosition.begin()->second.startPos + space;
40             estimateItemHeight_ = itemsSize / itemPosition.size() - space;
41             for (const auto& pos : itemPosition) {
42                 if (pos.second.groupInfo && pos.second.groupInfo.value().averageHeight > 0) {
43                     groupedItemHeight_ = pos.second.groupInfo.value().averageHeight;
44                     break;
45                 }
46             }
47         }
48     }
49 
CalculateFrameNode(RefPtr<FrameNode> frameNode)50     void CalculateFrameNode(RefPtr<FrameNode> frameNode)
51     {
52         CHECK_NULL_VOID(frameNode);
53         auto listItemGroupPatten = frameNode->GetPattern<ListItemGroupPattern>();
54         if (listItemGroupPatten) {
55             if (currentIndex_ > 0) {
56                 estimateHeight_ += spaceWidth_;
57             }
58             if (currentIndex_ == startIndex_) {
59                 estimateOffset_ = listItemGroupPatten->GetEstimateOffset(estimateHeight_, targetPos_);
60             }
61             if (currLane_ > 0) {
62                 estimateHeight_ += currRowHeight_;
63                 currLane_ = 0;
64                 currRowHeight_ = 0.0f;
65             }
66             estimateHeight_ += listItemGroupPatten->GetEstimateHeight(groupedItemHeight_);
67             currentIndex_++;
68             return;
69         }
70         auto listItemPatten = frameNode->GetPattern<ListItemPattern>();
71         if (currentIndex_ > 0 && currLane_ == 0) {
72             estimateHeight_ += spaceWidth_;
73         }
74         if (currentIndex_ == startIndex_) {
75             estimateOffset_ = estimateHeight_ - targetPos_.first;
76         }
77         float height = 0.0f;
78         if (listItemPatten) {
79             height = listItemPatten->GetEstimateHeight(GetAverageItemHeight(), axis_);
80         } else {
81             height = GetMainAxisSize(frameNode->GetGeometryNode()->GetMarginFrameSize(), axis_);
82         }
83         currRowHeight_ = std::max(currRowHeight_, height);
84         currLane_++;
85         if (currLane_ == lanes_) {
86             estimateHeight_ += currRowHeight_;
87             currLane_ = 0;
88             currRowHeight_ = 0.0f;
89         }
90         currentIndex_++;
91         if (listItemPatten && listItemPatten->GetLayouted()) {
92             totalItemHeight_ += height;
93             totalItemCount_++;
94         }
95     }
96 
CalculateUINode(RefPtr<UINode> node)97     void CalculateUINode(RefPtr<UINode> node)
98     {
99         CHECK_NULL_VOID(node);
100         auto children = node->GetChildren();
101         for (const auto& child : children) {
102             if (AceType::InstanceOf<FrameNode>(child)) {
103                 auto frameNode = AceType::DynamicCast<FrameNode>(child);
104                 CalculateFrameNode(frameNode);
105             } else if (AceType::InstanceOf<LazyForEachNode>(child) ||
106                 AceType::InstanceOf<RepeatVirtualScrollNode>(child)) {
107                 CalculateLazyForEachNode(child);
108             } else {
109                 CalculateUINode(child);
110             }
111         }
112     }
113 
GetLazyForEachIndexAverageHeight(RefPtr<UINode> node,int32_t startIndex,int32_t endIndex,bool & hasGroup)114     float GetLazyForEachIndexAverageHeight(RefPtr<UINode> node,
115         int32_t startIndex, int32_t endIndex, bool &hasGroup)
116     {
117         auto itor = itemPosition_.find(startIndex);
118         float totalHeight = 0.0f;
119         int32_t itemCount = 0;
120         while (itor != itemPosition_.end() && itor->first <= endIndex) {
121             if (!itor->second.isGroup) {
122                 totalHeight += itor->second.endPos - itor->second.startPos;
123                 itor++;
124                 itemCount++;
125                 continue;
126             }
127             hasGroup = true;
128             if (itor->first == startIndex_ || itor->first == endIndex_) {
129                 auto child = node->GetFrameChildByIndex(itor->first - currentIndex_, false);
130                 auto frameNode = AceType::DynamicCast<FrameNode>(child);
131                 if (!frameNode) {
132                     itor++;
133                     continue;
134                 }
135                 auto group = frameNode->GetPattern<ListItemGroupPattern>();
136                 if (!group || !group->HasLayoutedItem()) {
137                     itor++;
138                     continue;
139                 }
140                 totalHeight += group->GetEstimateHeight(groupedItemHeight_);
141             } else {
142                 totalHeight += itor->second.endPos - itor->second.startPos;
143             }
144             itor++;
145             itemCount++;
146         }
147         if (itemCount == 0) {
148             return estimateItemHeight_;
149         }
150         return totalHeight / itemCount;
151     }
152 
CalculateOffset(RefPtr<UINode> node,float averageHeight)153     float CalculateOffset(RefPtr<UINode> node, float averageHeight)
154     {
155         auto itor = itemPosition_.begin();
156         float skipHeight = 0.0f;
157         while (itor != itemPosition_.end() && itor->second.isGroup) {
158             auto child = node->GetFrameChildByIndex(itor->first - currentIndex_, false);
159             auto frameNode = AceType::DynamicCast<FrameNode>(child);
160             if (frameNode) {
161                 auto group = frameNode->GetPattern<ListItemGroupPattern>();
162                 if (group && group->HasLayoutedItem()) {
163                     std::pair<float, float> pos = { itor->second.startPos, itor->second.endPos };
164                     return group->GetEstimateOffset(estimateOffset_ + skipHeight, pos);
165                 }
166             }
167             skipHeight += averageHeight;
168             itor++;
169         }
170         return estimateOffset_ - targetPos_.first;
171     }
172 
CalculateLazyForEachNode(RefPtr<UINode> node)173     void CalculateLazyForEachNode(RefPtr<UINode> node)
174     {
175         int32_t count = node->FrameCount();
176         if (count <= 0) {
177             return;
178         }
179         if ((endIndex_ < currentIndex_) || (startIndex_ >= currentIndex_ + count)) {
180             estimateHeight_ += (estimateItemHeight_ + spaceWidth_) * GetLines(lanes_, count);
181             if (currentIndex_ > 0) {
182                 estimateHeight_ -= spaceWidth_;
183             }
184             currentIndex_ += count;
185             return;
186         }
187         if (currentIndex_ > 0 && currLane_ == 0) {
188             estimateHeight_ += spaceWidth_;
189         }
190         bool hasGroup = false;
191         int32_t startIndex = std::max(currentIndex_, startIndex_);
192         int32_t endIndex = std::min(currentIndex_ + count - 1, endIndex_);
193         float averageHeight = GetLazyForEachIndexAverageHeight(node, startIndex, endIndex, hasGroup);
194         int32_t lanes = hasGroup ? 1 : lanes_;
195         if (startIndex == startIndex_) {
196             int32_t curr = GetLines(lanes, startIndex_ - currentIndex_);
197             estimateOffset_ = estimateHeight_ + (averageHeight + spaceWidth_) * curr;
198             estimateOffset_ = CalculateOffset(node, averageHeight);
199         }
200         estimateHeight_ += (averageHeight + spaceWidth_) * GetLines(lanes, count) - spaceWidth_;
201         currentIndex_ += count;
202     }
203 
GetEstimateHeightAndOffset(RefPtr<UINode> node)204     bool GetEstimateHeightAndOffset(RefPtr<UINode> node)
205     {
206         CalculateUINode(node);
207         if (currLane_ > 0) {
208             estimateHeight_ += currRowHeight_;
209             currLane_ = 0;
210             currRowHeight_ = 0.0f;
211         }
212         return true;
213     }
214 
GetEstimateHeight()215     float GetEstimateHeight() const
216     {
217         return estimateHeight_;
218     }
219 
GetEstimateOffset()220     float GetEstimateOffset() const
221     {
222         return estimateOffset_;
223     }
224 
225 private:
GetAverageItemHeight()226     float GetAverageItemHeight() const
227     {
228         if (totalItemCount_ > 0) {
229             return totalItemHeight_ / totalItemCount_;
230         }
231         return estimateItemHeight_;
232     }
233 
GetLines(int32_t lanes,int32_t count)234     static int32_t GetLines(int32_t lanes, int32_t count)
235     {
236         if (lanes > 1) {
237             int32_t lines = count / lanes;
238             if (count % lanes > 0) {
239                 lines += 1;
240             }
241             return lines;
242         } else {
243             return count;
244         }
245     }
246 
247     Axis axis_ = Axis::VERTICAL;
248     int32_t currentIndex_ = 0;
249     int32_t startIndex_ = 0;
250     int32_t endIndex_ = 0;
251     std::pair<float, float> targetPos_ = { 0.0f, 0.0f };
252     float estimateHeight_ = 0.0f;
253     float estimateOffset_ = 0.0f;
254     float spaceWidth_ = 0.0f;
255 
256     float totalItemHeight_ = 0.0f;
257     float totalItemCount_ = 0.0f;
258 
259     float estimateItemHeight_ = DEFAULT_ITEM_HEIGHT;
260     float groupedItemHeight_ = DEFAULT_ITEM_HEIGHT;
261 
262     int32_t lanes_ = 1;
263     int32_t currLane_ = 0;
264     float currRowHeight_ = 0.0f;
265 
266     const ListLayoutAlgorithm::PositionMap& itemPosition_;
267 };
268 } // namespace OHOS::Ace::NG
269 #endif
270