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/menu/menu_layout_algorithm.h"
17
18 #include <optional>
19 #include <vector>
20
21 #include "base/memory/referenced.h"
22 #include "base/subwindow/subwindow_manager.h"
23 #include "base/utils/utils.h"
24 #include "core/components/common/layout/grid_system_manager.h"
25 #include "core/components_ng/pattern/menu/menu_item/menu_item_pattern.h"
26 #include "core/components_ng/pattern/menu/menu_layout_property.h"
27 #include "core/components_ng/pattern/menu/menu_pattern.h"
28 #include "core/components_ng/pattern/menu/menu_theme.h"
29 #include "core/components_ng/property/layout_constraint.h"
30 #include "core/components_ng/property/measure_property.h"
31 #include "core/components_v2/inspector/inspector_constants.h"
32 #include "core/pipeline/pipeline_base.h"
33 #include "core/pipeline_ng/pipeline_context.h"
34
35 namespace OHOS::Ace::NG {
36
37 namespace {
38 constexpr uint32_t MIN_GRID_COUNTS = 2;
39 constexpr uint32_t GRID_COUNTS_4 = 4;
40 constexpr uint32_t GRID_COUNTS_6 = 6;
41 constexpr uint32_t GRID_COUNTS_8 = 8;
42 constexpr uint32_t GRID_COUNTS_12 = 12;
43
GetMaxGridCounts(const RefPtr<GridColumnInfo> & columnInfo)44 uint32_t GetMaxGridCounts(const RefPtr<GridColumnInfo>& columnInfo)
45 {
46 CHECK_NULL_RETURN(columnInfo, GRID_COUNTS_8);
47 auto currentColumns = columnInfo->GetParent()->GetColumns();
48 auto maxGridCounts = GRID_COUNTS_8;
49 switch (currentColumns) {
50 case GRID_COUNTS_4:
51 maxGridCounts = GRID_COUNTS_4;
52 break;
53 case GRID_COUNTS_8:
54 maxGridCounts = GRID_COUNTS_6;
55 break;
56 case GRID_COUNTS_12:
57 maxGridCounts = GRID_COUNTS_8;
58 break;
59 default:
60 break;
61 }
62 return maxGridCounts;
63 }
64 } // namespace
65
Initialize(LayoutWrapper * layoutWrapper)66 void MenuLayoutAlgorithm::Initialize(LayoutWrapper* layoutWrapper)
67 {
68 CHECK_NULL_VOID(layoutWrapper);
69 // currently using click point as menu position
70 auto props = AceType::DynamicCast<MenuLayoutProperty>(layoutWrapper->GetLayoutProperty());
71 CHECK_NULL_VOID(props);
72 auto menuPattern = layoutWrapper->GetHostNode()->GetPattern<MenuPattern>();
73 auto targetSize = props->GetTargetSizeValue(SizeF());
74 position_ = props->GetMenuOffset().value_or(OffsetF());
75 LOGD("menu position_ = %{public}s, targetSize = %{public}s", position_.ToString().c_str(),
76 targetSize.ToString().c_str());
77
78 auto pipeline = PipelineBase::GetCurrentContext();
79 CHECK_NULL_VOID(pipeline);
80 auto theme = pipeline->GetTheme<SelectTheme>();
81 CHECK_NULL_VOID(theme);
82 margin_ = static_cast<float>(theme->GetOutPadding().ConvertToPx());
83 optionPadding_ = margin_;
84
85 auto constraint = props->GetLayoutConstraint();
86 auto wrapperIdealSize =
87 CreateIdealSize(constraint.value(), Axis::FREE, props->GetMeasureType(MeasureType::MATCH_PARENT), true);
88 wrapperSize_ = wrapperIdealSize;
89 topSpace_ = position_.GetY() - targetSize.Height() - margin_ * 2.0f;
90 bottomSpace_ = wrapperSize_.Height() - position_.GetY() - margin_ * 2.0f;
91 leftSpace_ = position_.GetX();
92 rightSpace_ = wrapperSize_.Width() - leftSpace_;
93
94 auto context = PipelineContext::GetCurrentContext();
95 CHECK_NULL_VOID(context);
96 auto stageManager = context->GetStageManager();
97 CHECK_NULL_VOID(stageManager);
98 auto page = stageManager->GetLastPage();
99 CHECK_NULL_VOID(page);
100 pageOffset_ = page->GetOffsetRelativeToWindow();
101 topSpace_ -= pageOffset_.GetY();
102 leftSpace_ -= pageOffset_.GetX();
103 }
104
105 // Called to perform layout render node and child.
Measure(LayoutWrapper * layoutWrapper)106 void MenuLayoutAlgorithm::Measure(LayoutWrapper* layoutWrapper)
107 {
108 // initialize screen size and menu position
109 CHECK_NULL_VOID(layoutWrapper);
110 Initialize(layoutWrapper);
111
112 auto menuLayoutProperty = AceType::DynamicCast<MenuLayoutProperty>(layoutWrapper->GetLayoutProperty());
113 CHECK_NULL_VOID(menuLayoutProperty);
114 const auto& constraint = menuLayoutProperty->GetLayoutConstraint();
115 if (!constraint) {
116 LOGE("fail to measure menu due to layoutConstraint is nullptr");
117 return;
118 }
119 auto idealSize = CreateIdealSize(
120 constraint.value(), Axis::VERTICAL, menuLayoutProperty->GetMeasureType(MeasureType::MATCH_CONTENT), true);
121 const auto& padding = menuLayoutProperty->CreatePaddingAndBorder();
122 MinusPaddingToSize(padding, idealSize);
123
124 // calculate menu main size
125 auto childConstraint = CreateChildConstraint(layoutWrapper);
126 float idealHeight = 0.0f;
127 float idealWidth = 0.0f;
128 for (const auto& child : layoutWrapper->GetAllChildrenWithBuild()) {
129 child->Measure(childConstraint);
130 auto childSize = child->GetGeometryNode()->GetMarginFrameSize();
131 LOGD("child finish measure, child %{public}s size = %{public}s", child->GetHostTag().c_str(),
132 child->GetGeometryNode()->GetMarginFrameSize().ToString().c_str());
133 idealHeight += childSize.Height();
134 idealWidth = std::max(idealWidth, childSize.Width());
135 }
136 idealSize.SetHeight(idealHeight);
137 idealSize.SetWidth(idealWidth);
138 AddPaddingToSize(padding, idealSize);
139
140 auto geometryNode = layoutWrapper->GetGeometryNode();
141 CHECK_NULL_VOID(geometryNode);
142 LOGD("finish measure, menu size = %{public}f x %{public}f", idealSize.Width(), idealSize.Height());
143 geometryNode->SetFrameSize(idealSize);
144 }
145
Layout(LayoutWrapper * layoutWrapper)146 void MenuLayoutAlgorithm::Layout(LayoutWrapper* layoutWrapper)
147 {
148 CHECK_NULL_VOID(layoutWrapper);
149
150 auto menuNode = layoutWrapper->GetHostNode();
151 CHECK_NULL_VOID(menuNode);
152 auto menuPattern = menuNode->GetPattern<MenuPattern>();
153 CHECK_NULL_VOID(menuPattern);
154 if (menuPattern->IsSubMenu()) {
155 LayoutSubMenu(layoutWrapper);
156 return;
157 }
158
159 auto size = layoutWrapper->GetGeometryNode()->GetMarginFrameSize();
160 auto props = AceType::DynamicCast<MenuLayoutProperty>(layoutWrapper->GetLayoutProperty());
161 LOGD("MenuLayout: clickPosition = %{public}f, %{public}f", position_.GetX(), position_.GetY());
162 CHECK_NULL_VOID(props);
163
164 float x = HorizontalLayout(size, position_.GetX(), menuPattern->IsSelectMenu());
165 float y = VerticalLayout(size, position_.GetY());
166 if (!menuPattern->IsContextMenu()) {
167 x -= pageOffset_.GetX();
168 y -= pageOffset_.GetY();
169 }
170
171 MarginPropertyF margin;
172 if (!menuPattern->IsMultiMenu()) {
173 margin.left = margin.top = margin.right = margin.bottom = margin_;
174 }
175 auto geometryNode = layoutWrapper->GetGeometryNode();
176 CHECK_NULL_VOID(geometryNode);
177 geometryNode->SetFrameOffset(NG::OffsetF(x, y));
178
179 // translate each option by the height of previous options
180 OffsetF translate;
181 for (const auto& child : layoutWrapper->GetAllChildrenWithBuild()) {
182 LOGD("layout child at offset: %{public}s, tag %{public}s", translate.ToString().c_str(),
183 child->GetHostTag().c_str());
184 child->GetGeometryNode()->SetMarginFrameOffset(translate);
185 child->Layout();
186 translate += OffsetF(0, child->GetGeometryNode()->GetFrameSize().Height());
187 }
188
189 if (menuPattern->IsContextMenu()) {
190 std::vector<Rect> rects;
191 auto frameRect = layoutWrapper->GetGeometryNode()->GetFrameRect();
192 auto rect = Rect(frameRect.GetX(), frameRect.GetY(), frameRect.Width(), frameRect.Height());
193 rects.emplace_back(rect);
194 SubwindowManager::GetInstance()->SetHotAreas(rects);
195 }
196 }
197
UpdateConstraintWidth(LayoutWrapper * layoutWrapper,LayoutConstraintF & constraint)198 void MenuLayoutAlgorithm::UpdateConstraintWidth(LayoutWrapper* layoutWrapper, LayoutConstraintF& constraint)
199 {
200 // set min width
201 RefPtr<GridColumnInfo> columnInfo;
202 columnInfo = GridSystemManager::GetInstance().GetInfoByType(GridColumnType::MENU);
203 columnInfo->GetParent()->BuildColumnWidth();
204 float minWidth = static_cast<float>(columnInfo->GetWidth(MIN_GRID_COUNTS));
205 auto menuPattern = layoutWrapper->GetHostNode()->GetPattern<MenuPattern>();
206 constraint.minSize.SetWidth(minWidth);
207
208 // set max width
209 auto menuLayoutProperty = AceType::DynamicCast<MenuLayoutProperty>(layoutWrapper->GetLayoutProperty());
210 CHECK_NULL_VOID(menuLayoutProperty);
211 const auto& padding = menuLayoutProperty->CreatePaddingAndBorder();
212 auto maxHorizontalSpace = std::max(leftSpace_, rightSpace_) - 2.0f * padding.Width();
213 auto maxGridWidth = static_cast<float>(columnInfo->GetWidth(GetMaxGridCounts(columnInfo)));
214 auto maxWidth = std::min(maxHorizontalSpace, maxGridWidth);
215 maxWidth = std::min(constraint.maxSize.Width(), maxWidth);
216 constraint.maxSize.SetWidth(maxWidth);
217 constraint.percentReference.SetWidth(maxWidth);
218 }
219
UpdateConstraintHeight(LayoutWrapper * layoutWrapper,LayoutConstraintF & constraint)220 void MenuLayoutAlgorithm::UpdateConstraintHeight(LayoutWrapper* layoutWrapper, LayoutConstraintF& constraint)
221 {
222 auto maxSpaceHeight = std::max(topSpace_, bottomSpace_);
223 constraint.maxSize.SetHeight(maxSpaceHeight);
224 constraint.percentReference.SetHeight(maxSpaceHeight);
225 }
226
CreateChildConstraint(LayoutWrapper * layoutWrapper)227 LayoutConstraintF MenuLayoutAlgorithm::CreateChildConstraint(LayoutWrapper* layoutWrapper)
228 {
229 auto menuLayoutProperty = AceType::DynamicCast<MenuLayoutProperty>(layoutWrapper->GetLayoutProperty());
230 CHECK_NULL_RETURN(menuLayoutProperty, LayoutConstraintF());
231
232 auto childConstraint = menuLayoutProperty->CreateChildConstraint();
233 UpdateConstraintWidth(layoutWrapper, childConstraint);
234 UpdateConstraintHeight(layoutWrapper, childConstraint);
235 auto menuPattern = layoutWrapper->GetHostNode()->GetPattern<MenuPattern>();
236 CHECK_NULL_RETURN(menuLayoutProperty, childConstraint);
237 if (!menuPattern->IsMultiMenu()) {
238 UpdateConstraintBaseOnOptions(layoutWrapper, childConstraint);
239 } else {
240 UpdateConstraintBaseOnMenuItems(layoutWrapper, childConstraint);
241 }
242 return childConstraint;
243 }
244
UpdateConstraintBaseOnOptions(LayoutWrapper * layoutWrapper,LayoutConstraintF & constraint)245 void MenuLayoutAlgorithm::UpdateConstraintBaseOnOptions(LayoutWrapper* layoutWrapper, LayoutConstraintF& constraint)
246 {
247 auto menuPattern = layoutWrapper->GetHostNode()->GetPattern<MenuPattern>();
248 CHECK_NULL_VOID(menuPattern);
249 auto options = menuPattern->GetOptions();
250 if (options.empty()) {
251 LOGD("options is empty, no need to update constraint.");
252 return;
253 }
254 auto maxChildrenWidth = constraint.minSize.Width();
255 auto optionConstraint = constraint;
256 optionConstraint.maxSize.MinusWidth(optionPadding_ * 2.0f);
257 auto optionsLayoutWrapper = GetOptionsLayoutWrappper(layoutWrapper);
258 for (const auto& optionWrapper : optionsLayoutWrapper) {
259 optionWrapper->Measure(optionConstraint);
260 auto childSize = optionWrapper->GetGeometryNode()->GetMarginFrameSize();
261 maxChildrenWidth = std::max(maxChildrenWidth, childSize.Width());
262 }
263 UpdateOptionConstraint(optionsLayoutWrapper, maxChildrenWidth);
264 constraint.minSize.SetWidth(maxChildrenWidth + optionPadding_ * 2.0f);
265 }
266
GetOptionsLayoutWrappper(LayoutWrapper * layoutWrapper)267 std::list<RefPtr<LayoutWrapper>> MenuLayoutAlgorithm::GetOptionsLayoutWrappper(LayoutWrapper* layoutWrapper)
268 {
269 std::list<RefPtr<LayoutWrapper>> optionsWrapper;
270 auto scrollWrapper = layoutWrapper->GetOrCreateChildByIndex(0);
271 CHECK_NULL_RETURN(scrollWrapper, optionsWrapper);
272 auto columnWrapper = scrollWrapper->GetOrCreateChildByIndex(0);
273 CHECK_NULL_RETURN(columnWrapper, optionsWrapper);
274 optionsWrapper = columnWrapper->GetAllChildrenWithBuild();
275 return optionsWrapper;
276 }
277
UpdateOptionConstraint(std::list<RefPtr<LayoutWrapper>> & options,float width)278 void MenuLayoutAlgorithm::UpdateOptionConstraint(std::list<RefPtr<LayoutWrapper>>& options, float width)
279 {
280 for (const auto& option : options) {
281 auto optionLayoutProps = option->GetLayoutProperty();
282 CHECK_NULL_VOID(optionLayoutProps);
283 optionLayoutProps->UpdateCalcMinSize(CalcSize(CalcLength(width), std::nullopt));
284 }
285 }
286
UpdateConstraintBaseOnMenuItems(LayoutWrapper * layoutWrapper,LayoutConstraintF & constraint)287 void MenuLayoutAlgorithm::UpdateConstraintBaseOnMenuItems(LayoutWrapper* layoutWrapper, LayoutConstraintF& constraint)
288 {
289 // multiMenu children are menuItem or menuItemGroup, constrain width is same as the menu
290 auto maxChildrenWidth = GetChildrenMaxWidth(layoutWrapper, constraint);
291 constraint.minSize.SetWidth(maxChildrenWidth);
292 }
293
LayoutSubMenu(LayoutWrapper * layoutWrapper)294 void MenuLayoutAlgorithm::LayoutSubMenu(LayoutWrapper* layoutWrapper)
295 {
296 auto size = layoutWrapper->GetGeometryNode()->GetFrameSize();
297 auto menuLayoutProperty = AceType::DynamicCast<MenuLayoutProperty>(layoutWrapper->GetLayoutProperty());
298 CHECK_NULL_VOID(menuLayoutProperty);
299 auto menuNode = layoutWrapper->GetHostNode();
300 CHECK_NULL_VOID(menuNode);
301 auto menuPattern = menuNode->GetPattern<MenuPattern>();
302 CHECK_NULL_VOID(menuPattern);
303 auto parentMenuItem = menuPattern->GetParentMenuItem();
304 CHECK_NULL_VOID(parentMenuItem);
305 auto menuItemSize = parentMenuItem->GetGeometryNode()->GetFrameSize();
306
307 float x = HorizontalLayoutSubMenu(size, position_.GetX(), menuItemSize) - pageOffset_.GetX();
308 float y = VerticalLayoutSubMenu(size, position_.GetY(), menuItemSize) - pageOffset_.GetY();
309
310 const auto& geometryNode = layoutWrapper->GetGeometryNode();
311 CHECK_NULL_VOID(geometryNode);
312 geometryNode->SetMarginFrameOffset(NG::OffsetF(x, y));
313
314 if (parentMenuItem) {
315 auto parentPattern = parentMenuItem->GetPattern<MenuItemPattern>();
316 auto topLeftPoint = OffsetF(x, y);
317 auto bottomRightPoint = OffsetF(x + size.Width(), y + size.Height());
318 parentPattern->AddHoverRegions(topLeftPoint, bottomRightPoint);
319 }
320
321 auto child = layoutWrapper->GetOrCreateChildByIndex(0);
322 child->Layout();
323 }
324
325 // return vertical offset
VerticalLayout(const SizeF & size,float position)326 float MenuLayoutAlgorithm::VerticalLayout(const SizeF& size, float position)
327 {
328 float wrapperHeight = wrapperSize_.Height();
329 // can put menu below click point
330 if (bottomSpace_ >= size.Height()) {
331 return position + margin_;
332 }
333
334 // put menu above click point
335 if (topSpace_ >= size.Height()) {
336 return topSpace_ - size.Height() + margin_ + pageOffset_.GetY();
337 }
338
339 // line up bottom of menu with bottom of the screen
340 if (size.Height() < wrapperHeight) {
341 return wrapperHeight - size.Height();
342 }
343 // can't fit in screen, line up with top of the screen
344 return 0.0f;
345 }
346
347 // returns horizontal offset
HorizontalLayout(const SizeF & size,float position,bool isSelectMenu)348 float MenuLayoutAlgorithm::HorizontalLayout(const SizeF& size, float position, bool isSelectMenu)
349 {
350 float wrapperWidth = wrapperSize_.Width();
351 // can fit menu on the right side of position
352 if (rightSpace_ >= size.Width()) {
353 return position + margin_;
354 }
355
356 // fit menu on the left side
357 if (!isSelectMenu && leftSpace_ >= size.Width()) {
358 return position - size.Width();
359 }
360
361 // line up right side of menu with right boundary of the screen
362 if (size.Width() < wrapperWidth) {
363 return wrapperWidth - size.Width();
364 }
365
366 // can't fit in screen, line up with left side of the screen
367 return 0.0f;
368 }
369
370 // return vertical offset
VerticalLayoutSubMenu(const SizeF & size,float position,const SizeF & menuItemSize)371 float MenuLayoutAlgorithm::VerticalLayoutSubMenu(const SizeF& size, float position, const SizeF& menuItemSize)
372 {
373 float wrapperHeight = wrapperSize_.Height();
374 float bottomSpace = wrapperHeight - position;
375 // line up top of subMenu with top of the menuItem
376 if (bottomSpace >= size.Height()) {
377 return position;
378 }
379 // line up bottom of menu with bottom of the screen
380 if (size.Height() < wrapperHeight) {
381 return wrapperHeight - size.Height();
382 }
383 // can't fit in screen, line up with top of the screen
384 return 0.0f;
385 }
386
387 // returns submenu horizontal offset
HorizontalLayoutSubMenu(const SizeF & size,float position,const SizeF & menuItemSize)388 float MenuLayoutAlgorithm::HorizontalLayoutSubMenu(const SizeF& size, float position, const SizeF& menuItemSize)
389 {
390 float wrapperWidth = wrapperSize_.Width();
391 float rightSpace = wrapperWidth - position;
392 float leftSpace = position - pageOffset_.GetX() - menuItemSize.Width();
393 // can fit subMenu on the right side of menuItem
394 if (rightSpace >= size.Width()) {
395 return position;
396 }
397 // fit subMenu on the left side of menuItem
398 if (leftSpace >= size.Width()) {
399 return position - size.Width() - menuItemSize.Width();
400 }
401 // line up right side of menu with right boundary of the screen
402 if (size.Width() < wrapperWidth) {
403 return wrapperWidth - size.Width();
404 }
405 // can't fit in screen, line up with left side of the screen
406 return 0.0f;
407 }
408
GetChildrenMaxWidth(LayoutWrapper * layoutWrapper,const LayoutConstraintF & layoutConstraint)409 float MenuLayoutAlgorithm::GetChildrenMaxWidth(LayoutWrapper* layoutWrapper, const LayoutConstraintF& layoutConstraint)
410 {
411 float maxWidth = layoutConstraint.minSize.Width();
412 for (const auto& child : layoutWrapper->GetAllChildrenWithBuild()) {
413 child->Measure(layoutConstraint);
414 auto childSize = child->GetGeometryNode()->GetFrameSize();
415 maxWidth = std::max(maxWidth, childSize.Width());
416 }
417 return maxWidth;
418 }
419
420 } // namespace OHOS::Ace::NG
421