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 #include "core/components_ng/pattern/menu/menu_item/menu_item_layout_property.h"
28 #include "core/components_ng/pattern/select_overlay/select_overlay_node.h"
29
30 namespace OHOS::Ace::NG {
31 namespace {
32 struct SelectOverlayRightClickMenuLayoutHelper {
AdjustLayoutConstraintsOHOS::Ace::NG::__anon890cfb1e0111::SelectOverlayRightClickMenuLayoutHelper33 static void AdjustLayoutConstraints(
34 LayoutConstraintF& constrainMinWidth, const RefPtr<LayoutWrapper>& child, LayoutWrapper* layoutWrapper)
35 {
36 auto host = layoutWrapper->GetHostNode();
37 CHECK_NULL_VOID(host);
38 auto scrollNode = host->GetParentFrameNode();
39 CHECK_NULL_VOID(scrollNode);
40 auto outterMenuNode = scrollNode->GetParentFrameNode();
41 CHECK_NULL_VOID(outterMenuNode);
42 auto outterMenuPattern = outterMenuNode->GetPattern<MenuPattern>();
43 CHECK_NULL_VOID(outterMenuPattern);
44 auto menuWrapperNode = outterMenuNode->GetParentFrameNode();
45 CHECK_NULL_VOID(menuWrapperNode);
46 auto childLayoutProperty = child->GetLayoutProperty();
47 CHECK_NULL_VOID(childLayoutProperty);
48 if (menuWrapperNode->GetInspectorIdValue("") != SelectOverlayRrightClickMenuWrapper ||
49 !outterMenuPattern->IsSelectOverlayRightClickMenu() ||
50 child->GetHostTag() != V2::RELATIVE_CONTAINER_ETS_TAG) {
51 return;
52 }
53 childLayoutProperty->UpdateUserDefinedIdealSize(
54 { CalcLength(1.0, DimensionUnit::PERCENT), CalcLength(0.0, DimensionUnit::AUTO) });
55 constrainMinWidth.percentReference.SetWidth(constrainMinWidth.selfIdealSize.Width().value());
56 auto row = child->GetChildByIndex(0);
57 CHECK_NULL_VOID(row);
58 auto layoutProperty = row->GetLayoutProperty();
59 CHECK_NULL_VOID(layoutProperty);
60 layoutProperty->UpdateUserDefinedIdealSize(
61 { CalcLength(1.0, DimensionUnit::PERCENT), CalcLength(1.0, DimensionUnit::AUTO) });
62 auto pasteButtonRow = child->GetChildByIndex(1);
63 CHECK_NULL_VOID(pasteButtonRow);
64 auto pasteButton = pasteButtonRow->GetChildByIndex(0);
65 CHECK_NULL_VOID(pasteButton);
66 auto pasteButtonLayoutProperty = pasteButton->GetLayoutProperty();
67 CHECK_NULL_VOID(pasteButtonLayoutProperty);
68 pasteButtonLayoutProperty->UpdateUserDefinedIdealSize(
69 { CalcLength(1.0, DimensionUnit::PERCENT), CalcLength(1.0, DimensionUnit::PERCENT) });
70 }
71
AdjustLayoutConstraintsAutoWidthOHOS::Ace::NG::__anon890cfb1e0111::SelectOverlayRightClickMenuLayoutHelper72 static void AdjustLayoutConstraintsAutoWidth(const RefPtr<LayoutWrapper>& child, LayoutWrapper* layoutWrapper)
73 {
74 auto host = layoutWrapper->GetHostNode();
75 CHECK_NULL_VOID(host);
76 auto scrollNode = host->GetParentFrameNode();
77 CHECK_NULL_VOID(scrollNode);
78 auto outterMenuNode = scrollNode->GetParentFrameNode();
79 CHECK_NULL_VOID(outterMenuNode);
80 auto outterMenuPattern = outterMenuNode->GetPattern<MenuPattern>();
81 CHECK_NULL_VOID(outterMenuPattern);
82 auto menuWrapperNode = outterMenuNode->GetParentFrameNode();
83 CHECK_NULL_VOID(menuWrapperNode);
84 auto childLayoutProperty = child->GetLayoutProperty();
85 CHECK_NULL_VOID(childLayoutProperty);
86 if (menuWrapperNode->GetInspectorIdValue("") != SelectOverlayRrightClickMenuWrapper ||
87 !outterMenuPattern->IsSelectOverlayRightClickMenu() ||
88 child->GetHostTag() != V2::RELATIVE_CONTAINER_ETS_TAG) {
89 return;
90 }
91 childLayoutProperty->UpdateUserDefinedIdealSize(
92 { CalcLength(0.0, DimensionUnit::AUTO), CalcLength(0.0, DimensionUnit::AUTO) });
93 auto row = child->GetChildByIndex(0);
94 CHECK_NULL_VOID(row);
95 auto layoutProperty = row->GetLayoutProperty();
96 CHECK_NULL_VOID(layoutProperty);
97 layoutProperty->UpdateUserDefinedIdealSize(
98 { CalcLength(0.0, DimensionUnit::AUTO), CalcLength(0.0, DimensionUnit::AUTO) });
99 auto pasteButtonRow = child->GetChildByIndex(1);
100 CHECK_NULL_VOID(pasteButtonRow);
101 auto pasteButton = pasteButtonRow->GetChildByIndex(0);
102 CHECK_NULL_VOID(pasteButton);
103 auto pasteButtonLayoutProperty = pasteButton->GetLayoutProperty();
104 CHECK_NULL_VOID(pasteButtonLayoutProperty);
105 pasteButtonLayoutProperty->UpdateUserDefinedIdealSize(
106 { CalcLength(0.0, DimensionUnit::AUTO), CalcLength(0.0, DimensionUnit::AUTO) });
107 }
108 };
109 } // namespace
110
Measure(LayoutWrapper * layoutWrapper)111 void MultiMenuLayoutAlgorithm::Measure(LayoutWrapper* layoutWrapper)
112 {
113 CHECK_NULL_VOID(layoutWrapper);
114 auto layoutProperty = layoutWrapper->GetLayoutProperty();
115 CHECK_NULL_VOID(layoutProperty);
116 auto layoutConstraint = layoutProperty->GetLayoutConstraint();
117 CHECK_NULL_VOID(layoutConstraint);
118 auto childConstraint = layoutProperty->CreateChildConstraint();
119 childConstraint.maxSize.SetWidth(layoutConstraint->maxSize.Width());
120 // constraint max size minus padding
121 const auto& padding = layoutProperty->CreatePaddingAndBorder();
122 auto node = layoutWrapper->GetHostNode();
123 CHECK_NULL_VOID(node);
124 auto pattern = node->GetPattern<MenuPattern>();
125 CHECK_NULL_VOID(pattern);
126 if (!pattern->IsEmbedded()) {
127 MinusPaddingToSize(padding, childConstraint.maxSize);
128 }
129 if (layoutConstraint->selfIdealSize.Width().has_value()) {
130 // when Menu is set self ideal width, make children node adaptively fill up.
131 if (Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_ELEVEN)) {
132 if (LessNotEqual(layoutConstraint->selfIdealSize.Width().value(), MIN_MENU_WIDTH.ConvertToPx())) {
133 RefPtr<GridColumnInfo> columnInfo;
134 columnInfo = GridSystemManager::GetInstance().GetInfoByType(GridColumnType::MENU);
135 columnInfo->GetParent()->BuildColumnWidth();
136 auto minWidth = static_cast<float>(columnInfo->GetWidth(MENU_MIN_GRID_COUNTS));
137 layoutConstraint->selfIdealSize.SetWidth(minWidth);
138
139 UpdateMenuDefaultConstraintByDevice(pattern, childConstraint, padding.Width(), layoutConstraint, true);
140 }
141 }
142 auto idealWidth =
143 std::max(layoutConstraint->minSize.Width(),
144 std::min(layoutConstraint->maxSize.Width(), layoutConstraint->selfIdealSize.Width().value())) -
145 padding.Width();
146 childConstraint.selfIdealSize.SetWidth(idealWidth);
147 } else {
148 // constraint min width base on grid column
149 auto columnInfo = GridSystemManager::GetInstance().GetInfoByType(GridColumnType::MENU);
150 CHECK_NULL_VOID(columnInfo);
151 CHECK_NULL_VOID(columnInfo->GetParent());
152 columnInfo->GetParent()->BuildColumnWidth();
153 auto minWidth = static_cast<float>(columnInfo->GetWidth()) - padding.Width();
154 childConstraint.minSize.SetWidth(minWidth);
155
156 UpdateMenuDefaultConstraintByDevice(pattern, childConstraint, padding.Width(), layoutConstraint, false);
157 }
158 // Calculate max width of menu items
159 UpdateConstraintBaseOnMenuItems(layoutWrapper, childConstraint);
160 UpdateSelfSize(layoutWrapper, childConstraint, layoutConstraint);
161 }
162
UpdateMenuDefaultConstraintByDevice(const RefPtr<MenuPattern> & pattern,LayoutConstraintF & childConstraint,float paddingWidth,std::optional<LayoutConstraintF> & layoutConstraint,bool idealSizeHasVal)163 void MultiMenuLayoutAlgorithm::UpdateMenuDefaultConstraintByDevice(const RefPtr<MenuPattern>& pattern,
164 LayoutConstraintF& childConstraint, float paddingWidth, std::optional<LayoutConstraintF>& layoutConstraint,
165 bool idealSizeHasVal)
166 {
167 CHECK_NULL_VOID(pattern);
168
169 // only 2in1 device has restrictions on the menu width in API13
170 CHECK_NULL_VOID(Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_THIRTEEN));
171 auto pipeline = PipelineBase::GetCurrentContext();
172 CHECK_NULL_VOID(pipeline);
173 auto theme = pipeline->GetTheme<SelectTheme>();
174 CHECK_NULL_VOID(theme);
175 auto expandDisplay = theme->GetExpandDisplay();
176 CHECK_NULL_VOID(expandDisplay);
177
178 auto mainMenuPattern = pattern->GetMainMenuPattern();
179 CHECK_NULL_VOID(mainMenuPattern);
180 if (!mainMenuPattern->IsContextMenu() && !mainMenuPattern->IsMenu()) {
181 return;
182 }
183
184 if (idealSizeHasVal) {
185 layoutConstraint->selfIdealSize.SetWidth(theme->GetMenuDefaultWidth().ConvertToPx());
186 } else {
187 childConstraint.minSize.SetWidth(theme->GetMenuDefaultWidth().ConvertToPx() - paddingWidth);
188 }
189 }
190
Layout(LayoutWrapper * layoutWrapper)191 void MultiMenuLayoutAlgorithm::Layout(LayoutWrapper* layoutWrapper)
192 {
193 CHECK_NULL_VOID(layoutWrapper);
194 BoxLayoutAlgorithm::PerformLayout(layoutWrapper);
195
196 auto pipeline = PipelineBase::GetCurrentContext();
197 CHECK_NULL_VOID(pipeline);
198 auto theme = pipeline->GetTheme<SelectTheme>();
199 CHECK_NULL_VOID(theme);
200 auto layoutProperty = layoutWrapper->GetLayoutProperty();
201 CHECK_NULL_VOID(layoutProperty);
202 auto node = layoutWrapper->GetHostNode();
203 CHECK_NULL_VOID(node);
204 auto pattern = node->GetPattern<MenuPattern>();
205 CHECK_NULL_VOID(pattern);
206 OffsetF translate(0.0f, 0.0f);
207 const auto& padding = layoutProperty->CreatePaddingAndBorder();
208 auto outPadding = static_cast<float>(theme->GetOutPadding().ConvertToPx());
209 if (!pattern->IsEmbedded()) {
210 translate.AddX(padding.left.value_or(outPadding));
211 translate.AddY(padding.top.value_or(outPadding));
212 }
213 // translate each option by the height of previous options
214 for (const auto& child : layoutWrapper->GetAllChildrenWithBuild()) {
215 child->GetGeometryNode()->SetMarginFrameOffset(translate);
216 child->Layout();
217 translate.AddY(child->GetGeometryNode()->GetMarginFrameSize().Height());
218 }
219 }
220
UpdateSelfSize(LayoutWrapper * layoutWrapper,LayoutConstraintF & childConstraint,std::optional<LayoutConstraintF> & layoutConstraint)221 void MultiMenuLayoutAlgorithm::UpdateSelfSize(LayoutWrapper* layoutWrapper,
222 LayoutConstraintF& childConstraint, std::optional<LayoutConstraintF>& layoutConstraint)
223 {
224 float contentHeight = 0.0f;
225 float contentWidth = childConstraint.selfIdealSize.Width().value();
226 for (const auto& child : layoutWrapper->GetAllChildrenWithBuild()) {
227 auto constrainMinWidth = ResetLayoutConstraintMinWidth(child, childConstraint);
228 SelectOverlayRightClickMenuLayoutHelper::AdjustLayoutConstraints(
229 constrainMinWidth, child, layoutWrapper);
230 child->Measure(constrainMinWidth);
231 auto childSize = child->GetGeometryNode()->GetMarginFrameSize();
232 contentHeight += childSize.Height();
233 }
234 layoutWrapper->GetGeometryNode()->SetContentSize(SizeF(contentWidth, contentHeight));
235 BoxLayoutAlgorithm::PerformMeasureSelf(layoutWrapper);
236
237 auto node = layoutWrapper->GetHostNode();
238 CHECK_NULL_VOID(node);
239 auto pattern = node->GetPattern<MenuPattern>();
240 CHECK_NULL_VOID(pattern);
241 // Stack or Embedded submenu must follow parent width
242 if (pattern->IsStackSubmenu() || pattern->IsEmbedded()) {
243 auto idealSize = layoutWrapper->GetGeometryNode()->GetFrameSize();
244 auto width = layoutConstraint->maxSize.Width();
245 idealSize.SetWidth(width);
246 layoutWrapper->GetGeometryNode()->SetFrameSize(idealSize);
247 } else if (layoutConstraint->selfIdealSize.Width().has_value()) {
248 auto idealWidth = std::max(layoutConstraint->minSize.Width(),
249 std::min(layoutConstraint->maxSize.Width(), layoutConstraint->selfIdealSize.Width().value()));
250 auto idealSize = layoutWrapper->GetGeometryNode()->GetFrameSize();
251 idealSize.SetWidth(idealWidth);
252 layoutWrapper->GetGeometryNode()->SetFrameSize(idealSize);
253 }
254 }
255
UpdateConstraintBaseOnMenuItems(LayoutWrapper * layoutWrapper,LayoutConstraintF & constraint)256 void MultiMenuLayoutAlgorithm::UpdateConstraintBaseOnMenuItems(
257 LayoutWrapper* layoutWrapper, LayoutConstraintF& constraint)
258 {
259 // multiMenu children are menuItem or menuItemGroup, constrain width is same as the menu
260 auto maxChildrenWidth = GetChildrenMaxWidth(layoutWrapper, constraint);
261 constraint.selfIdealSize.SetWidth(maxChildrenWidth);
262 }
263
GetChildrenMaxWidth(LayoutWrapper * layoutWrapper,const LayoutConstraintF & layoutConstraint)264 float MultiMenuLayoutAlgorithm::GetChildrenMaxWidth(
265 LayoutWrapper* layoutWrapper, const LayoutConstraintF& layoutConstraint)
266 {
267 float maxWidth = 0.0f;
268 for (const auto& child : layoutWrapper->GetAllChildrenWithBuild()) {
269 auto childConstraint = ResetLayoutConstraintMinWidth(child, layoutConstraint);
270 SelectOverlayRightClickMenuLayoutHelper::AdjustLayoutConstraintsAutoWidth(child, layoutWrapper);
271 child->Measure(childConstraint);
272 auto childSize = child->GetGeometryNode()->GetMarginFrameSize();
273 maxWidth = std::max(maxWidth, childSize.Width());
274 }
275 return maxWidth;
276 }
277
ResetLayoutConstraintMinWidth(const RefPtr<LayoutWrapper> & child,const LayoutConstraintF & layoutConstraint)278 LayoutConstraintF MultiMenuLayoutAlgorithm::ResetLayoutConstraintMinWidth(
279 const RefPtr<LayoutWrapper>& child, const LayoutConstraintF& layoutConstraint)
280 {
281 auto childLayoutProps = child->GetLayoutProperty();
282 CHECK_NULL_RETURN(childLayoutProps, layoutConstraint);
283 auto childConstraint = layoutConstraint;
284 const auto& calcConstraint = childLayoutProps->GetCalcLayoutConstraint();
285 if (calcConstraint && calcConstraint->selfIdealSize.has_value() &&
286 calcConstraint->selfIdealSize.value().Width().has_value()) {
287 childConstraint.minSize.Reset();
288 }
289 return childConstraint;
290 }
291 } // namespace OHOS::Ace::NG
292