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