1 /*
2 * Copyright (c) 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_item/menu_item_pattern.h"
17
18 #include <memory>
19
20 #include "base/geometry/ng/offset_t.h"
21 #include "base/memory/ace_type.h"
22 #include "base/utils/utils.h"
23 #include "core/components/select/select_theme.h"
24 #include "core/components/theme/icon_theme.h"
25 #include "core/components_ng/base/frame_node.h"
26 #include "core/components_ng/base/view_stack_processor.h"
27 #include "core/components_ng/pattern/image/image_pattern.h"
28 #include "core/components_ng/pattern/menu/menu_item/menu_item_event_hub.h"
29 #include "core/components_ng/pattern/menu/menu_view.h"
30 #include "core/components_ng/pattern/menu/wrapper/menu_wrapper_pattern.h"
31 #include "core/components_ng/pattern/text/text_layout_property.h"
32 #include "core/components_ng/pattern/text/text_pattern.h"
33 #include "core/components_v2/inspector/inspector_constants.h"
34 #include "core/pipeline/pipeline_base.h"
35
36 namespace OHOS::Ace::NG {
37 namespace {
UpdateFontSize(RefPtr<TextLayoutProperty> & textProperty,RefPtr<MenuLayoutProperty> & menuProperty,const std::optional<Dimension> & fontSize,const Dimension & defaultFontSize)38 void UpdateFontSize(RefPtr<TextLayoutProperty>& textProperty, RefPtr<MenuLayoutProperty>& menuProperty,
39 const std::optional<Dimension>& fontSize, const Dimension& defaultFontSize)
40 {
41 if (fontSize.has_value()) {
42 textProperty->UpdateFontSize(fontSize.value());
43 } else if (menuProperty && menuProperty->GetFontSize().has_value()) {
44 textProperty->UpdateFontSize(menuProperty->GetFontSize().value());
45 } else {
46 textProperty->UpdateFontSize(defaultFontSize);
47 }
48 }
49
UpdateFontWeight(RefPtr<TextLayoutProperty> & textProperty,RefPtr<MenuLayoutProperty> & menuProperty,const std::optional<FontWeight> & fontWeight)50 void UpdateFontWeight(RefPtr<TextLayoutProperty>& textProperty, RefPtr<MenuLayoutProperty>& menuProperty,
51 const std::optional<FontWeight>& fontWeight)
52 {
53 if (fontWeight.has_value()) {
54 textProperty->UpdateFontWeight(fontWeight.value());
55 } else if (menuProperty && menuProperty->GetFontWeight().has_value()) {
56 textProperty->UpdateFontWeight(menuProperty->GetFontWeight().value());
57 } else {
58 textProperty->UpdateFontWeight(FontWeight::REGULAR);
59 }
60 }
61
UpdateFontStyle(RefPtr<TextLayoutProperty> & textProperty,RefPtr<MenuLayoutProperty> & menuProperty,const std::optional<Ace::FontStyle> & fontStyle)62 void UpdateFontStyle(RefPtr<TextLayoutProperty>& textProperty, RefPtr<MenuLayoutProperty>& menuProperty,
63 const std::optional<Ace::FontStyle>& fontStyle)
64 {
65 if (fontStyle.has_value()) {
66 textProperty->UpdateItalicFontStyle(fontStyle.value());
67 } else if (menuProperty && menuProperty->GetItalicFontStyle().has_value()) {
68 textProperty->UpdateItalicFontStyle(menuProperty->GetItalicFontStyle().value());
69 } else {
70 textProperty->UpdateItalicFontStyle(Ace::FontStyle::NORMAL);
71 }
72 }
73
UpdateFontColor(RefPtr<TextLayoutProperty> & textProperty,RefPtr<MenuLayoutProperty> & menuProperty,const std::optional<Color> & fontColor,const Color & defaultFontColor)74 void UpdateFontColor(RefPtr<TextLayoutProperty>& textProperty, RefPtr<MenuLayoutProperty>& menuProperty,
75 const std::optional<Color>& fontColor, const Color& defaultFontColor)
76 {
77 if (fontColor.has_value()) {
78 textProperty->UpdateTextColor(fontColor.value());
79 } else if (menuProperty && menuProperty->GetFontColor().has_value()) {
80 textProperty->UpdateTextColor(menuProperty->GetFontColor().value());
81 } else {
82 textProperty->UpdateTextColor(defaultFontColor);
83 }
84 }
85
UpdateFontFamily(RefPtr<TextLayoutProperty> & textProperty,RefPtr<MenuLayoutProperty> & menuProperty,const std::optional<std::vector<std::string>> & fontFamilies)86 void UpdateFontFamily(RefPtr<TextLayoutProperty>& textProperty, RefPtr<MenuLayoutProperty>& menuProperty,
87 const std::optional<std::vector<std::string>>& fontFamilies)
88 {
89 std::vector<std::string> emptyFontfamily;
90 if (fontFamilies.has_value()) {
91 textProperty->UpdateFontFamily(fontFamilies.value());
92 } else if (menuProperty && menuProperty->GetFontFamily().has_value()) {
93 textProperty->UpdateFontFamily(menuProperty->GetFontFamily().value());
94 } else {
95 textProperty->UpdateFontFamily(emptyFontfamily);
96 }
97 }
98
UpdateIconSrc(RefPtr<FrameNode> & node,const std::string & src,const Dimension & horizontalSize,const Dimension & verticalSize,const Color & color)99 void UpdateIconSrc(RefPtr<FrameNode>& node, const std::string& src, const Dimension& horizontalSize,
100 const Dimension& verticalSize, const Color& color)
101 {
102 ImageSourceInfo imageSourceInfo;
103 imageSourceInfo.SetSrc(src);
104 imageSourceInfo.SetFillColor(color);
105
106 auto props = node->GetLayoutProperty<ImageLayoutProperty>();
107 CHECK_NULL_VOID(props);
108 props->UpdateImageSourceInfo(imageSourceInfo);
109 props->UpdateAlignment(Alignment::CENTER);
110 CalcSize idealSize = { CalcLength(horizontalSize), CalcLength(verticalSize) };
111 MeasureProperty layoutConstraint;
112 layoutConstraint.selfIdealSize = idealSize;
113 props->UpdateCalcLayoutProperty(layoutConstraint);
114
115 auto iconRenderProperty = node->GetPaintProperty<ImageRenderProperty>();
116 CHECK_NULL_VOID(iconRenderProperty);
117 iconRenderProperty->UpdateSvgFillColor(color);
118 }
119 } // namespace
120
OnMountToParentDone()121 void MenuItemPattern::OnMountToParentDone()
122 {
123 UpdateTextNodes();
124 }
125
OnAttachToFrameNode()126 void MenuItemPattern::OnAttachToFrameNode()
127 {
128 RegisterOnKeyEvent();
129 RegisterOnClick();
130 RegisterOnTouch();
131 RegisterOnHover();
132 }
133
OnAttachToFrameNode()134 void CustomMenuItemPattern::OnAttachToFrameNode()
135 {
136 RegisterOnKeyEvent();
137 RegisterOnTouch();
138 }
139
OnModifyDone()140 void MenuItemPattern::OnModifyDone()
141 {
142 Pattern::OnModifyDone();
143 auto host = GetHost();
144 CHECK_NULL_VOID(host);
145 RefPtr<FrameNode> leftRow =
146 host->GetChildAtIndex(0) ? AceType::DynamicCast<FrameNode>(host->GetChildAtIndex(0)) : nullptr;
147 CHECK_NULL_VOID(leftRow);
148 AddSelectIcon(leftRow);
149 UpdateIcon(leftRow, true);
150 auto menuNode = GetMenu();
151 auto menuProperty = menuNode ? menuNode->GetLayoutProperty<MenuLayoutProperty>() : nullptr;
152 UpdateText(leftRow, menuProperty, false);
153
154 RefPtr<FrameNode> rightRow =
155 host->GetChildAtIndex(1) ? AceType::DynamicCast<FrameNode>(host->GetChildAtIndex(1)) : nullptr;
156 CHECK_NULL_VOID(rightRow);
157 UpdateText(rightRow, menuProperty, true);
158 UpdateIcon(rightRow, false);
159 if (IsDisabled()) {
160 UpdateDisabledStyle();
161 }
162 SetAccessibilityAction();
163
164 host->GetRenderContext()->SetClipToBounds(true);
165 }
166
GetMenuWrapper()167 RefPtr<FrameNode> MenuItemPattern::GetMenuWrapper()
168 {
169 auto host = GetHost();
170 CHECK_NULL_RETURN(host, nullptr);
171 auto parent = host->GetParent();
172 while (parent) {
173 if (parent->GetTag() == V2::MENU_WRAPPER_ETS_TAG || parent->GetTag() == V2::SELECT_OVERLAY_ETS_TAG) {
174 return AceType::DynamicCast<FrameNode>(parent);
175 }
176 parent = parent->GetParent();
177 }
178 return nullptr;
179 }
180
GetMenu(bool needTopMenu)181 RefPtr<FrameNode> MenuItemPattern::GetMenu(bool needTopMenu)
182 {
183 auto host = GetHost();
184 CHECK_NULL_RETURN(host, nullptr);
185 auto parent = host->GetParent();
186 RefPtr<FrameNode> menuNode = nullptr;
187 while (parent) {
188 if (parent->GetTag() == V2::MENU_ETS_TAG) {
189 menuNode = AceType::DynamicCast<FrameNode>(parent);
190 if (!needTopMenu) {
191 return menuNode;
192 }
193 }
194 parent = parent->GetParent();
195 }
196 return menuNode;
197 }
198
GetMenuPattern(bool needTopMenu)199 RefPtr<MenuPattern> MenuItemPattern::GetMenuPattern(bool needTopMenu)
200 {
201 auto menu = GetMenu(true);
202 if (!menu) {
203 return nullptr;
204 }
205 return menu->GetPattern<MenuPattern>();
206 }
207
ShowSubMenu()208 void MenuItemPattern::ShowSubMenu()
209 {
210 auto host = GetHost();
211 CHECK_NULL_VOID(host);
212 LOGI("MenuItemPattern::ShowSubMenu menu item id is %{public}d", host->GetId());
213 auto buildFunc = GetSubBuilder();
214 if (!buildFunc || isSubMenuShowed_) {
215 return;
216 }
217
218 // Hide SubMenu of parent Menu node
219 auto parentMenu = GetMenu();
220 CHECK_NULL_VOID(parentMenu);
221 auto parentMenuPattern = parentMenu->GetPattern<MenuPattern>();
222 CHECK_NULL_VOID(parentMenuPattern);
223 auto showedSubMenu = parentMenuPattern->GetShowedSubMenu();
224 if (showedSubMenu) {
225 auto showedSubMenuPattern = showedSubMenu->GetPattern<MenuPattern>();
226 CHECK_NULL_VOID(showedSubMenuPattern);
227 auto showedMenuItem = showedSubMenuPattern->GetParentMenuItem();
228 CHECK_NULL_VOID(showedMenuItem);
229 if (showedMenuItem->GetId() != host->GetId()) {
230 parentMenuPattern->HideSubMenu();
231 }
232 }
233
234 isSubMenuShowed_ = true;
235
236 NG::ScopedViewStackProcessor builderViewStackProcessor;
237 buildFunc();
238 auto customNode = NG::ViewStackProcessor::GetInstance()->Finish();
239 bool isSelectOverlayMenu = IsSelectOverlayMenu();
240 MenuType menuType = isSelectOverlayMenu ? MenuType::SELECT_OVERLAY_SUB_MENU : MenuType::SUB_MENU;
241 auto subMenu = MenuView::Create(customNode, host->GetId(), host->GetTag(), menuType);
242 CHECK_NULL_VOID(subMenu);
243 auto menuPattern = subMenu->GetPattern<MenuPattern>();
244 CHECK_NULL_VOID(menuPattern);
245 menuPattern->SetParentMenuItem(host);
246 subMenuId_ = subMenu->GetId();
247 AddSelfHoverRegion(host);
248
249 auto menuWrapper = GetMenuWrapper();
250 CHECK_NULL_VOID(menuWrapper);
251 subMenu->MountToParent(menuWrapper);
252
253 OffsetF offset = GetSubMenuPostion(host);
254 auto menuProps = subMenu->GetLayoutProperty<MenuLayoutProperty>();
255 CHECK_NULL_VOID(menuProps);
256 menuProps->UpdateMenuOffset(offset);
257 menuWrapper->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF_AND_CHILD);
258 RegisterWrapperMouseEvent();
259
260 // select overlay menu no need focus
261 if (!isSelectOverlayMenu) {
262 auto focusHub = subMenu->GetOrCreateFocusHub();
263 CHECK_NULL_VOID(focusHub);
264 focusHub->RequestFocusWithDefaultFocusFirstly();
265 }
266 parentMenuPattern->SetShowedSubMenu(subMenu);
267 }
268
CloseMenu()269 void MenuItemPattern::CloseMenu()
270 {
271 // no need close for selection menu
272 if (IsSelectOverlayMenu()) {
273 return;
274 }
275 auto menuWrapper = GetMenuWrapper();
276 CHECK_NULL_VOID(menuWrapper);
277 auto menuWrapperPattern = menuWrapper->GetPattern<MenuWrapperPattern>();
278 CHECK_NULL_VOID(menuWrapperPattern);
279 menuWrapperPattern->HideMenu();
280 }
281
RegisterOnClick()282 void MenuItemPattern::RegisterOnClick()
283 {
284 auto host = GetHost();
285 CHECK_NULL_VOID(host);
286 auto event = [weak = WeakClaim(this)](GestureEvent& /* info */) {
287 auto pattern = weak.Upgrade();
288 CHECK_NULL_VOID(pattern);
289 auto host = pattern->GetHost();
290 CHECK_NULL_VOID(host);
291 auto hub = host->GetEventHub<MenuItemEventHub>();
292 CHECK_NULL_VOID(hub);
293 auto onChange = hub->GetOnChange();
294 auto selectedChangeEvent = hub->GetSelectedChangeEvent();
295 pattern->SetChange();
296 if (selectedChangeEvent) {
297 LOGI("trigger onChangeEvent");
298 selectedChangeEvent(pattern->IsSelected());
299 }
300 if (onChange) {
301 LOGI("trigger onChange");
302 onChange(pattern->IsSelected());
303 }
304 host->OnAccessibilityEvent(AccessibilityEventType::SELECTED);
305
306 if (pattern->GetSubBuilder() != nullptr) {
307 pattern->ShowSubMenu();
308 return;
309 }
310
311 // hide menu when menu item is clicked
312 pattern->CloseMenu();
313 };
314 auto clickEvent = MakeRefPtr<ClickEvent>(std::move(event));
315
316 auto gestureHub = host->GetOrCreateGestureEventHub();
317 CHECK_NULL_VOID(gestureHub);
318 gestureHub->AddClickEvent(clickEvent);
319 }
320
RegisterOnTouch()321 void MenuItemPattern::RegisterOnTouch()
322 {
323 auto host = GetHost();
324 CHECK_NULL_VOID(host);
325 auto gestureHub = host->GetOrCreateGestureEventHub();
326 CHECK_NULL_VOID(gestureHub);
327
328 auto touchCallback = [weak = WeakClaim(this)](const TouchEventInfo& info) {
329 auto pattern = weak.Upgrade();
330 CHECK_NULL_VOID(pattern);
331 pattern->OnTouch(info);
332 };
333 auto touchEvent = MakeRefPtr<TouchEventImpl>(std::move(touchCallback));
334 gestureHub->AddTouchEvent(touchEvent);
335 }
336
RegisterOnHover()337 void MenuItemPattern::RegisterOnHover()
338 {
339 auto host = GetHost();
340 CHECK_NULL_VOID(host);
341 auto inputHub = host->GetOrCreateInputEventHub();
342 CHECK_NULL_VOID(inputHub);
343 auto mouseTask = [weak = WeakClaim(this)](bool isHover) {
344 auto pattern = weak.Upgrade();
345 CHECK_NULL_VOID(pattern);
346 pattern->OnHover(isHover);
347 };
348 auto mouseEvent = MakeRefPtr<InputEvent>(std::move(mouseTask));
349 inputHub->AddOnHoverEvent(mouseEvent);
350 }
351
RegisterOnKeyEvent()352 void MenuItemPattern::RegisterOnKeyEvent()
353 {
354 auto host = GetHost();
355 CHECK_NULL_VOID(host);
356 auto focusHub = host->GetOrCreateFocusHub();
357 CHECK_NULL_VOID(focusHub);
358 auto onKeyEvent = [wp = WeakClaim(this)](const KeyEvent& event) -> bool {
359 auto pattern = wp.Upgrade();
360 CHECK_NULL_RETURN_NOLOG(pattern, false);
361 return pattern->OnKeyEvent(event);
362 };
363 focusHub->SetOnKeyEventInternal(std::move(onKeyEvent));
364 }
365
OnTouch(const TouchEventInfo & info)366 void MenuItemPattern::OnTouch(const TouchEventInfo& info)
367 {
368 // change menu item paint props on press
369 auto touchType = info.GetTouches().front().GetTouchType();
370 auto pipeline = PipelineBase::GetCurrentContext();
371 CHECK_NULL_VOID(pipeline);
372 auto theme = pipeline->GetTheme<SelectTheme>();
373 CHECK_NULL_VOID(theme);
374
375 if (touchType == TouchType::DOWN) {
376 // change background color, update press status
377 SetBgBlendColor(GetSubBuilder() ? theme->GetHoverColor() : theme->GetClickedColor());
378 } else if (touchType == TouchType::UP) {
379 SetBgBlendColor(isHovered_ ? theme->GetHoverColor() : Color::TRANSPARENT);
380 } else {
381 return;
382 }
383 PlayBgColorAnimation(false);
384 auto host = GetHost();
385 CHECK_NULL_VOID(host);
386 host->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
387 }
388
OnTouch(const TouchEventInfo & info)389 void CustomMenuItemPattern::OnTouch(const TouchEventInfo& info)
390 {
391 auto touchType = info.GetTouches().front().GetTouchType();
392
393 // close menu when touch up
394 // can't use onClick because that conflicts with interactions developers might set to the customNode
395 // recognize gesture as click if touch up position is close to last touch down position
396 if (touchType == TouchType::DOWN) {
397 lastTouchOffset_ = std::make_unique<Offset>(info.GetTouches().front().GetLocalLocation());
398 } else if (touchType == TouchType::UP) {
399 auto touchUpOffset = info.GetTouches().front().GetLocalLocation();
400 if (lastTouchOffset_ && (touchUpOffset - *lastTouchOffset_).GetDistance() <= DEFAULT_CLICK_DISTANCE) {
401 CloseMenu();
402 }
403 lastTouchOffset_.reset();
404 }
405 }
406
OnHover(bool isHover)407 void MenuItemPattern::OnHover(bool isHover)
408 {
409 isHovered_ = isHover;
410 auto pipeline = PipelineBase::GetCurrentContext();
411 CHECK_NULL_VOID(pipeline);
412 auto theme = pipeline->GetTheme<SelectTheme>();
413 CHECK_NULL_VOID(theme);
414
415 if (isHover || isSubMenuShowed_) {
416 // keep hover color when subMenu showed
417 SetBgBlendColor(theme->GetHoverColor());
418 ShowSubMenu();
419 } else {
420 SetBgBlendColor(Color::TRANSPARENT);
421 }
422 PlayBgColorAnimation();
423 auto host = GetHost();
424 CHECK_NULL_VOID(host);
425 host->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
426 }
427
OnKeyEvent(const KeyEvent & event)428 bool MenuItemPattern::OnKeyEvent(const KeyEvent& event)
429 {
430 if (event.action != KeyAction::DOWN) {
431 return false;
432 }
433 auto host = GetHost();
434 CHECK_NULL_RETURN(host, false);
435 auto focusHub = host->GetOrCreateFocusHub();
436 CHECK_NULL_RETURN(focusHub, false);
437 if (event.code == KeyCode::KEY_ENTER) {
438 focusHub->OnClick(event);
439 return true;
440 }
441 if (event.code == KeyCode::KEY_DPAD_RIGHT && GetSubBuilder() && !isSubMenuShowed_) {
442 auto pipeline = PipelineBase::GetCurrentContext();
443 CHECK_NULL_RETURN(pipeline, false);
444 auto theme = pipeline->GetTheme<SelectTheme>();
445 CHECK_NULL_RETURN(theme, false);
446 SetBgBlendColor(theme->GetHoverColor());
447 PlayBgColorAnimation();
448 host->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
449 ShowSubMenu();
450 return true;
451 }
452 return false;
453 }
454
OnKeyEvent(const KeyEvent & event)455 bool CustomMenuItemPattern::OnKeyEvent(const KeyEvent& event)
456 {
457 if (event.action != KeyAction::DOWN) {
458 return false;
459 }
460 auto host = GetHost();
461 CHECK_NULL_RETURN(host, false);
462 auto focusHub = host->GetOrCreateFocusHub();
463 CHECK_NULL_RETURN(focusHub, false);
464 if (event.code == KeyCode::KEY_ENTER || event.code == KeyCode::KEY_SPACE) {
465 focusHub->OnClick(event);
466 CloseMenu();
467 return true;
468 }
469 return false;
470 }
471
RegisterWrapperMouseEvent()472 void MenuItemPattern::RegisterWrapperMouseEvent()
473 {
474 auto menuWrapper = GetMenuWrapper();
475 if (menuWrapper && !wrapperMouseEvent_) {
476 auto inputHub = menuWrapper->GetOrCreateInputEventHub();
477 CHECK_NULL_VOID(inputHub);
478 auto menuWrapperPattern = menuWrapper->GetPattern<MenuWrapperPattern>();
479 CHECK_NULL_VOID(menuWrapperPattern);
480
481 auto mouseTask = [weak = WeakClaim(this), menuWrapperPattern](MouseInfo& info) {
482 auto pattern = weak.Upgrade();
483 CHECK_NULL_VOID(pattern);
484 if (menuWrapperPattern) {
485 menuWrapperPattern->HandleMouseEvent(info, pattern);
486 }
487 };
488 wrapperMouseEvent_ = MakeRefPtr<InputEvent>(std::move(mouseTask));
489 inputHub->AddOnMouseEvent(wrapperMouseEvent_);
490 }
491 }
492
AddSelfHoverRegion(const RefPtr<FrameNode> & targetNode)493 void MenuItemPattern::AddSelfHoverRegion(const RefPtr<FrameNode>& targetNode)
494 {
495 OffsetF topLeftPoint;
496 OffsetF bottomRightPoint;
497 auto frameSize = targetNode->GetGeometryNode()->GetMarginFrameSize();
498 topLeftPoint = targetNode->GetPaintRectOffset();
499 bottomRightPoint = targetNode->GetPaintRectOffset() + OffsetF(frameSize.Width(), frameSize.Height());
500 AddHoverRegions(topLeftPoint, bottomRightPoint);
501 }
502
GetSubMenuPostion(const RefPtr<FrameNode> & targetNode)503 OffsetF MenuItemPattern::GetSubMenuPostion(const RefPtr<FrameNode>& targetNode)
504 {
505 // show menu at left top point of targetNode
506 OffsetF position;
507 auto frameSize = targetNode->GetGeometryNode()->GetMarginFrameSize();
508 position = targetNode->GetPaintRectOffset() + OffsetF(frameSize.Width(), 0.0);
509 return position;
510 }
511
AddHoverRegions(const OffsetF & topLeftPoint,const OffsetF & bottomRightPoint)512 void MenuItemPattern::AddHoverRegions(const OffsetF& topLeftPoint, const OffsetF& bottomRightPoint)
513 {
514 TouchRegion hoverRegion = TouchRegion(
515 Offset(topLeftPoint.GetX(), topLeftPoint.GetY()), Offset(bottomRightPoint.GetX(), bottomRightPoint.GetY()));
516 hoverRegions_.emplace_back(hoverRegion);
517 LOGI("MenuItemPattern::AddHoverRegions hoverRegion is %{public}s to %{public}s", topLeftPoint.ToString().c_str(),
518 bottomRightPoint.ToString().c_str());
519 }
520
IsInHoverRegions(double x,double y)521 bool MenuItemPattern::IsInHoverRegions(double x, double y)
522 {
523 for (auto hoverRegion : hoverRegions_) {
524 if (hoverRegion.ContainsInRegion(x, y)) {
525 return true;
526 }
527 }
528 return false;
529 }
530
PlayBgColorAnimation(bool isHoverChange)531 void MenuItemPattern::PlayBgColorAnimation(bool isHoverChange)
532 {
533 auto pipeline = PipelineBase::GetCurrentContext();
534 CHECK_NULL_VOID(pipeline);
535 auto theme = pipeline->GetTheme<SelectTheme>();
536 CHECK_NULL_VOID(theme);
537 AnimationOption option;
538 if (isHoverChange) {
539 option.SetDuration(theme->GetHoverAnimationDuration());
540 option.SetCurve(Curves::FRICTION);
541 } else {
542 option.SetDuration(theme->GetPressAnimationDuration());
543 option.SetCurve(Curves::SHARP);
544 }
545
546 AnimationUtils::Animate(option, [weak = WeakClaim(this)]() {
547 auto pattern = weak.Upgrade();
548 CHECK_NULL_VOID_NOLOG(pattern);
549 auto host = pattern->GetHost();
550 CHECK_NULL_VOID_NOLOG(host);
551 auto renderContext = host->GetRenderContext();
552 CHECK_NULL_VOID_NOLOG(renderContext);
553 renderContext->BlendBgColor(pattern->GetBgBlendColor());
554 });
555 }
556
AddSelectIcon(RefPtr<FrameNode> & row)557 void MenuItemPattern::AddSelectIcon(RefPtr<FrameNode>& row)
558 {
559 auto itemProperty = GetLayoutProperty<MenuItemLayoutProperty>();
560 CHECK_NULL_VOID(itemProperty);
561 if (!itemProperty->GetSelectIcon().value_or(false)) {
562 if (selectIcon_) {
563 row->RemoveChildAtIndex(0);
564 selectIcon_ = nullptr;
565 row->MarkModifyDone();
566 row->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
567 }
568 return;
569 }
570 if (!selectIcon_) {
571 selectIcon_ = FrameNode::CreateFrameNode(
572 V2::IMAGE_ETS_TAG, ElementRegister::GetInstance()->MakeUniqueId(), AceType::MakeRefPtr<ImagePattern>());
573 CHECK_NULL_VOID(selectIcon_);
574 }
575 auto pipeline = PipelineBase::GetCurrentContext();
576 CHECK_NULL_VOID(pipeline);
577 auto iconTheme = pipeline->GetTheme<IconTheme>();
578 CHECK_NULL_VOID(iconTheme);
579 auto userIcon = itemProperty->GetSelectIconSrc().value_or("");
580 auto iconPath = userIcon.empty() ? iconTheme->GetIconPath(InternalResource::ResourceId::MENU_OK_SVG) : userIcon;
581 auto selectTheme = pipeline->GetTheme<SelectTheme>();
582 CHECK_NULL_VOID(selectTheme);
583 UpdateIconSrc(selectIcon_, iconPath, selectTheme->GetIconSideLength(), selectTheme->GetIconSideLength(),
584 selectTheme->GetMenuIconColor());
585
586 auto renderContext = selectIcon_->GetRenderContext();
587 CHECK_NULL_VOID(renderContext);
588 renderContext->SetVisible(isSelected_);
589
590 selectIcon_->MountToParent(row, 0);
591 selectIcon_->MarkModifyDone();
592 selectIcon_->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
593 }
594
UpdateIcon(RefPtr<FrameNode> & row,bool isStart)595 void MenuItemPattern::UpdateIcon(RefPtr<FrameNode>& row, bool isStart)
596 {
597 auto itemProperty = GetLayoutProperty<MenuItemLayoutProperty>();
598 CHECK_NULL_VOID(itemProperty);
599 auto iconSrc = isStart ? itemProperty->GetStartIcon().value_or("") : itemProperty->GetEndIcon().value_or("");
600 auto& iconNode = isStart ? startIcon_ : endIcon_;
601 if (iconSrc.empty()) {
602 row->RemoveChild(iconNode); // it's safe even if iconNode is nullptr
603 iconNode = nullptr;
604 row->MarkModifyDone();
605 row->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
606 return;
607 }
608
609 if (!iconNode) {
610 iconNode = FrameNode::CreateFrameNode(
611 V2::IMAGE_ETS_TAG, ElementRegister::GetInstance()->MakeUniqueId(), AceType::MakeRefPtr<ImagePattern>());
612 CHECK_NULL_VOID(iconNode);
613 }
614 auto pipeline = PipelineBase::GetCurrentContext();
615 CHECK_NULL_VOID(pipeline);
616 auto selectTheme = pipeline->GetTheme<SelectTheme>();
617 CHECK_NULL_VOID(selectTheme);
618 auto iconWidth = isStart ? selectTheme->GetIconSideLength() : selectTheme->GetEndIconWidth();
619 auto iconHeight = isStart ? selectTheme->GetIconSideLength() : selectTheme->GetEndIconHeight();
620 UpdateIconSrc(iconNode, iconSrc, iconWidth, iconHeight, selectTheme->GetMenuIconColor());
621
622 iconNode->MountToParent(row, ((isStart && selectIcon_) || (!isStart && label_)) ? 1 : 0);
623 iconNode->MarkModifyDone();
624 iconNode->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
625 }
626
UpdateText(RefPtr<FrameNode> & row,RefPtr<MenuLayoutProperty> & menuProperty,bool isLabel)627 void MenuItemPattern::UpdateText(RefPtr<FrameNode>& row, RefPtr<MenuLayoutProperty>& menuProperty, bool isLabel)
628 {
629 auto itemProperty = GetLayoutProperty<MenuItemLayoutProperty>();
630 CHECK_NULL_VOID(itemProperty);
631 auto content = isLabel ? itemProperty->GetLabel().value_or("") : itemProperty->GetContent().value_or("");
632 auto& node = isLabel ? label_ : content_;
633 if (content.empty()) {
634 (void)row->RemoveChild(node); // it's safe even if node is nullptr
635 node = nullptr;
636 row->MarkModifyDone();
637 row->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
638 return;
639 }
640
641 if (!node) {
642 node = FrameNode::CreateFrameNode(
643 V2::TEXT_ETS_TAG, ElementRegister::GetInstance()->MakeUniqueId(), AceType::MakeRefPtr<TextPattern>());
644 }
645 auto textProperty = node ? node->GetLayoutProperty<TextLayoutProperty>() : nullptr;
646 CHECK_NULL_VOID(textProperty);
647 auto renderContext = node->GetRenderContext();
648 CHECK_NULL_VOID(renderContext);
649 renderContext->UpdateClipEdge(false);
650 auto context = PipelineBase::GetCurrentContext();
651 auto theme = context ? context->GetTheme<SelectTheme>() : nullptr;
652 CHECK_NULL_VOID(theme);
653 auto fontSize = isLabel ? itemProperty->GetLabelFontSize() : itemProperty->GetFontSize();
654 UpdateFontSize(textProperty, menuProperty, fontSize, theme->GetMenuFontSize());
655 auto fontWeight = isLabel ? itemProperty->GetLabelFontWeight() : itemProperty->GetFontWeight();
656 UpdateFontWeight(textProperty, menuProperty, fontWeight);
657 auto fontStyle = isLabel ? itemProperty->GetLabelItalicFontStyle() : itemProperty->GetItalicFontStyle();
658 UpdateFontStyle(textProperty, menuProperty, fontStyle);
659 auto fontColor = isLabel ? itemProperty->GetLabelFontColor() : itemProperty->GetFontColor();
660 UpdateFontColor(
661 textProperty, menuProperty, fontColor, isLabel ? theme->GetSecondaryFontColor() : theme->GetMenuFontColor());
662 auto fontFamily = isLabel ? itemProperty->GetLabelFontFamily() : itemProperty->GetFontFamily();
663 UpdateFontFamily(textProperty, menuProperty, fontFamily);
664 textProperty->UpdateContent(content);
665 textProperty->UpdateMaxLines(1);
666 textProperty->UpdateTextOverflow(TextOverflow::ELLIPSIS);
667 node->MountToParent(row, isLabel ? 0 : DEFAULT_NODE_SLOT);
668 node->MarkModifyDone();
669 node->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
670 }
671
UpdateTextNodes()672 void MenuItemPattern::UpdateTextNodes()
673 {
674 auto host = GetHost();
675 CHECK_NULL_VOID(host);
676 auto menuNode = GetMenu();
677 CHECK_NULL_VOID(menuNode);
678 auto menuProperty = menuNode->GetLayoutProperty<MenuLayoutProperty>();
679 RefPtr<FrameNode> leftRow =
680 host->GetChildAtIndex(0) ? AceType::DynamicCast<FrameNode>(host->GetChildAtIndex(0)) : nullptr;
681 CHECK_NULL_VOID(leftRow);
682 UpdateText(leftRow, menuProperty, false);
683 RefPtr<FrameNode> rightRow =
684 host->GetChildAtIndex(1) ? AceType::DynamicCast<FrameNode>(host->GetChildAtIndex(1)) : nullptr;
685 CHECK_NULL_VOID(rightRow);
686 UpdateText(rightRow, menuProperty, true);
687 }
688
IsDisabled()689 bool MenuItemPattern::IsDisabled()
690 {
691 auto eventHub = GetHost()->GetEventHub<MenuItemEventHub>();
692 CHECK_NULL_RETURN(eventHub, true);
693 return !eventHub->IsEnabled();
694 }
695
UpdateDisabledStyle()696 void MenuItemPattern::UpdateDisabledStyle()
697 {
698 CHECK_NULL_VOID(content_);
699 auto context = PipelineBase::GetCurrentContext();
700 CHECK_NULL_VOID(context);
701 auto theme = context->GetTheme<SelectTheme>();
702 CHECK_NULL_VOID(theme);
703 content_->GetRenderContext()->UpdateForegroundColor(theme->GetDisabledMenuFontColor());
704 content_->MarkModifyDone();
705 }
706
SetAccessibilityAction()707 void MenuItemPattern::SetAccessibilityAction()
708 {
709 auto host = GetHost();
710 CHECK_NULL_VOID(host);
711 auto accessibilityProperty = host->GetAccessibilityProperty<AccessibilityProperty>();
712 CHECK_NULL_VOID(accessibilityProperty);
713 accessibilityProperty->SetActionSelect([weakPtr = WeakClaim(this)]() {
714 const auto& pattern = weakPtr.Upgrade();
715 CHECK_NULL_VOID(pattern);
716 auto host = pattern->GetHost();
717 CHECK_NULL_VOID(host);
718 auto hub = host->GetEventHub<MenuItemEventHub>();
719 CHECK_NULL_VOID(hub);
720 auto onChange = hub->GetOnChange();
721 auto selectedChangeEvent = hub->GetSelectedChangeEvent();
722 pattern->SetChange();
723 if (selectedChangeEvent) {
724 selectedChangeEvent(pattern->IsSelected());
725 }
726 if (onChange) {
727 onChange(pattern->IsSelected());
728 }
729
730 if (pattern->GetSubBuilder() != nullptr) {
731 pattern->ShowSubMenu();
732 return;
733 }
734
735 pattern->CloseMenu();
736 });
737 }
738
IsSelectOverlayMenu()739 bool MenuItemPattern::IsSelectOverlayMenu()
740 {
741 auto topLevelMenuPattern = GetMenuPattern(true);
742 if (!topLevelMenuPattern) {
743 return false;
744 }
745 return topLevelMenuPattern->IsSelectOverlayExtensionMenu() || topLevelMenuPattern->IsSelectOverlayCustomMenu() ||
746 topLevelMenuPattern->IsSelectOverlaySubMenu();
747 }
748 } // namespace OHOS::Ace::NG
749