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