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/manager/select_overlay/select_overlay_manager.h"
17
18 #include <memory>
19
20 #include "base/utils/utils.h"
21 #include "core/common/container.h"
22 #include "core/components_ng/pattern/pattern.h"
23 #include "core/components_ng/pattern/select_overlay/select_overlay_node.h"
24 #include "core/pipeline/base/element_register.h"
25 #include "core/pipeline_ng/pipeline_context.h"
26 namespace OHOS::Ace::NG {
CreateAndShowSelectOverlay(const SelectOverlayInfo & info,const WeakPtr<SelectionHost> & host,bool animation)27 RefPtr<SelectOverlayProxy> SelectOverlayManager::CreateAndShowSelectOverlay(
28 const SelectOverlayInfo& info, const WeakPtr<SelectionHost>& host, bool animation)
29 {
30 host_ = host;
31 auto current = selectOverlayItem_.Upgrade();
32 if (selectedByMouseInfo_.selectedNode.Upgrade() && info.callerFrameNode.Upgrade() &&
33 selectedByMouseInfo_.selectedNode.Upgrade() != info.callerFrameNode.Upgrade()) {
34 if (selectedByMouseInfo_.onResetSelection) {
35 selectedByMouseInfo_.onResetSelection();
36 }
37 selectedByMouseInfo_.clear();
38 }
39 if (current) {
40 if (info.isUsingMouse && IsSameSelectOverlayInfo(info)) {
41 auto proxy = MakeRefPtr<SelectOverlayProxy>(current->GetId());
42 return proxy;
43 }
44 auto frameNode = GetCallerHost();
45 CHECK_NULL_RETURN(frameNode, nullptr);
46 if (frameNode->GetTag() != V2::RICH_EDITOR_ETS_TAG) {
47 NotifyOverlayClosed(true);
48 }
49 DestroySelectOverlay(current->GetId());
50 }
51 selectOverlayInfo_ = info;
52 SelectOverlayInfo selectInfo = info;
53 if (selectInfo.callerFrameNode.Invalid()) {
54 selectInfo.callerFrameNode = GetCallerHost();
55 }
56 auto infoPtr = std::make_shared<SelectOverlayInfo>(selectInfo);
57 auto selectOverlayNode = SelectOverlayNode::CreateSelectOverlayNode(infoPtr);
58 selectOverlayItem_ = selectOverlayNode;
59
60 auto taskExecutor = Container::CurrentTaskExecutor();
61 taskExecutor->PostTask(
62 [weakRoot = rootNodeWeak_, overlayNode = selectOverlayNode, animation,
63 isUsingMouse = infoPtr->isUsingMouse, weak = WeakClaim(this), weakCaller = infoPtr->callerFrameNode] {
64 auto selectOverlayManager = weak.Upgrade();
65 CHECK_NULL_VOID(selectOverlayManager);
66 CHECK_NULL_VOID(overlayNode);
67 if (overlayNode != selectOverlayManager->GetSelectOverlayItem()) {
68 return;
69 }
70 auto rootNode = weakRoot.Upgrade();
71 auto container = Container::Current();
72 if (container && container->IsScenceBoardWindow()) {
73 auto root = selectOverlayManager->FindWindowScene(weakCaller.Upgrade());
74 rootNode = DynamicCast<FrameNode>(root);
75 }
76 CHECK_NULL_VOID(rootNode);
77 // get keyboard index to put selet_overlay before keyboard node
78 int32_t slot = DEFAULT_NODE_SLOT;
79 int32_t index = 0;
80 for (const auto& it : rootNode->GetChildren()) {
81 if (it->GetTag() == V2::KEYBOARD_ETS_TAG) {
82 slot = index;
83 break;
84 }
85 index++;
86 }
87
88 overlayNode->MountToParent(rootNode, slot);
89 rootNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
90 if (!isUsingMouse) {
91 auto node = DynamicCast<SelectOverlayNode>(overlayNode);
92 CHECK_NULL_VOID(node);
93 node->ShowSelectOverlay(animation);
94 }
95 auto context = PipelineContext::GetCurrentContext();
96 CHECK_NULL_VOID(context);
97 context->AddAfterLayoutTask([weakNode = WeakPtr<FrameNode>(rootNode)]() {
98 auto hostNode = weakNode.Upgrade();
99 CHECK_NULL_VOID(hostNode);
100 hostNode->OnAccessibilityEvent(AccessibilityEventType::PAGE_CHANGE);
101 });
102 },
103 TaskExecutor::TaskType::UI);
104
105 auto proxy = MakeRefPtr<SelectOverlayProxy>(selectOverlayNode->GetId());
106 return proxy;
107 }
108
109 // This function will be used in SceneBoard Thread only.
110 // if need to show the select-overlay component,
111 // it expects to receive the target component bound by the select-overlay component to find the windowScene component.
112 // if need to hide the select-overlay component,
113 // it expects to receive the the select-overlay component to return the parent component.
114 // And the parent component will be the windowScene component exactly.
FindWindowScene(RefPtr<FrameNode> targetNode)115 RefPtr<UINode> SelectOverlayManager::FindWindowScene(RefPtr<FrameNode> targetNode)
116 {
117 auto container = Container::Current();
118 if (!container || !container->IsScenceBoardWindow()) {
119 return rootNodeWeak_.Upgrade();
120 }
121 CHECK_NULL_RETURN(targetNode, nullptr);
122 auto parent = targetNode->GetParent();
123 while (parent && parent->GetTag() != V2::WINDOW_SCENE_ETS_TAG) {
124 parent = parent->GetParent();
125 }
126 CHECK_NULL_RETURN(parent, nullptr);
127 return parent;
128 }
129
DestroySelectOverlay(const RefPtr<SelectOverlayProxy> & proxy,bool animation)130 void SelectOverlayManager::DestroySelectOverlay(const RefPtr<SelectOverlayProxy>& proxy, bool animation)
131 {
132 auto id = proxy->GetSelectOverlayId();
133 DestroySelectOverlay(id, animation);
134 }
135
DestroySelectOverlay(int32_t overlayId,bool animation)136 void SelectOverlayManager::DestroySelectOverlay(int32_t overlayId, bool animation)
137 {
138 auto current = selectOverlayItem_.Upgrade();
139 if (current && (current->GetId() == overlayId)) {
140 DestroyHelper(current, animation);
141 }
142 }
143
DestroySelectOverlay(bool animation)144 bool SelectOverlayManager::DestroySelectOverlay(bool animation)
145 {
146 auto current = selectOverlayItem_.Upgrade();
147 if (current) {
148 DestroyHelper(current, animation);
149 return true;
150 }
151 return false;
152 }
153
ResetSelectionAndDestroySelectOverlay(bool animation)154 bool SelectOverlayManager::ResetSelectionAndDestroySelectOverlay(bool animation)
155 {
156 NotifyOverlayClosed(true);
157 return DestroySelectOverlay(animation);
158 }
159
DestroyHelper(const RefPtr<FrameNode> & overlay,bool animation)160 void SelectOverlayManager::DestroyHelper(const RefPtr<FrameNode>& overlay, bool animation)
161 {
162 auto rootNode = rootNodeWeak_.Upgrade();
163 CHECK_NULL_VOID(rootNode);
164 if (animation && !selectOverlayInfo_.isUsingMouse) {
165 selectOverlayItem_.Reset();
166 host_.Reset();
167 touchDownPoints_.clear();
168 selectOverlayInfo_.callerFrameNode.Reset();
169 auto node = DynamicCast<SelectOverlayNode>(overlay);
170 node->HideSelectOverlay([overlayWeak = WeakClaim(RawPtr(overlay)), managerWeak = WeakClaim(this)]() {
171 auto manager = managerWeak.Upgrade();
172 CHECK_NULL_VOID(manager);
173 auto overlay = overlayWeak.Upgrade();
174 CHECK_NULL_VOID(overlay);
175 manager->Destroy(overlay);
176 });
177 } else {
178 Destroy(overlay);
179 selectOverlayItem_.Reset();
180 host_.Reset();
181 touchDownPoints_.clear();
182 selectOverlayInfo_.callerFrameNode.Reset();
183 }
184 }
185
Destroy(const RefPtr<FrameNode> & overlay)186 void SelectOverlayManager::Destroy(const RefPtr<FrameNode>& overlay)
187 {
188 auto rootNode = overlay->GetParent();
189 CHECK_NULL_VOID(rootNode);
190 rootNode->RemoveChild(overlay);
191 rootNode->MarkNeedSyncRenderTree();
192 rootNode->RebuildRenderContextTree();
193 auto context = PipelineContext::GetCurrentContext();
194 CHECK_NULL_VOID(context);
195 context->AddAfterRenderTask([weakNode = WeakPtr<UINode>(rootNode)]() {
196 auto hostNode = weakNode.Upgrade();
197 CHECK_NULL_VOID(hostNode);
198 if (AceType::InstanceOf<FrameNode>(hostNode)) {
199 auto frameNode = AceType::DynamicCast<FrameNode>(hostNode);
200 CHECK_NULL_VOID(frameNode);
201 frameNode->OnAccessibilityEvent(AccessibilityEventType::PAGE_CHANGE);
202 }
203 });
204 }
205
HasSelectOverlay(int32_t overlayId)206 bool SelectOverlayManager::HasSelectOverlay(int32_t overlayId)
207 {
208 auto current = selectOverlayItem_.Upgrade();
209 CHECK_NULL_RETURN(current, false);
210 return current->GetId() == overlayId;
211 }
212
IsInSelectedOrSelectOverlayArea(const PointF & point)213 bool SelectOverlayManager::IsInSelectedOrSelectOverlayArea(const PointF& point)
214 {
215 auto host = host_.Upgrade();
216 if (host && host->IsTouchTestPointInArea(Offset { point.GetX(), point.GetY() }, IsTouchInCallerArea(point))) {
217 return true;
218 }
219 auto current = selectOverlayItem_.Upgrade();
220 CHECK_NULL_RETURN(current, false);
221 auto selectOverlayNode = DynamicCast<SelectOverlayNode>(current);
222 if (selectOverlayNode) {
223 return selectOverlayNode->IsInSelectedOrSelectOverlayArea(point);
224 }
225 // get the menu rect not the out wrapper
226 const auto& children = current->GetChildren();
227 for (const auto& it : children) {
228 auto child = DynamicCast<FrameNode>(it);
229 if (child == nullptr) {
230 continue;
231 }
232 auto frameRect = child->GetGeometryNode()->GetFrameRect();
233 if (frameRect.IsInRegion(point)) {
234 return true;
235 }
236 }
237 return false;
238 }
239
GetSelectOverlayNode(int32_t overlayId)240 RefPtr<SelectOverlayNode> SelectOverlayManager::GetSelectOverlayNode(int32_t overlayId)
241 {
242 auto current = selectOverlayItem_.Upgrade();
243 if (current && (current->GetId() == overlayId)) {
244 return DynamicCast<SelectOverlayNode>(current);
245 }
246 return nullptr;
247 }
248
IsSameSelectOverlayInfo(const SelectOverlayInfo & info)249 bool SelectOverlayManager::IsSameSelectOverlayInfo(const SelectOverlayInfo& info)
250 {
251 if (selectOverlayInfo_.menuInfo.IsIconChanged(info.menuInfo)) {
252 return false;
253 }
254 if (selectOverlayInfo_.isUsingMouse != info.isUsingMouse) {
255 return false;
256 }
257 if (selectOverlayInfo_.rightClickOffset != info.rightClickOffset) {
258 return false;
259 }
260 return true;
261 }
262
HandleGlobalEvent(const TouchEvent & touchPoint,const NG::OffsetF & rootOffset,bool isMousePressAtSelectedNode)263 void SelectOverlayManager::HandleGlobalEvent(
264 const TouchEvent& touchPoint, const NG::OffsetF& rootOffset, bool isMousePressAtSelectedNode)
265 {
266 ResetSelection(touchPoint, isMousePressAtSelectedNode);
267 CHECK_NULL_VOID(!selectOverlayItem_.Invalid());
268 NG::PointF point { touchPoint.x - rootOffset.GetX(), touchPoint.y - rootOffset.GetY() };
269 // handle global touch event.
270 if (touchPoint.type == TouchType::DOWN && touchPoint.sourceType == SourceType::TOUCH) {
271 if (touchDownPoints_.empty() && !IsTouchInCallerArea(point) && !IsInSelectedOrSelectOverlayArea(point)) {
272 touchDownPoints_.emplace_back(touchPoint);
273 }
274 return;
275 }
276 if (touchPoint.type == TouchType::MOVE && touchPoint.sourceType == SourceType::TOUCH) {
277 if (touchDownPoints_.empty()) {
278 return;
279 }
280 auto lastTouchDownPoint = touchDownPoints_.back();
281 if (lastTouchDownPoint.id != touchPoint.id) {
282 return;
283 }
284 auto deltaOffset = touchPoint.GetOffset() - lastTouchDownPoint.GetOffset();
285 auto deltaDistance = deltaOffset.GetDistance();
286 auto context = PipelineBase::GetCurrentContext();
287 auto thresholdDistance = context ? context->NormalizeToPx(Dimension(5, DimensionUnit::VP)) : 5;
288 if (deltaDistance > thresholdDistance) {
289 touchDownPoints_.clear();
290 }
291 return;
292 }
293 bool acceptTouchUp = !touchDownPoints_.empty();
294 if (touchPoint.type == TouchType::UP && touchPoint.sourceType == SourceType::TOUCH && acceptTouchUp) {
295 auto lastTouchDownPoint = touchDownPoints_.back();
296 if (lastTouchDownPoint.id != touchPoint.id) {
297 return;
298 }
299 touchDownPoints_.pop_back();
300 point.SetX(lastTouchDownPoint.x - rootOffset.GetX());
301 point.SetY(lastTouchDownPoint.y - rootOffset.GetY());
302 }
303
304 // handle global mouse event.
305 if ((touchPoint.type != TouchType::DOWN || touchPoint.sourceType != SourceType::MOUSE) && !acceptTouchUp) {
306 return;
307 }
308 if (!IsInSelectedOrSelectOverlayArea(point)) {
309 NotifyOverlayClosed(true);
310 DestroySelectOverlay();
311 }
312 }
313
ResetSelection(const TouchEvent & touchPoint,bool isMousePressAtSelectedNode)314 void SelectOverlayManager::ResetSelection(const TouchEvent& touchPoint, bool isMousePressAtSelectedNode)
315 {
316 if (touchPoint.type == TouchType::DOWN && !isMousePressAtSelectedNode && !selectOverlayItem_.Upgrade()) {
317 CHECK_NULL_VOID(selectedByMouseInfo_.selectedNode.Upgrade());
318 if (selectedByMouseInfo_.onResetSelection) {
319 selectedByMouseInfo_.onResetSelection();
320 }
321 selectedByMouseInfo_.clear();
322 }
323 }
324
IsTouchInCallerArea(const std::optional<NG::PointF> & point) const325 bool SelectOverlayManager::IsTouchInCallerArea(const std::optional<NG::PointF>& point) const
326 {
327 if (point.has_value() && selectOverlayInfo_.checkIsTouchInHostArea &&
328 selectOverlayInfo_.checkIsTouchInHostArea(point.value())) {
329 return true;
330 }
331 if (touchTestResults_.empty()) {
332 return false;
333 }
334 auto frameNode = GetCallerHost();
335 CHECK_NULL_RETURN(frameNode, false);
336 auto id = std::to_string(frameNode->GetId());
337 for (auto testId : touchTestResults_) {
338 if (testId == id) {
339 return true;
340 }
341 }
342 return false;
343 }
344
GetCallerHost() const345 RefPtr<FrameNode> SelectOverlayManager::GetCallerHost() const
346 {
347 auto host = host_.Upgrade();
348 CHECK_NULL_RETURN(host, nullptr);
349 auto pattern = DynamicCast<Pattern>(host);
350 CHECK_NULL_RETURN(pattern, nullptr);
351 return pattern->GetHost();
352 }
353
NotifyOverlayClosed(bool closedByGlobalEvent)354 void SelectOverlayManager::NotifyOverlayClosed(bool closedByGlobalEvent)
355 {
356 auto current = selectOverlayItem_.Upgrade();
357 if (current) {
358 auto selectOverlayNode = DynamicCast<SelectOverlayNode>(current);
359 CHECK_NULL_VOID(selectOverlayNode);
360 selectOverlayNode->SetClosedByGlobalEvent(closedByGlobalEvent);
361 }
362 }
363
MarkDirty(PropertyChangeFlag flag)364 void SelectOverlayManager::MarkDirty(PropertyChangeFlag flag)
365 {
366 auto selectOverlayItem = selectOverlayItem_.Upgrade();
367 if (selectOverlayItem) {
368 selectOverlayItem->MarkDirtyNode(flag);
369 }
370 }
371
NotifyOnScrollCallback(int32_t id,Axis axis,float offset,int32_t source)372 void SelectOverlayManager::NotifyOnScrollCallback(int32_t id, Axis axis, float offset, int32_t source)
373 {
374 if (parentScrollCallbacks_.empty()) {
375 return;
376 }
377 auto it = parentScrollCallbacks_.find(id);
378 if (it == parentScrollCallbacks_.end()) {
379 return;
380 }
381 auto callbackMap = it->second;
382 if (callbackMap.empty()) {
383 parentScrollCallbacks_.erase(id);
384 return;
385 }
386 for (const auto& pair : callbackMap) {
387 pair.second(axis, offset, source);
388 }
389 }
390
RegisterScrollCallback(int32_t scrollableParentId,int32_t callbackId,ScrollableParentCallback && callback)391 void SelectOverlayManager::RegisterScrollCallback(
392 int32_t scrollableParentId, int32_t callbackId, ScrollableParentCallback&& callback)
393 {
394 auto it = parentScrollCallbacks_.find(scrollableParentId);
395 if (it == parentScrollCallbacks_.end()) {
396 std::map<int32_t, ScrollableParentCallback> callbackMap = { { callbackId, std::move(callback) } };
397 parentScrollCallbacks_.insert(std::make_pair(scrollableParentId, callbackMap));
398 } else {
399 it->second.insert(std::make_pair(callbackId, std::move(callback)));
400 }
401 }
402
RemoveScrollCallback(int32_t callbackId)403 void SelectOverlayManager::RemoveScrollCallback(int32_t callbackId)
404 {
405 if (parentScrollCallbacks_.empty()) {
406 return;
407 }
408 for (auto it = parentScrollCallbacks_.begin(); it != parentScrollCallbacks_.end();) {
409 it->second.erase(callbackId);
410 if (it->second.empty()) {
411 it = parentScrollCallbacks_.erase(it);
412 } else {
413 ++it;
414 }
415 }
416 }
417 } // namespace OHOS::Ace::NG
418