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