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