• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2022 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/select_overlay/select_overlay_layout_algorithm.h"
17 
18 #include <cmath>
19 #include <optional>
20 
21 #include "base/geometry/ng/offset_t.h"
22 #include "base/utils/string_utils.h"
23 #include "base/utils/utils.h"
24 #include "core/components/text_overlay/text_overlay_theme.h"
25 #include "core/components_ng/pattern/linear_layout/linear_layout_pattern.h"
26 #include "core/components_ng/pattern/linear_layout/linear_layout_property.h"
27 #include "core/pipeline_ng/pipeline_context.h"
28 
29 namespace OHOS::Ace::NG {
30 namespace {
31 constexpr Dimension MORE_MENU_INTERVAL = 8.0_vp;
32 }
33 
Measure(LayoutWrapper * layoutWrapper)34 void SelectOverlayLayoutAlgorithm::Measure(LayoutWrapper* layoutWrapper)
35 {
36     auto layoutProperty = layoutWrapper->GetLayoutProperty();
37     CHECK_NULL_VOID(layoutProperty);
38     auto layoutConstraint = layoutProperty->CreateChildConstraint();
39     auto pipeline = PipelineContext::GetCurrentContext();
40     CHECK_NULL_VOID(pipeline);
41     auto safeAreaManager = pipeline->GetSafeAreaManager();
42     CHECK_NULL_VOID(safeAreaManager);
43     auto keyboardHeight = safeAreaManager->GetKeyboardInset().Length();
44     auto maxSize =
45         SizeF(layoutConstraint.maxSize.Width(), layoutConstraint.maxSize.Height() - keyboardHeight -
46                                                     (info_->isUsingMouse ? info_->rightClickOffset.GetY() : 0.0f));
47     layoutConstraint.maxSize = maxSize;
48     bool isMouseCustomMenu = false;
49     if (info_->menuInfo.menuBuilder) {
50         auto customMenuLayoutWrapper = layoutWrapper->GetChildByIndex(0);
51         CHECK_NULL_VOID(customMenuLayoutWrapper);
52         auto customMenuLayoutConstraint = layoutConstraint;
53         CalculateCustomMenuLayoutConstraint(layoutWrapper, customMenuLayoutConstraint);
54         customMenuLayoutWrapper->Measure(customMenuLayoutConstraint);
55         isMouseCustomMenu = true;
56     }
57     auto childIndex = -1;
58     for (auto&& child : layoutWrapper->GetAllChildrenWithBuild()) {
59         childIndex++;
60         if (childIndex == 0 && isMouseCustomMenu) {
61             continue;
62         }
63         child->Measure(layoutConstraint);
64     }
65     PerformMeasureSelf(layoutWrapper);
66 }
67 
CalculateCustomMenuLayoutConstraint(LayoutWrapper * layoutWrapper,LayoutConstraintF & layoutConstraint)68 void SelectOverlayLayoutAlgorithm::CalculateCustomMenuLayoutConstraint(
69     LayoutWrapper* layoutWrapper, LayoutConstraintF& layoutConstraint)
70 {
71     auto pipeline = PipelineContext::GetCurrentContext();
72     CHECK_NULL_VOID(pipeline);
73     auto theme = pipeline->GetTheme<TextOverlayTheme>();
74     CHECK_NULL_VOID(theme);
75     // Calculate the spacing with text and handle, menu is fixed up the handle and text.
76     double menuSpacingBetweenText = theme->GetMenuSpacingWithText().ConvertToPx();
77     double menuSpacingBetweenHandle = theme->GetHandleDiameter().ConvertToPx();
78 
79     // paint rect is in global position, need to convert to local position
80     auto offset = layoutWrapper->GetGeometryNode()->GetFrameOffset();
81     const auto firstHandleRect = info_->firstHandle.paintRect - offset;
82     const auto secondHandleRect = info_->secondHandle.paintRect - offset;
83 
84     auto top = info_->isNewAvoid ? info_->selectArea.Top() : firstHandleRect.Top();
85     auto bottom = info_->isNewAvoid ? info_->selectArea.Bottom() : secondHandleRect.Bottom();
86     auto topSpace = top - menuSpacingBetweenText - menuSpacingBetweenHandle;
87     auto bottomSpace = layoutConstraint.maxSize.Height() -
88                        (bottom + menuSpacingBetweenText + menuSpacingBetweenHandle);
89     if (info_->isUsingMouse) {
90         layoutConstraint.selfIdealSize = OptionalSizeF(std::nullopt, layoutConstraint.maxSize.Height());
91     } else {
92         layoutConstraint.selfIdealSize = OptionalSizeF(std::nullopt, std::max(topSpace, bottomSpace));
93     }
94 }
95 
CalculateCustomMenuByMouseOffset(LayoutWrapper * layoutWrapper)96 OffsetF SelectOverlayLayoutAlgorithm::CalculateCustomMenuByMouseOffset(LayoutWrapper* layoutWrapper)
97 {
98     auto menuOffset = info_->rightClickOffset;
99     auto layoutProperty = layoutWrapper->GetLayoutProperty();
100     CHECK_NULL_RETURN(layoutProperty, menuOffset);
101     auto layoutConstraint = layoutProperty->GetLayoutConstraint();
102     CHECK_NULL_RETURN(layoutConstraint, menuOffset);
103     auto menu = layoutWrapper->GetOrCreateChildByIndex(0);
104     CHECK_NULL_RETURN(menu, menuOffset);
105     auto maxWidth = layoutConstraint->selfIdealSize.Width().value_or(0.0f);
106     auto menuSize = menu->GetGeometryNode()->GetFrameSize();
107     if (GreatNotEqual(menuOffset.GetX() + menuSize.Width(), maxWidth)) {
108         if (GreatOrEqual(menuOffset.GetX(), menuSize.Width())) {
109             menuOffset.SetX(menuOffset.GetX() - menuSize.Width());
110         } else if (LessOrEqual(menuSize.Width(), maxWidth)) {
111             menuOffset.SetX(maxWidth - menuSize.Width());
112         } else if (GreatNotEqual(menuSize.Width(), maxWidth)) {
113             menuOffset.SetX(menuOffset.GetX() - menuSize.Width() / 2.0f);
114         }
115     }
116     return menuOffset;
117 }
118 
Layout(LayoutWrapper * layoutWrapper)119 void SelectOverlayLayoutAlgorithm::Layout(LayoutWrapper* layoutWrapper)
120 {
121     auto menu = layoutWrapper->GetOrCreateChildByIndex(0);
122     CHECK_NULL_VOID(menu);
123     auto isNewAvoid = info_->isNewAvoid && !info_->isSingleHandle;
124     auto shouldInActiveByHandle =
125         !info_->firstHandle.isShow && !info_->secondHandle.isShow && !info_->isSelectRegionVisible;
126     if ((!CheckInShowArea(*info_) || (!isNewAvoid && shouldInActiveByHandle)) && !info_->isUsingMouse) {
127         menu->SetActive(false);
128         return;
129     } else {
130         menu->SetActive(true);
131     }
132     OffsetF menuOffset;
133     if (info_->isUsingMouse) {
134         menuOffset = CalculateCustomMenuByMouseOffset(layoutWrapper);
135     } else {
136         menuOffset = ComputeSelectMenuPosition(layoutWrapper);
137     }
138     menu->GetGeometryNode()->SetMarginFrameOffset(menuOffset);
139 
140     // custom menu need to layout all menu and submenu
141     if (info_->menuInfo.menuBuilder) {
142         for (const auto& child : layoutWrapper->GetAllChildrenWithBuild()) {
143             child->Layout();
144         }
145         return;
146     }
147     menu->Layout();
148     auto button = layoutWrapper->GetOrCreateChildByIndex(1);
149     CHECK_NULL_VOID(button);
150     auto menuNode = menu->GetHostNode();
151     CHECK_NULL_VOID(menuNode);
152     auto menuContext = menuNode->GetRenderContext();
153     CHECK_NULL_VOID(menuContext);
154     auto offset = OffsetF();
155     if (menuContext->GetOffset()) {
156         offset =
157             OffsetF(menuContext->GetOffset()->GetX().ConvertToPx(), menuContext->GetOffset()->GetY().ConvertToPx());
158     }
159     if (!info_->menuInfo.menuIsShow || info_->menuInfo.menuDisable) {
160         hasExtensionMenu_ = false;
161         return;
162     }
163     hasExtensionMenu_ = true;
164     button->GetGeometryNode()->SetMarginFrameOffset(menuOffset);
165     button->Layout();
166     auto extensionMenuOffset = ComputeExtensionMenuPosition(layoutWrapper, offset);
167 
168     auto extensionMenu = layoutWrapper->GetOrCreateChildByIndex(2);
169     CHECK_NULL_VOID(extensionMenu);
170     extensionMenu->GetGeometryNode()->SetMarginFrameOffset(extensionMenuOffset);
171     extensionMenu->Layout();
172 }
173 
CheckInShowArea(const SelectOverlayInfo & info)174 bool SelectOverlayLayoutAlgorithm::CheckInShowArea(const SelectOverlayInfo& info)
175 {
176     if (info.useFullScreen) {
177         return true;
178     }
179     if (info.isSingleHandle) {
180         return info.firstHandle.paintRect.IsWrappedBy(info.showArea);
181     }
182     return info.firstHandle.paintRect.IsWrappedBy(info.showArea) &&
183            info.secondHandle.paintRect.IsWrappedBy(info.showArea);
184 }
185 
ComputeSelectMenuPosition(LayoutWrapper * layoutWrapper)186 OffsetF SelectOverlayLayoutAlgorithm::ComputeSelectMenuPosition(LayoutWrapper* layoutWrapper)
187 {
188     auto menuItem = layoutWrapper->GetOrCreateChildByIndex(0);
189     CHECK_NULL_RETURN(menuItem, OffsetF());
190     auto pipeline = PipelineContext::GetCurrentContext();
191     CHECK_NULL_RETURN(pipeline, OffsetF());
192     auto theme = pipeline->GetTheme<TextOverlayTheme>();
193     CHECK_NULL_RETURN(theme, OffsetF());
194     OffsetF menuPosition;
195     bool isExtension = false;
196 
197     // Calculate the spacing with text and handle, menu is fixed up the handle and text.
198     double menuSpacingBetweenText = theme->GetMenuSpacingWithText().ConvertToPx();
199     double menuSpacingBetweenHandle = theme->GetHandleDiameter().ConvertToPx();
200 
201     auto width = menuItem->GetGeometryNode()->GetMarginFrameSize().Width();
202     auto height = menuItem->GetGeometryNode()->GetMarginFrameSize().Height();
203 
204     // When the extended menu is displayed, the default menu becomes circular, but the position of the circle is aligned
205     // with the end of the original menu.
206     if (GreatNotEqual(width, height)) {
207         menuWidth_ = width;
208         menuHeight_ = height;
209     } else {
210         isExtension = true;
211     }
212     auto menuWidth = menuWidth_.value_or(width);
213     auto menuHeight = menuHeight_.value_or(height);
214 
215     // paint rect is in global position, need to convert to local position
216     auto offset = layoutWrapper->GetGeometryNode()->GetFrameOffset();
217     const auto firstHandleRect = info_->firstHandle.paintRect - offset;
218     const auto secondHandleRect = info_->secondHandle.paintRect - offset;
219 
220     auto singleHandle = firstHandleRect;
221     if (!info_->firstHandle.isShow) {
222         singleHandle = secondHandleRect;
223     }
224     if (IsTextAreaSelectAll()) {
225         singleHandle = RectF(info_->menuInfo.menuOffset.value().GetX(), info_->menuInfo.menuOffset.value().GetY(),
226             singleHandle.Width(), singleHandle.Height());
227     }
228 
229     if (info_->isSingleHandle) {
230         auto menuSpacing = static_cast<float>(menuSpacingBetweenText);
231         menuPosition = OffsetF((singleHandle.Left() + singleHandle.Right() - menuWidth) / 2.0f,
232             static_cast<float>(singleHandle.Top() - menuSpacing - menuHeight));
233     } else {
234         auto menuSpacing = static_cast<float>(menuSpacingBetweenText + menuSpacingBetweenHandle);
235         menuPosition = OffsetF((firstHandleRect.Left() + secondHandleRect.Left() - menuWidth) / 2.0f,
236             static_cast<float>(firstHandleRect.Top() - menuSpacing - menuHeight));
237 
238         if (!info_->firstHandle.isShow && info_->secondHandle.isShow && !info_->handleReverse) {
239             menuPosition.SetY(secondHandleRect.Bottom() + menuSpacing);
240         }
241         if (info_->firstHandle.isShow && !info_->secondHandle.isShow && info_->handleReverse) {
242             menuPosition.SetY(firstHandleRect.Bottom() + menuSpacing);
243         }
244         if (info_->firstHandle.isShow && info_->secondHandle.isShow &&
245             !NearEqual(firstHandleRect.Top(), secondHandleRect.Top())) {
246             auto top = std::min(firstHandleRect.Top(), secondHandleRect.Top());
247             menuPosition.SetY(static_cast<float>(top - menuSpacing - menuHeight));
248         }
249         if (!info_->firstHandle.isShow && info_->secondHandle.isShow) {
250             menuPosition.SetX(secondHandleRect.Left() - menuWidth / 2.0f);
251         }
252         if (info_->firstHandle.isShow && !info_->secondHandle.isShow) {
253             menuPosition.SetX(firstHandleRect.Left() - menuWidth / 2.0f);
254         }
255     }
256 
257     auto overlayWidth = layoutWrapper->GetGeometryNode()->GetFrameSize().Width();
258     RectF viewPort = layoutWrapper->GetGeometryNode()->GetFrameRect() - offset;
259     auto frameNode = info_->callerFrameNode.Upgrade();
260     if (frameNode) {
261         auto viewPortOption = frameNode->GetViewPort();
262         if (viewPortOption.has_value()) {
263             viewPort = viewPortOption.value();
264         }
265     }
266     // Adjust position of overlay.
267     auto adjustPositionXWithViewPort = [&](OffsetF& menuPosition) {
268         if (LessOrEqual(menuPosition.GetX(), viewPort.GetX())) {
269             menuPosition.SetX(theme->GetDefaultMenuPositionX());
270         } else if (GreatOrEqual(menuPosition.GetX() + menuWidth, viewPort.GetX() + viewPort.Width())) {
271             menuPosition.SetX(overlayWidth - menuWidth - theme->GetDefaultMenuPositionX());
272         }
273     };
274     adjustPositionXWithViewPort(menuPosition);
275     if (LessNotEqual(menuPosition.GetY(), menuHeight)) {
276         if (IsTextAreaSelectAll()) {
277             menuPosition.SetY(singleHandle.Top());
278         } else {
279             menuPosition.SetY(
280                 static_cast<float>(singleHandle.Bottom() + menuSpacingBetweenText + menuSpacingBetweenHandle));
281         }
282     }
283     auto spaceBetweenViewPort = menuSpacingBetweenText + menuSpacingBetweenHandle;
284     if (LessNotEqual(menuPosition.GetY(), viewPort.GetY() - spaceBetweenViewPort - menuHeight) ||
285         LessNotEqual(menuPosition.GetY(), menuSpacingBetweenText)) {
286         auto menuOffsetY = viewPort.GetY() - spaceBetweenViewPort - menuHeight;
287         if (menuOffsetY > menuSpacingBetweenText) {
288             menuPosition.SetY(menuOffsetY);
289         } else {
290             menuPosition.SetY(menuSpacingBetweenText);
291         }
292     } else if (GreatOrEqual(menuPosition.GetY(), viewPort.GetY() + viewPort.Height() + spaceBetweenViewPort)) {
293         menuPosition.SetY(viewPort.GetY() + viewPort.Height() + spaceBetweenViewPort);
294     }
295 
296     auto safeAreaManager = pipeline->GetSafeAreaManager();
297     if (safeAreaManager) {
298         // ignore status bar
299         auto top = safeAreaManager->GetSystemSafeArea().top_.Length();
300         if (menuPosition.GetY() < top) {
301             menuPosition.SetY(top);
302         }
303     }
304     if (info_->firstHandle.isShow && info_->secondHandle.isShow &&
305         !NearEqual(firstHandleRect.Top(), secondHandleRect.Top())) {
306         auto menuRect = RectF(menuPosition, SizeF(menuWidth, menuHeight));
307         auto downHandleRect =
308             LessNotEqual(firstHandleRect.Top(), secondHandleRect.Top()) ? secondHandleRect : firstHandleRect;
309         auto circleDiameter = menuSpacingBetweenHandle;
310         auto circleOffset =
311             OffsetF(downHandleRect.GetX() - (circleDiameter - downHandleRect.Width()) / 2.0f, downHandleRect.Bottom());
312         auto downHandleCircleRect = RectF(circleOffset, SizeF(circleDiameter, circleDiameter));
313         if (menuRect.IsIntersectWith(downHandleRect) || menuRect.IsInnerIntersectWith(downHandleCircleRect)) {
314             auto menuSpacing = static_cast<float>(menuSpacingBetweenText + circleDiameter);
315             menuPosition.SetY(downHandleRect.Bottom() + menuSpacing);
316         }
317     }
318     auto menuRect = RectF(menuPosition, SizeF(menuWidth, menuHeight));
319     menuPosition = info_->isNewAvoid && !info_->isSingleHandle ? NewMenuAvoidStrategy(menuWidth, menuHeight) :
320         AdjustSelectMenuOffset(layoutWrapper, menuRect, menuSpacingBetweenText, menuSpacingBetweenHandle);
321     adjustPositionXWithViewPort(menuPosition);
322     defaultMenuEndOffset_ = menuPosition + OffsetF(menuWidth, 0.0f);
323     if (isExtension) {
324         return defaultMenuEndOffset_ - OffsetF(width, 0);
325     }
326     return menuPosition;
327 }
328 
AdjustSelectMenuOffset(LayoutWrapper * layoutWrapper,const RectF & menuRect,double spaceBetweenText,double spaceBetweenHandle)329 OffsetF SelectOverlayLayoutAlgorithm::AdjustSelectMenuOffset(
330     LayoutWrapper* layoutWrapper, const RectF& menuRect, double spaceBetweenText, double spaceBetweenHandle)
331 {
332     auto menuOffset = menuRect.GetOffset();
333     if (!info_->firstHandle.isShow && !info_->secondHandle.isShow) {
334         return menuOffset;
335     }
336     auto offset = layoutWrapper->GetGeometryNode()->GetFrameOffset();
337     auto upHandle = info_->handleReverse ? info_->secondHandle : info_->firstHandle;
338     auto downHandle = info_->handleReverse ? info_->firstHandle : info_->secondHandle;
339     // the menu is too far away.
340     auto pipeline = PipelineContext::GetCurrentContext();
341     auto hostFrameNode = info_->callerFrameNode.Upgrade();
342     if (pipeline && hostFrameNode) {
343         auto hostFrameRect = hostFrameNode->GetGeometryNode()->GetFrameRect();
344         auto hostGlobalOffset = hostFrameNode->GetPaintRectOffset() - pipeline->GetRootRect().GetOffset();
345         auto centerX = menuRect.Width() / 2.0f;
346         if (GreatNotEqual(menuRect.GetX() + centerX, hostGlobalOffset.GetX() + hostFrameRect.Width())) {
347             menuOffset.SetX(hostGlobalOffset.GetX() + hostFrameRect.Width() - centerX);
348         } else if (LessNotEqual(menuRect.GetX() + centerX, hostGlobalOffset.GetX())) {
349             menuOffset.SetX(hostGlobalOffset.GetX() - centerX);
350         }
351     }
352     // menu cover up handle
353     auto upPaint = upHandle.paintRect - offset;
354     auto downPaint = downHandle.paintRect - offset;
355     if (upHandle.isShow && !downHandle.isShow) {
356         auto circleOffset = OffsetF(
357             upPaint.GetX() - (spaceBetweenHandle - upPaint.Width()) / 2.0f, upPaint.GetY() - spaceBetweenHandle);
358         auto upCircleRect = RectF(circleOffset, SizeF(spaceBetweenHandle, spaceBetweenHandle));
359         if (menuRect.IsIntersectWith(upPaint) || menuRect.IsIntersectWith(upCircleRect)) {
360             menuOffset.SetY(upPaint.Bottom() + spaceBetweenText + spaceBetweenHandle);
361         }
362         return menuOffset;
363     }
364     // avoid soft keyboard and root bottom
365     if ((!upHandle.isShow && downHandle.isShow) || info_->menuInfo.menuBuilder) {
366         CHECK_NULL_RETURN(pipeline, menuOffset);
367         auto safeAreaManager = pipeline->GetSafeAreaManager();
368         CHECK_NULL_RETURN(safeAreaManager, menuOffset);
369         auto keyboardInsert = safeAreaManager->GetKeyboardInset();
370         auto shouldAvoidKeyboard =
371             GreatNotEqual(keyboardInsert.Length(), 0.0f) && GreatNotEqual(menuRect.Bottom(), keyboardInsert.start);
372         auto rootRect = layoutWrapper->GetGeometryNode()->GetFrameRect();
373         auto shouldAvoidBottom = GreatNotEqual(menuRect.Bottom(), rootRect.Height());
374         auto menuSpace = NearEqual(upPaint.Top(), downPaint.Top()) ? spaceBetweenHandle : spaceBetweenText;
375         auto offsetY = downPaint.GetY() - menuSpace - menuRect.Height();
376         if ((shouldAvoidKeyboard || shouldAvoidBottom) && offsetY > 0) {
377             menuOffset.SetY(offsetY);
378         }
379     }
380     return menuOffset;
381 }
382 
ComputeExtensionMenuPosition(LayoutWrapper * layoutWrapper,const OffsetF & offset)383 OffsetF SelectOverlayLayoutAlgorithm::ComputeExtensionMenuPosition(LayoutWrapper* layoutWrapper, const OffsetF& offset)
384 {
385     auto extensionItem = layoutWrapper->GetOrCreateChildByIndex(2);
386     CHECK_NULL_RETURN(extensionItem, OffsetF());
387     auto extensionLayoutConstraint = extensionItem->GetLayoutProperty()->GetLayoutConstraint();
388     auto extensionLayoutConstraintMaxSize = extensionLayoutConstraint->maxSize;
389     auto extensionWidth = extensionItem->GetGeometryNode()->GetMarginFrameSize().Width();
390     auto extensionHeight = extensionItem->GetGeometryNode()->GetMarginFrameSize().Height();
391     auto menuItem = layoutWrapper->GetOrCreateChildByIndex(0);
392     CHECK_NULL_RETURN(menuItem, OffsetF());
393     auto menuHeight = menuItem->GetGeometryNode()->GetMarginFrameSize().Height();
394     auto extensionOffset =
395         defaultMenuEndOffset_ - OffsetF(extensionWidth, -menuHeight - MORE_MENU_INTERVAL.ConvertToPx());
396     auto extensionBottom = extensionOffset.GetY() + extensionHeight;
397     auto isCoveredBySoftKeyBoard = [extensionBottom]() -> bool {
398         auto pipeline = PipelineContext::GetCurrentContext();
399         CHECK_NULL_RETURN(pipeline, false);
400         auto safeAreaManager = pipeline->GetSafeAreaManager();
401         CHECK_NULL_RETURN(safeAreaManager, false);
402         auto keyboardInsert = safeAreaManager->GetKeyboardInset();
403         return GreatNotEqual(keyboardInsert.Length(), 0.0f) && GreatNotEqual(extensionBottom, keyboardInsert.start);
404     };
405     if (GreatNotEqual(extensionBottom, extensionLayoutConstraintMaxSize.Height()) || isCoveredBySoftKeyBoard()) {
406         extensionOffset =
407             defaultMenuEndOffset_ - OffsetF(extensionWidth, extensionHeight + MORE_MENU_INTERVAL.ConvertToPx());
408     }
409     return extensionOffset;
410 }
411 
IsTextAreaSelectAll()412 bool SelectOverlayLayoutAlgorithm::IsTextAreaSelectAll()
413 {
414     return info_->menuInfo.menuOffset.has_value() && (!info_->firstHandle.isShow || !info_->secondHandle.isShow);
415 }
416 
NewMenuAvoidStrategy(float menuWidth,float menuHeight)417 OffsetF SelectOverlayLayoutAlgorithm::NewMenuAvoidStrategy(float menuWidth, float menuHeight)
418 {
419     auto pipeline = PipelineContext::GetCurrentContext();
420     CHECK_NULL_RETURN(pipeline, OffsetF());
421     auto theme = pipeline->GetTheme<TextOverlayTheme>();
422     CHECK_NULL_RETURN(theme, OffsetF());
423     OffsetF menuPosition;
424 
425     // Calculate the spacing with text and handle, menu is fixed up the handle and
426     // text.
427     double menuSpacingBetweenText = theme->GetMenuSpacingWithText().ConvertToPx();
428     double menuSpacingBetweenHandle = theme->GetHandleDiameter().ConvertToPx();
429     auto selectArea = info_->selectArea;
430     // 安全区域
431     auto safeAreaManager = pipeline->GetSafeAreaManager();
432     CHECK_NULL_RETURN(safeAreaManager, OffsetF());
433     auto topArea = safeAreaManager->GetSystemSafeArea().top_.Length();
434     auto keyboardInsert = safeAreaManager->GetKeyboardInset();
435     auto hasKeyboard = GreatNotEqual(keyboardInsert.Length(), 0.0f);
436     // 顶部避让
437     float positionX = (selectArea.Left() + selectArea.Right() - menuWidth) / 2.0f;
438     auto menuSpacing =
439         static_cast<float>(menuSpacingBetweenText + menuSpacingBetweenHandle);
440     menuPosition =
441         OffsetF(positionX, selectArea.Top() - menuSpacing - menuHeight);
442     if (LessNotEqual(menuPosition.GetY(), topArea)) { // 顶部避让失败,实行底部避让
443         menuPosition = OffsetF(positionX, selectArea.Bottom() + menuSpacing);
444     }
445     menuPosition = OffsetF(positionX, std::max((float)topArea, menuPosition.GetY()));
446     auto viewPort = pipeline->GetRootRect();
447     if ((hasKeyboard && (menuPosition.GetY() + menuHeight) > keyboardInsert.start) ||
448         (menuPosition.GetY() + menuHeight) > viewPort.Bottom()) { // 底部避让失败,实行选中区避让
449         selectArea = selectArea.IntersectRectT(viewPort);
450         menuPosition = OffsetF(positionX, (selectArea.Top() + selectArea.Bottom() - menuHeight) / 2.0f);
451     }
452     return menuPosition;
453 }
454 } // namespace OHOS::Ace::NG