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 <optional>
19
20 #include "base/geometry/ng/offset_t.h"
21 #include "base/utils/string_utils.h"
22 #include "base/utils/utils.h"
23 #include "core/components/text_overlay/text_overlay_theme.h"
24 #include "core/components_ng/pattern/linear_layout/linear_layout_pattern.h"
25 #include "core/components_ng/pattern/linear_layout/linear_layout_property.h"
26 #include "core/pipeline_ng/pipeline_context.h"
27
28 namespace OHOS::Ace::NG {
29 namespace {
30 constexpr Dimension MORE_MENU_INTERVAL = 8.0_vp;
31 }
Layout(LayoutWrapper * layoutWrapper)32 void SelectOverlayLayoutAlgorithm::Layout(LayoutWrapper* layoutWrapper)
33 {
34 auto menu = layoutWrapper->GetOrCreateChildByIndex(0);
35 CHECK_NULL_VOID(menu);
36 if (!CheckInShowArea(*info_) || (!info_->firstHandle.isShow && !info_->secondHandle.isShow)) {
37 menu->SetActive(false);
38 return;
39 } else {
40 menu->SetActive(true);
41 }
42
43 // custom menu need to layout all menu and submenu
44 if (info_->menuInfo.menuBuilder) {
45 for (const auto& child : layoutWrapper->GetAllChildrenWithBuild()) {
46 child->Layout();
47 }
48 return;
49 }
50
51 auto menuOffset = ComputeSelectMenuPosition(layoutWrapper);
52 menu->GetGeometryNode()->SetMarginFrameOffset(menuOffset);
53 menu->Layout();
54 auto button = layoutWrapper->GetOrCreateChildByIndex(1);
55 CHECK_NULL_VOID(button);
56 auto menuNode = menu->GetHostNode();
57 CHECK_NULL_VOID(menuNode);
58 auto menuContext = menuNode->GetRenderContext();
59 CHECK_NULL_VOID(menuContext);
60 auto offset = OffsetF();
61 if (menuContext->GetOffset()) {
62 offset =
63 OffsetF(menuContext->GetOffset()->GetX().ConvertToPx(), menuContext->GetOffset()->GetY().ConvertToPx());
64 }
65 if (!info_->menuInfo.menuIsShow) {
66 hasExtensionMenu_ = false;
67 return;
68 }
69 hasExtensionMenu_ = true;
70 button->GetGeometryNode()->SetMarginFrameOffset(menuOffset);
71 button->Layout();
72 auto extensionMenuOffset = ComputeExtensionMenuPosition(layoutWrapper, offset);
73
74 auto extensionMenu = layoutWrapper->GetOrCreateChildByIndex(2);
75 CHECK_NULL_VOID(extensionMenu);
76 extensionMenu->GetGeometryNode()->SetMarginFrameOffset(extensionMenuOffset);
77 extensionMenu->Layout();
78 }
79
CheckInShowArea(const SelectOverlayInfo & info)80 bool SelectOverlayLayoutAlgorithm::CheckInShowArea(const SelectOverlayInfo& info)
81 {
82 if (info.useFullScreen) {
83 return true;
84 }
85 if (info.isSingleHandle) {
86 return info.firstHandle.paintRect.IsWrappedBy(info.showArea);
87 }
88 return info.firstHandle.paintRect.IsWrappedBy(info.showArea) &&
89 info.secondHandle.paintRect.IsWrappedBy(info.showArea);
90 }
91
ComputeSelectMenuPosition(LayoutWrapper * layoutWrapper)92 OffsetF SelectOverlayLayoutAlgorithm::ComputeSelectMenuPosition(LayoutWrapper* layoutWrapper)
93 {
94 auto menuItem = layoutWrapper->GetOrCreateChildByIndex(0);
95 CHECK_NULL_RETURN(menuItem, OffsetF());
96 auto pipeline = PipelineContext::GetCurrentContext();
97 CHECK_NULL_RETURN(pipeline, OffsetF());
98 auto theme = pipeline->GetTheme<TextOverlayTheme>();
99 CHECK_NULL_RETURN(theme, OffsetF());
100 OffsetF menuPosition;
101 bool isExtension = false;
102
103 // Calculate the spacing with text and handle, menu is fixed up the handle and text.
104 double menuSpacingBetweenText = theme->GetMenuSpacingWithText().ConvertToPx();
105 double menuSpacingBetweenHandle = theme->GetHandleDiameter().ConvertToPx();
106
107 auto width = menuItem->GetGeometryNode()->GetMarginFrameSize().Width();
108 auto height = menuItem->GetGeometryNode()->GetMarginFrameSize().Height();
109
110 // When the extended menu is displayed, the default menu becomes circular, but the position of the circle is aligned
111 // with the end of the original menu.
112 if (GreatNotEqual(width, height)) {
113 menuWidth_ = width;
114 menuHeight_ = height;
115 } else {
116 isExtension = true;
117 }
118 auto menuWidth = menuWidth_.value_or(width);
119 auto menuHeight = menuHeight_.value_or(height);
120
121 // paint rect is in global position, need to convert to local position
122 auto offset = layoutWrapper->GetGeometryNode()->GetFrameOffset();
123 const auto firstHandleRect = info_->firstHandle.paintRect - offset;
124 const auto secondHandleRect = info_->secondHandle.paintRect - offset;
125
126 auto singleHandle = firstHandleRect;
127 if (!info_->firstHandle.isShow) {
128 singleHandle = secondHandleRect;
129 }
130 if (IsTextAreaSelectAll()) {
131 singleHandle = RectF(info_->menuInfo.menuOffset.value().GetX(), info_->menuInfo.menuOffset.value().GetY(),
132 singleHandle.Width(), singleHandle.Height());
133 }
134
135 if (info_->isSingleHandle) {
136 auto menuSpacing = static_cast<float>(menuSpacingBetweenText);
137 menuPosition = OffsetF((singleHandle.Left() + singleHandle.Right() - menuWidth) / 2.0f,
138 static_cast<float>(singleHandle.Top() - menuSpacing - menuHeight));
139 } else {
140 auto menuSpacing = static_cast<float>(menuSpacingBetweenText + menuSpacingBetweenHandle);
141 menuPosition = OffsetF((firstHandleRect.Left() + secondHandleRect.Left() - menuWidth) / 2.0f,
142 static_cast<float>(firstHandleRect.Top() - menuSpacing - menuHeight));
143 }
144
145 auto overlayWidth = layoutWrapper->GetGeometryNode()->GetFrameSize().Width();
146 RectF viewPort = layoutWrapper->GetGeometryNode()->GetFrameRect() - offset;
147 auto frameNode = info_->callerFrameNode.Upgrade();
148 if (frameNode) {
149 auto viewPortOption = frameNode->GetViewPort();
150 if (viewPortOption.has_value()) {
151 viewPort = viewPortOption.value();
152 }
153 }
154 LOGD("select_overlay viewPort Rect: %{public}s", viewPort.ToString().c_str());
155
156 // Adjust position of overlay.
157 if (LessOrEqual(menuPosition.GetX(), viewPort.GetX())) {
158 menuPosition.SetX(theme->GetDefaultMenuPositionX());
159 } else if (GreatOrEqual(menuPosition.GetX() + menuWidth, viewPort.GetX() + viewPort.Width())) {
160 menuPosition.SetX(overlayWidth - menuWidth - theme->GetDefaultMenuPositionX());
161 }
162 if (LessNotEqual(menuPosition.GetY(), menuHeight)) {
163 if (IsTextAreaSelectAll()) {
164 menuPosition.SetY(singleHandle.Top());
165 } else {
166 menuPosition.SetY(
167 static_cast<float>(singleHandle.Bottom() + menuSpacingBetweenText + menuSpacingBetweenHandle));
168 }
169 }
170 if (LessNotEqual(menuPosition.GetY(), viewPort.GetY() - menuSpacingBetweenText - menuHeight) ||
171 LessNotEqual(menuPosition.GetY(), menuSpacingBetweenText)) {
172 auto menuOffsetY = viewPort.GetY() - menuSpacingBetweenText - menuHeight;
173 if (menuOffsetY > menuSpacingBetweenText) {
174 menuPosition.SetY(menuOffsetY);
175 } else {
176 menuPosition.SetY(menuSpacingBetweenText);
177 }
178 } else if (GreatOrEqual(menuPosition.GetY(), viewPort.GetY() + viewPort.Height() + menuSpacingBetweenText)) {
179 menuPosition.SetY(viewPort.GetY() + viewPort.Height() + menuSpacingBetweenText);
180 }
181 LOGD("select_overlay menuPosition: %{public}s", menuPosition.ToString().c_str());
182 defaultMenuEndOffset_ = menuPosition + OffsetF(menuWidth, 0.0f);
183 if (isExtension) {
184 return defaultMenuEndOffset_ - OffsetF(width, 0);
185 }
186 return menuPosition;
187 }
188
ComputeExtensionMenuPosition(LayoutWrapper * layoutWrapper,const OffsetF & offset)189 OffsetF SelectOverlayLayoutAlgorithm::ComputeExtensionMenuPosition(LayoutWrapper* layoutWrapper, const OffsetF& offset)
190 {
191 auto extensionItem = layoutWrapper->GetOrCreateChildByIndex(2);
192 CHECK_NULL_RETURN(extensionItem, OffsetF());
193 auto extensionLayoutConstraint = extensionItem->GetLayoutProperty()->GetLayoutConstraint();
194 auto extensionLayoutConstraintMaxSize = extensionLayoutConstraint->maxSize;
195 auto extensionWidth = extensionItem->GetGeometryNode()->GetMarginFrameSize().Width();
196 auto extensionHeight = extensionItem->GetGeometryNode()->GetMarginFrameSize().Height();
197 auto menuItem = layoutWrapper->GetOrCreateChildByIndex(0);
198 CHECK_NULL_RETURN(menuItem, OffsetF());
199 auto menuHeight = menuItem->GetGeometryNode()->GetMarginFrameSize().Height();
200 auto extensionOffset =
201 defaultMenuEndOffset_ - OffsetF(extensionWidth, -menuHeight - MORE_MENU_INTERVAL.ConvertToPx());
202 if (extensionOffset.GetY() + extensionHeight > extensionLayoutConstraintMaxSize.Height()) {
203 extensionOffset =
204 defaultMenuEndOffset_ - OffsetF(extensionWidth, extensionHeight + MORE_MENU_INTERVAL.ConvertToPx());
205 }
206 return extensionOffset;
207 }
208
IsTextAreaSelectAll()209 bool SelectOverlayLayoutAlgorithm::IsTextAreaSelectAll()
210 {
211 return info_->menuInfo.menuOffset.has_value() && (!info_->firstHandle.isShow || !info_->secondHandle.isShow);
212 }
213
214 } // namespace OHOS::Ace::NG