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