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