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_controller.h"
17 
18 #include "core/components/overlay/overlay_element.h"
19 #include "core/components/page_transition/page_transition_element.h"
20 
21 namespace OHOS::Ace {
22 namespace {
23 
GetSharedEffect(const ShareId & shareId,const WeakPtr<SharedTransitionElement> & destWeak,const WeakPtr<SharedTransitionElement> & srcWeak)24 RefPtr<SharedTransitionEffect> GetSharedEffect(const ShareId& shareId, const WeakPtr<SharedTransitionElement>& destWeak,
25     const WeakPtr<SharedTransitionElement>& srcWeak)
26 {
27     auto dest = destWeak.Upgrade();
28     auto src = srcWeak.Upgrade();
29     if ((!src) && (!dest)) {
30         return nullptr;
31     }
32     RefPtr<SharedTransitionEffect> effect = dest ? dest->GetEffect() : nullptr;
33     if (!effect) {
34         effect = src ? src->GetEffect() : nullptr;
35     }
36     if (!effect) {
37         // use default effect.
38         effect = SharedTransitionEffect::GetSharedTransitionEffect(
39             SharedTransitionEffectType::SHARED_EFFECT_EXCHANGE, shareId);
40     }
41     return effect;
42 }
43 
44 } // namespace
45 
SharedTransitionController(const WeakPtr<PipelineContext> & context)46 SharedTransitionController::SharedTransitionController(const WeakPtr<PipelineContext>& context) : context_(context)
47 {
48 };
49 
RegisterTransitionListener()50 void SharedTransitionController::RegisterTransitionListener()
51 {
52     auto pipelineContext = context_.Upgrade();
53     if (!pipelineContext) {
54         LOGE("Register Transition listener to stage failed. pipeline context is null.");
55         return;
56     }
57     auto weak = AceType::WeakClaim(this);
58     pipelineContext->AddPageTransitionListener(
59         [weak](const TransitionEvent& event, const WeakPtr<PageElement>& in, const WeakPtr<PageElement>& out) {
60             if ((event != TransitionEvent::POP_START) && (event != TransitionEvent::PUSH_START)) {
61                 return;
62             }
63             auto controller = weak.Upgrade();
64             if (!controller) {
65                 LOGE("handle event: %{public}d failed. controller is null.", event);
66                 return;
67             }
68             auto context = controller->context_.Upgrade();
69             if (!context) {
70                 LOGE("Add shared transition to pipeline failed. context is null.");
71                 return;
72             }
73             if (event == TransitionEvent::PUSH_START) {
74                 controller->pageDest_ = in;
75                 controller->pageSrc_ = out;
76             } else {
77                 controller->pageDest_ = out;
78                 controller->pageSrc_ = in;
79             }
80             controller->event_ = event;
81 
82             // when page pushed in, new pushed page's layout parameters is valid after perform layout.
83             // And shared transition needs new pushed page's layout parameters.
84             // So start shared transition in prepare animation.
85             controller->StartSharedTransition();
86         });
87 }
88 
StartSharedTransition()89 void SharedTransitionController::StartSharedTransition()
90 {
91     // finish previous transition
92     for (const auto& controller : controllers_) {
93         if (controller) {
94             controller->Finish();
95             controller->ClearAllListeners();
96             controller->ClearInterpolators();
97         }
98     }
99     controllers_.clear();
100     stopControllerCount_ = 0;
101 
102     auto pipelineContext = context_.Upgrade();
103     if (!pipelineContext) {
104         LOGE("Start shared transition failed. pipeline is null.");
105         return;
106     }
107     auto overlay = pipelineContext->GetOverlayElement();
108     if (!overlay) {
109         LOGE("Start shared transition failed. overlay is null.");
110         return;
111     }
112     KickoffSharedTransition(event_, overlay);
113 }
114 
KickoffSharedTransition(TransitionEvent event,RefPtr<OverlayElement> & overlay)115 void SharedTransitionController::KickoffSharedTransition(TransitionEvent event, RefPtr<OverlayElement>& overlay)
116 {
117     auto pageDest = pageDest_.Upgrade();
118     if (!pageDest) {
119         return;
120     }
121     auto destTransition = PageTransitionElement::GetTransitionElement(pageDest);
122     auto pageId = pageDest->GetPageId();
123     if (!destTransition) {
124         LOGE("Kickoff shared transition failed. page transition is null. page id: %{public}d", pageId);
125         return;
126     }
127     hasSharedTransition_ = PrepareTransition(overlay);
128     if (!hasSharedTransition_) {
129         return;
130     }
131 
132     if (!controllers_.empty()) {
133         controllers_.front()->AddStartListener([overlayWeak = WeakClaim(RawPtr(overlay))]() {
134             auto overlay = overlayWeak.Upgrade();
135             if (overlay) {
136                 auto overlayRender = overlay->GetRenderNode();
137                 if (overlayRender) {
138                     overlayRender->SetVisible(true);
139                 }
140             }
141         });
142     }
143 
144     for (const auto& controller : controllers_) {
145         if (controller) {
146             controller->SetFillMode(FillMode::FORWARDS);
147             controller->SetAllowRunningAsynchronously(true);
148             controller->AddStopListener([effectWeak = WeakClaim(this), overlayWeak = WeakClaim(RawPtr(overlay))]() {
149                 auto effect = effectWeak.Upgrade();
150                 if (!effect) {
151                     LOGE("effect is null.");
152                     return;
153                 }
154                 effect->stopControllerCount_++;
155                 if (static_cast<uint32_t>(effect->stopControllerCount_) >= effect->controllers_.size()) {
156                     auto overlay = overlayWeak.Upgrade();
157                     if (overlay) {
158                         // shared element will be removed when get off shuttle, just make sure no shared left on the
159                         // overlay
160                         overlay->Clear();
161                         auto overlayRender = overlay->GetRenderNode();
162                         if (overlayRender) {
163                             overlayRender->SetVisible(false);
164                         }
165                     }
166                 }
167             });
168         }
169     }
170 }
171 
CheckAndCreateTransition(std::vector<RefPtr<SharedTransitionEffect>> & effects,RefPtr<OverlayElement> & overlay)172 bool SharedTransitionController::CheckAndCreateTransition(
173     std::vector<RefPtr<SharedTransitionEffect>>& effects, RefPtr<OverlayElement>& overlay)
174 {
175     bool hasShared = false;
176     for (auto& effect : effects) {
177         const auto& shareId = effect->GetShareId();
178         if (!effect->Allow(event_)) {
179             LOGE("Shared transition not allowed, event: %{public}d, share id: %{public}s", event_, shareId.c_str());
180             continue;
181         }
182         if (!PrepareEachTransition(shareId, effect, overlay)) {
183             LOGE("Prepare shared transition failed. share id: %{public}s", shareId.c_str());
184             continue;
185         }
186         hasShared = true;
187     }
188     return hasShared;
189 }
190 
PrepareTransition(RefPtr<OverlayElement> overlay,bool preCheck)191 bool SharedTransitionController::PrepareTransition(RefPtr<OverlayElement> overlay, bool preCheck)
192 {
193     auto pageDest = pageDest_.Upgrade();
194     auto pageSrc = pageSrc_.Upgrade();
195     if ((!pageSrc) || (!pageDest)) {
196         return false;
197     }
198     const auto& srcMap = pageSrc->GetSharedTransitionMap();
199     const auto& destMap = pageDest->GetSharedTransitionMap();
200     bool hasShared = false;
201     std::vector<RefPtr<SharedTransitionEffect>> effects;
202     std::vector<RefPtr<SharedTransitionEffect>> anchorEffects;
203 
204     // find out all exchange effect or static effect in dest page
205     for (auto& item : destMap) {
206         auto shareId = item.first;
207         auto& destWeak = item.second;
208         auto srcSharedIter = srcMap.find(shareId);
209         WeakPtr<SharedTransitionElement> srcWeak;
210         if (srcSharedIter == srcMap.end()) {
211         } else {
212             srcWeak = srcSharedIter->second;
213         }
214         RefPtr<SharedTransitionEffect> effect = GetSharedEffect(shareId, destWeak, srcWeak);
215         if (!effect) {
216             continue;
217         }
218         if (preCheck) {
219             // Return true, when find the first shared transition.
220             return true;
221         }
222         effect->SetSharedElement(srcWeak, destWeak);
223         effect->setCurrentSharedElement(destWeak);
224         if (effect->GetType() == SharedTransitionEffectType::SHARED_EFFECT_STATIC) {
225             anchorEffects.push_back(effect);
226         } else {
227             effects.push_back(effect);
228         }
229     }
230 
231     // find out all static effect in source page only in ace declarative
232     auto context = context_.Upgrade();
233     if (context && context->GetIsDeclarative()) {
234         for (auto& item : srcMap) {
235             auto sharedId = item.first;
236             auto& sourceWeak = item.second;
237             RefPtr<SharedTransitionEffect> effect = GetSharedEffect(sharedId, nullptr, sourceWeak);
238             if (!effect || effect->GetType() != SharedTransitionEffectType::SHARED_EFFECT_STATIC) {
239                 LOGE(
240                     "Shared effect is null or type is not static, maybe no shared element at all. share id: %{public}s",
241                     sharedId.c_str());
242                 continue;
243             }
244             if (preCheck) {
245                 // Return true, when find the first shared transition.
246                 return true;
247             }
248             effect->SetSharedElement(sourceWeak, nullptr);
249             effect->setCurrentSharedElement(sourceWeak);
250             if (effect->GetType() == SharedTransitionEffectType::SHARED_EFFECT_STATIC) {
251                 anchorEffects.push_back(effect);
252             } else {
253                 effects.push_back(effect);
254             }
255         }
256     }
257 
258     // prepare each sharedTransition effect
259     hasShared = CheckAndCreateTransition(effects, overlay);
260     bool needsAnchor = hasShared || (context && !context->GetIsDeclarative());
261     if (needsAnchor) {
262         // anchor effects only available when other effects are available
263         hasShared = CheckAndCreateTransition(anchorEffects, overlay) || hasShared;
264     }
265     return hasShared;
266 }
267 
PrepareEachTransition(const ShareId & shareId,RefPtr<SharedTransitionEffect> & effect,RefPtr<OverlayElement> & overlay)268 bool SharedTransitionController::PrepareEachTransition(
269     const ShareId& shareId, RefPtr<SharedTransitionEffect>& effect, RefPtr<OverlayElement>& overlay)
270 {
271     auto currentWeak = effect->GetCurrentSharedElement();
272     auto current = currentWeak.Upgrade();
273     if (!current) {
274         LOGE("Prepare each transition failed. dest is null. share id: %{public}s", shareId.c_str());
275         return false;
276     }
277     auto option = current->GetOption();
278     if (!effect->CreateAnimation(option, event_, false)) {
279         LOGE("Create animation failed. event: %{public}d, share id: %{public}s", event_, shareId.c_str());
280         return false;
281     }
282     auto tmp = CREATE_ANIMATOR();
283     if (!effect->ApplyAnimation(overlay, tmp, option, event_)) {
284         LOGE("Apply animation failed. event: %{public}d, share id: %{public}s", event_, shareId.c_str());
285         return false;
286     }
287     auto animator = effect->GetAnimator();
288     if (!animator) {
289         LOGE("GetAnimator failed. event: %{public}d, share id: %{public}s", event_, shareId.c_str());
290         return false;
291     }
292     controllers_.push_back(animator);
293     current->SetVisible(false);
294     animator->AddStopListener([currentWeak, shareId, event = event_]() {
295         auto current = currentWeak.Upgrade();
296         if (!current) {
297             LOGE("Stop shared element failed. shared in element is null. event: %{public}d, shareId: %{public}s", event,
298                 shareId.c_str());
299             return;
300         }
301         current->SetVisible(true);
302     });
303     return true;
304 }
305 
HasSharedTransition(TransitionEvent event)306 bool SharedTransitionController::HasSharedTransition(TransitionEvent event)
307 {
308     if (event_ == event) {
309         return hasSharedTransition_;
310     } else {
311         auto originEvent = event_;
312         event_ = event;
313         bool hasSharedTransition = PrepareTransition(nullptr, true);
314         event_ = originEvent;
315         return hasSharedTransition;
316     }
317 }
318 
319 } // namespace OHOS::Ace
320