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