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