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 namespace OHOS::Ace::NG {
19 namespace {
UpdateGridItemRowAndColumnInfo(const RefPtr<LayoutWrapper> & itemLayoutWrapper,GridItemIndexInfo irregualItemInfo)20 void UpdateGridItemRowAndColumnInfo(const RefPtr<LayoutWrapper>& itemLayoutWrapper, GridItemIndexInfo irregualItemInfo)
21 {
22 auto gridItemHost = itemLayoutWrapper->GetHostNode();
23 CHECK_NULL_VOID(gridItemHost);
24 auto gridItemPattern = gridItemHost->GetPattern<GridItemPattern>();
25 CHECK_NULL_VOID(gridItemPattern);
26 gridItemPattern->SetIrregularItemInfo(irregualItemInfo);
27 }
28 } // namespace
29
AdjustRowColSpan(const RefPtr<LayoutWrapper> & itemLayoutWrapper,LayoutWrapper * layoutWrapper,int32_t itemIndex)30 void GridScrollWithOptionsLayoutAlgorithm::AdjustRowColSpan(
31 const RefPtr<LayoutWrapper>& itemLayoutWrapper, LayoutWrapper* layoutWrapper, int32_t itemIndex)
32 {
33 auto result = GetCrossStartAndSpan(layoutWrapper, itemIndex);
34 if (info_.axis_ == Axis::VERTICAL) {
35 currentItemColStart_ = result.first;
36 currentItemColSpan_ = result.second;
37 currentItemColEnd_ = currentItemColStart_ + currentItemColSpan_ - 1;
38 currentItemRowStart_ = -1;
39 currentItemRowEnd_ = -1;
40 currentItemRowSpan_ = 1;
41 } else {
42 currentItemRowStart_ = result.first;
43 currentItemRowSpan_ = result.second;
44 currentItemRowEnd_ = currentItemRowStart_ + currentItemRowSpan_ - 1;
45 currentItemColStart_ = -1;
46 currentItemColEnd_ = -1;
47 currentItemColSpan_ = 1;
48 }
49
50 if (currentItemRowSpan_ > 1 || currentItemColSpan_ > 1) {
51 info_.hasBigItem_ = true;
52 bool isVertical = info_.axis_ == Axis::VERTICAL;
53 GridItemIndexInfo irregualItemInfo;
54 irregualItemInfo.mainStart = isVertical ? currentItemRowStart_ : currentItemColStart_;
55 irregualItemInfo.mainEnd = isVertical ? currentItemRowEnd_ : currentItemColEnd_;
56 irregualItemInfo.mainSpan = isVertical ? currentItemRowSpan_ : currentItemColSpan_;
57 irregualItemInfo.crossStart = isVertical ? currentItemColStart_ : currentItemRowStart_;
58 irregualItemInfo.crossEnd = isVertical ? currentItemColEnd_ : currentItemRowEnd_;
59 irregualItemInfo.crossSpan = isVertical ? currentItemColSpan_ : currentItemRowSpan_;
60 UpdateGridItemRowAndColumnInfo(itemLayoutWrapper, irregualItemInfo);
61 }
62 }
63
LargeItemLineHeight(const RefPtr<LayoutWrapper> & itemWrapper)64 void GridScrollWithOptionsLayoutAlgorithm::LargeItemLineHeight(const RefPtr<LayoutWrapper>& itemWrapper)
65 {
66 auto itemSize = itemWrapper->GetGeometryNode()->GetMarginFrameSize();
67 auto itemMainSize = GetMainAxisSize(itemSize, info_.axis_);
68 if (LessNotEqual(itemMainSize, 0.0f)) {
69 TAG_LOGI(
70 AceLogTag::ACE_GRID, "item height of index %{public}d is less than zero", info_.endIndex_ + 1);
71 itemMainSize = 0.0f;
72 }
73 cellAveLength_ = std::max(itemMainSize, cellAveLength_);
74 }
75
GetTargetIndexInfoWithBenchMark(LayoutWrapper * layoutWrapper,bool isTargetBackward,int32_t targetIndex)76 void GridScrollWithOptionsLayoutAlgorithm::GetTargetIndexInfoWithBenchMark(
77 LayoutWrapper* layoutWrapper, bool isTargetBackward, int32_t targetIndex)
78 {
79 int32_t benchmarkIndex = (isTargetBackward && !info_.gridMatrix_.empty())
80 ? info_.gridMatrix_.rbegin()->second.rbegin()->second + 1
81 : 0;
82 int32_t mainStartIndex = (isTargetBackward && !info_.gridMatrix_.empty())
83 ? info_.gridMatrix_.rbegin()->first + 1
84 : 0;
85 int32_t currentIndex = benchmarkIndex;
86 int32_t headOfMainStartLine = currentIndex;
87
88 while (currentIndex < targetIndex) {
89 int32_t crossGridReserve = info_.crossCount_;
90 /* go through a new line */
91 while ((crossGridReserve > 0) && (currentIndex <= targetIndex)) {
92 auto crossPos = GetCrossStartAndSpan(layoutWrapper, currentIndex);
93 auto gridSpan = crossPos.second;
94 if (crossGridReserve >= gridSpan) {
95 crossGridReserve -= gridSpan;
96 } else if (info_.crossCount_ >= gridSpan) {
97 ++mainStartIndex;
98 headOfMainStartLine = currentIndex;
99 crossGridReserve = info_.crossCount_ - gridSpan;
100 }
101 ++currentIndex;
102 }
103 if (currentIndex > targetIndex) {
104 break;
105 }
106 ++mainStartIndex;
107 headOfMainStartLine = currentIndex;
108 }
109 info_.startMainLineIndex_ = mainStartIndex;
110 info_.startIndex_ = headOfMainStartLine;
111 info_.endIndex_ = headOfMainStartLine - 1;
112 info_.prevOffset_ = 0;
113 info_.currentOffset_ = 0;
114 info_.ResetPositionFlags();
115 info_.gridMatrix_.clear();
116 info_.lineHeightMap_.clear();
117 info_.irregularItemsPosition_.clear();
118 }
119
GetCrossStartAndSpan(LayoutWrapper * layoutWrapper,int32_t itemIndex)120 std::pair<int32_t, int32_t> GridScrollWithOptionsLayoutAlgorithm::GetCrossStartAndSpan(
121 LayoutWrapper* layoutWrapper, int32_t itemIndex)
122 {
123 auto layoutProperty = DynamicCast<GridLayoutProperty>(layoutWrapper->GetLayoutProperty());
124 CHECK_NULL_RETURN(layoutProperty, std::make_pair(-1, 1));
125 const auto& options = *layoutProperty->GetLayoutOptions();
126 if (options.irregularIndexes.empty()) {
127 return std::make_pair(-1, 1);
128 }
129
130 auto firstIrregularIndex = *(options.irregularIndexes.begin());
131 if (itemIndex < firstIrregularIndex) {
132 return std::make_pair(itemIndex % crossCount_, 1);
133 }
134
135 // without function
136 if (!options.getSizeByIndex) {
137 if (options.irregularIndexes.find(itemIndex) != options.irregularIndexes.end()) {
138 return std::make_pair(0, crossCount_);
139 }
140 int32_t crossStart = -1;
141 auto iter = options.irregularIndexes.upper_bound(itemIndex);
142 auto crossCount = static_cast<int32_t>(crossCount_);
143 if (iter == options.irregularIndexes.end()) {
144 crossStart = (itemIndex - (*(options.irregularIndexes.rbegin()) + 1)) % crossCount;
145 } else {
146 if (iter != options.irregularIndexes.begin()) {
147 crossStart = (itemIndex - (*(--iter) + 1)) % crossCount;
148 } else {
149 crossStart = itemIndex % crossCount;
150 }
151 }
152 return std::make_pair(crossStart, 1);
153 }
154
155 return GetCrossStartAndSpanWithUserFunction(itemIndex, options, firstIrregularIndex);
156 }
157
JumpToLastIrregularItem(const std::map<int32_t,int32_t> & irregularItemsPosition,int32_t & sum,int32_t & lastIndex,int32_t targetIndex)158 static void JumpToLastIrregularItem(
159 const std::map<int32_t, int32_t>& irregularItemsPosition, int32_t& sum, int32_t& lastIndex, int32_t targetIndex)
160 {
161 if (irregularItemsPosition.empty()) {
162 return;
163 }
164
165 auto iter = irregularItemsPosition.lower_bound(targetIndex);
166 if (iter == irregularItemsPosition.begin()) {
167 return;
168 }
169 if (iter != irregularItemsPosition.end()) {
170 --iter;
171 sum = iter->second;
172 lastIndex = iter->first;
173 } else {
174 auto lastIter = irregularItemsPosition.rbegin();
175 sum = lastIter->second;
176 lastIndex = lastIter->first;
177 }
178 }
179
ResetInvalidCrossSpan(uint32_t crossCount,int32_t & crossSpan)180 static void ResetInvalidCrossSpan(uint32_t crossCount, int32_t& crossSpan)
181 {
182 if (crossSpan > static_cast<int32_t>(crossCount) || crossSpan <= 0) {
183 crossSpan = 1;
184 }
185 }
186
InitIrregularItemsPosition(std::map<int32_t,int32_t> & irregularItemsPosition,const GridLayoutOptions & options,int32_t firstIrregularIndex,Axis axis,int32_t crossCount)187 static void InitIrregularItemsPosition(std::map<int32_t, int32_t>& irregularItemsPosition,
188 const GridLayoutOptions& options, int32_t firstIrregularIndex, Axis axis, int32_t crossCount)
189 {
190 if (irregularItemsPosition.empty()) {
191 auto sum = firstIrregularIndex;
192 auto crossSpan = options.getSizeByIndex(firstIrregularIndex).GetCrossSize(axis);
193 ResetInvalidCrossSpan(crossCount, crossSpan);
194 // first irregular item in new line
195 if (crossCount != 0) {
196 auto crossStart = sum % crossCount;
197 if (crossStart + crossSpan > crossCount) {
198 sum += (crossCount - crossStart);
199 }
200 }
201 irregularItemsPosition.emplace(firstIrregularIndex, sum + crossSpan);
202 }
203 }
204
GetCrossStartAndSpanWithUserFunction(int32_t itemIndex,const GridLayoutOptions & options,int32_t firstIrregularIndex)205 std::pair<int32_t, int32_t> GridScrollWithOptionsLayoutAlgorithm::GetCrossStartAndSpanWithUserFunction(
206 int32_t itemIndex, const GridLayoutOptions& options, int32_t firstIrregularIndex)
207 {
208 auto crossCount = static_cast<int32_t>(crossCount_);
209 InitIrregularItemsPosition(
210 info_.irregularItemsPosition_, options, firstIrregularIndex, info_.axis_, crossCount);
211 auto sum = firstIrregularIndex;
212 auto lastIndex = firstIrregularIndex;
213 JumpToLastIrregularItem(info_.irregularItemsPosition_, sum, lastIndex, itemIndex);
214 auto iter = options.irregularIndexes.find(lastIndex);
215 if (iter == options.irregularIndexes.end()) {
216 iter = options.irregularIndexes.begin();
217 }
218 for (; iter != options.irregularIndexes.end(); ++iter) {
219 auto index = *iter;
220 if (index >= itemIndex) {
221 break;
222 }
223
224 if (index == lastIndex) {
225 continue;
226 }
227
228 auto crossSpan = options.getSizeByIndex(index).GetCrossSize(info_.axis_);
229 ResetInvalidCrossSpan(crossCount_, crossSpan);
230 auto irregularStart = (sum + index - lastIndex - 1) % crossCount;
231 // put it into next line
232 if (irregularStart + crossSpan > crossCount) {
233 sum += (crossCount - irregularStart);
234 }
235 sum += (index - lastIndex - 1);
236 sum += crossSpan;
237 lastIndex = index;
238 info_.irregularItemsPosition_.emplace(index, sum);
239 }
240 sum += ((itemIndex > lastIndex) ? (itemIndex - lastIndex - 1) : 0);
241 auto crossStart = sum % crossCount;
242 bool isRegularItem = (options.irregularIndexes.find(itemIndex) == options.irregularIndexes.end());
243 auto crossSpan = isRegularItem ? 1 : options.getSizeByIndex(itemIndex).GetCrossSize(info_.axis_);
244 ResetInvalidCrossSpan(crossCount_, crossSpan);
245 if (crossStart + crossSpan > crossCount) {
246 sum += (crossCount - crossStart);
247 crossStart = 0;
248 }
249 if (!isRegularItem) {
250 sum += crossSpan;
251 info_.irregularItemsPosition_.emplace(itemIndex, sum);
252 }
253 return std::make_pair(crossStart, crossSpan);
254 }
255
SkipLargeOffset(float mainSize,LayoutWrapper * layoutWrapper)256 void GridScrollWithOptionsLayoutAlgorithm::SkipLargeOffset(float mainSize, LayoutWrapper* layoutWrapper)
257 {
258 SkipForwardLines(mainSize, layoutWrapper);
259 SkipBackwardLines(mainSize, layoutWrapper);
260 }
261
SkipIrregularLines(LayoutWrapper * layoutWrapper,bool forward)262 void GridScrollWithOptionsLayoutAlgorithm::SkipIrregularLines(LayoutWrapper* layoutWrapper, bool forward)
263 {
264 auto layoutProperty = DynamicCast<GridLayoutProperty>(layoutWrapper->GetLayoutProperty());
265 CHECK_NULL_VOID(layoutProperty);
266 const auto& options = *layoutProperty->GetLayoutOptions();
267 if (options.irregularIndexes.empty()) {
268 return SkipRegularLines(forward);
269 }
270 if (options.getSizeByIndex) {
271 return GridScrollLayoutAlgorithm::SkipIrregularLines(layoutWrapper, forward);
272 }
273
274 info_.SkipStartIndexByOffset(options, mainGap_);
275 }
276
CalculateCachedCount(LayoutWrapper * layoutWrapper,int32_t cachedCount)277 std::pair<int32_t, int32_t> GridScrollWithOptionsLayoutAlgorithm::CalculateCachedCount(
278 LayoutWrapper* layoutWrapper, int32_t cachedCount)
279 {
280 if (cachedCount == 0 || info_.crossCount_ == 1) {
281 return std::make_pair(cachedCount, cachedCount);
282 }
283 int32_t cache = cachedCount * info_.crossCount_;
284
285 CHECK_NULL_RETURN(layoutWrapper, std::make_pair(cache, cache));
286 auto props = DynamicCast<GridLayoutProperty>(layoutWrapper->GetLayoutProperty());
287 CHECK_NULL_RETURN(props, std::make_pair(cache, cache));
288
289 const auto& options = *props->GetLayoutOptions();
290 if (options.irregularIndexes.empty()) {
291 return std::make_pair(cache, cache);
292 }
293 int32_t start = CalculateStartCachedCount(options, cachedCount);
294 int32_t end = CalculateEndCachedCount(options, cachedCount);
295 return std::make_pair(start, end);
296 }
297
CalculateStartCachedCount(const GridLayoutOptions & options,int32_t cachedCount)298 int32_t GridScrollWithOptionsLayoutAlgorithm::CalculateStartCachedCount(
299 const GridLayoutOptions& options, int32_t cachedCount)
300 {
301 int32_t start = cachedCount * info_.crossCount_;
302
303 if (info_.startMainLineIndex_ - cachedCount <= 0) {
304 return std::min(info_.startIndex_, start);
305 }
306
307 auto startLine = info_.gridMatrix_.find(info_.startMainLineIndex_ - cachedCount);
308 if (startLine != info_.gridMatrix_.end()) {
309 auto line = startLine->second;
310 if (!line.empty()) {
311 auto index = line.begin()->second;
312 return info_.startIndex_ - index;
313 }
314 }
315
316 auto firstIrregularIndex = *(options.irregularIndexes.begin());
317 if (info_.startIndex_ <= firstIrregularIndex) {
318 return start;
319 }
320
321 if (!options.getSizeByIndex) {
322 auto iter = options.irregularIndexes.lower_bound(info_.startIndex_);
323 auto crossCount = static_cast<int32_t>(crossCount_);
324 if (iter == options.irregularIndexes.end()) {
325 return start;
326 }
327 if (*iter == info_.startIndex_ && iter != options.irregularIndexes.begin()) {
328 iter--;
329 }
330
331 int lineCount = 0;
332 int sum = 0;
333 int32_t diff = info_.startIndex_ - *(iter)-1;
334 while (lineCount < cachedCount) {
335 if (diff >= (cachedCount - lineCount) * crossCount) {
336 return (cachedCount - lineCount) * crossCount + sum;
337 }
338
339 if (diff == 0) {
340 sum++;
341 lineCount++;
342 }
343
344 if (diff > 0 && diff <= (cachedCount - lineCount - 1) * crossCount) {
345 lineCount += std::ceil(diff / crossCount) + 1;
346 sum += diff;
347 }
348
349 if (iter == options.irregularIndexes.begin()) {
350 return (cachedCount - lineCount) * crossCount + sum;
351 }
352
353 diff = (*iter) - (*(--iter)) - 1;
354 }
355 return sum;
356 }
357 return start;
358 }
359
CalculateEndCachedCount(const GridLayoutOptions & options,int32_t cachedCount)360 int32_t GridScrollWithOptionsLayoutAlgorithm::CalculateEndCachedCount(
361 const GridLayoutOptions& options, int32_t cachedCount)
362 {
363 if (info_.startIndex_ + cachedCount >= info_.childrenCount_ - 1) {
364 return info_.startIndex_;
365 }
366
367 int32_t end = cachedCount * info_.crossCount_;
368
369 auto endLine = info_.gridMatrix_.find(info_.endMainLineIndex_ + cachedCount);
370 if (endLine != info_.gridMatrix_.end()) {
371 auto line = endLine->second;
372 if (!line.empty()) {
373 auto index = line.rbegin()->second;
374 return index - info_.endIndex_;
375 }
376 }
377
378 auto lastIrregularIndex = *(options.irregularIndexes.rbegin());
379 if (info_.endIndex_ >= lastIrregularIndex) {
380 return end;
381 }
382
383 if (!options.getSizeByIndex) {
384 auto iter = options.irregularIndexes.upper_bound(info_.endIndex_);
385 auto crossCount = static_cast<int32_t>(crossCount_);
386 if (iter == options.irregularIndexes.end()) {
387 return end;
388 }
389
390 int lineCount = 0;
391 int sum = 0;
392 int32_t diff = *(iter)-info_.endIndex_ - 1;
393 while (lineCount < cachedCount) {
394 if (diff >= (cachedCount - lineCount) * crossCount) {
395 return (cachedCount - lineCount) * crossCount + sum;
396 }
397
398 if (diff == 0) {
399 sum++;
400 lineCount++;
401 }
402
403 if (diff > 0 && diff <= (cachedCount - lineCount - 1) * crossCount) {
404 lineCount += std::ceil(diff / crossCount) + 1;
405 sum += diff;
406 }
407
408 if (iter == options.irregularIndexes.end()) {
409 return (cachedCount - lineCount) * crossCount + sum;
410 }
411
412 diff = -*(iter) + *(++iter) - 1;
413 }
414 return sum;
415 }
416 return end;
417 }
418 } // namespace OHOS::Ace::NG
419