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