1 /*
2 * Copyright (c) 2021-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/stack/stack_element.h"
17
18 #include "core/components/bubble/render_bubble.h"
19 #include "core/components/dialog/dialog_component.h"
20 #include "core/components/dialog/dialog_element.h"
21 #include "core/components/dialog_tween/render_dialog_tween.h"
22 #include "core/components/drop_filter/drop_filter_element.h"
23 #include "core/components/picker/picker_base_element.h"
24 #include "core/components/select_popup/select_popup_element.h"
25 #include "core/components/text_overlay/text_overlay_element.h"
26 #include "core/components_v2/inspector/inspector_composed_component.h"
27 #include "core/components_v2/inspector/inspector_composed_element.h"
28
29 namespace OHOS::Ace {
30
PushInstant(const RefPtr<Component> & newComponent,bool disableTouchEvent)31 void StackElement::PushInstant(const RefPtr<Component>& newComponent, bool disableTouchEvent)
32 {
33 PopupComponentInfo pushComponentInfo = { -1, "-1", Operation::DIRECT_PUSH, newComponent };
34 popupComponentInfos_.emplace_back(pushComponentInfo);
35 disableTouchEvent_ = disableTouchEvent;
36 PerformBuild();
37 }
38
PushComponent(const RefPtr<Component> & newComponent,bool disableTouchEvent)39 void StackElement::PushComponent(const RefPtr<Component>& newComponent, bool disableTouchEvent)
40 {
41 PopupComponentInfo pushComponentInfo = { -1, "-1", Operation::DIRECT_PUSH, newComponent };
42 popupComponentInfos_.emplace_back(pushComponentInfo);
43 disableTouchEvent_ = disableTouchEvent;
44 MarkDirty();
45 }
46
PopComponent()47 void StackElement::PopComponent()
48 {
49 PopupComponentInfo popComponentInfo = { -1, "-1", Operation::DIRECT_POP, nullptr };
50 popupComponentInfos_.emplace_back(popComponentInfo);
51 MarkDirty();
52 }
53
PushToastComponent(const RefPtr<Component> & newComponent,int32_t toastId)54 void StackElement::PushToastComponent(const RefPtr<Component>& newComponent, int32_t toastId)
55 {
56 PopupComponentInfo pushComponentInfo = { toastId, "-1", Operation::TOAST_PUSH, newComponent };
57 popupComponentInfos_.emplace_back(pushComponentInfo);
58 MarkDirty();
59 }
60
PopToastComponent(int32_t toastPopId)61 void StackElement::PopToastComponent(int32_t toastPopId)
62 {
63 PopupComponentInfo popComponentInfo = { toastPopId, "-1", Operation::TOAST_POP, nullptr };
64 popupComponentInfos_.emplace_back(popComponentInfo);
65 MarkDirty();
66 }
67
PushPanel(const RefPtr<Component> & newComponent,bool disableTouchEvent)68 void StackElement::PushPanel(const RefPtr<Component>& newComponent, bool disableTouchEvent)
69 {
70 PopupComponentInfo pushComponentInfo = { -1, "-1", Operation::PANEL_PUSH, newComponent };
71 popupComponentInfos_.emplace_back(pushComponentInfo);
72 disableTouchEvent_ = disableTouchEvent;
73 MarkDirty();
74 }
75
HasOverlayChild()76 bool StackElement::HasOverlayChild()
77 {
78 bool hasMultiChild = children_.size() > 1;
79 // avoid pop toast to protect page not empty.
80 for (const auto& child: children_) {
81 if (!std::none_of(toastStack_.begin(), toastStack_.end(),
82 [child](const ToastInfo& toast) { return toast.child == child; })) {
83 return false;
84 }
85 }
86 return hasMultiChild;
87 }
88
PopPanel()89 void StackElement::PopPanel()
90 {
91 PopDialog();
92 }
93
PushDialog(const RefPtr<Component> & newComponent,bool disableTouchEvent)94 bool StackElement::PushDialog(const RefPtr<Component>& newComponent, bool disableTouchEvent)
95 {
96 auto context = context_.Upgrade();
97 if (context) {
98 AccessibilityEvent stackEvent;
99 stackEvent.eventType = "ejectdismiss";
100 context->SendEventToAccessibility(stackEvent);
101 }
102 PopupComponentInfo pushComponentInfo = { -1, "-1", Operation::DIALOG_PUSH, newComponent };
103 CreateInspectorComponent(pushComponentInfo);
104
105 popupComponentInfos_.emplace_back(pushComponentInfo);
106 disableTouchEvent_ = disableTouchEvent;
107 MarkDirty();
108 return true;
109 }
110
PopDialog(int32_t id)111 bool StackElement::PopDialog(int32_t id)
112 {
113 LOGI("StackElement::PopDialog id is %{public}d", id);
114 auto context = context_.Upgrade();
115 if (context) {
116 AccessibilityEvent stackEvent;
117 stackEvent.eventType = "ejectdismiss";
118 context->SendEventToAccessibility(stackEvent);
119 }
120 PopupComponentInfo popComponentInfo = { id, "-1", Operation::DIALOG_POP, nullptr };
121 popupComponentInfos_.emplace_back(popComponentInfo);
122 MarkDirty();
123 return true;
124 }
125
PopTextOverlay()126 void StackElement::PopTextOverlay()
127 {
128 PopupComponentInfo popComponentInfo = { -1, "-1", Operation::TEXT_OVERLAY_POP, nullptr };
129 popupComponentInfos_.emplace_back(popComponentInfo);
130 MarkDirty();
131 }
132
PopPopup(const ComposeId & id)133 void StackElement::PopPopup(const ComposeId& id)
134 {
135 PopupComponentInfo popComponentInfo = { -1, id, Operation::POPUP_POP, nullptr };
136 popupComponentInfos_.emplace_back(popComponentInfo);
137 MarkDirty();
138 }
139
PopMenu()140 void StackElement::PopMenu()
141 {
142 PopupComponentInfo popComponentInfo = { -1, "-1", Operation::MENU_POP, nullptr };
143 popupComponentInfos_.emplace_back(popComponentInfo);
144 MarkDirty();
145 }
146
PopVideo()147 void StackElement::PopVideo()
148 {
149 PopupComponentInfo popComponentInfo = { -1, "-1", Operation::VIDEO_POP, nullptr };
150 popupComponentInfos_.emplace_back(popComponentInfo);
151 MarkDirty();
152 }
153
PopInstant()154 void StackElement::PopInstant()
155 {
156 auto child = children_.end();
157 if (child != children_.begin()) {
158 child--;
159 UpdateChild(*child, nullptr);
160 }
161 EnableTouchEventAndRequestFocus();
162 }
163
PerformBuild()164 void StackElement::PerformBuild()
165 {
166 // rebuild popup component
167 for (auto& info : popupComponentInfos_) {
168 PerformPopupChild(info);
169 }
170
171 ComponentGroupElement::PerformBuild();
172 popupComponentInfos_.clear();
173 }
174
PerformPopupChild(PopupComponentInfo & popupComponentInfo)175 void StackElement::PerformPopupChild(PopupComponentInfo& popupComponentInfo)
176 {
177 switch (popupComponentInfo.operation) {
178 case Operation::TOAST_PUSH:
179 PerformPushToast(popupComponentInfo);
180 break;
181 case Operation::DIRECT_PUSH:
182 case Operation::DIALOG_PUSH:
183 case Operation::PANEL_PUSH:
184 PerformPushChild(popupComponentInfo);
185 break;
186 case Operation::TOAST_POP:
187 PerformPopToastById(popupComponentInfo.popId);
188 break;
189 case Operation::DIALOG_POP:
190 PerformPopDialog(popupComponentInfo.popId);
191 break;
192 case Operation::TEXT_OVERLAY_POP:
193 PerformPopTextOverlay();
194 break;
195 case Operation::POPUP_POP:
196 PerformPopPopup(popupComponentInfo.id);
197 break;
198 case Operation::MENU_POP:
199 PerformPopMenu();
200 break;
201 case Operation::VIDEO_POP:
202 PerformPopVideo();
203 break;
204 case Operation::DIRECT_POP:
205 PerformDirectPop();
206 break;
207 default:
208 break;
209 }
210 }
211
PerformPushToast(PopupComponentInfo & popupComponentInfo)212 void StackElement::PerformPushToast(PopupComponentInfo& popupComponentInfo)
213 {
214 if (!popupComponentInfo.IsValid() || popupComponentInfo.operation != Operation::TOAST_PUSH) {
215 return;
216 }
217 PerformPopToast();
218 // store toast element
219 RefPtr<Element> toastElement = UpdateChild(nullptr, popupComponentInfo.component);
220 if (toastElement) {
221 ToastInfo toastInfo = { popupComponentInfo.popId, toastElement };
222 toastStack_.emplace_back(toastInfo);
223 }
224 popupComponentInfo.component = nullptr;
225 }
226
PerformPushChild(PopupComponentInfo & popupComponentInfo)227 void StackElement::PerformPushChild(PopupComponentInfo& popupComponentInfo)
228 {
229 if (!popupComponentInfo.IsValid()) {
230 return;
231 }
232 // store toast element
233 if (!UpdateChild(nullptr, popupComponentInfo.component)) {
234 return;
235 }
236 for (auto child = (++children_.rbegin()); child != children_.rend(); ++child) {
237 auto renderNode = (*child)->GetRenderNode();
238 if (renderNode) {
239 renderNode->SetDisableTouchEvent(disableTouchEvent_);
240 }
241 }
242 auto renderNode = GetRenderNode();
243 if (!renderNode) {
244 return;
245 }
246 renderNode->MarkNeedLayout();
247 if (isPageElement()) {
248 if (!focusNodes_.empty() && focusNodes_.back()->IsFocusable()) {
249 focusNodes_.back()->RequestFocus();
250 }
251 }
252 popupComponentInfo.component = nullptr;
253 }
254
PerformPopToastById(int32_t toastId)255 void StackElement::PerformPopToastById(int32_t toastId)
256 {
257 if (toastStack_.empty()) {
258 return;
259 }
260 for (auto iter = toastStack_.end() - 1; iter >= toastStack_.begin(); --iter) {
261 if (iter->toastId == toastId) {
262 UpdateChild(iter->child, nullptr);
263 toastStack_.erase(iter);
264 break;
265 }
266 }
267 EnableTouchEventAndRequestFocus();
268 }
269
PerformPopToast()270 void StackElement::PerformPopToast()
271 {
272 if (!toastStack_.empty()) {
273 UpdateChild(toastStack_.back().child, nullptr);
274 toastStack_.pop_back();
275 }
276 }
277
PerformPopDialog(int32_t id)278 void StackElement::PerformPopDialog(int32_t id)
279 {
280 if (id >= 0) {
281 PerformPopDialogById(id);
282 return;
283 }
284
285 bool hasDialog = std::any_of(children_.begin(), children_.end(), [](const RefPtr<Element>& child) {
286 return AceType::InstanceOf<V2::InspectorComposedElement>(child) || AceType::InstanceOf<DialogElement>(child) ||
287 AceType::InstanceOf<PickerBaseElement>(child) || AceType::InstanceOf<DropFilterElement>(child);
288 });
289 if (!hasDialog) {
290 EnableTouchEventAndRequestFocus();
291 return;
292 }
293 for (auto iter = children_.rbegin(); iter != children_.rend(); ++iter) {
294 if (AceType::InstanceOf<V2::InspectorComposedElement>(*iter) || AceType::InstanceOf<DialogElement>(*iter) ||
295 AceType::InstanceOf<PickerBaseElement>(*iter) || AceType::InstanceOf<DropFilterElement>(*iter)) {
296 UpdateChild(*iter, nullptr);
297 break;
298 }
299 }
300 EnableTouchEventAndRequestFocus();
301 }
302
PerformPopDialogById(int32_t id)303 void StackElement::PerformPopDialogById(int32_t id)
304 {
305 bool hasDialog = std::any_of(children_.begin(), children_.end(), [](const RefPtr<Element>& child) {
306 return AceType::InstanceOf<V2::InspectorComposedElement>(child) || AceType::InstanceOf<DialogElement>(child);
307 });
308 if (!hasDialog) {
309 EnableTouchEventAndRequestFocus();
310 return;
311 }
312 for (auto iter = children_.rbegin(); iter != children_.rend(); ++iter) {
313 auto dialogElement = AceType::DynamicCast<DialogElement>(*iter);
314 if (dialogElement && dialogElement->GetDialogId() == id) {
315 UpdateChild(*iter, nullptr);
316 break;
317 }
318 auto inspectorComposedElement = AceType::DynamicCast<V2::InspectorComposedElement>(*iter);
319 if (inspectorComposedElement) {
320 dialogElement = inspectorComposedElement->GetContentElement<DialogElement>(DialogElement::TypeId());
321 if (dialogElement && dialogElement->GetDialogId() == id) {
322 UpdateChild(inspectorComposedElement, nullptr);
323 break;
324 }
325 }
326 }
327 EnableTouchEventAndRequestFocus();
328 }
329
PerformPopTextOverlay()330 void StackElement::PerformPopTextOverlay()
331 {
332 for (auto iter = children_.rbegin(); iter != children_.rend(); ++iter) {
333 if (AceType::InstanceOf<TextOverlayElement>(*iter)) {
334 UpdateChild(*iter, nullptr);
335 break;
336 }
337 }
338 if (IsFocusable()) {
339 RequestFocus();
340 }
341 EnableTouchEventAndRequestFocus();
342 }
343
PerformPopPopup(const ComposeId & id)344 void StackElement::PerformPopPopup(const ComposeId& id)
345 {
346 for (auto iter = children_.rbegin(); iter != children_.rend(); ++iter) {
347 auto child = DynamicCast<TweenElement>(*iter);
348 if (child && child->GetId() == id) {
349 auto themeManager = GetThemeManager();
350 if (!themeManager || !themeManager->GetTheme<PopupTheme>()) {
351 LOGE("themeManager or get theme is null!");
352 return;
353 }
354
355 auto context = context_.Upgrade();
356 if (context && !context->GetOnShow()) {
357 UpdateChild(child, nullptr);
358 break;
359 }
360
361 auto theme = themeManager->GetTheme<PopupTheme>();
362 auto hideAlphaAnimation = AceType::MakeRefPtr<CurveAnimation<float>>(1.0f, 0.0f, Curves::FAST_OUT_SLOW_IN);
363 TweenOption hideOption;
364 hideOption.SetDuration(theme->GetHideTime());
365 hideOption.SetOpacityAnimation(hideAlphaAnimation);
366
367 auto animator = child->GetController();
368 animator->ClearAllListeners();
369 child->SetOption(hideOption);
370 child->ApplyOptions();
371 child->ApplyKeyframes();
372 animator->AddStopListener(
373 [weakStack = AceType::WeakClaim(this), weakChild = AceType::WeakClaim(AceType::RawPtr(child))] {
374 auto lastStack = weakStack.Upgrade();
375 auto child = weakChild.Upgrade();
376 if (lastStack && child) {
377 lastStack->UpdateChild(child, nullptr);
378 }
379 });
380 animator->Play();
381 break;
382 }
383 }
384 if (IsFocusable()) {
385 RequestFocus();
386 }
387 EnableTouchEventAndRequestFocus();
388 }
389
PerformPopMenu()390 void StackElement::PerformPopMenu()
391 {
392 for (auto iter = children_.rbegin(); iter != children_.rend(); ++iter) {
393 if (AceType::InstanceOf<SelectPopupElement>(*iter)) {
394 UpdateChild(*iter, nullptr);
395 break;
396 }
397 }
398 if (IsFocusable()) {
399 RequestFocus();
400 }
401 EnableTouchEventAndRequestFocus();
402 }
403
PerformPopVideo()404 void StackElement::PerformPopVideo()
405 {
406 for (auto iter = children_.rbegin(); iter != children_.rend(); ++iter) {
407 auto element = DynamicCast<ComposedElement>(*iter);
408 if (element && StringUtils::EndWith(element->GetName(), "fullscreen")) {
409 UpdateChild(*iter, nullptr);
410 break;
411 }
412 }
413 EnableTouchEventAndRequestFocus();
414 }
415
PerformDirectPop()416 void StackElement::PerformDirectPop()
417 {
418 auto child = children_.end();
419 while (child != children_.begin()) {
420 child--;
421 bool isNotToast = std::none_of(
422 toastStack_.begin(), toastStack_.end(), [child](const ToastInfo& toast) { return toast.child == *child; });
423 if (isNotToast) {
424 UpdateChild(*child, nullptr);
425 break;
426 }
427 }
428 EnableTouchEventAndRequestFocus();
429 }
430
RequestNextFocus(bool vertical,bool reverse,const Rect & rect)431 bool StackElement::RequestNextFocus(bool vertical, bool reverse, const Rect& rect)
432 {
433 if (!isPageElement()) {
434 return GoToNextFocus(reverse, rect);
435 }
436 return false;
437 }
438
OnFocus()439 void StackElement::OnFocus()
440 {
441 if (!isPageElement()) {
442 FocusGroup::OnFocus();
443 return;
444 }
445 if (focusNodes_.empty()) {
446 itLastFocusNode_ = focusNodes_.end();
447 return;
448 }
449 // Only focus on the top focusable child.
450 itLastFocusNode_ = focusNodes_.end();
451 while (itLastFocusNode_ != focusNodes_.begin()) {
452 --itLastFocusNode_;
453 (*itLastFocusNode_)->SetParentFocusable(IsParentFocusable());
454 if ((*itLastFocusNode_)->RequestFocusImmediately()) {
455 FocusNode::OnFocus();
456 break;
457 }
458 }
459
460 if (!IsCurrentFocus()) {
461 itLastFocusNode_ = focusNodes_.end();
462 } else {
463 // lower focusable node can not be focus.
464 auto iter = itLastFocusNode_;
465 while (iter != focusNodes_.begin()) {
466 --iter;
467 (*iter)->SetParentFocusable(false);
468 }
469 }
470 }
471
OnBlur()472 void StackElement::OnBlur()
473 {
474 FocusGroup::OnBlur();
475 if (!isPageElement()) {
476 return;
477 }
478
479 auto iter = focusNodes_.end();
480 while (iter != focusNodes_.begin()) {
481 --iter;
482 (*iter)->SetParentFocusable(IsParentFocusable());
483 }
484 }
485
EnableTouchEventAndRequestFocus()486 void StackElement::EnableTouchEventAndRequestFocus()
487 {
488 for (auto& child : children_) {
489 auto renderNode = child->GetRenderNode();
490 if (renderNode) {
491 renderNode->SetDisableTouchEvent(false);
492 }
493 }
494 if (IsFocusable()) {
495 RequestFocus();
496 }
497 }
498
CreateInspectorComponent(PopupComponentInfo & componentInfo) const499 void StackElement::CreateInspectorComponent(PopupComponentInfo& componentInfo) const
500 {
501 auto dialog = AceType::DynamicCast<DialogComponent>(componentInfo.component);
502 if (!dialog) {
503 return;
504 }
505 auto inspectorTag = dialog->GetInspectorTag();
506 if (V2::InspectorComposedComponent::HasInspectorFinished(inspectorTag)) {
507 auto composedComponent = AceType::MakeRefPtr<V2::InspectorComposedComponent>(
508 V2::InspectorComposedComponent::GenerateId(), inspectorTag);
509 composedComponent->SetChild(componentInfo.component);
510 componentInfo.component = composedComponent;
511 }
512 }
513
PopPopupIfExist() const514 bool StackElement::PopPopupIfExist() const
515 {
516 auto bubbleElement = GetBubble(GetLastChild());
517 if (!bubbleElement) {
518 return false;
519 }
520 auto renderBubble = DynamicCast<RenderBubble>(bubbleElement->GetRenderNode());
521 if (!renderBubble) {
522 return false;
523 }
524 renderBubble->PopBubble();
525 return true;
526 }
527
GetBubble(const RefPtr<Element> & element) const528 RefPtr<BubbleElement> StackElement::GetBubble(const RefPtr<Element>& element) const
529 {
530 if (!element) {
531 return nullptr;
532 }
533
534 auto bubble = DynamicCast<BubbleElement>(element);
535 if (bubble) {
536 return bubble;
537 }
538
539 return GetBubble(element->GetFirstChild());
540 }
541
PopDialogIfExist() const542 bool StackElement::PopDialogIfExist() const
543 {
544 auto dialogTweenElement = GetDialog(GetLastChild());
545 if (!dialogTweenElement) {
546 return false;
547 }
548 auto renderDialogTween = DynamicCast<RenderDialogTween>(dialogTweenElement->GetRenderNode());
549 if (!renderDialogTween) {
550 return false;
551 }
552 renderDialogTween->PopDialog();
553 return true;
554 }
555
GetDialog(const RefPtr<Element> & element) const556 RefPtr<DialogTweenElement> StackElement::GetDialog(const RefPtr<Element>& element) const
557 {
558 if (!element) {
559 return nullptr;
560 }
561
562 auto dialog = DynamicCast<DialogTweenElement>(element);
563 if (dialog) {
564 return dialog;
565 }
566
567 return GetDialog(element->GetFirstChild());
568 }
569
570 } // namespace OHOS::Ace
571