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
GetInnerMenu(RefPtr<UINode> & innerMenuNode,const PointF & position)69 bool MenuWrapperPattern::GetInnerMenu(RefPtr<UINode>& innerMenuNode, const PointF& position)
70 {
71 auto host = GetHost();
72 CHECK_NULL_RETURN(host, false);
73 auto children = host->GetChildren();
74 CHECK_NULL_RETURN(!children.empty(), false);
75 for (auto iter = children.rbegin(); iter != children.rend(); ++iter) {
76 auto& child = *iter;
77 if (!child || child->GetTag() != V2::MENU_ETS_TAG) {
78 continue;
79 }
80 auto frameNode = AceType::DynamicCast<FrameNode>(child);
81 CHECK_NULL_RETURN(frameNode, false);
82 auto menuPattern = frameNode->GetPattern<MenuPattern>();
83 CHECK_NULL_RETURN(menuPattern, false);
84 auto subMenuNode = menuPattern->GetShowedSubMenu();
85 innerMenuNode = subMenuNode ? subMenuNode : menuPattern->GetFirstInnerMenu();
86 if (!innerMenuNode) {
87 innerMenuNode = child;
88 }
89 auto geometryNode = frameNode->GetGeometryNode();
90 CHECK_NULL_RETURN(geometryNode, false);
91 return CheckPointInMenuZone(frameNode, position);
92 }
93 return false;
94 }
95
FindTouchedMenuItem(const RefPtr<UINode> & menuNode,const PointF & position)96 RefPtr<FrameNode> MenuWrapperPattern::FindTouchedMenuItem(const RefPtr<UINode>& menuNode, const PointF& position)
97 {
98 CHECK_NULL_RETURN(menuNode, nullptr);
99 RefPtr<FrameNode> menuItem = nullptr;
100 const auto& children = menuNode->GetChildren();
101 for (auto child : children) {
102 if (child->GetTag() == V2::MENU_ITEM_ETS_TAG) {
103 auto frameNode = AceType::DynamicCast<FrameNode>(child);
104 auto pattern = frameNode ? frameNode->GetPattern<MenuItemPattern>() : nullptr;
105 menuItem = pattern ? pattern->FindTouchedEmbeddedMenuItem(position) : nullptr;
106 } else {
107 menuItem = FindTouchedMenuItem(child, position);
108 }
109 if (menuItem) {
110 if (CheckPointInMenuZone(menuItem, position)) {
111 break;
112 } else {
113 menuItem = nullptr;
114 }
115 }
116 }
117 return menuItem;
118 }
119
HandleInteraction(const TouchEventInfo & info)120 void MenuWrapperPattern::HandleInteraction(const TouchEventInfo& info)
121 {
122 CHECK_NULL_VOID(!info.GetChangedTouches().empty());
123 auto touch = info.GetChangedTouches().front();
124 auto host = GetHost();
125 CHECK_NULL_VOID(host);
126 auto position = PointF(
127 static_cast<float>(touch.GetGlobalLocation().GetX()), static_cast<float>(touch.GetGlobalLocation().GetY()));
128 RefPtr<UINode> innerMenuNode = nullptr;
129 auto isInRegion = GetInnerMenu(innerMenuNode, position);
130 CHECK_NULL_VOID(innerMenuNode);
131
132 ClearLastMenuItem();
133 // get menuNode's touch region
134 if (isInRegion) {
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 auto subMenu = host->GetChildren().back();
248 auto focusMenu = MenuFocusViewShow();
249 CHECK_NULL_VOID(focusMenu);
250 menuPattern->SetShowedSubMenu(nullptr);
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 = PointF(
410 static_cast<float>(touch.GetGlobalLocation().GetX()), static_cast<float>(touch.GetGlobalLocation().GetY()));
411 auto children = host->GetChildren();
412 if (touch.GetTouchType() == TouchType::DOWN) {
413 // Record the latest touch finger ID. If other fingers are pressed, the latest one prevails
414 if (fingerId_ != -1) {
415 ClearLastMenuItem();
416 }
417 fingerId_ = touch.GetFingerId();
418 TAG_LOGD(AceLogTag::ACE_MENU, "record newest finger ID %{public}d", fingerId_);
419 for (auto child = children.rbegin(); child != children.rend(); ++child) {
420 // get child frame node of menu wrapper
421 auto menuWrapperChildNode = DynamicCast<FrameNode>(*child);
422 CHECK_NULL_VOID(menuWrapperChildNode);
423 // get menuWrapperChildNode's touch region
424 if (CheckPointInMenuZone(menuWrapperChildNode, position)) {
425 currentTouchItem_ = FindTouchedMenuItem(menuWrapperChildNode, position);
426 ChangeCurMenuItemBgColor();
427 return;
428 }
429 // if DOWN-touched outside the menu region, then hide menu
430 auto menuPattern = menuWrapperChildNode->GetPattern<MenuPattern>();
431 if (!menuPattern) {
432 continue;
433 }
434 HideMenu(menuPattern, menuWrapperChildNode, position);
435 }
436 return;
437 }
438 // When the Move or Up event is not the recorded finger ID, this event is not responded
439 if (fingerId_ != touch.GetFingerId()) {
440 return;
441 }
442 ChangeTouchItem(info, touch.GetTouchType());
443 }
444
ChangeTouchItem(const TouchEventInfo & info,TouchType touchType)445 void MenuWrapperPattern::ChangeTouchItem(const TouchEventInfo& info, TouchType touchType)
446 {
447 auto host = GetHost();
448 CHECK_NULL_VOID(host);
449 if (touchType == TouchType::MOVE) {
450 auto menuNode = DynamicCast<FrameNode>(host->GetChildAtIndex(0));
451 CHECK_NULL_VOID(menuNode);
452 if (GetPreviewMode() != MenuPreviewMode::NONE || IsSelectOverlayCustomMenu(menuNode)) {
453 return;
454 }
455 HandleInteraction(info);
456 } else if (touchType == TouchType::UP || touchType == TouchType::CANCEL) {
457 if (currentTouchItem_) {
458 auto currentTouchItemPattern = currentTouchItem_->GetPattern<MenuItemPattern>();
459 CHECK_NULL_VOID(currentTouchItemPattern);
460 currentTouchItemPattern->NotifyPressStatus(false);
461 currentTouchItem_ = nullptr;
462 }
463 // Reset finger ID when touch Up or Cancel
464 TAG_LOGD(AceLogTag::ACE_MENU, "reset finger ID %{public}d", fingerId_);
465 fingerId_ = -1;
466 }
467 }
468
HideMenu(const RefPtr<MenuPattern> & menuPattern,const RefPtr<FrameNode> & menu,const PointF & position)469 void MenuWrapperPattern::HideMenu(const RefPtr<MenuPattern>& menuPattern, const RefPtr<FrameNode>& menu,
470 const PointF& position)
471 {
472 auto host = GetHost();
473 CHECK_NULL_VOID(host);
474 auto mainMenu = DynamicCast<FrameNode>(host->GetFirstChild());
475 CHECK_NULL_VOID(mainMenu);
476 bool isFindTargetId = false;
477 if (CheckPointInMenuZone(mainMenu, position)) {
478 isFindTargetId = true;
479 }
480 if (menuPattern->IsSubMenu() || menuPattern->IsSelectOverlaySubMenu()) {
481 if (HasStackSubMenu() && !isFindTargetId) {
482 UpdateMenuAnimation(host);
483 }
484 HideSubMenu();
485 } else {
486 if (HasEmbeddedSubMenu() && embeddedSubMenuCount_ > 0 && !isFindTargetId) {
487 UpdateMenuAnimation(host);
488 }
489 HideMenu(menu);
490 }
491 }
492
UpdateMenuAnimation(const RefPtr<FrameNode> & host)493 void MenuWrapperPattern::UpdateMenuAnimation(const RefPtr<FrameNode>& host)
494 {
495 // update Menu disappear animation direction
496 // change to LEFT_BOTTOM -> RIGHT_TOP by calling SetExitAnimation
497 // or keep BOTTOM -> TOP by default
498 CHECK_NULL_VOID(host);
499 auto outterMenu = host->GetFirstChild();
500 CHECK_NULL_VOID(outterMenu);
501 auto innerMenu = GetMenuChild(outterMenu);
502 if (!innerMenu && host->GetChildren().size() > 1) {
503 SetExitAnimation(host);
504 return;
505 }
506 CHECK_NULL_VOID(innerMenu);
507 auto innerMenuPattern = innerMenu->GetPattern<MenuPattern>();
508 CHECK_NULL_VOID(innerMenuPattern);
509 auto layoutProps = innerMenuPattern->GetLayoutProperty<MenuLayoutProperty>();
510 CHECK_NULL_VOID(layoutProps);
511 auto expandingMode = layoutProps->GetExpandingMode().value_or(SubMenuExpandingMode::SIDE);
512 if (host->GetChildren().size() > 1) {
513 SetExitAnimation(host);
514 }
515 if (expandingMode == SubMenuExpandingMode::EMBEDDED && embeddedSubMenuCount_ > 0) {
516 SetExitAnimation(host);
517 }
518 }
519
SetExitAnimation(const RefPtr<FrameNode> & host)520 void MenuWrapperPattern::SetExitAnimation(const RefPtr<FrameNode>& host)
521 {
522 CHECK_NULL_VOID(host);
523 auto outterMenu = AceType::DynamicCast<FrameNode>(host->GetFirstChild());
524 CHECK_NULL_VOID(outterMenu);
525 auto outterMenuPattern = outterMenu->GetPattern<MenuPattern>();
526 CHECK_NULL_VOID(outterMenuPattern);
527 outterMenuPattern->SetDisappearAnimation(false);
528 }
529
CheckAndShowAnimation()530 void MenuWrapperPattern::CheckAndShowAnimation()
531 {
532 if (isFirstShow_) {
533 // only start animation when menu wrapper mount on.
534 StartShowAnimation();
535 isFirstShow_ = false;
536 }
537 }
538
MarkWholeSubTreeNoDraggable(const RefPtr<FrameNode> & frameNode)539 void MenuWrapperPattern::MarkWholeSubTreeNoDraggable(const RefPtr<FrameNode>& frameNode)
540 {
541 CHECK_NULL_VOID(frameNode);
542 auto eventHub = frameNode->GetEventHub<EventHub>();
543 CHECK_NULL_VOID(eventHub);
544 auto gestureEventHub = eventHub->GetGestureEventHub();
545 CHECK_NULL_VOID(gestureEventHub);
546 gestureEventHub->SetDragForbiddenForcely(true);
547 }
548
MarkAllMenuNoDraggable()549 void MenuWrapperPattern::MarkAllMenuNoDraggable()
550 {
551 auto host = GetHost();
552 CHECK_NULL_VOID(host);
553 for (const auto& child : host->GetChildren()) {
554 auto node = DynamicCast<FrameNode>(child);
555 if (node && node->GetTag() == V2::MENU_ETS_TAG) {
556 MarkWholeSubTreeNoDraggable(node);
557 }
558 }
559 }
560
OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper> & dirty,const DirtySwapConfig & config)561 bool MenuWrapperPattern::OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper>& dirty, const DirtySwapConfig& config)
562 {
563 auto pipeline = PipelineBase::GetCurrentContext();
564 CHECK_NULL_RETURN(pipeline, false);
565 auto theme = pipeline->GetTheme<SelectTheme>();
566 CHECK_NULL_RETURN(theme, false);
567 auto expandDisplay = theme->GetExpandDisplay();
568 auto host = GetHost();
569 CHECK_NULL_RETURN(host, false);
570 auto menuNode = DynamicCast<FrameNode>(host->GetChildAtIndex(0));
571 CHECK_NULL_RETURN(menuNode, false);
572 auto menuPattern = AceType::DynamicCast<MenuPattern>(menuNode->GetPattern());
573 CHECK_NULL_RETURN(menuPattern, false);
574 // copy menu pattern properties to rootMenu
575 auto layoutProperty = menuPattern->GetLayoutProperty<MenuLayoutProperty>();
576 CHECK_NULL_RETURN(layoutProperty, false);
577 isShowInSubWindow_ = layoutProperty->GetShowInSubWindowValue(true);
578 if ((IsContextMenu() && !IsHide()) || ((expandDisplay && isShowInSubWindow_) && !IsHide())) {
579 SetHotAreas(dirty);
580 }
581 MarkAllMenuNoDraggable();
582 MarkWholeSubTreeNoDraggable(GetPreview());
583 CheckAndShowAnimation();
584 return false;
585 }
586
SetHotAreas(const RefPtr<LayoutWrapper> & layoutWrapper)587 void MenuWrapperPattern::SetHotAreas(const RefPtr<LayoutWrapper>& layoutWrapper)
588 {
589 auto pipeline = PipelineBase::GetCurrentContext();
590 CHECK_NULL_VOID(pipeline);
591 auto theme = pipeline->GetTheme<SelectTheme>();
592 CHECK_NULL_VOID(theme);
593 auto expandDisplay = theme->GetExpandDisplay();
594 if ((layoutWrapper->GetAllChildrenWithBuild().empty() || !IsContextMenu()) &&
595 !(expandDisplay && isShowInSubWindow_)) {
596 return;
597 }
598 auto layoutProps = layoutWrapper->GetLayoutProperty();
599 CHECK_NULL_VOID(layoutProps);
600 float safeAreaInsetsLeft = 0.0f;
601 float safeAreaInsetsTop = 0.0f;
602 auto&& safeAreaInsets = layoutProps->GetSafeAreaInsets();
603 if (safeAreaInsets) {
604 safeAreaInsetsLeft = static_cast<float>(safeAreaInsets->left_.end);
605 safeAreaInsetsTop = static_cast<float>(safeAreaInsets->top_.end);
606 }
607 std::vector<Rect> rects;
608 for (const auto& child : layoutWrapper->GetAllChildrenWithBuild()) {
609 auto frameRect = child->GetGeometryNode()->GetFrameRect();
610 // rect is relative to window
611 auto rect = Rect(frameRect.GetX() + safeAreaInsetsLeft, frameRect.GetY() + safeAreaInsetsTop, frameRect.Width(),
612 frameRect.Height());
613
614 rects.emplace_back(rect);
615 }
616 // If container is UIExtensionWindow, set hotArea size equals to subwindow's filterColumnNode's size
617 if (IsContextMenu() && GetPreviewMode() != MenuPreviewMode::NONE) {
618 auto filterNode = GetFilterColumnNode();
619 if (filterNode) {
620 auto frameRect = filterNode->GetGeometryNode()->GetFrameRect();
621 auto rect = Rect(frameRect.GetX(), frameRect.GetY(), frameRect.Width(), frameRect.Height());
622 rects.emplace_back(rect);
623 }
624 }
625 SubwindowManager::GetInstance()->SetHotAreas(rects, GetHost()->GetId(), GetContainerId());
626 }
627
StartShowAnimation()628 void MenuWrapperPattern::StartShowAnimation()
629 {
630 auto host = GetHost();
631 CHECK_NULL_VOID(host);
632 auto context = host->GetRenderContext();
633 CHECK_NULL_VOID(context);
634 if (GetPreviewMode() == MenuPreviewMode::NONE) {
635 context->UpdateOffset(GetAnimationOffset());
636 context->UpdateOpacity(0.0);
637 }
638
639 AnimationUtils::Animate(
640 animationOption_,
641 [context, weak = WeakClaim(this)]() {
642 if (context) {
643 auto pattern = weak.Upgrade();
644 CHECK_NULL_VOID(pattern);
645 if (pattern->GetPreviewMode() == MenuPreviewMode::NONE) {
646 context->UpdateOffset(OffsetT<Dimension>());
647 context->UpdateOpacity(1.0);
648 }
649 }
650 },
651 animationOption_.GetOnFinishEvent());
652 }
653
SetAniamtinOption(const AnimationOption & animationOption)654 void MenuWrapperPattern::SetAniamtinOption(const AnimationOption& animationOption)
655 {
656 animationOption_.SetCurve(animationOption.GetCurve());
657 animationOption_.SetDuration(animationOption.GetDuration());
658 animationOption_.SetFillMode(animationOption.GetFillMode());
659 animationOption_.SetOnFinishEvent(animationOption.GetOnFinishEvent());
660 }
661
GetAnimationOffset()662 OffsetT<Dimension> MenuWrapperPattern::GetAnimationOffset()
663 {
664 OffsetT<Dimension> offset;
665
666 auto pipeline = PipelineBase::GetCurrentContext();
667 CHECK_NULL_RETURN(pipeline, offset);
668 auto theme = pipeline->GetTheme<SelectTheme>();
669 CHECK_NULL_RETURN(theme, offset);
670 auto animationOffset = theme->GetMenuAnimationOffset();
671
672 switch (menuPlacement_) {
673 case Placement::LEFT:
674 case Placement::LEFT_TOP:
675 case Placement::LEFT_BOTTOM:
676 offset.SetX(animationOffset);
677 break;
678 case Placement::RIGHT:
679 case Placement::RIGHT_TOP:
680 case Placement::RIGHT_BOTTOM:
681 offset.SetX(-animationOffset);
682 break;
683 case Placement::TOP:
684 case Placement::TOP_LEFT:
685 case Placement::TOP_RIGHT:
686 offset.SetY(animationOffset);
687 break;
688 default:
689 offset.SetY(-animationOffset);
690 break;
691 }
692 return offset;
693 }
694
IsSelectOverlayCustomMenu(const RefPtr<FrameNode> & menu) const695 bool MenuWrapperPattern::IsSelectOverlayCustomMenu(const RefPtr<FrameNode>& menu) const
696 {
697 auto menuPattern = menu->GetPattern<MenuPattern>();
698 CHECK_NULL_RETURN(menuPattern, false);
699 return menuPattern->IsSelectOverlayCustomMenu();
700 }
701
RegisterMenuCallback(const RefPtr<FrameNode> & menuWrapperNode,const MenuParam & menuParam)702 void MenuWrapperPattern::RegisterMenuCallback(const RefPtr<FrameNode>& menuWrapperNode, const MenuParam& menuParam)
703 {
704 TAG_LOGD(AceLogTag::ACE_DIALOG, "register menu enter");
705 CHECK_NULL_VOID(menuWrapperNode);
706 auto pattern = menuWrapperNode->GetPattern<MenuWrapperPattern>();
707 CHECK_NULL_VOID(pattern);
708 pattern->RegisterMenuAppearCallback(menuParam.onAppear);
709 pattern->RegisterMenuDisappearCallback(menuParam.onDisappear);
710 pattern->RegisterMenuAboutToAppearCallback(menuParam.aboutToAppear);
711 pattern->RegisterMenuAboutToDisappearCallback(menuParam.aboutToDisappear);
712 pattern->RegisterMenuStateChangeCallback(menuParam.onStateChange);
713 }
714
SetMenuTransitionEffect(const RefPtr<FrameNode> & menuWrapperNode,const MenuParam & menuParam)715 void MenuWrapperPattern::SetMenuTransitionEffect(const RefPtr<FrameNode>& menuWrapperNode, const MenuParam& menuParam)
716 {
717 TAG_LOGD(AceLogTag::ACE_DIALOG, "set menu transition effect");
718 CHECK_NULL_VOID(menuWrapperNode);
719 auto pattern = menuWrapperNode->GetPattern<MenuWrapperPattern>();
720 CHECK_NULL_VOID(pattern);
721 pattern->SetHasTransitionEffect(menuParam.hasTransitionEffect);
722 if (menuParam.hasTransitionEffect) {
723 auto renderContext = menuWrapperNode->GetRenderContext();
724 CHECK_NULL_VOID(renderContext);
725 CHECK_NULL_VOID(menuParam.transition);
726 renderContext->UpdateChainedTransition(menuParam.transition);
727 }
728 pattern->SetHasPreviewTransitionEffect(menuParam.hasPreviewTransitionEffect);
729 if (menuParam.hasPreviewTransitionEffect) {
730 auto previewChild = pattern->GetPreview();
731 CHECK_NULL_VOID(previewChild);
732 auto renderContext = previewChild->GetRenderContext();
733 CHECK_NULL_VOID(renderContext);
734 CHECK_NULL_VOID(menuParam.previewTransition);
735 renderContext->UpdateChainedTransition(menuParam.previewTransition);
736 }
737 }
738
GetMenuChild(const RefPtr<UINode> & node)739 RefPtr<FrameNode> MenuWrapperPattern::GetMenuChild(const RefPtr<UINode>& node)
740 {
741 CHECK_NULL_RETURN(node, nullptr);
742 RefPtr<FrameNode> menuChild;
743 auto child = node->GetChildAtIndex(0);
744 while (child) {
745 if (child->GetTag() == V2::JS_VIEW_ETS_TAG) {
746 auto customNode = DynamicCast<CustomNode>(child);
747 CHECK_NULL_RETURN(customNode, nullptr);
748 customNode->Render();
749 } else if (child->GetTag() == V2::MENU_ETS_TAG) {
750 menuChild = DynamicCast<FrameNode>(child);
751 break;
752 }
753 child = child->GetChildAtIndex(0);
754 }
755 return menuChild;
756 }
757
ClearAllSubMenu()758 void MenuWrapperPattern::ClearAllSubMenu()
759 {
760 auto host = GetHost();
761 CHECK_NULL_VOID(host);
762 auto children = host->GetChildren();
763 for (auto child = children.rbegin(); child != children.rend(); ++child) {
764 auto frameNode = DynamicCast<FrameNode>(*child);
765 if (!frameNode) {
766 continue;
767 }
768 auto pattern = frameNode->GetPattern<MenuPattern>();
769 if (pattern && pattern->IsSubMenu()) {
770 host->RemoveChild(frameNode);
771 host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF_AND_CHILD);
772 }
773 }
774 }
775
StopHoverImageToPreviewAnimation()776 void MenuWrapperPattern::StopHoverImageToPreviewAnimation()
777 {
778 auto menuWrapperNode = GetHost();
779 CHECK_NULL_VOID(menuWrapperNode);
780 auto menuWrapperPattern = menuWrapperNode->GetPattern<MenuWrapperPattern>();
781 CHECK_NULL_VOID(menuWrapperPattern);
782
783 auto flexNode = menuWrapperPattern->GetHoverImageFlexNode();
784 CHECK_NULL_VOID(flexNode);
785 auto flexContext = flexNode->GetRenderContext();
786 CHECK_NULL_VOID(flexContext);
787
788 auto stackNode = menuWrapperPattern->GetHoverImageStackNode();
789 CHECK_NULL_VOID(stackNode);
790 auto stackContext = stackNode->GetRenderContext();
791 CHECK_NULL_VOID(stackContext);
792
793 auto menuChild = menuWrapperPattern->GetMenu();
794 CHECK_NULL_VOID(menuChild);
795 auto menuPattern = menuChild->GetPattern<MenuPattern>();
796 CHECK_NULL_VOID(menuPattern);
797 auto originPosition = menuPattern->GetPreviewOriginOffset();
798
799 auto geometryNode = flexNode->GetGeometryNode();
800 CHECK_NULL_VOID(geometryNode);
801 auto position = geometryNode->GetFrameOffset();
802
803 auto flexPosition = originPosition;
804 if (Positive(hoverImageToPreviewRate_)) {
805 flexPosition += (position - originPosition) * hoverImageToPreviewRate_;
806 }
807
808 AnimationUtils::Animate(AnimationOption(Curves::LINEAR, 0),
809 [stackContext, flexContext, flexPosition, scale = hoverImageToPreviewScale_]() {
810 if (flexContext) {
811 flexContext->UpdatePosition(
812 OffsetT<Dimension>(Dimension(flexPosition.GetX()), Dimension(flexPosition.GetY())));
813 }
814
815 CHECK_NULL_VOID(stackContext && Positive(scale));
816 stackContext->UpdateTransformScale(VectorF(scale, scale));
817 });
818 }
819
DumpInfo()820 void MenuWrapperPattern::DumpInfo()
821 {
822 DumpLog::GetInstance().AddDesc("MenuPreviewMode: " + std::to_string(dumpInfo_.menuPreviewMode));
823 DumpLog::GetInstance().AddDesc("MenuType: " + std::to_string(dumpInfo_.menuType));
824 DumpLog::GetInstance().AddDesc("EnableArrow: " + std::to_string(dumpInfo_.enableArrow));
825 DumpLog::GetInstance().AddDesc("Offset: " + dumpInfo_.offset.ToString());
826 DumpLog::GetInstance().AddDesc("TargetNode: " + dumpInfo_.targetNode);
827 DumpLog::GetInstance().AddDesc("TargetOffset: " + dumpInfo_.targetOffset.ToString());
828 DumpLog::GetInstance().AddDesc("TargetSize: " + dumpInfo_.targetSize.ToString());
829 DumpLog::GetInstance().AddDesc("MenuWindowRect: " + dumpInfo_.menuWindowRect.ToString());
830 DumpLog::GetInstance().AddDesc("WrapperRect: " + dumpInfo_.wrapperRect.ToString());
831 DumpLog::GetInstance().AddDesc("PreviewBeginScale: " + std::to_string(dumpInfo_.previewBeginScale));
832 DumpLog::GetInstance().AddDesc("PreviewEndScale: " + std::to_string(dumpInfo_.previewEndScale));
833 DumpLog::GetInstance().AddDesc("Top: " + std::to_string(dumpInfo_.top));
834 DumpLog::GetInstance().AddDesc("Bottom: " + std::to_string(dumpInfo_.bottom));
835 DumpLog::GetInstance().AddDesc("GlobalLocation: " + dumpInfo_.globalLocation.ToString());
836 DumpLog::GetInstance().AddDesc("OriginPlacement: " + dumpInfo_.originPlacement);
837 DumpLog::GetInstance().AddDesc("DefaultPlacement: " + dumpInfo_.defaultPlacement);
838 DumpLog::GetInstance().AddDesc("FinalPosition: " + dumpInfo_.finalPosition.ToString());
839 DumpLog::GetInstance().AddDesc("FinalPlacement: " + dumpInfo_.finalPlacement);
840 }
841
CheckPointInMenuZone(const RefPtr<FrameNode> & node,const PointF & point)842 bool MenuWrapperPattern::CheckPointInMenuZone(const RefPtr<FrameNode>& node, const PointF& point)
843 {
844 CHECK_NULL_RETURN(node, false);
845 auto childOffset = node->GetPaintRectOffset(false, true);
846 auto childSize = node->GetPaintRectWithTransform();
847 auto menuZone = RectF(childOffset.GetX(), childOffset.GetY(), childSize.Width(), childSize.Height());
848 return menuZone.IsInRegion(point);
849 }
850 } // namespace OHOS::Ace::NG
851