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