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