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 LOGD("Don't need pop popup component");
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 LOGD("PerformPopToast");
280 if (!toastStack_.empty()) {
281 UpdateChild(toastStack_.back().child, nullptr);
282 toastStack_.pop_back();
283 }
284 }
285
PerformPopDialog(int32_t id)286 void StackElement::PerformPopDialog(int32_t id)
287 {
288 if (id >= 0) {
289 PerformPopDialogById(id);
290 return;
291 }
292
293 bool hasDialog = std::any_of(children_.begin(), children_.end(), [](const RefPtr<Element>& child) {
294 return AceType::InstanceOf<V2::InspectorComposedElement>(child) || AceType::InstanceOf<DialogElement>(child) ||
295 AceType::InstanceOf<PickerBaseElement>(child) || AceType::InstanceOf<DropFilterElement>(child);
296 });
297 if (!hasDialog) {
298 EnableTouchEventAndRequestFocus();
299 return;
300 }
301 for (auto iter = children_.rbegin(); iter != children_.rend(); ++iter) {
302 if (AceType::InstanceOf<V2::InspectorComposedElement>(*iter) || AceType::InstanceOf<DialogElement>(*iter) ||
303 AceType::InstanceOf<PickerBaseElement>(*iter) || AceType::InstanceOf<DropFilterElement>(*iter)) {
304 UpdateChild(*iter, nullptr);
305 break;
306 }
307 }
308 EnableTouchEventAndRequestFocus();
309 }
310
PerformPopDialogById(int32_t id)311 void StackElement::PerformPopDialogById(int32_t id)
312 {
313 bool hasDialog = std::any_of(children_.begin(), children_.end(), [](const RefPtr<Element>& child) {
314 return AceType::InstanceOf<V2::InspectorComposedElement>(child) || AceType::InstanceOf<DialogElement>(child);
315 });
316 if (!hasDialog) {
317 EnableTouchEventAndRequestFocus();
318 return;
319 }
320 for (auto iter = children_.rbegin(); iter != children_.rend(); ++iter) {
321 auto dialogElement = AceType::DynamicCast<DialogElement>(*iter);
322 if (dialogElement && dialogElement->GetDialogId() == id) {
323 UpdateChild(*iter, nullptr);
324 break;
325 }
326 auto inspectorComposedElement = AceType::DynamicCast<V2::InspectorComposedElement>(*iter);
327 if (inspectorComposedElement) {
328 dialogElement = inspectorComposedElement->GetContentElement<DialogElement>(DialogElement::TypeId());
329 if (dialogElement && dialogElement->GetDialogId() == id) {
330 UpdateChild(inspectorComposedElement, nullptr);
331 break;
332 }
333 }
334 }
335 EnableTouchEventAndRequestFocus();
336 }
337
PerformPopTextOverlay()338 void StackElement::PerformPopTextOverlay()
339 {
340 for (auto iter = children_.rbegin(); iter != children_.rend(); ++iter) {
341 if (AceType::InstanceOf<TextOverlayElement>(*iter)) {
342 UpdateChild(*iter, nullptr);
343 break;
344 }
345 }
346 if (IsFocusable()) {
347 RequestFocus();
348 }
349 EnableTouchEventAndRequestFocus();
350 }
351
PerformPopPopup(const ComposeId & id)352 void StackElement::PerformPopPopup(const ComposeId& id)
353 {
354 for (auto iter = children_.rbegin(); iter != children_.rend(); ++iter) {
355 auto child = DynamicCast<TweenElement>(*iter);
356 if (child && child->GetId() == id) {
357 auto themeManager = GetThemeManager();
358 if (!themeManager || !themeManager->GetTheme<PopupTheme>()) {
359 LOGE("themeManager or get theme is null!");
360 return;
361 }
362
363 auto context = context_.Upgrade();
364 if (context && !context->GetOnShow()) {
365 UpdateChild(child, nullptr);
366 break;
367 }
368
369 auto theme = themeManager->GetTheme<PopupTheme>();
370 auto hideAlphaAnimation = AceType::MakeRefPtr<CurveAnimation<float>>(1.0f, 0.0f, Curves::FAST_OUT_SLOW_IN);
371 TweenOption hideOption;
372 hideOption.SetDuration(theme->GetHideTime());
373 hideOption.SetOpacityAnimation(hideAlphaAnimation);
374
375 auto animator = child->GetController();
376 animator->ClearAllListeners();
377 child->SetOption(hideOption);
378 child->ApplyOptions();
379 child->ApplyKeyframes();
380 animator->AddStopListener(
381 [weakStack = AceType::WeakClaim(this), weakChild = AceType::WeakClaim(AceType::RawPtr(child))] {
382 auto lastStack = weakStack.Upgrade();
383 auto child = weakChild.Upgrade();
384 if (lastStack && child) {
385 lastStack->UpdateChild(child, nullptr);
386 }
387 });
388 animator->Play();
389 break;
390 }
391 }
392 if (IsFocusable()) {
393 RequestFocus();
394 }
395 EnableTouchEventAndRequestFocus();
396 }
397
PerformPopMenu()398 void StackElement::PerformPopMenu()
399 {
400 for (auto iter = children_.rbegin(); iter != children_.rend(); ++iter) {
401 if (AceType::InstanceOf<SelectPopupElement>(*iter)) {
402 UpdateChild(*iter, nullptr);
403 break;
404 }
405 }
406 if (IsFocusable()) {
407 RequestFocus();
408 }
409 EnableTouchEventAndRequestFocus();
410 }
411
PerformPopVideo()412 void StackElement::PerformPopVideo()
413 {
414 for (auto iter = children_.rbegin(); iter != children_.rend(); ++iter) {
415 auto element = DynamicCast<ComposedElement>(*iter);
416 if (element && StringUtils::EndWith(element->GetName(), "fullscreen")) {
417 UpdateChild(*iter, nullptr);
418 break;
419 }
420 }
421 EnableTouchEventAndRequestFocus();
422 }
423
PerformDirectPop()424 void StackElement::PerformDirectPop()
425 {
426 auto child = children_.end();
427 while (child != children_.begin()) {
428 child--;
429 bool isNotToast = std::none_of(
430 toastStack_.begin(), toastStack_.end(), [child](const ToastInfo& toast) { return toast.child == *child; });
431 if (isNotToast) {
432 UpdateChild(*child, nullptr);
433 break;
434 }
435 }
436 EnableTouchEventAndRequestFocus();
437 }
438
RequestNextFocus(bool vertical,bool reverse,const Rect & rect)439 bool StackElement::RequestNextFocus(bool vertical, bool reverse, const Rect& rect)
440 {
441 if (!isPageElement()) {
442 return GoToNextFocus(reverse, rect);
443 }
444 return false;
445 }
446
OnFocus()447 void StackElement::OnFocus()
448 {
449 if (!isPageElement()) {
450 FocusGroup::OnFocus();
451 return;
452 }
453 if (focusNodes_.empty()) {
454 itLastFocusNode_ = focusNodes_.end();
455 return;
456 }
457 // Only focus on the top focusable child.
458 itLastFocusNode_ = focusNodes_.end();
459 while (itLastFocusNode_ != focusNodes_.begin()) {
460 --itLastFocusNode_;
461 (*itLastFocusNode_)->SetParentFocusable(IsParentFocusable());
462 if ((*itLastFocusNode_)->RequestFocusImmediately()) {
463 FocusNode::OnFocus();
464 break;
465 }
466 }
467
468 if (!IsCurrentFocus()) {
469 itLastFocusNode_ = focusNodes_.end();
470 } else {
471 // lower focusable node can not be focus.
472 auto iter = itLastFocusNode_;
473 while (iter != focusNodes_.begin()) {
474 --iter;
475 (*iter)->SetParentFocusable(false);
476 }
477 }
478 }
479
OnBlur()480 void StackElement::OnBlur()
481 {
482 FocusGroup::OnBlur();
483 if (!isPageElement()) {
484 return;
485 }
486
487 auto iter = focusNodes_.end();
488 while (iter != focusNodes_.begin()) {
489 --iter;
490 (*iter)->SetParentFocusable(IsParentFocusable());
491 }
492 }
493
EnableTouchEventAndRequestFocus()494 void StackElement::EnableTouchEventAndRequestFocus()
495 {
496 for (auto& child : children_) {
497 auto renderNode = child->GetRenderNode();
498 if (renderNode) {
499 renderNode->SetDisableTouchEvent(false);
500 }
501 }
502 if (IsFocusable()) {
503 RequestFocus();
504 }
505 }
506
CreateInspectorComponent(PopupComponentInfo & componentInfo) const507 void StackElement::CreateInspectorComponent(PopupComponentInfo& componentInfo) const
508 {
509 auto dialog = AceType::DynamicCast<DialogComponent>(componentInfo.component);
510 if (!dialog) {
511 return;
512 }
513 auto inspectorTag = dialog->GetInspectorTag();
514 if (V2::InspectorComposedComponent::HasInspectorFinished(inspectorTag)) {
515 auto composedComponent = AceType::MakeRefPtr<V2::InspectorComposedComponent>(
516 V2::InspectorComposedComponent::GenerateId(), inspectorTag);
517 composedComponent->SetChild(componentInfo.component);
518 componentInfo.component = composedComponent;
519 }
520 }
521
PopPopupIfExist() const522 bool StackElement::PopPopupIfExist() const
523 {
524 auto bubbleElement = GetBubble(GetLastChild());
525 if (!bubbleElement) {
526 return false;
527 }
528 auto renderBubble = DynamicCast<RenderBubble>(bubbleElement->GetRenderNode());
529 if (!renderBubble) {
530 return false;
531 }
532 renderBubble->PopBubble();
533 return true;
534 }
535
GetBubble(const RefPtr<Element> & element) const536 RefPtr<BubbleElement> StackElement::GetBubble(const RefPtr<Element>& element) const
537 {
538 if (!element) {
539 return nullptr;
540 }
541
542 auto bubble = DynamicCast<BubbleElement>(element);
543 if (bubble) {
544 return bubble;
545 }
546
547 return GetBubble(element->GetFirstChild());
548 }
549
PopDialogIfExist() const550 bool StackElement::PopDialogIfExist() const
551 {
552 auto dialogTweenElement = GetDialog(GetLastChild());
553 if (!dialogTweenElement) {
554 return false;
555 }
556 auto renderDialogTween = DynamicCast<RenderDialogTween>(dialogTweenElement->GetRenderNode());
557 if (!renderDialogTween) {
558 return false;
559 }
560 renderDialogTween->PopDialog();
561 return true;
562 }
563
GetDialog(const RefPtr<Element> & element) const564 RefPtr<DialogTweenElement> StackElement::GetDialog(const RefPtr<Element>& element) const
565 {
566 if (!element) {
567 return nullptr;
568 }
569
570 auto dialog = DynamicCast<DialogTweenElement>(element);
571 if (dialog) {
572 return dialog;
573 }
574
575 return GetDialog(element->GetFirstChild());
576 }
577
578 } // namespace OHOS::Ace
579