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