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