• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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