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