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