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/grid/grid_scroll/grid_scroll_with_options_layout_algorithm.h"
17 
18 #include "core/components_ng/pattern/grid/grid_item_pattern.h"
19 
20 namespace OHOS::Ace::NG {
21 namespace {
UpdateGridItemRowAndColumnInfo(const RefPtr<LayoutWrapper> & itemLayoutWrapper,GridItemIndexInfo irregualItemInfo)22 void UpdateGridItemRowAndColumnInfo(const RefPtr<LayoutWrapper>& itemLayoutWrapper, GridItemIndexInfo irregualItemInfo)
23 {
24     auto gridItemHost = itemLayoutWrapper->GetHostNode();
25     CHECK_NULL_VOID(gridItemHost);
26     auto gridItemPattern = gridItemHost->GetPattern<GridItemPattern>();
27     CHECK_NULL_VOID(gridItemPattern);
28     gridItemPattern->SetIrregularItemInfo(irregualItemInfo);
29 }
30 } // namespace
31 
AdjustRowColSpan(const RefPtr<LayoutWrapper> & itemLayoutWrapper,LayoutWrapper * layoutWrapper,int32_t itemIndex)32 void GridScrollWithOptionsLayoutAlgorithm::AdjustRowColSpan(
33     const RefPtr<LayoutWrapper>& itemLayoutWrapper, LayoutWrapper* layoutWrapper, int32_t itemIndex)
34 {
35     auto result = GetCrossStartAndSpan(layoutWrapper, itemIndex);
36     if (gridLayoutInfo_.axis_ == Axis::VERTICAL) {
37         currentItemColStart_ = result.first;
38         currentItemColSpan_ = result.second;
39         currentItemColEnd_ = currentItemColStart_ + currentItemColSpan_ - 1;
40         currentItemRowStart_ = -1;
41         currentItemRowEnd_ = -1;
42         currentItemRowSpan_ = 1;
43     } else {
44         currentItemRowStart_ = result.first;
45         currentItemRowSpan_ = result.second;
46         currentItemRowEnd_ = currentItemRowStart_ + currentItemRowSpan_ - 1;
47         currentItemColStart_ = -1;
48         currentItemColEnd_ = -1;
49         currentItemColSpan_ = 1;
50     }
51 
52     if (currentItemRowSpan_ > 1 || currentItemColSpan_ > 1) {
53         gridLayoutInfo_.hasBigItem_ = true;
54         bool isVertical = gridLayoutInfo_.axis_ == Axis::VERTICAL;
55         GridItemIndexInfo irregualItemInfo;
56         irregualItemInfo.mainStart = isVertical ? currentItemRowStart_ : currentItemColStart_;
57         irregualItemInfo.mainEnd = isVertical ? currentItemRowEnd_ : currentItemColEnd_;
58         irregualItemInfo.mainSpan = isVertical ? currentItemRowSpan_ : currentItemColSpan_;
59         irregualItemInfo.crossStart = isVertical ? currentItemColStart_ : currentItemRowStart_;
60         irregualItemInfo.crossEnd = isVertical ? currentItemColEnd_ : currentItemRowEnd_;
61         irregualItemInfo.crossSpan = isVertical ? currentItemColSpan_ : currentItemRowSpan_;
62         UpdateGridItemRowAndColumnInfo(itemLayoutWrapper, irregualItemInfo);
63     }
64 }
65 
LargeItemLineHeight(const RefPtr<LayoutWrapper> & itemWrapper,bool &)66 void GridScrollWithOptionsLayoutAlgorithm::LargeItemLineHeight(
67     const RefPtr<LayoutWrapper>& itemWrapper, bool& /* hasNormalItem */)
68 {
69     auto itemSize = itemWrapper->GetGeometryNode()->GetMarginFrameSize();
70     auto itemMainSize = GetMainAxisSize(itemSize, gridLayoutInfo_.axis_);
71     if (LessNotEqual(itemMainSize, 0.0f)) {
72         TAG_LOGI(
73             AceLogTag::ACE_GRID, "item height of index %{public}d is less than zero", gridLayoutInfo_.endIndex_ + 1);
74         itemMainSize = 0.0f;
75     }
76     cellAveLength_ = std::max(itemMainSize, cellAveLength_);
77 }
78 
GetTargetIndexInfoWithBenchMark(LayoutWrapper * layoutWrapper,bool isTargetBackward,int32_t targetIndex)79 void GridScrollWithOptionsLayoutAlgorithm::GetTargetIndexInfoWithBenchMark(
80     LayoutWrapper* layoutWrapper, bool isTargetBackward, int32_t targetIndex)
81 {
82     int32_t benchmarkIndex = (isTargetBackward && !gridLayoutInfo_.gridMatrix_.empty())
83                                  ? gridLayoutInfo_.gridMatrix_.rbegin()->second.rbegin()->second + 1
84                                  : 0;
85     int32_t mainStartIndex = (isTargetBackward && !gridLayoutInfo_.gridMatrix_.empty())
86                                  ? gridLayoutInfo_.gridMatrix_.rbegin()->first + 1
87                                  : 0;
88     int32_t currentIndex = benchmarkIndex;
89     int32_t headOfMainStartLine = currentIndex;
90 
91     while (currentIndex < targetIndex) {
92         int32_t crossGridReserve = gridLayoutInfo_.crossCount_;
93         /* go through a new line */
94         while ((crossGridReserve > 0) && (currentIndex <= targetIndex)) {
95             auto crossPos = GetCrossStartAndSpan(layoutWrapper, currentIndex);
96             auto gridSpan = crossPos.second;
97             if (crossGridReserve >= gridSpan) {
98                 crossGridReserve -= gridSpan;
99             } else if (gridLayoutInfo_.crossCount_ >= gridSpan) {
100                 ++mainStartIndex;
101                 headOfMainStartLine = currentIndex;
102                 crossGridReserve = gridLayoutInfo_.crossCount_ - gridSpan;
103             }
104             ++currentIndex;
105         }
106         if (currentIndex > targetIndex) {
107             break;
108         }
109         ++mainStartIndex;
110         headOfMainStartLine = currentIndex;
111     }
112     gridLayoutInfo_.startMainLineIndex_ = mainStartIndex;
113     gridLayoutInfo_.startIndex_ = headOfMainStartLine;
114     gridLayoutInfo_.endIndex_ = headOfMainStartLine - 1;
115     gridLayoutInfo_.prevOffset_ = 0;
116     gridLayoutInfo_.currentOffset_ = 0;
117     gridLayoutInfo_.ResetPositionFlags();
118     gridLayoutInfo_.gridMatrix_.clear();
119     gridLayoutInfo_.lineHeightMap_.clear();
120     gridLayoutInfo_.irregularItemsPosition_.clear();
121 }
122 
GetCrossStartAndSpan(LayoutWrapper * layoutWrapper,int32_t itemIndex)123 std::pair<int32_t, int32_t> GridScrollWithOptionsLayoutAlgorithm::GetCrossStartAndSpan(
124     LayoutWrapper* layoutWrapper, int32_t itemIndex)
125 {
126     auto layoutProperty = DynamicCast<GridLayoutProperty>(layoutWrapper->GetLayoutProperty());
127     CHECK_NULL_RETURN(layoutProperty, std::make_pair(-1, 1));
128     const auto& options = *layoutProperty->GetLayoutOptions();
129     if (options.irregularIndexes.empty()) {
130         return std::make_pair(itemIndex % crossCount_, 1);
131     }
132 
133     auto firstIrregularIndex = *(options.irregularIndexes.begin());
134     if (itemIndex < firstIrregularIndex) {
135         return std::make_pair(itemIndex % crossCount_, 1);
136     }
137 
138     // without function
139     if (!options.getSizeByIndex) {
140         if (options.irregularIndexes.find(itemIndex) != options.irregularIndexes.end()) {
141             return std::make_pair(0, crossCount_);
142         }
143         int32_t crossStart = -1;
144         auto iter = options.irregularIndexes.upper_bound(itemIndex);
145         auto crossCount = static_cast<int32_t>(crossCount_);
146         if (iter == options.irregularIndexes.end()) {
147             crossStart = (itemIndex - (*(options.irregularIndexes.rbegin()) + 1)) % crossCount;
148         } else {
149             if (iter != options.irregularIndexes.begin()) {
150                 crossStart = (itemIndex - (*(--iter) + 1)) % crossCount;
151             } else {
152                 crossStart = itemIndex % crossCount;
153             }
154         }
155         return std::make_pair(crossStart, 1);
156     }
157 
158     return GetCrossStartAndSpanWithUserFunction(itemIndex, options, firstIrregularIndex);
159 }
160 
JumpToLastIrregularItem(const std::map<int32_t,int32_t> & irregularItemsPosition,int32_t & sum,int32_t & lastIndex,int32_t targetIndex)161 static void JumpToLastIrregularItem(
162     const std::map<int32_t, int32_t>& irregularItemsPosition, int32_t& sum, int32_t& lastIndex, int32_t targetIndex)
163 {
164     if (irregularItemsPosition.empty()) {
165         return;
166     }
167 
168     auto iter = irregularItemsPosition.lower_bound(targetIndex);
169     if (iter == irregularItemsPosition.begin()) {
170         return;
171     }
172     if (iter != irregularItemsPosition.end()) {
173         --iter;
174         sum = iter->second;
175         lastIndex = iter->first;
176     } else {
177         auto lastIter = irregularItemsPosition.rbegin();
178         sum = lastIter->second;
179         lastIndex = lastIter->first;
180     }
181 }
182 
ResetInvalidCrossSpan(uint32_t crossCount,int32_t & crossSpan)183 static void ResetInvalidCrossSpan(uint32_t crossCount, int32_t& crossSpan)
184 {
185     if (crossSpan > static_cast<int32_t>(crossCount) || crossSpan <= 0) {
186         crossSpan = 1;
187     }
188 }
189 
InitIrregularItemsPosition(std::map<int32_t,int32_t> & irregularItemsPosition,const GridLayoutOptions & options,int32_t firstIrregularIndex,Axis axis,int32_t crossCount)190 static void InitIrregularItemsPosition(std::map<int32_t, int32_t>& irregularItemsPosition,
191     const GridLayoutOptions& options, int32_t firstIrregularIndex, Axis axis, int32_t crossCount)
192 {
193     if (irregularItemsPosition.empty()) {
194         auto sum = firstIrregularIndex;
195         auto crossSpan = options.getSizeByIndex(firstIrregularIndex).GetCrossSize(axis);
196         ResetInvalidCrossSpan(crossCount, crossSpan);
197         // first irregular item in new line
198         if (crossCount != 0) {
199             auto crossStart = sum % crossCount;
200             if (crossStart + crossSpan > crossCount) {
201                 sum += (crossCount - crossStart);
202             }
203         }
204         irregularItemsPosition.emplace(firstIrregularIndex, sum + crossSpan);
205     }
206 }
207 
GetCrossStartAndSpanWithUserFunction(int32_t itemIndex,const GridLayoutOptions & options,int32_t firstIrregularIndex)208 std::pair<int32_t, int32_t> GridScrollWithOptionsLayoutAlgorithm::GetCrossStartAndSpanWithUserFunction(
209     int32_t itemIndex, const GridLayoutOptions& options, int32_t firstIrregularIndex)
210 {
211     auto crossCount = static_cast<int32_t>(crossCount_);
212     InitIrregularItemsPosition(
213         gridLayoutInfo_.irregularItemsPosition_, options, firstIrregularIndex, gridLayoutInfo_.axis_, crossCount);
214     auto sum = firstIrregularIndex;
215     auto lastIndex = firstIrregularIndex;
216     JumpToLastIrregularItem(gridLayoutInfo_.irregularItemsPosition_, sum, lastIndex, itemIndex);
217     auto iter = options.irregularIndexes.find(lastIndex);
218     if (iter == options.irregularIndexes.end()) {
219         iter = options.irregularIndexes.begin();
220     }
221     for (; iter != options.irregularIndexes.end(); ++iter) {
222         auto index = *iter;
223         if (index >= itemIndex) {
224             break;
225         }
226 
227         if (index == lastIndex) {
228             continue;
229         }
230 
231         auto crossSpan = options.getSizeByIndex(index).GetCrossSize(gridLayoutInfo_.axis_);
232         ResetInvalidCrossSpan(crossCount_, crossSpan);
233         auto irregularStart = (sum + index - lastIndex - 1) % crossCount;
234         // put it into next line
235         if (irregularStart + crossSpan > crossCount) {
236             sum += (crossCount - irregularStart);
237         }
238         sum += (index - lastIndex - 1);
239         sum += crossSpan;
240         lastIndex = index;
241         gridLayoutInfo_.irregularItemsPosition_.emplace(index, sum);
242     }
243     sum += ((itemIndex > lastIndex) ? (itemIndex - lastIndex - 1) : 0);
244     auto crossStart = sum % crossCount;
245     bool isRegularItem = (options.irregularIndexes.find(itemIndex) == options.irregularIndexes.end());
246     auto crossSpan = isRegularItem ? 1 : options.getSizeByIndex(itemIndex).GetCrossSize(gridLayoutInfo_.axis_);
247     ResetInvalidCrossSpan(crossCount_, crossSpan);
248     if (crossStart + crossSpan > crossCount) {
249         sum += (crossCount - crossStart);
250         crossStart = 0;
251     }
252     if (!isRegularItem) {
253         sum += crossSpan;
254         gridLayoutInfo_.irregularItemsPosition_.emplace(itemIndex, sum);
255     }
256     return std::make_pair(crossStart, crossSpan);
257 }
258 
SkipLargeOffset(float mainSize,LayoutWrapper * layoutWrapper)259 void GridScrollWithOptionsLayoutAlgorithm::SkipLargeOffset(float mainSize, LayoutWrapper* layoutWrapper)
260 {
261     SkipForwardLines(mainSize, layoutWrapper);
262     SkipBackwardLines(mainSize, layoutWrapper);
263 }
264 
SkipIrregularLines(LayoutWrapper * layoutWrapper,bool forward)265 void GridScrollWithOptionsLayoutAlgorithm::SkipIrregularLines(LayoutWrapper* layoutWrapper, bool forward)
266 {
267     auto layoutProperty = DynamicCast<GridLayoutProperty>(layoutWrapper->GetLayoutProperty());
268     CHECK_NULL_VOID(layoutProperty);
269     const auto& options = *layoutProperty->GetLayoutOptions();
270     if (options.irregularIndexes.empty()) {
271         return SkipRegularLines(forward);
272     }
273     if (options.getSizeByIndex) {
274         return GridScrollLayoutAlgorithm::SkipIrregularLines(layoutWrapper, forward);
275     }
276 
277     gridLayoutInfo_.SkipStartIndexByOffset(options, mainGap_);
278 }
279 } // namespace OHOS::Ace::NG
280