1 /*
2 * Copyright (c) 2022 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 "base/utils/utils.h"
19 #include "core/components/common/properties/shadow_config.h"
20 #include "core/components/select/select_theme.h"
21 #include "core/components_ng/event/click_event.h"
22 #include "core/components_ng/pattern/menu/menu_item/menu_item_pattern.h"
23 #include "core/components_ng/pattern/menu/wrapper/menu_wrapper_pattern.h"
24 #include "core/components_ng/pattern/option/option_pattern.h"
25 #include "core/components_v2/inspector/inspector_constants.h"
26 #include "core/event/touch_event.h"
27 #include "core/pipeline/pipeline_base.h"
28 #include "core/pipeline_ng/pipeline_context.h"
29
30 namespace OHOS::Ace::NG {
OnModifyDone()31 void MenuPattern::OnModifyDone()
32 {
33 RegisterOnTouch();
34 auto host = GetHost();
35 CHECK_NULL_VOID(host);
36 auto focusHub = host->GetOrCreateFocusHub();
37 CHECK_NULL_VOID(focusHub);
38 RegisterOnKeyEvent(focusHub);
39 DisableTabInMenu();
40
41 if (IsMultiMenu()) {
42 return;
43 }
44
45 auto renderContext = host->GetRenderContext();
46 CHECK_NULL_VOID(renderContext);
47
48 // get theme from SelectThemeManager
49 auto pipeline = PipelineBase::GetCurrentContext();
50 CHECK_NULL_VOID(pipeline);
51 auto theme = pipeline->GetTheme<SelectTheme>();
52
53 // set background color
54 auto bgColor = theme->GetBackgroundColor();
55 renderContext->UpdateBackgroundColor(bgColor);
56
57 // make menu round rect
58 BorderRadiusProperty borderRadius;
59 borderRadius.SetRadius(theme->GetMenuBorderRadius());
60 renderContext->UpdateBorderRadius(borderRadius);
61 renderContext->UpdateBackShadow(ShadowConfig::DefaultShadowM);
62 renderContext->SetClipToBounds(true);
63 }
64
65 // close menu on touch up
RegisterOnTouch()66 void MenuPattern::RegisterOnTouch()
67 {
68 CHECK_NULL_VOID_NOLOG(!onTouch_);
69 auto host = GetHost();
70 CHECK_NULL_VOID(host);
71 auto gesture = host->GetOrCreateGestureEventHub();
72 CHECK_NULL_VOID(gesture);
73 auto touchTask = [weak = WeakClaim(this)](const TouchEventInfo& info) {
74 auto pattern = weak.Upgrade();
75 CHECK_NULL_VOID_NOLOG(pattern);
76 pattern->OnTouchEvent(info);
77 };
78 onTouch_ = MakeRefPtr<TouchEventImpl>(std::move(touchTask));
79 gesture->AddTouchEvent(onTouch_);
80 }
81
OnTouchEvent(const TouchEventInfo & info)82 void MenuPattern::OnTouchEvent(const TouchEventInfo& info)
83 {
84 if (IsMultiMenuOutside() || IsMultiMenu()) {
85 // not click hide menu for multi menu
86 return;
87 }
88 if (!options_.empty()) {
89 // not click hide menu for select and bindMenu default option
90 return;
91 }
92 auto touchType = info.GetTouches().front().GetTouchType();
93 if (touchType == TouchType::DOWN) {
94 lastTouchOffset_ = info.GetTouches().front().GetLocalLocation();
95 } else if (touchType == TouchType::UP) {
96 if (lastTouchOffset_.has_value() && info.GetTouches().front().GetLocalLocation() == lastTouchOffset_) {
97 HideMenu();
98 }
99 lastTouchOffset_.reset();
100 }
101 }
102
RegisterOnKeyEvent(const RefPtr<FocusHub> & focusHub)103 void MenuPattern::RegisterOnKeyEvent(const RefPtr<FocusHub>& focusHub)
104 {
105 auto onKeyEvent = [wp = WeakClaim(this)](const KeyEvent& event) -> bool {
106 auto pattern = wp.Upgrade();
107 CHECK_NULL_RETURN_NOLOG(pattern, false);
108 return pattern->OnKeyEvent(event);
109 };
110 focusHub->SetOnKeyEventInternal(std::move(onKeyEvent));
111 }
112
OnKeyEvent(const KeyEvent & event) const113 bool MenuPattern::OnKeyEvent(const KeyEvent& event) const
114 {
115 if (event.action != KeyAction::DOWN || IsMultiMenu()) {
116 return false;
117 }
118 if (event.code == KeyCode::KEY_ESCAPE) {
119 HideMenu();
120 return true;
121 }
122 if (event.code == KeyCode::KEY_DPAD_LEFT && IsSubMenu()) {
123 auto menuWrapper = GetMenuWrapper();
124 CHECK_NULL_RETURN(menuWrapper, true);
125 auto wrapperPattern = menuWrapper->GetPattern<MenuWrapperPattern>();
126 CHECK_NULL_RETURN(wrapperPattern, true);
127 wrapperPattern->HideSubMenu();
128 return true;
129 }
130 return false;
131 }
132
RemoveParentHoverStyle()133 void MenuPattern::RemoveParentHoverStyle()
134 {
135 if (!IsSubMenu()) {
136 return;
137 }
138 auto menuItemParent = GetParentMenuItem();
139 CHECK_NULL_VOID(menuItemParent);
140 auto menuItemPattern = menuItemParent->GetPattern<MenuItemPattern>();
141 CHECK_NULL_VOID(menuItemPattern);
142 menuItemPattern->SetIsSubMenuShowed(false);
143
144 auto pipeline = PipelineBase::GetCurrentContext();
145 CHECK_NULL_VOID(pipeline);
146 auto theme = pipeline->GetTheme<SelectTheme>();
147 CHECK_NULL_VOID(theme);
148 menuItemPattern->UpdateBackgroundColor(theme->GetBackgroundColor());
149 }
150
HideMenu() const151 void MenuPattern::HideMenu() const
152 {
153 if (IsContextMenu()) {
154 SubwindowManager::GetInstance()->HideMenuNG(targetId_);
155 return;
156 }
157 auto pipeline = PipelineContext::GetCurrentContext();
158 CHECK_NULL_VOID(pipeline);
159 auto overlayManager = pipeline->GetOverlayManager();
160 CHECK_NULL_VOID(overlayManager);
161 overlayManager->HideMenu(targetId_);
162 LOGI("MenuPattern closing menu %{public}d", targetId_);
163 }
164
GetMenuWrapper() const165 RefPtr<FrameNode> MenuPattern::GetMenuWrapper() const
166 {
167 auto host = GetHost();
168 CHECK_NULL_RETURN(host, nullptr);
169 auto parent = host->GetParent();
170 while (parent) {
171 if (parent->GetTag() == V2::MENU_WRAPPER_ETS_TAG) {
172 return AceType::DynamicCast<FrameNode>(parent);
173 }
174 parent = parent->GetParent();
175 }
176 return nullptr;
177 }
178
179 // judge children has component menu
IsMultiMenuOutside() const180 bool MenuPattern::IsMultiMenuOutside() const
181 {
182 auto host = GetHost();
183 CHECK_NULL_RETURN(host, false);
184 auto child = host->GetChildAtIndex(0);
185 while (child) {
186 // is component menu
187 if (child->GetTag() == V2::MENU_ETS_TAG) {
188 return AceType::DynamicCast<FrameNode>(child);
189 }
190 child = child->GetChildAtIndex(0);
191 }
192 return false;
193 }
194
195 // mount option on menu
MountOption(const RefPtr<FrameNode> & option)196 void MenuPattern::MountOption(const RefPtr<FrameNode>& option)
197 {
198 auto column = GetMenuColumn();
199 CHECK_NULL_VOID(column);
200 auto pattern = option->GetPattern<OptionPattern>();
201 CHECK_NULL_VOID(pattern);
202 pattern->SetMenu(WeakClaim(RawPtr(GetHost())));
203 AddOptionNode(option);
204 option->MountToParent(column);
205 }
206
207 // remove option from menu
RemoveOption()208 void MenuPattern::RemoveOption()
209 {
210 auto column = GetMenuColumn();
211 CHECK_NULL_VOID(column);
212 auto endOption = column->GetChildren().back();
213 CHECK_NULL_VOID(endOption);
214 column->RemoveChild(endOption);
215 PopOptionNode();
216 }
217
GetMenuColumn() const218 RefPtr<FrameNode> MenuPattern::GetMenuColumn() const
219 {
220 auto menu = GetHost();
221 CHECK_NULL_RETURN(menu, nullptr);
222 auto scroll = menu->GetChildren().front();
223 CHECK_NULL_RETURN(scroll, nullptr);
224 auto column = scroll->GetChildren().front();
225 return DynamicCast<FrameNode>(column);
226 }
227
DisableTabInMenu()228 void MenuPattern::DisableTabInMenu()
229 {
230 if (IsMultiMenu()) {
231 // multi menu not has scroll and column
232 return;
233 }
234 // disable tab in menu
235 auto column = GetMenuColumn();
236 CHECK_NULL_VOID(column);
237 auto columnFocusHub = column->GetOrCreateFocusHub();
238 CHECK_NULL_VOID(columnFocusHub);
239
240 auto onKeyEvent = [](const KeyEvent& event) -> bool {
241 if (event.action != KeyAction::DOWN) {
242 return false;
243 }
244 return event.code == KeyCode::KEY_TAB;
245 };
246 columnFocusHub->SetOnKeyEventInternal(std::move(onKeyEvent));
247 }
248 } // namespace OHOS::Ace::NG
249