• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/waterflow/layout/top_down/water_flow_layout_algorithm.h"
17 
18 #include "base/geometry/axis.h"
19 #include "core/components_ng/base/frame_node.h"
20 #include "core/components_ng/layout/layout_wrapper.h"
21 #include "core/components_ng/pattern/waterflow/layout/top_down/water_flow_layout_info.h"
22 #include "core/components_ng/pattern/waterflow/layout/water_flow_layout_utils.h"
23 #include "core/components_ng/pattern/waterflow/water_flow_layout_property.h"
24 #include "core/components_ng/property/measure_utils.h"
25 #include "core/components_ng/property/templates_parser.h"
26 
27 namespace OHOS::Ace::NG {
28 namespace {
29 const std::string UNIT_AUTO = "auto";
30 } // namespace
31 
ComputeCrossPosition(int32_t crossIndex) const32 float WaterFlowLayoutAlgorithm::ComputeCrossPosition(int32_t crossIndex) const
33 {
34     float position = 0.0f;
35     for (int32_t index = 0; index < crossIndex; ++index) {
36         if (index >= 0 && index < static_cast<int32_t>(itemsCrossSize_.size())) {
37             position += itemsCrossSize_.at(index);
38         }
39     }
40     position += crossIndex * crossGap_;
41     return position;
42 }
43 
InitialItemsCrossSize(const RefPtr<WaterFlowLayoutProperty> & layoutProperty,const SizeF & frameSize,int32_t childrenCount)44 void WaterFlowLayoutAlgorithm::InitialItemsCrossSize(
45     const RefPtr<WaterFlowLayoutProperty>& layoutProperty, const SizeF& frameSize, int32_t childrenCount)
46 {
47     itemsCrossSize_.clear();
48     itemsCrossPosition_.clear();
49     auto rowsTemplate = layoutProperty->GetRowsTemplate().value_or("1fr");
50     auto columnsTemplate = layoutProperty->GetColumnsTemplate().value_or("1fr");
51     axis_ = layoutProperty->GetAxis();
52     auto scale = layoutProperty->GetLayoutConstraint()->scaleProperty;
53     auto rowsGap = ConvertToPx(layoutProperty->GetRowsGap().value_or(0.0_vp), scale, frameSize.Height()).value_or(0);
54     auto columnsGap =
55         ConvertToPx(layoutProperty->GetColumnsGap().value_or(0.0_vp), scale, frameSize.Width()).value_or(0);
56     mainGap_ = axis_ == Axis::HORIZONTAL ? columnsGap : rowsGap;
57     crossGap_ = axis_ == Axis::VERTICAL ? columnsGap : rowsGap;
58 
59     auto crossSize = frameSize.CrossSize(axis_);
60     std::vector<double> crossLens;
61     std::pair<std::vector<double>, double> cross;
62     if (axis_ == Axis::VERTICAL) {
63         cross =
64             ParseTemplateArgs(WaterFlowLayoutUtils::PreParseArgs(columnsTemplate), crossSize, crossGap_, childrenCount);
65     } else {
66         cross =
67             ParseTemplateArgs(WaterFlowLayoutUtils::PreParseArgs(rowsTemplate), crossSize, crossGap_, childrenCount);
68     }
69     crossLens = cross.first;
70     if (crossLens.empty()) {
71         crossLens.push_back(crossSize);
72     }
73     crossGap_ = cross.second;
74 
75     // cross count changed by auto-fill and cross size change
76     if (!layoutInfo_->items_[0].empty() && crossLens.size() != layoutInfo_->items_[0].size()) {
77         layoutInfo_->Reset();
78     }
79 
80     int32_t index = 0;
81     for (const auto& len : crossLens) {
82         itemsCrossSize_.try_emplace(index, len);
83         itemsCrossPosition_.try_emplace(index, ComputeCrossPosition(index));
84         layoutInfo_->items_[0].try_emplace(index, std::map<int32_t, std::pair<float, float>>());
85         ++index;
86     }
87 }
88 
Measure(LayoutWrapper * layoutWrapper)89 void WaterFlowLayoutAlgorithm::Measure(LayoutWrapper* layoutWrapper)
90 {
91     auto layoutProperty = AceType::DynamicCast<WaterFlowLayoutProperty>(layoutWrapper->GetLayoutProperty());
92     CHECK_NULL_VOID(layoutProperty);
93 
94     Axis axis = layoutProperty->GetAxis();
95     auto idealSize =
96         CreateIdealSize(layoutProperty->GetLayoutConstraint().value(), axis, layoutProperty->GetMeasureType(), true);
97     if (NearZero(GetCrossAxisSize(idealSize, axis))) {
98         TAG_LOGI(AceLogTag::ACE_WATERFLOW, "cross size is 0, skip measure");
99         skipMeasure_ = true;
100         return;
101     }
102     auto matchChildren = GreaterOrEqualToInfinity(GetMainAxisSize(idealSize, axis));
103     if (!matchChildren) {
104         layoutWrapper->GetGeometryNode()->SetFrameSize(idealSize);
105     }
106     MinusPaddingToSize(layoutProperty->CreatePaddingAndBorder(), idealSize);
107 
108     int32_t updateIdx = GetUpdateIdx(layoutWrapper, layoutInfo_->footerIndex_);
109     if (updateIdx != -1) {
110         layoutInfo_->Reset(updateIdx);
111         layoutWrapper->GetHostNode()->ChildrenUpdatedFrom(-1);
112     }
113 
114     layoutInfo_->childrenCount_ = layoutWrapper->GetTotalChildCount();
115 
116     InitialItemsCrossSize(layoutProperty, idealSize, layoutInfo_->childrenCount_);
117     mainSize_ = GetMainAxisSize(idealSize, axis);
118     if (layoutInfo_->jumpIndex_ >= 0 && layoutInfo_->jumpIndex_ < layoutInfo_->childrenCount_) {
119         auto crossIndex = layoutInfo_->GetCrossIndex(layoutInfo_->jumpIndex_);
120         if (crossIndex == -1) {
121             // jump to out of cache
122         } else {
123             layoutInfo_->JumpTo(layoutInfo_->items_[0][crossIndex][layoutInfo_->jumpIndex_]);
124         }
125     } else {
126         layoutInfo_->jumpIndex_ = EMPTY_JUMP_INDEX;
127     }
128 
129     FillViewport(mainSize_, layoutWrapper);
130     if (layoutInfo_->targetIndex_.has_value()) {
131         MeasureToTarget(layoutWrapper, layoutInfo_->endIndex_, std::nullopt);
132     }
133     if (layoutInfo_->jumpIndex_ != EMPTY_JUMP_INDEX) {
134         if (layoutInfo_->extraOffset_.has_value() && Negative(layoutInfo_->extraOffset_.value())) {
135             layoutInfo_->extraOffset_.reset();
136         }
137         layoutInfo_->JumpTo({ footerMainStartPos_, footerMainSize_ });
138     }
139     if (matchChildren) {
140         mainSize_ = layoutInfo_->GetMaxMainHeight() + footerMainSize_;
141         idealSize.SetMainSize(mainSize_, axis_);
142         AddPaddingToSize(layoutProperty->CreatePaddingAndBorder(), idealSize);
143         layoutWrapper->GetGeometryNode()->SetFrameSize(idealSize);
144     }
145     layoutInfo_->lastMainSize_ = mainSize_;
146 
147     layoutWrapper->SetCacheCount(layoutProperty->GetCachedCountValue(layoutInfo_->defCachedCount_));
148 }
149 
MeasureToTarget(LayoutWrapper * layoutWrapper,int32_t startFrom,std::optional<int64_t> cacheDeadline)150 bool WaterFlowLayoutAlgorithm::MeasureToTarget(
151     LayoutWrapper* layoutWrapper, int32_t startFrom, std::optional<int64_t> cacheDeadline)
152 {
153     if (layoutInfo_->targetIndex_.value() > layoutInfo_->childrenCount_) {
154         layoutInfo_->targetIndex_.reset();
155         return false;
156     }
157     auto layoutProperty = AceType::DynamicCast<WaterFlowLayoutProperty>(layoutWrapper->GetLayoutProperty());
158     int32_t currentIndex = startFrom;
159     auto position = GetItemPosition(currentIndex);
160     if (layoutInfo_->targetIndex_.value() == LAST_ITEM) {
161         layoutInfo_->targetIndex_ = layoutInfo_->childrenCount_ - 1;
162     }
163     while (layoutInfo_->targetIndex_.has_value() && (startFrom < layoutInfo_->targetIndex_.value())) {
164         auto itemWrapper = layoutWrapper->GetOrCreateChildByIndex(
165             GetChildIndexWithFooter(currentIndex), !cacheDeadline, cacheDeadline.has_value());
166         if (!itemWrapper) {
167             layoutInfo_->targetIndex_.reset();
168             return false;
169         }
170         auto itemCrossSize = itemsCrossSize_.find(position.crossIndex);
171         if (itemCrossSize == itemsCrossSize_.end()) {
172             return false;
173         }
174         itemWrapper->Measure(WaterFlowLayoutUtils::CreateChildConstraint(
175             { itemCrossSize->second, mainSize_, axis_ }, layoutProperty, itemWrapper));
176         if (cacheDeadline) {
177             itemWrapper->Layout();
178             itemWrapper->SetActive(false);
179         }
180         auto itemSize = itemWrapper->GetGeometryNode()->GetMarginFrameSize();
181         auto itemHeight = GetMainAxisSize(itemSize, axis_);
182         auto item = layoutInfo_->items_[0][position.crossIndex].find(currentIndex);
183         if (item == layoutInfo_->items_[0][position.crossIndex].end()) {
184             layoutInfo_->items_[0][position.crossIndex][currentIndex] =
185                 std::make_pair(position.startMainPos, itemHeight);
186         } else {
187             if (item->second.second != itemHeight) {
188                 item->second.second = itemHeight;
189                 layoutInfo_->ClearCacheAfterIndex(currentIndex);
190                 TAG_LOGD(AceLogTag::ACE_WATERFLOW, "item size changed");
191             }
192         }
193         if (layoutInfo_->targetIndex_.value() == currentIndex) {
194             layoutInfo_->targetIndex_.reset();
195         }
196         currentIndex++;
197         position = GetItemPosition(currentIndex);
198         if (cacheDeadline && GetSysTimestamp() > *cacheDeadline) {
199             break;
200         }
201     }
202     return true;
203 }
204 
Layout(LayoutWrapper * layoutWrapper)205 void WaterFlowLayoutAlgorithm::Layout(LayoutWrapper* layoutWrapper)
206 {
207     if (skipMeasure_) {
208         skipMeasure_ = false;
209         return;
210     }
211     auto layoutProperty = AceType::DynamicCast<WaterFlowLayoutProperty>(layoutWrapper->GetLayoutProperty());
212     const int32_t cachedCount = layoutProperty->GetCachedCountValue(layoutInfo_->defCachedCount_);
213 
214     auto size = layoutWrapper->GetGeometryNode()->GetFrameSize();
215     auto padding = layoutWrapper->GetLayoutProperty()->CreatePaddingAndBorder();
216     MinusPaddingToSize(padding, size);
217     auto childFrameOffset = OffsetF(padding.left.value_or(0.0f), padding.top.value_or(0.0f));
218     layoutInfo_->UpdateStartIndex();
219     if (!layoutProperty->HasCachedCount()) {
220         layoutInfo_->UpdateDefaultCachedCount();
221     }
222 
223     auto firstIndex = layoutInfo_->endIndex_;
224     auto crossSize = size.CrossSize(axis_);
225     auto layoutDirection = layoutWrapper->GetLayoutProperty()->GetNonAutoLayoutDirection();
226     const bool isRtl = layoutDirection == TextDirection::RTL && axis_ == Axis::VERTICAL;
227     for (const auto& mainPositions : layoutInfo_->items_[0]) {
228         for (const auto& item : mainPositions.second) {
229             if (item.first < layoutInfo_->startIndex_ - cachedCount ||
230                 item.first > layoutInfo_->endIndex_ + cachedCount) {
231                 continue;
232             }
233             auto itemCrossPosition = itemsCrossPosition_.find(mainPositions.first);
234             if (itemCrossPosition == itemsCrossPosition_.end()) {
235                 return;
236             }
237             auto currentOffset = childFrameOffset;
238             auto crossOffset = itemCrossPosition->second;
239             auto mainOffset = item.second.first + layoutInfo_->currentOffset_;
240             if (isRtl) {
241                 crossOffset = crossSize - crossOffset - itemsCrossSize_.at(mainPositions.first);
242             }
243             if (layoutProperty->IsReverse()) {
244                 mainOffset = mainSize_ - item.second.second - mainOffset;
245             }
246             if (axis_ == Axis::VERTICAL) {
247                 currentOffset += OffsetF(crossOffset, mainOffset);
248             } else {
249                 currentOffset += OffsetF(mainOffset, crossOffset);
250             }
251             const bool isCache = item.first < layoutInfo_->startIndex_ || item.first > layoutInfo_->endIndex_;
252             auto wrapper = layoutWrapper->GetChildByIndex(GetChildIndexWithFooter(item.first), isCache);
253             if (!wrapper) {
254                 continue;
255             }
256             wrapper->GetGeometryNode()->SetMarginFrameOffset(currentOffset);
257             wrapper->Layout();
258             // recode restore info
259             if (item.first == layoutInfo_->startIndex_) {
260                 layoutInfo_->storedOffset_ = mainOffset;
261             }
262 
263             if (!isCache && NonNegative(mainOffset + item.second.second)) {
264                 firstIndex = std::min(firstIndex, item.first);
265             }
266             auto frameNode = AceType::DynamicCast<FrameNode>(wrapper);
267             if (frameNode) {
268                 frameNode->MarkAndCheckNewOpIncNode();
269             }
270         }
271     }
272     layoutInfo_->firstIndex_ = firstIndex;
273     layoutWrapper->SetActiveChildRange(layoutInfo_->NodeIdx(layoutInfo_->FirstIdx()),
274         layoutInfo_->NodeIdx(layoutInfo_->endIndex_), cachedCount, cachedCount);
275     PreloadItems(layoutWrapper, layoutInfo_, cachedCount);
276 
277     LayoutFooter(layoutWrapper, childFrameOffset, layoutProperty->IsReverse());
278 }
279 
LayoutFooter(LayoutWrapper * layoutWrapper,const OffsetF & childFrameOffset,bool reverse)280 void WaterFlowLayoutAlgorithm::LayoutFooter(LayoutWrapper* layoutWrapper, const OffsetF& childFrameOffset, bool reverse)
281 {
282     if (layoutInfo_->itemEnd_ && layoutInfo_->footerIndex_ >= 0) {
283         auto footer = layoutWrapper->GetOrCreateChildByIndex(layoutInfo_->footerIndex_);
284         CHECK_NULL_VOID(footer);
285         auto footerOffset = childFrameOffset;
286         auto mainOffset = layoutInfo_->GetMaxMainHeight() + layoutInfo_->currentOffset_;
287         if (reverse) {
288             mainOffset = mainSize_ - footerMainSize_ - mainOffset;
289         }
290         footerOffset += (axis_ == Axis::VERTICAL) ? OffsetF(0, mainOffset) : OffsetF(mainOffset, 0);
291         footer->GetGeometryNode()->SetMarginFrameOffset(footerOffset);
292         footer->Layout();
293     }
294 }
295 
GetItemPosition(int32_t index)296 FlowItemPosition WaterFlowLayoutAlgorithm::GetItemPosition(int32_t index)
297 {
298     auto crossIndex = layoutInfo_->GetCrossIndex(index);
299     // already in layoutInfo
300     if (crossIndex != -1) {
301         return { crossIndex, layoutInfo_->GetStartMainPos(crossIndex, index) };
302     }
303     auto itemIndex = layoutInfo_->GetCrossIndexForNextItem(layoutInfo_->GetSegment(index));
304     if (itemIndex.lastItemIndex < 0) {
305         return { itemIndex.crossIndex, 0.0f };
306     }
307     auto mainHeight = layoutInfo_->GetMainHeight(itemIndex.crossIndex, itemIndex.lastItemIndex);
308     return { itemIndex.crossIndex, mainHeight + mainGap_ };
309 }
310 
FillViewport(float mainSize,LayoutWrapper * layoutWrapper)311 void WaterFlowLayoutAlgorithm::FillViewport(float mainSize, LayoutWrapper* layoutWrapper)
312 {
313     if (layoutInfo_->currentOffset_ >= 0) {
314         if (!canOverScroll_) {
315             layoutInfo_->currentOffset_ = 0;
316         }
317         layoutInfo_->itemStart_ = true;
318     } else {
319         layoutInfo_->itemStart_ = false;
320     }
321 
322     layoutInfo_->UpdateStartIndex();
323     auto layoutProperty = AceType::DynamicCast<WaterFlowLayoutProperty>(layoutWrapper->GetLayoutProperty());
324     auto currentIndex = layoutInfo_->startIndex_;
325     auto position = GetItemPosition(currentIndex);
326     bool fill = false;
327     while (
328         LessNotEqual(position.startMainPos + layoutInfo_->currentOffset_, mainSize) || layoutInfo_->jumpIndex_ >= 0) {
329         auto itemWrapper = layoutWrapper->GetOrCreateChildByIndex(GetChildIndexWithFooter(currentIndex));
330         if (!itemWrapper) {
331             break;
332         }
333         auto itemCrossSize = itemsCrossSize_.find(position.crossIndex);
334         if (itemCrossSize == itemsCrossSize_.end()) {
335             break;
336         }
337         itemWrapper->Measure(WaterFlowLayoutUtils::CreateChildConstraint(
338             { itemCrossSize->second, mainSize_, axis_ }, layoutProperty, itemWrapper));
339         auto itemSize = itemWrapper->GetGeometryNode()->GetMarginFrameSize();
340         auto itemHeight = GetMainAxisSize(itemSize, axis_);
341         auto item = layoutInfo_->items_[0][position.crossIndex].find(currentIndex);
342         if (item == layoutInfo_->items_[0][position.crossIndex].end()) {
343             layoutInfo_->items_[0][position.crossIndex][currentIndex] =
344                 std::make_pair(position.startMainPos, itemHeight);
345         } else {
346             if (item->second.second != itemHeight) {
347                 TAG_LOGI(AceLogTag::ACE_WATERFLOW,
348                     "item size change. currentIdx:%{public}d,cacheHeight:%{public}f,itemHeight:%{public}f",
349                     currentIndex, item->second.second, itemHeight);
350                 item->second.second = itemHeight;
351                 layoutInfo_->ClearCacheAfterIndex(currentIndex);
352             }
353         }
354         if (layoutInfo_->jumpIndex_ == currentIndex) {
355             layoutInfo_->currentOffset_ =
356                 layoutInfo_->JumpToTargetAlign(layoutInfo_->items_[0][position.crossIndex][currentIndex]);
357             layoutInfo_->currentOffset_ += layoutInfo_->restoreOffset_;
358             // restoreOffSet only be used once
359             layoutInfo_->restoreOffset_ = 0.0f;
360             layoutInfo_->align_ = ScrollAlign::START;
361             layoutInfo_->jumpIndex_ = EMPTY_JUMP_INDEX;
362             layoutInfo_->itemStart_ = false;
363             if (layoutInfo_->extraOffset_.has_value()) {
364                 layoutInfo_->currentOffset_ += layoutInfo_->extraOffset_.value();
365                 layoutInfo_->extraOffset_.reset();
366             }
367         }
368         position = GetItemPosition(++currentIndex);
369         fill = true;
370     }
371     layoutInfo_->endIndex_ = !fill ? currentIndex : currentIndex - 1;
372 
373     layoutInfo_->itemEnd_ = GetChildIndexWithFooter(currentIndex) == layoutInfo_->childrenCount_;
374     if (layoutInfo_->itemEnd_) {
375         ModifyCurrentOffsetWhenReachEnd(mainSize, layoutWrapper);
376     } else {
377         layoutInfo_->offsetEnd_ = false;
378     }
379 }
380 
ModifyCurrentOffsetWhenReachEnd(float mainSize,LayoutWrapper * layoutWrapper)381 void WaterFlowLayoutAlgorithm::ModifyCurrentOffsetWhenReachEnd(float mainSize, LayoutWrapper* layoutWrapper)
382 {
383     auto maxItemHeight = layoutInfo_->GetMaxMainHeight();
384     if (layoutInfo_->footerIndex_ >= 0) {
385         footerMainStartPos_ = maxItemHeight;
386         footerMainSize_ = WaterFlowLayoutUtils::MeasureFooter(layoutWrapper, axis_);
387         maxItemHeight += footerMainSize_;
388     }
389     layoutInfo_->maxHeight_ = maxItemHeight;
390 
391     if (mainSize >= maxItemHeight) {
392         if (!canOverScroll_) {
393             layoutInfo_->currentOffset_ = 0;
394         }
395         layoutInfo_->offsetEnd_ = true;
396         layoutInfo_->itemStart_ = true;
397         return;
398     }
399 
400     if (layoutInfo_->currentOffset_ + maxItemHeight <= mainSize) {
401         layoutInfo_->offsetEnd_ = true;
402         if (!canOverScroll_) {
403             layoutInfo_->currentOffset_ = mainSize - maxItemHeight;
404         }
405 
406         auto oldStart = layoutInfo_->startIndex_;
407         layoutInfo_->UpdateStartIndex();
408         // lazyforeach
409         for (auto i = oldStart; i >= layoutInfo_->startIndex_; i--) {
410             auto itemWrapper = layoutWrapper->GetOrCreateChildByIndex(GetChildIndexWithFooter(i));
411             CHECK_NULL_VOID(itemWrapper);
412             auto layoutProperty = AceType::DynamicCast<WaterFlowLayoutProperty>(layoutWrapper->GetLayoutProperty());
413             float crossSize = itemsCrossSize_.at(layoutInfo_->GetCrossIndex(i));
414             itemWrapper->Measure(WaterFlowLayoutUtils::CreateChildConstraint(
415                 { crossSize, mainSize_, axis_ }, layoutProperty, itemWrapper));
416         }
417     } else {
418         layoutInfo_->offsetEnd_ = false;
419     }
420 }
421 
AppendCacheItem(LayoutWrapper * host,int32_t itemIdx,int64_t deadline)422 bool WaterFlowLayoutAlgorithm::AppendCacheItem(LayoutWrapper* host, int32_t itemIdx, int64_t deadline)
423 {
424     const int32_t lastItem = layoutInfo_->GetLastItem();
425     if (itemIdx <= lastItem) {
426         return host->GetOrCreateChildByIndex(itemIdx, false, true);
427     }
428     const auto sub = layoutInfo_->targetIndex_;
429     layoutInfo_->targetIndex_ = itemIdx;
430     const bool res = MeasureToTarget(host, lastItem, deadline);
431     layoutInfo_->targetIndex_ = sub;
432     return res;
433 }
434 } // namespace OHOS::Ace::NG
435