• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2022-2023 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/menu_pattern.h"
17 
18 #include <stack>
19 
20 #include "base/geometry/dimension.h"
21 #include "base/log/dump_log.h"
22 #include "base/memory/ace_type.h"
23 #include "base/memory/referenced.h"
24 #include "base/utils/utils.h"
25 #include "core/animation/animation_pub.h"
26 #include "core/animation/spring_curve.h"
27 #include "core/common/container.h"
28 #include "core/components/common/layout/grid_system_manager.h"
29 #include "core/components/common/properties/shadow_config.h"
30 #include "core/components/select/select_theme.h"
31 #include "core/components/theme/shadow_theme.h"
32 #include "core/components_ng/base/ui_node.h"
33 #include "core/components_ng/manager/drag_drop/utils/drag_animation_helper.h"
34 #include "core/components_ng/pattern/menu/menu_item/menu_item_layout_property.h"
35 #include "core/components_ng/pattern/menu/menu_item/menu_item_pattern.h"
36 #include "core/components_ng/pattern/menu/menu_item_group/menu_item_group_pattern.h"
37 #include "core/components_ng/pattern/menu/menu_layout_property.h"
38 #include "core/components_ng/pattern/menu/menu_theme.h"
39 #include "core/components_ng/pattern/menu/menu_view.h"
40 #include "core/components_ng/pattern/menu/multi_menu_layout_algorithm.h"
41 #include "core/components_ng/pattern/menu/preview/menu_preview_pattern.h"
42 #include "core/components_ng/pattern/menu/sub_menu_layout_algorithm.h"
43 #include "core/components_ng/pattern/menu/wrapper/menu_wrapper_pattern.h"
44 #include "core/components_ng/pattern/option/option_pattern.h"
45 #include "core/components_ng/pattern/option/option_view.h"
46 #include "core/components_ng/pattern/scroll/scroll_pattern.h"
47 #include "core/components_ng/pattern/stack/stack_pattern.h"
48 #include "core/components_ng/pattern/text/text_layout_property.h"
49 #include "core/components_ng/property/border_property.h"
50 #include "core/components_v2/inspector/inspector_constants.h"
51 #include "core/event/touch_event.h"
52 #include "core/pipeline/pipeline_base.h"
53 #include "core/pipeline_ng/pipeline_context.h"
54 
55 namespace OHOS::Ace::NG {
56 namespace {
57 constexpr float PAN_MAX_VELOCITY = 2000.0f;
58 constexpr Dimension MIN_SELECT_MENU_WIDTH = 64.0_vp;
59 constexpr int32_t COLUMN_NUM = 2;
60 constexpr int32_t STACK_EXPAND_DISAPPEAR_DURATION = 300;
61 constexpr double MENU_ORIGINAL_SCALE = 0.6f;
62 constexpr double MOUNT_MENU_OPACITY = 0.4f;
63 
64 constexpr double VELOCITY = 0.0f;
65 constexpr double MASS = 1.0f;
66 constexpr double STIFFNESS = 228.0f;
67 constexpr double DAMPING = 22.0f;
68 constexpr double STACK_MENU_DAMPING = 26.0f;
69 const RefPtr<InterpolatingSpring> MENU_ANIMATION_CURVE =
70     AceType::MakeRefPtr<InterpolatingSpring>(VELOCITY, MASS, STIFFNESS, DAMPING);
71 const RefPtr<InterpolatingSpring> MAIN_MENU_ANIMATION_CURVE =
72     AceType::MakeRefPtr<InterpolatingSpring>(0.0f, 1.0f, 528.0f, 35.0f);
73 const RefPtr<InterpolatingSpring> STACK_MENU_CURVE =
74     AceType::MakeRefPtr<InterpolatingSpring>(VELOCITY, MASS, STIFFNESS, STACK_MENU_DAMPING);
75 const RefPtr<Curve> CUSTOM_PREVIEW_ANIMATION_CURVE =
76     AceType::MakeRefPtr<InterpolatingSpring>(0.0f, 1.0f, 328.0f, 34.0f);
77 const float MINIMUM_AMPLITUDE_RATION = 0.08f;
78 
79 constexpr double MOUNT_MENU_FINAL_SCALE = 0.95f;
80 constexpr double SEMI_CIRCLE_ANGEL = 90.0f;
81 constexpr Dimension PADDING = 4.0_vp;
82 
83 
UpdateFontStyle(RefPtr<MenuLayoutProperty> & menuProperty,RefPtr<MenuItemLayoutProperty> & itemProperty,RefPtr<MenuItemPattern> & itemPattern,bool & contentChanged,bool & labelChanged)84 void UpdateFontStyle(RefPtr<MenuLayoutProperty>& menuProperty, RefPtr<MenuItemLayoutProperty>& itemProperty,
85     RefPtr<MenuItemPattern>& itemPattern, bool& contentChanged, bool& labelChanged)
86 {
87     auto contentNode = itemPattern->GetContentNode();
88     CHECK_NULL_VOID(contentNode);
89     auto textLayoutProperty = contentNode->GetLayoutProperty<TextLayoutProperty>();
90     CHECK_NULL_VOID(textLayoutProperty);
91     auto label = itemPattern->GetLabelNode();
92     RefPtr<TextLayoutProperty> labelProperty = label ? label->GetLayoutProperty<TextLayoutProperty>() : nullptr;
93     if (menuProperty->GetItalicFontStyle().has_value()) {
94         if (!itemProperty->GetItalicFontStyle().has_value()) {
95             textLayoutProperty->UpdateItalicFontStyle(menuProperty->GetItalicFontStyle().value());
96             contentChanged = true;
97         }
98         if (labelProperty && !itemProperty->GetLabelItalicFontStyle().has_value()) {
99             labelProperty->UpdateItalicFontStyle(menuProperty->GetItalicFontStyle().value());
100             labelChanged = true;
101         }
102     }
103     if (menuProperty->GetFontFamily().has_value()) {
104         if (!itemProperty->GetFontFamily().has_value()) {
105             textLayoutProperty->UpdateFontFamily(menuProperty->GetFontFamily().value());
106             contentChanged = true;
107         }
108         if (labelProperty && !itemProperty->GetLabelFontFamily().has_value()) {
109             labelProperty->UpdateFontFamily(menuProperty->GetFontFamily().value());
110             labelChanged = true;
111         }
112     }
113 }
114 
UpdateMenuItemTextNode(RefPtr<MenuLayoutProperty> & menuProperty,RefPtr<MenuItemLayoutProperty> & itemProperty,RefPtr<MenuItemPattern> & itemPattern)115 void UpdateMenuItemTextNode(RefPtr<MenuLayoutProperty>& menuProperty, RefPtr<MenuItemLayoutProperty>& itemProperty,
116     RefPtr<MenuItemPattern>& itemPattern)
117 {
118     auto contentNode = itemPattern->GetContentNode();
119     CHECK_NULL_VOID(contentNode);
120     auto textLayoutProperty = contentNode->GetLayoutProperty<TextLayoutProperty>();
121     CHECK_NULL_VOID(textLayoutProperty);
122     auto label = itemPattern->GetLabelNode();
123     RefPtr<TextLayoutProperty> labelProperty = label ? label->GetLayoutProperty<TextLayoutProperty>() : nullptr;
124     bool contentChanged = false;
125     bool labelChanged = false;
126     if (menuProperty->GetFontSize().has_value()) {
127         if (!itemProperty->GetFontSize().has_value()) {
128             textLayoutProperty->UpdateFontSize(menuProperty->GetFontSize().value());
129             contentChanged = true;
130         }
131         if (labelProperty && !itemProperty->GetLabelFontSize().has_value()) {
132             labelProperty->UpdateFontSize(menuProperty->GetFontSize().value());
133             labelChanged = true;
134         }
135     }
136     if (menuProperty->GetFontWeight().has_value()) {
137         if (!itemProperty->GetFontWeight().has_value()) {
138             textLayoutProperty->UpdateFontWeight(menuProperty->GetFontWeight().value());
139             contentChanged = true;
140         }
141         if (labelProperty && !itemProperty->GetLabelFontWeight().has_value()) {
142             labelProperty->UpdateFontWeight(menuProperty->GetFontWeight().value());
143             labelChanged = true;
144         }
145     }
146     if (menuProperty->GetFontColor().has_value()) {
147         if (!itemProperty->GetFontColor().has_value()) {
148             textLayoutProperty->UpdateTextColor(menuProperty->GetFontColor().value());
149             contentChanged = true;
150         }
151         if (labelProperty && !itemProperty->GetLabelFontColor().has_value()) {
152             labelProperty->UpdateTextColor(menuProperty->GetFontColor().value());
153             labelChanged = true;
154         }
155     }
156     UpdateFontStyle(menuProperty, itemProperty, itemPattern, contentChanged, labelChanged);
157     if (contentChanged) {
158         contentNode->MarkModifyDone();
159         contentNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
160     }
161     if (labelChanged) {
162         label->MarkModifyDone();
163         label->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
164     }
165 }
166 
ShowMenuOpacityAnimation(const RefPtr<MenuTheme> & menuTheme,const RefPtr<RenderContext> & renderContext,int32_t delay)167 void ShowMenuOpacityAnimation(const RefPtr<MenuTheme>& menuTheme, const RefPtr<RenderContext>& renderContext,
168     int32_t delay)
169 {
170     CHECK_NULL_VOID(menuTheme);
171     CHECK_NULL_VOID(renderContext);
172 
173     renderContext->UpdateOpacity(0.0);
174     AnimationOption option = AnimationOption();
175     option.SetCurve(Curves::FRICTION);
176     option.SetDuration(menuTheme->GetContextMenuAppearDuration());
177     option.SetDelay(delay);
178     AnimationUtils::Animate(option, [renderContext]() {
179         if (renderContext) {
180             renderContext->UpdateOpacity(1.0);
181         }
182     });
183 }
184 } // namespace
185 
OnAttachToFrameNode()186 void MenuPattern::OnAttachToFrameNode()
187 {
188     RegisterOnTouch();
189     auto host = GetHost();
190     CHECK_NULL_VOID(host);
191     auto focusHub = host->GetOrCreateFocusHub();
192     CHECK_NULL_VOID(focusHub);
193     RegisterOnKeyEvent(focusHub);
194     DisableTabInMenu();
195     InitTheme(host);
196     auto pipelineContext = host->GetContextWithCheck();
197     CHECK_NULL_VOID(pipelineContext);
198     auto targetNode = FrameNode::GetFrameNode(targetTag_, targetId_);
199     CHECK_NULL_VOID(targetNode);
200     auto eventHub = targetNode->GetEventHub<EventHub>();
201     CHECK_NULL_VOID(eventHub);
202     OnAreaChangedFunc onAreaChangedFunc = [menuNodeWk = WeakPtr<FrameNode>(host)](const RectF& /* oldRect */,
203                                               const OffsetF& /* oldOrigin */, const RectF& /* rect */,
204                                               const OffsetF& /* origin */) {
205         auto menuNode = menuNodeWk.Upgrade();
206         CHECK_NULL_VOID(menuNode);
207         auto menuPattern = menuNode->GetPattern<MenuPattern>();
208         CHECK_NULL_VOID(menuPattern);
209         auto menuWarpper = menuPattern->GetMenuWrapper();
210         CHECK_NULL_VOID(menuWarpper);
211         auto warpperPattern = menuWarpper->GetPattern<MenuWrapperPattern>();
212         CHECK_NULL_VOID(warpperPattern);
213         if (!warpperPattern->IsHide()) {
214             menuNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
215         }
216     };
217     eventHub->AddInnerOnAreaChangedCallback(host->GetId(), std::move(onAreaChangedFunc));
218 
219     auto foldModeChangeCallback = [weak = WeakClaim(this)](FoldDisplayMode foldDisplayMode) {
220         auto menuPattern = weak.Upgrade();
221         CHECK_NULL_VOID(menuPattern);
222         auto menuWrapper = menuPattern->GetMenuWrapper();
223         CHECK_NULL_VOID(menuWrapper);
224         auto wrapperPattern = menuWrapper->GetPattern<MenuWrapperPattern>();
225         CHECK_NULL_VOID(wrapperPattern);
226         wrapperPattern->SetHasFoldModeChangedTransition(true);
227     };
228     foldDisplayModeChangedCallbackId_ =
229         pipelineContext->RegisterFoldDisplayModeChangedCallback(std::move(foldModeChangeCallback));
230 }
231 
OnDetachFromFrameNode(FrameNode * frameNode)232 void MenuPattern::OnDetachFromFrameNode(FrameNode* frameNode)
233 {
234     CHECK_NULL_VOID(frameNode);
235     auto targetNode = FrameNode::GetFrameNode(targetTag_, targetId_);
236     CHECK_NULL_VOID(targetNode);
237     auto eventHub = targetNode->GetEventHub<EventHub>();
238     CHECK_NULL_VOID(eventHub);
239     eventHub->RemoveInnerOnAreaChangedCallback(frameNode->GetId());
240 
241     if (foldDisplayModeChangedCallbackId_.has_value()) {
242         auto pipeline = frameNode->GetContext();
243         CHECK_NULL_VOID(pipeline);
244         pipeline->UnRegisterFoldDisplayModeChangedCallback(foldDisplayModeChangedCallbackId_.value_or(-1));
245     }
246 }
247 
OnModifyDone()248 void MenuPattern::OnModifyDone()
249 {
250     Pattern::OnModifyDone();
251     auto host = GetHost();
252     CHECK_NULL_VOID(host);
253     isNeedDivider_ = false;
254     auto uiNode = AceType::DynamicCast<UINode>(host);
255     UpdateMenuItemChildren(uiNode);
256 
257     auto innerMenuCount = GetInnerMenuCount();
258     if (innerMenuCount == 1) {
259         ResetTheme(host, false);
260     } else if (innerMenuCount > 1) {
261         // multiple inner menus, reset outer container's shadow for desktop UX
262         ResetTheme(host, true);
263     }
264 
265     auto menuWrapperNode = GetMenuWrapper();
266     CHECK_NULL_VOID(menuWrapperNode);
267     auto menuWrapperPattern = menuWrapperNode->GetPattern<MenuWrapperPattern>();
268     CHECK_NULL_VOID(menuWrapperPattern);
269     if (!menuWrapperPattern->GetHasCustomRadius()) {
270         auto menuFirstNode = GetFirstInnerMenu();
271         if (menuFirstNode) {
272             CopyMenuAttr(menuFirstNode);
273         }
274     }
275 
276     auto menuLayoutProperty = GetLayoutProperty<MenuLayoutProperty>();
277     CHECK_NULL_VOID(menuLayoutProperty);
278     if (menuLayoutProperty->GetBorderRadius().has_value()) {
279         BorderRadiusProperty borderRadius = menuLayoutProperty->GetBorderRadiusValue();
280         UpdateBorderRadius(host, borderRadius);
281     }
282 
283     SetAccessibilityAction();
284 
285     if (previewMode_ != MenuPreviewMode::NONE) {
286         auto node = host->GetChildren().front();
287         CHECK_NULL_VOID(node);
288         auto scroll = AceType::DynamicCast<FrameNode>(node);
289         CHECK_NULL_VOID(scroll);
290         auto hub = scroll->GetEventHub<EventHub>();
291         CHECK_NULL_VOID(hub);
292         auto gestureHub = hub->GetOrCreateGestureEventHub();
293         CHECK_NULL_VOID(gestureHub);
294         InitPanEvent(gestureHub);
295     }
296 }
297 
CreateMenuScroll(const RefPtr<UINode> & node)298 RefPtr<FrameNode> CreateMenuScroll(const RefPtr<UINode>& node)
299 {
300     auto scroll = FrameNode::CreateFrameNode(
301         V2::SCROLL_ETS_TAG, ElementRegister::GetInstance()->MakeUniqueId(), AceType::MakeRefPtr<ScrollPattern>());
302     CHECK_NULL_RETURN(scroll, nullptr);
303     auto props = scroll->GetLayoutProperty<ScrollLayoutProperty>();
304     props->UpdateAxis(Axis::VERTICAL);
305     props->UpdateAlignment(Alignment::CENTER_LEFT);
306     auto pipeline = PipelineBase::GetCurrentContext();
307     CHECK_NULL_RETURN(pipeline, nullptr);
308     auto theme = pipeline->GetTheme<SelectTheme>();
309     CHECK_NULL_RETURN(theme, nullptr);
310     auto contentPadding = static_cast<float>(theme->GetOutPadding().ConvertToPx());
311     PaddingProperty padding;
312     padding.left = padding.right = padding.top = padding.bottom = CalcLength(contentPadding);
313     props->UpdatePadding(padding);
314     if (node) {
315         node->MountToParent(scroll);
316     }
317     auto renderContext = scroll->GetRenderContext();
318     CHECK_NULL_RETURN(renderContext, nullptr);
319     BorderRadiusProperty borderRadius;
320     if (Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE)) {
321         borderRadius.SetRadius(theme->GetMenuDefaultRadius());
322     } else {
323         borderRadius.SetRadius(theme->GetMenuBorderRadius());
324     }
325     renderContext->UpdateBorderRadius(borderRadius);
326     return scroll;
327 }
328 
FireBuilder()329 void MenuPattern::FireBuilder()
330 {
331     auto host = GetHost();
332     CHECK_NULL_VOID(host);
333     host->RemoveChild(builderNode_.Upgrade());
334     if (!makeFunc_.has_value()) {
335         return;
336     }
337     auto column = FrameNode::CreateFrameNode(V2::COLUMN_ETS_TAG, ElementRegister::GetInstance()->MakeUniqueId(),
338         AceType::MakeRefPtr<LinearLayoutPattern>(true));
339     auto scroll = CreateMenuScroll(column);
340     CHECK_NULL_VOID(scroll);
341     builderNode_ = scroll;
342     for (size_t i = 0; i < selectProperties_.size(); i++) {
343         auto contentModifierNode = BuildContentModifierNode(i);
344         if (contentModifierNode) {
345             contentModifierNode->MarkModifyDone();
346             contentModifierNode->MountToParent(column);
347         }
348     }
349     auto scrollPattern = scroll->GetPattern<ScrollPattern>();
350     CHECK_NULL_VOID(scrollPattern);
351     scrollPattern->SetIsSelectScroll(true);
352     scroll->MountToParent(host);
353     scroll->MarkModifyDone();
354     host->MarkModifyDone();
355     SetIsSelectMenu(true);
356 }
357 
BuildContentModifierNode(int index)358 RefPtr<FrameNode> MenuPattern::BuildContentModifierNode(int index)
359 {
360     if (!makeFunc_.has_value()) {
361         return nullptr;
362     }
363     auto property = selectProperties_[index];
364     MenuItemConfiguration menuItemConfiguration(property.value, property.icon, property.symbolModifier,
365         index, property.selected, property.selectEnable);
366     return (makeFunc_.value())(menuItemConfiguration);
367 }
368 
UpdateSelectIndex(int32_t index)369 void MenuPattern::UpdateSelectIndex(int32_t index)
370 {
371     for (size_t i = 0; i < selectParams_.size(); i++) {
372         selectProperties_[i].selected = index == static_cast<int32_t>(i);
373     }
374     FireBuilder();
375 }
376 
BeforeCreateLayoutWrapper()377 void InnerMenuPattern::BeforeCreateLayoutWrapper()
378 {
379     RecordItemsAndGroups();
380 
381     // determine menu type based on sibling menu count
382     auto count = FindSiblingMenuCount();
383     if (count > 0) {
384         SetType(MenuType::DESKTOP_MENU);
385         ApplyDesktopMenuTheme();
386     } else {
387         SetType(MenuType::MULTI_MENU);
388         ApplyMultiMenuTheme();
389     }
390 }
391 
OnModifyDone()392 void InnerMenuPattern::OnModifyDone()
393 {
394     Pattern::OnModifyDone();
395     auto host = GetHost();
396     CHECK_NULL_VOID(host);
397     ResetNeedDivider();
398     auto uiNode = AceType::DynamicCast<UINode>(host);
399     UpdateMenuItemChildren(uiNode);
400     SetAccessibilityAction();
401 }
402 
403 // close menu on touch up
RegisterOnTouch()404 void MenuPattern::RegisterOnTouch()
405 {
406     CHECK_NULL_VOID(!onTouch_);
407     auto host = GetHost();
408     CHECK_NULL_VOID(host);
409     auto gesture = host->GetOrCreateGestureEventHub();
410     CHECK_NULL_VOID(gesture);
411     auto touchTask = [weak = WeakClaim(this)](const TouchEventInfo& info) {
412         auto pattern = weak.Upgrade();
413         CHECK_NULL_VOID(pattern);
414         pattern->OnTouchEvent(info);
415     };
416     onTouch_ = MakeRefPtr<TouchEventImpl>(std::move(touchTask));
417     gesture->AddTouchEvent(onTouch_);
418 }
419 
OnTouchEvent(const TouchEventInfo & info)420 void MenuPattern::OnTouchEvent(const TouchEventInfo& info)
421 {
422     if (GetInnerMenuCount() > 0 || IsMultiMenu() || IsDesktopMenu()|| IsSelectOverlayCustomMenu()) {
423         // not click hide menu for multi menu or select overlay custom menu
424         return;
425     }
426     if (!options_.empty()) {
427         // not click hide menu for select and bindMenu default option
428         return;
429     }
430     if (!needHideAfterTouch_) {
431         // not click hide menu if needn't hide after touch
432         return;
433     }
434     auto touchType = info.GetTouches().front().GetTouchType();
435     if (touchType == TouchType::DOWN) {
436         lastTouchOffset_ = info.GetTouches().front().GetLocalLocation();
437     } else if (touchType == TouchType::UP) {
438         auto touchUpOffset = info.GetTouches().front().GetLocalLocation();
439         if (lastTouchOffset_.has_value() &&
440             (touchUpOffset - lastTouchOffset_.value()).GetDistance() <= DEFAULT_CLICK_DISTANCE) {
441             auto touchGlobalLocation = info.GetTouches().front().GetGlobalLocation();
442             auto position = OffsetF(static_cast<float>(touchGlobalLocation.GetX()),
443                 static_cast<float>(touchGlobalLocation.GetY()));
444             TAG_LOGI(AceLogTag::ACE_MENU, "will hide menu, position is %{public}s.", position.ToString().c_str());
445             HideMenu(true, position);
446         }
447         lastTouchOffset_.reset();
448     }
449 }
450 
RegisterOnKeyEvent(const RefPtr<FocusHub> & focusHub)451 void MenuPattern::RegisterOnKeyEvent(const RefPtr<FocusHub>& focusHub)
452 {
453     auto onKeyEvent = [wp = WeakClaim(this)](const KeyEvent& event) -> bool {
454         auto pattern = wp.Upgrade();
455         CHECK_NULL_RETURN(pattern, false);
456         return pattern->OnKeyEvent(event);
457     };
458     focusHub->SetOnKeyEventInternal(std::move(onKeyEvent));
459 }
460 
OnKeyEvent(const KeyEvent & event) const461 bool MenuPattern::OnKeyEvent(const KeyEvent& event) const
462 {
463     if (event.action != KeyAction::DOWN || IsMultiMenu() || IsDesktopMenu()) {
464         return false;
465     }
466     if ((event.code == KeyCode::KEY_DPAD_LEFT || event.code == KeyCode::KEY_ESCAPE) &&
467         (IsSubMenu() || IsSelectOverlaySubMenu())) {
468         auto menuWrapper = GetMenuWrapper();
469         CHECK_NULL_RETURN(menuWrapper, true);
470         auto wrapperPattern = menuWrapper->GetPattern<MenuWrapperPattern>();
471         CHECK_NULL_RETURN(wrapperPattern, true);
472         wrapperPattern->HideSubMenu();
473         return true;
474     }
475     return false;
476 }
477 
RemoveParentHoverStyle()478 void MenuPattern::RemoveParentHoverStyle()
479 {
480     if (!IsSubMenu()) {
481         return;
482     }
483     auto menuItemParent = GetParentMenuItem();
484     CHECK_NULL_VOID(menuItemParent);
485     auto menuItemPattern = menuItemParent->GetPattern<MenuItemPattern>();
486     CHECK_NULL_VOID(menuItemPattern);
487     menuItemPattern->SetIsSubMenuShowed(false);
488     menuItemPattern->OnHover(false);
489 }
490 
UpdateMenuItemChildren(RefPtr<UINode> & host)491 void MenuPattern::UpdateMenuItemChildren(RefPtr<UINode>& host)
492 {
493     CHECK_NULL_VOID(host);
494     auto layoutProperty = GetLayoutProperty<MenuLayoutProperty>();
495     CHECK_NULL_VOID(layoutProperty);
496     const auto& children = host->GetChildren();
497     int32_t index = 0;
498     for (auto child : children) {
499         if (child->GetTag() == V2::MENU_ITEM_ETS_TAG) {
500             auto itemNode = AceType::DynamicCast<FrameNode>(child);
501             CHECK_NULL_VOID(itemNode);
502             auto itemProperty = itemNode->GetLayoutProperty<MenuItemLayoutProperty>();
503             CHECK_NULL_VOID(itemProperty);
504             auto itemPattern = itemNode->GetPattern<MenuItemPattern>();
505             CHECK_NULL_VOID(itemPattern);
506 
507             auto expandingMode = layoutProperty->GetExpandingMode().value_or(SubMenuExpandingMode::SIDE);
508             if (expandingMode != itemPattern->GetExpandingMode() || IsEmbedded()) {
509                 auto expandNode = itemPattern->GetHost();
510                 CHECK_NULL_VOID(expandNode);
511                 expandNode->MarkModifyDone();
512                 expandNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
513             }
514             UpdateMenuItemTextNode(layoutProperty, itemProperty, itemPattern);
515             itemPattern->UpdateNeedDivider(isNeedDivider_);
516             isNeedDivider_ = true;
517             itemPattern->SetIndex(index);
518         } else if (child->GetTag() == V2::MENU_ITEM_GROUP_ETS_TAG) {
519             auto childItemNode = AceType::DynamicCast<FrameNode>(child);
520             CHECK_NULL_VOID(childItemNode);
521             auto pattern = childItemNode->GetPattern<MenuItemGroupPattern>();
522             CHECK_NULL_VOID(pattern);
523             pattern->ModifyDivider();
524             auto itemGroupNode = AceType::DynamicCast<UINode>(child);
525             CHECK_NULL_VOID(itemGroupNode);
526             isNeedDivider_ = false;
527             UpdateMenuItemChildren(itemGroupNode);
528             isNeedDivider_ = false;
529             auto accessibilityProperty =
530                 childItemNode->GetAccessibilityProperty<AccessibilityProperty>();
531             CHECK_NULL_VOID(accessibilityProperty);
532             accessibilityProperty->SetAccessibilityLevel(AccessibilityProperty::Level::NO_STR);
533         } else if (child->GetTag() == V2::JS_FOR_EACH_ETS_TAG || child->GetTag() == V2::JS_SYNTAX_ITEM_ETS_TAG
534             ||  child->GetTag() == V2::JS_IF_ELSE_ETS_TAG || child->GetTag() == V2::JS_REPEAT_ETS_TAG) {
535             auto nodesSet = AceType::DynamicCast<UINode>(child);
536             CHECK_NULL_VOID(nodesSet);
537             UpdateMenuItemChildren(nodesSet);
538         } else {
539             // do nothing
540         }
541         index++;
542     }
543 }
544 
UpdateSelectParam(const std::vector<SelectParam> & params)545 void MenuPattern::UpdateSelectParam(const std::vector<SelectParam>& params)
546 {
547     if (!isSelectMenu_) {
548         return;
549     }
550     auto host = GetHost();
551     CHECK_NULL_VOID(host);
552     const auto& children = GetOptions();
553     auto childCount = children.size();
554     auto paramCount = params.size();
555     size_t updateCount = std::min(paramCount, childCount);
556     auto childIt = children.begin();
557     for (size_t i = 0; i < updateCount; i++, childIt++) {
558         const auto& childNode = AceType::DynamicCast<FrameNode>(*childIt);
559         CHECK_NULL_VOID(childNode);
560         if (i == 0) {
561             auto props = childNode->GetPaintProperty<OptionPaintProperty>();
562             CHECK_NULL_VOID(props);
563             props->UpdateNeedDivider(false);
564             auto focusHub = childNode->GetOrCreateFocusHub();
565             CHECK_NULL_VOID(focusHub);
566             focusHub->SetIsDefaultFocus(true);
567         }
568         auto optionPattern = childNode->GetPattern<OptionPattern>();
569         CHECK_NULL_VOID(optionPattern);
570         optionPattern->UpdateText(params.at(i).text);
571         optionPattern->UpdateIcon(params.at(i).icon, params.at(i).symbolIcon);
572         childNode->MarkModifyDone();
573         childNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
574     }
575     for (size_t i = updateCount; i < paramCount; i++) {
576         auto optionNode = OptionView::CreateSelectOption(params.at(i), i);
577         if (i == 0) {
578             auto props = optionNode->GetPaintProperty<OptionPaintProperty>();
579             props->UpdateNeedDivider(false);
580         }
581         MountOption(optionNode);
582         optionNode->MarkModifyDone();
583         optionNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
584     }
585     for (size_t i = childCount; i > updateCount; i--) {
586         RemoveOption();
587     }
588     host->MarkModifyDone();
589     host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
590 }
591 
HideMenu(bool isMenuOnTouch,OffsetF position) const592 void MenuPattern::HideMenu(bool isMenuOnTouch, OffsetF position) const
593 {
594     auto host = GetHost();
595     CHECK_NULL_VOID(host);
596     auto pipeline = host->GetContextWithCheck();
597     CHECK_NULL_VOID(pipeline);
598     auto theme = pipeline->GetTheme<SelectTheme>();
599     CHECK_NULL_VOID(theme);
600     auto expandDisplay = theme->GetExpandDisplay();
601     auto rootMenuPattern = AceType::DynamicCast<MenuPattern>(host->GetPattern());
602     CHECK_NULL_VOID(rootMenuPattern);
603     // copy menu pattern properties to rootMenu
604     auto layoutProperty = rootMenuPattern->GetLayoutProperty<MenuLayoutProperty>();
605     CHECK_NULL_VOID(layoutProperty);
606     bool isShowInSubWindow = layoutProperty->GetShowInSubWindowValue(true);
607     auto wrapper = GetMenuWrapper();
608     CHECK_NULL_VOID(wrapper);
609     if (wrapper->GetTag() == V2::SELECT_OVERLAY_ETS_TAG) {
610         return;
611     }
612     if (((IsContextMenu() || (expandDisplay && isShowInSubWindow))) && (targetTag_ != V2::SELECT_ETS_TAG)) {
613         SubwindowManager::GetInstance()->HideMenuNG(wrapper, targetId_);
614         return;
615     }
616 
617     if (HideStackExpandMenu(position)) {
618         return;
619     }
620 
621     auto overlayManager = pipeline->GetOverlayManager();
622     CHECK_NULL_VOID(overlayManager);
623     overlayManager->HideMenu(wrapper, targetId_, isMenuOnTouch);
624     overlayManager->EraseMenuInfo(targetId_);
625 }
626 
HideStackExpandMenu(const OffsetF & position) const627 bool MenuPattern::HideStackExpandMenu(const OffsetF& position) const
628 {
629     auto wrapper = GetMenuWrapper();
630     CHECK_NULL_RETURN(wrapper, false);
631     auto outterMenu = wrapper->GetFirstChild();
632     CHECK_NULL_RETURN(outterMenu, false);
633     auto menuWrapperPattern = wrapper->GetPattern<MenuWrapperPattern>();
634     CHECK_NULL_RETURN(menuWrapperPattern, false);
635     auto innerMenu = menuWrapperPattern->GetMenuChild(outterMenu);
636     CHECK_NULL_RETURN(innerMenu, false);
637     auto innerMenuPattern = AceType::DynamicCast<MenuPattern>(innerMenu->GetPattern());
638     CHECK_NULL_RETURN(innerMenuPattern, false);
639     auto layoutProps = innerMenuPattern->GetLayoutProperty<MenuLayoutProperty>();
640     CHECK_NULL_RETURN(layoutProps, false);
641     auto expandingMode = layoutProps->GetExpandingMode().value_or(SubMenuExpandingMode::SIDE);
642     if (IsSubMenu() && expandingMode == SubMenuExpandingMode::STACK) {
643         auto host = GetHost();
644         CHECK_NULL_RETURN(host, false);
645         auto hostZone = host->GetPaintRectOffset(false, true);
646         auto scroll = host->GetFirstChild();
647         CHECK_NULL_RETURN(scroll, false);
648         auto column = scroll->GetFirstChild();
649         CHECK_NULL_RETURN(column, false);
650         auto clickAreaNode = AceType::DynamicCast<FrameNode>(column->GetFirstChild());
651         CHECK_NULL_RETURN(clickAreaNode, false);
652         auto clickAreaZone = clickAreaNode->GetGeometryNode()->GetFrameRect();
653         clickAreaZone.SetLeft(hostZone.GetX());
654         clickAreaZone.SetTop(hostZone.GetY());
655         if (clickAreaZone.IsInRegion(PointF(position.GetX(), position.GetY()))) {
656             HideStackMenu();
657             return true;
658         }
659     } else if (expandingMode == SubMenuExpandingMode::STACK) {
660         auto host = GetHost();
661         CHECK_NULL_RETURN(host, false);
662         auto hostZone = host->GetPaintRectOffset(false, true);
663         auto clickAreaZone = host->GetGeometryNode()->GetFrameRect();
664         clickAreaZone.SetLeft(hostZone.GetX());
665         clickAreaZone.SetTop(hostZone.GetY());
666         if (clickAreaZone.IsInRegion(PointF(position.GetX(), position.GetY()))) {
667             auto wrapperPattern = wrapper->GetPattern<MenuWrapperPattern>();
668             CHECK_NULL_RETURN(wrapperPattern, false);
669             wrapperPattern->HideSubMenu();
670             return true;
671         }
672     }
673     return false;
674 }
675 
HideStackMenu() const676 void MenuPattern::HideStackMenu() const
677 {
678     auto host = GetHost();
679     CHECK_NULL_VOID(host);
680     auto wrapper = GetMenuWrapper();
681     CHECK_NULL_VOID(wrapper);
682     AnimationOption option;
683     option.SetOnFinishEvent(
684         [weak = WeakClaim(RawPtr(wrapper)), subMenuWk = WeakClaim(RawPtr(host))] {
685             auto subMenu = subMenuWk.Upgrade();
686             CHECK_NULL_VOID(subMenu);
687             auto pipeline = subMenu->GetContextWithCheck();
688             CHECK_NULL_VOID(pipeline);
689             auto taskExecutor = pipeline->GetTaskExecutor();
690             CHECK_NULL_VOID(taskExecutor);
691             taskExecutor->PostTask(
692                 [weak, subMenuWk]() {
693                     auto subMenuNode = subMenuWk.Upgrade();
694                     CHECK_NULL_VOID(subMenuNode);
695                     auto menuWrapper = weak.Upgrade();
696                     CHECK_NULL_VOID(menuWrapper);
697                     menuWrapper->RemoveChild(subMenuNode);
698                     menuWrapper->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF_AND_CHILD);
699                 },
700                 TaskExecutor::TaskType::UI, "HideStackMenu");
701     });
702     auto menuPattern = AceType::DynamicCast<MenuPattern>(host->GetPattern());
703     if (menuPattern) {
704         menuPattern->RemoveParentHoverStyle();
705         auto frameNode = FrameNode::GetFrameNode(menuPattern->GetTargetTag(), menuPattern->GetTargetId());
706         CHECK_NULL_VOID(frameNode);
707         auto menuItem = frameNode->GetPattern<MenuItemPattern>();
708         if (menuItem) {
709             menuItem->SetIsSubMenuShowed(false);
710         }
711     }
712     auto menuNode = AceType::DynamicCast<FrameNode>(wrapper->GetFirstChild());
713     CHECK_NULL_VOID(menuNode);
714     ShowStackExpandDisappearAnimation(menuNode, host, option);
715 }
716 
HideSubMenu()717 void MenuPattern::HideSubMenu()
718 {
719     if (!showedSubMenu_) {
720         return;
721     }
722     auto subMenuPattern = showedSubMenu_->GetPattern<MenuPattern>();
723     CHECK_NULL_VOID(subMenuPattern);
724     subMenuPattern->RemoveParentHoverStyle();
725 
726     auto menuItem = subMenuPattern->GetParentMenuItem();
727     CHECK_NULL_VOID(menuItem);
728     auto menuItemPattern = menuItem->GetPattern<MenuItemPattern>();
729     CHECK_NULL_VOID(menuItemPattern);
730     menuItemPattern->SetIsSubMenuShowed(false);
731     menuItemPattern->ClearHoverRegions();
732     menuItemPattern->ResetWrapperMouseEvent();
733 
734     auto wrapper = GetMenuWrapper();
735     CHECK_NULL_VOID(wrapper);
736     wrapper->RemoveChild(showedSubMenu_);
737     wrapper->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF_AND_CHILD);
738     showedSubMenu_.Reset();
739 }
740 
GetMenuWrapper() const741 RefPtr<FrameNode> MenuPattern::GetMenuWrapper() const
742 {
743     auto host = GetHost();
744     CHECK_NULL_RETURN(host, nullptr);
745     auto parent = host->GetParent();
746     while (parent) {
747         if (parent->GetTag() == V2::MENU_WRAPPER_ETS_TAG || parent->GetTag() == V2::SELECT_OVERLAY_ETS_TAG) {
748             return AceType::DynamicCast<FrameNode>(parent);
749         }
750         parent = parent->GetParent();
751     }
752     return nullptr;
753 }
754 
755 // search for inner <Menu> node, once found a <Menu> node, count the number of sibling <Menu>
GetInnerMenuCount() const756 uint32_t MenuPattern::GetInnerMenuCount() const
757 {
758     if (type_ == MenuType::MULTI_MENU || type_ == MenuType::DESKTOP_MENU || IsSelectOverlayCustomMenu()) {
759         return 0;
760     }
761 
762     auto host = GetHost();
763     CHECK_NULL_RETURN(host, 0);
764     auto child = host->GetChildAtIndex(0);
765     uint32_t depth = 0;
766     while (child && depth < MAX_SEARCH_DEPTH) {
767         // found component <Menu>
768         if (child->GetTag() == V2::JS_VIEW_ETS_TAG) {
769             child = child->GetFrameChildByIndex(0, false);
770             if (child && child->GetTag() == V2::JS_VIEW_ETS_TAG) {
771                 child = child->GetChildAtIndex(0);
772                 ++depth;
773             }
774             continue;
775         }
776         if (child->GetTag() == V2::MENU_ETS_TAG) {
777             auto parent = child->GetParent();
778             CHECK_NULL_RETURN(parent, 0);
779             return parent->GetChildren().size();
780         }
781         child = child->GetChildAtIndex(0);
782         ++depth;
783     }
784     return 0;
785 }
786 
GetFirstInnerMenu() const787 RefPtr<FrameNode> MenuPattern::GetFirstInnerMenu() const
788 {
789     if (type_ == MenuType::MULTI_MENU || type_ == MenuType::DESKTOP_MENU) {
790         return nullptr;
791     }
792 
793     auto host = GetHost();
794     CHECK_NULL_RETURN(host, nullptr);
795     uint32_t depth = 0;
796     auto child = host->GetChildAtIndex(0);
797     while (child && depth < MAX_SEARCH_DEPTH) {
798         // found component <Menu>
799         if (child->GetTag() == V2::JS_VIEW_ETS_TAG) {
800             child = child->GetFrameChildByIndex(0, false);
801             if (child && child->GetTag() == V2::JS_VIEW_ETS_TAG) {
802                 child = child->GetChildAtIndex(0);
803                 ++depth;
804             }
805             continue;
806         }
807         if (child->GetTag() == V2::MENU_ETS_TAG) {
808             return AceType::DynamicCast<FrameNode>(child);
809         }
810         child = child->GetChildAtIndex(0);
811         ++depth;
812     }
813     return nullptr;
814 }
815 
CopyMenuAttr(const RefPtr<FrameNode> & menuNode) const816 void MenuPattern::CopyMenuAttr(const RefPtr<FrameNode>& menuNode) const
817 {
818     auto pattern = AceType::DynamicCast<MenuPattern>(menuNode->GetPattern());
819     CHECK_NULL_VOID(pattern);
820 
821     auto host = GetHost();
822     CHECK_NULL_VOID(host);
823     auto rootMenuPattern = AceType::DynamicCast<MenuPattern>(host->GetPattern());
824     CHECK_NULL_VOID(rootMenuPattern);
825 
826     // copy menu pattern properties to rootMenu
827     auto layoutProperty = pattern->GetLayoutProperty<MenuLayoutProperty>();
828     CHECK_NULL_VOID(layoutProperty);
829     auto rootMenuLayoutProperty = rootMenuPattern->GetLayoutProperty<MenuLayoutProperty>();
830     CHECK_NULL_VOID(rootMenuLayoutProperty);
831     if (layoutProperty->GetBorderRadius().has_value()) {
832         rootMenuLayoutProperty->UpdateBorderRadius(layoutProperty->GetBorderRadiusValue());
833     }
834 }
835 
836 // mount option on menu
MountOption(const RefPtr<FrameNode> & option)837 void MenuPattern::MountOption(const RefPtr<FrameNode>& option)
838 {
839     auto column = GetMenuColumn();
840     CHECK_NULL_VOID(column);
841     auto pattern = option->GetPattern<OptionPattern>();
842     CHECK_NULL_VOID(pattern);
843     pattern->SetMenu(GetHost());
844     AddOptionNode(option);
845     option->MountToParent(column);
846 }
847 
848 // remove option from menu
RemoveOption()849 void MenuPattern::RemoveOption()
850 {
851     auto column = GetMenuColumn();
852     CHECK_NULL_VOID(column);
853     auto endOption = column->GetChildren().back();
854     CHECK_NULL_VOID(endOption);
855     column->RemoveChild(endOption);
856     PopOptionNode();
857 }
858 
GetMenuColumn() const859 RefPtr<FrameNode> MenuPattern::GetMenuColumn() const
860 {
861     auto menu = GetHost();
862     CHECK_NULL_RETURN(menu, nullptr);
863     auto scroll = menu->GetChildren().front();
864     CHECK_NULL_RETURN(scroll, nullptr);
865     auto column = scroll->GetChildren().front();
866     return DynamicCast<FrameNode>(column);
867 }
868 
DisableTabInMenu()869 void MenuPattern::DisableTabInMenu()
870 {
871     if (IsMultiMenu() || IsDesktopMenu()) {
872         // multi menu not has scroll and column
873         return;
874     }
875     // disable tab in menu
876     auto column = GetMenuColumn();
877     CHECK_NULL_VOID(column);
878     auto columnFocusHub = column->GetOrCreateFocusHub();
879     CHECK_NULL_VOID(columnFocusHub);
880 
881     auto onKeyEvent = [](const KeyEvent& event) -> bool {
882         if (event.action != KeyAction::DOWN) {
883             return false;
884         }
885         return event.code == KeyCode::KEY_TAB;
886     };
887     columnFocusHub->SetOnKeyEventInternal(std::move(onKeyEvent));
888 }
889 
CreateLayoutAlgorithm()890 RefPtr<LayoutAlgorithm> MenuPattern::CreateLayoutAlgorithm()
891 {
892     switch (type_) {
893         case MenuType::MULTI_MENU:
894         case MenuType::DESKTOP_MENU:
895             return MakeRefPtr<MultiMenuLayoutAlgorithm>();
896         case MenuType::SUB_MENU:
897         case MenuType::SELECT_OVERLAY_SUB_MENU:
898             return MakeRefPtr<SubMenuLayoutAlgorithm>();
899         default:
900             return MakeRefPtr<MenuLayoutAlgorithm>(targetId_, targetTag_, lastPosition_);
901     }
902 }
903 
GetShadowFromTheme(ShadowStyle shadowStyle,Shadow & shadow)904 bool MenuPattern::GetShadowFromTheme(ShadowStyle shadowStyle, Shadow& shadow)
905 {
906     auto colorMode = SystemProperties::GetColorMode();
907     if (shadowStyle == ShadowStyle::None) {
908         return true;
909     }
910     auto host = GetHost();
911     auto pipelineContext = host->GetContextRefPtr();
912     CHECK_NULL_RETURN(pipelineContext, false);
913     auto shadowTheme = pipelineContext->GetTheme<ShadowTheme>();
914     CHECK_NULL_RETURN(shadowTheme, false);
915     shadow = shadowTheme->GetShadow(shadowStyle, colorMode);
916     return true;
917 }
918 
ResetTheme(const RefPtr<FrameNode> & host,bool resetForDesktopMenu)919 void MenuPattern::ResetTheme(const RefPtr<FrameNode>& host, bool resetForDesktopMenu)
920 {
921     auto renderContext = host->GetRenderContext();
922     CHECK_NULL_VOID(renderContext);
923     auto scroll = DynamicCast<FrameNode>(host->GetFirstChild());
924     CHECK_NULL_VOID(scroll);
925 
926     if (resetForDesktopMenu) {
927         // DesktopMenu apply shadow on inner Menu node
928         Shadow shadow;
929         if (GetShadowFromTheme(ShadowStyle::None, shadow)) {
930             renderContext->UpdateBackShadow(shadow);
931         }
932     } else {
933         Shadow shadow;
934         if (GetShadowFromTheme(ShadowStyle::OuterDefaultMD, shadow)) {
935             renderContext->UpdateBackShadow(shadow);
936         }
937     }
938     // to enable inner menu shadow effect for desktopMenu, need to remove clipping from container
939     bool clip = !resetForDesktopMenu;
940     scroll->GetRenderContext()->SetClipToBounds(clip);
941 
942     // move padding from scroll to inner menu
943     auto scrollProp = scroll->GetLayoutProperty();
944     scrollProp->UpdatePadding(PaddingProperty());
945 }
946 
InitTheme(const RefPtr<FrameNode> & host)947 void MenuPattern::InitTheme(const RefPtr<FrameNode>& host)
948 {
949     CHECK_NULL_VOID(host);
950     auto renderContext = host->GetRenderContext();
951     CHECK_NULL_VOID(renderContext);
952     auto pipeline = host->GetContextWithCheck();
953     CHECK_NULL_VOID(pipeline);
954     auto theme = pipeline->GetTheme<SelectTheme>();
955     CHECK_NULL_VOID(theme);
956     auto expandDisplay = theme->GetExpandDisplay();
957     expandDisplay_ = expandDisplay;
958     if (Container::LessThanAPIVersion(PlatformVersion::VERSION_ELEVEN) || !renderContext->IsUniRenderEnabled()) {
959         auto bgColor = theme->GetBackgroundColor();
960         renderContext->UpdateBackgroundColor(bgColor);
961     }
962     Shadow shadow;
963     if (GetShadowFromTheme(ShadowStyle::OuterDefaultMD, shadow)) {
964         renderContext->UpdateBackShadow(shadow);
965     }
966     // make menu round rect
967     BorderRadiusProperty borderRadius;
968     if (Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE)) {
969         borderRadius.SetRadius(theme->GetMenuDefaultRadius());
970     } else {
971         borderRadius.SetRadius(theme->GetMenuBorderRadius());
972     }
973     renderContext->UpdateBorderRadius(borderRadius);
974 }
975 
InitTheme(const RefPtr<FrameNode> & host)976 void InnerMenuPattern::InitTheme(const RefPtr<FrameNode>& host)
977 {
978     CHECK_NULL_VOID(host);
979     MenuPattern::InitTheme(host);
980     // inner menu applies shadow in OnModifyDone(), where it can determine if it's a DesktopMenu or a regular menu
981 
982     auto layoutProperty = host->GetLayoutProperty();
983     if (layoutProperty->GetPaddingProperty()) {
984         // if user defined padding exists, skip applying default padding
985         return;
986     }
987     auto pipeline = host->GetContextWithCheck();
988     CHECK_NULL_VOID(pipeline);
989     auto theme = pipeline->GetTheme<SelectTheme>();
990     // apply default padding from theme on inner menu
991     PaddingProperty padding;
992     padding.SetEdges(CalcLength(theme->GetOutPadding()));
993     host->GetLayoutProperty()->UpdatePadding(padding);
994 
995     host->GetRenderContext()->SetClipToBounds(true);
996 }
997 
SetAccessibilityAction()998 void MenuPattern::SetAccessibilityAction()
999 {
1000     auto host = GetHost();
1001     CHECK_NULL_VOID(host);
1002     auto accessibilityProperty = host->GetAccessibilityProperty<AccessibilityProperty>();
1003     CHECK_NULL_VOID(accessibilityProperty);
1004     accessibilityProperty->SetActionScrollForward([weakPtr = WeakClaim(this)]() {
1005         const auto& pattern = weakPtr.Upgrade();
1006         auto host = pattern->GetHost();
1007         CHECK_NULL_VOID(host);
1008         auto firstChild = DynamicCast<FrameNode>(host->GetChildAtIndex(0));
1009         CHECK_NULL_VOID(firstChild);
1010         if (firstChild && firstChild->GetTag() == V2::SCROLL_ETS_TAG) {
1011             auto scrollPattern = firstChild->GetPattern<ScrollPattern>();
1012             CHECK_NULL_VOID(scrollPattern);
1013             scrollPattern->ScrollPage(false, true);
1014         }
1015     });
1016 
1017     accessibilityProperty->SetActionScrollBackward([weakPtr = WeakClaim(this)]() {
1018         const auto& pattern = weakPtr.Upgrade();
1019         auto host = pattern->GetHost();
1020         CHECK_NULL_VOID(host);
1021         auto firstChild = DynamicCast<FrameNode>(host->GetChildAtIndex(0));
1022         CHECK_NULL_VOID(firstChild);
1023         if (firstChild && firstChild->GetTag() == V2::SCROLL_ETS_TAG) {
1024             auto scrollPattern = firstChild->GetPattern<ScrollPattern>();
1025             CHECK_NULL_VOID(scrollPattern);
1026             scrollPattern->ScrollPage(true, true);
1027         }
1028     });
1029 }
1030 
GetTransformCenter() const1031 Offset MenuPattern::GetTransformCenter() const
1032 {
1033     auto host = GetHost();
1034     CHECK_NULL_RETURN(host, Offset());
1035     auto geometryNode = host->GetGeometryNode();
1036     CHECK_NULL_RETURN(geometryNode, Offset());
1037     auto size = geometryNode->GetFrameSize();
1038     auto layoutAlgorithmWrapper = host->GetLayoutAlgorithm();
1039     CHECK_NULL_RETURN(layoutAlgorithmWrapper, Offset());
1040     auto layoutAlgorithm = AceType::DynamicCast<MenuLayoutAlgorithm>(layoutAlgorithmWrapper->GetLayoutAlgorithm());
1041     CHECK_NULL_RETURN(layoutAlgorithm, Offset());
1042     auto placement = layoutAlgorithm->GetPlacement();
1043     switch (placement) {
1044         case Placement::BOTTOM_LEFT:
1045         case Placement::RIGHT_TOP:
1046             return Offset();
1047         case Placement::BOTTOM_RIGHT:
1048         case Placement::LEFT_TOP:
1049             return Offset(size.Width(), 0.0f);
1050         case Placement::TOP_LEFT:
1051         case Placement::RIGHT_BOTTOM:
1052             return Offset(0.0f, size.Height());
1053         case Placement::TOP_RIGHT:
1054         case Placement::LEFT_BOTTOM:
1055             return Offset(size.Width(), size.Height());
1056         case Placement::BOTTOM:
1057             return Offset(size.Width() / 2, 0.0f);
1058         case Placement::LEFT:
1059             return Offset(size.Width(), size.Height() / 2);
1060         case Placement::TOP:
1061             return Offset(size.Width() / 2, size.Height());
1062         case Placement::RIGHT:
1063             return Offset(0.0f, size.Height() / 2);
1064         default:
1065             return Offset();
1066     }
1067 }
1068 
ShowPreviewMenuScaleAnimation()1069 void MenuPattern::ShowPreviewMenuScaleAnimation()
1070 {
1071     auto menuWrapper = GetMenuWrapper();
1072     CHECK_NULL_VOID(menuWrapper);
1073     auto menuWrapperPattern = menuWrapper->GetPattern<MenuWrapperPattern>();
1074     CHECK_NULL_VOID(menuWrapperPattern);
1075 
1076     auto preview = isShowHoverImage_ ? menuWrapperPattern->GetHoverImageFlexNode() : menuWrapperPattern->GetPreview();
1077     CHECK_NULL_VOID(preview);
1078     bool isHoverImageTarget = isShowHoverImage_ && preview->GetTag() == V2::FLEX_ETS_TAG;
1079     auto previewRenderContext = preview->GetRenderContext();
1080     CHECK_NULL_VOID(previewRenderContext);
1081     auto previewGeometryNode = preview->GetGeometryNode();
1082     CHECK_NULL_VOID(previewGeometryNode);
1083     auto previewPosition = previewGeometryNode->GetFrameOffset();
1084     OffsetF previewOriginPosition = GetPreviewOriginOffset();
1085     auto host = GetHost();
1086     CHECK_NULL_VOID(host);
1087     auto pipeline = host->GetContextWithCheck();
1088     CHECK_NULL_VOID(pipeline);
1089     auto menuTheme = pipeline->GetTheme<NG::MenuTheme>();
1090     CHECK_NULL_VOID(menuTheme);
1091     auto springMotionResponse = menuTheme->GetSpringMotionResponse();
1092     auto springMotionDampingFraction = menuTheme->GetSpringMotionDampingFraction();
1093     auto delay = isHoverImageTarget ? menuTheme->GetHoverImageDelayDuration() : 0;
1094 
1095     previewRenderContext->UpdatePosition(
1096         OffsetT<Dimension>(Dimension(previewOriginPosition.GetX()), Dimension(previewOriginPosition.GetY())));
1097     AnimationOption scaleOption = AnimationOption();
1098     auto motion = AceType::MakeRefPtr<ResponsiveSpringMotion>(springMotionResponse, springMotionDampingFraction);
1099     if (isHoverImageTarget) {
1100         scaleOption.SetCurve(CUSTOM_PREVIEW_ANIMATION_CURVE);
1101     } else {
1102         scaleOption.SetCurve(motion);
1103     }
1104     scaleOption.SetDelay(delay);
1105     AnimationUtils::Animate(scaleOption, [previewRenderContext, previewPosition]() {
1106         CHECK_NULL_VOID(previewRenderContext);
1107         previewRenderContext->UpdatePosition(
1108             OffsetT<Dimension>(Dimension(previewPosition.GetX()), Dimension(previewPosition.GetY())));
1109     });
1110 }
1111 
ShowPreviewMenuAnimation()1112 void MenuPattern::ShowPreviewMenuAnimation()
1113 {
1114     CHECK_NULL_VOID(isFirstShow_ && previewMode_ != MenuPreviewMode::NONE);
1115     auto host = GetHost();
1116     CHECK_NULL_VOID(host);
1117     MenuView::CalcHoverScaleInfo(host);
1118     ShowPreviewMenuScaleAnimation();
1119 
1120     MenuView::ShowPixelMapAnimation(host);
1121     auto renderContext = host->GetRenderContext();
1122     CHECK_NULL_VOID(renderContext);
1123     renderContext->UpdateTransformCenter(DimensionOffset(GetTransformCenter()));
1124     auto menuPosition = host->GetPaintRectOffset(false, true);
1125 
1126     auto pipeline = host->GetContextWithCheck();
1127     CHECK_NULL_VOID(pipeline);
1128     auto menuTheme = pipeline->GetTheme<NG::MenuTheme>();
1129     CHECK_NULL_VOID(menuTheme);
1130     auto menuAnimationScale = menuTheme->GetMenuAnimationScale();
1131     auto springMotionResponse = menuTheme->GetSpringMotionResponse();
1132     auto springMotionDampingFraction = menuTheme->GetSpringMotionDampingFraction();
1133 
1134     renderContext->UpdateTransformScale(VectorF(menuAnimationScale, menuAnimationScale));
1135 
1136     renderContext->UpdatePosition(
1137         OffsetT<Dimension>(Dimension(originOffset_.GetX()), Dimension(originOffset_.GetY())));
1138 
1139     auto delay = isShowHoverImage_ ? menuTheme->GetHoverImageDelayDuration() : 0;
1140     ShowMenuOpacityAnimation(menuTheme, renderContext, delay);
1141 
1142     AnimationOption scaleOption = AnimationOption();
1143     auto motion = AceType::MakeRefPtr<ResponsiveSpringMotion>(springMotionResponse, springMotionDampingFraction);
1144     scaleOption.SetCurve(motion);
1145     scaleOption.SetDelay(delay);
1146     AnimationUtils::Animate(scaleOption, [renderContext, menuPosition]() {
1147         if (renderContext) {
1148             renderContext->UpdateTransformScale(VectorF(1.0f, 1.0f));
1149             renderContext->UpdatePosition(
1150                 OffsetT<Dimension>(Dimension(menuPosition.GetX()), Dimension(menuPosition.GetY())));
1151         }
1152     });
1153     isFirstShow_ = false;
1154 }
1155 
ShowMenuAppearAnimation()1156 void MenuPattern::ShowMenuAppearAnimation()
1157 {
1158     auto host = GetHost();
1159     CHECK_NULL_VOID(host);
1160     if (isMenuShow_ && Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE) &&
1161         previewMode_ == MenuPreviewMode::NONE) {
1162         auto renderContext = host->GetRenderContext();
1163         CHECK_NULL_VOID(renderContext);
1164         auto offset = GetTransformCenter();
1165         renderContext->UpdateTransformCenter(DimensionOffset(offset));
1166         auto menuPosition = host->GetPaintRectOffset(false, true);
1167         if (IsSelectOverlayExtensionMenu() && !isExtensionMenuShow_) {
1168             menuPosition = GetEndOffset();
1169         }
1170         if (IsSelectOverlayExtensionMenu()) {
1171             SetEndOffset(menuPosition);
1172         }
1173 
1174         renderContext->UpdateTransformScale(VectorF(MENU_ORIGINAL_SCALE, MENU_ORIGINAL_SCALE));
1175         renderContext->UpdateOpacity(0.0f);
1176 
1177         AnimationOption option = AnimationOption();
1178         option.SetCurve(MAIN_MENU_ANIMATION_CURVE);
1179         AnimationUtils::Animate(option, [this, renderContext, menuPosition]() {
1180             CHECK_NULL_VOID(renderContext);
1181             if (IsSelectOverlayExtensionMenu()) {
1182                 renderContext->UpdatePosition(
1183                     OffsetT<Dimension>(Dimension(menuPosition.GetX()), Dimension(menuPosition.GetY())));
1184             }
1185             renderContext->UpdateOpacity(1.0f);
1186             renderContext->UpdateTransformScale(VectorF(1.0f, 1.0f));
1187         });
1188         isExtensionMenuShow_ = false;
1189     }
1190     isMenuShow_ = false;
1191 }
1192 
ShowStackExpandMenu()1193 void MenuPattern::ShowStackExpandMenu()
1194 {
1195     auto host = GetHost();
1196     CHECK_NULL_VOID(host);
1197     if (!isSubMenuShow_) {
1198         return;
1199     }
1200     auto renderContext = host->GetRenderContext();
1201     CHECK_NULL_VOID(renderContext);
1202     auto menuWarpper = GetMenuWrapper();
1203     CHECK_NULL_VOID(menuWarpper);
1204     auto menuWrapperPattern = menuWarpper->GetPattern<MenuWrapperPattern>();
1205     CHECK_NULL_VOID(menuWrapperPattern);
1206     auto outterMenu = menuWrapperPattern->GetMenu();
1207     CHECK_NULL_VOID(outterMenu);
1208 
1209     auto [originOffset, endOffset] = GetMenuOffset(outterMenu);
1210     auto outterMenuContext = outterMenu->GetRenderContext();
1211     CHECK_NULL_VOID(outterMenuContext);
1212 
1213     renderContext->UpdatePosition(
1214         OffsetT<Dimension>(Dimension(originOffset.GetX()), Dimension(originOffset.GetY())));
1215 
1216     AnimationOption opacityOption = AnimationOption();
1217     opacityOption.SetCurve(Curves::FRICTION);
1218     opacityOption.SetDuration(STACK_EXPAND_DISAPPEAR_DURATION);
1219     AnimationUtils::Animate(opacityOption, [renderContext, outterMenuContext]() {
1220         if (renderContext) {
1221             renderContext->UpdateOpacity(1.0f);
1222         }
1223         if (outterMenuContext) {
1224             outterMenuContext->UpdateOpacity(MOUNT_MENU_OPACITY);
1225         }
1226     });
1227 
1228     AnimationOption translateOption = AnimationOption();
1229     translateOption.SetCurve(STACK_MENU_CURVE);
1230     AnimationUtils::Animate(translateOption, [renderContext, menuPosition = endOffset, outterMenuContext]() {
1231         if (renderContext) {
1232             renderContext->UpdatePosition(
1233                 OffsetT<Dimension>(Dimension(menuPosition.GetX()), Dimension(menuPosition.GetY())));
1234         }
1235         if (outterMenuContext) {
1236             outterMenuContext->UpdateTransformScale(VectorF(MOUNT_MENU_FINAL_SCALE, MOUNT_MENU_FINAL_SCALE));
1237         }
1238     });
1239     ShowArrowRotateAnimation();
1240     isSubMenuShow_ = false;
1241 }
1242 
GetMenuOffset(const RefPtr<FrameNode> & outterMenu,bool isNeedRestoreNodeId) const1243 std::pair<OffsetF, OffsetF> MenuPattern::GetMenuOffset(const RefPtr<FrameNode>& outterMenu,
1244     bool isNeedRestoreNodeId) const
1245 {
1246     CHECK_NULL_RETURN(outterMenu, std::make_pair(OffsetF(), OffsetF()));
1247     auto scroll = outterMenu->GetFirstChild();
1248     CHECK_NULL_RETURN(scroll, std::make_pair(OffsetF(), OffsetF()));
1249     auto innerMenu = scroll->GetFirstChild();
1250     CHECK_NULL_RETURN(innerMenu, std::make_pair(OffsetF(), OffsetF()));
1251     auto children = innerMenu->GetChildren();
1252     MenuItemInfo menuItemInfo;
1253     for (auto child : children) {
1254         menuItemInfo = GetInnerMenuOffset(child, isNeedRestoreNodeId);
1255         if (menuItemInfo.isFindTargetId) {
1256             break;
1257         }
1258     }
1259     return {menuItemInfo.originOffset, menuItemInfo.endOffset};
1260 }
1261 
GetInnerMenuOffset(const RefPtr<UINode> & child,bool isNeedRestoreNodeId) const1262 MenuItemInfo MenuPattern::GetInnerMenuOffset(const RefPtr<UINode>& child, bool isNeedRestoreNodeId) const
1263 {
1264     MenuItemInfo menuItemInfo;
1265     CHECK_NULL_RETURN(child, menuItemInfo);
1266     if (child->GetTag() == V2::MENU_ITEM_ETS_TAG) {
1267         menuItemInfo = GetMenuItemInfo(child, isNeedRestoreNodeId);
1268         if (menuItemInfo.isFindTargetId) {
1269             return menuItemInfo;
1270         }
1271     } else {
1272         const auto& groupChildren = child->GetChildren();
1273         for (auto child : groupChildren) {
1274             menuItemInfo = GetInnerMenuOffset(child, isNeedRestoreNodeId);
1275             if (menuItemInfo.isFindTargetId) {
1276                 return menuItemInfo;
1277             }
1278         }
1279     }
1280     return menuItemInfo;
1281 }
1282 
GetMenuItemInfo(const RefPtr<UINode> & child,bool isNeedRestoreNodeId) const1283 MenuItemInfo MenuPattern::GetMenuItemInfo(const RefPtr<UINode>& child, bool isNeedRestoreNodeId) const
1284 {
1285     MenuItemInfo menuItemInfo;
1286     auto menuItem = AceType::DynamicCast<FrameNode>(child);
1287     CHECK_NULL_RETURN(menuItem, menuItemInfo);
1288     if (menuItem->GetTag() == V2::MENU_ITEM_ETS_TAG) {
1289         auto menuItemPattern = menuItem->GetPattern<MenuItemPattern>();
1290         CHECK_NULL_RETURN(menuItemPattern, menuItemInfo);
1291         if (menuItem->GetId() == menuItemPattern->GetClickMenuItemId()) {
1292             auto offset = menuItem->GetPaintRectOffset(false, true);
1293             menuItemInfo.originOffset = offset - OffsetF(PADDING.ConvertToPx(), PADDING.ConvertToPx());
1294             auto menuItemFrameSize = menuItem->GetGeometryNode()->GetFrameSize();
1295             menuItemInfo.endOffset = menuItemInfo.originOffset + OffsetF(0.0f, menuItemFrameSize.Height());
1296             menuItemInfo.isFindTargetId = true;
1297             if (isNeedRestoreNodeId) {
1298                 menuItemPattern->SetClickMenuItemId(-1);
1299             }
1300         }
1301     }
1302     return menuItemInfo;
1303 }
1304 
ShowArrowRotateAnimation() const1305 void MenuPattern::ShowArrowRotateAnimation() const
1306 {
1307     auto host = GetHost();
1308     CHECK_NULL_VOID(host);
1309     auto subImageNode = GetImageNode(host);
1310     CHECK_NULL_VOID(subImageNode);
1311     auto subImageContext = subImageNode->GetRenderContext();
1312     CHECK_NULL_VOID(subImageContext);
1313     subImageContext->UpdateTransformRotate(Vector5F(0.0f, 0.0f, 1.0f, 0.0f, 0.0f));
1314     AnimationOption option = AnimationOption();
1315     option.SetCurve(MENU_ANIMATION_CURVE);
1316     AnimationUtils::Animate(option, [subImageContext]() {
1317         if (subImageContext) {
1318             subImageContext->UpdateTransformRotate(Vector5F(0.0f, 0.0f, 1.0f, SEMI_CIRCLE_ANGEL, 0.0f));
1319         }
1320     });
1321 }
1322 
GetImageNode(const RefPtr<FrameNode> & host) const1323 RefPtr<FrameNode> MenuPattern::GetImageNode(const RefPtr<FrameNode>& host) const
1324 {
1325     auto scroll = host->GetFirstChild();
1326     CHECK_NULL_RETURN(scroll, nullptr);
1327     auto innerMenu = scroll->GetFirstChild();
1328     CHECK_NULL_RETURN(innerMenu, nullptr);
1329     auto menuItem = innerMenu->GetFirstChild();
1330     CHECK_NULL_RETURN(menuItem, nullptr);
1331     auto rightRow = menuItem->GetChildAtIndex(1);
1332     CHECK_NULL_RETURN(rightRow, nullptr);
1333     auto image = AceType::DynamicCast<FrameNode>(rightRow->GetChildren().back());
1334     return image;
1335 }
1336 
ShowStackExpandDisappearAnimation(const RefPtr<FrameNode> & menuNode,const RefPtr<FrameNode> & subMenuNode,AnimationOption & option) const1337 void MenuPattern::ShowStackExpandDisappearAnimation(const RefPtr<FrameNode>& menuNode,
1338     const RefPtr<FrameNode>& subMenuNode, AnimationOption& option) const
1339 {
1340     CHECK_NULL_VOID(menuNode);
1341     CHECK_NULL_VOID(subMenuNode);
1342 
1343     auto [originOffset, endOffset] = GetMenuOffset(menuNode, true);
1344     auto subMenuPos = subMenuNode->GetPaintRectOffset(false, true);
1345     auto menuPosition = OffsetF(subMenuPos.GetX(), originOffset.GetY());
1346 
1347     option.SetCurve(STACK_MENU_CURVE);
1348     AnimationUtils::Animate(option, [menuNode, menuPosition, subMenuNode]() {
1349         auto menuContext = menuNode->GetRenderContext();
1350         auto subMenuContext = subMenuNode->GetRenderContext();
1351         if (subMenuContext) {
1352             subMenuContext->UpdatePosition(
1353                 OffsetT<Dimension>(Dimension(menuPosition.GetX()), Dimension(menuPosition.GetY())));
1354         }
1355         if (menuContext) {
1356             menuContext->UpdateTransformScale(VectorF(1.0f, 1.0f));
1357         }
1358     });
1359 
1360     option.SetCurve(MENU_ANIMATION_CURVE);
1361     auto subImageNode = GetImageNode(subMenuNode);
1362     AnimationUtils::Animate(option, [subImageNode]() {
1363         CHECK_NULL_VOID(subImageNode);
1364         auto subImageContext = subImageNode->GetRenderContext();
1365         CHECK_NULL_VOID(subImageContext);
1366         subImageContext->UpdateTransformRotate(Vector5F(0.0f, 0.0f, 1.0f, 0.0f, 0.0f));
1367     });
1368 
1369     option.SetCurve(Curves::FRICTION);
1370     option.SetDuration(STACK_EXPAND_DISAPPEAR_DURATION);
1371     AnimationUtils::Animate(option, [menuNode, subMenuNode]() {
1372         auto menuContext = menuNode->GetRenderContext();
1373         auto subMenuContext = subMenuNode->GetRenderContext();
1374         if (subMenuContext) {
1375             subMenuContext->UpdateOpacity(0.0f);
1376         }
1377         if (menuContext) {
1378             menuContext->UpdateOpacity(1.0f);
1379         }
1380     }, option.GetOnFinishEvent());
1381 }
1382 
ShowMenuDisappearAnimation()1383 void MenuPattern::ShowMenuDisappearAnimation()
1384 {
1385     if (!Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE)) {
1386         return;
1387     }
1388     auto host = GetHost();
1389     CHECK_NULL_VOID(host);
1390     auto menuContext = host->GetRenderContext();
1391     MAIN_MENU_ANIMATION_CURVE->UpdateMinimumAmplitudeRatio(MINIMUM_AMPLITUDE_RATION);
1392     auto menuPosition = GetEndOffset();
1393     AnimationOption option = AnimationOption();
1394     option.SetCurve(MAIN_MENU_ANIMATION_CURVE);
1395     AnimationUtils::Animate(option, [menuContext, menuPosition]() {
1396         if (menuContext) {
1397             menuContext->UpdatePosition(
1398                 OffsetT<Dimension>(Dimension(menuPosition.GetX()), Dimension(menuPosition.GetY())));
1399             menuContext->UpdateTransformScale(VectorF(MENU_ORIGINAL_SCALE, MENU_ORIGINAL_SCALE));
1400             menuContext->UpdateOpacity(0.0f);
1401         }
1402     });
1403 }
1404 
OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper> & dirty,const DirtySwapConfig & config)1405 bool MenuPattern::OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper>& dirty, const DirtySwapConfig& config)
1406 {
1407     ShowPreviewMenuAnimation();
1408     ShowMenuAppearAnimation();
1409     ShowStackExpandMenu();
1410     auto host = GetHost();
1411     CHECK_NULL_RETURN(host, false);
1412     auto menuPosition = host->GetPaintRectOffset(false, true);
1413     SetEndOffset(menuPosition);
1414     if (config.skipMeasure || dirty->SkipMeasureContent()) {
1415         return false;
1416     }
1417 
1418     auto pipeline = host->GetContextWithCheck();
1419     CHECK_NULL_RETURN(pipeline, false);
1420     auto theme = pipeline->GetTheme<SelectTheme>();
1421     CHECK_NULL_RETURN(theme, false);
1422     auto renderContext = dirty->GetHostNode()->GetRenderContext();
1423     CHECK_NULL_RETURN(renderContext, false);
1424 
1425     auto menuProp = DynamicCast<MenuLayoutProperty>(dirty->GetLayoutProperty());
1426     CHECK_NULL_RETURN(menuProp, false);
1427     BorderRadiusProperty radius;
1428     auto defaultRadius = Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE) ?
1429         theme->GetMenuDefaultRadius() : theme->GetMenuBorderRadius();
1430     radius.SetRadius(defaultRadius);
1431     if (menuProp->GetBorderRadius().has_value()) {
1432         auto borderRadius = menuProp->GetBorderRadiusValue();
1433         auto geometryNode = dirty->GetGeometryNode();
1434         CHECK_NULL_RETURN(geometryNode, false);
1435         auto idealSize = geometryNode->GetMarginFrameSize();
1436         radius = CalcIdealBorderRadius(borderRadius, idealSize);
1437         UpdateBorderRadius(dirty->GetHostNode(), radius);
1438     }
1439     auto menuWrapper = GetMenuWrapper();
1440     DragAnimationHelper::ShowGatherAnimationWithMenu(menuWrapper);
1441     return true;
1442 }
1443 
CalcIdealBorderRadius(const BorderRadiusProperty & borderRadius,const SizeF & menuSize)1444 BorderRadiusProperty MenuPattern::CalcIdealBorderRadius(const BorderRadiusProperty& borderRadius, const SizeF& menuSize)
1445 {
1446     Dimension defaultDimension(0);
1447     BorderRadiusProperty radius = { defaultDimension, defaultDimension, defaultDimension, defaultDimension };
1448     auto host = GetHost();
1449     CHECK_NULL_RETURN(host, radius);
1450     auto pipeline = host->GetContextWithCheck();
1451     CHECK_NULL_RETURN(pipeline, radius);
1452     auto theme = pipeline->GetTheme<SelectTheme>();
1453     CHECK_NULL_RETURN(theme, radius);
1454     auto defaultRadius = Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE) ?
1455         theme->GetMenuDefaultRadius() : theme->GetMenuBorderRadius();
1456     radius.SetRadius(defaultRadius);
1457     auto radiusTopLeft = borderRadius.radiusTopLeft.value_or(Dimension()).ConvertToPx();
1458     auto radiusTopRight = borderRadius.radiusTopRight.value_or(Dimension()).ConvertToPx();
1459     auto radiusBottomLeft = borderRadius.radiusBottomLeft.value_or(Dimension()).ConvertToPx();
1460     auto radiusBottomRight = borderRadius.radiusBottomRight.value_or(Dimension()).ConvertToPx();
1461     auto maxRadiusW = std::max(radiusTopLeft + radiusTopRight, radiusBottomLeft + radiusBottomRight);
1462     auto maxRadiusH = std::max(radiusTopLeft + radiusBottomLeft, radiusTopRight + radiusBottomRight);
1463     if (Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE)) {
1464         if (LessOrEqual(maxRadiusW, menuSize.Width()) && LessOrEqual(maxRadiusH, menuSize.Height())) {
1465             radius = borderRadius;
1466         }
1467     } else {
1468         if (LessNotEqual(maxRadiusW, menuSize.Width())) {
1469             radius = borderRadius;
1470         }
1471     }
1472 
1473     return radius;
1474 }
1475 
UpdateBorderRadius(const RefPtr<FrameNode> & menuNode,const BorderRadiusProperty & borderRadius)1476 void MenuPattern::UpdateBorderRadius(const RefPtr<FrameNode>& menuNode, const BorderRadiusProperty& borderRadius)
1477 {
1478     CHECK_NULL_VOID(menuNode);
1479     auto menuRenderContext = menuNode->GetRenderContext();
1480     CHECK_NULL_VOID(menuRenderContext);
1481     menuRenderContext->UpdateBorderRadius(borderRadius);
1482     if (Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_ELEVEN)) {
1483         menuRenderContext->UpdateOuterBorderRadius(borderRadius);
1484     }
1485     auto node = menuNode->GetChildren().front();
1486     CHECK_NULL_VOID(node);
1487     auto scrollNode = AceType::DynamicCast<FrameNode>(node);
1488     CHECK_NULL_VOID(scrollNode);
1489     auto scrollRenderContext = scrollNode->GetRenderContext();
1490     CHECK_NULL_VOID(scrollRenderContext);
1491     scrollRenderContext->UpdateBorderRadius(borderRadius);
1492 }
1493 
UpdateBorderRadius(const RefPtr<FrameNode> & menuNode,const BorderRadiusProperty & borderRadius)1494 void InnerMenuPattern::UpdateBorderRadius(const RefPtr<FrameNode>& menuNode, const BorderRadiusProperty& borderRadius)
1495 {
1496     CHECK_NULL_VOID(menuNode);
1497     auto menuRenderContext = menuNode->GetRenderContext();
1498     CHECK_NULL_VOID(menuRenderContext);
1499     menuRenderContext->UpdateBorderRadius(borderRadius);
1500 }
1501 
GetMainMenuPattern() const1502 RefPtr<MenuPattern> MenuPattern::GetMainMenuPattern() const
1503 {
1504     auto wrapperFrameNode = GetMenuWrapper();
1505     CHECK_NULL_RETURN(wrapperFrameNode, nullptr);
1506     auto mainMenuUINode = wrapperFrameNode->GetChildAtIndex(0);
1507     CHECK_NULL_RETURN(mainMenuUINode, nullptr);
1508     auto mainMenuFrameNode = AceType::DynamicCast<FrameNode>(mainMenuUINode);
1509     return mainMenuFrameNode->GetPattern<MenuPattern>();
1510 }
1511 
RecordItemsAndGroups()1512 void InnerMenuPattern::RecordItemsAndGroups()
1513 {
1514     itemsAndGroups_.clear();
1515     auto host = GetHost();
1516     std::stack<RefPtr<UINode>> nodeStack;
1517     nodeStack.emplace(host);
1518     bool isMenu = true;
1519 
1520     while (!nodeStack.empty()) {
1521         auto currentNode = nodeStack.top();
1522         nodeStack.pop();
1523         // push items and item groups, skip menu node
1524         if (!isMenu && AceType::InstanceOf<FrameNode>(currentNode)) {
1525             itemsAndGroups_.emplace_back(WeakClaim(RawPtr(currentNode)));
1526             continue;
1527         }
1528         isMenu = false;
1529         // skip other type UiNode, such as ForEachNode
1530         for (int32_t index = static_cast<int32_t>(currentNode->GetChildren().size()) - 1; index >= 0; index--) {
1531             nodeStack.push(currentNode->GetChildAtIndex(index));
1532         }
1533     }
1534 }
1535 
FindSiblingMenuCount()1536 uint32_t InnerMenuPattern::FindSiblingMenuCount()
1537 {
1538     auto host = GetHost();
1539     CHECK_NULL_RETURN(host, 0);
1540     auto parent = host->GetParent();
1541     CHECK_NULL_RETURN(parent, 0);
1542     auto siblings = parent->GetChildren();
1543     uint32_t count = 0;
1544     for (auto&& sibling : siblings) {
1545         if (sibling->GetTag() == V2::MENU_ETS_TAG && sibling != host) {
1546             ++count;
1547         }
1548     }
1549     return count;
1550 }
1551 
ApplyDesktopMenuTheme()1552 void InnerMenuPattern::ApplyDesktopMenuTheme()
1553 {
1554     auto host = GetHost();
1555     CHECK_NULL_VOID(host);
1556     Shadow shadow;
1557     if (GetShadowFromTheme(ShadowStyle::OuterDefaultSM, shadow)) {
1558         host->GetRenderContext()->UpdateBackShadow(shadow);
1559     }
1560 }
1561 
ApplyMultiMenuTheme()1562 void InnerMenuPattern::ApplyMultiMenuTheme()
1563 {
1564     auto host = GetHost();
1565     CHECK_NULL_VOID(host);
1566     Shadow shadow;
1567     if (GetShadowFromTheme(ShadowStyle::None, shadow)) {
1568         host->GetRenderContext()->UpdateBackShadow(shadow);
1569     }
1570 }
1571 
OnColorConfigurationUpdate()1572 void MenuPattern::OnColorConfigurationUpdate()
1573 {
1574     auto host = GetHost();
1575     CHECK_NULL_VOID(host);
1576     auto pipeline = host->GetContextWithCheck();
1577     CHECK_NULL_VOID(pipeline);
1578 
1579     auto menuTheme = pipeline->GetTheme<SelectTheme>();
1580     CHECK_NULL_VOID(menuTheme);
1581 
1582     auto menuPattern = host->GetPattern<MenuPattern>();
1583     CHECK_NULL_VOID(menuPattern);
1584 
1585     auto renderContext = host->GetRenderContext();
1586     if (Container::LessThanAPIVersion(PlatformVersion::VERSION_ELEVEN) || !renderContext->IsUniRenderEnabled()) {
1587         renderContext->UpdateBackgroundColor(menuTheme->GetBackgroundColor());
1588     } else {
1589         renderContext->UpdateBackBlurStyle(renderContext->GetBackBlurStyle());
1590     }
1591 
1592     auto optionNode = menuPattern->GetOptions();
1593     for (const auto& child : optionNode) {
1594         auto optionsPattern = child->GetPattern<OptionPattern>();
1595         optionsPattern->SetFontColor(menuTheme->GetFontColor());
1596 
1597         child->MarkModifyDone();
1598         child->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
1599     }
1600     host->SetNeedCallChildrenUpdate(false);
1601 }
1602 
InitPanEvent(const RefPtr<GestureEventHub> & gestureHub)1603 void MenuPattern::InitPanEvent(const RefPtr<GestureEventHub>& gestureHub)
1604 {
1605     CHECK_NULL_VOID(gestureHub);
1606     auto actionEndTask = [weak = WeakClaim(this)](const GestureEvent& info) {
1607         auto pattern = weak.Upgrade();
1608         CHECK_NULL_VOID(pattern);
1609         if (pattern->IsMenuScrollable()) {
1610             return;
1611         }
1612         auto offsetX = static_cast<float>(info.GetOffsetX());
1613         auto offsetY = static_cast<float>(info.GetOffsetY());
1614         auto offsetPerSecondX = info.GetVelocity().GetOffsetPerSecond().GetX();
1615         auto offsetPerSecondY = info.GetVelocity().GetOffsetPerSecond().GetY();
1616         auto velocity =
1617             static_cast<float>(std::sqrt(offsetPerSecondX * offsetPerSecondX + offsetPerSecondY * offsetPerSecondY));
1618         pattern->HandleDragEnd(offsetX, offsetY, velocity);
1619     };
1620     auto actionScrollEndTask = [weak = WeakClaim(this)](const GestureEvent& info) {
1621         auto pattern = weak.Upgrade();
1622         CHECK_NULL_VOID(pattern);
1623         if (pattern->IsMenuScrollable()) {
1624             return;
1625         }
1626         auto offsetX = static_cast<float>(info.GetOffsetX());
1627         auto offsetY = static_cast<float>(info.GetOffsetY());
1628         auto offsetPerSecondX = info.GetVelocity().GetOffsetPerSecond().GetX();
1629         auto offsetPerSecondY = info.GetVelocity().GetOffsetPerSecond().GetY();
1630         auto velocity =
1631             static_cast<float>(std::sqrt(offsetPerSecondX * offsetPerSecondX + offsetPerSecondY * offsetPerSecondY));
1632         pattern->HandleScrollDragEnd(offsetX, offsetY, velocity);
1633     };
1634     PanDirection panDirection;
1635     panDirection.type = PanDirection::ALL;
1636     auto panEvent = MakeRefPtr<PanEvent>(nullptr, nullptr, std::move(actionEndTask), nullptr);
1637     gestureHub->AddPanEvent(panEvent, panDirection, 1, DEFAULT_PAN_DISTANCE);
1638     gestureHub->AddPreviewMenuHandleDragEnd(std::move(actionScrollEndTask));
1639 }
1640 
HandleDragEnd(float offsetX,float offsetY,float velocity)1641 void MenuPattern::HandleDragEnd(float offsetX, float offsetY, float velocity)
1642 {
1643     if ((LessOrEqual(std::abs(offsetY), std::abs(offsetX)) || LessOrEqual(offsetY, 0.0f)) &&
1644         LessOrEqual(velocity, PAN_MAX_VELOCITY)) {
1645         return;
1646     }
1647     auto menuWrapper = GetMenuWrapper();
1648     CHECK_NULL_VOID(menuWrapper);
1649     auto wrapperPattern = menuWrapper->GetPattern<MenuWrapperPattern>();
1650     CHECK_NULL_VOID(wrapperPattern);
1651     wrapperPattern->HideMenu();
1652 }
1653 
HandleScrollDragEnd(float offsetX,float offsetY,float velocity)1654 void MenuPattern::HandleScrollDragEnd(float offsetX, float offsetY, float velocity)
1655 {
1656     if ((LessOrEqual(std::abs(offsetY), std::abs(offsetX)) || !NearZero(offsetY)) &&
1657         LessOrEqual(velocity, PAN_MAX_VELOCITY)) {
1658         return;
1659     }
1660     auto menuWrapper = GetMenuWrapper();
1661     CHECK_NULL_VOID(menuWrapper);
1662     auto wrapperPattern = menuWrapper->GetPattern<MenuWrapperPattern>();
1663     CHECK_NULL_VOID(wrapperPattern);
1664     wrapperPattern->HideMenu();
1665 }
1666 
DumpInfo()1667 void MenuPattern::DumpInfo()
1668 {
1669     DumpLog::GetInstance().AddDesc(
1670         std::string("MenuType: ").append(std::to_string(static_cast<int32_t>(GetMenuType()))));
1671 }
1672 
GetSelectMenuWidth()1673 float MenuPattern::GetSelectMenuWidth()
1674 {
1675     RefPtr<GridColumnInfo> columnInfo = GridSystemManager::GetInstance().GetInfoByType(GridColumnType::MENU);
1676     CHECK_NULL_RETURN(columnInfo, MIN_SELECT_MENU_WIDTH.ConvertToPx());
1677     auto parent = columnInfo->GetParent();
1678     CHECK_NULL_RETURN(parent, MIN_SELECT_MENU_WIDTH.ConvertToPx());
1679     parent->BuildColumnWidth();
1680     auto defaultWidth = static_cast<float>(columnInfo->GetWidth(COLUMN_NUM));
1681     float finalWidth = MIN_SELECT_MENU_WIDTH.ConvertToPx();
1682 
1683     if (IsWidthModifiedBySelect()) {
1684         auto menuLayoutProperty = GetLayoutProperty<MenuLayoutProperty>();
1685         auto selectmodifiedwidth = menuLayoutProperty->GetSelectMenuModifiedWidth();
1686         finalWidth = selectmodifiedwidth.value();
1687     } else {
1688         finalWidth = defaultWidth;
1689     }
1690 
1691     if (finalWidth < MIN_SELECT_MENU_WIDTH.ConvertToPx()) {
1692         finalWidth = defaultWidth;
1693     }
1694 
1695     return finalWidth;
1696 }
1697 
OnItemPressed(const RefPtr<UINode> & parent,int32_t index,bool press,bool hover)1698 void MenuPattern::OnItemPressed(const RefPtr<UINode>& parent, int32_t index, bool press, bool hover)
1699 {
1700     CHECK_NULL_VOID(parent);
1701     if (parent->GetTag() == V2::MENU_ITEM_GROUP_ETS_TAG) {
1702         auto pattern = DynamicCast<FrameNode>(parent)->GetPattern<MenuItemGroupPattern>();
1703         CHECK_NULL_VOID(pattern);
1704         pattern->OnIntItemPressed(index, press);
1705     }
1706     RefPtr<UINode> nextNode = nullptr;
1707     if (parent->GetTag() == V2::JS_SYNTAX_ITEM_ETS_TAG) {
1708         nextNode = GetForEachMenuItem(parent, true);
1709     } else {
1710         const size_t size = parent->GetChildren().size();
1711         if (size == 0 || index >= static_cast<int32_t>(size - 1)) {
1712             return;
1713         }
1714         nextNode = parent->GetChildAtIndex(index + 1);
1715     }
1716     CHECK_NULL_VOID(nextNode);
1717     if (nextNode->GetTag() == V2::JS_FOR_EACH_ETS_TAG) {
1718         nextNode = GetForEachMenuItem(nextNode, true);
1719     }
1720     CHECK_NULL_VOID(nextNode);
1721     if (!InstanceOf<FrameNode>(nextNode)) {
1722         LOGW("next menuNode is not a frameNode! type = %{public}s", nextNode->GetTag().c_str());
1723         return;
1724     }
1725     if (nextNode->GetTag() == V2::MENU_ITEM_GROUP_ETS_TAG) {
1726         auto pattern = DynamicCast<FrameNode>(nextNode)->GetPattern<MenuItemGroupPattern>();
1727         CHECK_NULL_VOID(pattern);
1728         pattern->OnExtItemPressed(press, true);
1729     } else {
1730         auto props = DynamicCast<FrameNode>(nextNode)->GetPaintProperty<MenuItemPaintProperty>();
1731         CHECK_NULL_VOID(props);
1732         // need save needDivider property due to some items shoud not have divide in not pressed state
1733         hover ? props->UpdateHover(press) : props->UpdatePress(press);
1734         nextNode->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
1735     }
1736     if (index > 0) {
1737         auto prevNode = parent->GetChildAtIndex(index - 1);
1738         CHECK_NULL_VOID(prevNode);
1739         if (!InstanceOf<FrameNode>(prevNode)) {
1740             LOGW("prev menuNode is not a frameNode! type = %{public}s", prevNode->GetTag().c_str());
1741             return;
1742         }
1743         if (prevNode->GetTag() == V2::MENU_ITEM_GROUP_ETS_TAG) {
1744             auto pattern = DynamicCast<FrameNode>(prevNode)->GetPattern<MenuItemGroupPattern>();
1745             CHECK_NULL_VOID(pattern);
1746             pattern->OnExtItemPressed(press, false);
1747         }
1748     }
1749 }
1750 
GetForEachMenuItem(const RefPtr<UINode> & parent,bool next)1751 RefPtr<UINode> MenuPattern::GetForEachMenuItem(const RefPtr<UINode>& parent, bool next)
1752 {
1753     CHECK_NULL_RETURN(parent, nullptr);
1754     if (parent->GetTag() == V2::JS_SYNTAX_ITEM_ETS_TAG) {
1755         auto forEachNode = AceType::DynamicCast<UINode>(parent->GetParent());
1756         CHECK_NULL_RETURN(forEachNode, nullptr);
1757         auto syntIndex = forEachNode->GetChildIndex(parent);
1758         const size_t size = forEachNode->GetChildren().size();
1759         if (next) {
1760             if (size > 0 && syntIndex < static_cast<int32_t>(size - 1)) { // next is inside forEach
1761                 auto nextSyntax = forEachNode->GetChildAtIndex(syntIndex + 1);
1762                 CHECK_NULL_RETURN(nextSyntax, nullptr);
1763                 return nextSyntax->GetFirstChild();
1764             } else { // next is after forEach
1765                 return GetOutsideForEachMenuItem(forEachNode, true);
1766             }
1767         } else {
1768             if (syntIndex > 0) { // prev is inside forEach
1769                 auto prevSyntax = forEachNode->GetChildAtIndex(syntIndex - 1);
1770                 CHECK_NULL_RETURN(prevSyntax, nullptr);
1771                 return prevSyntax->GetFirstChild();
1772             } else { // prev is before forEach
1773                 return GetOutsideForEachMenuItem(forEachNode, false);
1774             }
1775         }
1776     }
1777     if (parent->GetTag() == V2::JS_FOR_EACH_ETS_TAG) {
1778         auto nextSyntax = next? parent->GetFirstChild(): parent->GetLastChild();
1779         CHECK_NULL_RETURN(nextSyntax, nullptr);
1780         return nextSyntax->GetFirstChild();
1781     }
1782     return nullptr;
1783 }
1784 
GetOutsideForEachMenuItem(const RefPtr<UINode> & forEachNode,bool next)1785 RefPtr<UINode> MenuPattern::GetOutsideForEachMenuItem(const RefPtr<UINode>& forEachNode, bool next)
1786 {
1787     auto parentForEachNode = AceType::DynamicCast<UINode>(forEachNode->GetParent());
1788     CHECK_NULL_RETURN(parentForEachNode, nullptr);
1789     auto forEachIndex = parentForEachNode->GetChildIndex(forEachNode);
1790     int32_t shift = next ? 1 : -1;
1791     const size_t size = parentForEachNode->GetChildren().size();
1792     if (size > 0 && (forEachIndex + shift) >= 0 && (forEachIndex + shift) <= static_cast<int32_t>(size - 1)) {
1793         return parentForEachNode->GetChildAtIndex(forEachIndex + shift);
1794     } else {
1795         return nullptr;
1796     }
1797 }
1798 
IsMenuScrollable() const1799 bool MenuPattern::IsMenuScrollable() const
1800 {
1801     auto host = GetHost();
1802     CHECK_NULL_RETURN(host, false);
1803     auto firstChild = DynamicCast<FrameNode>(host->GetChildAtIndex(0));
1804     CHECK_NULL_RETURN(firstChild, false);
1805     if (firstChild->GetTag() == V2::SCROLL_ETS_TAG) {
1806         auto scrollPattern = firstChild->GetPattern<ScrollPattern>();
1807         CHECK_NULL_RETURN(scrollPattern, false);
1808         return scrollPattern->IsScrollable() && Positive(scrollPattern->GetScrollableDistance());
1809     }
1810     return false;
1811 }
1812 
1813 } // namespace OHOS::Ace::NG
1814