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