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/flex/wrap_layout_algorithm.h"
17
18 #include "core/components_ng/pattern/flex/flex_layout_property.h"
19
20 namespace OHOS::Ace::NG {
21
22 /**
23 * Determine whether to start the layout from the upper left corner
24 */
25
IsStartTopLeft(WrapDirection direction,TextDirection textDirection)26 bool IsStartTopLeft(WrapDirection direction, TextDirection textDirection)
27 {
28 switch (direction) {
29 case WrapDirection::HORIZONTAL:
30 return textDirection == TextDirection::LTR;
31 case WrapDirection::HORIZONTAL_REVERSE:
32 return textDirection == TextDirection::RTL;
33 case WrapDirection::VERTICAL:
34 return true;
35 case WrapDirection::VERTICAL_REVERSE:
36 return false;
37 default:
38 return true;
39 }
40 }
41
IsColumnReverse(WrapDirection direction)42 bool IsColumnReverse(WrapDirection direction)
43 {
44 switch (direction) {
45 case WrapDirection::VERTICAL:
46 return false;
47 case WrapDirection::VERTICAL_REVERSE:
48 return true;
49 default:
50 return false;
51 }
52 }
53
UpdatePercentSensitive(LayoutWrapper * layoutWrapper)54 void WrapLayoutAlgorithm::UpdatePercentSensitive(LayoutWrapper *layoutWrapper)
55 {
56 CHECK_NULL_VOID(layoutWrapper && layoutWrapper->GetHostTag() == V2::FLEX_ETS_TAG);
57 auto layoutAlgorithmWrapper = layoutWrapper->GetLayoutAlgorithm();
58 CHECK_NULL_VOID(layoutAlgorithmWrapper);
59 layoutAlgorithmWrapper->SetPercentWidth(true);
60 layoutAlgorithmWrapper->SetPercentHeight(true);
61 }
62
Measure(LayoutWrapper * layoutWrapper)63 void WrapLayoutAlgorithm::Measure(LayoutWrapper* layoutWrapper)
64 {
65 CHECK_NULL_VOID(layoutWrapper);
66 auto children = layoutWrapper->GetAllChildrenWithBuild();
67 if (children.empty()) {
68 layoutWrapper->GetGeometryNode()->SetFrameSize(SizeF());
69 return;
70 }
71 outOfLayoutChildren_.clear();
72 auto flexProp = AceType::DynamicCast<FlexLayoutProperty>(layoutWrapper->GetLayoutProperty());
73 CHECK_NULL_VOID(flexProp);
74 UpdatePercentSensitive(layoutWrapper);
75 direction_ = flexProp->GetWrapDirection().value_or(WrapDirection::HORIZONTAL);
76 // alignment for alignContent, alignment when cross axis has extra space
77 alignment_ = flexProp->GetAlignment().value_or(WrapAlignment::START);
78 // alignment for justifyContent, main axis alignment
79 mainAlignment_ = flexProp->GetMainAlignment().value_or(WrapAlignment::START);
80 // alignment for alignItems, crossAxisAlignment
81 crossAlignment_ = flexProp->GetCrossAlignment().value_or(WrapAlignment::START);
82 textDir_ = flexProp->GetLayoutDirection();
83 if (textDir_ == TextDirection::AUTO) {
84 textDir_ = AceApplicationInfo::GetInstance().IsRightToLeft() ? TextDirection::RTL : TextDirection::LTR;
85 }
86 isHorizontal_ = direction_ == WrapDirection::HORIZONTAL || direction_ == WrapDirection::HORIZONTAL_REVERSE;
87 isReverse_ = !IsStartTopLeft(direction_, textDir_);
88 isRightDirection_ = textDir_ == TextDirection::RTL;
89 isColumnReverse_ = IsColumnReverse(direction_);
90 PerformLayoutInitialize(flexProp);
91 totalMainLength_ = 0.0f;
92 totalCrossLength_ = 0.0f;
93 auto realMaxSize = GetLeftSize(0.0f, mainLengthLimit_, crossLengthLimit_);
94 auto childLayoutConstraint = layoutWrapper->GetLayoutProperty()->CreateChildConstraint();
95 padding_ = layoutWrapper->GetLayoutProperty()->CreatePaddingAndBorder();
96 MinusPaddingToSize(padding_, realMaxSize);
97 mainLengthLimit_ = GetMainAxisLengthOfSize(realMaxSize);
98 crossLengthLimit_ = GetCrossAxisLengthOfSize(realMaxSize);
99 childLayoutConstraint.UpdateMaxSizeWithCheck(realMaxSize);
100 childLayoutConstraint.UpdateMinSizeWithCheck(SizeF(0.0f, 0.0f));
101 if (isDialogStretch_) {
102 HandleDialogStretch();
103 return;
104 }
105 spacing_ = flexProp->GetSpaceValue({});
106 contentSpace_ = flexProp->GetCrossSpaceValue({});
107 auto spacing = static_cast<float>(spacing_.ConvertToPx());
108 auto contentSpace = static_cast<float>(contentSpace_.ConvertToPx());
109 float currentMainLength = 0.0f;
110 float currentCrossLength = 0.0f;
111 int32_t currentItemCount = 0;
112 float baselineDistance = 0.0f;
113 contentList_.clear();
114 std::list<RefPtr<LayoutWrapper>> currentMainAxisItemsList;
115 for (auto& item : children) {
116 if (item->GetLayoutProperty()->GetVisibilityValue(VisibleType::VISIBLE) == VisibleType::GONE) {
117 continue;
118 }
119 item->Measure(childLayoutConstraint);
120 if (item->IsOutOfLayout()) {
121 outOfLayoutChildren_.emplace_back(item);
122 continue;
123 }
124 // can place current child at current row
125 if (GreatOrEqual(mainLengthLimit_, currentMainLength + GetItemMainAxisLength(item->GetGeometryNode()))) {
126 currentMainLength += GetItemMainAxisLength(item->GetGeometryNode());
127 currentMainLength += spacing;
128 currentCrossLength = std::max(currentCrossLength, GetItemCrossAxisLength(item->GetGeometryNode()));
129 if (crossAlignment_ == WrapAlignment::BASELINE) {
130 baselineDistance = std::max(baselineDistance, item->GetBaselineDistance());
131 }
132 currentMainAxisItemsList.emplace_back(item);
133 currentItemCount += 1;
134 } else {
135 // after finish processing previous row, reverse align order if developer meant to
136 currentMainLength -= spacing;
137 // save info of current main axis items into struct
138 auto contentInfo =
139 ContentInfo(currentMainLength, currentCrossLength, currentItemCount, currentMainAxisItemsList);
140 contentInfo.maxBaselineDistance = baselineDistance;
141 // measure items again if cross axis alignment is stretch
142 // and a item has main axis size differ than content height
143 StretchItemsInContent(layoutWrapper, contentInfo);
144 contentList_.emplace_back(contentInfo);
145 currentMainAxisItemsList.clear();
146 // place current item on a new main axis
147 totalMainLength_ = std::max(currentMainLength, totalMainLength_);
148 totalCrossLength_ += currentCrossLength + contentSpace;
149 currentMainLength = GetItemMainAxisLength(item->GetGeometryNode()) + spacing;
150 currentCrossLength = GetItemCrossAxisLength(item->GetGeometryNode());
151 if (crossAlignment_ == WrapAlignment::BASELINE) {
152 baselineDistance = item->GetBaselineDistance();
153 }
154 currentMainAxisItemsList.emplace_back(item);
155 currentItemCount = 1;
156 }
157 }
158 if (currentItemCount != 0) {
159 // Add last content into list
160 currentMainLength -= spacing;
161 auto contentInfo =
162 ContentInfo(currentMainLength, currentCrossLength, currentItemCount, currentMainAxisItemsList);
163 contentInfo.maxBaselineDistance = baselineDistance;
164 StretchItemsInContent(layoutWrapper, contentInfo);
165 contentList_.emplace_back(contentInfo);
166 totalMainLength_ = std::max(currentMainLength, totalMainLength_);
167 totalCrossLength_ += currentCrossLength;
168 }
169 if (isHorizontal_) {
170 frameSize_ = SizeF(mainLengthLimit_, hasIdealHeight_ ? crossLengthLimit_ : totalCrossLength_);
171 } else {
172 frameSize_ = SizeF(hasIdealWidth_ ? crossLengthLimit_ : totalCrossLength_, mainLengthLimit_);
173 }
174 auto& calcLayoutConstraint = layoutWrapper->GetLayoutProperty()->GetCalcLayoutConstraint();
175 if (Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_ELEVEN) && calcLayoutConstraint) {
176 OptionalSizeF finalSize(frameSize_.Width(), frameSize_.Height());
177 finalSize = UpdateOptionSizeByCalcLayoutConstraint(finalSize, calcLayoutConstraint,
178 layoutWrapper->GetLayoutProperty()->GetLayoutConstraint()->percentReference);
179 frameSize_.SetHeight(finalSize.Height().value_or(frameSize_.Height()));
180 frameSize_.SetWidth(finalSize.Width().value_or(frameSize_.Width()));
181 }
182 AddPaddingToSize(padding_, frameSize_);
183 layoutWrapper->GetGeometryNode()->SetFrameSize(frameSize_);
184 frameOffset_ = layoutWrapper->GetGeometryNode()->GetFrameOffset();
185 }
186
GetMainAxisLengthOfSize(const SizeF & size) const187 float WrapLayoutAlgorithm::GetMainAxisLengthOfSize(const SizeF& size) const
188 {
189 if (!isHorizontal_) {
190 return size.Height();
191 }
192 return size.Width();
193 }
194
GetCrossAxisLengthOfSize(const SizeF & size) const195 float WrapLayoutAlgorithm::GetCrossAxisLengthOfSize(const SizeF& size) const
196 {
197 if (!isHorizontal_) {
198 return size.Width();
199 }
200 return size.Height();
201 }
202
StretchItemsInContent(LayoutWrapper * layoutWrapper,const ContentInfo & content)203 void WrapLayoutAlgorithm::StretchItemsInContent(LayoutWrapper* layoutWrapper, const ContentInfo& content)
204 {
205 if (crossAlignment_ != WrapAlignment::STRETCH) {
206 return;
207 }
208 auto childLayoutConstraint = layoutWrapper->GetLayoutProperty()->CreateChildConstraint();
209 for (const auto& item : content.itemList) {
210 auto itemCrossAxisLength = GetItemCrossAxisLength(item->GetGeometryNode());
211 // if content cross axis size is larger than item cross axis size,
212 // measure items again with content cross axis size as ideal size
213 if (GreatNotEqual(content.crossLength, itemCrossAxisLength)) {
214 if (isHorizontal_) {
215 childLayoutConstraint.selfIdealSize.SetHeight(content.crossLength);
216 } else {
217 childLayoutConstraint.selfIdealSize.SetWidth(content.crossLength);
218 }
219 item->Measure(childLayoutConstraint);
220 }
221 }
222 }
223
Layout(LayoutWrapper * layoutWrapper)224 void WrapLayoutAlgorithm::Layout(LayoutWrapper* layoutWrapper)
225 {
226 auto children = layoutWrapper->GetAllChildrenWithBuild();
227 if (children.empty()) {
228 return;
229 }
230 OffsetF startPosition;
231 OffsetF spaceBetweenContentsOnCrossAxis;
232 if (isHorizontal_) {
233 LayoutWholeWrap(startPosition, spaceBetweenContentsOnCrossAxis, layoutWrapper);
234 TraverseContent(startPosition, spaceBetweenContentsOnCrossAxis);
235 } else {
236 LayoutWholeColumnWrap(startPosition, spaceBetweenContentsOnCrossAxis, layoutWrapper);
237 TraverseColumnContent(startPosition, spaceBetweenContentsOnCrossAxis);
238 }
239
240 for (const auto& child : children) {
241 child->Layout();
242 }
243 contentList_.clear();
244 }
245
HandleDialogStretch()246 void WrapLayoutAlgorithm::HandleDialogStretch()
247 {
248 }
249
PerformLayoutInitialize(const RefPtr<LayoutProperty> & layoutProp)250 void WrapLayoutAlgorithm::PerformLayoutInitialize(const RefPtr<LayoutProperty>& layoutProp)
251 {
252 CHECK_NULL_VOID(layoutProp);
253 auto constraint = layoutProp->GetLayoutConstraint();
254 // if flex width and height is not set, wrap is as large as children, no need to set alignment_.
255 if (constraint->selfIdealSize.Height() || constraint->selfIdealSize.Width()) {
256 auto widthValue = constraint->selfIdealSize.Width();
257 auto heightValue = constraint->selfIdealSize.Height();
258 hasIdealWidth_ = widthValue.has_value();
259 hasIdealHeight_ = heightValue.has_value();
260 if (isHorizontal_) {
261 mainLengthLimit_ = hasIdealWidth_ ? widthValue.value() : constraint->maxSize.Width();
262 crossLengthLimit_ = hasIdealHeight_ ? heightValue.value() : constraint->maxSize.Height();
263 } else {
264 mainLengthLimit_ = hasIdealHeight_ ? heightValue.value() : constraint->maxSize.Height();
265 crossLengthLimit_ = hasIdealWidth_ ? widthValue.value() : constraint->maxSize.Width();
266 }
267 return;
268 }
269 if (AceApplicationInfo::GetInstance().GreatOrEqualTargetAPIVersion(PlatformVersion::VERSION_TWELVE)) {
270 if (isHorizontal_) {
271 mainLengthLimit_ = std::min(constraint->maxSize.Width(), constraint->percentReference.Width());
272 crossLengthLimit_ = std::min(constraint->maxSize.Height(), constraint->percentReference.Height());
273 } else {
274 mainLengthLimit_ = std::min(constraint->maxSize.Height(), constraint->percentReference.Height());
275 crossLengthLimit_ = std::min(constraint->maxSize.Width(), constraint->percentReference.Width());
276 }
277 } else {
278 if (isHorizontal_) {
279 mainLengthLimit_ = constraint->maxSize.Width();
280 crossLengthLimit_ = constraint->maxSize.Height();
281 } else {
282 mainLengthLimit_ = constraint->maxSize.Height();
283 crossLengthLimit_ = constraint->maxSize.Width();
284 }
285 }
286 }
287
GetLeftSize(float crossLength,float mainLeftLength,float crossLeftLength)288 SizeF WrapLayoutAlgorithm::GetLeftSize(float crossLength, float mainLeftLength, float crossLeftLength)
289 {
290 if (isHorizontal_) {
291 return SizeF(mainLeftLength, crossLeftLength - crossLength);
292 }
293 return SizeF(crossLeftLength - crossLength, mainLeftLength);
294 }
295
GetItemMainAxisLength(const RefPtr<GeometryNode> & item) const296 float WrapLayoutAlgorithm::GetItemMainAxisLength(const RefPtr<GeometryNode>& item) const
297 {
298 return isHorizontal_ ? item->GetMarginFrameSize().Width() : item->GetMarginFrameSize().Height();
299 }
300
GetItemCrossAxisLength(const RefPtr<GeometryNode> & item) const301 float WrapLayoutAlgorithm::GetItemCrossAxisLength(const RefPtr<GeometryNode>& item) const
302 {
303 return !isHorizontal_ ? item->GetMarginFrameSize().Width() : item->GetMarginFrameSize().Height();
304 }
305
AddPaddingToStartPosition(OffsetF & startPosition) const306 void WrapLayoutAlgorithm::AddPaddingToStartPosition(OffsetF& startPosition) const
307 {
308 switch (direction_) {
309 // horizontal or vertical will start from top left
310 case WrapDirection::HORIZONTAL:
311 case WrapDirection::VERTICAL:
312 if (textDir_ == TextDirection::RTL) {
313 startPosition.AddX(-padding_.right.value_or(0.0f));
314 } else {
315 startPosition.AddX(padding_.left.value_or(0.0f));
316 }
317 startPosition.AddY(padding_.top.value_or(0.0f));
318 break;
319 case WrapDirection::HORIZONTAL_REVERSE:
320 if (textDir_ == TextDirection::RTL) {
321 startPosition.AddX(padding_.left.value_or(0.0f));
322 } else {
323 startPosition.AddX(-padding_.right.value_or(0.0f));
324 }
325 startPosition.AddY(padding_.top.value_or(0.0f));
326 break;
327 case WrapDirection::VERTICAL_REVERSE:
328 startPosition.AddX(padding_.left.value_or(0.0f));
329 startPosition.AddY(-padding_.bottom.value_or(0.0f));
330 break;
331 default:
332 LOGW("Unknown direction");
333 }
334 }
335
AddExtraSpaceToStartPosition(OffsetF & startPosition,float extraSpace,bool onMainAxis) const336 void WrapLayoutAlgorithm::AddExtraSpaceToStartPosition(OffsetF& startPosition, float extraSpace, bool onMainAxis) const
337 {
338 if (isReverse_) {
339 extraSpace = -extraSpace;
340 }
341 if (onMainAxis) {
342 if (isHorizontal_) {
343 startPosition.AddX(extraSpace);
344 } else {
345 startPosition.AddY(extraSpace);
346 }
347 return;
348 }
349 if (isHorizontal_) {
350 startPosition.AddY(extraSpace);
351 return;
352 }
353 startPosition.AddX(extraSpace);
354 }
355
LayoutWholeWrap(OffsetF & startPosition,OffsetF & spaceBetweenContentsOnCrossAxis,LayoutWrapper * layoutWrapper)356 void WrapLayoutAlgorithm::LayoutWholeWrap(
357 OffsetF& startPosition, OffsetF& spaceBetweenContentsOnCrossAxis, LayoutWrapper* layoutWrapper)
358 {
359 auto contentNum = static_cast<int32_t>(contentList_.size());
360 if (contentNum == 0) {
361 return;
362 }
363
364 const auto& layoutProp = layoutWrapper->GetLayoutProperty();
365 CHECK_NULL_VOID(layoutProp);
366 AddPaddingToStartPosition(startPosition);
367 if (isReverse_) {
368 AddExtraSpaceToStartPosition(startPosition, isHorizontal_ ? -frameSize_.Width() : -frameSize_.Height(), true);
369 }
370 // if cross axis size is not set, cross axis size is as large as children cross axis size sum
371 // no need to set alignment_.
372 if ((!isHorizontal_ && hasIdealWidth_ && crossLengthLimit_ <= totalCrossLength_) ||
373 (!isHorizontal_ && !hasIdealWidth_)) {
374 return;
375 }
376 if ((isHorizontal_ && hasIdealHeight_ && crossLengthLimit_ <= totalCrossLength_) ||
377 (isHorizontal_ && !hasIdealHeight_)) {
378 return;
379 }
380
381 auto crossAxisRemainSpace = crossLengthLimit_ - totalCrossLength_;
382
383 if (isReverse_) {
384 crossAxisRemainSpace = -crossAxisRemainSpace;
385 }
386 // switch align content enum, alignment when extra space exists in container extra spaces
387
388 switch (alignment_) {
389 case WrapAlignment::START:
390 break;
391 // for reverse cases, start position will not include "first" item's main axis size
392 case WrapAlignment::END: {
393 AddExtraSpaceToStartPosition(startPosition, crossAxisRemainSpace, false);
394 break;
395 }
396 case WrapAlignment::CENTER: {
397 // divided the space by two
398 crossAxisRemainSpace /= 2.0f;
399 AddExtraSpaceToStartPosition(startPosition, crossAxisRemainSpace, false);
400 break;
401 }
402 case WrapAlignment::SPACE_BETWEEN: {
403 // space between will not affect start position, update space between only
404 float crossSpace =
405 contentNum > 1 ? (crossLengthLimit_ - totalCrossLength_) / static_cast<float>(contentNum - 1) : 0.0f;
406 spaceBetweenContentsOnCrossAxis = isHorizontal_ ? OffsetF(0.0f, crossSpace) : OffsetF(crossSpace, 0.0f);
407 break;
408 }
409 case WrapAlignment::SPACE_EVENLY: {
410 float crossSpace = contentNum != -1 ? crossAxisRemainSpace / static_cast<float>(contentNum + 1) : 0.0f;
411 AddExtraSpaceToStartPosition(startPosition, crossSpace, false);
412 spaceBetweenContentsOnCrossAxis =
413 isHorizontal_ ? OffsetF(0.0f, std::abs(crossSpace)) : OffsetF(std::abs(crossSpace), 0.0f);
414 break;
415 }
416 case WrapAlignment::SPACE_AROUND: {
417 float crossSpace = crossAxisRemainSpace / static_cast<float>(contentNum);
418 AddExtraSpaceToStartPosition(startPosition, crossSpace / 2.0f, false);
419 spaceBetweenContentsOnCrossAxis =
420 isHorizontal_ ? OffsetF(0.0f, std::abs(crossSpace)) : OffsetF(std::abs(crossSpace), 0.0);
421 break;
422 }
423 default: {
424 break;
425 }
426 }
427 }
428
GetMainAxisRemainSpace(float totalMainLength) const429 SizeF WrapLayoutAlgorithm::GetMainAxisRemainSpace(float totalMainLength) const
430 {
431 if (isHorizontal_) {
432 return SizeF(mainLengthLimit_ - totalMainLength, 0.0f);
433 }
434 return SizeF(0.0f, mainLengthLimit_ - totalMainLength);
435 }
436
GetCrossAxisRemainSpace(float totalCrossLength) const437 SizeF WrapLayoutAlgorithm::GetCrossAxisRemainSpace(float totalCrossLength) const
438 {
439 if (isHorizontal_) {
440 return SizeF(0.0f, crossLengthLimit_ - totalCrossLength);
441 }
442 return SizeF(crossLengthLimit_ - totalCrossLength, 0.0f);
443 }
444
GetMainAxisOffset(const OffsetF & offset) const445 float WrapLayoutAlgorithm::GetMainAxisOffset(const OffsetF& offset) const
446 {
447 if (isHorizontal_) {
448 return offset.GetX();
449 }
450 return offset.GetY();
451 }
452
GetCrossAxisOffset(const OffsetF & offset) const453 float WrapLayoutAlgorithm::GetCrossAxisOffset(const OffsetF& offset) const
454 {
455 if (isHorizontal_) {
456 return offset.GetY();
457 }
458 return offset.GetX();
459 }
460
TraverseContent(const OffsetF & startPosition,const OffsetF & spaceBetweenContentsOnCrossAxis)461 void WrapLayoutAlgorithm::TraverseContent(const OffsetF& startPosition, const OffsetF& spaceBetweenContentsOnCrossAxis)
462 {
463 // determine the content start position by main axis
464 OffsetF contentPosition(startPosition.GetX(), startPosition.GetY());
465 auto contentSpace = static_cast<float>(contentSpace_.ConvertToPx());
466 auto spaceBetween = isHorizontal_ ? spaceBetweenContentsOnCrossAxis.GetY() : spaceBetweenContentsOnCrossAxis.GetX();
467 for (const auto& content : contentList_) {
468 LayoutContent(content, contentPosition);
469 if (isHorizontal_) {
470 contentPosition.AddY(content.crossLength + contentSpace + spaceBetween);
471 } else {
472 contentPosition.AddX(content.crossLength + contentSpace + spaceBetween);
473 }
474 }
475 }
476
GetItemMainOffset(float mainSpace) const477 OffsetF WrapLayoutAlgorithm::GetItemMainOffset(float mainSpace) const
478 {
479 // calculate the offset of each item in content
480 if (isHorizontal_) {
481 return OffsetF(mainSpace, 0.0);
482 }
483 return OffsetF(0.0, mainSpace);
484 }
485
CalcItemCrossAxisOffset(const ContentInfo & content,const OffsetF & contentOffset,const RefPtr<GeometryNode> & node)486 float WrapLayoutAlgorithm::CalcItemCrossAxisOffset(
487 const ContentInfo& content, const OffsetF& contentOffset, const RefPtr<GeometryNode>& node)
488 {
489 switch (crossAlignment_) {
490 case WrapAlignment::START:
491 // stretch has been processed in measure, result is the same as start
492 case WrapAlignment::STRETCH: {
493 if (isHorizontal_) {
494 return contentOffset.GetY();
495 }
496 return contentOffset.GetX();
497 }
498 case WrapAlignment::END: {
499 auto itemFrameSize = node->GetMarginFrameSize();
500 if (isHorizontal_) {
501 return contentOffset.GetY() + content.crossLength - itemFrameSize.Height();
502 }
503 return contentOffset.GetX() + content.crossLength - itemFrameSize.Width();
504 }
505 case WrapAlignment::CENTER: {
506 // divide the space by two
507 auto itemFrameSize = node->GetMarginFrameSize();
508 if (isHorizontal_) {
509 return contentOffset.GetY() + (content.crossLength - itemFrameSize.Height()) / 2.0f;
510 }
511 return contentOffset.GetX() + (content.crossLength - itemFrameSize.Width()) / 2.0f;
512 }
513 case WrapAlignment::BASELINE: {
514 break;
515 }
516 default: {
517 if (isHorizontal_) {
518 return contentOffset.GetY();
519 }
520 return contentOffset.GetX();
521
522 break;
523 }
524 }
525 if (isHorizontal_) {
526 return contentOffset.GetY();
527 }
528 return contentOffset.GetX();
529 }
530
CalcItemMainAxisStartAndSpaceBetween(OffsetF & startPosition,OffsetF & spaceBetweenItemsOnMainAxis,const ContentInfo & content)531 void WrapLayoutAlgorithm::CalcItemMainAxisStartAndSpaceBetween(
532 OffsetF& startPosition, OffsetF& spaceBetweenItemsOnMainAxis, const ContentInfo& content)
533 {
534 // switch align content enum, alignment when extra space exists in container extra spaces
535 float spaceLeftOnMainAxis = mainLengthLimit_ - content.mainLength;
536 switch (mainAlignment_) {
537 case WrapAlignment::START:
538 break;
539 case WrapAlignment::END: {
540 AddExtraSpaceToStartPosition(startPosition, spaceLeftOnMainAxis, true);
541 break;
542 }
543 case WrapAlignment::CENTER: {
544 AddExtraSpaceToStartPosition(startPosition, spaceLeftOnMainAxis / 2.0f, true);
545 break;
546 }
547 case WrapAlignment::SPACE_BETWEEN: {
548 float mainSpace = content.count > 1 ? spaceLeftOnMainAxis / static_cast<float>(content.count - 1) : 0.0f;
549 spaceBetweenItemsOnMainAxis = isHorizontal_ ? OffsetF(mainSpace, 0.0f) : OffsetF(0.0f, mainSpace);
550 break;
551 }
552 case WrapAlignment::SPACE_EVENLY: {
553 float mainSpace = content.count != -1 ? spaceLeftOnMainAxis / static_cast<float>(content.count + 1) : 0.0f;
554 AddExtraSpaceToStartPosition(startPosition, mainSpace, true);
555 spaceBetweenItemsOnMainAxis = isHorizontal_ ? OffsetF(mainSpace, 0.0f) : OffsetF(0.0f, mainSpace);
556 break;
557 }
558 case WrapAlignment::SPACE_AROUND: {
559 float mainSpace = content.count != 0 ? spaceLeftOnMainAxis / static_cast<float>(content.count) : 0.0f;
560 AddExtraSpaceToStartPosition(startPosition, mainSpace / 2.0f, true);
561 spaceBetweenItemsOnMainAxis = isHorizontal_ ? OffsetF(mainSpace, 0.0f) : OffsetF(0.0f, mainSpace);
562 break;
563 }
564 default: {
565 break;
566 }
567 }
568 }
569
LayoutContent(const ContentInfo & content,const OffsetF & position)570 void WrapLayoutAlgorithm::LayoutContent(const ContentInfo& content, const OffsetF& position)
571 {
572 int32_t itemNum = content.count;
573 if (itemNum == 0) {
574 return;
575 }
576 OffsetF contentStartPosition(position.GetX(), position.GetY());
577 OffsetF spaceBetweenItemsOnMainAxis;
578 CalcItemMainAxisStartAndSpaceBetween(contentStartPosition, spaceBetweenItemsOnMainAxis, content);
579
580 FlexItemProperties flexItemProperties;
581 GetFlexItemProperties(content, flexItemProperties);
582 float remainSpace = mainLengthLimit_ - currentMainLength_;
583 for (const auto& itemWrapper : content.itemList) {
584 auto item = itemWrapper->GetGeometryNode();
585 if (GreatNotEqual(remainSpace, 0.0f)) {
586 CalcFlexGrowLayout(itemWrapper, flexItemProperties, remainSpace);
587 }
588 // calc start position and between space
589 auto itemMainAxisOffset = isHorizontal_ ? contentStartPosition.GetX() : contentStartPosition.GetY();
590 if (isReverse_) {
591 itemMainAxisOffset -= GetItemMainAxisLength(item);
592 }
593 auto itemCrossAxisOffset = CalcItemCrossAxisOffset(content, contentStartPosition, item);
594 OffsetF offset;
595 float contentMainAxisSpan = 0.0f;
596 if (isHorizontal_) {
597 offset = OffsetF(itemMainAxisOffset, itemCrossAxisOffset);
598 contentMainAxisSpan = item->GetMarginFrameSize().Width() + static_cast<float>(spacing_.ConvertToPx()) +
599 spaceBetweenItemsOnMainAxis.GetX();
600 contentStartPosition.AddX(isReverse_ ? -contentMainAxisSpan : contentMainAxisSpan);
601 } else {
602 offset = OffsetF(itemCrossAxisOffset, itemMainAxisOffset);
603 contentMainAxisSpan = item->GetMarginFrameSize().Height() + static_cast<float>(spacing_.ConvertToPx()) +
604 spaceBetweenItemsOnMainAxis.GetY();
605 contentStartPosition.AddY(isReverse_ ? -contentMainAxisSpan : contentMainAxisSpan);
606 }
607 itemWrapper->GetGeometryNode()->SetMarginFrameOffset(offset);
608 }
609 }
610
GetFlexItemProperties(const ContentInfo & content,FlexItemProperties & flexItemProperties)611 void WrapLayoutAlgorithm::GetFlexItemProperties(const ContentInfo& content, FlexItemProperties& flexItemProperties)
612 {
613 auto spacing = static_cast<float>(spacing_.ConvertToPx());
614 currentMainLength_ = 0.0f;
615 for (const auto& itemWrapper : content.itemList) {
616 if (!itemWrapper) {
617 continue;
618 }
619 currentMainLength_ += GetItemMainAxisLength(itemWrapper->GetGeometryNode()) + spacing;
620 auto layoutProperty = itemWrapper->GetLayoutProperty();
621 if (!layoutProperty) {
622 continue;
623 }
624 const auto& flexItemProperty = layoutProperty->GetFlexItemProperty();
625 if (!flexItemProperty) {
626 continue;
627 }
628 auto flexGrow = flexItemProperty->GetFlexGrow().value_or(0.0f);
629 if (GreatNotEqual(flexGrow, 0.0f)) {
630 flexItemProperties.totalGrow += flexGrow;
631 }
632 }
633 }
634
CalcFlexGrowLayout(const RefPtr<LayoutWrapper> & itemWrapper,const FlexItemProperties & flexItemProperties,float remainSpace)635 void WrapLayoutAlgorithm::CalcFlexGrowLayout(
636 const RefPtr<LayoutWrapper>& itemWrapper, const FlexItemProperties& flexItemProperties, float remainSpace)
637 {
638 CHECK_NULL_VOID(itemWrapper);
639 auto layoutProperty = itemWrapper->GetLayoutProperty();
640 CHECK_NULL_VOID(layoutProperty);
641 auto& flexItemProperty = layoutProperty->GetFlexItemProperty();
642 CHECK_NULL_VOID(flexItemProperty);
643 auto layoutConstraint = layoutProperty->GetLayoutConstraint();
644 if (!layoutConstraint.has_value()) {
645 return;
646 }
647
648 auto layoutConstraintValue = layoutConstraint.value();
649 float itemFlex = flexItemProperty->GetFlexGrow().value_or(0.0f);
650 if (GreatNotEqual(itemFlex, 0.0f) && GreatNotEqual(remainSpace, 0.0f) &&
651 GreatNotEqual(flexItemProperties.totalGrow, 0.0f)) {
652 float flexSize = itemFlex * remainSpace / flexItemProperties.totalGrow;
653 flexSize += GetItemMainAxisLength(itemWrapper->GetGeometryNode());
654 OptionalSizeF& selfIdealSize = layoutConstraintValue.selfIdealSize;
655 if (direction_ == WrapDirection::HORIZONTAL || direction_ == WrapDirection::HORIZONTAL_REVERSE) {
656 selfIdealSize.SetWidth(flexSize);
657 } else {
658 selfIdealSize.SetHeight(flexSize);
659 }
660 itemWrapper->Measure(layoutConstraintValue);
661 }
662 }
663
AddPaddingToStartPositionForColumn(OffsetF & startPosition) const664 void WrapLayoutAlgorithm::AddPaddingToStartPositionForColumn(OffsetF& startPosition) const
665 {
666 switch (direction_) {
667 // vertical will start from top left
668 case WrapDirection::VERTICAL:
669 if (isRightDirection_) {
670 startPosition.AddX(-padding_.right.value_or(0.0f));
671 } else {
672 startPosition.AddX(padding_.left.value_or(0.0f));
673 }
674 startPosition.AddY(padding_.top.value_or(0.0f));
675 break;
676 case WrapDirection::VERTICAL_REVERSE:
677 if (isRightDirection_) {
678 startPosition.AddX(-padding_.right.value_or(0.0f));
679 } else {
680 startPosition.AddX(padding_.left.value_or(0.0f));
681 }
682 startPosition.AddY(-padding_.bottom.value_or(0.0f));
683 break;
684 default:
685 LOGW("Unknown direction");
686 }
687 }
688
UpdateStartPositionByAlign(OffsetF & startPosition,float crossAxisRemainSpace,OffsetF & spaceBetweenContentsOnCrossAxis,int32_t contentNum)689 void WrapLayoutAlgorithm::UpdateStartPositionByAlign(
690 OffsetF& startPosition, float crossAxisRemainSpace, OffsetF& spaceBetweenContentsOnCrossAxis, int32_t contentNum)
691 {
692 // switch align content enum, alignment when extra space exists in container extra spaces
693 switch (alignment_) {
694 case WrapAlignment::START:
695 break;
696 // for reverse cases, start position will not include "first" item's main axis size
697 case WrapAlignment::END: {
698 startPosition.AddX(crossAxisRemainSpace);
699 break;
700 }
701 case WrapAlignment::CENTER: {
702 // divided the space by two
703 crossAxisRemainSpace /= 2.0f;
704 startPosition.AddX(crossAxisRemainSpace);
705 break;
706 }
707 case WrapAlignment::SPACE_BETWEEN: {
708 // space between will not affect start position, update space between only
709 float crossSpace =
710 contentNum > 1 ? (crossLengthLimit_ - totalCrossLength_) / static_cast<float>(contentNum - 1) : 0.0f;
711 spaceBetweenContentsOnCrossAxis = OffsetF(crossSpace, 0.0f);
712 break;
713 }
714 case WrapAlignment::SPACE_EVENLY: {
715 float crossSpace = contentNum != -1 ? crossAxisRemainSpace / static_cast<float>(contentNum + 1) : 0.0f;
716 startPosition.AddX(crossSpace);
717 spaceBetweenContentsOnCrossAxis =
718 isHorizontal_ ? OffsetF(0.0f, std::abs(crossSpace)) : OffsetF(std::abs(crossSpace), 0.0f);
719 break;
720 }
721 case WrapAlignment::SPACE_AROUND: {
722 float crossSpace = contentNum != 0 ? crossAxisRemainSpace / static_cast<float>(contentNum) : 0.0f;
723 startPosition.AddX(crossSpace / 2.0f);
724 spaceBetweenContentsOnCrossAxis = OffsetF(std::abs(crossSpace), 0.0);
725 break;
726 }
727 default: {
728 break;
729 }
730 }
731 }
732
LayoutWholeColumnWrap(OffsetF & startPosition,OffsetF & spaceBetweenContentsOnCrossAxis,LayoutWrapper * layoutWrapper)733 void WrapLayoutAlgorithm::LayoutWholeColumnWrap(
734 OffsetF& startPosition, OffsetF& spaceBetweenContentsOnCrossAxis, LayoutWrapper* layoutWrapper)
735 {
736 auto contentNum = static_cast<int32_t>(contentList_.size());
737 if (contentNum == 0) {
738 return;
739 }
740
741 const auto& layoutProp = layoutWrapper->GetLayoutProperty();
742 CHECK_NULL_VOID(layoutProp);
743 AddPaddingToStartPositionForColumn(startPosition);
744 if (isRightDirection_) {
745 startPosition.AddX(frameSize_.Width());
746 }
747 if (isColumnReverse_) {
748 AddExtraSpaceToStartPosition(startPosition, -frameSize_.Height(), true);
749 }
750 // if cross axis size is not set, cross axis size is as large as children cross axis size sum
751 // no need to set alignment_.
752 if ((!isHorizontal_ && hasIdealWidth_ && crossLengthLimit_ <= totalCrossLength_) ||
753 (!isHorizontal_ && !hasIdealWidth_)) {
754 return;
755 }
756 auto crossAxisRemainSpace = crossLengthLimit_ - totalCrossLength_;
757 if (isRightDirection_) {
758 crossAxisRemainSpace = -crossAxisRemainSpace;
759 }
760 UpdateStartPositionByAlign(startPosition, crossAxisRemainSpace, spaceBetweenContentsOnCrossAxis, contentNum);
761 }
762
LayoutColumnContent(const ContentInfo & content,const OffsetF & position)763 void WrapLayoutAlgorithm::LayoutColumnContent(const ContentInfo& content, const OffsetF& position)
764 {
765 int32_t itemNum = content.count;
766 if (itemNum == 0) {
767 return;
768 }
769 OffsetF contentStartPosition(position.GetX(), position.GetY());
770 OffsetF spaceBetweenItemsOnMainAxis;
771 CalcItemMainAxisStartAndSpaceBetween(contentStartPosition, spaceBetweenItemsOnMainAxis, content);
772
773 FlexItemProperties flexItemProperties;
774 GetFlexItemProperties(content, flexItemProperties);
775 float remainSpace = mainLengthLimit_ - currentMainLength_;
776 for (const auto& itemWrapper : content.itemList) {
777 auto item = itemWrapper->GetGeometryNode();
778 if (GreatNotEqual(remainSpace, 0.0f)) {
779 CalcFlexGrowLayout(itemWrapper, flexItemProperties, remainSpace);
780 }
781 // calc start position and between space
782 auto itemMainAxisOffset = isHorizontal_ ? contentStartPosition.GetX() : contentStartPosition.GetY();
783 auto itemCrossAxisOffset = CalcItemCrossAxisOffset(content, contentStartPosition, item);
784 if (isRightDirection_) {
785 itemCrossAxisOffset -= GetItemCrossAxisLength(item);
786 }
787 OffsetF offset;
788 float contentMainAxisSpan = 0.0f;
789 if (isColumnReverse_) {
790 itemMainAxisOffset -= GetItemMainAxisLength(item);
791 }
792 offset = OffsetF(itemCrossAxisOffset, itemMainAxisOffset);
793 contentMainAxisSpan = item->GetMarginFrameSize().Height() + static_cast<float>(spacing_.ConvertToPx()) +
794 spaceBetweenItemsOnMainAxis.GetY();
795 contentStartPosition.AddY(isColumnReverse_ ? -contentMainAxisSpan : contentMainAxisSpan);
796 itemWrapper->GetGeometryNode()->SetMarginFrameOffset(offset);
797 }
798 }
799
TraverseColumnContent(const OffsetF & startPosition,const OffsetF & spaceBetweenContentsOnCrossAxis)800 void WrapLayoutAlgorithm::TraverseColumnContent(
801 const OffsetF& startPosition, const OffsetF& spaceBetweenContentsOnCrossAxis)
802 {
803 // determine the content start position by main axis
804 OffsetF contentPosition(startPosition.GetX(), startPosition.GetY());
805 auto contentSpace = static_cast<float>(contentSpace_.ConvertToPx());
806 auto spaceBetween = spaceBetweenContentsOnCrossAxis.GetX();
807 for (const auto& content : contentList_) {
808 LayoutColumnContent(content, contentPosition);
809 if (isRightDirection_) {
810 float leftSpace = content.crossLength + contentSpace + spaceBetween;
811 contentPosition.AddX(-leftSpace);
812 } else {
813 contentPosition.AddX(content.crossLength + contentSpace + spaceBetween);
814 }
815 }
816 }
817 } // namespace OHOS::Ace::NG
818