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