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