1 /*
2 * Copyright (c) 2022-2023 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/components_ng/manager/shared_overlay/shared_overlay_manager.h"
17
18 #include <sstream>
19
20 #include "base/memory/referenced.h"
21 #include "base/utils/utils.h"
22 #include "core/components_ng/base/frame_node.h"
23 #include "core/components_ng/pattern/stage/page_pattern.h"
24 #include "core/components_ng/property/calc_length.h"
25 #include "core/pipeline/base/element_register.h"
26 #include "core/pipeline/pipeline_base.h"
27
28 namespace OHOS::Ace::NG {
29
30 namespace {
GetSharedEffect(const ShareId & shareId,const WeakPtr<FrameNode> & destWeak,const WeakPtr<FrameNode> & srcWeak)31 RefPtr<SharedTransitionEffect> GetSharedEffect(
32 const ShareId& shareId, const WeakPtr<FrameNode>& destWeak, const WeakPtr<FrameNode>& srcWeak)
33 {
34 auto dest = destWeak.Upgrade();
35 auto src = srcWeak.Upgrade();
36 if ((!src) && (!dest)) {
37 return nullptr;
38 }
39 std::shared_ptr<SharedTransitionOption> options;
40 if (dest && dest->GetRenderContext()->HasSharedTransitionOption()) {
41 options = dest->GetRenderContext()->GetSharedTransitionOption();
42 } else if (src && src->GetRenderContext()->HasSharedTransitionOption()) {
43 options = src->GetRenderContext()->GetSharedTransitionOption();
44 }
45 if (!options) {
46 // use default transition params
47 const int32_t defaultDuration = 1000;
48 options = std::make_shared<SharedTransitionOption>();
49 options->curve = Curves::LINEAR;
50 options->duration = defaultDuration;
51 }
52 if (options->type == SharedTransitionEffectType::SHARED_EFFECT_EXCHANGE && (!src || !dest)) {
53 return nullptr;
54 }
55 return SharedTransitionEffect::GetSharedTransitionEffect(shareId, options);
56 }
57
CreateBlankFrameNode(const RefPtr<FrameNode> & node)58 RefPtr<FrameNode> CreateBlankFrameNode(const RefPtr<FrameNode>& node)
59 {
60 auto pattern = AceType::MakeRefPtr<Pattern>();
61 auto nodeId = ElementRegister::GetInstance()->MakeUniqueId();
62 auto newNode = FrameNode::CreateFrameNode(node->GetTag(), nodeId, pattern);
63 newNode->SetGeometryNode(node->GetGeometryNode()->Clone());
64 auto frameSize = node->GetGeometryNode()->GetFrameSize();
65 // set size so the node will keep its size
66 newNode->GetLayoutProperty()->UpdateUserDefinedIdealSize(
67 CalcSize(CalcLength(frameSize.Width()), CalcLength(frameSize.Height())));
68 return newNode;
69 }
70
ReplaceFrameNode(const RefPtr<FrameNode> & node,const RefPtr<FrameNode> & newNode)71 bool ReplaceFrameNode(const RefPtr<FrameNode>& node, const RefPtr<FrameNode>& newNode)
72 {
73 auto parent = node->GetParent();
74 CHECK_NULL_RETURN(parent, false);
75 parent->ReplaceChild(node, newNode);
76 parent->RebuildRenderContextTree();
77 return true;
78 }
79
80 } // namespace
81
StartSharedTransition(const RefPtr<FrameNode> & pageSrc,const RefPtr<FrameNode> & pageDest)82 void SharedOverlayManager::StartSharedTransition(const RefPtr<FrameNode>& pageSrc, const RefPtr<FrameNode>& pageDest)
83 {
84 CHECK_NULL_VOID(pageSrc);
85 CHECK_NULL_VOID(pageDest);
86 auto patternSrc = pageSrc->GetPattern<PagePattern>();
87 CHECK_NULL_VOID(patternSrc);
88 auto patternDest = pageDest->GetPattern<PagePattern>();
89 CHECK_NULL_VOID(patternDest);
90 patternSrc->BuildSharedTransitionMap();
91 patternDest->BuildSharedTransitionMap();
92 PrepareSharedTransition(pageSrc, pageDest);
93 auto pipeline = PipelineBase::GetCurrentContext();
94 for (const auto& effect : effects_) {
95 auto controller = effect->GetController();
96 if (controller) {
97 TAG_LOGD(AceLogTag::ACE_ANIMATION, "sharedTransition starts, shareId = %{public}s, id = %{public}d",
98 effect->GetShareId().c_str(), effect->GetController()->GetId());
99 controller->SetFillMode(FillMode::FORWARDS);
100 controller->SetAllowRunningAsynchronously(true);
101 controller->AttachScheduler(pipeline);
102 controller->Forward();
103 }
104 }
105 }
106
PrepareSharedTransition(const RefPtr<FrameNode> & pageSrc,const RefPtr<FrameNode> & pageDest)107 void SharedOverlayManager::PrepareSharedTransition(const RefPtr<FrameNode>& pageSrc, const RefPtr<FrameNode>& pageDest)
108 {
109 ClearAllEffects();
110 auto patternDest = pageDest->GetPattern<PagePattern>();
111 CHECK_NULL_VOID(patternDest);
112 auto patternSrc = pageSrc->GetPattern<PagePattern>();
113 CHECK_NULL_VOID(patternSrc);
114 pageOffset_ = pageDest->GetRenderContext()->GetPaintRectWithoutTransform().GetOffset();
115 const auto& srcMap = patternSrc->GetSharedTransitionMap();
116 const auto& destMap = patternDest->GetSharedTransitionMap();
117 std::list<RefPtr<SharedTransitionEffect>> effects;
118 std::list<RefPtr<SharedTransitionEffect>> anchorEffects;
119
120 // find out all exchange effect or static effect in dest page
121 for (const auto& item : destMap) {
122 const auto& shareId = item.first;
123 const auto& destWeak = item.second;
124 auto srcSharedIter = srcMap.find(shareId);
125 WeakPtr<FrameNode> srcWeak;
126 if (srcSharedIter != srcMap.end()) {
127 srcWeak = srcSharedIter->second;
128 }
129 RefPtr<SharedTransitionEffect> effect = GetSharedEffect(shareId, destWeak, srcWeak);
130 if (!effect) {
131 continue;
132 }
133 effect->SetSharedNode(srcWeak, destWeak);
134 if (effect->GetType() == SharedTransitionEffectType::SHARED_EFFECT_STATIC) {
135 anchorEffects.emplace_back(effect);
136 } else {
137 effects.emplace_back(effect);
138 }
139 }
140 // find out all static effect in source page
141 for (const auto& item : srcMap) {
142 const auto& sharedId = item.first;
143 const auto& sourceWeak = item.second;
144 RefPtr<SharedTransitionEffect> effect = GetSharedEffect(sharedId, nullptr, sourceWeak);
145 if (!effect || effect->GetType() != SharedTransitionEffectType::SHARED_EFFECT_STATIC) {
146 continue;
147 }
148 auto destSharedIter = destMap.find(sharedId);
149 if (destSharedIter != destMap.end()) {
150 // src is static, it has been added to effects in previous for loop
151 continue;
152 }
153 effect->SetSharedNode(sourceWeak, nullptr);
154 anchorEffects.emplace_back(effect);
155 }
156 if (effects.size()) {
157 TAG_LOGD(AceLogTag::ACE_ANIMATION,
158 "prepare sharedTransition, effectSize:%{public}zu, anchorEffectSize:%{public}zu, srcMap "
159 "size:%{public}zu, destMap size:%{public}zu",
160 effects.size(), anchorEffects.size(), srcMap.size(), destMap.size());
161 }
162 // prepare each sharedTransition effect
163 CheckAndPrepareTransition(effects, effects_);
164 CheckAndPrepareTransition(anchorEffects, effects_);
165 if (!effects_.empty()) {
166 sharedManager_->RebuildRenderContextTree();
167 }
168 }
169
CheckAndPrepareTransition(std::list<RefPtr<SharedTransitionEffect>> & effects,std::list<RefPtr<SharedTransitionEffect>> & effectiveEffects)170 void SharedOverlayManager::CheckAndPrepareTransition(
171 std::list<RefPtr<SharedTransitionEffect>>& effects, std::list<RefPtr<SharedTransitionEffect>>& effectiveEffects)
172 {
173 for (auto& effect : effects) {
174 const auto& shareId = effect->GetShareId();
175 if (!effect->Allow()) {
176 continue;
177 }
178 if (!PrepareEachTransition(effect)) {
179 TAG_LOGI(
180 AceLogTag::ACE_ANIMATION, "Prepare shared transition failed. share id: %{public}s", shareId.c_str());
181 continue;
182 }
183 if (!CheckIn(effect)) {
184 continue;
185 }
186 effectiveEffects.emplace_back(effect);
187 }
188 }
189
PrepareEachTransition(const RefPtr<SharedTransitionEffect> & effect)190 bool SharedOverlayManager::PrepareEachTransition(const RefPtr<SharedTransitionEffect>& effect)
191 {
192 if (!effect->CreateAnimation()) {
193 return false;
194 }
195 if (!effect->ApplyAnimation()) {
196 return false;
197 }
198 return true;
199 }
200
ClearAllEffects()201 void SharedOverlayManager::ClearAllEffects()
202 {
203 while (!effects_.empty()) {
204 auto& effect = effects_.front();
205 effect->StopPlayingEffect();
206 effects_.pop_front();
207 }
208 }
209
CheckIn(const RefPtr<SharedTransitionEffect> & effect)210 bool SharedOverlayManager::CheckIn(const RefPtr<SharedTransitionEffect>& effect)
211 {
212 // Check-in
213 if (!AboardShuttle(effect)) {
214 return false;
215 }
216 const auto& controller = effect->GetController();
217 CHECK_NULL_RETURN(controller, false);
218 // Arrange Return Shuttle
219 controller->AddStopListener([weak = WeakClaim(this), effectWeak = WeakPtr<SharedTransitionEffect>(effect)]() {
220 auto shared = weak.Upgrade();
221 CHECK_NULL_VOID(shared);
222 auto effect = effectWeak.Upgrade();
223 shared->GetOffShuttle(effect);
224 });
225 return true;
226 }
227
PassengerAboard(const RefPtr<SharedTransitionEffect> & effect,const RefPtr<FrameNode> & passenger)228 void SharedOverlayManager::PassengerAboard(
229 const RefPtr<SharedTransitionEffect>& effect, const RefPtr<FrameNode>& passenger)
230 {
231 auto ticket = passenger->GetPaintRectOffsetToPage();
232 // Get offset relative to stage(or overlay), for safeArea
233 ticket += pageOffset_;
234 TAG_LOGD(AceLogTag::ACE_ANIMATION, "Transition passenger offset is %{public}s, id = %{public}s",
235 ticket.ToString().c_str(), effect->GetShareId().c_str());
236 auto initialPosition = passenger->GetRenderContext()->GetPosition();
237 // save initialFrameOffset for static type sharedTransition
238 auto initialFrameOffset = passenger->GetGeometryNode()->GetFrameOffset();
239 const auto& initialMarginPtr = passenger->GetLayoutProperty()->GetMarginProperty();
240 auto initialMargin = initialMarginPtr ? std::make_optional<MarginProperty>(*initialMarginPtr) : std::nullopt;
241 auto zIndex = passenger->GetRenderContext()->GetZIndex();
242 effect->SetPassengerInitZIndex(zIndex);
243 effect->SetPassengerInitPos(initialPosition);
244 effect->SetPassengerInitFrameOffset(initialFrameOffset);
245 effect->SetPassengerInitMargin(initialMargin);
246 bool isPassengerCurrentFocused = false;
247 auto passengerFocusHub = passenger->GetFocusHub();
248 if (passengerFocusHub) {
249 isPassengerCurrentFocused = passengerFocusHub->IsCurrentFocus();
250 }
251 effect->SetPassengerCurrentFocused(isPassengerCurrentFocused);
252 auto passengerHolder = CreateBlankFrameNode(passenger);
253 passengerHolder->GetLayoutProperty()->UpdateVisibility(VisibleType::INVISIBLE);
254 ReplaceFrameNode(passenger, passengerHolder);
255 effect->SetPassengerHolder(passengerHolder);
256 sharedManager_->AddChild(passenger);
257 auto offset = OffsetT<Dimension>(Dimension(ticket.GetX()), Dimension(ticket.GetY()));
258 if (initialMargin) {
259 passenger->GetLayoutProperty()->UpdateMargin(MarginProperty());
260 passenger->GetLayoutProperty()->CleanDirty();
261 }
262 passenger->GetRenderContext()->UpdateZIndex(effect->GetZIndex());
263 passenger->GetRenderContext()->UpdatePosition(offset);
264 passenger->GetRenderContext()->OnModifyDone();
265 passenger->GetEventHub<EventHub>()->SetEnabledInternal(false);
266 }
267
AboardShuttle(const RefPtr<SharedTransitionEffect> & effect)268 bool SharedOverlayManager::AboardShuttle(const RefPtr<SharedTransitionEffect>& effect)
269 {
270 auto passenger = effect->GetPassengerNode().Upgrade();
271 if (!passenger) {
272 return false;
273 }
274 if (effect->GetType() == SharedTransitionEffectType::SHARED_EFFECT_EXCHANGE) {
275 // passenger is src
276 auto dest = effect->GetDestSharedNode().Upgrade();
277 if (!dest) {
278 return false;
279 }
280 PassengerAboard(effect, passenger);
281 auto exchangeEffect = AceType::DynamicCast<SharedTransitionExchange>(effect);
282 auto destVisible = dest->GetLayoutProperty()->GetVisibilityValue(VisibleType::VISIBLE);
283 exchangeEffect->SetInitialDestVisible(destVisible);
284 exchangeEffect->SetVisibleToDest(VisibleType::INVISIBLE);
285 } else {
286 PassengerAboard(effect, passenger);
287 }
288 return true;
289 }
290
GetOffShuttle(const RefPtr<SharedTransitionEffect> & effect)291 void SharedOverlayManager::GetOffShuttle(const RefPtr<SharedTransitionEffect>& effect)
292 {
293 CHECK_NULL_VOID(effect);
294 TAG_LOGD(AceLogTag::ACE_ANIMATION, "Animation off shuttle, id: %{public}s", effect->GetShareId().c_str());
295 auto passenger = effect->GetPassengerNode().Upgrade();
296 CHECK_NULL_VOID(passenger);
297 sharedManager_->RemoveChild(passenger);
298 sharedManager_->RebuildRenderContextTree();
299 auto passengerHolder = effect->GetPassengerHolder().Upgrade();
300 if (passengerHolder) {
301 // restore the position and zIndex of passenger frameNode
302 passenger->GetGeometryNode()->SetFrameOffset(effect->GetPassengerInitFrameOffset());
303 if (effect->GetPassengerInitPos().has_value()) {
304 passenger->GetRenderContext()->UpdatePosition(effect->GetPassengerInitPos().value());
305 } else {
306 passenger->GetRenderContext()->ResetPositionProperty();
307 passenger->GetRenderContext()->OnPositionUpdate(OffsetT<Dimension>());
308 }
309 if (effect->GetPassengerInitZIndex().has_value()) {
310 passenger->GetRenderContext()->UpdateZIndex(effect->GetPassengerInitZIndex().value());
311 } else {
312 passenger->GetRenderContext()->ResetZIndex();
313 passenger->GetRenderContext()->OnZIndexUpdate(0);
314 }
315 if (effect->GetPassengerInitMargin().has_value()) {
316 passenger->GetLayoutProperty()->UpdateMargin(effect->GetPassengerInitMargin().value());
317 passenger->MarkDirtyNode();
318 }
319 // restore initialFrameOffset for static type sharedTransition, because it may not layout again
320 ReplaceFrameNode(passengerHolder, passenger);
321 passenger->GetEventHub<EventHub>()->RestoreEnabled();
322 auto isPassengerCurrentFocused = effect->GetPassengerCurrentFocused();
323 if (isPassengerCurrentFocused) {
324 auto passengerFocusHub = passenger->GetFocusHub();
325 if (passengerFocusHub) {
326 passengerFocusHub->RequestFocusImmediately();
327 }
328 }
329 // The callback is to restore the parameters used by passenger in the animation
330 effect->PerformFinishCallback();
331 passenger->GetRenderContext()->OnModifyDone();
332 }
333 if (effect->GetType() == SharedTransitionEffectType::SHARED_EFFECT_EXCHANGE) {
334 // restore the visibility of dest frameNode
335 auto exchangeEffect = AceType::DynamicCast<SharedTransitionExchange>(effect);
336 auto destVisible = exchangeEffect->GetInitialDestVisible();
337 exchangeEffect->SetVisibleToDest(destVisible);
338 exchangeEffect->DestRequestDefaultFocus();
339 }
340 }
341
OnBackPressed()342 bool SharedOverlayManager::OnBackPressed()
343 {
344 bool inSharedTransition = false;
345 for (const auto& effect : effects_) {
346 if (effect->GetController()->IsRunning()) {
347 inSharedTransition = true;
348 break;
349 }
350 }
351 return inSharedTransition;
352 }
353
StopSharedTransition()354 void SharedOverlayManager::StopSharedTransition()
355 {
356 for (const auto& effect : effects_) {
357 auto controller = effect->GetController();
358 if (controller->IsRunning()) {
359 // When two new pages switch, let controller finishes, so passenger can go back to the original page.
360 controller->Finish();
361 }
362 }
363 effects_.clear();
364 }
365
366 } // namespace OHOS::Ace::NG
367