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