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