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