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/menu/wrapper/menu_wrapper_pattern.h"
17
18 #include "base/log/dump_log.h"
19 #include "core/components_ng/pattern/menu/preview/menu_preview_pattern.h"
20
21 namespace OHOS::Ace::NG {
HideMenu(const RefPtr<FrameNode> & menu)22 void MenuWrapperPattern::HideMenu(const RefPtr<FrameNode>& menu)
23 {
24 CHECK_NULL_VOID(menu);
25 auto host = GetHost();
26 CHECK_NULL_VOID(host);
27 if (host->GetTag() == V2::SELECT_OVERLAY_ETS_TAG) {
28 return;
29 }
30 SetIsStopHoverImageAnimation(true);
31 auto menuPattern = menu->GetPattern<MenuPattern>();
32 CHECK_NULL_VOID(menuPattern);
33 menuPattern->HideMenu();
34 }
35
OnModifyDone()36 void MenuWrapperPattern::OnModifyDone()
37 {
38 InitFocusEvent();
39 }
40
InitFocusEvent()41 void MenuWrapperPattern::InitFocusEvent()
42 {
43 auto host = GetHost();
44 CHECK_NULL_VOID(host);
45 auto focusHub = host->GetOrCreateFocusHub();
46 CHECK_NULL_VOID(focusHub);
47 auto blurTask = [weak = WeakClaim(this)]() {
48 auto pattern = weak.Upgrade();
49 CHECK_NULL_VOID(pattern);
50 TAG_LOGI(AceLogTag::ACE_MENU, "will hide menu due to lost focus");
51 pattern->HideMenu();
52 };
53 focusHub->SetOnBlurInternal(std::move(blurTask));
54 }
55
GetShowedSubMenu()56 RefPtr<FrameNode> MenuWrapperPattern::GetShowedSubMenu()
57 {
58 auto host = GetHost();
59 CHECK_NULL_RETURN(host, nullptr);
60 return DynamicCast<FrameNode>(host->GetLastChild());
61 }
62
GetInnerMenu(RefPtr<UINode> & innerMenuNode,const PointF & position)63 bool MenuWrapperPattern::GetInnerMenu(RefPtr<UINode>& innerMenuNode, const PointF& position)
64 {
65 auto host = GetHost();
66 CHECK_NULL_RETURN(host, false);
67 auto children = host->GetChildren();
68 CHECK_NULL_RETURN(!children.empty(), false);
69 for (auto iter = children.rbegin(); iter != children.rend(); ++iter) {
70 auto& child = *iter;
71 if (!child || child->GetTag() != V2::MENU_ETS_TAG) {
72 continue;
73 }
74 auto frameNode = AceType::DynamicCast<FrameNode>(child);
75 CHECK_NULL_RETURN(frameNode, false);
76 auto menuPattern = frameNode->GetPattern<MenuPattern>();
77 CHECK_NULL_RETURN(menuPattern, false);
78 auto subMenuNode = menuPattern->GetShowedSubMenu();
79 innerMenuNode = subMenuNode ? subMenuNode : menuPattern->GetFirstInnerMenu();
80 if (!innerMenuNode) {
81 innerMenuNode = child;
82 }
83 auto geometryNode = frameNode->GetGeometryNode();
84 CHECK_NULL_RETURN(geometryNode, false);
85 return CheckPointInMenuZone(frameNode, position);
86 }
87 return false;
88 }
89
FindTouchedMenuItem(const RefPtr<UINode> & menuNode,const PointF & position)90 RefPtr<FrameNode> MenuWrapperPattern::FindTouchedMenuItem(const RefPtr<UINode>& menuNode, const PointF& position)
91 {
92 CHECK_NULL_RETURN(menuNode, nullptr);
93 RefPtr<FrameNode> menuItem = nullptr;
94 const auto& children = menuNode->GetChildren();
95 for (auto child : children) {
96 if (!child) {
97 continue;
98 }
99 if (child->GetTag() == V2::MENU_ITEM_ETS_TAG) {
100 auto frameNode = AceType::DynamicCast<FrameNode>(child);
101 auto pattern = frameNode ? frameNode->GetPattern<MenuItemPattern>() : nullptr;
102 menuItem = pattern ? pattern->FindTouchedEmbeddedMenuItem(position) : nullptr;
103 } else {
104 menuItem = FindTouchedMenuItem(child, position);
105 }
106 if (menuItem) {
107 if (CheckPointInMenuZone(menuItem, position)) {
108 break;
109 } else {
110 menuItem = nullptr;
111 }
112 }
113 }
114 return menuItem;
115 }
116
HandleInteraction(const TouchEventInfo & info)117 void MenuWrapperPattern::HandleInteraction(const TouchEventInfo& info)
118 {
119 CHECK_NULL_VOID(!info.GetChangedTouches().empty());
120 auto touch = info.GetChangedTouches().front();
121 auto host = GetHost();
122 CHECK_NULL_VOID(host);
123 auto position = PointF(
124 static_cast<float>(touch.GetGlobalLocation().GetX()), static_cast<float>(touch.GetGlobalLocation().GetY()));
125 RefPtr<UINode> innerMenuNode = nullptr;
126 auto isInRegion = GetInnerMenu(innerMenuNode, position);
127 CHECK_NULL_VOID(innerMenuNode);
128
129 ClearLastMenuItem();
130 // get menuNode's touch region
131 if (isInRegion) {
132 currentTouchItem_ = FindTouchedMenuItem(innerMenuNode, position);
133 ChangeCurMenuItemBgColor();
134 lastTouchItem_ = currentTouchItem_;
135 }
136 innerMenuNode->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
137 }
138
ChangeCurMenuItemBgColor()139 void MenuWrapperPattern::ChangeCurMenuItemBgColor()
140 {
141 if (!currentTouchItem_) {
142 return;
143 }
144 auto pipeline = PipelineBase::GetCurrentContext();
145 CHECK_NULL_VOID(pipeline);
146 auto theme = pipeline->GetTheme<SelectTheme>();
147 CHECK_NULL_VOID(theme);
148 auto curMenuItemPattern = currentTouchItem_->GetPattern<MenuItemPattern>();
149 CHECK_NULL_VOID(curMenuItemPattern);
150 if (curMenuItemPattern->IsDisabled() || curMenuItemPattern->IsStackSubmenuHeader()) {
151 return;
152 }
153 curMenuItemPattern->NotifyPressStatus(true);
154 }
155
ClearLastMenuItem()156 void MenuWrapperPattern::ClearLastMenuItem()
157 {
158 if (lastTouchItem_) {
159 auto lastMenuItemPattern = lastTouchItem_->GetPattern<MenuItemPattern>();
160 CHECK_NULL_VOID(lastMenuItemPattern);
161 lastMenuItemPattern->NotifyPressStatus(false);
162 lastTouchItem_ = nullptr;
163 }
164 }
165
OnAttachToFrameNode()166 void MenuWrapperPattern::OnAttachToFrameNode()
167 {
168 RegisterOnTouch();
169 }
170
171 // close subMenu when mouse move outside
HandleMouseEvent(const MouseInfo & info,RefPtr<MenuItemPattern> & menuItemPattern)172 void MenuWrapperPattern::HandleMouseEvent(const MouseInfo& info, RefPtr<MenuItemPattern>& menuItemPattern)
173 {
174 CHECK_NULL_VOID(menuItemPattern);
175 auto host = GetHost();
176 CHECK_NULL_VOID(host);
177 auto subMenu = host->GetChildren().back();
178 if (host->GetChildren().size() <= 1) {
179 return;
180 }
181 auto subMenuNode = DynamicCast<FrameNode>(subMenu);
182 CHECK_NULL_VOID(subMenuNode);
183 auto subMenuPattern = subMenuNode->GetPattern<MenuPattern>();
184 CHECK_NULL_VOID(subMenuPattern);
185 auto currentHoverMenuItem = subMenuPattern->GetParentMenuItem();
186 CHECK_NULL_VOID(currentHoverMenuItem);
187 auto geometryNode = subMenuNode->GetGeometryNode();
188 CHECK_NULL_VOID(geometryNode);
189 auto offset = subMenuNode->GetPaintRectOffset(false, true);
190 auto frameSize = geometryNode->GetFrameSize();
191 auto menuZone = RectF(offset.GetX(), offset.GetY(), frameSize.Width(), frameSize.Height());
192 auto menuItemNode = menuItemPattern->GetHost();
193 CHECK_NULL_VOID(menuItemNode);
194 if (currentHoverMenuItem->GetId() != menuItemNode->GetId()) {
195 return;
196 }
197 const auto& mousePosition = info.GetGlobalLocation();
198 if (!menuItemPattern->IsInHoverRegions(mousePosition.GetX(), mousePosition.GetY()) &&
199 menuItemPattern->IsSubMenuShowed()) {
200 menuItemPattern->CheckHideSubMenu(
201 [weak = WeakClaim(this), weakSubMenu = WeakClaim(RawPtr(subMenuNode))] {
202 auto pattern = weak.Upgrade();
203 CHECK_NULL_VOID(pattern);
204 auto subMenu = weakSubMenu.Upgrade();
205 CHECK_NULL_VOID(subMenu);
206 if (subMenu == pattern->GetShowedSubMenu()) {
207 pattern->HideSubMenu();
208 }
209 },
210 PointF(mousePosition.GetX(), mousePosition.GetY()), menuZone);
211 } else {
212 menuItemPattern->CancelHideSubMenuTask(PointF(mousePosition.GetX(), mousePosition.GetY()));
213 }
214 }
215
HideMenu()216 void MenuWrapperPattern::HideMenu()
217 {
218 auto host = GetHost();
219 CHECK_NULL_VOID(host);
220 auto menuNode = DynamicCast<FrameNode>(host->GetChildAtIndex(0));
221 CHECK_NULL_VOID(menuNode);
222 HideMenu(menuNode);
223 }
224
GetExpandingMode(const RefPtr<UINode> & subMenu,SubMenuExpandingMode & expandingMode,bool & hasAnimation)225 void MenuWrapperPattern::GetExpandingMode(const RefPtr<UINode>& subMenu, SubMenuExpandingMode& expandingMode,
226 bool& hasAnimation)
227 {
228 CHECK_NULL_VOID(subMenu);
229 auto subMenuNode = DynamicCast<FrameNode>(subMenu);
230 CHECK_NULL_VOID(subMenuNode);
231 auto subMenuPattern = subMenuNode->GetPattern<MenuPattern>();
232 CHECK_NULL_VOID(subMenuPattern);
233 hasAnimation = subMenuPattern->GetDisappearAnimation();
234 auto menuItem = FrameNode::GetFrameNode(subMenuPattern->GetTargetTag(), subMenuPattern->GetTargetId());
235 CHECK_NULL_VOID(menuItem);
236 auto menuItemPattern = menuItem->GetPattern<MenuItemPattern>();
237 CHECK_NULL_VOID(menuItemPattern);
238 auto menuNode = menuItemPattern->GetMenu();
239 CHECK_NULL_VOID(menuNode);
240 auto menuProperty = menuNode->GetLayoutProperty<MenuLayoutProperty>();
241 CHECK_NULL_VOID(menuProperty);
242 expandingMode = menuProperty->GetExpandingMode().value_or(SubMenuExpandingMode::SIDE);
243 menuItemPattern->SetIsSubMenuShowed (false);
244 }
245
HideSubMenu()246 void MenuWrapperPattern::HideSubMenu()
247 {
248 auto host = GetHost();
249 CHECK_NULL_VOID(host);
250 if (host->GetChildren().size() <= 1) {
251 // sub menu not show
252 return;
253 }
254 auto subMenu = host->GetChildren().back();
255 CHECK_NULL_VOID(subMenu);
256 auto menuNode = GetParentMenu(subMenu);
257 CHECK_NULL_VOID(menuNode);
258 auto menuPattern = menuNode->GetPattern<MenuPattern>();
259 CHECK_NULL_VOID(menuPattern);
260 MenuFocusViewShow(menuNode);
261 menuPattern->SetShowedSubMenu(nullptr);
262 auto innerMenu = GetMenuChild(menuNode);
263 if (!innerMenu) {
264 UpdateMenuAnimation(host);
265 SendToAccessibility(subMenu, false);
266 host->RemoveChild(subMenu);
267 host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF_AND_CHILD);
268 return;
269 }
270 auto innerMenuPattern = innerMenu->GetPattern<MenuPattern>();
271 CHECK_NULL_VOID(innerMenuPattern);
272 auto layoutProps = innerMenuPattern->GetLayoutProperty<MenuLayoutProperty>();
273 CHECK_NULL_VOID(layoutProps);
274 auto expandingMode = layoutProps->GetExpandingMode().value_or(SubMenuExpandingMode::SIDE);
275 bool hasAnimation = menuPattern->GetDisappearAnimation();
276 GetExpandingMode(subMenu, expandingMode, hasAnimation);
277 if (expandingMode == SubMenuExpandingMode::STACK && hasAnimation) {
278 HideStackExpandMenu(subMenu);
279 } else {
280 UpdateMenuAnimation(host);
281 SendToAccessibility(subMenu, false);
282 host->RemoveChild(subMenu);
283 host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF_AND_CHILD);
284 }
285 }
286
GetParentMenu(const RefPtr<UINode> & subMenu)287 RefPtr<FrameNode> MenuWrapperPattern::GetParentMenu(const RefPtr<UINode>& subMenu)
288 {
289 auto subMenuNode = AceType::DynamicCast<FrameNode>(subMenu);
290 CHECK_NULL_RETURN(subMenuNode, nullptr);
291 auto subMenuPattern = subMenuNode->GetPattern<MenuPattern>();
292 CHECK_NULL_RETURN(subMenuPattern, nullptr);
293 auto menuItem = subMenuPattern->GetParentMenuItem();
294 CHECK_NULL_RETURN(menuItem, nullptr);
295 auto itemPattern = menuItem->GetPattern<MenuItemPattern>();
296 CHECK_NULL_RETURN(itemPattern, nullptr);
297 return itemPattern->GetMenu(true);
298 }
299
SendToAccessibility(const RefPtr<UINode> & subMenu,bool isShow)300 void MenuWrapperPattern::SendToAccessibility(const RefPtr<UINode>& subMenu, bool isShow)
301 {
302 auto subMenuNode = AceType::DynamicCast<FrameNode>(subMenu);
303 CHECK_NULL_VOID(subMenuNode);
304 auto accessibilityProperty = subMenuNode->GetAccessibilityProperty<MenuAccessibilityProperty>();
305 CHECK_NULL_VOID(accessibilityProperty);
306 accessibilityProperty->SetAccessibilityIsShow(isShow);
307 subMenuNode->OnAccessibilityEvent(AccessibilityEventType::PAGE_CLOSE);
308 }
309
HasStackSubMenu()310 bool MenuWrapperPattern::HasStackSubMenu()
311 {
312 auto outterMenu = GetMenu();
313 CHECK_NULL_RETURN(outterMenu, false);
314 auto innerMenu = GetMenuChild(outterMenu);
315 CHECK_NULL_RETURN(innerMenu, false);
316 auto innerMenuPattern = innerMenu->GetPattern<MenuPattern>();
317 CHECK_NULL_RETURN(innerMenuPattern, false);
318 auto layoutProps = innerMenuPattern->GetLayoutProperty<MenuLayoutProperty>();
319 CHECK_NULL_RETURN(layoutProps, false);
320 auto expandingMode = layoutProps->GetExpandingMode().value_or(SubMenuExpandingMode::SIDE);
321 if (expandingMode != SubMenuExpandingMode::STACK) {
322 return false;
323 }
324 auto host = GetHost();
325 CHECK_NULL_RETURN(host, false);
326 auto menuCount = 0;
327 for (const auto& child : host->GetChildren()) {
328 if (child && child->GetTag() == V2::MENU_ETS_TAG) {
329 menuCount++;
330 }
331 }
332 return menuCount > 1;
333 }
334
HasEmbeddedSubMenu()335 bool MenuWrapperPattern::HasEmbeddedSubMenu()
336 {
337 auto outterMenu = GetMenu();
338 CHECK_NULL_RETURN(outterMenu, false);
339 auto innerMenu = GetMenuChild(outterMenu);
340 CHECK_NULL_RETURN(innerMenu, false);
341 auto innerMenuPattern = innerMenu->GetPattern<MenuPattern>();
342 CHECK_NULL_RETURN(innerMenuPattern, false);
343 auto layoutProps = innerMenuPattern->GetLayoutProperty<MenuLayoutProperty>();
344 CHECK_NULL_RETURN(layoutProps, false);
345 auto expandingMode = layoutProps->GetExpandingMode().value_or(SubMenuExpandingMode::SIDE);
346 return expandingMode == SubMenuExpandingMode::EMBEDDED;
347 }
348
MenuFocusViewShow(const RefPtr<FrameNode> & menuNode)349 void MenuWrapperPattern::MenuFocusViewShow(const RefPtr<FrameNode>& menuNode)
350 {
351 CHECK_NULL_VOID(menuNode);
352 // SelectOverlay's custom menu does not need to be focused.
353 auto isCustomMenu = IsSelectOverlayCustomMenu(menuNode);
354 auto isRightClickMenu = IsSelectOverlayRightClickMenu(menuNode);
355 if (!isCustomMenu && !isRightClickMenu) {
356 auto menuPattern = menuNode->GetPattern<MenuPattern>();
357 CHECK_NULL_VOID(menuPattern);
358 menuPattern->FocusViewShow();
359 }
360 }
361
HideStackExpandMenu(const RefPtr<UINode> & subMenu)362 void MenuWrapperPattern::HideStackExpandMenu(const RefPtr<UINode>& subMenu)
363 {
364 auto host = GetHost();
365 CHECK_NULL_VOID(host);
366 auto menuNode = host->GetFirstChild();
367 CHECK_NULL_VOID(menuNode);
368 AnimationOption option;
369 option.SetOnFinishEvent(
370 [weak = WeakClaim(RawPtr(host)), subMenuWk = WeakClaim(RawPtr(subMenu))] {
371 auto pipeline = PipelineBase::GetCurrentContext();
372 CHECK_NULL_VOID(pipeline);
373 auto taskExecutor = pipeline->GetTaskExecutor();
374 CHECK_NULL_VOID(taskExecutor);
375 taskExecutor->PostTask(
376 [weak, subMenuWk]() {
377 auto subMenuNode = subMenuWk.Upgrade();
378 CHECK_NULL_VOID(subMenuNode);
379 auto menuWrapper = weak.Upgrade();
380 CHECK_NULL_VOID(menuWrapper);
381
382 auto subMenuFrameNode = DynamicCast<FrameNode>(subMenuNode);
383 CHECK_NULL_VOID(subMenuFrameNode);
384 auto accessibilityProperty =
385 subMenuFrameNode->GetAccessibilityProperty<MenuAccessibilityProperty>();
386 CHECK_NULL_VOID(accessibilityProperty);
387 accessibilityProperty->SetAccessibilityIsShow(false);
388 subMenuFrameNode->OnAccessibilityEvent(AccessibilityEventType::PAGE_CLOSE,
389 WindowsContentChangeTypes::CONTENT_CHANGE_TYPE_SUBTREE);
390 TAG_LOGI(AceLogTag::ACE_MENU, "Send event to %{public}d",
391 static_cast<int32_t>(AccessibilityEventType::PAGE_CLOSE));
392
393 menuWrapper->RemoveChild(subMenuNode);
394 menuWrapper->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF_AND_CHILD);
395 },
396 TaskExecutor::TaskType::UI, "HideStackExpandMenu");
397 });
398 auto menuFrameNode = DynamicCast<FrameNode>(menuNode);
399 CHECK_NULL_VOID(menuFrameNode);
400 auto menuNodePattern = menuFrameNode->GetPattern<MenuPattern>();
401 CHECK_NULL_VOID(menuNodePattern);
402 auto subMenuFrameNode = DynamicCast<FrameNode>(subMenu);
403 CHECK_NULL_VOID(subMenuFrameNode);
404 menuNodePattern->ShowStackMenuDisappearAnimation(menuFrameNode,
405 subMenuFrameNode, option);
406 menuNodePattern->SetDisappearAnimation(true);
407 auto subMenuPattern = subMenuFrameNode->GetPattern<MenuPattern>();
408 CHECK_NULL_VOID(subMenuPattern);
409 subMenuPattern->SetSubMenuShow(false);
410 }
411
RegisterOnTouch()412 void MenuWrapperPattern::RegisterOnTouch()
413 {
414 // if already initialized touch event
415 CHECK_NULL_VOID(!onTouch_);
416 auto host = GetHost();
417 CHECK_NULL_VOID(host);
418 auto gesture = host->GetOrCreateGestureEventHub();
419 CHECK_NULL_VOID(gesture);
420 // hide menu when touched outside the menu region
421 auto touchTask = [weak = WeakClaim(this)](const TouchEventInfo& info) {
422 auto pattern = weak.Upgrade();
423 CHECK_NULL_VOID(pattern);
424 pattern->OnTouchEvent(info);
425 };
426 onTouch_ = MakeRefPtr<TouchEventImpl>(std::move(touchTask));
427 gesture->AddTouchEvent(onTouch_);
428 }
429
OnTouchEvent(const TouchEventInfo & info)430 void MenuWrapperPattern::OnTouchEvent(const TouchEventInfo& info)
431 {
432 CHECK_NULL_VOID(!info.GetChangedTouches().empty());
433 auto touch = info.GetChangedTouches().front();
434 // filter out other touch types
435 if (touch.GetTouchType() != TouchType::DOWN &&
436 Container::LessThanAPITargetVersion(PlatformVersion::VERSION_TWELVE)) {
437 return;
438 }
439 if (IsHide()) {
440 return;
441 }
442 auto host = GetHost();
443 CHECK_NULL_VOID(host);
444
445 auto position = PointF(
446 static_cast<float>(touch.GetGlobalLocation().GetX()), static_cast<float>(touch.GetGlobalLocation().GetY()));
447 auto children = host->GetChildren();
448 if (touch.GetTouchType() == TouchType::DOWN) {
449 // Record the latest touch finger ID. If other fingers are pressed, the latest one prevails
450 fingerId_ = touch.GetFingerId();
451 TAG_LOGD(AceLogTag::ACE_MENU, "record newest finger ID %{public}d", fingerId_);
452 for (auto child = children.rbegin(); child != children.rend(); ++child) {
453 // get child frame node of menu wrapper
454 auto menuWrapperChildNode = DynamicCast<FrameNode>(*child);
455 CHECK_NULL_VOID(menuWrapperChildNode);
456 // get menuWrapperChildNode's touch region
457 if (CheckPointInMenuZone(menuWrapperChildNode, position)) {
458 HandleInteraction(info);
459 return;
460 }
461 // if DOWN-touched outside the menu region, then hide menu
462 auto menuPattern = menuWrapperChildNode->GetPattern<MenuPattern>();
463 if (!menuPattern) {
464 continue;
465 }
466 TAG_LOGI(AceLogTag::ACE_MENU, "will hide menu due to touch down");
467 HideMenu(menuPattern, menuWrapperChildNode, position);
468 }
469 return;
470 }
471 // When the Move or Up event is not the recorded finger ID, this event is not responded
472 if (fingerId_ != touch.GetFingerId()) {
473 return;
474 }
475 ChangeTouchItem(info, touch.GetTouchType());
476 }
477
ChangeTouchItem(const TouchEventInfo & info,TouchType touchType)478 void MenuWrapperPattern::ChangeTouchItem(const TouchEventInfo& info, TouchType touchType)
479 {
480 auto host = GetHost();
481 CHECK_NULL_VOID(host);
482 if (touchType == TouchType::MOVE) {
483 auto menuNode = DynamicCast<FrameNode>(host->GetChildAtIndex(0));
484 CHECK_NULL_VOID(menuNode);
485 if (GetPreviewMode() != MenuPreviewMode::NONE || IsSelectOverlayCustomMenu(menuNode)) {
486 return;
487 }
488 HandleInteraction(info);
489 } else if (touchType == TouchType::UP || touchType == TouchType::CANCEL) {
490 if (currentTouchItem_) {
491 auto currentTouchItemPattern = currentTouchItem_->GetPattern<MenuItemPattern>();
492 CHECK_NULL_VOID(currentTouchItemPattern);
493 currentTouchItemPattern->NotifyPressStatus(false);
494 currentTouchItem_ = nullptr;
495 }
496 // Reset finger ID when touch Up or Cancel
497 TAG_LOGD(AceLogTag::ACE_MENU, "reset finger ID %{public}d", fingerId_);
498 fingerId_ = -1;
499 }
500 }
501
HideMenu(const RefPtr<MenuPattern> & menuPattern,const RefPtr<FrameNode> & menu,const PointF & position)502 void MenuWrapperPattern::HideMenu(const RefPtr<MenuPattern>& menuPattern, const RefPtr<FrameNode>& menu,
503 const PointF& position)
504 {
505 CHECK_NULL_VOID(menuPattern);
506 auto host = GetHost();
507 CHECK_NULL_VOID(host);
508 auto mainMenu = DynamicCast<FrameNode>(host->GetFirstChild());
509 CHECK_NULL_VOID(mainMenu);
510 bool isFindTargetId = false;
511 if (CheckPointInMenuZone(mainMenu, position)) {
512 isFindTargetId = true;
513 }
514 if (menuPattern->IsSubMenu() || menuPattern->IsSelectOverlaySubMenu()) {
515 if (HasStackSubMenu() && !isFindTargetId) {
516 UpdateMenuAnimation(host);
517 }
518 HideSubMenu();
519 } else {
520 if (HasEmbeddedSubMenu() && embeddedSubMenuCount_ > 0 && !isFindTargetId) {
521 UpdateMenuAnimation(host);
522 }
523 HideMenu(menu);
524 }
525 }
526
UpdateMenuAnimation(const RefPtr<FrameNode> & host)527 void MenuWrapperPattern::UpdateMenuAnimation(const RefPtr<FrameNode>& host)
528 {
529 // update Menu disappear animation direction
530 // change to LEFT_BOTTOM -> RIGHT_TOP by calling SetExitAnimation
531 // or keep BOTTOM -> TOP by default
532 CHECK_NULL_VOID(host);
533 auto outterMenu = host->GetFirstChild();
534 CHECK_NULL_VOID(outterMenu);
535 auto innerMenu = GetMenuChild(outterMenu);
536 if (!innerMenu && host->GetChildren().size() > 1) {
537 SetExitAnimation(host);
538 return;
539 }
540 CHECK_NULL_VOID(innerMenu);
541 auto innerMenuPattern = innerMenu->GetPattern<MenuPattern>();
542 CHECK_NULL_VOID(innerMenuPattern);
543 auto layoutProps = innerMenuPattern->GetLayoutProperty<MenuLayoutProperty>();
544 CHECK_NULL_VOID(layoutProps);
545 auto expandingMode = layoutProps->GetExpandingMode().value_or(SubMenuExpandingMode::SIDE);
546 if (host->GetChildren().size() > 1) {
547 SetExitAnimation(host);
548 }
549 if (expandingMode == SubMenuExpandingMode::EMBEDDED && embeddedSubMenuCount_ > 0) {
550 SetExitAnimation(host);
551 }
552 }
553
SetExitAnimation(const RefPtr<FrameNode> & host)554 void MenuWrapperPattern::SetExitAnimation(const RefPtr<FrameNode>& host)
555 {
556 CHECK_NULL_VOID(host);
557 auto outterMenu = AceType::DynamicCast<FrameNode>(host->GetFirstChild());
558 CHECK_NULL_VOID(outterMenu);
559 auto outterMenuPattern = outterMenu->GetPattern<MenuPattern>();
560 CHECK_NULL_VOID(outterMenuPattern);
561 outterMenuPattern->SetDisappearAnimation(false);
562 }
563
CheckAndShowAnimation()564 void MenuWrapperPattern::CheckAndShowAnimation()
565 {
566 if (isFirstShow_) {
567 // only start animation when menu wrapper mount on.
568 StartShowAnimation();
569 isFirstShow_ = false;
570 }
571 }
572
MarkWholeSubTreeNoDraggable(const RefPtr<FrameNode> & frameNode)573 void MenuWrapperPattern::MarkWholeSubTreeNoDraggable(const RefPtr<FrameNode>& frameNode)
574 {
575 CHECK_NULL_VOID(frameNode);
576 auto eventHub = frameNode->GetEventHub<EventHub>();
577 CHECK_NULL_VOID(eventHub);
578 auto gestureEventHub = eventHub->GetOrCreateGestureEventHub();
579 CHECK_NULL_VOID(gestureEventHub);
580 gestureEventHub->SetDragForbiddenForcely(true);
581 }
582
MarkAllMenuNoDraggable()583 void MenuWrapperPattern::MarkAllMenuNoDraggable()
584 {
585 auto host = GetHost();
586 CHECK_NULL_VOID(host);
587 for (const auto& child : host->GetChildren()) {
588 auto node = DynamicCast<FrameNode>(child);
589 if (node && node->GetTag() == V2::MENU_ETS_TAG) {
590 MarkWholeSubTreeNoDraggable(node);
591 }
592 }
593 }
594
OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper> & dirty,const DirtySwapConfig & config)595 bool MenuWrapperPattern::OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper>& dirty, const DirtySwapConfig& config)
596 {
597 forceUpdateEmbeddedMenu_ = false;
598 auto pipeline = PipelineBase::GetCurrentContext();
599 CHECK_NULL_RETURN(pipeline, false);
600 auto theme = pipeline->GetTheme<SelectTheme>();
601 CHECK_NULL_RETURN(theme, false);
602 auto expandDisplay = theme->GetExpandDisplay();
603 auto host = GetHost();
604 CHECK_NULL_RETURN(host, false);
605 auto menuNode = DynamicCast<FrameNode>(host->GetChildAtIndex(0));
606 CHECK_NULL_RETURN(menuNode, false);
607 auto menuPattern = AceType::DynamicCast<MenuPattern>(menuNode->GetPattern());
608 CHECK_NULL_RETURN(menuPattern, false);
609 // copy menu pattern properties to rootMenu
610 auto layoutProperty = menuPattern->GetLayoutProperty<MenuLayoutProperty>();
611 CHECK_NULL_RETURN(layoutProperty, false);
612 isShowInSubWindow_ = layoutProperty->GetShowInSubWindowValue(true);
613 if (host->IsOnMainTree() &&
614 ((IsContextMenu() && !IsHide()) || ((expandDisplay && isShowInSubWindow_) && !IsHide()) ||
615 GetIsSelectOverlaySubWindowWrapper())) {
616 SetHotAreas(dirty);
617 }
618 MarkAllMenuNoDraggable();
619 MarkWholeSubTreeNoDraggable(GetPreview());
620 CheckAndShowAnimation();
621 return false;
622 }
623
IsNeedSetHotAreas(const RefPtr<LayoutWrapper> & layoutWrapper)624 bool MenuWrapperPattern::IsNeedSetHotAreas(const RefPtr<LayoutWrapper>& layoutWrapper)
625 {
626 CHECK_NULL_RETURN(layoutWrapper, false);
627 auto pipeline = PipelineBase::GetCurrentContext();
628 CHECK_NULL_RETURN(pipeline, false);
629 auto theme = pipeline->GetTheme<SelectTheme>();
630 CHECK_NULL_RETURN(theme, false);
631 bool menuNotNeedsHotAreas = (layoutWrapper->GetAllChildrenWithBuild().empty() || !IsContextMenu()) &&
632 !(theme->GetExpandDisplay() && isShowInSubWindow_);
633 if (menuNotNeedsHotAreas && !GetIsSelectOverlaySubWindowWrapper()) {
634 return false;
635 }
636
637 return true;
638 }
639
SetHotAreas(const RefPtr<LayoutWrapper> & layoutWrapper)640 void MenuWrapperPattern::SetHotAreas(const RefPtr<LayoutWrapper>& layoutWrapper)
641 {
642 CHECK_NULL_VOID(layoutWrapper);
643 if (!IsNeedSetHotAreas(layoutWrapper)) {
644 return;
645 }
646 auto layoutProps = layoutWrapper->GetLayoutProperty();
647 CHECK_NULL_VOID(layoutProps);
648 float safeAreaInsetsLeft = 0.0f;
649 float safeAreaInsetsTop = 0.0f;
650 auto&& safeAreaInsets = layoutProps->GetSafeAreaInsets();
651 if (safeAreaInsets) {
652 safeAreaInsetsLeft = static_cast<float>(safeAreaInsets->left_.end);
653 safeAreaInsetsTop = static_cast<float>(safeAreaInsets->top_.end);
654 }
655 std::vector<Rect> rects;
656 for (const auto& child : layoutWrapper->GetAllChildrenWithBuild()) {
657 CHECK_NULL_VOID(child);
658 auto childGeometryNode = child->GetGeometryNode();
659 CHECK_NULL_VOID(childGeometryNode);
660 auto frameRect = childGeometryNode->GetFrameRect();
661 // rect is relative to window
662 auto childNode = child->GetHostNode();
663 if (childNode &&
664 (childNode->GetTag() == V2::MENU_PREVIEW_ETS_TAG || childNode->GetTag() == V2::IMAGE_ETS_TAG)) {
665 frameRect = childNode->GetPaintRectWithTransform(); // get preview area with scale transform
666 }
667 auto rect = Rect(frameRect.GetX() + safeAreaInsetsLeft, frameRect.GetY() + safeAreaInsetsTop, frameRect.Width(),
668 frameRect.Height());
669
670 rects.emplace_back(rect);
671 }
672 // If container is UIExtensionWindow, set hotArea size equals to subwindow's filterColumnNode's size
673 if (IsContextMenu() && GetPreviewMode() != MenuPreviewMode::NONE) {
674 auto filterNode = GetFilterColumnNode();
675 if (filterNode) {
676 auto filterNodeGeometryNode = filterNode->GetGeometryNode();
677 CHECK_NULL_VOID(filterNodeGeometryNode);
678 auto frameRect = filterNodeGeometryNode->GetFrameRect();
679 auto rect = Rect(frameRect.GetX(), frameRect.GetY(), frameRect.Width(), frameRect.Height());
680 rects.emplace_back(rect);
681 }
682 }
683 auto subwindowManager = SubwindowManager::GetInstance();
684 CHECK_NULL_VOID(subwindowManager);
685 auto host = GetHost();
686 CHECK_NULL_VOID(host);
687 if (GetIsSelectOverlaySubWindowWrapper()) {
688 subwindowManager->SetSelectOverlayHotAreas(rects, host->GetId(), GetContainerId());
689 } else {
690 subwindowManager->SetHotAreas(rects, SubwindowType::TYPE_MENU, host->GetId(), GetContainerId());
691 }
692 }
693
StartShowAnimation()694 void MenuWrapperPattern::StartShowAnimation()
695 {
696 auto host = GetHost();
697 CHECK_NULL_VOID(host);
698 auto context = host->GetRenderContext();
699 CHECK_NULL_VOID(context);
700 if (GetPreviewMode() == MenuPreviewMode::NONE) {
701 context->UpdateOffset(GetAnimationOffset());
702 context->UpdateOpacity(0.0);
703 }
704
705 AnimationUtils::Animate(
706 animationOption_,
707 [context, weak = WeakClaim(this)]() {
708 if (context) {
709 auto pattern = weak.Upgrade();
710 CHECK_NULL_VOID(pattern);
711 if (pattern->GetPreviewMode() == MenuPreviewMode::NONE) {
712 context->UpdateOffset(OffsetT<Dimension>());
713 context->UpdateOpacity(1.0);
714 }
715 }
716 },
717 animationOption_.GetOnFinishEvent());
718 }
719
SetAniamtinOption(const AnimationOption & animationOption)720 void MenuWrapperPattern::SetAniamtinOption(const AnimationOption& animationOption)
721 {
722 animationOption_.SetCurve(animationOption.GetCurve());
723 animationOption_.SetDuration(animationOption.GetDuration());
724 animationOption_.SetFillMode(animationOption.GetFillMode());
725 animationOption_.SetOnFinishEvent(animationOption.GetOnFinishEvent());
726 }
727
GetAnimationOffset()728 OffsetT<Dimension> MenuWrapperPattern::GetAnimationOffset()
729 {
730 OffsetT<Dimension> offset;
731
732 auto pipeline = PipelineBase::GetCurrentContext();
733 CHECK_NULL_RETURN(pipeline, offset);
734 auto theme = pipeline->GetTheme<SelectTheme>();
735 CHECK_NULL_RETURN(theme, offset);
736 auto animationOffset = theme->GetMenuAnimationOffset();
737
738 switch (menuPlacement_) {
739 case Placement::LEFT:
740 case Placement::LEFT_TOP:
741 case Placement::LEFT_BOTTOM:
742 offset.SetX(animationOffset);
743 break;
744 case Placement::RIGHT:
745 case Placement::RIGHT_TOP:
746 case Placement::RIGHT_BOTTOM:
747 offset.SetX(-animationOffset);
748 break;
749 case Placement::TOP:
750 case Placement::TOP_LEFT:
751 case Placement::TOP_RIGHT:
752 offset.SetY(animationOffset);
753 break;
754 default:
755 offset.SetY(-animationOffset);
756 break;
757 }
758 return offset;
759 }
760
IsSelectOverlayCustomMenu(const RefPtr<FrameNode> & menu) const761 bool MenuWrapperPattern::IsSelectOverlayCustomMenu(const RefPtr<FrameNode>& menu) const
762 {
763 CHECK_NULL_RETURN(menu, false);
764 auto menuPattern = menu->GetPattern<MenuPattern>();
765 CHECK_NULL_RETURN(menuPattern, false);
766 return menuPattern->IsSelectOverlayCustomMenu();
767 }
768
IsSelectOverlayRightClickMenu(const RefPtr<FrameNode> & menu) const769 bool MenuWrapperPattern::IsSelectOverlayRightClickMenu(const RefPtr<FrameNode>& menu) const
770 {
771 auto menuPattern = menu->GetPattern<MenuPattern>();
772 CHECK_NULL_RETURN(menuPattern, false);
773 return menuPattern->IsSelectOverlayRightClickMenu();
774 }
775
RegisterMenuCallback(const RefPtr<FrameNode> & menuWrapperNode,const MenuParam & menuParam)776 void MenuWrapperPattern::RegisterMenuCallback(const RefPtr<FrameNode>& menuWrapperNode, const MenuParam& menuParam)
777 {
778 TAG_LOGD(AceLogTag::ACE_DIALOG, "register menu enter");
779 CHECK_NULL_VOID(menuWrapperNode);
780 auto pattern = menuWrapperNode->GetPattern<MenuWrapperPattern>();
781 CHECK_NULL_VOID(pattern);
782 pattern->RegisterMenuAppearCallback(menuParam.onAppear);
783 pattern->RegisterMenuDisappearCallback(menuParam.onDisappear);
784 pattern->RegisterMenuAboutToAppearCallback(menuParam.aboutToAppear);
785 pattern->RegisterMenuAboutToDisappearCallback(menuParam.aboutToDisappear);
786 pattern->RegisterMenuStateChangeCallback(menuParam.onStateChange);
787 }
788
SetMenuTransitionEffect(const RefPtr<FrameNode> & menuWrapperNode,const MenuParam & menuParam)789 void MenuWrapperPattern::SetMenuTransitionEffect(const RefPtr<FrameNode>& menuWrapperNode, const MenuParam& menuParam)
790 {
791 TAG_LOGD(AceLogTag::ACE_DIALOG, "set menu transition effect");
792 CHECK_NULL_VOID(menuWrapperNode);
793 auto pattern = menuWrapperNode->GetPattern<MenuWrapperPattern>();
794 CHECK_NULL_VOID(pattern);
795 pattern->SetHasTransitionEffect(menuParam.hasTransitionEffect);
796 if (menuParam.hasTransitionEffect) {
797 auto renderContext = menuWrapperNode->GetRenderContext();
798 CHECK_NULL_VOID(renderContext);
799 CHECK_NULL_VOID(menuParam.transition);
800 renderContext->UpdateChainedTransition(menuParam.transition);
801 }
802 pattern->SetHasPreviewTransitionEffect(menuParam.hasPreviewTransitionEffect);
803 if (menuParam.hasPreviewTransitionEffect) {
804 auto previewChild = pattern->GetPreview();
805 CHECK_NULL_VOID(previewChild);
806 auto renderContext = previewChild->GetRenderContext();
807 CHECK_NULL_VOID(renderContext);
808 CHECK_NULL_VOID(menuParam.previewTransition);
809 renderContext->UpdateChainedTransition(menuParam.previewTransition);
810 }
811 }
812
GetMenuChild(const RefPtr<UINode> & node)813 RefPtr<FrameNode> MenuWrapperPattern::GetMenuChild(const RefPtr<UINode>& node)
814 {
815 CHECK_NULL_RETURN(node, nullptr);
816 RefPtr<FrameNode> menuChild;
817 auto child = node->GetChildAtIndex(0);
818 while (child) {
819 if (child->GetTag() == V2::JS_VIEW_ETS_TAG) {
820 auto customNode = DynamicCast<CustomNode>(child);
821 CHECK_NULL_RETURN(customNode, nullptr);
822 customNode->Render();
823 } else if (child->GetTag() == V2::MENU_ETS_TAG) {
824 menuChild = DynamicCast<FrameNode>(child);
825 break;
826 }
827 child = child->GetChildAtIndex(0);
828 }
829 return menuChild;
830 }
831
ClearAllSubMenu()832 void MenuWrapperPattern::ClearAllSubMenu()
833 {
834 auto host = GetHost();
835 CHECK_NULL_VOID(host);
836 auto children = host->GetChildren();
837 for (auto child = children.rbegin(); child != children.rend(); ++child) {
838 auto frameNode = DynamicCast<FrameNode>(*child);
839 if (!frameNode) {
840 continue;
841 }
842 auto pattern = frameNode->GetPattern<MenuPattern>();
843 if (pattern && pattern->IsSubMenu()) {
844 host->RemoveChild(frameNode);
845 host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF_AND_CHILD);
846 }
847 }
848 }
849
850
RequestPathRender()851 void MenuWrapperPattern::RequestPathRender()
852 {
853 auto host = GetHost();
854 CHECK_NULL_VOID(host);
855 auto paintProperty = host->GetPaintProperty<MenuWrapperPaintProperty>();
856 CHECK_NULL_VOID(paintProperty);
857 auto flag = paintProperty->GetRenderFlagValue(0);
858 flag++;
859 paintProperty->UpdateRenderFlag(flag);
860 host->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
861 }
862
IsMenuPreviewNode(const RefPtr<FrameNode> & frameNode) const863 bool MenuWrapperPattern::IsMenuPreviewNode(const RefPtr<FrameNode>& frameNode) const
864 {
865 if (GetPreviewMode() == MenuPreviewMode::NONE) {
866 return false;
867 }
868
869 CHECK_NULL_RETURN(frameNode, false);
870 auto tag = frameNode->GetTag();
871 auto isPreviewTag = tag == V2::IMAGE_ETS_TAG || tag == V2::MENU_PREVIEW_ETS_TAG || tag == V2::FLEX_ETS_TAG;
872 CHECK_NULL_RETURN(isPreviewTag, false);
873
874 auto host = GetHost();
875 CHECK_NULL_RETURN(host, false);
876 auto preview = host->GetChildAtIndex(1);
877 CHECK_NULL_RETURN(preview, false);
878 return preview->GetId() == frameNode->GetId();
879 }
880
DumpInfo()881 void MenuWrapperPattern::DumpInfo()
882 {
883 DumpLog::GetInstance().AddDesc("MenuPreviewMode: " + std::to_string(dumpInfo_.menuPreviewMode));
884 DumpLog::GetInstance().AddDesc("MenuType: " + std::to_string(dumpInfo_.menuType));
885 DumpLog::GetInstance().AddDesc("EnableArrow: " + std::to_string(dumpInfo_.enableArrow));
886 DumpLog::GetInstance().AddDesc("Offset: " + dumpInfo_.offset.ToString());
887 DumpLog::GetInstance().AddDesc("TargetNode: " + dumpInfo_.targetNode);
888 DumpLog::GetInstance().AddDesc("TargetOffset: " + dumpInfo_.targetOffset.ToString());
889 DumpLog::GetInstance().AddDesc("TargetSize: " + dumpInfo_.targetSize.ToString());
890 DumpLog::GetInstance().AddDesc("MenuWindowRect: " + dumpInfo_.menuWindowRect.ToString());
891 DumpLog::GetInstance().AddDesc("WrapperRect: " + dumpInfo_.wrapperRect.ToString());
892 DumpLog::GetInstance().AddDesc("PreviewBeginScale: " + std::to_string(dumpInfo_.previewBeginScale));
893 DumpLog::GetInstance().AddDesc("PreviewEndScale: " + std::to_string(dumpInfo_.previewEndScale));
894 DumpLog::GetInstance().AddDesc("Top: " + std::to_string(dumpInfo_.top));
895 DumpLog::GetInstance().AddDesc("Bottom: " + std::to_string(dumpInfo_.bottom));
896 DumpLog::GetInstance().AddDesc("Left: " + std::to_string(dumpInfo_.left));
897 DumpLog::GetInstance().AddDesc("Right: " + std::to_string(dumpInfo_.right));
898 DumpLog::GetInstance().AddDesc("GlobalLocation: " + dumpInfo_.globalLocation.ToString());
899 DumpLog::GetInstance().AddDesc("OriginPlacement: " + dumpInfo_.originPlacement);
900 DumpLog::GetInstance().AddDesc("DefaultPlacement: " + dumpInfo_.defaultPlacement);
901 DumpLog::GetInstance().AddDesc("FinalPosition: " + dumpInfo_.finalPosition.ToString());
902 DumpLog::GetInstance().AddDesc("FinalPlacement: " + dumpInfo_.finalPlacement);
903 }
904
DumpInfo(std::unique_ptr<JsonValue> & json)905 void MenuWrapperPattern::DumpInfo(std::unique_ptr<JsonValue>& json)
906 {
907 json->Put("MenuPreviewMode", std::to_string(dumpInfo_.menuPreviewMode).c_str());
908 json->Put("MenuType", std::to_string(dumpInfo_.menuType).c_str());
909 json->Put("EnableArrow", std::to_string(dumpInfo_.enableArrow).c_str());
910 json->Put("TargetNode", dumpInfo_.targetNode.c_str());
911 json->Put("TargetOffset", dumpInfo_.targetOffset.ToString().c_str());
912
913 json->Put("TargetSize", dumpInfo_.targetSize.ToString().c_str());
914 json->Put("MenuWindowRect", dumpInfo_.menuWindowRect.ToString().c_str());
915 json->Put("WrapperRect", dumpInfo_.wrapperRect.ToString().c_str());
916 json->Put("PreviewBeginScale", std::to_string(dumpInfo_.previewBeginScale).c_str());
917 json->Put("PreviewEndScale", std::to_string(dumpInfo_.previewEndScale).c_str());
918 json->Put("Top", std::to_string(dumpInfo_.top).c_str());
919 json->Put("Bottom", std::to_string(dumpInfo_.bottom).c_str());
920 json->Put("Left", std::to_string(dumpInfo_.left).c_str());
921 json->Put("Right", std::to_string(dumpInfo_.right).c_str());
922 json->Put("GlobalLocation", dumpInfo_.globalLocation.ToString().c_str());
923 json->Put("OriginPlacement", dumpInfo_.originPlacement.c_str());
924 json->Put("DefaultPlacement", dumpInfo_.defaultPlacement.c_str());
925 json->Put("FinalPosition", dumpInfo_.finalPosition.ToString().c_str());
926 json->Put("FinalPlacement", dumpInfo_.finalPlacement.c_str());
927 }
928
CheckPointInMenuZone(const RefPtr<FrameNode> & node,const PointF & point)929 bool MenuWrapperPattern::CheckPointInMenuZone(const RefPtr<FrameNode>& node, const PointF& point)
930 {
931 CHECK_NULL_RETURN(node, false);
932 auto childOffset = node->GetPaintRectOffset(false, true);
933 auto childSize = node->GetPaintRectWithTransform();
934 auto menuZone = RectF(childOffset.GetX(), childOffset.GetY(), childSize.Width(), childSize.Height());
935 return menuZone.IsInRegion(point);
936 }
937 } // namespace OHOS::Ace::NG
938