1 /*
2 * Copyright (c) 2023 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/menu/menu_item/menu_item_layout_algorithm.h"
17
18 #include "core/components_ng/pattern/menu/menu_item/menu_item_pattern.h"
19 #include "core/components_ng/pattern/security_component/security_component_layout_property.h"
20 #include "core/components_ng/property/measure_utils.h"
21
22 namespace OHOS::Ace::NG {
23 constexpr Dimension ITEM_BOTTOM_TOP_PADDING = 8.0_vp;
24 constexpr int32_t PADDING_MULTIPLE = 2;
25 // The maximum width of the right row is 1/3 of content area width
26 constexpr float RIGHT_ROW_MAX_WIDTH_WEIGHT = 3;
Measure(LayoutWrapper * layoutWrapper)27 void MenuItemLayoutAlgorithm::Measure(LayoutWrapper* layoutWrapper)
28 {
29 CHECK_NULL_VOID(layoutWrapper);
30 auto hostNode = layoutWrapper->GetHostNode();
31 CHECK_NULL_VOID(hostNode);
32 auto pipeline = hostNode->GetContext();
33 CHECK_NULL_VOID(pipeline);
34 auto theme = pipeline->GetTheme<SelectTheme>();
35 CHECK_NULL_VOID(theme);
36
37 horInterval_ = static_cast<float>(theme->GetMenuItemHorIntervalPadding().ConvertToPx());
38 middleSpace_ = static_cast<float>(theme->GetIconContentPadding().ConvertToPx());
39 auto props = layoutWrapper->GetLayoutProperty();
40 CHECK_NULL_VOID(props);
41
42 auto curLayoutConstraint = props->GetLayoutConstraint();
43 CHECK_NULL_VOID(curLayoutConstraint);
44 std::optional<LayoutConstraintF> layoutConstraint;
45 auto layoutPolicyProperty = props->GetLayoutPolicyProperty();
46 if (layoutPolicyProperty.has_value() && layoutPolicyProperty.value().IsFix()) {
47 std::optional<LayoutConstraintF> newLayoutConstraint = curLayoutConstraint;
48 RemoveParentRestrictionsForFixIdeal(props, newLayoutConstraint.value());
49 layoutConstraint = newLayoutConstraint;
50 } else {
51 layoutConstraint = curLayoutConstraint;
52 }
53 if (isOption_ && !showDefaultSelectedIcon_) {
54 MeasureOption(layoutWrapper, theme, props, layoutConstraint);
55 } else {
56 MeasureMenuItem(layoutWrapper, theme, props, layoutConstraint);
57 }
58 }
59
RemoveParentRestrictionsForFixIdeal(const RefPtr<LayoutProperty> layoutProperty,LayoutConstraintF & layoutConstraint)60 void MenuItemLayoutAlgorithm::RemoveParentRestrictionsForFixIdeal(
61 const RefPtr<LayoutProperty> layoutProperty, LayoutConstraintF& layoutConstraint)
62 {
63 CHECK_NULL_VOID(layoutProperty);
64 auto layoutPolicyProperty = layoutProperty->GetLayoutPolicyProperty();
65 if (layoutPolicyProperty.has_value()) {
66 auto& layoutPolicy = layoutPolicyProperty.value();
67 if (layoutPolicy.IsWidthFix()) {
68 layoutConstraint.maxSize.SetWidth(std::numeric_limits<float>::infinity());
69 }
70 if (layoutPolicy.IsHeightFix()) {
71 layoutConstraint.maxSize.SetHeight(std::numeric_limits<float>::infinity());
72 }
73 }
74 }
75
Layout(LayoutWrapper * layoutWrapper)76 void MenuItemLayoutAlgorithm::Layout(LayoutWrapper* layoutWrapper)
77 {
78 CHECK_NULL_VOID(layoutWrapper);
79 auto layoutProperty = layoutWrapper->GetLayoutProperty();
80 CHECK_NULL_VOID(layoutProperty);
81
82 if (isOption_ && !showDefaultSelectedIcon_) {
83 LayoutOption(layoutWrapper, layoutProperty);
84 } else {
85 LayoutMenuItem(layoutWrapper, layoutProperty);
86 }
87 }
88
CheckNeedMatchParent(LayoutWrapper * layoutWrapper,std::optional<LayoutConstraintF> & layoutConstraint)89 void MenuItemLayoutAlgorithm::CheckNeedMatchParent(LayoutWrapper* layoutWrapper,
90 std::optional<LayoutConstraintF>& layoutConstraint)
91 {
92 auto menuNode = layoutWrapper->GetHostNode();
93 auto menuItemPattern = menuNode ? menuNode->GetPattern<MenuItemPattern>() : nullptr;
94 SubMenuExpandingMode expandingMode = menuItemPattern ? menuItemPattern->GetExpandingMode()
95 : SubMenuExpandingMode::STACK;
96
97 auto isSubMenu = menuItemPattern ? menuItemPattern->IsSubMenu() : false;
98 auto isEmbedded = menuItemPattern ? menuItemPattern->IsEmbedded() : false;
99 bool matchParent = (expandingMode == SubMenuExpandingMode::STACK && isSubMenu) ||
100 (expandingMode == SubMenuExpandingMode::EMBEDDED && isEmbedded);
101 if (matchParent) {
102 auto width = layoutConstraint->maxSize.Width();
103 layoutConstraint->minSize.SetWidth(width);
104 }
105 }
106
CheckUserHeight(LayoutWrapper * layoutWrapper)107 void MenuItemLayoutAlgorithm::CheckUserHeight(LayoutWrapper* layoutWrapper)
108 {
109 auto props = layoutWrapper->GetLayoutProperty();
110 CHECK_NULL_VOID(props);
111 const auto& calcConstraint = props->GetCalcLayoutConstraint();
112 CHECK_NULL_VOID(calcConstraint);
113 auto layoutConstraint = props->GetLayoutConstraint();
114 CHECK_NULL_VOID(layoutConstraint);
115 if (calcConstraint->selfIdealSize.has_value() &&
116 calcConstraint->selfIdealSize.value().Height().has_value()) {
117 ScaleProperty scaleProperty;
118 if (layoutWrapper->GetGeometryNode() && layoutWrapper->GetGeometryNode()->GetParentLayoutConstraint()) {
119 scaleProperty = layoutWrapper->GetGeometryNode()->GetParentLayoutConstraint()->scaleProperty;
120 } else {
121 scaleProperty = layoutConstraint->scaleProperty;
122 }
123 userHeight_ =
124 ConvertToPx(calcConstraint->selfIdealSize.value().Height()->GetDimension(),
125 scaleProperty, layoutConstraint->percentReference.Height()).value_or(0.0f);
126 }
127 }
128
CalcItemHeight(float leftRowHeight,float rightRowHeight)129 float MenuItemLayoutAlgorithm::CalcItemHeight(float leftRowHeight, float rightRowHeight)
130 {
131 return GreatNotEqual(idealHeight_, 0.0f) ? idealHeight_
132 : std::max(leftRowHeight, rightRowHeight) + padding_.Height();
133 }
134
MeasureRightRow(LayoutWrapper * layoutWrapper,LayoutConstraintF & childConstraint)135 std::pair<float, float> MenuItemLayoutAlgorithm::MeasureRightRow(LayoutWrapper* layoutWrapper,
136 LayoutConstraintF& childConstraint)
137 {
138 auto defaultPair = std::make_pair(0.0f, 0.0f);
139 CHECK_NULL_RETURN(layoutWrapper, defaultPair);
140 auto rightRow = layoutWrapper->GetOrCreateChildByIndex(1);
141 CHECK_NULL_RETURN(rightRow, defaultPair);
142 auto children = rightRow->GetAllChildrenWithBuild();
143 CHECK_EQUAL_RETURN(children.empty(), true, defaultPair);
144 rightRow->Measure(childConstraint);
145
146 auto itemNode = layoutWrapper->GetHostNode();
147 CHECK_NULL_RETURN(itemNode, defaultPair);
148 auto pipeline = itemNode->GetContext();
149 CHECK_NULL_RETURN(pipeline, defaultPair);
150 auto theme = pipeline->GetTheme<SelectTheme>();
151 CHECK_NULL_RETURN(theme, defaultPair);
152 float iconContentPadding = static_cast<float>(theme->GetIconContentPadding().ConvertToPx());
153 float spaceWidth = childConstraint.maxSize.Width();
154 float rowWidth = 0.0f;
155 float rowHeight = Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_TWELVE) ?
156 theme->GetMenuChildMinHeight().ConvertToPx() : minItemHeight_;
157 for (auto it = children.rbegin(); it != children.rend();++it) {
158 const auto& child = *it;
159 if (child != children.front()) {
160 child->Measure(childConstraint);
161 } else {
162 auto labelConstraint = childConstraint;
163 labelConstraint.maxSize.SetWidth(spaceWidth);
164 child->Measure(labelConstraint);
165 }
166 auto childGeometryNode = child->GetGeometryNode();
167 CHECK_NULL_RETURN(childGeometryNode, defaultPair);
168 auto childSize = childGeometryNode->GetMarginFrameSize();
169 spaceWidth -= childSize.Width() + iconContentPadding;
170 rowWidth += childSize.Width() + iconContentPadding;
171 rowHeight = std::max(rowHeight, childSize.Height());
172 }
173 rowWidth -= iconContentPadding;
174 auto rightRowGeometryNode = rightRow->GetGeometryNode();
175 CHECK_NULL_RETURN(rightRowGeometryNode, defaultPair);
176 rightRowGeometryNode->SetFrameSize(SizeF(rowWidth, rowHeight));
177
178 auto marginFrameSize = rightRowGeometryNode->GetMarginFrameSize();
179 return {marginFrameSize.Width(), marginFrameSize.Height()};
180 }
181
CalcContentExpandWidth(std::optional<LayoutConstraintF> & layoutConstraint,float contentWidth,float leftRowWidth,float rightRowWidth)182 void MenuItemLayoutAlgorithm::CalcContentExpandWidth(std::optional<LayoutConstraintF>& layoutConstraint,
183 float contentWidth, float leftRowWidth, float rightRowWidth)
184 {
185 needExpandContent_ = false;
186 emptyWidth_ = 0.0f;
187 if (contentWidth < minRowWidth_) {
188 emptyWidth_ = minRowWidth_ - contentWidth;
189 needExpandContent_ = true;
190 }
191
192 idealWidth_ = 0.0f;
193 if (layoutConstraint->selfIdealSize.Width().has_value()) {
194 idealWidth_ = std::max(layoutConstraint->minSize.Width(),
195 std::min(layoutConstraint->maxSize.Width(), layoutConstraint->selfIdealSize.Width().value()));
196 float newLeftRowWidth = idealWidth_ - rightRowWidth - middleSpace_;
197 newLeftRowWidth -= padding_.Width();
198 if (newLeftRowWidth > leftRowWidth) {
199 emptyWidth_ = newLeftRowWidth - leftRowWidth;
200 needExpandContent_ = true;
201 }
202 }
203 }
204
MeasureItemViews(LayoutConstraintF & childConstraint,std::optional<LayoutConstraintF> & layoutConstraint,LayoutWrapper * layoutWrapper)205 void MenuItemLayoutAlgorithm::MeasureItemViews(LayoutConstraintF& childConstraint,
206 std::optional<LayoutConstraintF>& layoutConstraint, LayoutWrapper* layoutWrapper)
207 {
208 auto leftRow = layoutWrapper->GetOrCreateChildByIndex(0);
209 CHECK_NULL_VOID(leftRow);
210
211 // measure right row
212 childConstraint.maxSize.SetWidth((maxRowWidth_ - middleSpace_) / RIGHT_ROW_MAX_WIDTH_WEIGHT);
213 auto [rightRowWidth, rightRowHeight] = MeasureRightRow(layoutWrapper, childConstraint);
214
215 // measure left row
216 auto maxWidth = maxRowWidth_ - rightRowWidth - middleSpace_;
217 childConstraint.maxSize.SetWidth(maxWidth);
218 MeasureRow(layoutWrapper, leftRow, childConstraint);
219 float leftRowWidth = leftRow->GetGeometryNode()->GetMarginFrameSize().Width();
220 float leftRowHeight = leftRow->GetGeometryNode()->GetMarginFrameSize().Height();
221 float contentWidth = 0.0f;
222 contentWidth = leftRowWidth + rightRowWidth + padding_.Width() + middleSpace_;
223
224 CalcContentExpandWidth(layoutConstraint, contentWidth, leftRowWidth, rightRowWidth);
225
226 auto width = std::max(minRowWidth_, contentWidth);
227 auto actualWidth = GreatNotEqual(idealWidth_, 0.0f) ? idealWidth_ : width;
228 childConstraint.minSize.SetWidth(actualWidth - padding_.Width());
229 childConstraint.maxSize.SetWidth(actualWidth - padding_.Width());
230 auto expandableHeight = MeasureExpandableHeight(childConstraint, layoutWrapper);
231
232 float itemHeight = CalcItemHeight(leftRowHeight, rightRowHeight);
233 UpdateSelfSize(layoutWrapper, actualWidth, itemHeight, expandableHeight);
234 }
235
MeasureExpandableHeight(LayoutConstraintF & childConstraint,LayoutWrapper * layoutWrapper)236 float MenuItemLayoutAlgorithm::MeasureExpandableHeight(LayoutConstraintF& childConstraint, LayoutWrapper* layoutWrapper)
237 {
238 auto expandableHeight = 0.0f;
239 auto expandableArea = layoutWrapper->GetOrCreateChildByIndex(EXPANDABLE_AREA_VIEW_INDEX);
240 if (expandableArea) {
241 expandableArea->Measure(childConstraint);
242 auto expandableAreaGeometryNode = expandableArea->GetGeometryNode();
243 CHECK_NULL_RETURN(expandableAreaGeometryNode, expandableHeight);
244 expandableHeight = std::max(expandableAreaGeometryNode->GetMarginFrameSize().Height(), 0.0f);
245 }
246 return expandableHeight;
247 }
248
MeasureRow(LayoutWrapper * layoutWrapper,const RefPtr<LayoutWrapper> & row,const LayoutConstraintF & constraint)249 void MenuItemLayoutAlgorithm::MeasureRow(LayoutWrapper* layoutWrapper, const RefPtr<LayoutWrapper>& row,
250 const LayoutConstraintF& constraint)
251 {
252 auto itemNode = layoutWrapper->GetHostNode();
253 CHECK_NULL_VOID(itemNode);
254 auto pipeline = itemNode->GetContext();
255 CHECK_NULL_VOID(pipeline);
256
257 auto children = row->GetAllChildrenWithBuild();
258 CHECK_EQUAL_VOID(isOption_ && !showDefaultSelectedIcon_ && children.empty(), true);
259
260 float spaceWidth = constraint.maxSize.Width();
261 float rowWidth = 0.0f;
262 auto theme = pipeline->GetTheme<SelectTheme>();
263 CHECK_NULL_VOID(theme);
264 float rowHeight = 0.0f;
265 rowHeight = isOption_ && !showDefaultSelectedIcon_ ? 0.0f : (
266 Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_TWELVE) ?
267 theme->GetMenuChildMinHeight().ConvertToPx() : minItemHeight_
268 );
269 float iconContentPadding = 0.0f;
270 if (!isOption_) {
271 iconContentPadding = static_cast<float>(theme->GetIconContentPadding().ConvertToPx());
272 }
273
274 for (const auto& child : children) {
275 if (child != children.back()) {
276 // not content node
277 child->Measure(constraint);
278 } else {
279 // content node update constraint max width
280 auto contentConstraint = constraint;
281 contentConstraint.maxSize.SetWidth(spaceWidth);
282 child->Measure(contentConstraint);
283 }
284 auto childSize = child->GetGeometryNode()->GetMarginFrameSize();
285 spaceWidth -= childSize.Width() + iconContentPadding;
286 rowWidth += childSize.Width() + iconContentPadding;
287 rowHeight = std::max(rowHeight, childSize.Height());
288 }
289 if (!isOption_ && GreatNotEqual(rowWidth, iconContentPadding)) {
290 rowWidth -= iconContentPadding;
291 }
292 if (Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_FOURTEEN) && isOption_ &&
293 !showDefaultSelectedIcon_) {
294 rowHeight += (ITEM_BOTTOM_TOP_PADDING * PADDING_MULTIPLE).ConvertToPx();
295 }
296 row->GetGeometryNode()->SetFrameSize(SizeF(rowWidth, rowHeight));
297 }
298
CheckNeedExpandContent(LayoutWrapper * layoutWrapper,LayoutConstraintF & childConstraint)299 void MenuItemLayoutAlgorithm::CheckNeedExpandContent(LayoutWrapper* layoutWrapper,
300 LayoutConstraintF& childConstraint)
301 {
302 if (needExpandContent_) {
303 auto menuItemNode = layoutWrapper->GetHostNode();
304 CHECK_NULL_VOID(menuItemNode);
305 auto pattern = menuItemNode->GetPattern<MenuItemPattern>();
306 CHECK_NULL_VOID(pattern);
307 auto contentNode = pattern->GetContentNode();
308 CHECK_NULL_VOID(contentNode);
309
310 auto leftRow = layoutWrapper->GetChildByIndex(0);
311 CHECK_NULL_VOID(leftRow);
312 auto newRowSize = leftRow->GetGeometryNode()->GetFrameSize();
313 newRowSize.SetWidth(emptyWidth_ + newRowSize.Width());
314 leftRow->GetGeometryNode()->SetFrameSize(newRowSize);
315
316 auto oldTextSize = contentNode->GetGeometryNode()->GetFrameSize();
317 float newTextWidth = emptyWidth_ + oldTextSize.Width();
318 childConstraint.minSize.SetWidth(newTextWidth);
319 childConstraint.maxSize.SetWidth(newTextWidth);
320 contentNode->Measure(childConstraint);
321 }
322 }
323
UpdateSelfSize(LayoutWrapper * layoutWrapper,float width,float itemHeight,float expandableHeight)324 void MenuItemLayoutAlgorithm::UpdateSelfSize(LayoutWrapper* layoutWrapper,
325 float width, float itemHeight, float expandableHeight)
326 {
327 CHECK_NULL_VOID(layoutWrapper);
328 auto menuNode = layoutWrapper->GetHostNode();
329 auto menuItemPattern = menuNode ? menuNode->GetPattern<MenuItemPattern>() : nullptr;
330 auto isEmbedded = menuItemPattern ? menuItemPattern->IsEmbedded() : false;
331 auto expandingMode = menuItemPattern ? menuItemPattern->GetExpandingMode() : SubMenuExpandingMode::SIDE;
332 float menuItemHeight = itemHeight;
333 if (expandingMode == SubMenuExpandingMode::EMBEDDED && !isEmbedded && GreatNotEqual(menuItemHeight, 0.0f)) {
334 auto props = layoutWrapper->GetLayoutProperty();
335 CHECK_NULL_VOID(props);
336 const auto& calcConstraint = props->GetCalcLayoutConstraint();
337 if (calcConstraint) {
338 auto minWidth = CalcLength(minRowWidth_);
339 auto height = CalcLength(menuItemHeight + expandableHeight);
340 auto maxWidth = calcConstraint->maxSize->Width();
341 calcConstraint->UpdateMaxSizeWithCheck(CalcSize(maxWidth, height));
342 calcConstraint->UpdateMinSizeWithCheck(CalcSize(minWidth, height));
343 }
344 }
345
346 auto bordersHeight = 0.0f;
347 itemHeight += GetDividerStroke(layoutWrapper);
348 auto clickableArea = layoutWrapper->GetOrCreateChildByIndex(CLICKABLE_AREA_VIEW_INDEX);
349 if (Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_TWELVE)) {
350 float height = CalcSelfHeight(itemHeight, bordersHeight);
351 layoutWrapper->GetGeometryNode()->SetContentSize(SizeF(width, height + expandableHeight));
352 if (clickableArea) {
353 clickableArea->GetGeometryNode()->SetFrameSize(SizeF(width, height + bordersHeight));
354 }
355 } else {
356 layoutWrapper->GetGeometryNode()->SetContentSize(SizeF(width, itemHeight));
357 }
358 BoxLayoutAlgorithm::PerformMeasureSelf(layoutWrapper);
359 auto geometryNode = layoutWrapper->GetGeometryNode();
360 CHECK_NULL_VOID(geometryNode);
361 TAG_LOGD(AceLogTag::ACE_MENU, "MenuItem measure, itemContentSizeH:%{public}f, item marginFrameSizeH:%{public}f",
362 geometryNode->GetContentSize().Height(), geometryNode->GetMarginFrameSize().Height());
363 }
364
CalcSelfHeight(float itemHeight,float bordersHeight)365 float MenuItemLayoutAlgorithm::CalcSelfHeight(float itemHeight, float bordersHeight)
366 {
367 return std::max(itemHeight - bordersHeight, minItemHeight_);
368 }
369
GetDividerStroke(LayoutWrapper * layoutWrapper)370 float MenuItemLayoutAlgorithm::GetDividerStroke(LayoutWrapper* layoutWrapper)
371 {
372 auto menuItemNode = layoutWrapper->GetHostNode();
373 CHECK_NULL_RETURN(menuItemNode, 0.0f);
374 auto pattern = menuItemNode->GetPattern<MenuItemPattern>();
375 CHECK_NULL_RETURN(pattern, 0.0f);
376 auto topDivider = pattern->GetTopDivider();
377 if (topDivider && topDivider->GetParent()) {
378 return 0.0f;
379 }
380 return pattern->GetDividerStroke();
381 }
382
GetBordersHeight(LayoutWrapper * layoutWrapper)383 float MenuItemLayoutAlgorithm::GetBordersHeight(LayoutWrapper* layoutWrapper)
384 {
385 auto props = layoutWrapper->GetLayoutProperty();
386 CHECK_NULL_RETURN(props, 0.0f);
387 const auto& border = props->GetBorderWidthProperty();
388 CHECK_NULL_RETURN(border, 0.0f);
389 return border->topDimen.value_or(Dimension(0.0)).ConvertToPx() +
390 border->bottomDimen.value_or(Dimension(0.0)).ConvertToPx();
391 }
392
GetMenuItemVerticalPadding(LayoutWrapper * layoutWrapper)393 float MenuItemLayoutAlgorithm::GetMenuItemVerticalPadding(LayoutWrapper* layoutWrapper)
394 {
395 float ret = 0.0f;
396 CHECK_NULL_RETURN(layoutWrapper, ret);
397 auto hostNode = layoutWrapper->GetHostNode();
398 CHECK_NULL_RETURN(hostNode, ret);
399 auto pipeline = hostNode->GetContext();
400 CHECK_NULL_RETURN(pipeline, ret);
401 auto theme = pipeline->GetTheme<SelectTheme>();
402 CHECK_NULL_RETURN(theme, ret);
403 return theme->GetMenuItemVerticalPadding().ConvertToPx();
404 }
405
GetIdealWidth(LayoutWrapper * layoutWrapper)406 std::optional<float> MenuItemLayoutAlgorithm::GetIdealWidth(LayoutWrapper* layoutWrapper)
407 {
408 CHECK_NULL_RETURN(layoutWrapper, std::nullopt);
409 // layout property not update in layoutWrapper when measure
410 auto optionProps = layoutWrapper->GetLayoutProperty();
411 CHECK_NULL_RETURN(optionProps, std::nullopt);
412
413 const auto& layoutConstraint = optionProps->GetCalcLayoutConstraint();
414 CHECK_NULL_RETURN(layoutConstraint, std::nullopt);
415
416 const auto& minSize = layoutConstraint->minSize;
417 CHECK_NULL_RETURN(minSize, std::nullopt);
418
419 const auto& width = minSize->Width();
420 if (width->IsValid()) {
421 return width->GetDimension().ConvertToPx();
422 }
423
424 return std::nullopt;
425 }
426
UpdateIconMargin(LayoutWrapper * layoutWrapper)427 void MenuItemLayoutAlgorithm::UpdateIconMargin(LayoutWrapper* layoutWrapper)
428 {
429 auto optionNode = layoutWrapper->GetHostNode();
430 CHECK_NULL_VOID(optionNode);
431 auto optionPattern = optionNode->GetPattern<MenuItemPattern>();
432 CHECK_NULL_VOID(optionPattern);
433 auto layoutProps = layoutWrapper->GetLayoutProperty();
434 CHECK_NULL_VOID(layoutProps);
435 auto direction = layoutProps->GetNonAutoLayoutDirection();
436 bool isRtl = direction == TextDirection::RTL;
437 const auto& selectTheme = optionPattern->GetSelectTheme();
438 CHECK_NULL_VOID(selectTheme);
439 auto calcLength = CalcLength(selectTheme->GetIconContentPadding());
440 MarginProperty margin;
441 if (isRtl) {
442 margin.left = calcLength;
443 margin.right = CalcLength();
444 } else {
445 margin.left = CalcLength();
446 margin.right = calcLength;
447 }
448 Alignment align = isRtl ? Alignment::CENTER_RIGHT : Alignment::CENTER_LEFT;
449 auto child = layoutWrapper->GetOrCreateChildByIndex(0);
450 for (auto iconChild : child->GetAllChildrenWithBuild()) {
451 if ((iconChild->GetHostTag() == V2::IMAGE_ETS_TAG) || (iconChild->GetHostTag() == V2::SYMBOL_ETS_TAG)) {
452 auto iconProps = iconChild->GetLayoutProperty();
453 iconProps->UpdateAlignment(align);
454 iconProps->UpdateMargin(margin);
455 }
456 }
457 }
458
InitPadding(const RefPtr<LayoutProperty> & props,std::optional<LayoutConstraintF> & layoutConstraint)459 void MenuItemLayoutAlgorithm::InitPadding(const RefPtr<LayoutProperty>& props,
460 std::optional<LayoutConstraintF>& layoutConstraint)
461 {
462 padding_ = props->CreatePaddingAndBorderWithDefault(horInterval_, verInterval_, 0.0f, 0.0f);
463 }
464
UpdateIdealSize(LayoutWrapper * layoutWrapper,const RefPtr<LayoutProperty> & props,std::optional<LayoutConstraintF> & layoutConstraint)465 void MenuItemLayoutAlgorithm::UpdateIdealSize(LayoutWrapper* layoutWrapper, const RefPtr<LayoutProperty>& props,
466 std::optional<LayoutConstraintF>& layoutConstraint)
467 {
468 const auto& calcConstraint = props->GetCalcLayoutConstraint();
469 if (calcConstraint && calcConstraint->selfIdealSize.has_value() &&
470 calcConstraint->selfIdealSize.value().Width().has_value()) {
471 ScaleProperty scaleProperty;
472 if (layoutWrapper->GetGeometryNode() && layoutWrapper->GetGeometryNode()->GetParentLayoutConstraint()) {
473 scaleProperty = layoutWrapper->GetGeometryNode()->GetParentLayoutConstraint()->scaleProperty;
474 } else {
475 scaleProperty = layoutConstraint->scaleProperty;
476 }
477 layoutConstraint->selfIdealSize.SetWidth(
478 ConvertToPx(calcConstraint->selfIdealSize.value().Width()->GetDimension(), scaleProperty,
479 layoutConstraint->percentReference.Width()));
480 }
481 if (calcConstraint && calcConstraint->selfIdealSize.has_value() &&
482 calcConstraint->selfIdealSize.value().Height().has_value()) {
483 idealHeight_ = calcConstraint->selfIdealSize.value().Height()->GetDimension().ConvertToPx();
484 }
485 }
486
MeasureMenuItem(LayoutWrapper * layoutWrapper,const RefPtr<SelectTheme> & selectTheme,const RefPtr<LayoutProperty> & props,std::optional<LayoutConstraintF> & layoutConstraint)487 void MenuItemLayoutAlgorithm::MeasureMenuItem(LayoutWrapper* layoutWrapper, const RefPtr<SelectTheme>& selectTheme,
488 const RefPtr<LayoutProperty>& props, std::optional<LayoutConstraintF>& layoutConstraint)
489 {
490 if (Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE)) {
491 verInterval_ = GetMenuItemVerticalPadding(layoutWrapper) - GetBordersHeight(layoutWrapper);
492 }
493 InitPadding(props, layoutConstraint);
494 CHECK_NULL_VOID(layoutWrapper);
495 auto menuItemNode = layoutWrapper->GetHostNode();
496 CHECK_NULL_VOID(menuItemNode);
497 auto pattern = menuItemNode->GetPattern<MenuItemPattern>();
498 CHECK_NULL_VOID(pattern);
499 if (isOption_ && showDefaultSelectedIcon_) {
500 if (pattern->IsSelectOption() && pattern->GetHasOptionWidth()) {
501 auto selectOptionWidth = pattern->GetSelectOptionWidth();
502 layoutConstraint->minSize.SetWidth(selectOptionWidth);
503 layoutConstraint->maxSize.SetWidth(selectOptionWidth);
504 layoutConstraint->selfIdealSize.SetWidth(selectOptionWidth);
505 }
506 UpdateIconMargin(layoutWrapper);
507 }
508 maxRowWidth_ = layoutConstraint->maxSize.Width() - padding_.Width();
509 UpdateIdealSize(layoutWrapper, props, layoutConstraint);
510 if (layoutConstraint->selfIdealSize.Width().has_value()) {
511 maxRowWidth_ =
512 std::max(layoutConstraint->minSize.Width(),
513 std::min(layoutConstraint->maxSize.Width(), layoutConstraint->selfIdealSize.Width().value())) -
514 padding_.Width();
515 }
516 CheckNeedMatchParent(layoutWrapper, layoutConstraint);
517 minRowWidth_ = layoutConstraint->minSize.Width();
518
519 auto childConstraint = props->CreateChildConstraint();
520 minItemHeight_ = static_cast<float>(selectTheme->GetOptionMinHeight().ConvertToPx());
521 childConstraint.minSize.SetHeight(Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_TWELVE) ?
522 selectTheme->GetMenuChildMinHeight().ConvertToPx() : minItemHeight_);
523
524 iconSize_ = selectTheme->GetIconSideLength().ConvertToPx();
525 MeasureItemViews(childConstraint, layoutConstraint, layoutWrapper);
526 MeasureClickableArea(layoutWrapper);
527 CheckNeedExpandContent(layoutWrapper, childConstraint);
528 }
529
MeasureClickableArea(LayoutWrapper * layoutWrapper)530 void MenuItemLayoutAlgorithm::MeasureClickableArea(LayoutWrapper* layoutWrapper)
531 {
532 auto clickableArea = layoutWrapper->GetOrCreateChildByIndex(CLICKABLE_AREA_VIEW_INDEX);
533 if (GreatNotEqual(idealWidth_, 0.0f)) {
534 layoutWrapper->GetGeometryNode()->SetFrameWidth(idealWidth_);
535 if (clickableArea) {
536 clickableArea->GetGeometryNode()->SetFrameWidth(idealWidth_);
537 }
538 }
539 }
540
MeasureOption(LayoutWrapper * layoutWrapper,const RefPtr<SelectTheme> & selectTheme,const RefPtr<LayoutProperty> & props,const std::optional<LayoutConstraintF> & layoutConstraint)541 void MenuItemLayoutAlgorithm::MeasureOption(LayoutWrapper* layoutWrapper, const RefPtr<SelectTheme>& selectTheme,
542 const RefPtr<LayoutProperty>& props, const std::optional<LayoutConstraintF>& layoutConstraint)
543 {
544 auto optionNode = layoutWrapper->GetHostNode();
545 CHECK_NULL_VOID(optionNode);
546 auto optionPattern = optionNode->GetPattern<MenuItemPattern>();
547 CHECK_NULL_VOID(optionPattern);
548
549 auto idealSize = CreateIdealSize(layoutConstraint.value_or(LayoutConstraintF()), Axis::HORIZONTAL,
550 props->GetMeasureType(MeasureType::MATCH_CONTENT), true);
551 float maxChildWidth = layoutConstraint->maxSize.Width() - horInterval_ * 2.0f;
552 // measure child
553 auto childConstraint = props->CreateChildConstraint();
554 childConstraint.maxSize.SetWidth(maxChildWidth);
555 // set self size based on childNode size;
556 auto minOptionHeight = static_cast<float>(selectTheme->GetOptionMinHeight().ConvertToPx());
557 auto child = layoutWrapper->GetOrCreateChildByIndex(0);
558 CHECK_NULL_VOID(child);
559
560 auto textAlign = static_cast<TextAlign>(selectTheme->GetOptionContentNormalAlign());
561 auto rowChild = child->GetOrCreateChildByIndex(0);
562 if (rowChild && (rowChild->GetHostTag() == V2::PASTE_BUTTON_ETS_TAG) && textAlign != TextAlign::CENTER) {
563 auto securityLayoutProperty = DynamicCast<SecurityComponentLayoutProperty>(rowChild->GetLayoutProperty());
564 CHECK_NULL_VOID(securityLayoutProperty);
565 securityLayoutProperty->UpdateBackgroundLeftPadding(Dimension(horInterval_));
566 }
567 UpdateIconMargin(layoutWrapper);
568 MeasureRow(layoutWrapper, child, childConstraint);
569 auto childSize = child->GetGeometryNode()->GetMarginFrameSize();
570 childSize.AddWidth(horInterval_ * 2.0f);
571 idealSize.UpdateSizeWithCheck(childSize);
572 auto idealWidth = GetIdealWidth(layoutWrapper);
573 if (idealWidth.has_value()) {
574 auto optionPaintProperty = optionNode->GetPaintProperty<MenuItemPaintProperty>();
575 if (optionPaintProperty && (optionPaintProperty->GetIdealWidthForWeb() > 0) &&
576 (idealWidth.value() < optionPaintProperty->GetIdealWidthForWeb())) {
577 idealSize.SetWidth(std::min(optionPaintProperty->GetIdealWidthForWeb(), layoutConstraint->maxSize.Width()));
578 } else {
579 idealSize.SetWidth(idealWidth.value());
580 }
581 }
582 idealSize.SetHeight(std::max(minOptionHeight, idealSize.Height()));
583 if (optionPattern->IsSelectOption() && optionPattern->GetHasOptionWidth()) {
584 idealSize.SetWidth(optionPattern->GetSelectOptionWidth());
585 }
586 if (rowChild && (rowChild->GetHostTag() == V2::PASTE_BUTTON_ETS_TAG)) {
587 float dividerWidth = static_cast<float>(selectTheme->GetDefaultDividerWidth().ConvertToPx());
588 SizeF idealSizePaste(idealSize.Width() - dividerWidth, idealSize.Height() - dividerWidth);
589 childConstraint.selfIdealSize.SetSize(idealSizePaste);
590 rowChild->Measure(childConstraint);
591 }
592 layoutWrapper->GetGeometryNode()->SetFrameSize(idealSize);
593 if (textAlign == TextAlign::CENTER) {
594 ExtendTextAndRowNode(child, idealSize, horInterval_, childConstraint);
595 }
596 }
597
CalcRowTopSpace(float rowsHeight,float itemHeight,LayoutWrapper * layoutWrapper,float leftOrRightRowHeight)598 float MenuItemLayoutAlgorithm::CalcRowTopSpace(float rowsHeight, float itemHeight,
599 LayoutWrapper* layoutWrapper, float leftOrRightRowHeight)
600 {
601 return (itemHeight - leftOrRightRowHeight + GetBordersHeight(layoutWrapper)) / 2.0f;
602 }
603
LayoutMenuItem(LayoutWrapper * layoutWrapper,const RefPtr<LayoutProperty> & props)604 void MenuItemLayoutAlgorithm::LayoutMenuItem(LayoutWrapper* layoutWrapper, const RefPtr<LayoutProperty>& props)
605 {
606 auto layoutDirection = props->GetNonAutoLayoutDirection();
607 auto leftRow = layoutWrapper->GetOrCreateChildByIndex(0);
608 auto leftRowSize = leftRow ? leftRow->GetGeometryNode()->GetFrameSize() : SizeT(0.0f, 0.0f);
609 auto rightRow = layoutWrapper->GetOrCreateChildByIndex(1);
610 auto rightRowSize = rightRow ? rightRow->GetGeometryNode()->GetFrameSize() : SizeT(0.0f, 0.0f);
611 float rowsHeight = 0.0f;
612 float itemHeight = idealHeight_ > 0.0f ? idealHeight_ :
613 std::max(leftRowSize.Height(), rightRowSize.Height()) + padding_.Height();
614
615 CHECK_NULL_VOID(leftRow);
616 float topSpace = CalcRowTopSpace(rowsHeight, itemHeight, layoutWrapper, leftRowSize.Height());
617 leftRow->GetLayoutProperty()->UpdatePropertyChangeFlag(PROPERTY_UPDATE_LAYOUT);
618 leftRow->GetGeometryNode()->SetMarginFrameOffset(OffsetF(padding_.left.value_or(horInterval_), topSpace));
619 if (layoutDirection == TextDirection::RTL) {
620 leftRow->GetGeometryNode()->SetMarginFrameOffset(
621 OffsetF(layoutWrapper->GetGeometryNode()->GetFrameSize().Width() - padding_.right.value_or(horInterval_) -
622 leftRow->GetGeometryNode()->GetFrameSize().Width(),
623 topSpace));
624 }
625 leftRow->Layout();
626
627 CHECK_NULL_VOID(rightRow);
628 topSpace = CalcRowTopSpace(rowsHeight, itemHeight, layoutWrapper, rightRowSize.Height());
629 rightRow->GetGeometryNode()->SetMarginFrameOffset(
630 OffsetF(layoutWrapper->GetGeometryNode()->GetFrameSize().Width() - padding_.right.value_or(horInterval_) -
631 rightRow->GetGeometryNode()->GetFrameSize().Width(), topSpace));
632 if (layoutDirection == TextDirection::RTL) {
633 rightRow->GetGeometryNode()->SetMarginFrameOffset(OffsetF(padding_.left.value_or(horInterval_), topSpace));
634 }
635 rightRow->Layout();
636
637 auto clickableArea = layoutWrapper->GetOrCreateChildByIndex(CLICKABLE_AREA_VIEW_INDEX);
638 CHECK_NULL_VOID(clickableArea);
639 clickableArea->Layout();
640
641 auto expandableArea = layoutWrapper->GetOrCreateChildByIndex(EXPANDABLE_AREA_VIEW_INDEX);
642 CHECK_NULL_VOID(expandableArea);
643 expandableArea->GetLayoutProperty()->UpdatePropertyChangeFlag(PROPERTY_UPDATE_LAYOUT);
644 expandableArea->GetGeometryNode()->SetMarginFrameOffset(
645 OffsetF(padding_.left.value_or(horInterval_),
646 itemHeight));
647 expandableArea->Layout();
648 }
649
LayoutOption(LayoutWrapper * layoutWrapper,const RefPtr<LayoutProperty> & props)650 void MenuItemLayoutAlgorithm::LayoutOption(LayoutWrapper* layoutWrapper, const RefPtr<LayoutProperty>& props)
651 {
652 auto optionNode = layoutWrapper->GetHostNode();
653 CHECK_NULL_VOID(optionNode);
654 auto optionPattern = optionNode->GetPattern<MenuItemPattern>();
655 CHECK_NULL_VOID(optionPattern);
656 auto optionSize = layoutWrapper->GetGeometryNode()->GetFrameSize();
657 auto optionHeight = optionSize.Height();
658 auto child = layoutWrapper->GetOrCreateChildByIndex(0);
659 child->GetLayoutProperty()->UpdatePropertyChangeFlag(PROPERTY_UPDATE_LAYOUT);
660 auto rowChild = child->GetOrCreateChildByIndex(0);
661 if (rowChild && (rowChild->GetHostTag() == V2::PASTE_BUTTON_ETS_TAG)) {
662 float horInterval = 0.0f;
663 if (props->GetNonAutoLayoutDirection() == TextDirection::RTL) {
664 SizeF childSize = child->GetGeometryNode()->GetMarginFrameSize();
665 horInterval = optionSize.Width() - childSize.Width();
666 }
667 child->GetGeometryNode()->SetMarginFrameOffset(
668 OffsetF(horInterval, (optionHeight - child->GetGeometryNode()->GetFrameSize().Height()) / 2.0f));
669 child->Layout();
670 return;
671 }
672 const auto& selectTheme = optionPattern->GetSelectTheme();
673 CHECK_NULL_VOID(selectTheme);
674
675 float horInterval = horInterval_;
676 auto textAlign = static_cast<TextAlign>(selectTheme->GetOptionContentNormalAlign());
677 if (textAlign != TextAlign::CENTER && props->GetNonAutoLayoutDirection() == TextDirection::RTL) {
678 SizeF childSize = child->GetGeometryNode()->GetMarginFrameSize();
679 horInterval = optionSize.Width() - childSize.Width() - horInterval_;
680 }
681 child->GetGeometryNode()->SetMarginFrameOffset(
682 OffsetF(horInterval, (optionHeight - child->GetGeometryNode()->GetFrameSize().Height()) / 2.0f));
683 child->Layout();
684 }
685
ExtendTextAndRowNode(const RefPtr<LayoutWrapper> & row,const SizeF & optSize,float interval,const LayoutConstraintF & constraint)686 void MenuItemLayoutAlgorithm::ExtendTextAndRowNode(const RefPtr<LayoutWrapper>& row,
687 const SizeF& optSize, float interval, const LayoutConstraintF& constraint)
688 {
689 CHECK_NULL_VOID(row);
690 auto children = row->GetAllChildrenWithBuild();
691 if (children.empty()) {
692 return;
693 }
694 SizeF frameSize;
695 float imageNodeWidth = 0.0f;
696 for (auto textChild : children) {
697 if (!textChild) {
698 continue;
699 }
700 auto geometryNode = textChild->GetGeometryNode();
701 if (!geometryNode) {
702 continue;
703 }
704 if (textChild->GetHostTag() != V2::TEXT_ETS_TAG) {
705 imageNodeWidth += geometryNode->GetMarginFrameSize().Width();
706 continue;
707 }
708 frameSize = geometryNode->GetMarginFrameSize();
709 auto width = optSize.Width() - 2.0f * interval - imageNodeWidth;
710 auto contentConstraint = constraint;
711 contentConstraint.minSize.SetWidth(width);
712 textChild->Measure(contentConstraint);
713 }
714 auto geometryNode = row->GetGeometryNode();
715 CHECK_NULL_VOID(geometryNode);
716 frameSize = geometryNode->GetMarginFrameSize();
717 geometryNode->SetFrameSize(SizeF(optSize.Width() - 2.0f * interval, frameSize.Height()));
718 }
719 } // namespace OHOS::Ace::NG
720