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/log/dump_log.h"
21 #include "base/memory/ace_type.h"
22 #include "base/memory/referenced.h"
23 #include "base/utils/utils.h"
24 #include "core/components/common/properties/shadow_config.h"
25 #include "core/components/select/select_theme.h"
26 #include "core/components_ng/base/ui_node.h"
27 #include "core/components_ng/pattern/menu/menu_item/menu_item_layout_property.h"
28 #include "core/components_ng/pattern/menu/menu_item/menu_item_pattern.h"
29 #include "core/components_ng/pattern/menu/multi_menu_layout_algorithm.h"
30 #include "core/components_ng/pattern/menu/sub_menu_layout_algorithm.h"
31 #include "core/components_ng/pattern/menu/wrapper/menu_wrapper_pattern.h"
32 #include "core/components_ng/pattern/option/option_pattern.h"
33 #include "core/components_ng/pattern/option/option_view.h"
34 #include "core/components_ng/pattern/scroll/scroll_pattern.h"
35 #include "core/components_ng/pattern/text/text_layout_property.h"
36 #include "core/components_v2/inspector/inspector_constants.h"
37 #include "core/event/touch_event.h"
38 #include "core/pipeline/pipeline_base.h"
39 #include "core/pipeline_ng/pipeline_context.h"
40
41 namespace OHOS::Ace::NG {
42 namespace {
UpdateFontStyle(RefPtr<MenuLayoutProperty> & menuProperty,RefPtr<MenuItemLayoutProperty> & itemProperty,RefPtr<MenuItemPattern> & itemPattern,bool & contentChanged,bool & labelChanged)43 void UpdateFontStyle(RefPtr<MenuLayoutProperty>& menuProperty, RefPtr<MenuItemLayoutProperty>& itemProperty,
44 RefPtr<MenuItemPattern>& itemPattern, bool& contentChanged, bool& labelChanged)
45 {
46 auto contentNode = itemPattern->GetContentNode();
47 CHECK_NULL_VOID(contentNode);
48 auto textLayoutProperty = contentNode->GetLayoutProperty<TextLayoutProperty>();
49 CHECK_NULL_VOID(textLayoutProperty);
50 auto label = itemPattern->GetLabelNode();
51 RefPtr<TextLayoutProperty> labelProperty = label ? label->GetLayoutProperty<TextLayoutProperty>() : nullptr;
52 if (menuProperty->GetItalicFontStyle().has_value()) {
53 if (!itemProperty->GetItalicFontStyle().has_value()) {
54 textLayoutProperty->UpdateItalicFontStyle(menuProperty->GetItalicFontStyle().value());
55 contentChanged = true;
56 }
57 if (labelProperty && !itemProperty->GetLabelItalicFontStyle().has_value()) {
58 labelProperty->UpdateItalicFontStyle(menuProperty->GetItalicFontStyle().value());
59 labelChanged = true;
60 }
61 }
62 if (menuProperty->GetFontFamily().has_value()) {
63 if (!itemProperty->GetFontFamily().has_value()) {
64 textLayoutProperty->UpdateFontFamily(menuProperty->GetFontFamily().value());
65 contentChanged = true;
66 }
67 if (labelProperty && !itemProperty->GetLabelFontFamily().has_value()) {
68 labelProperty->UpdateFontFamily(menuProperty->GetFontFamily().value());
69 labelChanged = true;
70 }
71 }
72 }
73
UpdateMenuItemTextNode(RefPtr<MenuLayoutProperty> & menuProperty,RefPtr<MenuItemLayoutProperty> & itemProperty,RefPtr<MenuItemPattern> & itemPattern)74 void UpdateMenuItemTextNode(RefPtr<MenuLayoutProperty>& menuProperty, RefPtr<MenuItemLayoutProperty>& itemProperty,
75 RefPtr<MenuItemPattern>& itemPattern)
76 {
77 auto contentNode = itemPattern->GetContentNode();
78 CHECK_NULL_VOID(contentNode);
79 auto textLayoutProperty = contentNode->GetLayoutProperty<TextLayoutProperty>();
80 CHECK_NULL_VOID(textLayoutProperty);
81 auto label = itemPattern->GetLabelNode();
82 RefPtr<TextLayoutProperty> labelProperty = label ? label->GetLayoutProperty<TextLayoutProperty>() : nullptr;
83 bool contentChanged = false;
84 bool labelChanged = false;
85 if (menuProperty->GetFontSize().has_value()) {
86 if (!itemProperty->GetFontSize().has_value()) {
87 textLayoutProperty->UpdateFontSize(menuProperty->GetFontSize().value());
88 contentChanged = true;
89 }
90 if (labelProperty && !itemProperty->GetLabelFontSize().has_value()) {
91 labelProperty->UpdateFontSize(menuProperty->GetFontSize().value());
92 labelChanged = true;
93 }
94 }
95 if (menuProperty->GetFontWeight().has_value()) {
96 if (!itemProperty->GetFontWeight().has_value()) {
97 textLayoutProperty->UpdateFontWeight(menuProperty->GetFontWeight().value());
98 contentChanged = true;
99 }
100 if (labelProperty && !itemProperty->GetLabelFontWeight().has_value()) {
101 labelProperty->UpdateFontWeight(menuProperty->GetFontWeight().value());
102 labelChanged = true;
103 }
104 }
105 if (menuProperty->GetFontColor().has_value()) {
106 if (!itemProperty->GetFontColor().has_value()) {
107 textLayoutProperty->UpdateTextColor(menuProperty->GetFontColor().value());
108 contentChanged = true;
109 }
110 if (labelProperty && !itemProperty->GetLabelFontColor().has_value()) {
111 labelProperty->UpdateTextColor(menuProperty->GetFontColor().value());
112 labelChanged = true;
113 }
114 }
115 UpdateFontStyle(menuProperty, itemProperty, itemPattern, contentChanged, labelChanged);
116 if (contentChanged) {
117 contentNode->MarkModifyDone();
118 contentNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
119 }
120 if (labelChanged) {
121 label->MarkModifyDone();
122 label->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
123 }
124 }
125 } // namespace
126
OnAttachToFrameNode()127 void MenuPattern::OnAttachToFrameNode()
128 {
129 RegisterOnTouch();
130 auto host = GetHost();
131 CHECK_NULL_VOID(host);
132 auto focusHub = host->GetOrCreateFocusHub();
133 CHECK_NULL_VOID(focusHub);
134 RegisterOnKeyEvent(focusHub);
135 DisableTabInMenu();
136 InitTheme(host);
137 }
138
OnModifyDone()139 void MenuPattern::OnModifyDone()
140 {
141 Pattern::OnModifyDone();
142 auto host = GetHost();
143 CHECK_NULL_VOID(host);
144 UpdateMenuItemChildren(host);
145
146 auto innerMenuCount = GetInnerMenuCount();
147 if (innerMenuCount == 1) {
148 ResetTheme(host, false);
149 } else if (innerMenuCount > 1) {
150 // multiple inner menus, reset outer container's shadow for desktop UX
151 ResetTheme(host, true);
152 }
153 auto menuFirstNode = GetFirstInnerMenu();
154 if (menuFirstNode) {
155 CopyMenuAttr(menuFirstNode);
156 }
157 SetAccessibilityAction();
158 }
159
BeforeCreateLayoutWrapper()160 void InnerMenuPattern::BeforeCreateLayoutWrapper()
161 {
162 RecordItemsAndGroups();
163
164 // determine menu type based on sibling menu count
165 auto count = FindSiblingMenuCount();
166 if (count > 0) {
167 SetType(MenuType::DESKTOP_MENU);
168 ApplyDesktopMenuTheme();
169 } else {
170 SetType(MenuType::MULTI_MENU);
171 ApplyMultiMenuTheme();
172 }
173 }
174
OnModifyDone()175 void InnerMenuPattern::OnModifyDone()
176 {
177 Pattern::OnModifyDone();
178 auto host = GetHost();
179 CHECK_NULL_VOID(host);
180 UpdateMenuItemChildren(host);
181 SetAccessibilityAction();
182 }
183
184 // close menu on touch up
RegisterOnTouch()185 void MenuPattern::RegisterOnTouch()
186 {
187 CHECK_NULL_VOID_NOLOG(!onTouch_);
188 auto host = GetHost();
189 CHECK_NULL_VOID(host);
190 auto gesture = host->GetOrCreateGestureEventHub();
191 CHECK_NULL_VOID(gesture);
192 auto touchTask = [weak = WeakClaim(this)](const TouchEventInfo& info) {
193 auto pattern = weak.Upgrade();
194 CHECK_NULL_VOID_NOLOG(pattern);
195 pattern->OnTouchEvent(info);
196 };
197 onTouch_ = MakeRefPtr<TouchEventImpl>(std::move(touchTask));
198 gesture->AddTouchEvent(onTouch_);
199 }
200
OnTouchEvent(const TouchEventInfo & info)201 void MenuPattern::OnTouchEvent(const TouchEventInfo& info)
202 {
203 if (GetInnerMenuCount() > 0 || IsMultiMenu() || IsSelectOverlayCustomMenu()) {
204 // not click hide menu for multi menu or select overlay custom menu
205 return;
206 }
207 if (!options_.empty()) {
208 // not click hide menu for select and bindMenu default option
209 return;
210 }
211 auto touchType = info.GetTouches().front().GetTouchType();
212 if (touchType == TouchType::DOWN) {
213 lastTouchOffset_ = info.GetTouches().front().GetLocalLocation();
214 } else if (touchType == TouchType::UP) {
215 auto touchUpOffset = info.GetTouches().front().GetLocalLocation();
216 if (lastTouchOffset_.has_value() &&
217 (touchUpOffset - lastTouchOffset_.value()).GetDistance() <= DEFAULT_CLICK_DISTANCE) {
218 HideMenu(true);
219 }
220 lastTouchOffset_.reset();
221 }
222 }
223
RegisterOnKeyEvent(const RefPtr<FocusHub> & focusHub)224 void MenuPattern::RegisterOnKeyEvent(const RefPtr<FocusHub>& focusHub)
225 {
226 auto onKeyEvent = [wp = WeakClaim(this)](const KeyEvent& event) -> bool {
227 auto pattern = wp.Upgrade();
228 CHECK_NULL_RETURN_NOLOG(pattern, false);
229 return pattern->OnKeyEvent(event);
230 };
231 focusHub->SetOnKeyEventInternal(std::move(onKeyEvent));
232 }
233
OnKeyEvent(const KeyEvent & event) const234 bool MenuPattern::OnKeyEvent(const KeyEvent& event) const
235 {
236 if (event.action != KeyAction::DOWN || IsMultiMenu()) {
237 return false;
238 }
239 if ((event.code == KeyCode::KEY_DPAD_LEFT || event.code == KeyCode::KEY_ESCAPE) &&
240 (IsSubMenu() || IsSelectOverlaySubMenu())) {
241 auto menuWrapper = GetMenuWrapper();
242 CHECK_NULL_RETURN(menuWrapper, true);
243 auto wrapperPattern = menuWrapper->GetPattern<MenuWrapperPattern>();
244 CHECK_NULL_RETURN(wrapperPattern, true);
245 wrapperPattern->HideSubMenu();
246 return true;
247 }
248 return false;
249 }
250
RemoveParentHoverStyle()251 void MenuPattern::RemoveParentHoverStyle()
252 {
253 if (!IsSubMenu()) {
254 return;
255 }
256 auto menuItemParent = GetParentMenuItem();
257 CHECK_NULL_VOID(menuItemParent);
258 auto menuItemPattern = menuItemParent->GetPattern<MenuItemPattern>();
259 CHECK_NULL_VOID(menuItemPattern);
260 menuItemPattern->SetIsSubMenuShowed(false);
261
262 auto pipeline = PipelineBase::GetCurrentContext();
263 CHECK_NULL_VOID(pipeline);
264 auto theme = pipeline->GetTheme<SelectTheme>();
265 CHECK_NULL_VOID(theme);
266 menuItemPattern->SetBgBlendColor(Color::TRANSPARENT);
267 menuItemPattern->PlayBgColorAnimation();
268 }
269
UpdateMenuItemChildren(RefPtr<FrameNode> & host)270 void MenuPattern::UpdateMenuItemChildren(RefPtr<FrameNode>& host)
271 {
272 auto layoutProperty = GetLayoutProperty<MenuLayoutProperty>();
273 CHECK_NULL_VOID(layoutProperty);
274 const auto& children = host->GetChildren();
275 for (auto child : children) {
276 if (child->GetTag() == V2::MENU_ITEM_ETS_TAG) {
277 auto itemNode = AceType::DynamicCast<FrameNode>(child);
278 CHECK_NULL_VOID(itemNode);
279 auto itemProperty = itemNode->GetLayoutProperty<MenuItemLayoutProperty>();
280 CHECK_NULL_VOID(itemProperty);
281 auto itemPattern = itemNode->GetPattern<MenuItemPattern>();
282 CHECK_NULL_VOID(itemPattern);
283 UpdateMenuItemTextNode(layoutProperty, itemProperty, itemPattern);
284 } else if (child->GetTag() == V2::MENU_ITEM_GROUP_ETS_TAG) {
285 auto itemGroupNode = AceType::DynamicCast<FrameNode>(child);
286 CHECK_NULL_VOID(itemGroupNode);
287 UpdateMenuItemChildren(itemGroupNode);
288 } else {
289 // do nothing
290 }
291 }
292 }
293
UpdateSelectParam(const std::vector<SelectParam> & params)294 void MenuPattern::UpdateSelectParam(const std::vector<SelectParam>& params)
295 {
296 if (!isSelectMenu_) {
297 return;
298 }
299 auto host = GetHost();
300 CHECK_NULL_VOID(host);
301 const auto& children = GetOptions();
302 auto childCount = children.size();
303 auto paramCount = params.size();
304 size_t updateCount = std::min(paramCount, childCount);
305 auto childIt = children.begin();
306 for (size_t i = 0; i < updateCount; i++, childIt++) {
307 const auto& childNode = AceType::DynamicCast<FrameNode>(*childIt);
308 CHECK_NULL_VOID(childNode);
309 if (i == 0) {
310 auto props = childNode->GetPaintProperty<OptionPaintProperty>();
311 CHECK_NULL_VOID(props);
312 props->UpdateNeedDivider(false);
313 }
314 auto optionPattern = childNode->GetPattern<OptionPattern>();
315 CHECK_NULL_VOID(optionPattern);
316 optionPattern->UpdateText(params.at(i).first);
317 optionPattern->UpdateIcon(params.at(i).second);
318 childNode->MarkModifyDone();
319 childNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
320 }
321 for (size_t i = updateCount; i < paramCount; i++) {
322 auto optionNode = OptionView::CreateSelectOption(params.at(i).first, params.at(i).second, i);
323 if (i == 0) {
324 auto props = optionNode->GetPaintProperty<OptionPaintProperty>();
325 props->UpdateNeedDivider(false);
326 }
327 MountOption(optionNode);
328 optionNode->MarkModifyDone();
329 optionNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
330 }
331 for (size_t i = childCount; i > updateCount; i--) {
332 RemoveOption();
333 }
334 host->MarkModifyDone();
335 host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
336 }
337
HideMenu(bool isMenuOnTouch) const338 void MenuPattern::HideMenu(bool isMenuOnTouch) const
339 {
340 auto wrapper = GetMenuWrapper();
341 CHECK_NULL_VOID(wrapper);
342 if (wrapper->GetTag() == V2::SELECT_OVERLAY_ETS_TAG) {
343 return;
344 }
345 if (IsContextMenu()) {
346 SubwindowManager::GetInstance()->HideMenuNG(wrapper, targetId_);
347 return;
348 }
349 auto pipeline = PipelineContext::GetCurrentContext();
350 CHECK_NULL_VOID(pipeline);
351 auto overlayManager = pipeline->GetOverlayManager();
352 CHECK_NULL_VOID(overlayManager);
353 overlayManager->HideMenu(wrapper, targetId_, isMenuOnTouch);
354 LOGI("MenuPattern closing menu %{public}d", targetId_);
355 }
356
HideSubMenu()357 void MenuPattern::HideSubMenu()
358 {
359 if (!showedSubMenu_) {
360 return;
361 }
362 auto subMenuPattern = showedSubMenu_->GetPattern<MenuPattern>();
363 CHECK_NULL_VOID(subMenuPattern);
364 subMenuPattern->RemoveParentHoverStyle();
365
366 auto menuItem = subMenuPattern->GetParentMenuItem();
367 CHECK_NULL_VOID(menuItem);
368 auto menuItemPattern = menuItem->GetPattern<MenuItemPattern>();
369 CHECK_NULL_VOID(menuItemPattern);
370 menuItemPattern->SetIsSubMenuShowed(false);
371 menuItemPattern->ClearHoverRegions();
372 menuItemPattern->ResetWrapperMouseEvent();
373
374 auto wrapper = GetMenuWrapper();
375 CHECK_NULL_VOID(wrapper);
376 wrapper->RemoveChild(showedSubMenu_);
377 wrapper->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF_AND_CHILD);
378 showedSubMenu_.Reset();
379 }
380
GetMenuWrapper() const381 RefPtr<FrameNode> MenuPattern::GetMenuWrapper() const
382 {
383 auto host = GetHost();
384 CHECK_NULL_RETURN(host, nullptr);
385 auto parent = host->GetParent();
386 while (parent) {
387 if (parent->GetTag() == V2::MENU_WRAPPER_ETS_TAG || parent->GetTag() == V2::SELECT_OVERLAY_ETS_TAG) {
388 return AceType::DynamicCast<FrameNode>(parent);
389 }
390 parent = parent->GetParent();
391 }
392 return nullptr;
393 }
394
395 // search for inner <Menu> node, once found a <Menu> node, count the number of sibling <Menu>
GetInnerMenuCount() const396 uint32_t MenuPattern::GetInnerMenuCount() const
397 {
398 if (type_ == MenuType::MULTI_MENU || type_ == MenuType::DESKTOP_MENU) {
399 return 0;
400 }
401
402 auto host = GetHost();
403 CHECK_NULL_RETURN(host, 0);
404 auto child = host->GetChildAtIndex(0);
405 uint32_t depth = 0;
406 while (child && depth < MAX_SEARCH_DEPTH) {
407 // found component <Menu>
408 if (child->GetTag() == V2::MENU_ETS_TAG) {
409 auto parent = child->GetParent();
410 CHECK_NULL_RETURN(parent, 0);
411 return parent->GetChildren().size();
412 }
413 child = child->GetChildAtIndex(0);
414 ++depth;
415 }
416 return 0;
417 }
418
GetFirstInnerMenu() const419 RefPtr<FrameNode> MenuPattern::GetFirstInnerMenu() const
420 {
421 if (type_ == MenuType::MULTI_MENU || type_ == MenuType::DESKTOP_MENU) {
422 return nullptr;
423 }
424
425 auto host = GetHost();
426 CHECK_NULL_RETURN(host, nullptr);
427 uint32_t depth = 0;
428 auto child = host->GetChildAtIndex(0);
429 while (child && depth < MAX_SEARCH_DEPTH) {
430 // found component <Menu>
431 if (child->GetTag() == V2::MENU_ETS_TAG) {
432 return AceType::DynamicCast<FrameNode>(child);
433 }
434 child = child->GetChildAtIndex(0);
435 ++depth;
436 }
437 return nullptr;
438 }
439
CopyMenuAttr(const RefPtr<FrameNode> & menuNode) const440 void MenuPattern::CopyMenuAttr(const RefPtr<FrameNode>& menuNode) const
441 {
442 auto pattern = AceType::DynamicCast<MenuPattern>(menuNode->GetPattern());
443 CHECK_NULL_VOID(pattern);
444
445 auto host = GetHost();
446 CHECK_NULL_VOID(host);
447 auto rootMenuPattern = AceType::DynamicCast<MenuPattern>(host->GetPattern());
448 CHECK_NULL_VOID(rootMenuPattern);
449
450 // copy menu pattern properties to rootMenu
451 auto layoutProperty = pattern->GetLayoutProperty<MenuLayoutProperty>();
452 CHECK_NULL_VOID(layoutProperty);
453 auto rootMenuLayoutProperty = rootMenuPattern->GetLayoutProperty<MenuLayoutProperty>();
454 CHECK_NULL_VOID(rootMenuLayoutProperty);
455 if (layoutProperty->GetBorderRadius().has_value()) {
456 rootMenuLayoutProperty->UpdateBorderRadius(layoutProperty->GetBorderRadiusValue());
457 }
458 }
459
460 // mount option on menu
MountOption(const RefPtr<FrameNode> & option)461 void MenuPattern::MountOption(const RefPtr<FrameNode>& option)
462 {
463 auto column = GetMenuColumn();
464 CHECK_NULL_VOID(column);
465 auto pattern = option->GetPattern<OptionPattern>();
466 CHECK_NULL_VOID(pattern);
467 pattern->SetMenu(GetHost());
468 AddOptionNode(option);
469 option->MountToParent(column);
470 }
471
472 // remove option from menu
RemoveOption()473 void MenuPattern::RemoveOption()
474 {
475 auto column = GetMenuColumn();
476 CHECK_NULL_VOID(column);
477 auto endOption = column->GetChildren().back();
478 CHECK_NULL_VOID(endOption);
479 column->RemoveChild(endOption);
480 PopOptionNode();
481 }
482
GetMenuColumn() const483 RefPtr<FrameNode> MenuPattern::GetMenuColumn() const
484 {
485 auto menu = GetHost();
486 CHECK_NULL_RETURN(menu, nullptr);
487 auto scroll = menu->GetChildren().front();
488 CHECK_NULL_RETURN(scroll, nullptr);
489 auto column = scroll->GetChildren().front();
490 return DynamicCast<FrameNode>(column);
491 }
492
DisableTabInMenu()493 void MenuPattern::DisableTabInMenu()
494 {
495 if (IsMultiMenu()) {
496 // multi menu not has scroll and column
497 return;
498 }
499 // disable tab in menu
500 auto column = GetMenuColumn();
501 CHECK_NULL_VOID(column);
502 auto columnFocusHub = column->GetOrCreateFocusHub();
503 CHECK_NULL_VOID(columnFocusHub);
504
505 auto onKeyEvent = [](const KeyEvent& event) -> bool {
506 if (event.action != KeyAction::DOWN) {
507 return false;
508 }
509 return event.code == KeyCode::KEY_TAB;
510 };
511 columnFocusHub->SetOnKeyEventInternal(std::move(onKeyEvent));
512 }
513
CreateLayoutAlgorithm()514 RefPtr<LayoutAlgorithm> MenuPattern::CreateLayoutAlgorithm()
515 {
516 switch (type_) {
517 case MenuType::MULTI_MENU:
518 case MenuType::DESKTOP_MENU:
519 return MakeRefPtr<MultiMenuLayoutAlgorithm>();
520 case MenuType::SUB_MENU:
521 case MenuType::SELECT_OVERLAY_SUB_MENU:
522 return MakeRefPtr<SubMenuLayoutAlgorithm>();
523 default:
524 return MakeRefPtr<MenuLayoutAlgorithm>(targetId_, targetTag_);
525 }
526 }
527
ResetTheme(const RefPtr<FrameNode> & host,bool resetForDesktopMenu)528 void MenuPattern::ResetTheme(const RefPtr<FrameNode>& host, bool resetForDesktopMenu)
529 {
530 auto renderContext = host->GetRenderContext();
531 CHECK_NULL_VOID(renderContext);
532 auto scroll = DynamicCast<FrameNode>(host->GetFirstChild());
533 CHECK_NULL_VOID(scroll);
534
535 renderContext->UpdateBackgroundColor(Color::TRANSPARENT);
536
537 if (resetForDesktopMenu) {
538 // DesktopMenu apply shadow on inner Menu node
539 renderContext->UpdateBackShadow(ShadowConfig::NoneShadow);
540 } else {
541 renderContext->UpdateBackShadow(ShadowConfig::DefaultShadowM);
542 }
543 // to enable inner menu shadow effect for desktopMenu, need to remove clipping from container
544 bool clip = !resetForDesktopMenu;
545 scroll->GetRenderContext()->SetClipToBounds(clip);
546
547 // move padding from scroll to inner menu
548 auto scrollProp = scroll->GetLayoutProperty();
549 scrollProp->UpdatePadding(PaddingProperty());
550 }
551
InitTheme(const RefPtr<FrameNode> & host)552 void MenuPattern::InitTheme(const RefPtr<FrameNode>& host)
553 {
554 auto renderContext = host->GetRenderContext();
555 CHECK_NULL_VOID(renderContext);
556
557 auto pipeline = PipelineBase::GetCurrentContext();
558 CHECK_NULL_VOID(pipeline);
559 auto theme = pipeline->GetTheme<SelectTheme>();
560 CHECK_NULL_VOID(theme);
561
562 auto bgColor = theme->GetBackgroundColor();
563 renderContext->UpdateBackgroundColor(bgColor);
564 renderContext->UpdateBackShadow(ShadowConfig::DefaultShadowM);
565 // make menu round rect
566 BorderRadiusProperty borderRadius;
567 borderRadius.SetRadius(theme->GetMenuBorderRadius());
568 renderContext->UpdateBorderRadius(borderRadius);
569 }
570
InitTheme(const RefPtr<FrameNode> & host)571 void InnerMenuPattern::InitTheme(const RefPtr<FrameNode>& host)
572 {
573 MenuPattern::InitTheme(host);
574 // inner menu applies shadow in OnModifyDone(), where it can determine if it's a DesktopMenu or a regular menu
575
576 auto layoutProperty = host->GetLayoutProperty();
577 if (layoutProperty->GetPaddingProperty()) {
578 // if user defined padding exists, skip applying default padding
579 return;
580 }
581 auto pipeline = PipelineBase::GetCurrentContext();
582 CHECK_NULL_VOID(pipeline);
583 auto theme = pipeline->GetTheme<SelectTheme>();
584 // apply default padding from theme on inner menu
585 PaddingProperty padding;
586 padding.SetEdges(CalcLength(theme->GetOutPadding()));
587 host->GetLayoutProperty()->UpdatePadding(padding);
588
589 host->GetRenderContext()->SetClipToBounds(true);
590 }
591
SetAccessibilityAction()592 void MenuPattern::SetAccessibilityAction()
593 {
594 auto host = GetHost();
595 CHECK_NULL_VOID(host);
596 auto accessibilityProperty = host->GetAccessibilityProperty<AccessibilityProperty>();
597 CHECK_NULL_VOID(accessibilityProperty);
598 accessibilityProperty->SetActionScrollForward([weakPtr = WeakClaim(this)]() {
599 const auto& pattern = weakPtr.Upgrade();
600 auto host = pattern->GetHost();
601 CHECK_NULL_VOID(host);
602 auto firstChild = DynamicCast<FrameNode>(host->GetChildAtIndex(0));
603 CHECK_NULL_VOID(firstChild);
604 if (firstChild && firstChild->GetTag() == V2::SCROLL_ETS_TAG) {
605 auto scrollPattern = firstChild->GetPattern<ScrollPattern>();
606 CHECK_NULL_VOID(scrollPattern);
607 scrollPattern->ScrollPage(false, true);
608 }
609 });
610
611 accessibilityProperty->SetActionScrollBackward([weakPtr = WeakClaim(this)]() {
612 const auto& pattern = weakPtr.Upgrade();
613 auto host = pattern->GetHost();
614 CHECK_NULL_VOID(host);
615 auto firstChild = DynamicCast<FrameNode>(host->GetChildAtIndex(0));
616 CHECK_NULL_VOID(firstChild);
617 if (firstChild && firstChild->GetTag() == V2::SCROLL_ETS_TAG) {
618 auto scrollPattern = firstChild->GetPattern<ScrollPattern>();
619 CHECK_NULL_VOID(scrollPattern);
620 scrollPattern->ScrollPage(true, true);
621 }
622 });
623 }
624
OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper> & dirty,const DirtySwapConfig & config)625 bool MenuPattern::OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper>& dirty, const DirtySwapConfig& config)
626 {
627 if (config.skipMeasure || dirty->SkipMeasureContent()) {
628 return false;
629 }
630
631 auto pipeline = PipelineBase::GetCurrentContext();
632 CHECK_NULL_RETURN(pipeline, false);
633 auto theme = pipeline->GetTheme<SelectTheme>();
634 CHECK_NULL_RETURN(theme, false);
635 auto renderContext = dirty->GetHostNode()->GetRenderContext();
636 CHECK_NULL_RETURN(renderContext, false);
637
638 auto menuProp = DynamicCast<MenuLayoutProperty>(dirty->GetLayoutProperty());
639 CHECK_NULL_RETURN(menuProp, false);
640 BorderRadiusProperty radius;
641 auto defaultRadius = theme->GetMenuBorderRadius();
642 radius.SetRadius(defaultRadius);
643 if (menuProp->GetBorderRadius().has_value()) {
644 auto borderRadius = menuProp->GetBorderRadiusValue();
645 auto topRadius =
646 borderRadius.radiusTopLeft.value_or(Dimension()) + borderRadius.radiusTopRight.value_or(Dimension());
647 auto bottomRadius =
648 borderRadius.radiusBottomLeft.value_or(Dimension()) + borderRadius.radiusBottomRight.value_or(Dimension());
649 auto menuRadius = std::max(topRadius.ConvertToPx(), bottomRadius.ConvertToPx());
650 auto geometryNode = dirty->GetGeometryNode();
651 CHECK_NULL_RETURN(geometryNode, false);
652 auto idealSize = geometryNode->GetMarginFrameSize();
653 if (LessNotEqual(menuRadius, idealSize.Width())) {
654 radius = borderRadius;
655 }
656 }
657 renderContext->UpdateBorderRadius(radius);
658 return true;
659 }
660
GetMainMenuPattern() const661 RefPtr<MenuPattern> MenuPattern::GetMainMenuPattern() const
662 {
663 auto wrapperFrameNode = GetMenuWrapper();
664 CHECK_NULL_RETURN(wrapperFrameNode, nullptr);
665 auto mainMenuUINode = wrapperFrameNode->GetChildAtIndex(0);
666 CHECK_NULL_RETURN(mainMenuUINode, nullptr);
667 auto mainMenuFrameNode = AceType::DynamicCast<FrameNode>(mainMenuUINode);
668 return mainMenuFrameNode->GetPattern<MenuPattern>();
669 }
670
RecordItemsAndGroups()671 void InnerMenuPattern::RecordItemsAndGroups()
672 {
673 itemsAndGroups_.clear();
674 auto host = GetHost();
675 std::stack<RefPtr<UINode>> nodeStack;
676 nodeStack.emplace(host);
677 bool isMenu = true;
678
679 while (!nodeStack.empty()) {
680 auto currentNode = nodeStack.top();
681 nodeStack.pop();
682 // push items and item groups, skip menu node
683 if (!isMenu && AceType::InstanceOf<FrameNode>(currentNode)) {
684 itemsAndGroups_.emplace_back(WeakClaim(RawPtr(currentNode)));
685 continue;
686 }
687 isMenu = false;
688 // skip other type UiNode, such as ForEachNode
689 for (int32_t index = currentNode->GetChildren().size() - 1; index >= 0; index--) {
690 nodeStack.push(currentNode->GetChildAtIndex(index));
691 }
692 }
693 }
694
FindSiblingMenuCount()695 uint32_t InnerMenuPattern::FindSiblingMenuCount()
696 {
697 auto host = GetHost();
698 CHECK_NULL_RETURN(host, 0);
699 auto parent = host->GetParent();
700 CHECK_NULL_RETURN(parent, 0);
701 auto siblings = parent->GetChildren();
702 uint32_t count = 0;
703 for (auto&& sibling : siblings) {
704 if (sibling->GetTag() == V2::MENU_ETS_TAG && sibling != host) {
705 ++count;
706 }
707 }
708 return count;
709 }
710
ApplyDesktopMenuTheme()711 void InnerMenuPattern::ApplyDesktopMenuTheme()
712 {
713 auto host = GetHost();
714 CHECK_NULL_VOID(host);
715 host->GetRenderContext()->UpdateBackShadow(ShadowConfig::DefaultShadowS);
716 }
717
ApplyMultiMenuTheme()718 void InnerMenuPattern::ApplyMultiMenuTheme()
719 {
720 auto host = GetHost();
721 CHECK_NULL_VOID(host);
722 host->GetRenderContext()->UpdateBackShadow(ShadowConfig::NoneShadow);
723 }
724
OnColorConfigurationUpdate()725 void MenuPattern::OnColorConfigurationUpdate()
726 {
727 auto host = GetHost();
728 auto pipeline = PipelineBase::GetCurrentContext();
729 CHECK_NULL_VOID(pipeline);
730
731 auto menuTheme = pipeline->GetTheme<SelectTheme>();
732 CHECK_NULL_VOID(menuTheme);
733
734 auto menuPattern = host->GetPattern<MenuPattern>();
735 CHECK_NULL_VOID(menuPattern);
736
737 auto renderContext = host->GetRenderContext();
738 renderContext->UpdateBackgroundColor(menuTheme->GetBackgroundColor());
739
740 auto optionNode = menuPattern->GetOptions();
741 for (const auto& child : optionNode) {
742 auto optionsPattern = child->GetPattern<OptionPattern>();
743 optionsPattern->SetFontColor(menuTheme->GetFontColor());
744
745 child->MarkModifyDone();
746 child->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
747 }
748 host->SetNeedCallChildrenUpdate(false);
749 }
DumpInfo()750 void MenuPattern::DumpInfo()
751 {
752 DumpLog::GetInstance().AddDesc(
753 std::string("MenuType: ").append(std::to_string(static_cast<int32_t>(GetMenuType()))));
754 }
755 } // namespace OHOS::Ace::NG
756