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