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