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