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_ng/base/ui_node.h"
32 #include "core/components_ng/manager/drag_drop/utils/drag_animation_helper.h"
33 #include "core/components_ng/pattern/menu/menu_item/menu_item_layout_property.h"
34 #include "core/components_ng/pattern/menu/menu_item/menu_item_pattern.h"
35 #include "core/components_ng/pattern/menu/menu_item_group/menu_item_group_pattern.h"
36 #include "core/components_ng/pattern/menu/menu_layout_property.h"
37 #include "core/components_ng/pattern/menu/menu_theme.h"
38 #include "core/components_ng/pattern/menu/menu_view.h"
39 #include "core/components_ng/pattern/menu/multi_menu_layout_algorithm.h"
40 #include "core/components_ng/pattern/menu/preview/menu_preview_pattern.h"
41 #include "core/components_ng/pattern/menu/sub_menu_layout_algorithm.h"
42 #include "core/components_ng/pattern/menu/wrapper/menu_wrapper_pattern.h"
43 #include "core/components_ng/pattern/option/option_pattern.h"
44 #include "core/components_ng/pattern/option/option_view.h"
45 #include "core/components_ng/pattern/scroll/scroll_pattern.h"
46 #include "core/components_ng/pattern/text/text_layout_property.h"
47 #include "core/components_ng/pattern/stack/stack_pattern.h"
48 #include "core/components_ng/property/border_property.h"
49 #include "core/components_v2/inspector/inspector_constants.h"
50 #include "core/event/touch_event.h"
51 #include "core/pipeline/pipeline_base.h"
52 #include "core/pipeline_ng/pipeline_context.h"
53 #include "core/components/theme/shadow_theme.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             auto nodesSet = AceType::DynamicCast<UINode>(child);
535             CHECK_NULL_VOID(nodesSet);
536             UpdateMenuItemChildren(nodesSet);
537         } else {
538             // do nothing
539         }
540         index++;
541     }
542 }
543 
UpdateSelectParam(const std::vector<SelectParam> & params)544 void MenuPattern::UpdateSelectParam(const std::vector<SelectParam>& params)
545 {
546     if (!isSelectMenu_) {
547         return;
548     }
549     auto host = GetHost();
550     CHECK_NULL_VOID(host);
551     const auto& children = GetOptions();
552     auto childCount = children.size();
553     auto paramCount = params.size();
554     size_t updateCount = std::min(paramCount, childCount);
555     auto childIt = children.begin();
556     for (size_t i = 0; i < updateCount; i++, childIt++) {
557         const auto& childNode = AceType::DynamicCast<FrameNode>(*childIt);
558         CHECK_NULL_VOID(childNode);
559         if (i == 0) {
560             auto props = childNode->GetPaintProperty<OptionPaintProperty>();
561             CHECK_NULL_VOID(props);
562             props->UpdateNeedDivider(false);
563             auto focusHub = childNode->GetOrCreateFocusHub();
564             CHECK_NULL_VOID(focusHub);
565             focusHub->SetIsDefaultFocus(true);
566         }
567         auto optionPattern = childNode->GetPattern<OptionPattern>();
568         CHECK_NULL_VOID(optionPattern);
569         optionPattern->UpdateText(params.at(i).text);
570         optionPattern->UpdateIcon(params.at(i).icon, params.at(i).symbolIcon);
571         childNode->MarkModifyDone();
572         childNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
573     }
574     for (size_t i = updateCount; i < paramCount; i++) {
575         auto optionNode = OptionView::CreateSelectOption(params.at(i), i);
576         if (i == 0) {
577             auto props = optionNode->GetPaintProperty<OptionPaintProperty>();
578             props->UpdateNeedDivider(false);
579         }
580         MountOption(optionNode);
581         optionNode->MarkModifyDone();
582         optionNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
583     }
584     for (size_t i = childCount; i > updateCount; i--) {
585         RemoveOption();
586     }
587     host->MarkModifyDone();
588     host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
589 }
590 
HideMenu(bool isMenuOnTouch,OffsetF position) const591 void MenuPattern::HideMenu(bool isMenuOnTouch, OffsetF position) const
592 {
593     auto host = GetHost();
594     CHECK_NULL_VOID(host);
595     auto pipeline = host->GetContextWithCheck();
596     CHECK_NULL_VOID(pipeline);
597     auto theme = pipeline->GetTheme<SelectTheme>();
598     CHECK_NULL_VOID(theme);
599     auto expandDisplay = theme->GetExpandDisplay();
600     auto rootMenuPattern = AceType::DynamicCast<MenuPattern>(host->GetPattern());
601     CHECK_NULL_VOID(rootMenuPattern);
602     // copy menu pattern properties to rootMenu
603     auto layoutProperty = rootMenuPattern->GetLayoutProperty<MenuLayoutProperty>();
604     CHECK_NULL_VOID(layoutProperty);
605     bool isShowInSubWindow = layoutProperty->GetShowInSubWindowValue(true);
606     auto wrapper = GetMenuWrapper();
607     CHECK_NULL_VOID(wrapper);
608     if (wrapper->GetTag() == V2::SELECT_OVERLAY_ETS_TAG) {
609         return;
610     }
611     if (((IsContextMenu() || (expandDisplay && isShowInSubWindow))) && (targetTag_ != V2::SELECT_ETS_TAG)) {
612         SubwindowManager::GetInstance()->HideMenuNG(wrapper, targetId_);
613         return;
614     }
615 
616     if (HideStackExpandMenu(position)) {
617         return;
618     }
619 
620     auto overlayManager = pipeline->GetOverlayManager();
621     CHECK_NULL_VOID(overlayManager);
622     overlayManager->HideMenu(wrapper, targetId_, isMenuOnTouch);
623     overlayManager->EraseMenuInfo(targetId_);
624 }
625 
HideStackExpandMenu(const OffsetF & position) const626 bool MenuPattern::HideStackExpandMenu(const OffsetF& position) const
627 {
628     auto wrapper = GetMenuWrapper();
629     CHECK_NULL_RETURN(wrapper, false);
630     auto outterMenu = wrapper->GetFirstChild();
631     CHECK_NULL_RETURN(outterMenu, false);
632     auto menuWrapperPattern = wrapper->GetPattern<MenuWrapperPattern>();
633     CHECK_NULL_RETURN(menuWrapperPattern, false);
634     auto innerMenu = menuWrapperPattern->GetMenuChild(outterMenu);
635     CHECK_NULL_RETURN(innerMenu, false);
636     auto innerMenuPattern = AceType::DynamicCast<MenuPattern>(innerMenu->GetPattern());
637     CHECK_NULL_RETURN(innerMenuPattern, false);
638     auto layoutProps = innerMenuPattern->GetLayoutProperty<MenuLayoutProperty>();
639     CHECK_NULL_RETURN(layoutProps, false);
640     auto expandingMode = layoutProps->GetExpandingMode().value_or(SubMenuExpandingMode::SIDE);
641     if (IsSubMenu() && expandingMode == SubMenuExpandingMode::STACK) {
642         auto host = GetHost();
643         CHECK_NULL_RETURN(host, false);
644         auto hostZone = host->GetPaintRectOffset();
645         auto scroll = host->GetFirstChild();
646         CHECK_NULL_RETURN(scroll, false);
647         auto column = scroll->GetFirstChild();
648         CHECK_NULL_RETURN(column, false);
649         auto clickAreaNode = AceType::DynamicCast<FrameNode>(column->GetFirstChild());
650         CHECK_NULL_RETURN(clickAreaNode, false);
651         auto clickAreaZone = clickAreaNode->GetGeometryNode()->GetFrameRect();
652         clickAreaZone.SetLeft(hostZone.GetX());
653         clickAreaZone.SetTop(hostZone.GetY());
654         if (clickAreaZone.IsInRegion(PointF(position.GetX(), position.GetY()))) {
655             HideStackMenu();
656             return true;
657         }
658     } else if (expandingMode == SubMenuExpandingMode::STACK) {
659         auto host = GetHost();
660         CHECK_NULL_RETURN(host, false);
661         auto hostZone = host->GetPaintRectOffset();
662         auto clickAreaZone = host->GetGeometryNode()->GetFrameRect();
663         clickAreaZone.SetLeft(hostZone.GetX());
664         clickAreaZone.SetTop(hostZone.GetY());
665         if (clickAreaZone.IsInRegion(PointF(position.GetX(), position.GetY()))) {
666             auto wrapperPattern = wrapper->GetPattern<MenuWrapperPattern>();
667             CHECK_NULL_RETURN(wrapperPattern, false);
668             wrapperPattern->HideSubMenu();
669             return true;
670         }
671     }
672     return false;
673 }
674 
HideStackMenu() const675 void MenuPattern::HideStackMenu() const
676 {
677     auto host = GetHost();
678     CHECK_NULL_VOID(host);
679     auto wrapper = GetMenuWrapper();
680     CHECK_NULL_VOID(wrapper);
681     AnimationOption option;
682     option.SetOnFinishEvent(
683         [weak = WeakClaim(RawPtr(wrapper)), subMenuWk = WeakClaim(RawPtr(host))] {
684             auto subMenu = subMenuWk.Upgrade();
685             CHECK_NULL_VOID(subMenu);
686             auto pipeline = subMenu->GetContextWithCheck();
687             CHECK_NULL_VOID(pipeline);
688             auto taskExecutor = pipeline->GetTaskExecutor();
689             CHECK_NULL_VOID(taskExecutor);
690             taskExecutor->PostTask(
691                 [weak, subMenuWk]() {
692                     auto subMenuNode = subMenuWk.Upgrade();
693                     CHECK_NULL_VOID(subMenuNode);
694                     auto menuWrapper = weak.Upgrade();
695                     CHECK_NULL_VOID(menuWrapper);
696                     menuWrapper->RemoveChild(subMenuNode);
697                     menuWrapper->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF_AND_CHILD);
698                 },
699                 TaskExecutor::TaskType::UI, "HideStackMenu");
700     });
701     auto menuPattern = AceType::DynamicCast<MenuPattern>(host->GetPattern());
702     if (menuPattern) {
703         menuPattern->RemoveParentHoverStyle();
704         auto frameNode = FrameNode::GetFrameNode(menuPattern->GetTargetTag(), menuPattern->GetTargetId());
705         CHECK_NULL_VOID(frameNode);
706         auto menuItem = frameNode->GetPattern<MenuItemPattern>();
707         if (menuItem) {
708             menuItem->SetIsSubMenuShowed(false);
709         }
710     }
711     auto menuNode = AceType::DynamicCast<FrameNode>(wrapper->GetFirstChild());
712     CHECK_NULL_VOID(menuNode);
713     ShowStackExpandDisappearAnimation(menuNode, host, option);
714 }
715 
HideSubMenu()716 void MenuPattern::HideSubMenu()
717 {
718     if (!showedSubMenu_) {
719         return;
720     }
721     auto subMenuPattern = showedSubMenu_->GetPattern<MenuPattern>();
722     CHECK_NULL_VOID(subMenuPattern);
723     subMenuPattern->RemoveParentHoverStyle();
724 
725     auto menuItem = subMenuPattern->GetParentMenuItem();
726     CHECK_NULL_VOID(menuItem);
727     auto menuItemPattern = menuItem->GetPattern<MenuItemPattern>();
728     CHECK_NULL_VOID(menuItemPattern);
729     menuItemPattern->SetIsSubMenuShowed(false);
730     menuItemPattern->ClearHoverRegions();
731     menuItemPattern->ResetWrapperMouseEvent();
732 
733     auto wrapper = GetMenuWrapper();
734     CHECK_NULL_VOID(wrapper);
735     wrapper->RemoveChild(showedSubMenu_);
736     wrapper->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF_AND_CHILD);
737     showedSubMenu_.Reset();
738 }
739 
GetMenuWrapper() const740 RefPtr<FrameNode> MenuPattern::GetMenuWrapper() const
741 {
742     auto host = GetHost();
743     CHECK_NULL_RETURN(host, nullptr);
744     auto parent = host->GetParent();
745     while (parent) {
746         if (parent->GetTag() == V2::MENU_WRAPPER_ETS_TAG || parent->GetTag() == V2::SELECT_OVERLAY_ETS_TAG) {
747             return AceType::DynamicCast<FrameNode>(parent);
748         }
749         parent = parent->GetParent();
750     }
751     return nullptr;
752 }
753 
754 // search for inner <Menu> node, once found a <Menu> node, count the number of sibling <Menu>
GetInnerMenuCount() const755 uint32_t MenuPattern::GetInnerMenuCount() const
756 {
757     if (type_ == MenuType::MULTI_MENU || type_ == MenuType::DESKTOP_MENU || IsSelectOverlayCustomMenu()) {
758         return 0;
759     }
760 
761     auto host = GetHost();
762     CHECK_NULL_RETURN(host, 0);
763     auto child = host->GetChildAtIndex(0);
764     uint32_t depth = 0;
765     while (child && depth < MAX_SEARCH_DEPTH) {
766         // found component <Menu>
767         if (child->GetTag() == V2::JS_VIEW_ETS_TAG) {
768             child = child->GetFrameChildByIndex(0, false);
769             if (child && child->GetTag() == V2::JS_VIEW_ETS_TAG) {
770                 child = child->GetChildAtIndex(0);
771                 ++depth;
772             }
773             continue;
774         }
775         if (child->GetTag() == V2::MENU_ETS_TAG) {
776             auto parent = child->GetParent();
777             CHECK_NULL_RETURN(parent, 0);
778             return parent->GetChildren().size();
779         }
780         child = child->GetChildAtIndex(0);
781         ++depth;
782     }
783     return 0;
784 }
785 
GetFirstInnerMenu() const786 RefPtr<FrameNode> MenuPattern::GetFirstInnerMenu() const
787 {
788     if (type_ == MenuType::MULTI_MENU || type_ == MenuType::DESKTOP_MENU) {
789         return nullptr;
790     }
791 
792     auto host = GetHost();
793     CHECK_NULL_RETURN(host, nullptr);
794     uint32_t depth = 0;
795     auto child = host->GetChildAtIndex(0);
796     while (child && depth < MAX_SEARCH_DEPTH) {
797         // found component <Menu>
798         if (child->GetTag() == V2::JS_VIEW_ETS_TAG) {
799             child = child->GetFrameChildByIndex(0, false);
800             if (child && child->GetTag() == V2::JS_VIEW_ETS_TAG) {
801                 child = child->GetChildAtIndex(0);
802                 ++depth;
803             }
804             continue;
805         }
806         if (child->GetTag() == V2::MENU_ETS_TAG) {
807             return AceType::DynamicCast<FrameNode>(child);
808         }
809         child = child->GetChildAtIndex(0);
810         ++depth;
811     }
812     return nullptr;
813 }
814 
CopyMenuAttr(const RefPtr<FrameNode> & menuNode) const815 void MenuPattern::CopyMenuAttr(const RefPtr<FrameNode>& menuNode) const
816 {
817     auto pattern = AceType::DynamicCast<MenuPattern>(menuNode->GetPattern());
818     CHECK_NULL_VOID(pattern);
819 
820     auto host = GetHost();
821     CHECK_NULL_VOID(host);
822     auto rootMenuPattern = AceType::DynamicCast<MenuPattern>(host->GetPattern());
823     CHECK_NULL_VOID(rootMenuPattern);
824 
825     // copy menu pattern properties to rootMenu
826     auto layoutProperty = pattern->GetLayoutProperty<MenuLayoutProperty>();
827     CHECK_NULL_VOID(layoutProperty);
828     auto rootMenuLayoutProperty = rootMenuPattern->GetLayoutProperty<MenuLayoutProperty>();
829     CHECK_NULL_VOID(rootMenuLayoutProperty);
830     if (layoutProperty->GetBorderRadius().has_value()) {
831         rootMenuLayoutProperty->UpdateBorderRadius(layoutProperty->GetBorderRadiusValue());
832     }
833 }
834 
835 // mount option on menu
MountOption(const RefPtr<FrameNode> & option)836 void MenuPattern::MountOption(const RefPtr<FrameNode>& option)
837 {
838     auto column = GetMenuColumn();
839     CHECK_NULL_VOID(column);
840     auto pattern = option->GetPattern<OptionPattern>();
841     CHECK_NULL_VOID(pattern);
842     pattern->SetMenu(GetHost());
843     AddOptionNode(option);
844     option->MountToParent(column);
845 }
846 
847 // remove option from menu
RemoveOption()848 void MenuPattern::RemoveOption()
849 {
850     auto column = GetMenuColumn();
851     CHECK_NULL_VOID(column);
852     auto endOption = column->GetChildren().back();
853     CHECK_NULL_VOID(endOption);
854     column->RemoveChild(endOption);
855     PopOptionNode();
856 }
857 
GetMenuColumn() const858 RefPtr<FrameNode> MenuPattern::GetMenuColumn() const
859 {
860     auto menu = GetHost();
861     CHECK_NULL_RETURN(menu, nullptr);
862     auto scroll = menu->GetChildren().front();
863     CHECK_NULL_RETURN(scroll, nullptr);
864     auto column = scroll->GetChildren().front();
865     return DynamicCast<FrameNode>(column);
866 }
867 
DisableTabInMenu()868 void MenuPattern::DisableTabInMenu()
869 {
870     if (IsMultiMenu() || IsDesktopMenu()) {
871         // multi menu not has scroll and column
872         return;
873     }
874     // disable tab in menu
875     auto column = GetMenuColumn();
876     CHECK_NULL_VOID(column);
877     auto columnFocusHub = column->GetOrCreateFocusHub();
878     CHECK_NULL_VOID(columnFocusHub);
879 
880     auto onKeyEvent = [](const KeyEvent& event) -> bool {
881         if (event.action != KeyAction::DOWN) {
882             return false;
883         }
884         return event.code == KeyCode::KEY_TAB;
885     };
886     columnFocusHub->SetOnKeyEventInternal(std::move(onKeyEvent));
887 }
888 
CreateLayoutAlgorithm()889 RefPtr<LayoutAlgorithm> MenuPattern::CreateLayoutAlgorithm()
890 {
891     switch (type_) {
892         case MenuType::MULTI_MENU:
893         case MenuType::DESKTOP_MENU:
894             return MakeRefPtr<MultiMenuLayoutAlgorithm>();
895         case MenuType::SUB_MENU:
896         case MenuType::SELECT_OVERLAY_SUB_MENU:
897             return MakeRefPtr<SubMenuLayoutAlgorithm>();
898         default:
899             return MakeRefPtr<MenuLayoutAlgorithm>(targetId_, targetTag_, lastPosition_);
900     }
901 }
902 
GetShadowFromTheme(ShadowStyle shadowStyle,Shadow & shadow)903 bool MenuPattern::GetShadowFromTheme(ShadowStyle shadowStyle, Shadow& shadow)
904 {
905     auto colorMode = SystemProperties::GetColorMode();
906     if (shadowStyle == ShadowStyle::None) {
907         return true;
908     }
909     auto host = GetHost();
910     auto pipelineContext = host->GetContextRefPtr();
911     CHECK_NULL_RETURN(pipelineContext, false);
912     auto shadowTheme = pipelineContext->GetTheme<ShadowTheme>();
913     CHECK_NULL_RETURN(shadowTheme, false);
914     shadow = shadowTheme->GetShadow(shadowStyle, colorMode);
915     return true;
916 }
917 
ResetTheme(const RefPtr<FrameNode> & host,bool resetForDesktopMenu)918 void MenuPattern::ResetTheme(const RefPtr<FrameNode>& host, bool resetForDesktopMenu)
919 {
920     auto renderContext = host->GetRenderContext();
921     CHECK_NULL_VOID(renderContext);
922     auto scroll = DynamicCast<FrameNode>(host->GetFirstChild());
923     CHECK_NULL_VOID(scroll);
924 
925     if (resetForDesktopMenu) {
926         // DesktopMenu apply shadow on inner Menu node
927         Shadow shadow;
928         if (GetShadowFromTheme(ShadowStyle::None, shadow)) {
929             renderContext->UpdateBackShadow(shadow);
930         }
931     } else {
932         Shadow shadow;
933         if (GetShadowFromTheme(ShadowStyle::OuterDefaultMD, shadow)) {
934             renderContext->UpdateBackShadow(shadow);
935         }
936     }
937     // to enable inner menu shadow effect for desktopMenu, need to remove clipping from container
938     bool clip = !resetForDesktopMenu;
939     scroll->GetRenderContext()->SetClipToBounds(clip);
940 
941     // move padding from scroll to inner menu
942     auto scrollProp = scroll->GetLayoutProperty();
943     scrollProp->UpdatePadding(PaddingProperty());
944 }
945 
InitTheme(const RefPtr<FrameNode> & host)946 void MenuPattern::InitTheme(const RefPtr<FrameNode>& host)
947 {
948     CHECK_NULL_VOID(host);
949     auto renderContext = host->GetRenderContext();
950     CHECK_NULL_VOID(renderContext);
951     auto pipeline = host->GetContextWithCheck();
952     CHECK_NULL_VOID(pipeline);
953     auto theme = pipeline->GetTheme<SelectTheme>();
954     CHECK_NULL_VOID(theme);
955     auto expandDisplay = theme->GetExpandDisplay();
956     expandDisplay_ = expandDisplay;
957     if (Container::LessThanAPIVersion(PlatformVersion::VERSION_ELEVEN) || !renderContext->IsUniRenderEnabled()) {
958         auto bgColor = theme->GetBackgroundColor();
959         renderContext->UpdateBackgroundColor(bgColor);
960     }
961     Shadow shadow;
962     if (GetShadowFromTheme(ShadowStyle::OuterDefaultMD, shadow)) {
963         renderContext->UpdateBackShadow(shadow);
964     }
965     // make menu round rect
966     BorderRadiusProperty borderRadius;
967     if (Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE)) {
968         borderRadius.SetRadius(theme->GetMenuDefaultRadius());
969     } else {
970         borderRadius.SetRadius(theme->GetMenuBorderRadius());
971     }
972     renderContext->UpdateBorderRadius(borderRadius);
973 }
974 
InitTheme(const RefPtr<FrameNode> & host)975 void InnerMenuPattern::InitTheme(const RefPtr<FrameNode>& host)
976 {
977     CHECK_NULL_VOID(host);
978     MenuPattern::InitTheme(host);
979     // inner menu applies shadow in OnModifyDone(), where it can determine if it's a DesktopMenu or a regular menu
980 
981     auto layoutProperty = host->GetLayoutProperty();
982     if (layoutProperty->GetPaddingProperty()) {
983         // if user defined padding exists, skip applying default padding
984         return;
985     }
986     auto pipeline = host->GetContextWithCheck();
987     CHECK_NULL_VOID(pipeline);
988     auto theme = pipeline->GetTheme<SelectTheme>();
989     // apply default padding from theme on inner menu
990     PaddingProperty padding;
991     padding.SetEdges(CalcLength(theme->GetOutPadding()));
992     host->GetLayoutProperty()->UpdatePadding(padding);
993 
994     host->GetRenderContext()->SetClipToBounds(true);
995 }
996 
SetAccessibilityAction()997 void MenuPattern::SetAccessibilityAction()
998 {
999     auto host = GetHost();
1000     CHECK_NULL_VOID(host);
1001     auto accessibilityProperty = host->GetAccessibilityProperty<AccessibilityProperty>();
1002     CHECK_NULL_VOID(accessibilityProperty);
1003     accessibilityProperty->SetActionScrollForward([weakPtr = WeakClaim(this)]() {
1004         const auto& pattern = weakPtr.Upgrade();
1005         auto host = pattern->GetHost();
1006         CHECK_NULL_VOID(host);
1007         auto firstChild = DynamicCast<FrameNode>(host->GetChildAtIndex(0));
1008         CHECK_NULL_VOID(firstChild);
1009         if (firstChild && firstChild->GetTag() == V2::SCROLL_ETS_TAG) {
1010             auto scrollPattern = firstChild->GetPattern<ScrollPattern>();
1011             CHECK_NULL_VOID(scrollPattern);
1012             scrollPattern->ScrollPage(false, true);
1013         }
1014     });
1015 
1016     accessibilityProperty->SetActionScrollBackward([weakPtr = WeakClaim(this)]() {
1017         const auto& pattern = weakPtr.Upgrade();
1018         auto host = pattern->GetHost();
1019         CHECK_NULL_VOID(host);
1020         auto firstChild = DynamicCast<FrameNode>(host->GetChildAtIndex(0));
1021         CHECK_NULL_VOID(firstChild);
1022         if (firstChild && firstChild->GetTag() == V2::SCROLL_ETS_TAG) {
1023             auto scrollPattern = firstChild->GetPattern<ScrollPattern>();
1024             CHECK_NULL_VOID(scrollPattern);
1025             scrollPattern->ScrollPage(true, true);
1026         }
1027     });
1028 }
1029 
GetTransformCenter() const1030 Offset MenuPattern::GetTransformCenter() const
1031 {
1032     auto host = GetHost();
1033     CHECK_NULL_RETURN(host, Offset());
1034     auto geometryNode = host->GetGeometryNode();
1035     CHECK_NULL_RETURN(geometryNode, Offset());
1036     auto size = geometryNode->GetFrameSize();
1037     auto layoutAlgorithmWrapper = host->GetLayoutAlgorithm();
1038     CHECK_NULL_RETURN(layoutAlgorithmWrapper, Offset());
1039     auto layoutAlgorithm = AceType::DynamicCast<MenuLayoutAlgorithm>(layoutAlgorithmWrapper->GetLayoutAlgorithm());
1040     CHECK_NULL_RETURN(layoutAlgorithm, Offset());
1041     auto placement = layoutAlgorithm->GetPlacement();
1042     switch (placement) {
1043         case Placement::BOTTOM_LEFT:
1044         case Placement::RIGHT_TOP:
1045             return Offset();
1046         case Placement::BOTTOM_RIGHT:
1047         case Placement::LEFT_TOP:
1048             return Offset(size.Width(), 0.0f);
1049         case Placement::TOP_LEFT:
1050         case Placement::RIGHT_BOTTOM:
1051             return Offset(0.0f, size.Height());
1052         case Placement::TOP_RIGHT:
1053         case Placement::LEFT_BOTTOM:
1054             return Offset(size.Width(), size.Height());
1055         case Placement::BOTTOM:
1056             return Offset(size.Width() / 2, 0.0f);
1057         case Placement::LEFT:
1058             return Offset(size.Width(), size.Height() / 2);
1059         case Placement::TOP:
1060             return Offset(size.Width() / 2, size.Height());
1061         case Placement::RIGHT:
1062             return Offset(0.0f, size.Height() / 2);
1063         default:
1064             return Offset();
1065     }
1066 }
1067 
ShowPreviewMenuScaleAnimation()1068 void MenuPattern::ShowPreviewMenuScaleAnimation()
1069 {
1070     auto menuWrapper = GetMenuWrapper();
1071     CHECK_NULL_VOID(menuWrapper);
1072     auto menuWrapperPattern = menuWrapper->GetPattern<MenuWrapperPattern>();
1073     CHECK_NULL_VOID(menuWrapperPattern);
1074 
1075     auto preview = isShowHoverImage_ ? menuWrapperPattern->GetHoverImageFlexNode() : menuWrapperPattern->GetPreview();
1076     CHECK_NULL_VOID(preview);
1077     bool isHoverImageTarget = isShowHoverImage_ && preview->GetTag() == V2::FLEX_ETS_TAG;
1078     auto previewRenderContext = preview->GetRenderContext();
1079     CHECK_NULL_VOID(previewRenderContext);
1080     auto previewGeometryNode = preview->GetGeometryNode();
1081     CHECK_NULL_VOID(previewGeometryNode);
1082     auto previewPosition = previewGeometryNode->GetFrameOffset();
1083     OffsetF previewOriginPosition = GetPreviewOriginOffset();
1084     auto host = GetHost();
1085     CHECK_NULL_VOID(host);
1086     auto pipeline = host->GetContextWithCheck();
1087     CHECK_NULL_VOID(pipeline);
1088     auto menuTheme = pipeline->GetTheme<NG::MenuTheme>();
1089     CHECK_NULL_VOID(menuTheme);
1090     auto springMotionResponse = menuTheme->GetSpringMotionResponse();
1091     auto springMotionDampingFraction = menuTheme->GetSpringMotionDampingFraction();
1092     auto delay = isHoverImageTarget ? menuTheme->GetHoverImageDelayDuration() : 0;
1093 
1094     previewRenderContext->UpdatePosition(
1095         OffsetT<Dimension>(Dimension(previewOriginPosition.GetX()), Dimension(previewOriginPosition.GetY())));
1096     AnimationOption scaleOption = AnimationOption();
1097     auto motion = AceType::MakeRefPtr<ResponsiveSpringMotion>(springMotionResponse, springMotionDampingFraction);
1098     if (isHoverImageTarget) {
1099         scaleOption.SetCurve(CUSTOM_PREVIEW_ANIMATION_CURVE);
1100     } else {
1101         scaleOption.SetCurve(motion);
1102     }
1103     scaleOption.SetDelay(delay);
1104     AnimationUtils::Animate(scaleOption, [previewRenderContext, previewPosition]() {
1105         CHECK_NULL_VOID(previewRenderContext);
1106         previewRenderContext->UpdatePosition(
1107             OffsetT<Dimension>(Dimension(previewPosition.GetX()), Dimension(previewPosition.GetY())));
1108     });
1109 }
1110 
ShowPreviewMenuAnimation()1111 void MenuPattern::ShowPreviewMenuAnimation()
1112 {
1113     CHECK_NULL_VOID(isFirstShow_ && previewMode_ != MenuPreviewMode::NONE);
1114     auto host = GetHost();
1115     CHECK_NULL_VOID(host);
1116     MenuView::CalcHoverScaleInfo(host);
1117     ShowPreviewMenuScaleAnimation();
1118 
1119     MenuView::ShowPixelMapAnimation(host);
1120     auto renderContext = host->GetRenderContext();
1121     CHECK_NULL_VOID(renderContext);
1122     renderContext->UpdateTransformCenter(DimensionOffset(GetTransformCenter()));
1123     auto menuPosition = host->GetPaintRectOffset();
1124 
1125     auto pipeline = host->GetContextWithCheck();
1126     CHECK_NULL_VOID(pipeline);
1127     auto menuTheme = pipeline->GetTheme<NG::MenuTheme>();
1128     CHECK_NULL_VOID(menuTheme);
1129     auto menuAnimationScale = menuTheme->GetMenuAnimationScale();
1130     auto springMotionResponse = menuTheme->GetSpringMotionResponse();
1131     auto springMotionDampingFraction = menuTheme->GetSpringMotionDampingFraction();
1132 
1133     renderContext->UpdateTransformScale(VectorF(menuAnimationScale, menuAnimationScale));
1134 
1135     renderContext->UpdatePosition(
1136         OffsetT<Dimension>(Dimension(originOffset_.GetX()), Dimension(originOffset_.GetY())));
1137 
1138     auto delay = isShowHoverImage_ ? menuTheme->GetHoverImageDelayDuration() : 0;
1139     ShowMenuOpacityAnimation(menuTheme, renderContext, delay);
1140 
1141     AnimationOption scaleOption = AnimationOption();
1142     auto motion = AceType::MakeRefPtr<ResponsiveSpringMotion>(springMotionResponse, springMotionDampingFraction);
1143     scaleOption.SetCurve(motion);
1144     scaleOption.SetDelay(delay);
1145     AnimationUtils::Animate(scaleOption, [renderContext, menuPosition]() {
1146         if (renderContext) {
1147             renderContext->UpdateTransformScale(VectorF(1.0f, 1.0f));
1148             renderContext->UpdatePosition(
1149                 OffsetT<Dimension>(Dimension(menuPosition.GetX()), Dimension(menuPosition.GetY())));
1150         }
1151     });
1152     isFirstShow_ = false;
1153 }
1154 
ShowMenuAppearAnimation()1155 void MenuPattern::ShowMenuAppearAnimation()
1156 {
1157     auto host = GetHost();
1158     CHECK_NULL_VOID(host);
1159     if (isMenuShow_ && Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE) &&
1160         previewMode_ == MenuPreviewMode::NONE) {
1161         auto renderContext = host->GetRenderContext();
1162         CHECK_NULL_VOID(renderContext);
1163         auto offset = GetTransformCenter();
1164         renderContext->UpdateTransformCenter(DimensionOffset(offset));
1165         auto menuPosition = host->GetPaintRectOffset();
1166         if (IsSelectOverlayExtensionMenu() && !isExtensionMenuShow_) {
1167             menuPosition = GetEndOffset();
1168         }
1169         if (IsSelectOverlayExtensionMenu()) {
1170             SetEndOffset(menuPosition);
1171         }
1172 
1173         renderContext->UpdateTransformScale(VectorF(MENU_ORIGINAL_SCALE, MENU_ORIGINAL_SCALE));
1174         renderContext->UpdateOpacity(0.0f);
1175 
1176         AnimationOption option = AnimationOption();
1177         option.SetCurve(MAIN_MENU_ANIMATION_CURVE);
1178         AnimationUtils::Animate(option, [this, renderContext, menuPosition]() {
1179             CHECK_NULL_VOID(renderContext);
1180             if (IsSelectOverlayExtensionMenu()) {
1181                 renderContext->UpdatePosition(
1182                     OffsetT<Dimension>(Dimension(menuPosition.GetX()), Dimension(menuPosition.GetY())));
1183             }
1184             renderContext->UpdateOpacity(1.0f);
1185             renderContext->UpdateTransformScale(VectorF(1.0f, 1.0f));
1186         });
1187         isExtensionMenuShow_ = false;
1188     }
1189     isMenuShow_ = false;
1190 }
1191 
ShowStackExpandMenu()1192 void MenuPattern::ShowStackExpandMenu()
1193 {
1194     auto host = GetHost();
1195     CHECK_NULL_VOID(host);
1196     if (!isSubMenuShow_) {
1197         return;
1198     }
1199     auto renderContext = host->GetRenderContext();
1200     CHECK_NULL_VOID(renderContext);
1201     auto menuWarpper = GetMenuWrapper();
1202     CHECK_NULL_VOID(menuWarpper);
1203     auto menuWrapperPattern = menuWarpper->GetPattern<MenuWrapperPattern>();
1204     CHECK_NULL_VOID(menuWrapperPattern);
1205     auto outterMenu = menuWrapperPattern->GetMenu();
1206     CHECK_NULL_VOID(outterMenu);
1207 
1208     auto [originOffset, endOffset] = GetMenuOffset(outterMenu);
1209     auto outterMenuContext = outterMenu->GetRenderContext();
1210     CHECK_NULL_VOID(outterMenuContext);
1211 
1212     renderContext->UpdatePosition(
1213         OffsetT<Dimension>(Dimension(originOffset.GetX()), Dimension(originOffset.GetY())));
1214 
1215     AnimationOption opacityOption = AnimationOption();
1216     opacityOption.SetCurve(Curves::FRICTION);
1217     opacityOption.SetDuration(STACK_EXPAND_DISAPPEAR_DURATION);
1218     AnimationUtils::Animate(opacityOption, [renderContext, outterMenuContext]() {
1219         if (renderContext) {
1220             renderContext->UpdateOpacity(1.0f);
1221         }
1222         if (outterMenuContext) {
1223             outterMenuContext->UpdateOpacity(MOUNT_MENU_OPACITY);
1224         }
1225     });
1226 
1227     AnimationOption translateOption = AnimationOption();
1228     translateOption.SetCurve(STACK_MENU_CURVE);
1229     AnimationUtils::Animate(translateOption, [renderContext, menuPosition = endOffset, outterMenuContext]() {
1230         if (renderContext) {
1231             renderContext->UpdatePosition(
1232                 OffsetT<Dimension>(Dimension(menuPosition.GetX()), Dimension(menuPosition.GetY())));
1233         }
1234         if (outterMenuContext) {
1235             outterMenuContext->UpdateTransformScale(VectorF(MOUNT_MENU_FINAL_SCALE, MOUNT_MENU_FINAL_SCALE));
1236         }
1237     });
1238     ShowArrowRotateAnimation();
1239     isSubMenuShow_ = false;
1240 }
1241 
GetMenuOffset(const RefPtr<FrameNode> & outterMenu,bool isNeedRestoreNodeId) const1242 std::pair<OffsetF, OffsetF> MenuPattern::GetMenuOffset(const RefPtr<FrameNode>& outterMenu,
1243     bool isNeedRestoreNodeId) const
1244 {
1245     CHECK_NULL_RETURN(outterMenu, std::make_pair(OffsetF(), OffsetF()));
1246     auto scroll = outterMenu->GetFirstChild();
1247     CHECK_NULL_RETURN(scroll, std::make_pair(OffsetF(), OffsetF()));
1248     auto innerMenu = scroll->GetFirstChild();
1249     CHECK_NULL_RETURN(innerMenu, std::make_pair(OffsetF(), OffsetF()));
1250     auto children = innerMenu->GetChildren();
1251     MenuItemInfo menuItemInfo;
1252     for (auto child : children) {
1253         menuItemInfo = GetInnerMenuOffset(child, isNeedRestoreNodeId);
1254         if (menuItemInfo.isFindTargetId) {
1255             break;
1256         }
1257     }
1258     return {menuItemInfo.originOffset, menuItemInfo.endOffset};
1259 }
1260 
GetInnerMenuOffset(const RefPtr<UINode> & child,bool isNeedRestoreNodeId) const1261 MenuItemInfo MenuPattern::GetInnerMenuOffset(const RefPtr<UINode>& child, bool isNeedRestoreNodeId) const
1262 {
1263     MenuItemInfo menuItemInfo;
1264     CHECK_NULL_RETURN(child, menuItemInfo);
1265     if (child->GetTag() == V2::MENU_ITEM_ETS_TAG) {
1266         menuItemInfo = GetMenuItemInfo(child, isNeedRestoreNodeId);
1267         if (menuItemInfo.isFindTargetId) {
1268             return menuItemInfo;
1269         }
1270     } else {
1271         const auto& groupChildren = child->GetChildren();
1272         for (auto child : groupChildren) {
1273             menuItemInfo = GetInnerMenuOffset(child, isNeedRestoreNodeId);
1274             if (menuItemInfo.isFindTargetId) {
1275                 return menuItemInfo;
1276             }
1277         }
1278     }
1279     return menuItemInfo;
1280 }
1281 
GetMenuItemInfo(const RefPtr<UINode> & child,bool isNeedRestoreNodeId) const1282 MenuItemInfo MenuPattern::GetMenuItemInfo(const RefPtr<UINode>& child, bool isNeedRestoreNodeId) const
1283 {
1284     MenuItemInfo menuItemInfo;
1285     auto menuItem = AceType::DynamicCast<FrameNode>(child);
1286     CHECK_NULL_RETURN(menuItem, menuItemInfo);
1287     if (menuItem->GetTag() == V2::MENU_ITEM_ETS_TAG) {
1288         auto menuItemPattern = menuItem->GetPattern<MenuItemPattern>();
1289         CHECK_NULL_RETURN(menuItemPattern, menuItemInfo);
1290         if (menuItem->GetId() == menuItemPattern->GetClickMenuItemId()) {
1291             auto offset = menuItem->GetPaintRectOffset();
1292             menuItemInfo.originOffset = offset - OffsetF(PADDING.ConvertToPx(), PADDING.ConvertToPx());
1293             auto menuItemFrameSize = menuItem->GetGeometryNode()->GetFrameSize();
1294             menuItemInfo.endOffset = menuItemInfo.originOffset + OffsetF(0.0f, menuItemFrameSize.Height());
1295             menuItemInfo.isFindTargetId = true;
1296             if (isNeedRestoreNodeId) {
1297                 menuItemPattern->SetClickMenuItemId(-1);
1298             }
1299         }
1300     }
1301     return menuItemInfo;
1302 }
1303 
ShowArrowRotateAnimation() const1304 void MenuPattern::ShowArrowRotateAnimation() const
1305 {
1306     auto host = GetHost();
1307     CHECK_NULL_VOID(host);
1308     auto subImageNode = GetImageNode(host);
1309     CHECK_NULL_VOID(subImageNode);
1310     auto subImageContext = subImageNode->GetRenderContext();
1311     CHECK_NULL_VOID(subImageContext);
1312     subImageContext->UpdateTransformRotate(Vector5F(0.0f, 0.0f, 1.0f, 0.0f, 0.0f));
1313     AnimationOption option = AnimationOption();
1314     option.SetCurve(MENU_ANIMATION_CURVE);
1315     AnimationUtils::Animate(option, [subImageContext]() {
1316         if (subImageContext) {
1317             subImageContext->UpdateTransformRotate(Vector5F(0.0f, 0.0f, 1.0f, SEMI_CIRCLE_ANGEL, 0.0f));
1318         }
1319     });
1320 }
1321 
GetImageNode(const RefPtr<FrameNode> & host) const1322 RefPtr<FrameNode> MenuPattern::GetImageNode(const RefPtr<FrameNode>& host) const
1323 {
1324     auto scroll = host->GetFirstChild();
1325     CHECK_NULL_RETURN(scroll, nullptr);
1326     auto innerMenu = scroll->GetFirstChild();
1327     CHECK_NULL_RETURN(innerMenu, nullptr);
1328     auto menuItem = innerMenu->GetFirstChild();
1329     CHECK_NULL_RETURN(menuItem, nullptr);
1330     auto rightRow = menuItem->GetChildAtIndex(1);
1331     CHECK_NULL_RETURN(rightRow, nullptr);
1332     auto image = AceType::DynamicCast<FrameNode>(rightRow->GetChildren().back());
1333     return image;
1334 }
1335 
ShowStackExpandDisappearAnimation(const RefPtr<FrameNode> & menuNode,const RefPtr<FrameNode> & subMenuNode,AnimationOption & option) const1336 void MenuPattern::ShowStackExpandDisappearAnimation(const RefPtr<FrameNode>& menuNode,
1337     const RefPtr<FrameNode>& subMenuNode, AnimationOption& option) const
1338 {
1339     CHECK_NULL_VOID(menuNode);
1340     CHECK_NULL_VOID(subMenuNode);
1341 
1342     auto [originOffset, endOffset] = GetMenuOffset(menuNode, true);
1343     auto subMenuPos = subMenuNode->GetPaintRectOffset();
1344     auto menuPosition = OffsetF(subMenuPos.GetX(), originOffset.GetY());
1345 
1346     option.SetCurve(STACK_MENU_CURVE);
1347     AnimationUtils::Animate(option, [menuNode, menuPosition, subMenuNode]() {
1348         auto menuContext = menuNode->GetRenderContext();
1349         auto subMenuContext = subMenuNode->GetRenderContext();
1350         if (subMenuContext) {
1351             subMenuContext->UpdatePosition(
1352                 OffsetT<Dimension>(Dimension(menuPosition.GetX()), Dimension(menuPosition.GetY())));
1353         }
1354         if (menuContext) {
1355             menuContext->UpdateTransformScale(VectorF(1.0f, 1.0f));
1356         }
1357     });
1358 
1359     option.SetCurve(MENU_ANIMATION_CURVE);
1360     auto subImageNode = GetImageNode(subMenuNode);
1361     AnimationUtils::Animate(option, [subImageNode]() {
1362         CHECK_NULL_VOID(subImageNode);
1363         auto subImageContext = subImageNode->GetRenderContext();
1364         CHECK_NULL_VOID(subImageContext);
1365         subImageContext->UpdateTransformRotate(Vector5F(0.0f, 0.0f, 1.0f, 0.0f, 0.0f));
1366     });
1367 
1368     option.SetCurve(Curves::FRICTION);
1369     option.SetDuration(STACK_EXPAND_DISAPPEAR_DURATION);
1370     AnimationUtils::Animate(option, [menuNode, subMenuNode]() {
1371         auto menuContext = menuNode->GetRenderContext();
1372         auto subMenuContext = subMenuNode->GetRenderContext();
1373         if (subMenuContext) {
1374             subMenuContext->UpdateOpacity(0.0f);
1375         }
1376         if (menuContext) {
1377             menuContext->UpdateOpacity(1.0f);
1378         }
1379     }, option.GetOnFinishEvent());
1380 }
1381 
ShowMenuDisappearAnimation()1382 void MenuPattern::ShowMenuDisappearAnimation()
1383 {
1384     if (!Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE)) {
1385         return;
1386     }
1387     auto host = GetHost();
1388     CHECK_NULL_VOID(host);
1389     auto menuContext = host->GetRenderContext();
1390     MAIN_MENU_ANIMATION_CURVE->UpdateMinimumAmplitudeRatio(MINIMUM_AMPLITUDE_RATION);
1391     auto menuPosition = GetEndOffset();
1392     AnimationOption option = AnimationOption();
1393     option.SetCurve(MAIN_MENU_ANIMATION_CURVE);
1394     AnimationUtils::Animate(option, [menuContext, menuPosition]() {
1395         if (menuContext) {
1396             menuContext->UpdatePosition(
1397                 OffsetT<Dimension>(Dimension(menuPosition.GetX()), Dimension(menuPosition.GetY())));
1398             menuContext->UpdateTransformScale(VectorF(MENU_ORIGINAL_SCALE, MENU_ORIGINAL_SCALE));
1399             menuContext->UpdateOpacity(0.0f);
1400         }
1401     });
1402 }
1403 
OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper> & dirty,const DirtySwapConfig & config)1404 bool MenuPattern::OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper>& dirty, const DirtySwapConfig& config)
1405 {
1406     ShowPreviewMenuAnimation();
1407     ShowMenuAppearAnimation();
1408     ShowStackExpandMenu();
1409     auto host = GetHost();
1410     CHECK_NULL_RETURN(host, false);
1411     auto menuPosition = host->GetPaintRectOffset();
1412     SetEndOffset(menuPosition);
1413     if (config.skipMeasure || dirty->SkipMeasureContent()) {
1414         return false;
1415     }
1416 
1417     auto pipeline = host->GetContextWithCheck();
1418     CHECK_NULL_RETURN(pipeline, false);
1419     auto theme = pipeline->GetTheme<SelectTheme>();
1420     CHECK_NULL_RETURN(theme, false);
1421     auto renderContext = dirty->GetHostNode()->GetRenderContext();
1422     CHECK_NULL_RETURN(renderContext, false);
1423 
1424     auto menuProp = DynamicCast<MenuLayoutProperty>(dirty->GetLayoutProperty());
1425     CHECK_NULL_RETURN(menuProp, false);
1426     BorderRadiusProperty radius;
1427     auto defaultRadius = Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE) ?
1428         theme->GetMenuDefaultRadius() : theme->GetMenuBorderRadius();
1429     radius.SetRadius(defaultRadius);
1430     if (menuProp->GetBorderRadius().has_value()) {
1431         auto borderRadius = menuProp->GetBorderRadiusValue();
1432         auto geometryNode = dirty->GetGeometryNode();
1433         CHECK_NULL_RETURN(geometryNode, false);
1434         auto idealSize = geometryNode->GetMarginFrameSize();
1435         radius = CalcIdealBorderRadius(borderRadius, idealSize);
1436         UpdateBorderRadius(dirty->GetHostNode(), radius);
1437     }
1438     auto menuWrapper = GetMenuWrapper();
1439     DragAnimationHelper::ShowGatherAnimationWithMenu(menuWrapper);
1440     return true;
1441 }
1442 
CalcIdealBorderRadius(const BorderRadiusProperty & borderRadius,const SizeF & menuSize)1443 BorderRadiusProperty MenuPattern::CalcIdealBorderRadius(const BorderRadiusProperty& borderRadius, const SizeF& menuSize)
1444 {
1445     Dimension defaultDimension(0);
1446     BorderRadiusProperty radius = { defaultDimension, defaultDimension, defaultDimension, defaultDimension };
1447     auto host = GetHost();
1448     CHECK_NULL_RETURN(host, radius);
1449     auto pipeline = host->GetContextWithCheck();
1450     CHECK_NULL_RETURN(pipeline, radius);
1451     auto theme = pipeline->GetTheme<SelectTheme>();
1452     CHECK_NULL_RETURN(theme, radius);
1453     auto defaultRadius = Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE) ?
1454         theme->GetMenuDefaultRadius() : theme->GetMenuBorderRadius();
1455     radius.SetRadius(defaultRadius);
1456     auto radiusTopLeft = borderRadius.radiusTopLeft.value_or(Dimension()).ConvertToPx();
1457     auto radiusTopRight = borderRadius.radiusTopRight.value_or(Dimension()).ConvertToPx();
1458     auto radiusBottomLeft = borderRadius.radiusBottomLeft.value_or(Dimension()).ConvertToPx();
1459     auto radiusBottomRight = borderRadius.radiusBottomRight.value_or(Dimension()).ConvertToPx();
1460     auto maxRadiusW = std::max(radiusTopLeft + radiusTopRight, radiusBottomLeft + radiusBottomRight);
1461     auto maxRadiusH = std::max(radiusTopLeft + radiusBottomLeft, radiusTopRight + radiusBottomRight);
1462     if (Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE)) {
1463         if (LessOrEqual(maxRadiusW, menuSize.Width()) && LessOrEqual(maxRadiusH, menuSize.Height())) {
1464             radius = borderRadius;
1465         }
1466     } else {
1467         if (LessNotEqual(maxRadiusW, menuSize.Width())) {
1468             radius = borderRadius;
1469         }
1470     }
1471 
1472     return radius;
1473 }
1474 
UpdateBorderRadius(const RefPtr<FrameNode> & menuNode,const BorderRadiusProperty & borderRadius)1475 void MenuPattern::UpdateBorderRadius(const RefPtr<FrameNode>& menuNode, const BorderRadiusProperty& borderRadius)
1476 {
1477     CHECK_NULL_VOID(menuNode);
1478     auto menuRenderContext = menuNode->GetRenderContext();
1479     CHECK_NULL_VOID(menuRenderContext);
1480     menuRenderContext->UpdateBorderRadius(borderRadius);
1481     if (Container::GreatOrEqualAPIVersion(PlatformVersion::VERSION_ELEVEN)) {
1482         menuRenderContext->UpdateOuterBorderRadius(borderRadius);
1483     }
1484     auto node = menuNode->GetChildren().front();
1485     CHECK_NULL_VOID(node);
1486     auto scrollNode = AceType::DynamicCast<FrameNode>(node);
1487     CHECK_NULL_VOID(scrollNode);
1488     auto scrollRenderContext = scrollNode->GetRenderContext();
1489     CHECK_NULL_VOID(scrollRenderContext);
1490     scrollRenderContext->UpdateBorderRadius(borderRadius);
1491 }
1492 
UpdateBorderRadius(const RefPtr<FrameNode> & menuNode,const BorderRadiusProperty & borderRadius)1493 void InnerMenuPattern::UpdateBorderRadius(const RefPtr<FrameNode>& menuNode, const BorderRadiusProperty& borderRadius)
1494 {
1495     CHECK_NULL_VOID(menuNode);
1496     auto menuRenderContext = menuNode->GetRenderContext();
1497     CHECK_NULL_VOID(menuRenderContext);
1498     menuRenderContext->UpdateBorderRadius(borderRadius);
1499 }
1500 
GetMainMenuPattern() const1501 RefPtr<MenuPattern> MenuPattern::GetMainMenuPattern() const
1502 {
1503     auto wrapperFrameNode = GetMenuWrapper();
1504     CHECK_NULL_RETURN(wrapperFrameNode, nullptr);
1505     auto mainMenuUINode = wrapperFrameNode->GetChildAtIndex(0);
1506     CHECK_NULL_RETURN(mainMenuUINode, nullptr);
1507     auto mainMenuFrameNode = AceType::DynamicCast<FrameNode>(mainMenuUINode);
1508     return mainMenuFrameNode->GetPattern<MenuPattern>();
1509 }
1510 
RecordItemsAndGroups()1511 void InnerMenuPattern::RecordItemsAndGroups()
1512 {
1513     itemsAndGroups_.clear();
1514     auto host = GetHost();
1515     std::stack<RefPtr<UINode>> nodeStack;
1516     nodeStack.emplace(host);
1517     bool isMenu = true;
1518 
1519     while (!nodeStack.empty()) {
1520         auto currentNode = nodeStack.top();
1521         nodeStack.pop();
1522         // push items and item groups, skip menu node
1523         if (!isMenu && AceType::InstanceOf<FrameNode>(currentNode)) {
1524             itemsAndGroups_.emplace_back(WeakClaim(RawPtr(currentNode)));
1525             continue;
1526         }
1527         isMenu = false;
1528         // skip other type UiNode, such as ForEachNode
1529         for (int32_t index = static_cast<int32_t>(currentNode->GetChildren().size()) - 1; index >= 0; index--) {
1530             nodeStack.push(currentNode->GetChildAtIndex(index));
1531         }
1532     }
1533 }
1534 
FindSiblingMenuCount()1535 uint32_t InnerMenuPattern::FindSiblingMenuCount()
1536 {
1537     auto host = GetHost();
1538     CHECK_NULL_RETURN(host, 0);
1539     auto parent = host->GetParent();
1540     CHECK_NULL_RETURN(parent, 0);
1541     auto siblings = parent->GetChildren();
1542     uint32_t count = 0;
1543     for (auto&& sibling : siblings) {
1544         if (sibling->GetTag() == V2::MENU_ETS_TAG && sibling != host) {
1545             ++count;
1546         }
1547     }
1548     return count;
1549 }
1550 
ApplyDesktopMenuTheme()1551 void InnerMenuPattern::ApplyDesktopMenuTheme()
1552 {
1553     auto host = GetHost();
1554     CHECK_NULL_VOID(host);
1555     Shadow shadow;
1556     if (GetShadowFromTheme(ShadowStyle::OuterDefaultSM, shadow)) {
1557         host->GetRenderContext()->UpdateBackShadow(shadow);
1558     }
1559 }
1560 
ApplyMultiMenuTheme()1561 void InnerMenuPattern::ApplyMultiMenuTheme()
1562 {
1563     auto host = GetHost();
1564     CHECK_NULL_VOID(host);
1565     Shadow shadow;
1566     if (GetShadowFromTheme(ShadowStyle::None, shadow)) {
1567         host->GetRenderContext()->UpdateBackShadow(shadow);
1568     }
1569 }
1570 
OnColorConfigurationUpdate()1571 void MenuPattern::OnColorConfigurationUpdate()
1572 {
1573     auto host = GetHost();
1574     CHECK_NULL_VOID(host);
1575     auto pipeline = host->GetContextWithCheck();
1576     CHECK_NULL_VOID(pipeline);
1577 
1578     auto menuTheme = pipeline->GetTheme<SelectTheme>();
1579     CHECK_NULL_VOID(menuTheme);
1580 
1581     auto menuPattern = host->GetPattern<MenuPattern>();
1582     CHECK_NULL_VOID(menuPattern);
1583 
1584     auto renderContext = host->GetRenderContext();
1585     if (Container::LessThanAPIVersion(PlatformVersion::VERSION_ELEVEN) || !renderContext->IsUniRenderEnabled()) {
1586         renderContext->UpdateBackgroundColor(menuTheme->GetBackgroundColor());
1587     } else {
1588         renderContext->UpdateBackBlurStyle(renderContext->GetBackBlurStyle());
1589     }
1590 
1591     auto optionNode = menuPattern->GetOptions();
1592     for (const auto& child : optionNode) {
1593         auto optionsPattern = child->GetPattern<OptionPattern>();
1594         optionsPattern->SetFontColor(menuTheme->GetFontColor());
1595 
1596         child->MarkModifyDone();
1597         child->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
1598     }
1599     host->SetNeedCallChildrenUpdate(false);
1600 }
1601 
InitPanEvent(const RefPtr<GestureEventHub> & gestureHub)1602 void MenuPattern::InitPanEvent(const RefPtr<GestureEventHub>& gestureHub)
1603 {
1604     CHECK_NULL_VOID(gestureHub);
1605     auto actionEndTask = [weak = WeakClaim(this)](const GestureEvent& info) {
1606         auto pattern = weak.Upgrade();
1607         CHECK_NULL_VOID(pattern);
1608         if (pattern->IsMenuScrollable()) {
1609             return;
1610         }
1611         auto offsetX = static_cast<float>(info.GetOffsetX());
1612         auto offsetY = static_cast<float>(info.GetOffsetY());
1613         auto offsetPerSecondX = info.GetVelocity().GetOffsetPerSecond().GetX();
1614         auto offsetPerSecondY = info.GetVelocity().GetOffsetPerSecond().GetY();
1615         auto velocity =
1616             static_cast<float>(std::sqrt(offsetPerSecondX * offsetPerSecondX + offsetPerSecondY * offsetPerSecondY));
1617         pattern->HandleDragEnd(offsetX, offsetY, velocity);
1618     };
1619     auto actionScrollEndTask = [weak = WeakClaim(this)](const GestureEvent& info) {
1620         auto pattern = weak.Upgrade();
1621         CHECK_NULL_VOID(pattern);
1622         if (pattern->IsMenuScrollable()) {
1623             return;
1624         }
1625         auto offsetX = static_cast<float>(info.GetOffsetX());
1626         auto offsetY = static_cast<float>(info.GetOffsetY());
1627         auto offsetPerSecondX = info.GetVelocity().GetOffsetPerSecond().GetX();
1628         auto offsetPerSecondY = info.GetVelocity().GetOffsetPerSecond().GetY();
1629         auto velocity =
1630             static_cast<float>(std::sqrt(offsetPerSecondX * offsetPerSecondX + offsetPerSecondY * offsetPerSecondY));
1631         pattern->HandleScrollDragEnd(offsetX, offsetY, velocity);
1632     };
1633     PanDirection panDirection;
1634     panDirection.type = PanDirection::ALL;
1635     auto panEvent = MakeRefPtr<PanEvent>(nullptr, nullptr, std::move(actionEndTask), nullptr);
1636     gestureHub->AddPanEvent(panEvent, panDirection, 1, DEFAULT_PAN_DISTANCE);
1637     gestureHub->AddPreviewMenuHandleDragEnd(std::move(actionScrollEndTask));
1638 }
1639 
HandleDragEnd(float offsetX,float offsetY,float velocity)1640 void MenuPattern::HandleDragEnd(float offsetX, float offsetY, float velocity)
1641 {
1642     if ((LessOrEqual(std::abs(offsetY), std::abs(offsetX)) || LessOrEqual(offsetY, 0.0f)) &&
1643         LessOrEqual(velocity, PAN_MAX_VELOCITY)) {
1644         return;
1645     }
1646     auto menuWrapper = GetMenuWrapper();
1647     CHECK_NULL_VOID(menuWrapper);
1648     auto wrapperPattern = menuWrapper->GetPattern<MenuWrapperPattern>();
1649     CHECK_NULL_VOID(wrapperPattern);
1650     wrapperPattern->HideMenu();
1651 }
1652 
HandleScrollDragEnd(float offsetX,float offsetY,float velocity)1653 void MenuPattern::HandleScrollDragEnd(float offsetX, float offsetY, float velocity)
1654 {
1655     if ((LessOrEqual(std::abs(offsetY), std::abs(offsetX)) || !NearZero(offsetY)) &&
1656         LessOrEqual(velocity, PAN_MAX_VELOCITY)) {
1657         return;
1658     }
1659     auto menuWrapper = GetMenuWrapper();
1660     CHECK_NULL_VOID(menuWrapper);
1661     auto wrapperPattern = menuWrapper->GetPattern<MenuWrapperPattern>();
1662     CHECK_NULL_VOID(wrapperPattern);
1663     wrapperPattern->HideMenu();
1664 }
1665 
DumpInfo()1666 void MenuPattern::DumpInfo()
1667 {
1668     DumpLog::GetInstance().AddDesc(
1669         std::string("MenuType: ").append(std::to_string(static_cast<int32_t>(GetMenuType()))));
1670 }
1671 
GetSelectMenuWidth()1672 float MenuPattern::GetSelectMenuWidth()
1673 {
1674     RefPtr<GridColumnInfo> columnInfo = GridSystemManager::GetInstance().GetInfoByType(GridColumnType::MENU);
1675     CHECK_NULL_RETURN(columnInfo, MIN_SELECT_MENU_WIDTH.ConvertToPx());
1676     auto parent = columnInfo->GetParent();
1677     CHECK_NULL_RETURN(parent, MIN_SELECT_MENU_WIDTH.ConvertToPx());
1678     parent->BuildColumnWidth();
1679     auto defaultWidth = static_cast<float>(columnInfo->GetWidth(COLUMN_NUM));
1680     float finalWidth = MIN_SELECT_MENU_WIDTH.ConvertToPx();
1681 
1682     if (IsWidthModifiedBySelect()) {
1683         auto menuLayoutProperty = GetLayoutProperty<MenuLayoutProperty>();
1684         auto selectmodifiedwidth = menuLayoutProperty->GetSelectMenuModifiedWidth();
1685         finalWidth = selectmodifiedwidth.value();
1686     } else {
1687         finalWidth = defaultWidth;
1688     }
1689 
1690     if (finalWidth < MIN_SELECT_MENU_WIDTH.ConvertToPx()) {
1691         finalWidth = defaultWidth;
1692     }
1693 
1694     return finalWidth;
1695 }
1696 
OnItemPressed(const RefPtr<UINode> & parent,int32_t index,bool press,bool hover)1697 void MenuPattern::OnItemPressed(const RefPtr<UINode>& parent, int32_t index, bool press, bool hover)
1698 {
1699     CHECK_NULL_VOID(parent);
1700     if (parent->GetTag() == V2::MENU_ITEM_GROUP_ETS_TAG) {
1701         auto pattern = DynamicCast<FrameNode>(parent)->GetPattern<MenuItemGroupPattern>();
1702         CHECK_NULL_VOID(pattern);
1703         pattern->OnIntItemPressed(index, press);
1704     }
1705     RefPtr<UINode> nextNode = nullptr;
1706     if (parent->GetTag() == V2::JS_SYNTAX_ITEM_ETS_TAG) {
1707         nextNode = GetForEachMenuItem(parent, true);
1708     } else {
1709         const size_t size = parent->GetChildren().size();
1710         if (size == 0 || index >= static_cast<int32_t>(size - 1)) {
1711             return;
1712         }
1713         nextNode = parent->GetChildAtIndex(index + 1);
1714     }
1715     CHECK_NULL_VOID(nextNode);
1716     if (nextNode->GetTag() == V2::JS_FOR_EACH_ETS_TAG) {
1717         nextNode = GetForEachMenuItem(nextNode, true);
1718     }
1719     CHECK_NULL_VOID(nextNode);
1720     if (!InstanceOf<FrameNode>(nextNode)) {
1721         LOGW("next menuNode is not a frameNode! type = %{public}s", nextNode->GetTag().c_str());
1722         return;
1723     }
1724     if (nextNode->GetTag() == V2::MENU_ITEM_GROUP_ETS_TAG) {
1725         auto pattern = DynamicCast<FrameNode>(nextNode)->GetPattern<MenuItemGroupPattern>();
1726         CHECK_NULL_VOID(pattern);
1727         pattern->OnExtItemPressed(press, true);
1728     } else {
1729         auto props = DynamicCast<FrameNode>(nextNode)->GetPaintProperty<MenuItemPaintProperty>();
1730         CHECK_NULL_VOID(props);
1731         // need save needDivider property due to some items shoud not have divide in not pressed state
1732         hover ? props->UpdateHover(press) : props->UpdatePress(press);
1733         nextNode->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
1734     }
1735     if (index > 0) {
1736         auto prevNode = parent->GetChildAtIndex(index - 1);
1737         CHECK_NULL_VOID(prevNode);
1738         if (!InstanceOf<FrameNode>(prevNode)) {
1739             LOGW("prev menuNode is not a frameNode! type = %{public}s", prevNode->GetTag().c_str());
1740             return;
1741         }
1742         if (prevNode->GetTag() == V2::MENU_ITEM_GROUP_ETS_TAG) {
1743             auto pattern = DynamicCast<FrameNode>(prevNode)->GetPattern<MenuItemGroupPattern>();
1744             CHECK_NULL_VOID(pattern);
1745             pattern->OnExtItemPressed(press, false);
1746         }
1747     }
1748 }
1749 
GetForEachMenuItem(const RefPtr<UINode> & parent,bool next)1750 RefPtr<UINode> MenuPattern::GetForEachMenuItem(const RefPtr<UINode>& parent, bool next)
1751 {
1752     CHECK_NULL_RETURN(parent, nullptr);
1753     if (parent->GetTag() == V2::JS_SYNTAX_ITEM_ETS_TAG) {
1754         auto forEachNode = AceType::DynamicCast<UINode>(parent->GetParent());
1755         CHECK_NULL_RETURN(forEachNode, nullptr);
1756         auto syntIndex = forEachNode->GetChildIndex(parent);
1757         const size_t size = forEachNode->GetChildren().size();
1758         if (next) {
1759             if (size > 0 && syntIndex < static_cast<int32_t>(size - 1)) { // next is inside forEach
1760                 auto nextSyntax = forEachNode->GetChildAtIndex(syntIndex + 1);
1761                 CHECK_NULL_RETURN(nextSyntax, nullptr);
1762                 return nextSyntax->GetFirstChild();
1763             } else { // next is after forEach
1764                 return GetOutsideForEachMenuItem(forEachNode, true);
1765             }
1766         } else {
1767             if (syntIndex > 0) { // prev is inside forEach
1768                 auto prevSyntax = forEachNode->GetChildAtIndex(syntIndex - 1);
1769                 CHECK_NULL_RETURN(prevSyntax, nullptr);
1770                 return prevSyntax->GetFirstChild();
1771             } else { // prev is before forEach
1772                 return GetOutsideForEachMenuItem(forEachNode, false);
1773             }
1774         }
1775     }
1776     if (parent->GetTag() == V2::JS_FOR_EACH_ETS_TAG) {
1777         auto nextSyntax = next? parent->GetFirstChild(): parent->GetLastChild();
1778         CHECK_NULL_RETURN(nextSyntax, nullptr);
1779         return nextSyntax->GetFirstChild();
1780     }
1781     return nullptr;
1782 }
1783 
GetOutsideForEachMenuItem(const RefPtr<UINode> & forEachNode,bool next)1784 RefPtr<UINode> MenuPattern::GetOutsideForEachMenuItem(const RefPtr<UINode>& forEachNode, bool next)
1785 {
1786     auto parentForEachNode = AceType::DynamicCast<UINode>(forEachNode->GetParent());
1787     CHECK_NULL_RETURN(parentForEachNode, nullptr);
1788     auto forEachIndex = parentForEachNode->GetChildIndex(forEachNode);
1789     int32_t shift = next ? 1 : -1;
1790     const size_t size = parentForEachNode->GetChildren().size();
1791     if (size > 0 && (forEachIndex + shift) >= 0 && (forEachIndex + shift) <= static_cast<int32_t>(size - 1)) {
1792         return parentForEachNode->GetChildAtIndex(forEachIndex + shift);
1793     } else {
1794         return nullptr;
1795     }
1796 }
1797 
IsMenuScrollable() const1798 bool MenuPattern::IsMenuScrollable() const
1799 {
1800     auto host = GetHost();
1801     CHECK_NULL_RETURN(host, false);
1802     auto firstChild = DynamicCast<FrameNode>(host->GetChildAtIndex(0));
1803     CHECK_NULL_RETURN(firstChild, false);
1804     if (firstChild->GetTag() == V2::SCROLL_ETS_TAG) {
1805         auto scrollPattern = firstChild->GetPattern<ScrollPattern>();
1806         CHECK_NULL_RETURN(scrollPattern, false);
1807         return scrollPattern->IsScrollable() && Positive(scrollPattern->GetScrollableDistance());
1808     }
1809     return false;
1810 }
1811 
1812 } // namespace OHOS::Ace::NG
1813