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 
20 #include <optional>
21 
22 #include "base/geometry/ng/offset_t.h"
23 #include "base/utils/string_utils.h"
24 #include "base/utils/utils.h"
25 #include "core/components/text_overlay/text_overlay_theme.h"
26 #include "core/components_ng/pattern/linear_layout/linear_layout_pattern.h"
27 #include "core/components_ng/pattern/linear_layout/linear_layout_property.h"
28 #include "core/components_ng/pattern/select_overlay/select_overlay_pattern.h"
29 #include "core/components_ng/pattern/select_overlay/select_overlay_property.h"
30 #include "core/pipeline_ng/pipeline_context.h"
31 
32 namespace OHOS::Ace::NG {
33 namespace {
34 constexpr Dimension MORE_MENU_INTERVAL = 8.0_vp;
35 constexpr float ROUND_EPSILON = 0.5f;
36 }
37 
Measure(LayoutWrapper * layoutWrapper)38 void SelectOverlayLayoutAlgorithm::Measure(LayoutWrapper* layoutWrapper)
39 {
40     auto host = layoutWrapper->GetHostNode();
41     CHECK_NULL_VOID(host);
42     auto pattern = host->GetPattern<SelectOverlayPattern>();
43     CHECK_NULL_VOID(pattern);
44     if (pattern->GetMode() != SelectOverlayMode::HANDLE_ONLY) {
45         MeasureChild(layoutWrapper);
46     }
47     PerformMeasureSelf(layoutWrapper);
48     // match parent.
49     auto geometryNode = layoutWrapper->GetGeometryNode();
50     CHECK_NULL_VOID(geometryNode);
51     auto frameSize = geometryNode->GetFrameSize();
52     if (LessOrEqual(frameSize.Width(), 0.0f) || LessOrEqual(frameSize.Height(), 0.0f)) {
53         auto host = layoutWrapper->GetHostNode();
54         CHECK_NULL_VOID(host);
55         auto parentNode = host->GetAncestorNodeOfFrame();
56         CHECK_NULL_VOID(parentNode);
57         auto parentGeo = parentNode->GetGeometryNode();
58         CHECK_NULL_VOID(parentGeo);
59         geometryNode->SetFrameSize(parentGeo->GetFrameSize());
60     }
61 }
62 
MeasureChild(LayoutWrapper * layoutWrapper)63 void SelectOverlayLayoutAlgorithm::MeasureChild(LayoutWrapper* layoutWrapper)
64 {
65     auto layoutProperty = layoutWrapper->GetLayoutProperty();
66     CHECK_NULL_VOID(layoutProperty);
67     auto layoutConstraint = layoutProperty->CreateChildConstraint();
68     auto pipeline = PipelineContext::GetCurrentContextSafely();
69     CHECK_NULL_VOID(pipeline);
70     auto safeAreaManager = pipeline->GetSafeAreaManager();
71     CHECK_NULL_VOID(safeAreaManager);
72     auto keyboardHeight = safeAreaManager->IsNeedAvoidWindow() ? 0.0f : safeAreaManager->GetKeyboardInset().Length();
73     auto maxSize =
74         SizeF(layoutConstraint.maxSize.Width(), layoutConstraint.maxSize.Height() - keyboardHeight -
75                                                     (info_->isUsingMouse ? info_->rightClickOffset.GetY() : 0.0f));
76     layoutConstraint.maxSize = maxSize;
77     bool isMouseCustomMenu = false;
78     if (info_->menuInfo.menuBuilder) {
79         auto customMenuLayoutWrapper = layoutWrapper->GetChildByIndex(0);
80         CHECK_NULL_VOID(customMenuLayoutWrapper);
81         auto customNode = customMenuLayoutWrapper->GetHostNode();
82         if (customNode && customNode->GetTag() != "SelectMenu") {
83             auto customMenuLayoutConstraint = layoutConstraint;
84             CalculateCustomMenuLayoutConstraint(layoutWrapper, customMenuLayoutConstraint);
85             customMenuLayoutWrapper->Measure(customMenuLayoutConstraint);
86             isMouseCustomMenu = true;
87         }
88     }
89     auto childIndex = -1;
90     for (const auto& child : layoutWrapper->GetAllChildrenWithBuild()) {
91         childIndex++;
92         if (childIndex == 0 && isMouseCustomMenu) {
93             continue;
94         }
95         child->Measure(layoutConstraint);
96     }
97 }
98 
CalculateCustomMenuLayoutConstraint(LayoutWrapper * layoutWrapper,LayoutConstraintF & layoutConstraint)99 void SelectOverlayLayoutAlgorithm::CalculateCustomMenuLayoutConstraint(
100     LayoutWrapper* layoutWrapper, LayoutConstraintF& layoutConstraint)
101 {
102     auto pipeline = PipelineContext::GetCurrentContextSafely();
103     CHECK_NULL_VOID(pipeline);
104     auto theme = pipeline->GetTheme<TextOverlayTheme>();
105     CHECK_NULL_VOID(theme);
106     // Calculate the spacing with text and handle, menu is fixed up the handle and text.
107     double menuSpacingBetweenText = theme->GetMenuSpacingWithText().ConvertToPx();
108     double menuSpacingBetweenHandle = theme->GetHandleDiameter().ConvertToPx();
109 
110     // paint rect is in global position, need to convert to local position
111     auto offset = layoutWrapper->GetGeometryNode()->GetFrameOffset();
112     const auto firstHandleRect = info_->firstHandle.GetPaintRect() - offset;
113     const auto secondHandleRect = info_->secondHandle.GetPaintRect() - offset;
114 
115     auto top = info_->isNewAvoid ? info_->selectArea.Top() : firstHandleRect.Top();
116     auto bottom = info_->isNewAvoid ? info_->selectArea.Bottom() : secondHandleRect.Bottom();
117     auto topSpace = top - menuSpacingBetweenText - menuSpacingBetweenHandle;
118     auto bottomSpace = layoutConstraint.maxSize.Height() -
119                        (bottom + menuSpacingBetweenText + menuSpacingBetweenHandle);
120     if (info_->isUsingMouse) {
121         layoutConstraint.selfIdealSize = OptionalSizeF(std::nullopt, layoutConstraint.maxSize.Height());
122     } else {
123         layoutConstraint.selfIdealSize = OptionalSizeF(std::nullopt, std::max(topSpace, bottomSpace));
124     }
125 }
126 
CalculateCustomMenuByMouseOffset(LayoutWrapper * layoutWrapper)127 OffsetF SelectOverlayLayoutAlgorithm::CalculateCustomMenuByMouseOffset(LayoutWrapper* layoutWrapper)
128 {
129     auto menuOffset = info_->rightClickOffset;
130     auto layoutProperty = layoutWrapper->GetLayoutProperty();
131     CHECK_NULL_RETURN(layoutProperty, menuOffset);
132     auto layoutConstraint = layoutProperty->GetLayoutConstraint();
133     CHECK_NULL_RETURN(layoutConstraint, menuOffset);
134     auto menu = layoutWrapper->GetOrCreateChildByIndex(0);
135     CHECK_NULL_RETURN(menu, menuOffset);
136     auto maxWidth = layoutConstraint->selfIdealSize.Width().value_or(0.0f);
137     auto menuSize = menu->GetGeometryNode()->GetFrameSize();
138     if (GreatNotEqual(menuOffset.GetX() + menuSize.Width(), maxWidth)) {
139         if (GreatOrEqual(menuOffset.GetX(), menuSize.Width())) {
140             menuOffset.SetX(menuOffset.GetX() - menuSize.Width());
141         } else if (LessOrEqual(menuSize.Width(), maxWidth)) {
142             menuOffset.SetX(maxWidth - menuSize.Width());
143         } else if (GreatNotEqual(menuSize.Width(), maxWidth)) {
144             menuOffset.SetX(menuOffset.GetX() - menuSize.Width() / 2.0f);
145         }
146     }
147     auto maxHeight = layoutConstraint->selfIdealSize.Height().value_or(0.0f);
148     auto pipeline = PipelineContext::GetCurrentContextSafely();
149     CHECK_NULL_RETURN(pipeline, menuOffset);
150     auto safeAreaManager = pipeline->GetSafeAreaManager();
151     CHECK_NULL_RETURN(safeAreaManager, menuOffset);
152     auto keyboardInsert = safeAreaManager->GetKeyboardInset();
153     auto keyboardY = maxHeight - keyboardInsert.Length();
154     uint32_t top = safeAreaManager->GetSystemSafeArea().top_.Length();
155     if (GreatNotEqual(menuOffset.GetY() + menuSize.Height(), keyboardY)) {
156         auto currentY = menuOffset.GetY();
157         if (GreatOrEqual(currentY, menuSize.Height())) {
158             currentY = menuOffset.GetY() - menuSize.Height();
159         } else if (LessOrEqual(menuSize.Height(), keyboardY)) {
160             currentY = keyboardY - menuSize.Height();
161         } else if (GreatNotEqual(menuSize.Height(), keyboardY)) {
162             currentY = menuOffset.GetY() - menuSize.Height() / 2.0f;
163         }
164         if (GreatNotEqual(top, currentY)) {
165             currentY = top;
166         }
167         menuOffset.SetY(currentY);
168     }
169     return menuOffset;
170 }
171 
Layout(LayoutWrapper * layoutWrapper)172 void SelectOverlayLayoutAlgorithm::Layout(LayoutWrapper* layoutWrapper)
173 {
174     auto host = layoutWrapper->GetHostNode();
175     CHECK_NULL_VOID(host);
176     auto pattern = host->GetPattern<SelectOverlayPattern>();
177     CHECK_NULL_VOID(pattern);
178     if (pattern->GetMode() != SelectOverlayMode::HANDLE_ONLY) {
179         CheckHandleIsInClipViewPort();
180         LayoutChild(layoutWrapper, pattern->GetMode());
181     }
182 }
183 
LayoutChild(LayoutWrapper * layoutWrapper,SelectOverlayMode mode)184 void SelectOverlayLayoutAlgorithm::LayoutChild(LayoutWrapper* layoutWrapper, SelectOverlayMode mode)
185 {
186     auto menu = layoutWrapper->GetOrCreateChildByIndex(0);
187     CHECK_NULL_VOID(menu);
188     auto shouldInActiveByHandle =
189         !info_->firstHandle.isShow && !info_->secondHandle.isShow && !info_->isSelectRegionVisible;
190     if ((!CheckInShowArea(*info_) || (!info_->isNewAvoid && shouldInActiveByHandle)) && !info_->isUsingMouse) {
191         menu->SetActive(false);
192         return;
193     }
194     menu->SetActive(true);
195     OffsetF menuOffset;
196     if (info_->isUsingMouse) {
197         menuOffset = CalculateCustomMenuByMouseOffset(layoutWrapper);
198     } else {
199         menuOffset = ComputeSelectMenuPosition(layoutWrapper);
200     }
201     menu->GetGeometryNode()->SetMarginFrameOffset(menuOffset);
202 
203     // custom menu need to layout all menu and submenu
204     if (info_->menuInfo.menuBuilder) {
205         for (const auto& child : layoutWrapper->GetAllChildrenWithBuild()) {
206             child->Layout();
207         }
208         return;
209     }
210 
211     menu->Layout();
212     auto button = layoutWrapper->GetOrCreateChildByIndex(1);
213     CHECK_NULL_VOID(button);
214     if ((!info_->menuInfo.menuIsShow || info_->menuInfo.menuDisable) && mode != SelectOverlayMode::MENU_ONLY) {
215         hasExtensionMenu_ = false;
216         return;
217     }
218     hasExtensionMenu_ = true;
219     auto buttonSize = button->GetGeometryNode()->GetMarginFrameSize();
220     auto menuSize = menu->GetGeometryNode()->GetMarginFrameSize();
221     bool isReverse = IsReverseLayout(layoutWrapper);
222     OffsetF buttonOffset;
223     if (GreatNotEqual(menuSize.Width(), menuSize.Height())) {
224         auto diffY = std::max((menuSize.Height() - buttonSize.Height()) / 2.0f, 0.0f);
225         buttonOffset = isReverse ? OffsetF(menuOffset.GetX(), menuOffset.GetY() + diffY) :
226             OffsetF(menuOffset.GetX() + menuSize.Width() - buttonSize.Width(), menuOffset.GetY() + diffY);
227     } else {
228         buttonOffset = menuOffset;
229     }
230     button->GetGeometryNode()->SetMarginFrameOffset(buttonOffset);
231     button->Layout();
232 
233     LayoutExtensionMenu(layoutWrapper, button);
234 }
235 
LayoutExtensionMenu(LayoutWrapper * layoutWrapper,const RefPtr<LayoutWrapper> & button)236 void SelectOverlayLayoutAlgorithm::LayoutExtensionMenu(
237     LayoutWrapper* layoutWrapper, const RefPtr<LayoutWrapper>& button)
238 {
239     auto host = layoutWrapper->GetHostNode();
240     CHECK_NULL_VOID(host);
241     auto selectOverlayNode = DynamicCast<SelectOverlayNode>(host);
242     CHECK_NULL_VOID(selectOverlayNode);
243     // Avoid menu layout while the back animation is playing.
244     if (!selectOverlayNode->GetIsExtensionMenu() && selectOverlayNode->GetAnimationStatus()) {
245         return;
246     }
247     auto extensionMenu = layoutWrapper->GetOrCreateChildByIndex(2);
248     CHECK_NULL_VOID(extensionMenu);
249     extensionMenu->Layout();
250     CheckHideBackOrMoreButton(extensionMenu, button);
251 }
252 
CheckHideBackOrMoreButton(const RefPtr<LayoutWrapper> & extensionMenu,const RefPtr<LayoutWrapper> & button)253 void SelectOverlayLayoutAlgorithm::CheckHideBackOrMoreButton(
254     const RefPtr<LayoutWrapper>& extensionMenu, const RefPtr<LayoutWrapper>& button)
255 {
256     auto extensionMenuRect = extensionMenu->GetGeometryNode()->GetFrameRect();
257     auto buttonRect = button->GetGeometryNode()->GetFrameRect();
258     auto constraintRect = extensionMenuRect.Constrain(buttonRect);
259     if (GreatNotEqual(constraintRect.Width(), 0.0f) && GreatNotEqual(constraintRect.Height(), 0.0f)) {
260         hideMoreOrBack_ = true;
261     }
262 }
263 
CheckInShowArea(const SelectOverlayInfo & info)264 bool SelectOverlayLayoutAlgorithm::CheckInShowArea(const SelectOverlayInfo& info)
265 {
266     if (info.useFullScreen) {
267         return true;
268     }
269     if (info.isSingleHandle) {
270         return info.firstHandle.GetPaintRect().IsWrappedBy(info.showArea);
271     }
272     return info.firstHandle.GetPaintRect().IsWrappedBy(info.showArea) &&
273            info.secondHandle.GetPaintRect().IsWrappedBy(info.showArea);
274 }
275 
ComputeSelectMenuPosition(LayoutWrapper * layoutWrapper)276 OffsetF SelectOverlayLayoutAlgorithm::ComputeSelectMenuPosition(LayoutWrapper* layoutWrapper)
277 {
278     auto menuItem = layoutWrapper->GetOrCreateChildByIndex(0);
279     CHECK_NULL_RETURN(menuItem, OffsetF());
280     auto pipeline = PipelineContext::GetCurrentContextSafely();
281     CHECK_NULL_RETURN(pipeline, OffsetF());
282     auto theme = pipeline->GetTheme<TextOverlayTheme>();
283     CHECK_NULL_RETURN(theme, OffsetF());
284     OffsetF menuPosition;
285     bool isExtension = false;
286     bool isReverse = IsReverseLayout(layoutWrapper);
287 
288     // Calculate the spacing with text and handle, menu is fixed up the handle and text.
289     double menuSpacingBetweenText = theme->GetMenuSpacingWithText().ConvertToPx();
290     double menuSpacingBetweenHandle = theme->GetHandleDiameter().ConvertToPx();
291 
292     // When the extended menu is displayed, the default menu becomes circular, but the position of the circle is aligned
293     // with the end of the original menu.
294     auto width = menuItem->GetGeometryNode()->GetMarginFrameSize().Width();
295     auto height = menuItem->GetGeometryNode()->GetMarginFrameSize().Height();
296     auto backButton = layoutWrapper->GetOrCreateChildByIndex(1);
297     bool isBackButtonVisible = false;
298     if (backButton) {
299         isBackButtonVisible =
300             backButton->GetLayoutProperty()->GetVisibilityValue(VisibleType::INVISIBLE) == VisibleType::VISIBLE;
301     }
302     auto host = layoutWrapper->GetHostNode();
303     CHECK_NULL_RETURN(host, OffsetF());
304     auto selectOverlayNode = DynamicCast<SelectOverlayNode>(host);
305     CHECK_NULL_RETURN(selectOverlayNode, OffsetF());
306     auto isExtensionMEnu = selectOverlayNode->GetIsExtensionMenu();
307     if (!isBackButtonVisible && !isExtensionMEnu) {
308         menuWidth_ = width;
309         menuHeight_ = height;
310     } else {
311         isExtension = true;
312     }
313     auto menuWidth = menuWidth_.value_or(width);
314     auto menuHeight = menuHeight_.value_or(height);
315 
316     // paint rect is in global position, need to convert to local position
317     auto offset = layoutWrapper->GetGeometryNode()->GetFrameOffset();
318     const auto firstHandleRect = info_->firstHandle.GetPaintRect() - offset;
319     const auto secondHandleRect = info_->secondHandle.GetPaintRect() - offset;
320 
321     auto singleHandle = firstHandleRect;
322     if (!info_->firstHandle.isShow) {
323         singleHandle = secondHandleRect;
324     }
325     if (IsTextAreaSelectAll()) {
326         singleHandle = RectF(info_->menuInfo.menuOffset.value().GetX(), info_->menuInfo.menuOffset.value().GetY(),
327             singleHandle.Width(), singleHandle.Height());
328     }
329 
330     if (info_->isSingleHandle) {
331         auto menuSpacing = static_cast<float>(menuSpacingBetweenText);
332         menuPosition = OffsetF((singleHandle.Left() + singleHandle.Right() - menuWidth) / 2.0f,
333             static_cast<float>(singleHandle.Top() - menuSpacing - menuHeight));
334     } else {
335         auto menuSpacing = static_cast<float>(menuSpacingBetweenText + menuSpacingBetweenHandle);
336         menuPosition = OffsetF((firstHandleRect.Left() + secondHandleRect.Left() - menuWidth) / 2.0f,
337             static_cast<float>(firstHandleRect.Top() - menuSpacing - menuHeight));
338 
339         if (!info_->firstHandle.isShow && info_->secondHandle.isShow && !info_->handleReverse) {
340             menuPosition.SetY(secondHandleRect.Bottom() + menuSpacing);
341         }
342         if (info_->firstHandle.isShow && !info_->secondHandle.isShow && info_->handleReverse) {
343             menuPosition.SetY(firstHandleRect.Bottom() + menuSpacing);
344         }
345         if (info_->firstHandle.isShow && info_->secondHandle.isShow &&
346             !NearEqual(firstHandleRect.Top(), secondHandleRect.Top())) {
347             auto top = std::min(firstHandleRect.Top(), secondHandleRect.Top());
348             menuPosition.SetY(static_cast<float>(top - menuSpacing - menuHeight));
349         }
350         if (!info_->firstHandle.isShow && info_->secondHandle.isShow) {
351             menuPosition.SetX(secondHandleRect.Left() - menuWidth / 2.0f);
352         }
353         if (info_->firstHandle.isShow && !info_->secondHandle.isShow) {
354             menuPosition.SetX(firstHandleRect.Left() - menuWidth / 2.0f);
355         }
356     }
357 
358     auto overlayWidth = layoutWrapper->GetGeometryNode()->GetFrameSize().Width();
359     RectF viewPort = layoutWrapper->GetGeometryNode()->GetFrameRect() - offset;
360     auto overlayVP = viewPort;
361     info_->GetCallerNodeAncestorViewPort(viewPort);
362     // Adjust position of overlay.
363     auto adjustPositionXWithViewPort = [&](OffsetF& menuPosition) {
364         auto defaultMenuPositionX = theme->GetDefaultMenuPositionX();
365         if (LessOrEqual(menuPosition.GetX(), defaultMenuPositionX)) {
366             menuPosition.SetX(defaultMenuPositionX);
367         } else if (GreatOrEqual(
368             menuPosition.GetX() + menuWidth, overlayVP.GetX() + overlayVP.Width() - defaultMenuPositionX)) {
369             menuPosition.SetX(overlayWidth - menuWidth - defaultMenuPositionX);
370         }
371     };
372     adjustPositionXWithViewPort(menuPosition);
373     auto safeAreaManager = pipeline->GetSafeAreaManager();
374     if (LessNotEqual(menuPosition.GetY(), menuHeight)) {
375         if (IsTextAreaSelectAll()) {
376             menuPosition.SetY(singleHandle.Top());
377         } else if (info_->isSingleHandle &&
378             IsMenuAreaSmallerHandleArea(singleHandle, menuHeight, menuSpacingBetweenText)) {
379             if (safeAreaManager && safeAreaManager->GetSystemSafeArea().top_.Length() > singleHandle.Top()) {
380                 menuPosition.SetY(
381                     static_cast<float>(singleHandle.Bottom() + menuSpacingBetweenText + menuSpacingBetweenHandle));
382             }
383         } else {
384             menuPosition.SetY(
385                 static_cast<float>(singleHandle.Bottom() + menuSpacingBetweenText + menuSpacingBetweenHandle));
386         }
387     }
388     auto spaceBetweenViewPort = menuSpacingBetweenText + menuSpacingBetweenHandle;
389     if (LessNotEqual(menuPosition.GetY(), viewPort.GetY() - spaceBetweenViewPort - menuHeight) ||
390         LessNotEqual(menuPosition.GetY(), menuSpacingBetweenText)) {
391         auto menuOffsetY = viewPort.GetY() - spaceBetweenViewPort - menuHeight;
392         if (menuOffsetY > menuSpacingBetweenText) {
393             menuPosition.SetY(menuOffsetY);
394         } else {
395             menuPosition.SetY(menuSpacingBetweenText);
396         }
397     } else if (GreatOrEqual(menuPosition.GetY(), viewPort.GetY() + viewPort.Height() + spaceBetweenViewPort)) {
398         menuPosition.SetY(viewPort.GetY() + viewPort.Height() + spaceBetweenViewPort);
399     }
400 
401     if (safeAreaManager && !(info_->isSingleHandle &&
402         IsMenuAreaSmallerHandleArea(singleHandle, menuHeight, menuSpacingBetweenText))) {
403         // ignore status bar
404         auto top = safeAreaManager->GetSystemSafeArea().top_.Length();
405         if (menuPosition.GetY() < top) {
406             menuPosition.SetY(top);
407         }
408     }
409     if (info_->firstHandle.isShow && info_->secondHandle.isShow &&
410         !NearEqual(firstHandleRect.Top(), secondHandleRect.Top())) {
411         auto menuRect = RectF(menuPosition, SizeF(menuWidth, menuHeight));
412         auto downHandleRect =
413             LessNotEqual(firstHandleRect.Top(), secondHandleRect.Top()) ? secondHandleRect : firstHandleRect;
414         auto circleDiameter = menuSpacingBetweenHandle;
415         auto circleOffset =
416             OffsetF(downHandleRect.GetX() - (circleDiameter - downHandleRect.Width()) / 2.0f, downHandleRect.Bottom());
417         auto downHandleCircleRect = RectF(circleOffset, SizeF(circleDiameter, circleDiameter));
418         if (menuRect.IsIntersectWith(downHandleRect) || menuRect.IsInnerIntersectWith(downHandleCircleRect)) {
419             auto menuSpacing = static_cast<float>(menuSpacingBetweenText + circleDiameter);
420             menuPosition.SetY(downHandleRect.Bottom() + menuSpacing);
421         }
422     }
423     auto menuRect = RectF(menuPosition, SizeF(menuWidth, menuHeight));
424     menuPosition =
425         info_->isNewAvoid && !info_->isSingleHandle
426             ? NewMenuAvoidStrategy(layoutWrapper, menuWidth, menuHeight)
427             : AdjustSelectMenuOffset(layoutWrapper, menuRect, menuSpacingBetweenText, menuSpacingBetweenHandle);
428     AdjustMenuInRootRect(menuPosition, menuRect.GetSize(), layoutWrapper->GetGeometryNode()->GetFrameSize());
429 
430     defaultMenuStartOffset_ = menuPosition;
431     defaultMenuEndOffset_ = menuPosition + OffsetF(menuWidth, 0.0f);
432     // back and more button layout is on the left side of the selectmenu when reverse layout.
433     if (isExtension && !isReverse) {
434         return defaultMenuEndOffset_ - OffsetF(width, 0);
435     }
436     return menuPosition;
437 }
438 
AdjustMenuInRootRect(OffsetF & menuOffset,const SizeF & menuSize,const SizeF & rootSize)439 void SelectOverlayLayoutAlgorithm::AdjustMenuInRootRect(
440     OffsetF& menuOffset, const SizeF& menuSize, const SizeF& rootSize)
441 {
442     auto pipeline = PipelineContext::GetCurrentContextSafely();
443     CHECK_NULL_VOID(pipeline);
444     auto theme = pipeline->GetTheme<TextOverlayTheme>();
445     CHECK_NULL_VOID(theme);
446     // adjust x
447     auto defaultPositionX = theme->GetDefaultMenuPositionX();
448     auto menuX = LessOrEqual(menuOffset.GetX(), defaultPositionX) ? defaultPositionX : menuOffset.GetX();
449     menuX = GreatOrEqual(menuX + menuSize.Width(), rootSize.Width() - defaultPositionX)
450                 ? rootSize.Width() - defaultPositionX - menuSize.Width()
451                 : menuX;
452     menuOffset.SetX(menuX);
453     // adjust y
454     auto menuY = LessNotEqual(menuOffset.GetY(), 0.0f) ? 0.0f : menuOffset.GetY();
455     menuY = GreatNotEqual(menuY + menuSize.Height(), rootSize.Height()) ? rootSize.Height() - menuSize.Height() : menuY;
456     menuOffset.SetY(menuY);
457 }
458 
AdjustSelectMenuOffset(LayoutWrapper * layoutWrapper,const RectF & menuRect,double spaceBetweenText,double spaceBetweenHandle)459 OffsetF SelectOverlayLayoutAlgorithm::AdjustSelectMenuOffset(
460     LayoutWrapper* layoutWrapper, const RectF& menuRect, double spaceBetweenText, double spaceBetweenHandle)
461 {
462     auto menuOffset = menuRect.GetOffset();
463     CHECK_NULL_RETURN((info_->firstHandle.isShow || info_->secondHandle.isShow),
464         AdjustSelectMenuOffsetWhenHandlesUnshown(menuRect, spaceBetweenText));
465     auto offset = layoutWrapper->GetGeometryNode()->GetFrameOffset();
466     auto upHandle = info_->handleReverse ? info_->secondHandle : info_->firstHandle;
467     auto downHandle = info_->handleReverse ? info_->firstHandle : info_->secondHandle;
468     AdjustMenuTooFarAway(menuOffset, menuRect);
469     // menu cover up handle
470     auto upPaint = upHandle.GetPaintRect() - offset;
471     auto downPaint = downHandle.GetPaintRect() - offset;
472     if (!info_->isSingleHandle && upHandle.isShow && !downHandle.isShow) {
473         auto circleOffset = OffsetF(
474             upPaint.GetX() - (spaceBetweenHandle - upPaint.Width()) / 2.0f, upPaint.GetY() - spaceBetweenHandle);
475         auto upCircleRect = RectF(circleOffset, SizeF(spaceBetweenHandle, spaceBetweenHandle));
476         if (menuRect.IsIntersectWith(upPaint) || menuRect.IsIntersectWith(upCircleRect)) {
477             menuOffset.SetY(upPaint.Bottom() + spaceBetweenText + spaceBetweenHandle);
478         }
479         return menuOffset;
480     }
481     // avoid soft keyboard and root bottom
482     if ((!upHandle.isShow && downHandle.isShow) || info_->menuInfo.menuBuilder) {
483         auto pipeline = PipelineContext::GetCurrentContextSafely();
484         CHECK_NULL_RETURN(pipeline, menuOffset);
485         auto safeAreaManager = pipeline->GetSafeAreaManager();
486         CHECK_NULL_RETURN(safeAreaManager, menuOffset);
487         auto keyboardInsert = safeAreaManager->GetKeyboardInset();
488         auto shouldAvoidKeyboard =
489             GreatNotEqual(keyboardInsert.Length(), 0.0f) && GreatNotEqual(menuRect.Bottom(), keyboardInsert.start);
490         auto rootRect = layoutWrapper->GetGeometryNode()->GetFrameRect();
491         auto shouldAvoidBottom = GreatNotEqual(menuRect.Bottom(), rootRect.Height());
492         auto menuSpace = NearEqual(upPaint.Top(), downPaint.Top()) ? spaceBetweenHandle : spaceBetweenText;
493         auto offsetY = downPaint.GetY() - menuSpace - menuRect.Height();
494         auto topArea = safeAreaManager->GetSystemSafeArea().top_.Length();
495         if ((shouldAvoidKeyboard || shouldAvoidBottom) && offsetY > 0) {
496             if (topArea > offsetY) {
497                 offsetY = downPaint.Bottom() - spaceBetweenText - menuRect.Height();
498             }
499             menuOffset.SetY(offsetY);
500         } else {
501             if (topArea > menuOffset.GetY() && info_->isSingleHandle) {
502                 menuOffset.SetY(downPaint.Bottom() + spaceBetweenText + spaceBetweenHandle);
503             }
504             AdjustMenuOffsetAtSingleHandleBottom(downPaint, menuRect, menuOffset, spaceBetweenText);
505         }
506     }
507     return menuOffset;
508 }
509 
AdjustMenuOffsetAtSingleHandleBottom(const RectF handleRect,const RectF & menuRect,OffsetF & menuOffset,double spaceBetweenText)510 void SelectOverlayLayoutAlgorithm::AdjustMenuOffsetAtSingleHandleBottom(const RectF handleRect, const RectF& menuRect,
511     OffsetF& menuOffset, double spaceBetweenText)
512 {
513     CHECK_NULL_VOID(info_->isSingleHandle);
514     auto pipeline = PipelineContext::GetCurrentContextSafely();
515     CHECK_NULL_VOID(pipeline);
516     auto safeAreaManager = pipeline->GetSafeAreaManager();
517     CHECK_NULL_VOID(safeAreaManager);
518     auto keyboardInsert = safeAreaManager->GetKeyboardInset();
519     auto shouldAvoidKeyboard = GreatNotEqual(keyboardInsert.Length(), 0.0f) &&
520                                GreatNotEqual(menuOffset.GetY() + menuRect.Height(), keyboardInsert.start);
521     if (shouldAvoidKeyboard) {
522         menuOffset.SetY(handleRect.Bottom() - spaceBetweenText - menuRect.Height());
523     }
524 }
525 
AdjustSelectMenuOffsetWhenHandlesUnshown(const RectF & menuRect,double spaceBetweenText)526 OffsetF SelectOverlayLayoutAlgorithm::AdjustSelectMenuOffsetWhenHandlesUnshown(const RectF& menuRect,
527     double spaceBetweenText)
528 {
529     auto menuOffset = menuRect.GetOffset();
530     CHECK_NULL_RETURN(info_->isSingleHandle, menuOffset);
531     auto pipeline = PipelineContext::GetCurrentContextSafely();
532     CHECK_NULL_RETURN(pipeline, menuOffset);
533     auto safeAreaManager = pipeline->GetSafeAreaManager();
534     CHECK_NULL_RETURN(safeAreaManager, menuOffset);
535     auto topArea = safeAreaManager->GetSystemSafeArea().top_.Length();
536     auto selectArea = info_->selectArea;
537     if (topArea > menuOffset.GetY()) {
538         menuOffset.SetY((selectArea.Top() + selectArea.Bottom() - menuRect.Height()) / 2.0f);
539         return menuOffset;
540     }
541     auto keyboardInsert = safeAreaManager->GetKeyboardInset();
542     auto shouldAvoidKeyboard = GreatNotEqual(keyboardInsert.Length(), 0.0f) &&
543         GreatNotEqual(menuRect.Bottom(), keyboardInsert.start);
544     auto isBottomTouchKeyboard = GreatNotEqual(keyboardInsert.Length(), 0.0f) &&
545         GreatNotEqual(selectArea.Bottom(), keyboardInsert.start);
546     if (!isBottomTouchKeyboard && shouldAvoidKeyboard) {
547         menuOffset.SetY(selectArea.Bottom() - spaceBetweenText - menuRect.Height());
548         return menuOffset;
549     }
550     if (shouldAvoidKeyboard) {
551         menuOffset.SetY((selectArea.Top() + selectArea.Bottom() - menuRect.Height()) / 2.0f);
552     }
553     return menuOffset;
554 }
555 
IsMenuAreaSmallerHandleArea(RectF handleRect,float menuHeight,float menuDistance)556 bool SelectOverlayLayoutAlgorithm::IsMenuAreaSmallerHandleArea(RectF handleRect, float menuHeight, float menuDistance)
557 {
558     return handleRect.Height() > menuHeight + menuDistance;
559 }
560 
AdjustMenuTooFarAway(OffsetF & menuOffset,const RectF & menuRect)561 void SelectOverlayLayoutAlgorithm::AdjustMenuTooFarAway(OffsetF& menuOffset, const RectF& menuRect)
562 {
563     // the menu is too far away.
564     auto hostFrameNode = info_->callerFrameNode.Upgrade();
565     CHECK_NULL_VOID(hostFrameNode);
566     auto pipeline = hostFrameNode->GetContext();
567     CHECK_NULL_VOID(pipeline);
568     auto hostFrameRect = hostFrameNode->GetGeometryNode()->GetFrameRect();
569     auto hostGlobalOffset = hostFrameNode->GetPaintRectOffset() - pipeline->GetRootRect().GetOffset();
570     auto centerX = menuRect.Width() / 2.0f;
571     if (info_->callerNodeInfo) {
572         hostFrameRect = info_->callerNodeInfo->paintFrameRect;
573         hostGlobalOffset = info_->callerNodeInfo->paintOffset;
574     }
575     if (GreatNotEqual(menuRect.GetX() + centerX, hostGlobalOffset.GetX() + hostFrameRect.Width())) {
576         menuOffset.SetX(hostGlobalOffset.GetX() + hostFrameRect.Width() - centerX);
577         return;
578     }
579     if (LessNotEqual(menuRect.GetX() + centerX, hostGlobalOffset.GetX())) {
580         menuOffset.SetX(hostGlobalOffset.GetX() - centerX);
581     }
582 }
583 
ComputeExtensionMenuPosition(LayoutWrapper * layoutWrapper,const OffsetF & offset)584 OffsetF SelectOverlayLayoutAlgorithm::ComputeExtensionMenuPosition(LayoutWrapper* layoutWrapper, const OffsetF& offset)
585 {
586     auto extensionItem = layoutWrapper->GetOrCreateChildByIndex(2);
587     CHECK_NULL_RETURN(extensionItem, OffsetF());
588     auto extensionLayoutConstraint = extensionItem->GetLayoutProperty()->GetLayoutConstraint();
589     auto extensionLayoutConstraintMaxSize = extensionLayoutConstraint->maxSize;
590     auto extensionWidth = extensionItem->GetGeometryNode()->GetMarginFrameSize().Width();
591     auto extensionHeight = extensionItem->GetGeometryNode()->GetMarginFrameSize().Height();
592     auto menuItem = layoutWrapper->GetOrCreateChildByIndex(0);
593     CHECK_NULL_RETURN(menuItem, OffsetF());
594     auto menuHeight = menuItem->GetGeometryNode()->GetMarginFrameSize().Height();
595     auto extensionOffset =
596         defaultMenuEndOffset_ - OffsetF(extensionWidth, -menuHeight - MORE_MENU_INTERVAL.ConvertToPx());
597     auto extensionBottom = extensionOffset.GetY() + extensionHeight;
598     auto isCoveredBySoftKeyBoard = [extensionBottom]() -> bool {
599         auto pipeline = PipelineContext::GetCurrentContextSafely();
600         CHECK_NULL_RETURN(pipeline, false);
601         auto safeAreaManager = pipeline->GetSafeAreaManager();
602         CHECK_NULL_RETURN(safeAreaManager, false);
603         auto keyboardInsert = safeAreaManager->GetKeyboardInset();
604         return GreatNotEqual(keyboardInsert.Length(), 0.0f) && GreatNotEqual(extensionBottom, keyboardInsert.start);
605     };
606     if (GreatNotEqual(extensionBottom, extensionLayoutConstraintMaxSize.Height()) || isCoveredBySoftKeyBoard()) {
607         extensionOffset =
608             defaultMenuEndOffset_ - OffsetF(extensionWidth, extensionHeight + MORE_MENU_INTERVAL.ConvertToPx());
609     }
610     return extensionOffset;
611 }
612 
IsTextAreaSelectAll()613 bool SelectOverlayLayoutAlgorithm::IsTextAreaSelectAll()
614 {
615     return info_->menuInfo.menuOffset.has_value() && (!info_->firstHandle.isShow || !info_->secondHandle.isShow);
616 }
617 
NewMenuAvoidStrategy(LayoutWrapper * layoutWrapper,float menuWidth,float menuHeight)618 OffsetF SelectOverlayLayoutAlgorithm::NewMenuAvoidStrategy(
619     LayoutWrapper* layoutWrapper, float menuWidth, float menuHeight)
620 {
621     auto pipeline = PipelineContext::GetCurrentContextSafely();
622     CHECK_NULL_RETURN(pipeline, OffsetF());
623     auto theme = pipeline->GetTheme<TextOverlayTheme>();
624     CHECK_NULL_RETURN(theme, OffsetF());
625     double menuSpacingBetweenText = theme->GetMenuSpacingWithText().ConvertToPx();
626     double menuSpacingBetweenHandle = theme->GetHandleDiameter().ConvertToPx() +
627                                       theme->GetHandleDiameterStrokeWidth().ConvertToPx();
628     double safeSpacing = theme->GetMenuSafeSpacing().ConvertToPx();
629     auto selectArea = info_->selectArea;
630     // 安全区域
631     auto safeAreaManager = pipeline->GetSafeAreaManager();
632     CHECK_NULL_RETURN(safeAreaManager, OffsetF());
633     auto topArea = safeAreaManager->GetSystemSafeArea().top_.Length();
634     auto keyboardInsert = safeAreaManager->GetKeyboardInset();
635     float positionX = (selectArea.Left() + selectArea.Right() - menuWidth) / 2.0f;
636     auto hasKeyboard = !safeAreaManager->IsNeedAvoidWindow() && GreatNotEqual(keyboardInsert.Length(), 0.0f);
637     auto downHandle = info_->handleReverse ? info_->firstHandle : info_->secondHandle;
638     auto downHandleIsReallyShow = hasKeyboard ? ((LessOrEqual((double)downHandle.paintRect.Bottom(),
639         (double)keyboardInsert.start)) ? true : false) : downHandle.isShow;
640     auto upHandle = info_->handleReverse ? info_->secondHandle : info_->firstHandle;
641     auto offset = layoutWrapper->GetGeometryNode()->GetFrameOffset();
642     auto upPaint = upHandle.GetPaintRect() - offset;
643     auto downPaint = downHandle.GetPaintRect() - offset;
644     auto viewPort = pipeline->GetRootRect();
645     auto selectAndRootRectArea = selectArea.IntersectRectT(viewPort);
646     auto safeAreaBottom = safeAreaManager->GetSafeAreaWithoutProcess().bottom_.start;
647     auto menuAvoidBottomY = GreatNotEqual(safeAreaBottom, 0.0f) ? (safeAreaBottom - menuHeight)
648         : (viewPort.Bottom() - menuHeight);
649     auto bottomLimitOffsetY = hasKeyboard ? std::max(keyboardInsert.start - safeSpacing - menuHeight, (double)topArea)
650         : menuAvoidBottomY;
651 
652     AvoidStrategyMember avoidStrategyMember;
653     avoidStrategyMember.menuHeight = menuHeight;
654     avoidStrategyMember.menuSpacingBetweenText = menuSpacingBetweenText;
655     avoidStrategyMember.bottomLimitOffsetY = bottomLimitOffsetY;
656     avoidStrategyMember.menuSpacing = static_cast<float>(menuSpacingBetweenText + menuSpacingBetweenHandle);
657     avoidStrategyMember.hasKeyboard = GreatNotEqual(keyboardInsert.Length(), 0.0f);
658     avoidStrategyMember.keyboardInsertStart = keyboardInsert.start;
659     avoidStrategyMember.downHandleIsReallyShow = downHandle.isShow && downHandleIsReallyShow;
660     avoidStrategyMember.selectAreaTop = selectArea.Top();
661     avoidStrategyMember.selectAndRootRectAreaTop = upHandle.isShow ? upPaint.Top() : selectAndRootRectArea.Top();
662     avoidStrategyMember.selectAndRootRectAreaBottom =
663         avoidStrategyMember.downHandleIsReallyShow ? downPaint.Bottom() : selectAndRootRectArea.Bottom();
664     float offsetY = 0.0f;
665     NewMenuAvoidStrategyGetY(avoidStrategyMember, offsetY);
666     return OffsetF(positionX, offsetY);
667 }
668 
NewMenuAvoidStrategyGetY(const AvoidStrategyMember & avoidStrategyMember,float & offsetY)669 void SelectOverlayLayoutAlgorithm::NewMenuAvoidStrategyGetY(const AvoidStrategyMember& avoidStrategyMember,
670                                                             float& offsetY)
671 {
672     auto pipeline = PipelineContext::GetCurrentContextSafely();
673     CHECK_NULL_VOID(pipeline);
674     auto safeAreaManager = pipeline->GetSafeAreaManager();
675     CHECK_NULL_VOID(safeAreaManager);
676     auto topArea = safeAreaManager->GetSystemSafeArea().top_.Length();
677     auto upHandle = info_->handleReverse ? info_->secondHandle : info_->firstHandle;
678     // 顶部避让
679     offsetY = upHandle.isShow ? (avoidStrategyMember.selectAreaTop - avoidStrategyMember.menuSpacing -
680                   avoidStrategyMember.menuHeight) : (avoidStrategyMember.selectAreaTop -
681                   avoidStrategyMember.menuSpacingBetweenText - avoidStrategyMember.menuHeight);
682     if (!upHandle.isShow || LessOrEqual(offsetY, topArea)) {
683         auto selectBottom = avoidStrategyMember.hasKeyboard ? std::min(avoidStrategyMember.selectAndRootRectAreaBottom,
684             (double)avoidStrategyMember.keyboardInsertStart) : avoidStrategyMember.selectAndRootRectAreaBottom;
685         auto offsetBetweenSelectArea =
686             std::clamp((double)(avoidStrategyMember.selectAndRootRectAreaTop + selectBottom -
687                 avoidStrategyMember.menuHeight) / 2.0f, (double)topArea, avoidStrategyMember.bottomLimitOffsetY);
688         auto offsetYTmp = avoidStrategyMember.downHandleIsReallyShow ?
689                                   (avoidStrategyMember.selectAndRootRectAreaBottom + avoidStrategyMember.menuSpacing) :
690                                   (avoidStrategyMember.selectAndRootRectAreaBottom +
691                                   avoidStrategyMember.menuSpacingBetweenText);
692         if (avoidStrategyMember.downHandleIsReallyShow) {
693             bool isOffsetYInBottom = false;
694             // The upper handle is not visible and not in a single row, or offsetY <= topArea
695             if ((!upHandle.isShow && !info_->isSingleLine) || (LessOrEqual(offsetY, topArea))) {
696                 offsetY = offsetYTmp;
697                 isOffsetYInBottom = true;
698             }
699             if (isOffsetYInBottom && GreatNotEqual(offsetY, avoidStrategyMember.bottomLimitOffsetY)) {
700                 offsetY = offsetBetweenSelectArea;
701             }
702         } else {
703             if (info_->isSingleLine) {
704                 offsetY = LessOrEqual(offsetY, topArea) ?
705                     ((GreatNotEqual(offsetYTmp, avoidStrategyMember.bottomLimitOffsetY)) ?
706                         offsetBetweenSelectArea : offsetYTmp) : offsetY;
707             } else {
708                 offsetY = offsetBetweenSelectArea;
709             }
710         }
711     }
712     if (avoidStrategyMember.hasKeyboard && GreatNotEqual(offsetY, avoidStrategyMember.bottomLimitOffsetY)) {
713         offsetY = avoidStrategyMember.bottomLimitOffsetY;
714     }
715 }
716 
IsReverseLayout(LayoutWrapper * layoutWrapper) const717 bool SelectOverlayLayoutAlgorithm::IsReverseLayout(LayoutWrapper* layoutWrapper) const
718 {
719     CHECK_NULL_RETURN(layoutWrapper, false);
720     auto layoutProperty = layoutWrapper->GetLayoutProperty();
721     CHECK_NULL_RETURN(layoutProperty, false);
722     return layoutProperty->GetNonAutoLayoutDirection() == TextDirection::RTL;
723 }
724 
CheckHandleIsInClipViewPort()725 void SelectOverlayLayoutAlgorithm::CheckHandleIsInClipViewPort()
726 {
727     if (!info_->clipHandleDrawRect || info_->secondHandle.isPaintHandleWithPoints ||
728         info_->handleLevelMode == HandleLevelMode::EMBED) {
729         return;
730     }
731     if (!info_->isSingleHandle) {
732         RectF viewPort;
733         info_->GetCallerNodeAncestorViewPort(viewPort);
734         auto isInRegion = [](const RectF& viewPort, float left, float right, float verticalY) {
735             return LessOrEqual(left, viewPort.Right()) &&
736                    GreatOrEqual(right, viewPort.Left()) &&
737                    GreatOrEqual(verticalY, viewPort.Top() - ROUND_EPSILON) &&
738                    LessOrEqual(verticalY, viewPort.Bottom() + ROUND_EPSILON);
739         };
740         auto& handleOnTop = !info_->handleReverse ? info_->firstHandle : info_->secondHandle;
741         handleOnTop.isShow = isInRegion(
742             viewPort, handleOnTop.paintRect.Left(), handleOnTop.paintRect.Right(), handleOnTop.paintRect.Top());
743         auto& handleOnBottom = !info_->handleReverse ? info_->secondHandle : info_->firstHandle;
744         handleOnBottom.isShow = isInRegion(viewPort, handleOnBottom.paintRect.Left(), handleOnBottom.paintRect.Right(),
745             handleOnBottom.paintRect.Bottom());
746     }
747 }
748 } // namespace OHOS::Ace::NG