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