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/animation/shared_transition_effect.h"
17 
18 #include "core/components/common/properties/page_transition_option.h"
19 #include "core/components/overlay/overlay_element.h"
20 #include "core/components/positioned/positioned_component.h"
21 #include "core/components/shared_transition/shared_transition_element.h"
22 #include "core/components/tween/tween_component.h"
23 
24 namespace OHOS::Ace {
25 
SharedTransitionEffect(const ShareId & shareId,SharedTransitionEffectType type)26 SharedTransitionEffect::SharedTransitionEffect(const ShareId& shareId, SharedTransitionEffectType type)
27     : shareId_(shareId), type_(type)
28 {
29     controller_ = CREATE_ANIMATOR();
30 }
31 
CheckIn(TransitionEvent event,WeakPtr<SharedTransitionElement> & sharedWeak,Offset & ticket)32 bool SharedTransitionEffect::CheckIn(
33     TransitionEvent event, WeakPtr<SharedTransitionElement>& sharedWeak, Offset& ticket)
34 {
35     if (!controller_) {
36         LOGE("Check In failed. controller is null. share id: %{public}s", shareId_.c_str());
37         return false;
38     }
39     auto shared = sharedWeak.Upgrade();
40     if (!shared) {
41         LOGE("Check In failed. passenger element is null. share id: %{public}s", shareId_.c_str());
42         return false;
43     }
44     // Check-in
45     if (!shared->AboardShuttle(ticket)) {
46         LOGE("Check In failed. aboard shuttle failed. share id: %{public}s.", shareId_.c_str());
47         return false;
48     }
49     // Arrange Return Shuttle
50     controller_->AddStopListener([sharedWeak, shareId = shareId_]() {
51         auto shared = sharedWeak.Upgrade();
52         if (!shared) {
53             return;
54         }
55         shared->GetOffShuttle();
56     });
57     controller_->AddStopListener([destWeak = dest_, srcWeak = src_]() {
58         auto dest = destWeak.Upgrade();
59         if (dest) {
60             dest->SetSizeModified(nullptr);
61         }
62         auto src = srcWeak.Upgrade();
63         if (src) {
64             src->SetSizeModified(nullptr);
65         }
66     });
67     return true;
68 }
69 
TakeOff(TransitionEvent event,RefPtr<OverlayElement> & overlay,WeakPtr<SharedTransitionElement> & sharedWeak,const Offset & ticket,TweenOption & option)70 bool SharedTransitionEffect::TakeOff(TransitionEvent event, RefPtr<OverlayElement>& overlay,
71     WeakPtr<SharedTransitionElement>& sharedWeak, const Offset& ticket, TweenOption& option)
72 {
73     if (!controller_) {
74         LOGE("TakeOff failed. controller is null. share id: %{public}s", shareId_.c_str());
75         return false;
76     }
77     if (!overlay) {
78         LOGE("TakeOff failed. overlay is null. event: %{public}d, share id: %{public}s", event, shareId_.c_str());
79         return false;
80     }
81     auto shared = sharedWeak.Upgrade();
82     if (!shared) {
83         LOGE("TakeOff failed. shared is null. event: %{public}d, share id: %{public}s", event, shareId_.c_str());
84         return false;
85     }
86     auto passengerComponent = shared->GetPassengerComponent();
87     auto passengerElement = shared->GetPassengerElement();
88     if (!passengerComponent || !passengerElement) {
89         LOGE("TakeOff failed. passenger not found. event: %{public}d, share id: %{public}s.", event, shareId_.c_str());
90         return false;
91     }
92     auto tweenSeat = AceType::MakeRefPtr<TweenComponent>("TweenSeat");
93     // Find Seat.
94     auto seat = AceType::MakeRefPtr<PositionedComponent>(tweenSeat);
95     Component::MergeRSNode(seat);
96     seat->SetLeft(Dimension(ticket.GetX(), DimensionUnit::PX));
97     seat->SetTop(Dimension(ticket.GetY(), DimensionUnit::PX));
98     // set zIndex
99     auto zIndex = shared->GetZIndex();
100     if (zIndex != 0) {
101         seat->SetZIndex(zIndex);
102     }
103     // Take Off,
104     overlay->PushInstant(seat);
105     auto seatElement = AceType::DynamicCast<PositionedElement>(overlay->GetLastChild());
106     if (!seatElement) {
107         LOGE("TakeOff failed. seat not found. event: %{public}d, share id: %{public}s.", event, shareId_.c_str());
108         return false;
109     }
110     // make overlay un-focusable.
111     seatElement->SetFocusable(false);
112     auto seatRender = seatElement->GetRenderNode();
113     if (!seatRender) {
114         LOGE("TakeOff failed, render is nullptr");
115         return false;
116     }
117     seatRender->SetDisableTouchEvent(true);
118     return TakeOffTween(seatElement->GetFirstChild(), passengerComponent, passengerElement, option);
119 }
120 
TakeOffTween(const RefPtr<Element> & tweenElement,const RefPtr<Component> & passengerComponent,const RefPtr<Element> & passengerElement,TweenOption & option)121 bool SharedTransitionEffect::TakeOffTween(const RefPtr<Element>& tweenElement,
122     const RefPtr<Component>& passengerComponent, const RefPtr<Element>& passengerElement, TweenOption& option)
123 {
124     auto tweenSeatElement = AceType::DynamicCast<TweenElement>(tweenElement);
125     if (!tweenSeatElement) {
126         LOGE("TakeOff failed. tween not found. share id: %{public}s.", shareId_.c_str());
127         return false;
128     }
129     auto contentParent = tweenSeatElement->GetContentParent();
130     if (!contentParent) {
131         LOGE("TakeOff failed. content parent not found. share id: %{public}s.", shareId_.c_str());
132         return false;
133     }
134     passengerElement->SetNewComponent(passengerComponent);
135     passengerElement->Mount(contentParent);
136     auto passengerRender = passengerElement->GetRenderNode();
137     auto parentRender = contentParent->GetRenderNode();
138     if (passengerRender && parentRender) {
139         // Follow parent's hidden status.
140         passengerRender->SetHidden(parentRender->GetHidden());
141     }
142     tweenSeatElement->SetController(controller_);
143     tweenSeatElement->SetOption(option);
144     tweenSeatElement->ApplyKeyframes();
145     tweenSeatElement->ApplyOptions();
146     tweenSeatElement_ = tweenSeatElement;
147     return true;
148 }
149 
GetSharedTransitionEffect(SharedTransitionEffectType effect,const ShareId & shareId)150 RefPtr<SharedTransitionEffect> SharedTransitionEffect::GetSharedTransitionEffect(
151     SharedTransitionEffectType effect, const ShareId& shareId)
152 {
153     switch (effect) {
154         case SharedTransitionEffectType::SHARED_EFFECT_EXCHANGE: {
155             return AceType::MakeRefPtr<SharedTransitionExchange>(shareId);
156         }
157         case SharedTransitionEffectType::SHARED_EFFECT_STATIC: {
158             return AceType::MakeRefPtr<SharedTransitionStatic>(shareId);
159         }
160         default: {
161             LOGE("Unknown effect. effect: %{public}d, share id: %{public}s", effect, shareId.c_str());
162             return nullptr;
163         }
164     }
165 }
166 
ApplyAnimation(RefPtr<OverlayElement> & overlay,RefPtr<Animator> & controller,TweenOption & option,TransitionEvent event)167 bool SharedTransitionEffect::ApplyAnimation(
168     RefPtr<OverlayElement>& overlay, RefPtr<Animator>& controller, TweenOption& option, TransitionEvent event)
169 {
170     controller_->ClearAllListeners();
171     controller_->ClearInterpolators();
172     return true;
173 }
174 
Allow(TransitionEvent event)175 bool SharedTransitionExchange::Allow(TransitionEvent event)
176 {
177     auto dest = dest_.Upgrade();
178     auto src = src_.Upgrade();
179     if (!dest || !src) {
180         return false;
181     }
182     bool allow = false;
183     if (event == TransitionEvent::PUSH_START) {
184         // In Push Scene, dest means Enter and Source means Exit
185         allow = dest->IsEnablePushEnter() && src->IsEnablePushExit();
186     } else if (event == TransitionEvent::POP_START) {
187         // In Pop Scene, dest means Enter and Source means Exit
188         allow = dest->IsEnablePopEnter() && src->IsEnablePopExit();
189     }
190     return allow;
191 }
192 
AddLazyLoadCallback(TransitionEvent event)193 void SharedTransitionExchange::AddLazyLoadCallback(TransitionEvent event)
194 {
195     auto src = src_.Upgrade();
196     auto dest = dest_.Upgrade();
197     if (!dest || !src) {
198         LOGE("Add Lazy callback failed. Dest or src is null. event: %{public}d, share id: %{public}s", event,
199             shareId_.c_str());
200         return;
201     }
202     // Lazy load child size. make width and height animation later.
203     auto effectWeak = AceType::WeakClaim(this);
204     dest->SetSizeModified([effectWeak, event, shareId = shareId_]() {
205         auto effect = effectWeak.Upgrade();
206         if (!effect) {
207             LOGE("Create Lazy load animation failed. effect is null.");
208             return;
209         }
210         auto tweenSeatElement = effect->tweenSeatElement_.Upgrade();
211         if (!tweenSeatElement) {
212             LOGE("Create Lazy load animation failed. tween Seat Element is null.");
213             return;
214         }
215         TweenOption option = tweenSeatElement->GetOption();
216         option.ClearListeners();
217         if (!effect->CreateAnimation(option, event, true)) {
218             LOGE("Create animation failed. event: %{public}d, share id: %{public}s", event, shareId.c_str());
219             return;
220         }
221 
222         tweenSeatElement->SetOption(option);
223         effect->controller_->ClearInterpolators();
224         tweenSeatElement->ApplyKeyframes();
225     });
226 }
227 
CreateTranslateAnimation(TweenOption & option,TransitionEvent event,bool calledByLazyLoad)228 bool SharedTransitionExchange::CreateTranslateAnimation(
229     TweenOption& option, TransitionEvent event, bool calledByLazyLoad)
230 {
231     auto src = src_.Upgrade();
232     auto dest = dest_.Upgrade();
233     if (!dest || !src) {
234         LOGE("Create exchange animation failed. dest or src is null. event: %{public}d, share id: %{public}s", event,
235             shareId_.c_str());
236         return false;
237     }
238     auto& translateMap = option.GetTranslateAnimations();
239     auto translateIter = translateMap.find(AnimationType::TRANSLATE);
240     if ((calledByLazyLoad && autoTranslate_) || (translateIter == translateMap.end())) {
241         // if no custom translate animation, add exchange translate animation.
242         auto destOffset = dest->GetGlobalOffset();
243         auto srcOffset = src->GetGlobalOffset();
244         if (destOffset != srcOffset) {
245             auto translateAnimation = AceType::MakeRefPtr<CurveAnimation<DimensionOffset>>(
246                 Offset(0, 0), destOffset - srcOffset, Curves::FRICTION);
247             const auto& motionPathOption = option.GetMotionPathOption();
248             if (motionPathOption.IsValid()) {
249                 auto motionPathEvaluator =
250                     AceType::MakeRefPtr<MotionPathEvaluator>(motionPathOption, Offset(0, 0), destOffset - srcOffset);
251                 translateAnimation->SetEvaluator(motionPathEvaluator->CreateDimensionOffsetEvaluator());
252                 if (motionPathOption.GetRotate()) {
253                     auto rotateAnimation = AceType::MakeRefPtr<CurveAnimation<float>>(0.0f, 1.0f, option.GetCurve());
254                     rotateAnimation->SetEvaluator(motionPathEvaluator->CreateRotateEvaluator());
255                     option.SetTransformFloatAnimation(AnimationType::ROTATE_Z, rotateAnimation);
256                 }
257             }
258             option.SetTranslateAnimations(AnimationType::TRANSLATE, translateAnimation);
259             autoTranslate_ = true;
260         }
261     }
262     return true;
263 }
264 
CreateSizeAnimation(TweenOption & option,TransitionEvent event,bool isLazy)265 bool SharedTransitionExchange::CreateSizeAnimation(TweenOption& option, TransitionEvent event, bool isLazy)
266 {
267     auto src = src_.Upgrade();
268     auto dest = dest_.Upgrade();
269     if (!dest || !src) {
270         LOGE("Create exchange animation failed. dest or src is null. event: %{public}d, share id: %{public}s", event,
271             shareId_.c_str());
272         return false;
273     }
274     auto destSize = dest->GetSuitSize();
275     auto srcSize = src->GetSuitSize();
276 
277     // add width shared transition
278     auto& propertyMap = option.GetFloatPropertyAnimation();
279     auto widthIter = propertyMap.find(PropertyAnimatableType::PROPERTY_WIDTH);
280     if (((isLazy && autoWidth_) || (widthIter == propertyMap.end())) && !NearEqual(destSize.Width(), srcSize.Width())) {
281         auto widthAnimation =
282             AceType::MakeRefPtr<CurveAnimation<float>>(srcSize.Width(), destSize.Width(), Curves::FRICTION);
283         option.SetPropertyAnimationFloat(PropertyAnimatableType::PROPERTY_WIDTH, widthAnimation);
284         autoWidth_ = true;
285     }
286 
287     // add scaleY shared transition
288     auto heightIter = propertyMap.find(PropertyAnimatableType::PROPERTY_HEIGHT);
289     if (((isLazy && autoHeight_) || (heightIter == propertyMap.end())) &&
290         !NearEqual(destSize.Height(), srcSize.Height())) {
291         auto heightAnimation =
292             AceType::MakeRefPtr<CurveAnimation<float>>(srcSize.Height(), destSize.Height(), Curves::FRICTION);
293         option.SetPropertyAnimationFloat(PropertyAnimatableType::PROPERTY_HEIGHT, heightAnimation);
294         autoHeight_ = true;
295     }
296     return true;
297 }
298 
CreateOpacityAnimation(TweenOption & option,TransitionEvent event,bool isLazy)299 bool SharedTransitionExchange::CreateOpacityAnimation(TweenOption& option, TransitionEvent event, bool isLazy)
300 {
301     auto src = src_.Upgrade();
302     auto dest = dest_.Upgrade();
303     if (!dest || !src) {
304         LOGE("Create exchange animation failed. dest or src is null. event: %{public}d, share id: %{public}s", event,
305             shareId_.c_str());
306         return false;
307     }
308     auto destOpacity = dest->GetOpacity();
309     auto srcOpacity = src->GetOpacity();
310 
311     if (!NearEqual(destOpacity, srcOpacity) && !option.GetOpacityAnimation()) {
312         auto opacityAnimation = AceType::MakeRefPtr<CurveAnimation<float>>(srcOpacity, destOpacity, Curves::FRICTION);
313         option.SetOpacityAnimation(opacityAnimation);
314     }
315     return true;
316 }
317 
CreateAnimation(TweenOption & option,TransitionEvent event,bool isLazy)318 bool SharedTransitionExchange::CreateAnimation(TweenOption& option, TransitionEvent event, bool isLazy)
319 {
320     auto src = src_.Upgrade();
321     auto dest = dest_.Upgrade();
322     if (!dest || !src) {
323         LOGE("Create exchange animation failed. dest or src is null. event: %{public}d, share id: %{public}s", event,
324             shareId_.c_str());
325         return false;
326     }
327     if (!isLazy) {
328         autoTranslate_ = false;
329         autoWidth_ = false;
330         autoHeight_ = false;
331     }
332 
333     // add translate shared transition
334     if (!CreateTranslateAnimation(option, event, isLazy)) {
335         return false;
336     }
337     if (!CreateSizeAnimation(option, event, isLazy)) {
338         return false;
339     }
340     if (!CreateOpacityAnimation(option, event, isLazy)) {
341         return false;
342     }
343     AddLazyLoadCallback(event);
344     return true;
345 }
346 
ApplyAnimation(RefPtr<OverlayElement> & overlay,RefPtr<Animator> & controller,TweenOption & option,TransitionEvent event)347 bool SharedTransitionExchange::ApplyAnimation(RefPtr<OverlayElement>& overlay, RefPtr<Animator>& controller,
348     TweenOption& option, TransitionEvent event)
349 {
350     if (!SharedTransitionEffect::ApplyAnimation(overlay, controller, option, event)) {
351         return false;
352     }
353     Offset ticket;
354     if (!CheckIn(event, src_, ticket)) {
355         LOGE(
356             "Apply exchange failed. check in failed. event: %{public}d, share id: %{public}s", event, shareId_.c_str());
357         return false;
358     }
359     return TakeOff(event, overlay, src_, ticket, option);
360 }
361 
Allow(TransitionEvent event)362 bool SharedTransitionStatic::Allow(TransitionEvent event)
363 {
364     auto current = GetCurrentSharedElement().Upgrade();
365     if (!current) {
366         return false;
367     }
368     bool allow = false;
369     if (event == TransitionEvent::PUSH_START) {
370         // In Push Scene, dest means Enter and Source means Exit
371         allow = current->IsEnablePushEnter();
372     } else if (event == TransitionEvent::POP_START) {
373         // In Pop Scene, dest means Enter and Source means Exit
374         allow = current->IsEnablePopEnter();
375     }
376     return allow;
377 }
378 
CreateAnimation(TweenOption & option,TransitionEvent event,bool isLazy)379 bool SharedTransitionStatic::CreateAnimation(TweenOption& option, TransitionEvent event, bool isLazy)
380 {
381     if (src_.Invalid()) {
382         // anchor appearing, opacity 0 to 1
383         auto opacityAnimation = option.GetOpacityAnimation();
384         if (!opacityAnimation) {
385             TransitionTweenOptionFactory::CreateSharedTweenOption(
386                 SharedTransitionEffectType::SHARED_EFFECT_STATIC, option);
387         }
388     } else {
389         // anchor disappearing, opacity 1 to 0
390         if (!option.GetOpacityAnimation()) {
391             auto animation = AceType::MakeRefPtr<CurveAnimation<float>>(1.0f, 0.0f, option.GetCurve());
392             option.SetOpacityAnimation(animation);
393         }
394     }
395     return true;
396 }
397 
ApplyAnimation(RefPtr<OverlayElement> & overlay,RefPtr<Animator> & controller,TweenOption & option,TransitionEvent event)398 bool SharedTransitionStatic::ApplyAnimation(
399     RefPtr<OverlayElement>& overlay, RefPtr<Animator>& controller, TweenOption& option, TransitionEvent event)
400 {
401     if (!SharedTransitionEffect::ApplyAnimation(overlay, controller, option, event)) {
402         return false;
403     }
404     Offset ticket;
405     // the dest page and source page elements are in effect
406     auto current = GetCurrentSharedElement();
407     if (!CheckIn(event, current, ticket)) {
408         LOGE("Apply static fail. check in failed. event: %{public}d, share id: %{public}s", event, shareId_.c_str());
409         return false;
410     }
411     AddLazyLoadCallback();
412     return TakeOff(event, overlay, current, ticket, option);
413 }
414 
AddLazyLoadCallback()415 void SharedTransitionStatic::AddLazyLoadCallback()
416 {
417     auto current = GetCurrentSharedElement().Upgrade();
418     if (!current) {
419         LOGE("Add Lazy load Callback failed. current is null.");
420         return;
421     }
422     current->SetSizeModified([effectWeak = WeakClaim(this)]() {
423         auto effect = effectWeak.Upgrade();
424         if (!effect) {
425             LOGE("Fix static shared element position failed. effect is null");
426             return;
427         }
428         auto tweenSeatElement = effect->tweenSeatElement_.Upgrade();
429         if (!tweenSeatElement) {
430             LOGE("Fix static shared element position failed. tween element is null");
431             return;
432         }
433         auto positionedElement =
434             AceType::DynamicCast<PositionedElement>(tweenSeatElement->GetElementParent().Upgrade());
435         if (!positionedElement) {
436             LOGE("Fix static shared element position failed. positioned element is null");
437             return;
438         }
439         auto positionedRender = AceType::DynamicCast<RenderPositioned>(positionedElement->GetRenderNode());
440         if (!positionedRender) {
441             LOGE("Fix static shared element position failed. positioned render is null");
442             return;
443         }
444         auto dest = effect->currentWorking_.Upgrade();
445         if (!dest) {
446             return;
447         }
448         auto offset = dest->GetGlobalOffset();
449         positionedRender->SetTop(Dimension(offset.GetY(), DimensionUnit::PX));
450         positionedRender->SetLeft(Dimension(offset.GetX(), DimensionUnit::PX));
451     });
452 }
453 
454 } // namespace OHOS::Ace
455