1 /*
2 * Copyright (c) 2022 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/list/list_lanes_layout_algorithm.h"
17
18 #include "base/utils/utils.h"
19 #include "core/components_ng/base/frame_node.h"
20 #include "core/components_ng/syntax/lazy_for_each_node.h"
21 #include "core/components_v2/inspector/inspector_constants.h"
22
23 namespace OHOS::Ace::NG {
24
UpdateListItemConstraint(Axis axis,const OptionalSizeF & selfIdealSize,LayoutConstraintF & contentConstraint)25 void ListLanesLayoutAlgorithm::UpdateListItemConstraint(
26 Axis axis, const OptionalSizeF& selfIdealSize, LayoutConstraintF& contentConstraint)
27 {
28 contentConstraint.parentIdealSize = selfIdealSize;
29 contentConstraint.maxSize.SetMainSize(Infinity<float>(), axis);
30 groupLayoutConstraint_ = contentConstraint;
31 auto crossSizeOptional = selfIdealSize.CrossSize(axis);
32 if (crossSizeOptional.has_value()) {
33 float crossSize = crossSizeOptional.value();
34 groupLayoutConstraint_.maxSize.SetCrossSize(crossSize, axis);
35 if (lanes_ > 1) {
36 float laneGutter = GetLaneGutter();
37 crossSize = (crossSize + laneGutter) / lanes_ - laneGutter;
38 crossSize = crossSize <= 0 ? 1 : crossSize;
39 }
40 if (maxLaneLength_.has_value() && maxLaneLength_.value() < crossSize) {
41 crossSize = maxLaneLength_.value();
42 }
43 contentConstraint.percentReference.SetCrossSize(crossSize, axis);
44 contentConstraint.parentIdealSize.SetCrossSize(crossSize, axis);
45 contentConstraint.maxSize.SetCrossSize(crossSize, axis);
46 if (minLaneLength_.has_value()) {
47 contentConstraint.minSize.SetCrossSize(minLaneLength_.value(), axis);
48 }
49 }
50 }
51
MeasureAndGetChildHeight(LayoutWrapper * layoutWrapper,int32_t childIndex)52 float ListLanesLayoutAlgorithm::MeasureAndGetChildHeight(LayoutWrapper* layoutWrapper, int32_t childIndex)
53 {
54 auto wrapper = layoutWrapper->GetOrCreateChildByIndex(childIndex);
55 CHECK_NULL_RETURN(wrapper, 0.0f);
56 bool isGroup = wrapper->GetHostTag() == V2::LIST_ITEM_GROUP_ETS_TAG;
57 if (isGroup) {
58 auto listLayoutProperty =
59 AceType::DynamicCast<ListLayoutProperty>(layoutWrapper->GetLayoutProperty());
60 // true: layout forward, true: layout all group items.
61 SetListItemGroupParam(wrapper, 0.0f, true, listLayoutProperty, true);
62 wrapper->Measure(groupLayoutConstraint_);
63 } else {
64 wrapper->Measure(childLayoutConstraint_);
65 }
66 float mainLen = GetMainAxisSize(wrapper->GetGeometryNode()->GetMarginFrameSize(), axis_);
67 return mainLen;
68 }
69
LayoutALineForward(LayoutWrapper * layoutWrapper,int32_t & currentIndex,float startPos,float & endPos)70 int32_t ListLanesLayoutAlgorithm::LayoutALineForward(LayoutWrapper* layoutWrapper,
71 int32_t& currentIndex, float startPos, float& endPos)
72 {
73 float mainLen = 0.0f;
74 bool isGroup = false;
75 int32_t cnt = 0;
76 int32_t lanes = lanes_ > 1 ? lanes_ : 1;
77 for (int32_t i = 0; i < lanes && currentIndex + 1 <= GetMaxListItemIndex(); i++) {
78 auto wrapper = layoutWrapper->GetOrCreateChildByIndex(currentIndex + 1);
79 if (!wrapper) {
80 break;
81 }
82 isGroup = wrapper->GetHostTag() == V2::LIST_ITEM_GROUP_ETS_TAG;
83 if (isGroup && cnt > 0) {
84 wrapper->SetActive(false);
85 isGroup = false;
86 break;
87 }
88 cnt++;
89 ++currentIndex;
90 if (isGroup) {
91 ACE_SCOPED_TRACE("ListLayoutAlgorithm::MeasureListItemGroup:%d", currentIndex);
92 auto listLayoutProperty = AceType::DynamicCast<ListLayoutProperty>(layoutWrapper->GetLayoutProperty());
93 SetListItemGroupParam(wrapper, startPos, true, listLayoutProperty, GroupNeedAllLayout());
94 wrapper->Measure(groupLayoutConstraint_);
95 } else {
96 ACE_SCOPED_TRACE("ListLayoutAlgorithm::MeasureListItem:%d", currentIndex);
97 wrapper->Measure(childLayoutConstraint_);
98 }
99 mainLen = std::max(mainLen, GetMainAxisSize(wrapper->GetGeometryNode()->GetMarginFrameSize(), axis_));
100 if (isGroup) {
101 break;
102 }
103 }
104 if (cnt > 0) {
105 endPos = startPos + mainLen;
106 for (int32_t i = 0; i < cnt; i++) {
107 SetItemInfo(currentIndex - i, { startPos, endPos, isGroup });
108 }
109 }
110 return cnt;
111 }
112
LayoutALineBackward(LayoutWrapper * layoutWrapper,int32_t & currentIndex,float endPos,float & startPos)113 int32_t ListLanesLayoutAlgorithm::LayoutALineBackward(LayoutWrapper* layoutWrapper,
114 int32_t& currentIndex, float endPos, float& startPos)
115 {
116 float mainLen = 0.0f;
117 bool isGroup = false;
118 int32_t cnt = 0;
119 int32_t lanes = lanes_ > 1 ? lanes_ : 1;
120 for (int32_t i = 0; i < lanes && currentIndex - 1 >= 0; i++) {
121 if (currentIndex > GetMaxListItemIndex() + 1) {
122 --currentIndex;
123 continue;
124 }
125 auto wrapper = layoutWrapper->GetOrCreateChildByIndex(currentIndex - 1);
126 if (!wrapper) {
127 break;
128 }
129 isGroup = wrapper->GetHostTag() == V2::LIST_ITEM_GROUP_ETS_TAG;
130 if (isGroup && cnt > 0) {
131 wrapper->SetActive(false);
132 isGroup = false;
133 break;
134 }
135 --currentIndex;
136
137 cnt++;
138 if (isGroup) {
139 ACE_SCOPED_TRACE("ListLayoutAlgorithm::MeasureListItemGroup:%d", currentIndex);
140 auto listLayoutProperty = AceType::DynamicCast<ListLayoutProperty>(layoutWrapper->GetLayoutProperty());
141 SetListItemGroupParam(wrapper, endPos, false, listLayoutProperty, GroupNeedAllLayout());
142 wrapper->Measure(groupLayoutConstraint_);
143 } else {
144 ACE_SCOPED_TRACE("ListLayoutAlgorithm::MeasureListItem:%d", currentIndex);
145 wrapper->Measure(childLayoutConstraint_);
146 }
147 mainLen = std::max(mainLen, GetMainAxisSize(wrapper->GetGeometryNode()->GetMarginFrameSize(), axis_));
148 if (isGroup || (currentIndex - FindLanesStartIndex(layoutWrapper, currentIndex)) % lanes == 0) {
149 break;
150 }
151 }
152 if (cnt > 0) {
153 startPos = endPos - mainLen;
154 for (int32_t i = 0; i < cnt; i++) {
155 SetItemInfo(currentIndex + i, { startPos, endPos, isGroup });
156 }
157 }
158 return cnt;
159 }
160
CalculateLanesParam(std::optional<float> & minLaneLength,std::optional<float> & maxLaneLength,int32_t lanes,std::optional<float> crossSizeOptional,float laneGutter)161 int32_t ListLanesLayoutAlgorithm::CalculateLanesParam(std::optional<float>& minLaneLength,
162 std::optional<float>& maxLaneLength, int32_t lanes, std::optional<float> crossSizeOptional, float laneGutter)
163 {
164 if (lanes < 1) {
165 return 1;
166 }
167 // Case 1: lane length constrain is not set
168 // 1.1: use [lanes] set by user if [lanes] is set
169 // 1.2: set [lanes] to 1 if [lanes] is not set
170 if (!crossSizeOptional.has_value() || GreaterOrEqualToInfinity(crossSizeOptional.value()) ||
171 !minLaneLength.has_value() || !maxLaneLength.has_value()) {
172 maxLaneLength.reset();
173 minLaneLength.reset();
174 return lanes;
175 }
176 // Case 2: lane length constrain is set --> need to calculate [lanes_] according to contraint.
177 // We agreed on such rules (assuming we have a vertical list here):
178 // rule 1: [minLaneLength_] has a higher priority than [maxLaneLength_] when decide [lanes_], for e.g.,
179 // if [minLaneLength_] is 40, [maxLaneLength_] is 60, list's width is 120,
180 // the [lanes_] is 3 rather than 2.
181 // rule 2: after [lanes_] is determined by rule 1, the width of lane will be as large as it can be, for
182 // e.g.,
183 // if [minLaneLength_] is 40, [maxLaneLength_] is 60, list's width is 132, the [lanes_] is 3
184 // according to rule 1, then the width of lane will be 132 / 3 = 44 rather than 40,
185 // its [minLaneLength_].
186 auto crossSize = crossSizeOptional.value();
187 ModifyLaneLength(minLaneLength, maxLaneLength, crossSize);
188
189 // if minLaneLength is 40, maxLaneLength is 60
190 // when list's width is 120, lanes_ = 3
191 // when list's width is 80, lanes_ = 2
192 // when list's width is 70, lanes_ = 1
193 float maxLanes = (crossSize + laneGutter) / (minLaneLength.value() + laneGutter);
194 float minLanes = (crossSize + laneGutter) / (maxLaneLength.value() + laneGutter);
195 // let's considerate scenarios when maxCrossSize > 0
196 // now it's guaranteed that [minLaneLength_] <= [maxLaneLength_], i.e., maxLanes >= minLanes > 0
197 // there are 3 scenarios:
198 // 1. 1 > maxLanes >= minLanes > 0
199 // 2. maxLanes >= 1 >= minLanes > 0
200 // 3. maxLanes >= minLanes > 1
201 // 1. 1 > maxLanes >= minLanes > 0 ---> maxCrossSize < minLaneLength_ =< maxLaneLength
202 if (GreatNotEqual(1, maxLanes) && GreatOrEqual(maxLanes, minLanes)) {
203 lanes = 1;
204 minLaneLength = crossSize;
205 maxLaneLength = crossSize;
206 return lanes;
207 }
208 // 2. maxLanes >= 1 >= minLanes > 0 ---> minLaneLength_ = maxCrossSize < maxLaneLength
209 if (GreatOrEqual(maxLanes, 1) && LessOrEqual(minLanes, 1)) {
210 lanes = std::floor(maxLanes);
211 maxLaneLength = crossSize;
212 return lanes;
213 }
214 // 3. maxLanes >= minLanes > 1 ---> minLaneLength_ <= maxLaneLength < maxCrossSize
215 if (GreatOrEqual(maxLanes, minLanes) && GreatNotEqual(minLanes, 1)) {
216 lanes = std::floor(maxLanes);
217 return lanes;
218 }
219 lanes = 1;
220 LOGE("unexpected situation, set lanes to 1, maxLanes: %{public}f, minLanes: %{public}f, minLaneLength_: "
221 "%{public}f, maxLaneLength_: %{public}f",
222 maxLanes, minLanes, minLaneLength.value(), maxLaneLength.value());
223 return lanes;
224 }
225
CalculateLanes(const RefPtr<ListLayoutProperty> & layoutProperty,const LayoutConstraintF & layoutConstraint,std::optional<float> crossSizeOptional,Axis axis)226 void ListLanesLayoutAlgorithm::CalculateLanes(const RefPtr<ListLayoutProperty>& layoutProperty,
227 const LayoutConstraintF& layoutConstraint, std::optional<float> crossSizeOptional, Axis axis)
228 {
229 auto contentConstraint = layoutProperty->GetContentLayoutConstraint().value();
230 auto mainPercentRefer = GetMainAxisSize(contentConstraint.percentReference, axis);
231 int32_t lanes = layoutProperty->GetLanes().value_or(1);
232 lanes = lanes > 1 ? lanes : 1;
233 if (layoutProperty->GetLaneMinLength().has_value()) {
234 minLaneLength_ =
235 ConvertToPx(layoutProperty->GetLaneMinLength().value(), layoutConstraint.scaleProperty, mainPercentRefer);
236 }
237 if (layoutProperty->GetLaneMaxLength().has_value()) {
238 maxLaneLength_ =
239 ConvertToPx(layoutProperty->GetLaneMaxLength().value(), layoutConstraint.scaleProperty, mainPercentRefer);
240 }
241 float laneGutter = 0.0f;
242 if (layoutProperty->GetLaneGutter().has_value()) {
243 laneGutter = ConvertToPx(
244 layoutProperty->GetLaneGutter().value(), layoutConstraint.scaleProperty, crossSizeOptional.value_or(0.0))
245 .value();
246 SetLaneGutter(laneGutter);
247 }
248 lanes_ = CalculateLanesParam(minLaneLength_, maxLaneLength_, lanes, crossSizeOptional, laneGutter);
249 }
250
ModifyLaneLength(std::optional<float> & minLaneLength,std::optional<float> & maxLaneLength,float crossSize)251 void ListLanesLayoutAlgorithm::ModifyLaneLength(
252 std::optional<float>& minLaneLength, std::optional<float>& maxLaneLength, float crossSize)
253 {
254 if (GreatNotEqual(minLaneLength.value(), maxLaneLength.value())) {
255 LOGI("minLaneLength: %{public}f is greater than maxLaneLength: %{public}f, assign minLaneLength to"
256 " maxLaneLength",
257 minLaneLength.value(), maxLaneLength.value());
258 maxLaneLength = minLaneLength;
259 }
260 }
261
CalculateLaneCrossOffset(float crossSize,float childCrossSize)262 float ListLanesLayoutAlgorithm::CalculateLaneCrossOffset(float crossSize, float childCrossSize)
263 {
264 if (lanes_ <= 0) {
265 return 0.0f;
266 }
267 return ListLayoutAlgorithm::CalculateLaneCrossOffset(crossSize / lanes_, childCrossSize / lanes_);
268 }
269
GetLazyForEachIndex(const RefPtr<FrameNode> & host)270 int32_t ListLanesLayoutAlgorithm::GetLazyForEachIndex(const RefPtr<FrameNode>& host)
271 {
272 CHECK_NULL_RETURN(host, -1);
273 auto lazyForEach = AceType::DynamicCast<LazyForEachNode>(host->GetParent());
274 CHECK_NULL_RETURN_NOLOG(lazyForEach, -1);
275 return lazyForEach->GetIndexByUINode(host);
276 }
277
FindLanesStartIndex(LayoutWrapper * layoutWrapper,int32_t startIndex,int32_t index)278 int32_t ListLanesLayoutAlgorithm::FindLanesStartIndex(LayoutWrapper* layoutWrapper, int32_t startIndex, int32_t index)
279 {
280 auto wrapper = layoutWrapper->GetOrCreateChildByIndex(index, false);
281 CHECK_NULL_RETURN_NOLOG(wrapper, index);
282 if (wrapper->GetHostTag() == V2::LIST_ITEM_GROUP_ETS_TAG) {
283 return index;
284 }
285 auto lazyIndex = GetLazyForEachIndex(wrapper->GetHostNode());
286 if (lazyIndex > 0) {
287 index -= lazyIndex;
288 }
289 for (int32_t idx = index; idx > startIndex; idx--) {
290 auto wrapper = layoutWrapper->GetOrCreateChildByIndex(idx - 1, false);
291 CHECK_NULL_RETURN_NOLOG(wrapper, idx);
292 if (wrapper->GetHostTag() == V2::LIST_ITEM_GROUP_ETS_TAG) {
293 return idx;
294 }
295 }
296 if (startIndex == 0) {
297 return 0;
298 }
299 return -1;
300 }
301
FindLanesStartIndex(LayoutWrapper * layoutWrapper,int32_t index)302 int32_t ListLanesLayoutAlgorithm::FindLanesStartIndex(LayoutWrapper* layoutWrapper, int32_t index)
303 {
304 if (lanes_ == 1) {
305 return 0;
306 }
307 auto it = lanesItemRange_.upper_bound(index);
308 if (it == lanesItemRange_.begin()) {
309 int32_t startIdx = FindLanesStartIndex(layoutWrapper, 0, index);
310 lanesItemRange_[startIdx] = index;
311 return startIdx;
312 }
313 it--;
314 if (it->second >= index) {
315 return it->first;
316 }
317 int32_t startIdx = FindLanesStartIndex(layoutWrapper, it->second, index);
318 if (startIdx >= 0) {
319 lanesItemRange_[startIdx] = index;
320 return startIdx;
321 }
322 it->second = index;
323 return it->first;
324 }
325
GetLanesFloor(LayoutWrapper * layoutWrapper,int32_t index)326 int32_t ListLanesLayoutAlgorithm::GetLanesFloor(LayoutWrapper* layoutWrapper, int32_t index)
327 {
328 if (lanes_ > 1) {
329 int32_t startIndex = FindLanesStartIndex(layoutWrapper, index);
330 return index - (index - startIndex) % lanes_;
331 }
332 return index;
333 }
334
LayoutCachedALineForward(LayoutWrapper * layoutWrapper,int32_t & index,float & startPos,float crossSize)335 std::list<int32_t> ListLanesLayoutAlgorithm::LayoutCachedALineForward(LayoutWrapper* layoutWrapper,
336 int32_t& index, float& startPos, float crossSize)
337 {
338 std::list<int32_t> predictBuildList;
339 ListLayoutAlgorithm::PositionMap posMap;
340 float mainLen = 0.0f;
341 bool isGroup = false;
342 int32_t cnt = 0;
343 int32_t lanes = lanes_ > 1 ? lanes_ : 1;
344 for (int32_t i = 0; i < lanes && index + i <= GetMaxListItemIndex(); i++) {
345 auto wrapper = layoutWrapper->GetChildByIndex(index + i);
346 if (!wrapper || wrapper->CheckNeedForceMeasureAndLayout()) {
347 predictBuildList.emplace_back(index + i);
348 continue;
349 }
350 isGroup = wrapper->GetHostTag() == V2::LIST_ITEM_GROUP_ETS_TAG;
351 if (isGroup && cnt > 0) {
352 isGroup = false;
353 break;
354 }
355 cnt++;
356 mainLen = std::max(mainLen, GetMainAxisSize(wrapper->GetGeometryNode()->GetMarginFrameSize(), axis_));
357 if (isGroup) {
358 break;
359 }
360 }
361 if (cnt > 0) {
362 auto endPos = startPos + mainLen;
363 for (int32_t i = 0; i < cnt; i++) {
364 posMap[index + i] = { startPos, endPos, isGroup };
365 }
366 startPos = endPos + GetSpaceWidth();
367 auto startIndex = index;
368 for (const auto& pos: posMap) {
369 auto wrapper = layoutWrapper->GetChildByIndex(pos.first);
370 if (!wrapper) {
371 break;
372 }
373 LayoutItem(wrapper, pos.first, pos.second, startIndex, crossSize);
374 SyncGeometry(wrapper);
375 wrapper->SetActive(false);
376 }
377 }
378 index += cnt + static_cast<int32_t>(predictBuildList.size());
379 return predictBuildList;
380 }
381
LayoutCachedALineBackward(LayoutWrapper * layoutWrapper,int32_t & index,float & endPos,float crossSize)382 std::list<int32_t> ListLanesLayoutAlgorithm::LayoutCachedALineBackward(LayoutWrapper* layoutWrapper,
383 int32_t& index, float& endPos, float crossSize)
384 {
385 std::list<int32_t> predictBuildList;
386 ListLayoutAlgorithm::PositionMap posMap;
387 float mainLen = 0.0f;
388 bool isGroup = false;
389 int32_t cnt = 0;
390 int32_t lanes = lanes_ > 1 ? lanes_ : 1;
391 for (int32_t i = 0; i < lanes && index >= 0; i++) {
392 auto idx = index - i;
393 auto wrapper = layoutWrapper->GetChildByIndex(idx);
394 if (!wrapper || wrapper->CheckNeedForceMeasureAndLayout()) {
395 predictBuildList.emplace_back(idx);
396 continue;
397 }
398 isGroup = wrapper->GetHostTag() == V2::LIST_ITEM_GROUP_ETS_TAG;
399 if (isGroup && cnt > 0) {
400 isGroup = false;
401 break;
402 }
403
404 cnt++;
405 mainLen = std::max(mainLen, GetMainAxisSize(wrapper->GetGeometryNode()->GetMarginFrameSize(), axis_));
406 if (isGroup || (idx - FindLanesStartIndex(layoutWrapper, idx)) % lanes == 0) {
407 break;
408 }
409 }
410 if (cnt > 0) {
411 auto startPos = endPos - mainLen;
412 for (int32_t i = 0; i < cnt; i++) {
413 posMap[index - i] = { startPos, endPos, isGroup };
414 }
415 endPos = startPos - GetSpaceWidth();
416 auto startIndex = index - cnt + 1;
417 for (const auto& pos: posMap) {
418 auto wrapper = layoutWrapper->GetChildByIndex(pos.first);
419 if (!wrapper) {
420 break;
421 }
422 LayoutItem(wrapper, pos.first, pos.second, startIndex, crossSize);
423 SyncGeometry(wrapper);
424 wrapper->SetActive(false);
425 }
426 }
427 index -= cnt + static_cast<int32_t>(predictBuildList.size());
428 return predictBuildList;
429 }
430
LayoutCachedItem(LayoutWrapper * layoutWrapper,int32_t cacheCount)431 std::list<int32_t> ListLanesLayoutAlgorithm::LayoutCachedItem(LayoutWrapper* layoutWrapper, int32_t cacheCount)
432 {
433 std::list<int32_t> predictBuildList;
434 auto size = layoutWrapper->GetGeometryNode()->GetFrameSize();
435 auto padding = layoutWrapper->GetLayoutProperty()->CreatePaddingAndBorder();
436 MinusPaddingToSize(padding, size);
437 float crossSize = GetCrossAxisSize(size, axis_);
438
439 auto itemPosition = GetItemPosition();
440 auto currIndex = itemPosition.rbegin()->first + 1;
441 auto currPos = itemPosition.rbegin()->second.endPos + GetSpaceWidth();
442 for (int32_t i = 0; i < cacheCount && currIndex <= GetMaxListItemIndex(); i++) {
443 auto tmpList = LayoutCachedALineForward(layoutWrapper, currIndex, currPos, crossSize);
444 predictBuildList.merge(tmpList);
445 }
446 currIndex = itemPosition.begin()->first - 1;
447 currPos = itemPosition.begin()->second.startPos - GetSpaceWidth();
448 for (int32_t i = 0; i < cacheCount && currIndex >= 0; i++) {
449 auto tmpList = LayoutCachedALineBackward(layoutWrapper, currIndex, currPos, crossSize);
450 predictBuildList.merge(tmpList);
451 }
452 return predictBuildList;
453 }
454 } // namespace OHOS::Ace::NG