• 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/sub_menu_layout_algorithm.h"
17 
18 #include "core/components/container_modal/container_modal_constants.h"
19 #include "core/components_ng/pattern/menu/menu_item/menu_item_pattern.h"
20 namespace OHOS::Ace::NG {
21 
Layout(LayoutWrapper * layoutWrapper)22 void SubMenuLayoutAlgorithm::Layout(LayoutWrapper* layoutWrapper)
23 {
24     CHECK_NULL_VOID(layoutWrapper);
25     auto size = layoutWrapper->GetGeometryNode()->GetFrameSize();
26     auto menuNode = layoutWrapper->GetHostNode();
27     CHECK_NULL_VOID(menuNode);
28     auto menuPattern = menuNode->GetPattern<MenuPattern>();
29     CHECK_NULL_VOID(menuPattern);
30     auto props = AceType::DynamicCast<MenuLayoutProperty>(layoutWrapper->GetLayoutProperty());
31     CHECK_NULL_VOID(props);
32     auto parentMenuItem = menuPattern->GetParentMenuItem();
33     CHECK_NULL_VOID(parentMenuItem);
34     InitCanExpandCurrentWindow(props->GetShowInSubWindowValue(false));
35     if (Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_ELEVEN)) {
36         ModifySubMenuWrapper(layoutWrapper);
37     }
38     CheckMenuPadding(layoutWrapper);
39     const auto& geometryNode = layoutWrapper->GetGeometryNode();
40     CHECK_NULL_VOID(geometryNode);
41     auto parentItemPattern = parentMenuItem->GetPattern<MenuItemPattern>();
42     CHECK_NULL_VOID(parentItemPattern);
43     auto expandingMode = parentItemPattern->GetExpandingMode();
44     OffsetF position = GetSubMenuLayoutOffset(layoutWrapper, parentMenuItem, size,
45         expandingMode == SubMenuExpandingMode::STACK);
46     geometryNode->SetMarginFrameOffset(position);
47     if (parentMenuItem) {
48         auto parentPattern = parentMenuItem->GetPattern<MenuItemPattern>();
49         CHECK_NULL_VOID(parentPattern);
50         auto bottomRightPoint = position + OffsetF(size.Width(), size.Height());
51         auto pipelineContext = parentMenuItem->GetContextWithCheck();
52         CHECK_NULL_VOID(pipelineContext);
53         auto windowManager = pipelineContext->GetWindowManager();
54         auto isContainerModal = pipelineContext->GetWindowModal() == WindowModal::CONTAINER_MODAL && windowManager &&
55                                 windowManager->GetWindowMode() == WindowMode::WINDOW_MODE_FLOATING;
56         OffsetF wrapperOffset;
57         if ((!canExpandCurrentWindow_) && isContainerModal) {
58             auto newOffsetX = static_cast<float>(CONTAINER_BORDER_WIDTH.ConvertToPx());
59             if (Container::LessThanAPITargetVersion(PlatformVersion::VERSION_EIGHTEEN)) {
60                 newOffsetX += static_cast<float>(CONTENT_PADDING.ConvertToPx());
61             }
62             auto newOffsetY = static_cast<float>(pipelineContext->GetCustomTitleHeight().ConvertToPx()) +
63                                 static_cast<float>(CONTAINER_BORDER_WIDTH.ConvertToPx());
64             wrapperOffset = OffsetF(newOffsetX, newOffsetY);
65         }
66         parentPattern->AddHoverRegions(position + wrapperOffset, bottomRightPoint + wrapperOffset);
67     }
68     auto child = layoutWrapper->GetOrCreateChildByIndex(0);
69     CHECK_NULL_VOID(child);
70     child->Layout();
71     ClipMenuPath(layoutWrapper);
72 }
73 
GetSubMenuLayoutOffset(LayoutWrapper * layoutWrapper,const RefPtr<FrameNode> & parentMenuItem,const SizeF & size,bool stacked)74 OffsetF SubMenuLayoutAlgorithm::GetSubMenuLayoutOffset(LayoutWrapper* layoutWrapper,
75     const RefPtr<FrameNode>& parentMenuItem, const SizeF& size, bool stacked)
76 {
77     OffsetF position;
78     auto layoutDirection = layoutWrapper->GetLayoutProperty()->GetNonAutoLayoutDirection();
79     position = MenuLayoutAvoidAlgorithm(parentMenuItem, size, stacked, layoutWrapper);
80     if (layoutDirection == TextDirection::RTL) {
81         position.SetX(wrapperSize_.Width() - position.GetX() - size.Width());
82     }
83     return position;
84 }
85 
MenuLayoutAvoidAlgorithm(const RefPtr<FrameNode> & parentMenuItem,const SizeF & size,bool stacked,LayoutWrapper * layoutWrapper)86 OffsetF SubMenuLayoutAlgorithm::MenuLayoutAvoidAlgorithm(const RefPtr<FrameNode>& parentMenuItem,
87     const SizeF& size, bool stacked, LayoutWrapper* layoutWrapper)
88 {
89     auto pipelineContext = PipelineContext::GetMainPipelineContext();
90     CHECK_NULL_RETURN(pipelineContext, NG::OffsetF(0.0f, 0.0f));
91     auto menuItemSize = parentMenuItem->GetGeometryNode()->GetFrameSize();
92     position_ = GetSubMenuPosition(parentMenuItem, stacked);
93     if (layoutWrapper != nullptr) {
94         auto menuLayoutProperty = layoutWrapper->GetLayoutProperty();
95         CHECK_NULL_RETURN(menuLayoutProperty, NG::OffsetF(0.0f, 0.0f));
96         auto layoutDirection = menuLayoutProperty->GetNonAutoLayoutDirection();
97         if (layoutDirection == TextDirection::RTL) {
98             float leftSpace = position_.GetX() - menuItemSize.Width();
99             position_ = OffsetF(wrapperSize_.Width() - leftSpace, position_.GetY());
100         }
101     }
102     float x = HorizontalLayoutSubMenu(size, position_.GetX(), menuItemSize);
103     x = std::clamp(x, paddingStart_, wrapperSize_.Width() - size.Width() - paddingEnd_);
104     float y = 0.0f;
105     if (canExpandCurrentWindow_ || !Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_ELEVEN)) {
106         y = VerticalLayoutSubMenu(size, position_.GetY(), menuItemSize);
107     } else {
108         y = VerticalLayoutSubMenuHalfScreen(size, position_.GetY(), menuItemSize);
109     }
110     float yMinAvoid = wrapperRect_.Top() + paddingTop_;
111     float yMaxAvoid = wrapperRect_.Bottom() - paddingBottom_ - size.Height();
112     y = std::clamp(y, yMinAvoid, yMaxAvoid);
113     return NG::OffsetF(x, y);
114 }
115 
GetSubMenuPosition(const RefPtr<FrameNode> & parentMenuItem,bool stacked)116 OffsetF SubMenuLayoutAlgorithm::GetSubMenuPosition(const RefPtr<FrameNode>& parentMenuItem, bool stacked)
117 {
118     auto parentItemFrameSize = parentMenuItem->GetGeometryNode()->GetMarginFrameSize();
119     OffsetF position;
120     if (stacked) {
121         auto parentItemPattern = parentMenuItem->GetPattern<MenuItemPattern>();
122         if (parentItemPattern != nullptr) {
123             auto parentMenu = parentItemPattern->GetMenu();
124             position = parentMenu == nullptr
125                 ? parentMenuItem->GetPaintRectOffset(false, true) + OffsetF(parentItemFrameSize.Width(), 0.0)
126                 : OffsetF(parentMenu->GetPaintRectOffset(false, true).GetX(),
127                     parentMenuItem->GetPaintRectOffset(false, true).GetY() +
128                     parentItemFrameSize.Height()); // * 0.95
129         }
130     } else {
131         position = parentMenuItem->GetPaintRectOffset(false, true) + OffsetF(parentItemFrameSize.Width(), 0.0);
132     }
133 
134     auto pipelineContext = parentMenuItem->GetContextWithCheck();
135     CHECK_NULL_RETURN(pipelineContext, OffsetF());
136     auto windowManager = pipelineContext->GetWindowManager();
137     CHECK_NULL_RETURN(windowManager, OffsetF());
138     auto isContainerModal = pipelineContext->GetWindowModal() == WindowModal::CONTAINER_MODAL && windowManager &&
139                             windowManager->GetWindowMode() == WindowMode::WINDOW_MODE_FLOATING;
140     if ((!canExpandCurrentWindow_) && isContainerModal) {
141         auto newOffsetX = static_cast<float>(CONTAINER_BORDER_WIDTH.ConvertToPx());
142         if (Container::LessThanAPITargetVersion(PlatformVersion::VERSION_EIGHTEEN)) {
143             newOffsetX += static_cast<float>(CONTENT_PADDING.ConvertToPx());
144         }
145         auto newOffsetY = static_cast<float>(pipelineContext->GetCustomTitleHeight().ConvertToPx()) +
146                             static_cast<float>(CONTAINER_BORDER_WIDTH.ConvertToPx());
147         position -= OffsetF(newOffsetX, newOffsetY);
148     }
149     auto parentMenu = AceType::DynamicCast<FrameNode>(parentMenuItem->GetParent());
150     CHECK_NULL_RETURN(parentMenu, position);
151     auto scroll = AceType::DynamicCast<FrameNode>(parentMenu->GetParent());
152     CHECK_NULL_RETURN(scroll, position);
153     while (scroll && (scroll->GetTag() != V2::SCROLL_ETS_TAG)) {
154         scroll = AceType::DynamicCast<FrameNode>(scroll->GetParent());
155     }
156     CHECK_NULL_RETURN(scroll, position);
157     auto scrollGeometryNode = scroll->GetGeometryNode();
158     CHECK_NULL_RETURN(scrollGeometryNode, position);
159     auto scrollTop = scroll->GetPaintRectOffset(false, true).GetY();
160     auto scrollHeight = scrollGeometryNode->GetFrameSize().Height();
161     auto bottomOffset = scrollTop + scrollHeight;
162     if (parentMenuItem->GetPaintRectOffset(false, true).GetY() > bottomOffset) {
163         return scroll->GetPaintRectOffset(false, true) + OffsetF(parentItemFrameSize.Width(), 0.0);
164     }
165     return position;
166 }
167 
VerticalLayoutSubMenuHalfScreen(const SizeF & size,float position,const SizeF & menuItemSize)168 float SubMenuLayoutAlgorithm::VerticalLayoutSubMenuHalfScreen(
169     const SizeF& size, float position, const SizeF& menuItemSize)
170 {
171     auto pipelineContext = PipelineContext::GetMainPipelineContext();
172     CHECK_NULL_RETURN(pipelineContext, 0.0f);
173     auto safeAreaManager = pipelineContext->GetSafeAreaManager();
174     CHECK_NULL_RETURN(safeAreaManager, 0.0f);
175     float wrapperHeight = wrapperSize_.Height();
176 
177     float bottomSpace = wrapperSize_.Height() - (position_.GetY() - param_.windowsOffsetY) - margin_ * 2.0f;
178     // line up top of subMenu with top of the menuItem
179     if (bottomSpace >= size.Height()) {
180         return position;
181     }
182     // line up bottom of menu with bottom of the screen
183     if (size.Height() < wrapperHeight) {
184         return wrapperHeight - size.Height();
185     }
186     // can't fit in screen, line up with top of the screen
187     return 0.0f;
188 }
189 
190 // return submenu vertical offset
VerticalLayoutSubMenu(const SizeF & size,float position,const SizeF & menuItemSize)191 float SubMenuLayoutAlgorithm::VerticalLayoutSubMenu(const SizeF& size, float position, const SizeF& menuItemSize)
192 {
193     float bottomSpace = wrapperRect_.Bottom() - position - paddingBottom_;
194     // line up top of subMenu with top of the menuItem
195     if (bottomSpace >= size.Height()) {
196         return position;
197     }
198     // line up bottom of menu with bottom of the screen
199     if (size.Height() < wrapperRect_.Height()) {
200         return wrapperRect_.Bottom() - size.Height() - paddingBottom_;
201     }
202     // can't fit in screen, line up with top of the screen
203     return wrapperRect_.Top() + paddingTop_;
204 }
205 
206 // returns submenu horizontal offset
HorizontalLayoutSubMenu(const SizeF & size,float position,const SizeF & menuItemSize,LayoutWrapper * layoutWrapper)207 float SubMenuLayoutAlgorithm::HorizontalLayoutSubMenu(
208     const SizeF& size, float position, const SizeF& menuItemSize, LayoutWrapper* layoutWrapper)
209 {
210     float wrapperWidth = wrapperSize_.Width();
211     float rightSpace = wrapperWidth - position - paddingEnd_;
212     float leftSpace = position - menuItemSize.Width();
213     if (layoutWrapper != nullptr) {
214         auto menuLayoutProperty = layoutWrapper->GetLayoutProperty();
215         CHECK_NULL_RETURN(menuLayoutProperty, 0.0f);
216         auto layoutDirection = menuLayoutProperty->GetNonAutoLayoutDirection();
217         if (layoutDirection == TextDirection::RTL) {
218             rightSpace = position - menuItemSize.Width();
219             leftSpace = wrapperWidth - position;
220         }
221     }
222     // can fit subMenu on the right side of menuItem
223     if (rightSpace >= size.Width()) {
224         return position;
225     }
226     // fit subMenu on the left side of menuItem
227     if (leftSpace >= size.Width()) {
228         return position - size.Width() - menuItemSize.Width();
229     }
230     // line up right side of menu with right boundary of the screen
231     if (size.Width() < wrapperWidth) {
232         return wrapperWidth - size.Width() - paddingEnd_;
233     }
234     // can't fit in screen, line up with left side of the screen
235     return 0.0f;
236 }
237 
ModifySubMenuWrapper(LayoutWrapper * layoutWrapper)238 void SubMenuLayoutAlgorithm::ModifySubMenuWrapper(LayoutWrapper* layoutWrapper)
239 {
240     CHECK_NULL_VOID(layoutWrapper);
241     auto pipelineContext = PipelineContext::GetMainPipelineContext();
242     CHECK_NULL_VOID(pipelineContext);
243     auto safeAreaManager = pipelineContext->GetSafeAreaManager();
244     CHECK_NULL_VOID(safeAreaManager);
245     auto bottom = safeAreaManager->GetSystemSafeArea().bottom_.Length();
246     if (!canExpandCurrentWindow_) {
247         wrapperSize_ = SizeF(param_.menuWindowRect.Width(), param_.menuWindowRect.Height() - bottom);
248     } else {
249         wrapperSize_ = SizeF(wrapperSize_.Width(), wrapperSize_.Height());
250     }
251 }
252 
InitializePadding(LayoutWrapper * layoutWrapper)253 void SubMenuLayoutAlgorithm::InitializePadding(LayoutWrapper* layoutWrapper)
254 {
255     auto menuPattern = layoutWrapper->GetHostNode()->GetPattern<MenuPattern>();
256     CHECK_NULL_VOID(menuPattern);
257     auto host = menuPattern->GetHost();
258     CHECK_NULL_VOID(host);
259     auto pipeline = host->GetContextWithCheck();
260     CHECK_NULL_VOID(pipeline);
261     auto theme = pipeline->GetTheme<SelectTheme>();
262     CHECK_NULL_VOID(theme);
263     if (!menuPattern->IsSelectOverlayExtensionMenu()) {
264         margin_ = static_cast<float>(theme->GetMenuPadding().ConvertToPx());
265         paddingStart_ = static_cast<float>(theme->GetDefaultPaddingStart().ConvertToPx());
266         paddingEnd_ = static_cast<float>(theme->GetDefaultPaddingEnd().ConvertToPx());
267         if (!AceApplicationInfo::GetInstance().GreatOrEqualTargetAPIVersion(PlatformVersion::VERSION_TWELVE)) {
268             paddingTop_ = static_cast<float>(theme->GetDefaultPaddingTop().ConvertToPx());
269             paddingBottom_ = static_cast<float>(theme->GetDefaultPaddingBottomFixed().ConvertToPx());
270         }
271     }
272 }
273 
InitializePaddingAPI12(LayoutWrapper * layoutWrapper)274 void SubMenuLayoutAlgorithm::InitializePaddingAPI12(LayoutWrapper* layoutWrapper)
275 {
276     auto menuNode = layoutWrapper->GetHostNode();
277     CHECK_NULL_VOID(menuNode);
278     auto menuPattern = menuNode->GetPattern<MenuPattern>();
279     CHECK_NULL_VOID(menuPattern);
280     auto pipeline = PipelineContext::GetMainPipelineContext();
281     CHECK_NULL_VOID(pipeline);
282     auto theme = pipeline->GetTheme<SelectTheme>();
283     CHECK_NULL_VOID(theme);
284     if (!menuPattern->IsSelectOverlayExtensionMenu()) {
285         margin_ = static_cast<float>(theme->GetMenuPadding().ConvertToPx());
286         if (!canExpandCurrentWindow_) {
287             paddingStart_ = static_cast<float>(theme->GetMenuLargeMargin().ConvertToPx());
288             paddingEnd_ = static_cast<float>(theme->GetMenuLargeMargin().ConvertToPx());
289         } else {
290             paddingStart_ = static_cast<float>(theme->GetMenuMediumMargin().ConvertToPx());
291             paddingEnd_ = static_cast<float>(theme->GetMenuMediumMargin().ConvertToPx());
292         }
293     }
294 }
295 
CheckMenuPadding(LayoutWrapper * layoutWrapper)296 void SubMenuLayoutAlgorithm::CheckMenuPadding(LayoutWrapper* layoutWrapper)
297 {
298     CHECK_NULL_VOID(layoutWrapper);
299     if (Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_TWELVE)) {
300         InitializePaddingAPI12(layoutWrapper);
301     } else {
302         InitializePadding(layoutWrapper);
303     }
304 }
305 
306 } // namespace OHOS::Ace::NG
307