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/wrapper/menu_wrapper_pattern.h"
17
18 #include "base/utils/utils.h"
19 #include "core/common/container.h"
20 #include "core/components/common/properties/shadow_config.h"
21 #include "core/components/select/select_theme.h"
22 #include "core/components_ng/event/click_event.h"
23 #include "core/event/touch_event.h"
24 #include "core/pipeline_ng/pipeline_context.h"
25
26 namespace OHOS::Ace::NG {
HideMenu(const RefPtr<FrameNode> & menu)27 void MenuWrapperPattern::HideMenu(const RefPtr<FrameNode>& menu)
28 {
29 if (GetHost()->GetTag() == V2::SELECT_OVERLAY_ETS_TAG) {
30 return;
31 }
32 isHided_ = true;
33
34 auto menuPattern = menu->GetPattern<MenuPattern>();
35 CHECK_NULL_VOID(menuPattern);
36 menuPattern->HideMenu();
37 CallMenuStateChangeCallback("false");
38 }
39
OnAttachToFrameNode()40 void MenuWrapperPattern::OnAttachToFrameNode()
41 {
42 RegisterOnTouch();
43 }
44
OnModifyDone()45 void MenuWrapperPattern::OnModifyDone()
46 {
47 Pattern::OnModifyDone();
48
49 isHided_ = false;
50 }
51
52 // close subMenu when mouse move outside
HandleMouseEvent(const MouseInfo & info,RefPtr<MenuItemPattern> & menuItemPattern)53 void MenuWrapperPattern::HandleMouseEvent(const MouseInfo& info, RefPtr<MenuItemPattern>& menuItemPattern)
54 {
55 auto host = GetHost();
56 CHECK_NULL_VOID(host);
57 auto subMenu = host->GetChildren().back();
58 if (host->GetChildren().size() <= 1) {
59 return;
60 }
61 auto subMenuNode = DynamicCast<FrameNode>(subMenu);
62 CHECK_NULL_VOID(subMenuNode);
63 auto subMenuPattern = subMenuNode->GetPattern<MenuPattern>();
64 CHECK_NULL_VOID(subMenuPattern);
65 auto currentHoverMenuItem = subMenuPattern->GetParentMenuItem();
66 CHECK_NULL_VOID(currentHoverMenuItem);
67
68 auto menuItemNode = menuItemPattern->GetHost();
69 CHECK_NULL_VOID(menuItemNode);
70 if (currentHoverMenuItem->GetId() != menuItemNode->GetId()) {
71 return;
72 }
73 const auto& mousePosition = info.GetGlobalLocation();
74 if (!menuItemPattern->IsInHoverRegions(mousePosition.GetX(), mousePosition.GetY()) &&
75 menuItemPattern->IsSubMenuShowed()) {
76 LOGI("MenuWrapperPattern Hide SubMenu");
77 HideSubMenu();
78 menuItemPattern->SetIsSubMenuShowed(false);
79 menuItemPattern->ClearHoverRegions();
80 menuItemPattern->ResetWrapperMouseEvent();
81 }
82 }
83
HideMenu()84 void MenuWrapperPattern::HideMenu()
85 {
86 auto host = GetHost();
87 CHECK_NULL_VOID(host);
88 auto menuNode = DynamicCast<FrameNode>(host->GetChildAtIndex(0));
89 CHECK_NULL_VOID(menuNode);
90 HideMenu(menuNode);
91 }
92
HideSubMenu()93 void MenuWrapperPattern::HideSubMenu()
94 {
95 auto host = GetHost();
96 CHECK_NULL_VOID(host);
97 if (host->GetChildren().size() <= 1) {
98 // sub menu not show
99 return;
100 }
101 auto subMenu = host->GetChildren().back();
102 auto iter = host->GetChildren().begin();
103 int32_t focusNodeId = 2;
104 std::advance(iter, host->GetChildren().size() - focusNodeId);
105 auto focusMenu = *iter;
106 if (focusMenu) {
107 auto menuHub = DynamicCast<FrameNode>(focusMenu);
108 CHECK_NULL_VOID(menuHub);
109 // SelectOverlay's custom menu does not need to be focused.
110 auto isCustomMenu = IsSelectOverlayCustomMenu(menuHub);
111 if (!isCustomMenu) {
112 auto focusHub = menuHub->GetFocusHub();
113 CHECK_NULL_VOID(focusHub);
114 focusHub->SetParentFocusable(true);
115 focusHub->RequestFocusImmediately();
116 }
117 }
118 host->RemoveChild(subMenu);
119 auto menuPattern = DynamicCast<FrameNode>(subMenu)->GetPattern<MenuPattern>();
120 if (menuPattern) {
121 menuPattern->RemoveParentHoverStyle();
122 auto frameNode = FrameNode::GetFrameNode(menuPattern->GetTargetTag(), menuPattern->GetTargetId());
123 CHECK_NULL_VOID(frameNode);
124 auto menuItem = frameNode->GetPattern<MenuItemPattern>();
125 if (menuItem) {
126 menuItem->SetIsSubMenuShowed(false);
127 }
128 }
129 host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF_AND_CHILD);
130 }
131
RegisterOnTouch()132 void MenuWrapperPattern::RegisterOnTouch()
133 {
134 // if already initialized touch event
135 CHECK_NULL_VOID(!onTouch_);
136 auto host = GetHost();
137 CHECK_NULL_VOID(host);
138 auto gesture = host->GetOrCreateGestureEventHub();
139 CHECK_NULL_VOID(gesture);
140 // hide menu when touched outside the menu region
141 auto touchTask = [weak = WeakClaim(this)](const TouchEventInfo& info) {
142 auto pattern = weak.Upgrade();
143 CHECK_NULL_VOID(pattern);
144 pattern->OnTouchEvent(info);
145 };
146 onTouch_ = MakeRefPtr<TouchEventImpl>(std::move(touchTask));
147 gesture->AddTouchEvent(onTouch_);
148 }
149
OnTouchEvent(const TouchEventInfo & info)150 void MenuWrapperPattern::OnTouchEvent(const TouchEventInfo& info)
151 {
152 CHECK_NULL_VOID(!info.GetTouches().empty());
153 auto touch = info.GetTouches().front();
154 // filter out other touch types
155 if (touch.GetTouchType() != TouchType::DOWN) {
156 return;
157 }
158 if (IsHided()) {
159 return;
160 }
161 auto host = GetHost();
162 CHECK_NULL_VOID(host);
163
164 auto position = OffsetF(
165 static_cast<float>(touch.GetGlobalLocation().GetX()), static_cast<float>(touch.GetGlobalLocation().GetY()));
166 position -= host->GetPaintRectOffset();
167 auto children = host->GetChildren();
168 for (auto child = children.rbegin(); child != children.rend(); ++child) {
169 // get menu frame node (child of menu wrapper)
170 auto menuNode = DynamicCast<FrameNode>(*child);
171 CHECK_NULL_VOID(menuNode);
172 // get menuNode's touch region
173 auto menuZone = menuNode->GetGeometryNode()->GetFrameRect();
174 if (menuZone.IsInRegion(PointF(position.GetX(), position.GetY()))) {
175 return;
176 }
177 // if DOWN-touched outside the menu region, then hide menu
178 auto menuPattern = menuNode->GetPattern<MenuPattern>();
179 CHECK_NULL_VOID(menuPattern);
180 if (menuPattern->IsSubMenu() || menuPattern->IsSelectOverlaySubMenu()) {
181 HideSubMenu();
182 } else {
183 HideMenu(menuNode);
184 }
185 }
186 }
187
CheckAndShowAnimation()188 void MenuWrapperPattern::CheckAndShowAnimation()
189 {
190 if (isFirstShow_) {
191 // only start animation when menu wrapper mount on.
192 StartShowAnimation();
193 isFirstShow_ = false;
194 }
195 }
196
OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper> & dirty,const DirtySwapConfig & config)197 bool MenuWrapperPattern::OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper>& dirty, const DirtySwapConfig& config)
198 {
199 auto pipeline = PipelineBase::GetCurrentContext();
200 CHECK_NULL_RETURN(pipeline, false);
201 auto theme = pipeline->GetTheme<SelectTheme>();
202 CHECK_NULL_RETURN(theme, false);
203 auto expandDisplay = theme->GetExpandDisplay();
204 auto host = GetHost();
205 CHECK_NULL_RETURN(host, false);
206 auto menuNode = DynamicCast<FrameNode>(host->GetChildAtIndex(0));
207 CHECK_NULL_RETURN(menuNode, false);
208 auto menuPattern = AceType::DynamicCast<MenuPattern>(menuNode->GetPattern());
209 CHECK_NULL_RETURN(menuPattern, false);
210 // copy menu pattern properties to rootMenu
211 auto layoutProperty = menuPattern->GetLayoutProperty<MenuLayoutProperty>();
212 CHECK_NULL_RETURN(layoutProperty, false);
213 isShowInSubWindow_ = layoutProperty->GetShowInSubWindowValue(true);
214 if ((IsContextMenu() && !IsHided()) || ((expandDisplay && isShowInSubWindow_) && !IsHided())) {
215 SetHotAreas(dirty);
216 }
217 CheckAndShowAnimation();
218 return false;
219 }
220
SetHotAreas(const RefPtr<LayoutWrapper> & layoutWrapper)221 void MenuWrapperPattern::SetHotAreas(const RefPtr<LayoutWrapper>& layoutWrapper)
222 {
223 auto pipeline = PipelineBase::GetCurrentContext();
224 CHECK_NULL_VOID(pipeline);
225 auto theme = pipeline->GetTheme<SelectTheme>();
226 CHECK_NULL_VOID(theme);
227 auto expandDisplay = theme->GetExpandDisplay();
228 if ((layoutWrapper->GetAllChildrenWithBuild().empty() || !IsContextMenu()) &&
229 !(expandDisplay && isShowInSubWindow_)) {
230 return;
231 }
232 auto layoutProps = layoutWrapper->GetLayoutProperty();
233 CHECK_NULL_VOID(layoutProps);
234 float safeAreaInsetsLeft = 0.0f;
235 float safeAreaInsetsTop = 0.0f;
236 auto&& safeAreaInsets = layoutProps->GetSafeAreaInsets();
237 if (safeAreaInsets) {
238 safeAreaInsetsLeft = static_cast<float>(safeAreaInsets->left_.end);
239 safeAreaInsetsTop = static_cast<float>(safeAreaInsets->top_.end);
240 }
241 std::vector<Rect> rects;
242 for (const auto& child : layoutWrapper->GetAllChildrenWithBuild()) {
243 auto frameRect = child->GetGeometryNode()->GetFrameRect();
244 // rect is relative to window
245 auto rect = Rect(frameRect.GetX() + safeAreaInsetsLeft, frameRect.GetY() + safeAreaInsetsTop, frameRect.Width(),
246 frameRect.Height());
247
248 rects.emplace_back(rect);
249 }
250 SubwindowManager::GetInstance()->SetHotAreas(rects, GetHost()->GetId(), GetContainerId());
251 }
252
StartShowAnimation()253 void MenuWrapperPattern::StartShowAnimation()
254 {
255 auto host = GetHost();
256 CHECK_NULL_VOID(host);
257 auto context = host->GetRenderContext();
258 CHECK_NULL_VOID(context);
259 if (GetPreviewMode() == MenuPreviewMode::NONE) {
260 context->UpdateOffset(GetAnimationOffset());
261 context->UpdateOpacity(0.0);
262 }
263
264 AnimationUtils::Animate(
265 animationOption_,
266 [context, weak = WeakClaim(this)]() {
267 if (context) {
268 auto pattern = weak.Upgrade();
269 CHECK_NULL_VOID(pattern);
270 if (pattern->GetPreviewMode() == MenuPreviewMode::NONE) {
271 context->UpdateOffset(OffsetT<Dimension>());
272 context->UpdateOpacity(1.0);
273 }
274 }
275 },
276 animationOption_.GetOnFinishEvent());
277 }
278
SetAniamtinOption(const AnimationOption & animationOption)279 void MenuWrapperPattern::SetAniamtinOption(const AnimationOption& animationOption)
280 {
281 animationOption_.SetCurve(animationOption.GetCurve());
282 animationOption_.SetDuration(animationOption.GetDuration());
283 animationOption_.SetFillMode(animationOption.GetFillMode());
284 animationOption_.SetOnFinishEvent(animationOption.GetOnFinishEvent());
285 }
286
GetAnimationOffset()287 OffsetT<Dimension> MenuWrapperPattern::GetAnimationOffset()
288 {
289 OffsetT<Dimension> offset;
290
291 auto pipeline = PipelineBase::GetCurrentContext();
292 CHECK_NULL_RETURN(pipeline, offset);
293 auto theme = pipeline->GetTheme<SelectTheme>();
294 CHECK_NULL_RETURN(theme, offset);
295 auto animationOffset = theme->GetMenuAnimationOffset();
296
297 switch (menuPlacement_) {
298 case Placement::LEFT:
299 case Placement::LEFT_TOP:
300 case Placement::LEFT_BOTTOM:
301 offset.SetX(animationOffset);
302 break;
303 case Placement::RIGHT:
304 case Placement::RIGHT_TOP:
305 case Placement::RIGHT_BOTTOM:
306 offset.SetX(-animationOffset);
307 break;
308 case Placement::TOP:
309 case Placement::TOP_LEFT:
310 case Placement::TOP_RIGHT:
311 offset.SetY(animationOffset);
312 break;
313 default:
314 offset.SetY(-animationOffset);
315 break;
316 }
317 return offset;
318 }
319
IsSelectOverlayCustomMenu(const RefPtr<FrameNode> & menu) const320 bool MenuWrapperPattern::IsSelectOverlayCustomMenu(const RefPtr<FrameNode>& menu) const
321 {
322 auto menuPattern = menu->GetPattern<MenuPattern>();
323 CHECK_NULL_RETURN(menuPattern, false);
324 return menuPattern->IsSelectOverlayCustomMenu();
325 }
326 } // namespace OHOS::Ace::NG
327