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