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/page_transition/page_transition_element.h"
17
18 #include "base/utils/system_properties.h"
19 #include "core/animation/shared_transition_controller.h"
20 #include "core/components/clip/clip_component.h"
21 #include "core/components/clip/clip_element.h"
22 #include "core/components/clip/render_clip.h"
23 #include "core/components/page/page_element.h"
24 #include "core/components/page_transition/page_transition_component.h"
25 #include "core/components/transition/transition_component.h"
26
27 namespace OHOS::Ace {
28
29 namespace {
30
31 constexpr int32_t CHILDREN_SIZE_WHEN_SPLIT = 2;
32
GetOptionType(bool hasSharedTransition,TransitionDirection direction)33 TransitionOptionType GetOptionType(bool hasSharedTransition, TransitionDirection direction)
34 {
35 if (hasSharedTransition) {
36 if (direction == TransitionDirection::TRANSITION_IN) {
37 return TransitionOptionType::TRANSITION_SHARED_IN;
38 } else {
39 return TransitionOptionType::TRANSITION_SHARED_OUT;
40 }
41 } else {
42 if (direction == TransitionDirection::TRANSITION_IN) {
43 return TransitionOptionType::TRANSITION_IN;
44 } else {
45 return TransitionOptionType::TRANSITION_OUT;
46 }
47 }
48 }
49
50 } // namespace
51
Update()52 void PageTransitionElement::Update()
53 {
54 StackElement::Update();
55 UpdateTransitionOption();
56 }
57
PerformBuild()58 void PageTransitionElement::PerformBuild()
59 {
60 // PageTransitionElement only have two children. one is content, the other is background.
61 if (!children_.empty()) {
62 LOGE("perform build failed. not empty children. size: %{public}u, skip perform build.",
63 static_cast<int32_t>(children_.size()));
64 return;
65 }
66
67 auto pageTransitionComponent = AceType::DynamicCast<PageTransitionComponent>(component_);
68 if (!pageTransitionComponent) {
69 LOGE("PageTransitionElement::PerformBuild: get PageTransitionComponent failed!");
70 return;
71 }
72
73 if (!controller_) {
74 LOGD("create animator.");
75 controller_ = AceType::MakeRefPtr<Animator>(context_);
76 }
77 UpdateTransitionOption();
78
79 if (pageTransitionComponent->GetSeparation()) {
80 BuildSeparatedChild(pageTransitionComponent);
81 } else {
82 BuildCombinedChild(pageTransitionComponent);
83 }
84 SetTransitionController();
85 }
86
SetTransitionController()87 void PageTransitionElement::SetTransitionController()
88 {
89 if (!controller_) {
90 LOGE("set transition controller failed. controller is null");
91 return;
92 }
93
94 // stop controller first.
95 if (!controller_->IsStopped()) {
96 controller_->Stop();
97 }
98
99 if (contentTransition_) {
100 auto contentController = contentTransition_->GetController();
101 if (contentController && (!contentController->IsStopped())) {
102 contentController->Stop();
103 }
104 contentTransition_->SetController(controller_);
105 }
106 if (frontDecorationTransition_) {
107 auto frontController = frontDecorationTransition_->GetController();
108 if (frontController && (!frontController->IsStopped())) {
109 frontController->Stop();
110 }
111 frontDecorationTransition_->SetController(controller_);
112 }
113 if (backgroundTransition_) {
114 auto backgroundController = backgroundTransition_->GetController();
115 if (backgroundController && (!backgroundController->IsStopped())) {
116 backgroundController->Stop();
117 }
118 backgroundTransition_->SetController(controller_);
119 }
120 }
121
SetTransitionDirection(TransitionEvent event,TransitionDirection direction)122 void PageTransitionElement::SetTransitionDirection(TransitionEvent event, TransitionDirection direction)
123 {
124 if (!controller_) {
125 LOGE("set transition direction failed. controller is empty.");
126 return;
127 }
128 auto context = context_.Upgrade();
129 if (!context) {
130 LOGE("set transition direction failed. context is empty.");
131 return;
132 }
133 auto sharedController = context->GetSharedTransitionController();
134 if (!sharedController) {
135 LOGE("set transition direction failed. shared controller is null.");
136 return;
137 }
138 controller_->ClearInterpolators();
139
140 // stop controller first.
141 if (!controller_->IsStopped()) {
142 controller_->Stop();
143 }
144 TransitionOptionType optionType;
145 auto deviceType = SystemProperties::GetDeviceType();
146 if (deviceType == DeviceType::TV) {
147 // no shared transition UI standard on tv, just use default page transition parameters
148 optionType = GetOptionType(false, direction);
149 } else {
150 bool hasShared = sharedController->HasSharedTransition(event);
151 optionType = GetOptionType(hasShared, direction);
152 }
153 if (contentTransition_) {
154 LOGD("set content transition option type: %{public}d", optionType);
155 contentTransition_->SwitchTransitionOption(optionType);
156 }
157 if (frontDecorationTransition_) {
158 LOGD("set front transition option type: %{public}d", direction);
159 frontDecorationTransition_->SwitchTransitionOption(optionType);
160 }
161 if (backgroundTransition_) {
162 LOGD("set background transition option type: %{public}d", optionType);
163 backgroundTransition_->SwitchTransitionOption(optionType);
164 }
165 if (context && context->GetIsDeclarative()) {
166 if (floatAnimation_) {
167 controller_->AddInterpolator(std::move(floatAnimation_));
168 controller_->SetAllowRunningAsynchronously(false);
169 } else {
170 controller_->SetAllowRunningAsynchronously(true);
171 }
172 }
173 }
174
GetTransitionController() const175 const RefPtr<Animator>& PageTransitionElement::GetTransitionController() const
176 {
177 return controller_;
178 }
179
UpdateTransitionOption()180 void PageTransitionElement::UpdateTransitionOption()
181 {
182 if (!component_) {
183 LOGE("update transition option failed. component is null.");
184 return;
185 }
186 auto transitionComponent = AceType::DynamicCast<PageTransitionComponent>(component_);
187 if (!transitionComponent) {
188 LOGE("update transition option failed. transition is null.");
189 return;
190 }
191 isRightToLeft_ = transitionComponent->GetTextDirection() == TextDirection::RTL;
192
193 LOGI("Use user-defined transition parameters.");
194 contentInOption_ = transitionComponent->GetContentTransitionInOption();
195 contentOutOption_ = transitionComponent->GetContentTransitionOutOption();
196 pageTransitions_ = transitionComponent->GetPageTransitions();
197
198 sharedInOption_ = contentInOption_;
199 sharedOutOption_ = contentOutOption_;
200 isCustomOptionIn_ = contentInOption_.IsValid();
201 isCustomOptionOut_ = contentOutOption_.IsValid();
202 }
203
GetTransitionElement(const RefPtr<Element> & element)204 RefPtr<PageTransitionElement> PageTransitionElement::GetTransitionElement(const RefPtr<Element>& element)
205 {
206 // first try with page element.
207 RefPtr<PageElement> page = AceType::DynamicCast<PageElement>(element);
208 if (!page) {
209 return nullptr;
210 }
211 LOGD("try to get transition element from page element.");
212 auto child = page->GetFirstChild();
213 return AceType::DynamicCast<PageTransitionElement>(child);
214 }
215
SetTouchable(bool enable)216 void PageTransitionElement::SetTouchable(bool enable)
217 {
218 if (backgroundTransition_) {
219 backgroundTransition_->SetTouchable(enable);
220 }
221
222 if (contentTransition_) {
223 contentTransition_->SetTouchable(enable);
224 }
225 }
226
InitTransitionClip()227 void PageTransitionElement::InitTransitionClip()
228 {
229 if (!contentTransition_) {
230 LOGE("InitTransitionClip failed, content transition is null.");
231 return;
232 }
233 auto clipElement = contentTransition_->GetContentElement();
234 if (AceType::InstanceOf<ClipElement>(clipElement)) {
235 RefPtr<RenderClip> clipRender = DynamicCast<RenderClip>(clipElement->GetRenderNode());
236 if (clipRender) {
237 clipRender->SetWidth(0.0);
238 clipRender->SetHeight(0.0);
239 }
240 }
241 }
242
InitController(TransitionDirection direction,TransitionEvent event)243 void PageTransitionElement::InitController(TransitionDirection direction, TransitionEvent event)
244 {
245 if (!controller_) {
246 LOGE("init controller failed. controller is null.");
247 return;
248 }
249 if (event == TransitionEvent::PUSH_END || event == TransitionEvent::POP_END) {
250 LOGE("init controller failed. event can not be handled. event: %{public}d", event);
251 return;
252 }
253 SetTouchable(false);
254 if ((direction == TransitionDirection::TRANSITION_OUT) && (event == TransitionEvent::PUSH_START)) {
255 LOGD("No need to make it touchable after transition done, because page will not on the top of the stage.");
256 return;
257 }
258 if ((direction == TransitionDirection::TRANSITION_IN) && (event == TransitionEvent::POP_START)) {
259 LOGD("No need to make it touchable after transition done, because page will be destroyed.");
260 return;
261 }
262 auto weak = AceType::WeakClaim(this);
263 controller_->AddStopListener([weak]() {
264 LOGD("transition complete, prepare to clear it");
265 auto transition = weak.Upgrade();
266 if (transition) {
267 LOGD("transition complete, clear it");
268 transition->SetTouchable(true);
269 }
270 });
271 }
272
SetWrapHidden(bool hidden)273 void PageTransitionElement::SetWrapHidden(bool hidden)
274 {
275 if (contentTransition_) {
276 contentTransition_->SetWrapHidden(hidden);
277 }
278
279 if (backgroundTransition_) {
280 backgroundTransition_->SetWrapHidden(hidden);
281 }
282 }
283
AddPreFlush()284 void PageTransitionElement::AddPreFlush()
285 {
286 if (contentTransition_) {
287 contentTransition_->AddPreFlush();
288 }
289
290 if (backgroundTransition_) {
291 backgroundTransition_->AddPreFlush();
292 }
293 }
294
SkipPostFlush()295 void PageTransitionElement::SkipPostFlush()
296 {
297 if (contentTransition_) {
298 contentTransition_->SkipPostFlush();
299 }
300
301 if (backgroundTransition_) {
302 backgroundTransition_->SkipPostFlush();
303 }
304 }
305
GetContentElement() const306 RefPtr<Element> PageTransitionElement::GetContentElement() const
307 {
308 if (!contentTransition_) {
309 LOGE("get content element failed. content tween is null.");
310 return nullptr;
311 }
312 auto element = contentTransition_->GetContentElement();
313 if (AceType::InstanceOf<ClipElement>(element)) {
314 auto transition = DynamicCast<TransitionElement>(element->GetFirstChild());
315 if (transition) {
316 auto frontDecorationBox = transition->GetContentElement();
317 if (frontDecorationBox) {
318 return frontDecorationBox->GetFirstChild();
319 }
320 }
321 return transition;
322 }
323 return element;
324 }
325
LoadTransition()326 void PageTransitionElement::LoadTransition()
327 {
328 auto pageElement = GetPageElement();
329 if (!pageElement) {
330 return;
331 }
332 auto componentUpdated = pageElement->CallPageTransitionFunction();
333 // save origin component
334 auto componentOrigin = component_;
335 component_ = componentUpdated;
336 // update with updated component
337 UpdateTransitionOption();
338 // restore origin component
339 component_ = componentOrigin;
340 }
341
ResetPageTransitionAnimation()342 void PageTransitionElement::ResetPageTransitionAnimation()
343 {
344 if (contentTransition_) {
345 contentTransition_->ResetPageTransitionAnimation();
346 }
347 }
348
SetTransition(DeviceType deviceType,TransitionEvent event,TransitionDirection direction,const RRect & rrect)349 void PageTransitionElement::SetTransition(
350 DeviceType deviceType, TransitionEvent event, TransitionDirection direction, const RRect& rrect)
351 {
352 auto tweenOption =
353 TransitionTweenOptionFactory::CreateTransitionTweenOption(deviceType, event, isRightToLeft_, rrect, context_);
354 if (!tweenOption) {
355 LOGE("TransitionTweenOption is null.");
356 return;
357 }
358 bool isSetOutOption = false;
359 int32_t duration = tweenOption->GetTransitionContentInOption().GetDuration();
360 int32_t delay = 0;
361 if (direction == TransitionDirection::TRANSITION_OUT) {
362 if (isCustomOptionOut_) {
363 if (contentOutOption_.HasDurationChanged()) {
364 duration = contentOutOption_.GetDuration();
365 }
366 isSetOutOption = true;
367 } else {
368 contentOutOption_ = tweenOption->GetTransitionContentOutOption();
369 sharedOutOption_ = tweenOption->GetSharedOutOption();
370 }
371 }
372 if (direction == TransitionDirection::TRANSITION_IN) {
373 if (isCustomOptionIn_) {
374 if (contentInOption_.HasDurationChanged()) {
375 duration = contentInOption_.GetDuration();
376 }
377 } else {
378 contentInOption_ = tweenOption->GetTransitionContentInOption();
379 sharedInOption_ = tweenOption->GetSharedInOption();
380 }
381 }
382 auto context = GetContext().Upgrade();
383 if (context && context->GetIsDeclarative()) {
384 auto pageTransition = GetCurrentPageTransition(event, direction_);
385 isCustomOption_ = false;
386 isSetOutOption = true;
387 if (direction == TransitionDirection::TRANSITION_OUT) {
388 if (pageTransition) {
389 contentOutOption_ = ProcessPageTransition(pageTransition, event);
390 if (contentOutOption_.HasDurationChanged()) {
391 duration = contentOutOption_.GetDuration();
392 }
393 delay = contentOutOption_.GetDelay();
394 isCustomOption_ = true;
395 sharedOutOption_ = contentOutOption_;
396 }
397 } else {
398 if (pageTransition) {
399 contentInOption_ = ProcessPageTransition(pageTransition, event);
400 if (contentInOption_.HasDurationChanged()) {
401 duration = contentInOption_.GetDuration();
402 }
403 delay = contentInOption_.GetDelay();
404 isCustomOption_ = true;
405 sharedInOption_ = contentInOption_;
406 }
407 }
408 }
409 if (controller_) {
410 controller_->SetDuration(duration);
411 if (context && context->GetIsDeclarative() && delay >= 0) {
412 controller_->SetStartDelay(delay);
413 }
414 }
415 if (contentTransition_) {
416 contentTransition_->SetTransition(contentInOption_, contentOutOption_);
417 contentTransition_->SetSharedTransition(sharedInOption_, sharedOutOption_);
418 }
419 if (frontDecorationTransition_ && !isSetOutOption) {
420 // do not need option in.
421 TweenOption optionIn;
422 frontDecorationTransition_->SetTransition(optionIn, tweenOption->GetTransitionFrontDecorationOption());
423 frontDecorationTransition_->SetSharedTransition(
424 optionIn, tweenOption->GetSharedTransitionFrontDecorationOption());
425 }
426 if (backgroundTransition_) {
427 backgroundTransition_->SetTransition(
428 tweenOption->GetTransitionBackgroundInOption(), tweenOption->GetTransitionBackgroundOutOption());
429 }
430 }
431
BuildCombinedChild(const RefPtr<StackComponent> & component)432 void PageTransitionElement::BuildCombinedChild(const RefPtr<StackComponent>& component)
433 {
434 auto pageTransitionComponent = AceType::DynamicCast<PageTransitionComponent>(component);
435 if (!pageTransitionComponent) {
436 LOGE("Get PageTransitionComponent failed!");
437 return;
438 }
439 // create transition for content
440 auto box = AceType::MakeRefPtr<BoxComponent>();
441 Component::MergeRSNode(box);
442 auto front = AceType::MakeRefPtr<Decoration>();
443 front->SetBackgroundColor(Color::FromRGBO(0, 0, 0, 0.0));
444 box->SetFrontDecoration(front);
445 box->SetChild(pageTransitionComponent->GetContent());
446 auto transition = AceType::MakeRefPtr<TransitionComponent>(
447 TransitionComponent::AllocTransitionComponentId(), "frontDecoration_transition", box);
448
449 auto clip = AceType::MakeRefPtr<ClipComponent>(transition);
450 Component::MergeRSNode(clip);
451 auto contentTransitionComponent = AceType::MakeRefPtr<TransitionComponent>(
452 TransitionComponent::AllocTransitionComponentId(), "page_transition_content", clip);
453
454 // add transition for content
455 pageTransitionComponent->AppendChild(contentTransitionComponent);
456 StackElement::PerformBuild();
457
458 if (children_.size() != 1) {
459 LOGE("the children size is error.");
460 return;
461 }
462 auto childIter = children_.begin();
463 auto child = *childIter;
464
465 // child for content.
466 contentTransition_ = AceType::DynamicCast<TransitionElement>(child);
467 auto frontElement = contentTransition_->GetContentElement();
468 if (frontElement) {
469 frontDecorationTransition_ = DynamicCast<TransitionElement>(frontElement->GetFirstChild());
470 }
471 }
472
BuildSeparatedChild(const RefPtr<StackComponent> & component)473 void PageTransitionElement::BuildSeparatedChild(const RefPtr<StackComponent>& component)
474 {
475 auto pageTransitionComponent = AceType::DynamicCast<PageTransitionComponent>(component);
476 if (!pageTransitionComponent) {
477 LOGE("BuildSeparatedChild : get PageTransitionComponent failed!");
478 return;
479 }
480 // add transition for background
481 pageTransitionComponent->AppendChild(
482 AceType::MakeRefPtr<TransitionComponent>(TransitionComponent::AllocTransitionComponentId(),
483 "page_transition_background", pageTransitionComponent->GetBackground()));
484
485 // create transition for content
486 auto contentTransitionComponent =
487 AceType::MakeRefPtr<TransitionComponent>(TransitionComponent::AllocTransitionComponentId(),
488 "page_transition_content", pageTransitionComponent->GetContent());
489
490 // add transition for content
491 pageTransitionComponent->AppendChild(contentTransitionComponent);
492 StackElement::PerformBuild();
493
494 if (children_.size() != CHILDREN_SIZE_WHEN_SPLIT) {
495 LOGE("the children size is error.");
496 return;
497 }
498 auto childIter = children_.begin();
499 auto child = *childIter;
500
501 // child for background
502 backgroundTransition_ = AceType::DynamicCast<TransitionElement>(child);
503 child = *(++childIter);
504
505 // child for content.
506 contentTransition_ = AceType::DynamicCast<TransitionElement>(child);
507 }
508
GetContentTransitionElement() const509 const RefPtr<TransitionElement>& PageTransitionElement::GetContentTransitionElement() const
510 {
511 return contentTransition_;
512 }
513
GetBackgroundTransitionElement() const514 const RefPtr<TransitionElement>& PageTransitionElement::GetBackgroundTransitionElement() const
515 {
516 return backgroundTransition_;
517 }
518
ProcessPageTransition(const RefPtr<PageTransition> & pageTransition,TransitionEvent event)519 TweenOption PageTransitionElement::ProcessPageTransition(
520 const RefPtr<PageTransition>& pageTransition, TransitionEvent event)
521 {
522 auto tweenOption = pageTransition->GetTweenOption();
523 // 1.SlideEffect
524 auto transitionDeclarativeTweenOption = TransitionDeclarativeTweenOption(isRightToLeft_, GetContext());
525 transitionDeclarativeTweenOption.CreateSlideEffectAnimation(
526 tweenOption, pageTransition->GetSlideEffect(), pageTransition->GetType(), direction_);
527 // 2. callback
528 auto onExitHandler = pageTransition->GetOnExitHandler();
529 auto onEnterHandler = pageTransition->GetOnEnterHandler();
530 RouteType type = RouteType::PUSH;
531 if (event == TransitionEvent::POP_START || event == TransitionEvent::POP_END) {
532 type = RouteType::POP;
533 }
534 if (onExitHandler || onEnterHandler) {
535 floatAnimation_ = AceType::MakeRefPtr<CurveAnimation<float>>(0.0f, 1.0f, pageTransition->GetCurve());
536 if (onExitHandler) {
537 floatAnimation_->AddListener(
538 [type, onExitHandler](const float& progress) { onExitHandler(type, progress); });
539 }
540 if (onEnterHandler) {
541 floatAnimation_->AddListener(
542 [type, onEnterHandler](const float& progress) { onEnterHandler(type, progress); });
543 }
544 }
545 // 3. delay curve
546 return tweenOption;
547 }
548
GetCurrentPageTransition(TransitionEvent event,TransitionDirection direction) const549 RefPtr<PageTransition> PageTransitionElement::GetCurrentPageTransition(
550 TransitionEvent event, TransitionDirection direction) const
551 {
552 if (pageTransitions_.empty()) {
553 return nullptr;
554 }
555 auto type = GetPageTransitionType(event, direction);
556 auto pos = pageTransitions_.find(type);
557 if (pos != pageTransitions_.end()) {
558 return pos->second;
559 }
560
561 if (direction == TransitionDirection::TRANSITION_IN) {
562 type = PageTransitionType::ENTER;
563 } else {
564 type = PageTransitionType::EXIT;
565 }
566 pos = pageTransitions_.find(type);
567 if (pos != pageTransitions_.end()) {
568 return pos->second;
569 }
570 return nullptr;
571 }
572
GetPageTransitionType(TransitionEvent event,TransitionDirection direction)573 PageTransitionType PageTransitionElement::GetPageTransitionType(TransitionEvent event, TransitionDirection direction)
574 {
575 if (direction == TransitionDirection::TRANSITION_IN) {
576 if (event == TransitionEvent::POP_START) {
577 return PageTransitionType::ENTER_POP;
578 } else {
579 return PageTransitionType::ENTER_PUSH;
580 }
581 } else {
582 if (event == TransitionEvent::POP_START) {
583 return PageTransitionType::EXIT_POP;
584 } else {
585 return PageTransitionType::EXIT_PUSH;
586 }
587 }
588 }
589
590 } // namespace OHOS::Ace
591