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