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/multi_menu_layout_algorithm.h"
17
18 #include "base/geometry/dimension.h"
19 #include "base/geometry/ng/offset_t.h"
20 #include "base/utils/utils.h"
21 #include "core/components/common/layout/grid_system_manager.h"
22 #include "core/components/select/select_theme.h"
23 #include "core/components_ng/layout/box_layout_algorithm.h"
24 #include "core/components_ng/pattern/menu/menu_theme.h"
25 #include "core/components_ng/property/measure_utils.h"
26 #include "core/components_ng/pattern/menu/menu_pattern.h"
27
28 namespace OHOS::Ace::NG {
Measure(LayoutWrapper * layoutWrapper)29 void MultiMenuLayoutAlgorithm::Measure(LayoutWrapper* layoutWrapper)
30 {
31 CHECK_NULL_VOID(layoutWrapper);
32 auto layoutProperty = layoutWrapper->GetLayoutProperty();
33 CHECK_NULL_VOID(layoutProperty);
34 auto layoutConstraint = layoutProperty->GetLayoutConstraint();
35 CHECK_NULL_VOID(layoutConstraint);
36 auto childConstraint = layoutProperty->CreateChildConstraint();
37 childConstraint.maxSize.SetWidth(layoutConstraint->maxSize.Width());
38 // constraint max size minus padding
39 const auto& padding = layoutProperty->CreatePaddingAndBorder();
40 auto node = layoutWrapper->GetHostNode();
41 CHECK_NULL_VOID(node);
42 auto pattern = node->GetPattern<MenuPattern>();
43 CHECK_NULL_VOID(pattern);
44 if (!pattern->IsEmbedded()) {
45 MinusPaddingToSize(padding, childConstraint.maxSize);
46 }
47 if (layoutConstraint->selfIdealSize.Width().has_value()) {
48 // when Menu is set self ideal width, make children node adaptively fill up.
49 if (Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_ELEVEN)) {
50 if (LessNotEqual(layoutConstraint->selfIdealSize.Width().value(), MIN_MENU_WIDTH.ConvertToPx())) {
51 RefPtr<GridColumnInfo> columnInfo;
52 columnInfo = GridSystemManager::GetInstance().GetInfoByType(GridColumnType::MENU);
53 columnInfo->GetParent()->BuildColumnWidth();
54 auto minWidth = static_cast<float>(columnInfo->GetWidth(MENU_MIN_GRID_COUNTS));
55 layoutConstraint->selfIdealSize.SetWidth(minWidth);
56
57 UpdateMenuDefaultConstraintByDevice(pattern, childConstraint, padding.Width(), layoutConstraint, true);
58 }
59 }
60 auto idealWidth =
61 std::max(layoutConstraint->minSize.Width(),
62 std::min(layoutConstraint->maxSize.Width(), layoutConstraint->selfIdealSize.Width().value())) -
63 padding.Width();
64 childConstraint.selfIdealSize.SetWidth(idealWidth);
65 } else {
66 // constraint min width base on grid column
67 auto columnInfo = GridSystemManager::GetInstance().GetInfoByType(GridColumnType::MENU);
68 CHECK_NULL_VOID(columnInfo);
69 CHECK_NULL_VOID(columnInfo->GetParent());
70 columnInfo->GetParent()->BuildColumnWidth();
71 auto minWidth = static_cast<float>(columnInfo->GetWidth()) - padding.Width();
72 childConstraint.minSize.SetWidth(minWidth);
73
74 UpdateMenuDefaultConstraintByDevice(pattern, childConstraint, padding.Width(), layoutConstraint, false);
75 }
76 // Calculate max width of menu items
77 UpdateConstraintBaseOnMenuItems(layoutWrapper, childConstraint);
78 UpdateSelfSize(layoutWrapper, childConstraint, layoutConstraint);
79 }
80
UpdateMenuDefaultConstraintByDevice(const RefPtr<MenuPattern> & pattern,LayoutConstraintF & childConstraint,float paddingWidth,std::optional<LayoutConstraintF> & layoutConstraint,bool idealSizeHasVal)81 void MultiMenuLayoutAlgorithm::UpdateMenuDefaultConstraintByDevice(const RefPtr<MenuPattern>& pattern,
82 LayoutConstraintF& childConstraint, float paddingWidth, std::optional<LayoutConstraintF>& layoutConstraint,
83 bool idealSizeHasVal)
84 {
85 CHECK_NULL_VOID(pattern);
86
87 // only 2in1 device has restrictions on the menu width in API13
88 CHECK_NULL_VOID(Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_THIRTEEN));
89 auto pipeline = PipelineBase::GetCurrentContext();
90 CHECK_NULL_VOID(pipeline);
91 auto theme = pipeline->GetTheme<SelectTheme>();
92 CHECK_NULL_VOID(theme);
93 auto expandDisplay = theme->GetExpandDisplay();
94 CHECK_NULL_VOID(expandDisplay);
95
96 auto mainMenuPattern = pattern->GetMainMenuPattern();
97 CHECK_NULL_VOID(mainMenuPattern);
98 if (!mainMenuPattern->IsContextMenu() && !mainMenuPattern->IsMenu()) {
99 return;
100 }
101
102 if (idealSizeHasVal) {
103 layoutConstraint->selfIdealSize.SetWidth(theme->GetMenuDefaultWidth().ConvertToPx());
104 } else {
105 childConstraint.minSize.SetWidth(theme->GetMenuDefaultWidth().ConvertToPx() - paddingWidth);
106 }
107 }
108
Layout(LayoutWrapper * layoutWrapper)109 void MultiMenuLayoutAlgorithm::Layout(LayoutWrapper* layoutWrapper)
110 {
111 CHECK_NULL_VOID(layoutWrapper);
112 BoxLayoutAlgorithm::PerformLayout(layoutWrapper);
113
114 auto pipeline = PipelineBase::GetCurrentContext();
115 CHECK_NULL_VOID(pipeline);
116 auto theme = pipeline->GetTheme<SelectTheme>();
117 CHECK_NULL_VOID(theme);
118 auto layoutProperty = layoutWrapper->GetLayoutProperty();
119 CHECK_NULL_VOID(layoutProperty);
120 auto node = layoutWrapper->GetHostNode();
121 CHECK_NULL_VOID(node);
122 auto pattern = node->GetPattern<MenuPattern>();
123 CHECK_NULL_VOID(pattern);
124 OffsetF translate(0.0f, 0.0f);
125 const auto& padding = layoutProperty->CreatePaddingAndBorder();
126 auto outPadding = static_cast<float>(theme->GetOutPadding().ConvertToPx());
127 if (!pattern->IsEmbedded()) {
128 translate.AddX(padding.left.value_or(outPadding));
129 translate.AddY(padding.top.value_or(outPadding));
130 }
131 // translate each option by the height of previous options
132 for (const auto& child : layoutWrapper->GetAllChildrenWithBuild()) {
133 child->GetGeometryNode()->SetMarginFrameOffset(translate);
134 child->Layout();
135 translate.AddY(child->GetGeometryNode()->GetMarginFrameSize().Height());
136 }
137 }
138
UpdateSelfSize(LayoutWrapper * layoutWrapper,LayoutConstraintF & childConstraint,std::optional<LayoutConstraintF> & layoutConstraint)139 void MultiMenuLayoutAlgorithm::UpdateSelfSize(LayoutWrapper* layoutWrapper,
140 LayoutConstraintF& childConstraint, std::optional<LayoutConstraintF>& layoutConstraint)
141 {
142 float contentHeight = 0.0f;
143 float contentWidth = childConstraint.selfIdealSize.Width().value();
144 for (const auto& child : layoutWrapper->GetAllChildrenWithBuild()) {
145 child->Measure(ResetLayoutConstraintMinWidth(child, childConstraint));
146 auto childSize = child->GetGeometryNode()->GetMarginFrameSize();
147 contentHeight += childSize.Height();
148 }
149 layoutWrapper->GetGeometryNode()->SetContentSize(SizeF(contentWidth, contentHeight));
150 BoxLayoutAlgorithm::PerformMeasureSelf(layoutWrapper);
151
152 auto node = layoutWrapper->GetHostNode();
153 CHECK_NULL_VOID(node);
154 auto pattern = node->GetPattern<MenuPattern>();
155 CHECK_NULL_VOID(pattern);
156 // Stack or Embedded submenu must follow parent width
157 if (pattern->IsStackSubmenu() || pattern->IsEmbedded()) {
158 auto idealSize = layoutWrapper->GetGeometryNode()->GetFrameSize();
159 auto width = layoutConstraint->maxSize.Width();
160 idealSize.SetWidth(width);
161 layoutWrapper->GetGeometryNode()->SetFrameSize(idealSize);
162 } else if (layoutConstraint->selfIdealSize.Width().has_value()) {
163 auto idealWidth = std::max(layoutConstraint->minSize.Width(),
164 std::min(layoutConstraint->maxSize.Width(), layoutConstraint->selfIdealSize.Width().value()));
165 auto idealSize = layoutWrapper->GetGeometryNode()->GetFrameSize();
166 idealSize.SetWidth(idealWidth);
167 layoutWrapper->GetGeometryNode()->SetFrameSize(idealSize);
168 }
169 }
170
UpdateConstraintBaseOnMenuItems(LayoutWrapper * layoutWrapper,LayoutConstraintF & constraint)171 void MultiMenuLayoutAlgorithm::UpdateConstraintBaseOnMenuItems(
172 LayoutWrapper* layoutWrapper, LayoutConstraintF& constraint)
173 {
174 // multiMenu children are menuItem or menuItemGroup, constrain width is same as the menu
175 auto maxChildrenWidth = GetChildrenMaxWidth(layoutWrapper, constraint);
176 constraint.selfIdealSize.SetWidth(maxChildrenWidth);
177 }
178
GetChildrenMaxWidth(LayoutWrapper * layoutWrapper,const LayoutConstraintF & layoutConstraint)179 float MultiMenuLayoutAlgorithm::GetChildrenMaxWidth(
180 LayoutWrapper* layoutWrapper, const LayoutConstraintF& layoutConstraint)
181 {
182 float maxWidth = 0.0f;
183 for (const auto& child : layoutWrapper->GetAllChildrenWithBuild()) {
184 auto childConstraint = ResetLayoutConstraintMinWidth(child, layoutConstraint);
185 child->Measure(childConstraint);
186 auto childSize = child->GetGeometryNode()->GetMarginFrameSize();
187 maxWidth = std::max(maxWidth, childSize.Width());
188 }
189 return maxWidth;
190 }
191
ResetLayoutConstraintMinWidth(const RefPtr<LayoutWrapper> & child,const LayoutConstraintF & layoutConstraint)192 LayoutConstraintF MultiMenuLayoutAlgorithm::ResetLayoutConstraintMinWidth(
193 const RefPtr<LayoutWrapper>& child, const LayoutConstraintF& layoutConstraint)
194 {
195 auto childLayoutProps = child->GetLayoutProperty();
196 CHECK_NULL_RETURN(childLayoutProps, layoutConstraint);
197 auto childConstraint = layoutConstraint;
198 const auto& calcConstraint = childLayoutProps->GetCalcLayoutConstraint();
199 if (calcConstraint && calcConstraint->selfIdealSize.has_value() &&
200 calcConstraint->selfIdealSize.value().Width().has_value()) {
201 childConstraint.minSize.Reset();
202 }
203 return childConstraint;
204 }
205 } // namespace OHOS::Ace::NG
206