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