1 /*
2 * Copyright (c) 2023-2025 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/irregular/grid_irregular_layout_algorithm.h"
17
18 #include "core/components_ng/pattern/grid/grid_utils.h"
19 #include "core/components_ng/pattern/grid/irregular/grid_irregular_filler.h"
20 #include "core/components_ng/pattern/grid/irregular/grid_layout_range_solver.h"
21 #include "core/components_ng/pattern/grid/irregular/grid_layout_utils.h"
22 #include "core/components_ng/pattern/scrollable/scrollable_utils.h"
23 #include "core/components_ng/property/templates_parser.h"
24
25 namespace OHOS::Ace::NG {
26 namespace {
GetFillParameters(const RefPtr<FrameNode> & host,const GridLayoutInfo & info)27 GridIrregularFiller::FillParameters GetFillParameters(const RefPtr<FrameNode>& host, const GridLayoutInfo& info)
28 {
29 const auto& contentSize = host->GetGeometryNode()->GetContentSize();
30 auto props = AceType::DynamicCast<GridLayoutProperty>(host->GetLayoutProperty());
31 auto crossGap = GridUtils::GetCrossGap(props, contentSize, info.axis_);
32 auto mainGap = GridUtils::GetMainGap(props, contentSize, info.axis_);
33 std::string args =
34 info.axis_ == Axis::VERTICAL ? props->GetColumnsTemplate().value_or("") : props->GetRowsTemplate().value_or("");
35 const float crossSize = contentSize.CrossSize(info.axis_);
36 auto res = ParseTemplateArgs(GridUtils::ParseArgs(args), crossSize, crossGap, info.GetChildrenCount());
37 auto crossLens = std::vector<float>(res.first.begin(), res.first.end());
38 if (crossLens.empty()) {
39 crossLens.push_back(crossSize);
40 }
41 crossGap = res.second;
42 return { crossLens, crossGap, mainGap };
43 }
44 } // namespace
45
Measure(LayoutWrapper * layoutWrapper)46 void GridIrregularLayoutAlgorithm::Measure(LayoutWrapper* layoutWrapper)
47 {
48 if (info_.GetChildrenCount() <= 0) {
49 return;
50 }
51 wrapper_ = layoutWrapper;
52 auto props = DynamicCast<GridLayoutProperty>(wrapper_->GetLayoutProperty());
53
54 float mainSize = MeasureSelf(props);
55 auto gridLayoutProperty = AceType::DynamicCast<GridLayoutProperty>(layoutWrapper->GetLayoutProperty());
56 CHECK_NULL_VOID(gridLayoutProperty);
57 auto layoutPolicy = gridLayoutProperty->GetLayoutPolicyProperty();
58 auto isMainWrap = false;
59 if (layoutPolicy.has_value()) {
60 auto isVertical = info_.axis_ == Axis::VERTICAL;
61 auto widthLayoutPolicy = layoutPolicy.value().widthLayoutPolicy_.value_or(LayoutCalPolicy::NO_MATCH);
62 auto heightLayoutPolicy = layoutPolicy.value().heightLayoutPolicy_.value_or(LayoutCalPolicy::NO_MATCH);
63 auto isMainFix = (isVertical ? heightLayoutPolicy : widthLayoutPolicy) == LayoutCalPolicy::FIX_AT_IDEAL_SIZE;
64 isMainWrap = (isVertical ? heightLayoutPolicy : widthLayoutPolicy) == LayoutCalPolicy::WRAP_CONTENT;
65 if (isMainFix) {
66 frameSize_.SetMainSize(Infinity<float>(), info_.axis_);
67 }
68 }
69 bool matchChildren = GreaterOrEqualToInfinity(mainSize) || isMainWrap;
70 Init(props);
71
72 if (info_.targetIndex_) {
73 MeasureToTarget();
74 info_.targetIndex_.reset();
75 }
76
77 if (info_.jumpIndex_ != EMPTY_JUMP_INDEX) {
78 MeasureOnJump(mainSize);
79 } else {
80 MeasureOnOffset(mainSize);
81 }
82
83 if (props->GetAlignItems().value_or(GridItemAlignment::DEFAULT) == GridItemAlignment::STRETCH) {
84 GridLayoutBaseAlgorithm::AdjustChildrenHeight(layoutWrapper);
85 }
86
87 UpdateLayoutInfo();
88 if (matchChildren) {
89 AdaptToChildMainSize(props, mainSize, frameSize_);
90 }
91 const int32_t cacheCnt = props->GetCachedCountValue(info_.defCachedCount_) * info_.crossCount_;
92 if (props->GetShowCachedItemsValue(false)) {
93 SyncPreloadItems(cacheCnt);
94 } else {
95 PreloadItems(cacheCnt);
96 }
97 }
98
Layout(LayoutWrapper * layoutWrapper)99 void GridIrregularLayoutAlgorithm::Layout(LayoutWrapper* layoutWrapper)
100 {
101 const auto& info = info_;
102 if (info.childrenCount_ <= 0) {
103 return;
104 }
105 wrapper_ = layoutWrapper;
106 auto props = DynamicCast<GridLayoutProperty>(wrapper_->GetLayoutProperty());
107 CHECK_NULL_VOID(props);
108
109 const int32_t cacheLines = props->GetCachedCountValue(info.defCachedCount_);
110 if (!props->HasCachedCount()) {
111 info_.UpdateDefaultCachedCount();
112 }
113 auto cachedItemCnt = LayoutChildren(info.currentOffset_, cacheLines);
114
115 if (!props->GetShowCachedItemsValue(false)) {
116 // only use accurate counting method when cached items need to be shown
117 cachedItemCnt.first = cachedItemCnt.second = cacheLines * info.crossCount_;
118 }
119 LostChildFocusToSelf(layoutWrapper, std::min(info.startIndex_, info.endIndex_) - cachedItemCnt.first,
120 info.endIndex_ + cachedItemCnt.second);
121 wrapper_->SetActiveChildRange(std::min(info.startIndex_, info.endIndex_), info.endIndex_, cachedItemCnt.first,
122 cachedItemCnt.second, props->GetShowCachedItemsValue(false));
123 wrapper_->SetCacheCount(cachedItemCnt.first);
124 UpdateOverlay(wrapper_);
125 }
126
MeasureSelf(const RefPtr<GridLayoutProperty> & props)127 float GridIrregularLayoutAlgorithm::MeasureSelf(const RefPtr<GridLayoutProperty>& props)
128 {
129 // set self size
130 frameSize_ = CreateIdealSize(props->GetLayoutConstraint().value(), info_.axis_, props->GetMeasureType(), true);
131 wrapper_->GetGeometryNode()->SetFrameSize(frameSize_);
132
133 // set content size
134 const auto& padding = props->CreatePaddingAndBorder();
135 wrapper_->GetGeometryNode()->UpdatePaddingWithBorder(padding);
136 MinusPaddingToSize(padding, frameSize_);
137 info_.contentEndPadding_ = ScrollableUtils::CheckHeightExpansion(props, info_.axis_);
138 frameSize_.AddHeight(info_.contentEndPadding_);
139 wrapper_->GetGeometryNode()->SetContentSize(frameSize_);
140
141 return frameSize_.MainSize(info_.axis_);
142 }
143
Init(const RefPtr<GridLayoutProperty> & props)144 void GridIrregularLayoutAlgorithm::Init(const RefPtr<GridLayoutProperty>& props)
145 {
146 const auto& contentSize = wrapper_->GetGeometryNode()->GetContentSize();
147 crossGap_ = GridUtils::GetCrossGap(props, contentSize, info_.axis_);
148 mainGap_ = GridUtils::GetMainGap(props, contentSize, info_.axis_);
149
150 std::string args;
151 if (props->GetRowsTemplate()) {
152 info_.axis_ = Axis::HORIZONTAL;
153 args = props->GetRowsTemplate().value_or("");
154 } else {
155 info_.axis_ = Axis::VERTICAL;
156 args = props->GetColumnsTemplate().value_or("");
157 }
158
159 const float crossSize = contentSize.CrossSize(info_.axis_);
160 auto res = ParseTemplateArgs(GridUtils::ParseArgs(args), crossSize, crossGap_, info_.GetChildrenCount());
161
162 crossLens_ = std::vector<float>(res.first.begin(), res.first.end());
163 if (crossLens_.empty()) {
164 crossLens_.push_back(crossSize);
165 }
166
167 crossGap_ = res.second;
168
169 info_.crossCount_ = static_cast<int32_t>(crossLens_.size());
170 CheckForReset();
171 }
172
173 namespace {
PrepareJumpOnReset(GridLayoutInfo & info)174 inline void PrepareJumpOnReset(GridLayoutInfo& info)
175 {
176 info.jumpIndex_ = std::min(info.startIndex_, info.childrenCount_ - 1);
177 info.scrollAlign_ = ScrollAlign::START;
178 }
ResetMaps(GridLayoutInfo & info)179 inline void ResetMaps(GridLayoutInfo& info)
180 {
181 info.gridMatrix_.clear();
182 info.lineHeightMap_.clear();
183 }
ResetLayoutRange(GridLayoutInfo & info)184 inline void ResetLayoutRange(GridLayoutInfo& info)
185 {
186 info.startIndex_ = 0;
187 info.endIndex_ = -1;
188 info.startMainLineIndex_ = 0;
189 info.endMainLineIndex_ = -1;
190 info.currentOffset_ = 0.0f;
191 info.prevOffset_ = 0.0f;
192 }
193 } // namespace
194
CheckForReset()195 void GridIrregularLayoutAlgorithm::CheckForReset()
196 {
197 if (info_.IsResetted()) {
198 // reset layout info_ and perform jump to current startIndex
199 postJumpOffset_ = info_.currentOffset_;
200 PrepareJumpOnReset(info_);
201 ResetMaps(info_);
202 ResetLayoutRange(info_);
203 ResetFocusedIndex(wrapper_);
204 return;
205 }
206
207 int32_t updateIdx = wrapper_->GetHostNode()->GetChildrenUpdated();
208 if (updateIdx != -1) {
209 auto it = info_.FindInMatrix(updateIdx);
210 if (it != info_.gridMatrix_.end()) {
211 info_.ClearHeightsToEnd(it->first);
212 info_.ClearMatrixToEnd(updateIdx, it->first);
213 }
214 if (updateIdx <= info_.endIndex_) {
215 postJumpOffset_ = info_.currentOffset_;
216 PrepareJumpOnReset(info_);
217 ResetLayoutRange(info_);
218 ResetFocusedIndex(wrapper_);
219 }
220 wrapper_->GetHostNode()->ChildrenUpdatedFrom(-1);
221 return;
222 }
223
224 auto property = wrapper_->GetLayoutProperty();
225 CHECK_NULL_VOID(property);
226 if (property->GetPropertyChangeFlag() & PROPERTY_UPDATE_BY_CHILD_REQUEST) {
227 auto mainSize = wrapper_->GetGeometryNode()->GetContentSize().MainSize(info_.axis_);
228 overscrollOffsetBeforeJump_ =
229 -info_.GetDistanceToBottom(mainSize, info_.GetTotalHeightOfItemsInView(mainGap_, true), mainGap_);
230 postJumpOffset_ = info_.currentOffset_;
231 info_.lineHeightMap_.clear();
232 PrepareJumpOnReset(info_);
233 ResetLayoutRange(info_);
234 ResetFocusedIndex(wrapper_);
235 return;
236 }
237
238 if (wrapper_->ConstraintChanged()) {
239 // need to remeasure all items in current view
240 postJumpOffset_ = info_.currentOffset_;
241 info_.lineHeightMap_.clear();
242 PrepareJumpOnReset(info_);
243 }
244 }
245
MeasureOnOffset(float mainSize)246 void GridIrregularLayoutAlgorithm::MeasureOnOffset(float mainSize)
247 {
248 if (TrySkipping(mainSize)) {
249 return;
250 }
251
252 if (GreatNotEqual(info_.currentOffset_, info_.prevOffset_)) {
253 MeasureBackward(mainSize);
254 } else {
255 MeasureForward(mainSize);
256 }
257 }
258
259 namespace {
UpdateStartInfo(GridLayoutInfo & info,const GridLayoutRangeSolver::StartingRowInfo & res)260 inline void UpdateStartInfo(GridLayoutInfo& info, const GridLayoutRangeSolver::StartingRowInfo& res)
261 {
262 info.startMainLineIndex_ = res.row;
263 info.currentOffset_ = res.pos;
264 info.startIndex_ = res.idx;
265 }
266
GetPrevHeight(const GridLayoutInfo & info,float mainGap)267 inline float GetPrevHeight(const GridLayoutInfo& info, float mainGap)
268 {
269 return info.GetHeightInRange(info.startMainLineIndex_, info.endMainLineIndex_, mainGap);
270 }
271 } // namespace
272
MeasureForward(float mainSize)273 void GridIrregularLayoutAlgorithm::MeasureForward(float mainSize)
274 {
275 float heightToFill = mainSize - info_.currentOffset_ - GetPrevHeight(info_, mainGap_);
276 if (Positive(heightToFill)) {
277 GridIrregularFiller filler(&info_, wrapper_);
278 filler.Fill({ crossLens_, crossGap_, mainGap_ }, heightToFill, info_.endMainLineIndex_);
279 }
280
281 GridLayoutRangeSolver solver(&info_, wrapper_);
282 auto res = solver.FindStartingRow(mainGap_);
283 UpdateStartInfo(info_, res);
284 auto [endMainLineIdx, endIdx] = solver.SolveForwardForEndIdx(mainGap_, mainSize - res.pos, res.row);
285 info_.endMainLineIndex_ = endMainLineIdx;
286 info_.endIndex_ = endIdx;
287
288 if (info_.startIndex_ == 0 && NonNegative(info_.currentOffset_)) {
289 return;
290 }
291 // adjust offset
292 if (!canOverScrollEnd_ && info_.endIndex_ == info_.GetChildrenCount() - 1) {
293 float overDis =
294 -info_.GetDistanceToBottom(mainSize, info_.GetTotalHeightOfItemsInView(mainGap_, true), mainGap_);
295 if (Negative(overDis)) {
296 return;
297 }
298 info_.currentOffset_ += overDis;
299 if (Positive(info_.currentOffset_)) {
300 MeasureBackward(mainSize, true);
301 }
302 }
303 }
304
MeasureBackward(float mainSize,bool toAdjust)305 void GridIrregularLayoutAlgorithm::MeasureBackward(float mainSize, bool toAdjust)
306 {
307 // skip adding starting lines that are outside viewport in LayoutIrregular
308 auto [it, offset] = info_.SkipLinesAboveView(mainGap_);
309 GridIrregularFiller filler(&info_, wrapper_);
310 filler.MeasureBackward({ crossLens_, crossGap_, mainGap_ }, offset + it->second + mainGap_, it->first);
311
312 GridLayoutRangeSolver solver(&info_, wrapper_);
313 auto res = solver.FindStartingRow(mainGap_);
314 if ((toAdjust || !canOverScrollStart_) && res.row == 0) {
315 res.pos = std::min(res.pos, 0.0f);
316 }
317 UpdateStartInfo(info_, res);
318
319 auto [endLine, endIdx] = solver.SolveForwardForEndIdx(mainGap_, mainSize - res.pos, res.row);
320 info_.endMainLineIndex_ = endLine;
321 info_.endIndex_ = endIdx;
322 }
323
324 namespace {
325 constexpr float SKIP_THRESHOLD = 2.0f;
326 }
327
TrySkipping(float mainSize)328 bool GridIrregularLayoutAlgorithm::TrySkipping(float mainSize)
329 {
330 float delta = std::abs(info_.currentOffset_ - info_.prevOffset_);
331 if (enableSkip_ && GreatNotEqual(delta, mainSize)) {
332 // a more costly check, therefore perform after comparing to [mainSize]
333 if (LessOrEqual(delta, SKIP_THRESHOLD * info_.GetTotalHeightOfItemsInView(mainGap_))) {
334 return false;
335 }
336 info_.jumpIndex_ = Negative(info_.currentOffset_) ? SkipLinesForward() : SkipLinesBackward();
337 info_.scrollAlign_ = ScrollAlign::START;
338 info_.currentOffset_ = 0.0f;
339 Jump(mainSize);
340 return true;
341 }
342 return false;
343 }
344
MeasureOnJump(float mainSize)345 void GridIrregularLayoutAlgorithm::MeasureOnJump(float mainSize)
346 {
347 Jump(mainSize);
348
349 if (info_.extraOffset_ && !NearZero(*info_.extraOffset_)) {
350 info_.prevOffset_ = info_.currentOffset_;
351 info_.currentOffset_ += *info_.extraOffset_;
352 MeasureOnOffset(mainSize);
353 }
354 if (!NearZero(postJumpOffset_)) {
355 info_.currentOffset_ = postJumpOffset_;
356 enableSkip_ = false;
357 MeasureOnOffset(mainSize);
358 }
359 }
360
Jump(float mainSize)361 void GridIrregularLayoutAlgorithm::Jump(float mainSize)
362 {
363 if (info_.jumpIndex_ == JUMP_TO_BOTTOM_EDGE) {
364 GridIrregularFiller filler(&info_, wrapper_);
365 filler.FillMatrixOnly(info_.GetChildrenCount() - 1);
366 info_.PrepareJumpToBottom();
367 }
368
369 if (info_.jumpIndex_ == LAST_ITEM) {
370 info_.jumpIndex_ = info_.GetChildrenCount() - 1;
371 }
372
373 if (info_.scrollAlign_ == ScrollAlign::AUTO) {
374 int32_t height = GridLayoutUtils::GetItemSize(&info_, wrapper_, info_.jumpIndex_).rows;
375 info_.scrollAlign_ = info_.TransformAutoScrollAlign(info_.jumpIndex_, height, mainSize, mainGap_);
376 }
377 if (info_.scrollAlign_ == ScrollAlign::NONE) {
378 info_.jumpIndex_ = EMPTY_JUMP_INDEX;
379 return;
380 }
381
382 int32_t jumpLineIdx = FindJumpLineIdx(info_.jumpIndex_);
383
384 PrepareLineHeight(mainSize, jumpLineIdx);
385
386 GridLayoutRangeSolver solver(&info_, wrapper_);
387 const auto res = solver.FindRangeOnJump(info_.jumpIndex_, jumpLineIdx, mainGap_);
388
389 info_.currentOffset_ = res.pos;
390 info_.startMainLineIndex_ = res.startRow;
391 info_.startIndex_ = res.startIdx;
392 info_.endMainLineIndex_ = res.endRow;
393 info_.endIndex_ = res.endIdx;
394 info_.jumpIndex_ = EMPTY_JUMP_INDEX;
395 }
396
UpdateLayoutInfo()397 void GridIrregularLayoutAlgorithm::UpdateLayoutInfo()
398 {
399 info_.reachStart_ = info_.startIndex_ == 0 && NonNegative(info_.currentOffset_);
400 // GridLayoutInfo::reachEnd_ has a different meaning
401 info_.reachEnd_ = info_.endIndex_ == info_.GetChildrenCount() - 1;
402
403 float mainSize = wrapper_->GetGeometryNode()->GetContentSize().MainSize(info_.axis_);
404
405 info_.lastMainSize_ = mainSize;
406 info_.totalHeightOfItemsInView_ = info_.GetTotalHeightOfItemsInView(mainGap_, true);
407 info_.avgLineHeight_ = info_.GetTotalLineHeight(0.0f) / static_cast<float>(info_.lineHeightMap_.size());
408
409 if (info_.reachEnd_) {
410 info_.offsetEnd_ = NonPositive(info_.GetDistanceToBottom(mainSize, info_.totalHeightOfItemsInView_, mainGap_));
411 } else {
412 info_.offsetEnd_ = false;
413 }
414 info_.prevOffset_ = info_.currentOffset_;
415
416 // validity check
417 for (int i = info_.startMainLineIndex_; i <= info_.endMainLineIndex_; ++i) {
418 if (info_.lineHeightMap_.find(i) == info_.lineHeightMap_.end()) {
419 TAG_LOGW(AceLogTag::ACE_GRID,
420 "lineHeight at line %d not ready. Data is corrupted. StartLine = %d, EndLine = %d", i,
421 info_.startMainLineIndex_, info_.endMainLineIndex_);
422 info_.endMainLineIndex_ = i - 1;
423 info_.endIndex_ = info_.startIndex_ - 1;
424 return;
425 }
426 }
427 }
428
429 namespace {
GetAlignment(Axis axis,const RefPtr<GridLayoutProperty> & props)430 Alignment GetAlignment(Axis axis, const RefPtr<GridLayoutProperty>& props)
431 {
432 Alignment align = axis == Axis::VERTICAL ? Alignment::TOP_CENTER : Alignment::CENTER_LEFT;
433 const auto& positionProp = props->GetPositionProperty();
434 if (positionProp) {
435 align = positionProp->GetAlignment().value_or(align);
436 }
437 return align;
438 }
439 /* adjust mainOffset to the first cache line */
AdjustStartOffset(const std::map<int32_t,float> & lineHeights,int32_t startLine,int32_t cacheStartLine,float mainGap,float & mainOffset)440 void AdjustStartOffset(const std::map<int32_t, float>& lineHeights, int32_t startLine, int32_t cacheStartLine,
441 float mainGap, float& mainOffset)
442 {
443 auto startLineIt = lineHeights.lower_bound(startLine);
444 for (auto it = lineHeights.lower_bound(cacheStartLine); it != startLineIt; ++it) {
445 mainOffset -= mainGap + it->second;
446 }
447 }
448 } // namespace
449
LayoutChildren(float mainOffset,int32_t cacheLine)450 std::pair<int32_t, int32_t> GridIrregularLayoutAlgorithm::LayoutChildren(float mainOffset, int32_t cacheLine)
451 {
452 const auto& info = info_;
453 const auto& props = DynamicCast<GridLayoutProperty>(wrapper_->GetLayoutProperty());
454 const Alignment align = GetAlignment(info.axis_, props);
455
456 const auto& padding = *wrapper_->GetGeometryNode()->GetPadding();
457 mainOffset += info.axis_ == Axis::HORIZONTAL ? 0.0f : padding.top.value_or(0.0f);
458 auto crossPos = CalculateCrossPositions(padding);
459
460 auto frameSize = wrapper_->GetGeometryNode()->GetFrameSize();
461 MinusPaddingToSize(padding, frameSize);
462 const bool isRtl = props->GetNonAutoLayoutDirection() == TextDirection::RTL;
463 const int32_t cacheStartLine = info.startMainLineIndex_ - cacheLine;
464 AdjustStartOffset(info.lineHeightMap_, info.startMainLineIndex_, cacheStartLine, mainGap_, mainOffset);
465
466 std::pair<int32_t, int32_t> cacheCnt = { 0, 0 };
467 auto endIt = info.gridMatrix_.upper_bound(std::max(info.endMainLineIndex_ + cacheLine, info.startMainLineIndex_));
468 for (auto it = info.gridMatrix_.lower_bound(cacheStartLine); it != endIt; ++it) {
469 auto lineHeightIt = info.lineHeightMap_.find(it->first);
470 if (lineHeightIt == info.lineHeightMap_.end()) {
471 continue;
472 }
473 const bool isCache = !props->GetShowCachedItemsValue(false) &&
474 (it->first < info.startMainLineIndex_ || it->first > info.endMainLineIndex_);
475 const auto& row = it->second;
476 for (const auto& [c, itemIdx] : row) {
477 if (itemIdx < 0 || (itemIdx == 0 && (it->first > 0 || c > 0))) {
478 // not top-left tile
479 continue;
480 }
481 auto child = wrapper_->GetChildByIndex(itemIdx, isCache);
482 if (!child) {
483 continue;
484 }
485
486 if (it->first < info.startMainLineIndex_) {
487 ++cacheCnt.first;
488 } else if (it->first > info.endMainLineIndex_) {
489 ++cacheCnt.second;
490 }
491
492 SizeF blockSize = SizeF(crossLens_.at(c), lineHeightIt->second, info.axis_);
493 auto childSize = child->GetGeometryNode()->GetMarginFrameSize();
494 auto alignPos = Alignment::GetAlignPosition(blockSize, childSize, align);
495
496 OffsetF offset = OffsetF(crossPos[c], mainOffset, info.axis_);
497
498 if (isRtl) {
499 offset.SetX(frameSize.Width() - offset.GetX() - childSize.Width());
500 }
501 offset += OffsetF { padding.left.value_or(0.0f), 0.0f };
502 child->GetGeometryNode()->SetMarginFrameOffset(offset + alignPos);
503 if (!isCache && child->CheckNeedForceMeasureAndLayout()) {
504 child->Layout();
505 } else {
506 child->GetHostNode()->ForceSyncGeometryNode();
507 }
508 auto frameNode = DynamicCast<FrameNode>(child);
509 if (frameNode) {
510 frameNode->MarkAndCheckNewOpIncNode(info.axis_);
511 }
512 }
513 // add mainGap below the item
514 mainOffset += lineHeightIt->second + mainGap_;
515 }
516 return cacheCnt;
517 }
518
CalculateCrossPositions(const PaddingPropertyF & padding)519 std::vector<float> GridIrregularLayoutAlgorithm::CalculateCrossPositions(const PaddingPropertyF& padding)
520 {
521 std::vector<float> res(info_.crossCount_, 0.0f);
522 res[0] = info_.axis_ == Axis::HORIZONTAL ? padding.top.value_or(0.0f) : 0.0f;
523 for (int32_t i = 1; i < info_.crossCount_; ++i) {
524 res[i] = res[i - 1] + crossLens_[i - 1] + crossGap_;
525 }
526 return res;
527 }
528
FindJumpLineIdx(int32_t jumpIdx)529 int32_t GridIrregularLayoutAlgorithm::FindJumpLineIdx(int32_t jumpIdx)
530 {
531 GridIrregularFiller filler(&info_, wrapper_);
532 int32_t jumpLine = -1;
533 auto it = info_.FindInMatrix(jumpIdx);
534 if (it == info_.gridMatrix_.end()) {
535 // fill matrix up to jumpIndex_
536 jumpLine = filler.FillMatrixOnly(jumpIdx);
537 } else {
538 jumpLine = it->first;
539 }
540
541 if (info_.scrollAlign_ == ScrollAlign::END) {
542 // jump to the last line the item occupies
543 auto lastLine = jumpLine + GridLayoutUtils::GetItemSize(&info_, wrapper_, jumpIdx).rows - 1;
544 filler.FillMatrixByLine(jumpLine, lastLine + 1);
545 jumpLine = lastLine;
546 }
547 return jumpLine;
548 }
549
550 using FillParams = GridIrregularFiller::FillParameters;
PrepareLineHeight(float mainSize,int32_t & jumpLineIdx)551 void GridIrregularLayoutAlgorithm::PrepareLineHeight(float mainSize, int32_t& jumpLineIdx)
552 {
553 /* When mainSize can't be filled, adjust parameters and call function again. The maximum length of
554 * the recursion is 3 iterations ([Start && len not filled] -> [End && len not filled] -> [Start with jumpIdx 0]).
555 */
556 GridIrregularFiller filler(&info_, wrapper_);
557 const FillParams params { crossLens_, crossGap_, mainGap_ };
558 switch (info_.scrollAlign_) {
559 case ScrollAlign::START: {
560 // call this to ensure irregular items on the first line are measured, not skipped
561 filler.MeasureLineWithIrregulars(params, jumpLineIdx);
562
563 float len = filler.Fill(params, mainSize, jumpLineIdx).length;
564 // condition [jumpLineIdx > 0] guarantees a finite call stack
565 // Over scroll at bottom dose not need ScrollAlign::END
566 if (LessNotEqual(len, mainSize) && jumpLineIdx > 0 && NonPositive(overscrollOffsetBeforeJump_)) {
567 jumpLineIdx = info_.lineHeightMap_.rbegin()->first;
568 info_.scrollAlign_ = ScrollAlign::END;
569 PrepareLineHeight(mainSize, jumpLineIdx);
570 }
571 break;
572 }
573 case ScrollAlign::CENTER: {
574 // because the current line's height is unknown, we can't determine the exact target length to fill.
575 // Using the full [mainSize]
576 const auto pos = info_.GetItemPos(info_.jumpIndex_);
577 const float itemLen = filler.MeasureItem(params, info_.jumpIndex_, pos.first, pos.second, false).first;
578 const float targetLen = mainSize / 2.0f;
579 float backwardLen = filler.MeasureBackward(params, mainSize, jumpLineIdx);
580
581 auto jumpLine = info_.lineHeightMap_.find(jumpLineIdx);
582 if (jumpLine == info_.lineHeightMap_.end()) {
583 return;
584 }
585 backwardLen -= jumpLine->second / 2.0f;
586 if (LessNotEqual(backwardLen, targetLen)) {
587 jumpLineIdx = 0;
588 info_.scrollAlign_ = ScrollAlign::START;
589 PrepareLineHeight(mainSize, jumpLineIdx);
590 return;
591 }
592 float forwardLen = filler.Fill(params, std::max(mainSize, itemLen), jumpLineIdx).length;
593 forwardLen -= jumpLine->second / 2.0f;
594 if (LessNotEqual(forwardLen, targetLen)) {
595 jumpLineIdx = info_.lineHeightMap_.rbegin()->first;
596 info_.scrollAlign_ = ScrollAlign::END;
597 PrepareLineHeight(mainSize, jumpLineIdx);
598 }
599 break;
600 }
601 case ScrollAlign::END: {
602 float len = filler.MeasureBackward(params, mainSize, jumpLineIdx);
603 if (LessNotEqual(len, mainSize)) {
604 jumpLineIdx = 0;
605 info_.scrollAlign_ = ScrollAlign::START;
606 PrepareLineHeight(mainSize, jumpLineIdx);
607 }
608 break;
609 }
610 default:
611 break;
612 }
613 }
614
615 namespace {
AddLineHeight(float & height,int32_t curLine,int32_t startLine,const std::map<int32_t,float> & lineHeights)616 void AddLineHeight(float& height, int32_t curLine, int32_t startLine, const std::map<int32_t, float>& lineHeights)
617 {
618 auto iter = lineHeights.find(curLine);
619 if (iter != lineHeights.end()) {
620 height += iter->second;
621 } else {
622 // estimation
623 height += height / std::abs(curLine - startLine);
624 }
625 }
626 } // namespace
627
SkipLinesForward()628 int32_t GridIrregularLayoutAlgorithm::SkipLinesForward()
629 {
630 int32_t line = info_.startMainLineIndex_;
631 float height = 0.0f;
632 while (LessNotEqual(height, -info_.currentOffset_)) {
633 AddLineHeight(height, line++, info_.startMainLineIndex_, info_.lineHeightMap_);
634 }
635 GridIrregularFiller filler(&info_, wrapper_);
636 return filler.FillMatrixByLine(info_.startMainLineIndex_, line);
637 }
638
SkipLinesBackward() const639 int32_t GridIrregularLayoutAlgorithm::SkipLinesBackward() const
640 {
641 const auto& info = info_;
642 float height = info.GetHeightInRange(info.startMainLineIndex_, info.endMainLineIndex_ + 1, 0.0f);
643
644 float target = info.currentOffset_ + height;
645 int32_t line = info.startMainLineIndex_;
646 while (LessNotEqual(height, target) && line > 0) {
647 AddLineHeight(height, --line, info.endMainLineIndex_, info.lineHeightMap_);
648 }
649 return std::max(0, info.FindEndIdx(line).itemIdx);
650 }
651
MeasureToTarget()652 void GridIrregularLayoutAlgorithm::MeasureToTarget()
653 {
654 GridIrregularFiller filler(&info_, wrapper_);
655 FillParams param { crossLens_, crossGap_, mainGap_ };
656 if (info_.targetIndex_ < info_.startIndex_) {
657 auto it = info_.FindInMatrix(*info_.targetIndex_);
658 filler.MeasureBackwardToTarget(param, it->first, info_.startMainLineIndex_);
659 } else {
660 filler.FillToTarget(param, *info_.targetIndex_, info_.startMainLineIndex_);
661 }
662 }
663
IsIrregularLine(int32_t lineIndex) const664 bool GridIrregularLayoutAlgorithm::IsIrregularLine(int32_t lineIndex) const
665 {
666 const auto& line = info_.gridMatrix_.find(lineIndex);
667 if (line == info_.gridMatrix_.end()) {
668 return true;
669 }
670 auto props = DynamicCast<GridLayoutProperty>(wrapper_->GetLayoutProperty());
671 const auto& opts = *props->GetLayoutOptions();
672 return std::any_of(line->second.begin(), line->second.end(),
673 [opts](const auto& item) { return opts.irregularIndexes.count(std::abs(item.second)); });
674 }
675
SyncPreloadItems(int32_t cacheCnt)676 void GridIrregularLayoutAlgorithm::SyncPreloadItems(int32_t cacheCnt)
677 {
678 const int32_t start = std::max(info_.startIndex_ - cacheCnt, 0);
679 const int32_t end = std::min(info_.endIndex_ + cacheCnt, info_.GetChildrenCount() - 1);
680 GridIrregularFiller filler(&info_, wrapper_);
681 FillParams param { crossLens_, crossGap_, mainGap_ };
682 auto it = info_.FindInMatrix(start);
683 filler.MeasureBackwardToTarget(param, it->first, info_.startMainLineIndex_ - 1);
684 filler.FillToTarget(param, end, info_.endMainLineIndex_);
685 }
686
PreloadItems(int32_t cacheCnt)687 void GridIrregularLayoutAlgorithm::PreloadItems(int32_t cacheCnt)
688 {
689 std::list<GridPreloadItem> itemsToPreload;
690 for (int32_t i = 1; i <= cacheCnt; ++i) {
691 const int32_t l = info_.startIndex_ - i;
692 auto itemWrapper = wrapper_->GetChildByIndex(l, true);
693 if (l >= 0 && GridUtils::CheckNeedCacheLayout(itemWrapper)) {
694 itemsToPreload.emplace_back(l);
695 }
696 const int32_t r = info_.endIndex_ + i;
697 itemWrapper = wrapper_->GetChildByIndex(r, true);
698 if (r < info_.GetChildrenCount() && GridUtils::CheckNeedCacheLayout(itemWrapper)) {
699 itemsToPreload.emplace_back(r);
700 }
701 }
702
703 GridIrregularFiller filler(&info_, wrapper_);
704 filler.FillMatrixOnly(std::min(info_.GetChildrenCount(), info_.endIndex_ + cacheCnt));
705
706 GridLayoutUtils::PreloadGridItems(wrapper_->GetHostNode()->GetPattern<GridPattern>(), std::move(itemsToPreload),
707 [](const RefPtr<FrameNode>& host, int32_t itemIdx) {
708 CHECK_NULL_RETURN(host, false);
709 auto pattern = host->GetPattern<GridPattern>();
710 CHECK_NULL_RETURN(pattern, false);
711
712 ScopedLayout scope(host->GetContext());
713 auto& info = pattern->GetMutableLayoutInfo();
714 GridIrregularFiller filler(&info, RawPtr(host));
715 const auto pos = info.GetItemPos(itemIdx);
716 auto constraint =
717 filler.MeasureItem(GetFillParameters(host, info), itemIdx, pos.first, pos.second, true).second;
718
719 auto item = DynamicCast<FrameNode>(host->GetChildByIndex(itemIdx, true));
720 CHECK_NULL_RETURN(item, false);
721 item->GetGeometryNode()->SetParentLayoutConstraint(constraint);
722 item->Layout();
723 auto pipeline = pattern->GetContext();
724 if (pipeline) {
725 pipeline->FlushSyncGeometryNodeTasks();
726 }
727 item->SetActive(false);
728 return true;
729 });
730 }
731
AdaptToChildMainSize(RefPtr<GridLayoutProperty> & gridLayoutProperty,float mainSize,SizeF idealSize)732 void GridIrregularLayoutAlgorithm::AdaptToChildMainSize(
733 RefPtr<GridLayoutProperty>& gridLayoutProperty, float mainSize, SizeF idealSize)
734 {
735 auto lengthOfItemsInViewport = info_.GetTotalHeightOfItemsInView(mainGap_);
736 auto gridMainSize = std::min(lengthOfItemsInViewport, mainSize);
737 gridMainSize =
738 std::max(gridMainSize, GetMainAxisSize(gridLayoutProperty->GetLayoutConstraint()->minSize, info_.axis_));
739 idealSize.SetMainSize(gridMainSize, info_.axis_);
740 AddPaddingToSize(gridLayoutProperty->CreatePaddingAndBorder(), idealSize);
741 wrapper_->GetGeometryNode()->SetFrameSize(idealSize);
742 info_.lastMainSize_ = gridMainSize;
743 TAG_LOGI(AceLogTag::ACE_GRID, "gridMainSize:%{public}f", gridMainSize);
744 }
745 } // namespace OHOS::Ace::NG
746