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/card_transition_controller.h"
17 
18 #include "core/components/list/render_list.h"
19 #include "core/components/transform/transform_element.h"
20 
21 namespace OHOS::Ace {
22 namespace {
23 
24 constexpr float OPACITY_CHANGE_END = 0.286f;
25 constexpr float OPACITY_CHANGE_START = 0.571f;
26 
27 } // namespace
28 
CardTransitionController(const WeakPtr<PipelineContext> & context)29 CardTransitionController::CardTransitionController(const WeakPtr<PipelineContext>& context) : context_(context)
30 {
31     controller_ = CREATE_ANIMATOR(context);
32 };
33 
RegisterTransitionListener()34 void CardTransitionController::RegisterTransitionListener()
35 {
36     auto pipelineContext = context_.Upgrade();
37     if (!pipelineContext) {
38         LOGE("Register Transition listener to stage failed. pipeline context is null.");
39         return;
40     }
41     auto weak = AceType::WeakClaim(this);
42     pipelineContext->AddPageTransitionListener(
43         [weak](const TransitionEvent& event, const WeakPtr<PageElement>& in, const WeakPtr<PageElement>& out) {
44             if ((event != TransitionEvent::POP_START) && (event != TransitionEvent::PUSH_START)) {
45                 return;
46             }
47             auto controller = weak.Upgrade();
48             if (!controller) {
49                 LOGE("handle event: %{public}d failed. controller is null.", event);
50                 return;
51             }
52             if (event == TransitionEvent::PUSH_START) {
53                 controller->pageDest_ = in;
54                 controller->pageSrc_ = out;
55             } else {
56                 controller->pageDest_ = out;
57                 controller->pageSrc_ = in;
58             }
59             controller->event_ = event;
60             controller->PrepareTransition();
61         });
62 }
63 
PrepareTransition()64 void CardTransitionController::PrepareTransition()
65 {
66     // finish previous transition
67     controller_->Finish();
68     // clear controller
69     controller_->ClearAllListeners();
70     controller_->ClearInterpolators();
71     controller_->ClearPrepareListeners();
72     auto pipelineContext = context_.Upgrade();
73     if (!pipelineContext) {
74         LOGE("prepare card transition failed. pipeline is null.");
75         return;
76     }
77     auto pageDest = (event_ == TransitionEvent::PUSH_START) ? pageDest_.Upgrade() : pageSrc_.Upgrade();
78     if (!pageDest || pageDest->GetCardComposeId().empty()) {
79         return;
80     }
81     auto composeId = pageDest->GetCardComposeId();
82     auto cardComposeElement = pipelineContext->GetComposedElementById(composeId);
83     if (!cardComposeElement) {
84         LOGE("prepare card transition failed. cardComposeElement is null.");
85         return;
86     }
87     auto cardRender = cardComposeElement->GetRenderNode();
88     if (!cardRender) {
89         LOGE("prepare card transition failed. cardRender is null.");
90         return;
91     }
92     auto rrect = GetCardRect(composeId);
93     CreateCardAnimation(cardComposeElement, cardRender->GetGlobalOffset(), rrect);
94     CreateCardListAnimation(cardRender);
95     CreateExternalAnimation(cardRender->GetLayoutSize().Height(), cardRender->GetGlobalOffset().GetY());
96 }
97 
CreateCardAnimation(const RefPtr<Element> & cardComposeElement,const Offset & globalOffset,const RRect & rrect)98 void CardTransitionController::CreateCardAnimation(const RefPtr<Element>& cardComposeElement,
99     const Offset& globalOffset, const RRect& rrect)
100 {
101     auto listItemElement = cardComposeElement->GetFirstChild();
102     if (!listItemElement) {
103         LOGE("create card animation failed. listItemElement is null.");
104         return;
105     }
106     auto displayElement = AceType::DynamicCast<DisplayElement>(listItemElement->GetFirstChild());
107     if (!displayElement) {
108         LOGE("create card animation failed. displayElement is null.");
109         return;
110     }
111     auto displayRender = displayElement->GetRenderNode();
112     if (!displayRender) {
113         LOGE("create card animation failed. displayRender is null.");
114         return;
115     }
116     auto transformElement = GetCardTransformElement(cardComposeElement);
117     if (!transformElement) {
118         LOGE("create card animation failed. transformElement is null.");
119         return;
120     }
121     auto transformRender = AceType::DynamicCast<RenderTransform>(transformElement->GetRenderNode());
122     if (!transformRender) {
123         LOGE("create card animation failed. transformRender is null.");
124         return;
125     }
126     auto weakTransform = AceType::WeakClaim(AceType::RawPtr(transformRender));
127     CreateCardScaleAnimation(weakTransform, rrect);
128     CreateCardTranslateAnimation(weakTransform, globalOffset, rrect.GetRect().GetOffset() - globalOffset);
129     CreateCardOpacityAnimation(displayRender);
130     AddPrepareListener(weakTransform);
131     AddStopListener(weakTransform);
132 }
133 
CreateCardTranslateAnimation(const WeakPtr<RenderTransform> & weakTransform,const Offset & globalOffset,const Offset & marginOffset)134 void CardTransitionController::CreateCardTranslateAnimation(const WeakPtr<RenderTransform>& weakTransform,
135     const Offset& globalOffset, const Offset& marginOffset)
136 {
137     auto pipelineContext = context_.Upgrade();
138     if (!pipelineContext) {
139         LOGE("create card translate animation failed. pipeline is null.");
140         return;
141     }
142     Offset startOffset = (event_ == TransitionEvent::PUSH_START) ? Offset() : Offset() - (globalOffset + marginOffset);
143     Offset endOffset = (event_ == TransitionEvent::PUSH_START) ? Offset() - (globalOffset + marginOffset) : Offset();
144     auto offsetAnimation = AceType::MakeRefPtr<CurveAnimation<Offset>>(startOffset, endOffset, Curves::FRICTION);
145     offsetAnimation->AddListener([weakTransform](const Offset& offset) {
146         auto transform = weakTransform.Upgrade();
147         if (transform) {
148             transform->Translate(Dimension(offset.GetX()), Dimension(offset.GetY()));
149         }
150     });
151     controller_->AddInterpolator(offsetAnimation);
152 }
153 
CreateCardScaleAnimation(const WeakPtr<RenderTransform> & weakTransform,const RRect & rrect)154 void CardTransitionController::CreateCardScaleAnimation(const WeakPtr<RenderTransform>& weakTransform,
155     const RRect& rrect)
156 {
157     auto pipelineContext = context_.Upgrade();
158     if (!pipelineContext) {
159         LOGE("create card scale animation failed. pipeline is null.");
160         return;
161     }
162     auto transform = weakTransform.Upgrade();
163     if (!transform) {
164         LOGE("create card scale animation failed. transform is null.");
165         return;
166     }
167     auto width = rrect.Width();
168     if (NearEqual(width, 0.0)) {
169         LOGE("create card animation failed. width is zero.");
170         return;
171     }
172     transform->SetDisableClickEffect(true);
173     transform->ResetTransform();
174     transform->SetTransformOrigin(Dimension(), Dimension());
175     transform->MarkNeedUpdateOrigin();
176 
177     double startScale = (event_ == TransitionEvent::PUSH_START) ? 1.0 : pipelineContext->GetRootWidth() / width;
178     double endScale = (event_ == TransitionEvent::PUSH_START) ? pipelineContext->GetRootWidth() / width : 1.0;
179     auto scaleAnimation = AceType::MakeRefPtr<CurveAnimation<double>>(startScale, endScale, Curves::FRICTION);
180     scaleAnimation->AddListener([weakTransform](double value) {
181         auto transform = weakTransform.Upgrade();
182         if (transform) {
183             transform->Scale(value);
184         }
185     });
186     controller_->AddInterpolator(scaleAnimation);
187 }
188 
CreateCardOpacityAnimation(RefPtr<RenderNode> & displayRender)189 void CardTransitionController::CreateCardOpacityAnimation(RefPtr<RenderNode>& displayRender)
190 {
191     auto opacityAnimation = AceType::MakeRefPtr<KeyframeAnimation<uint8_t>>();
192     if (event_ == TransitionEvent::PUSH_START) {
193         auto opacityKeyframe1 = AceType::MakeRefPtr<Keyframe<uint8_t>>(0.0f, 255);
194         auto opacityKeyframe2 = AceType::MakeRefPtr<Keyframe<uint8_t>>(OPACITY_CHANGE_END, 0);
195         opacityKeyframe2->SetCurve(Curves::SHARP);
196         auto opacityKeyframe3 = AceType::MakeRefPtr<Keyframe<uint8_t>>(1.0f, 0);
197         opacityAnimation->AddKeyframe(opacityKeyframe1);
198         opacityAnimation->AddKeyframe(opacityKeyframe2);
199         opacityAnimation->AddKeyframe(opacityKeyframe3);
200     } else {
201         auto opacityKeyframe1 = AceType::MakeRefPtr<Keyframe<uint8_t>>(0.0f, 0);
202         auto opacityKeyframe2 = AceType::MakeRefPtr<Keyframe<uint8_t>>(OPACITY_CHANGE_START, 0);
203         auto opacityKeyframe3 = AceType::MakeRefPtr<Keyframe<uint8_t>>(1.0f, 255);
204         opacityKeyframe3->SetCurve(Curves::SHARP);
205         opacityAnimation->AddKeyframe(opacityKeyframe1);
206         opacityAnimation->AddKeyframe(opacityKeyframe2);
207         opacityAnimation->AddKeyframe(opacityKeyframe3);
208     }
209     opacityAnimation->AddListener([displayWeak = AceType::WeakClaim(AceType::RawPtr(displayRender))](uint8_t value) {
210         auto display = displayWeak.Upgrade();
211         if (display) {
212             display->UpdateOpacity(value);
213         }
214     });
215     controller_->AddInterpolator(opacityAnimation);
216 }
217 
CreateCardListAnimation(const RefPtr<RenderNode> & renderNode)218 void CardTransitionController::CreateCardListAnimation(const RefPtr<RenderNode>& renderNode)
219 {
220     auto renderListItem = AceType::DynamicCast<RenderListItem>(renderNode);
221     if (!renderListItem) {
222         return;
223     }
224     auto height = renderListItem->GetLayoutSize().Height();
225     auto start = (event_ == TransitionEvent::PUSH_START) ? 0.0 : height;
226     auto end = (event_ == TransitionEvent::PUSH_START) ? height : 0.0;
227     auto heightAnimation = AceType::MakeRefPtr<CurveAnimation<double>>(start, end, Curves::FRICTION);
228     auto listItemWeak = AceType::WeakClaim(AceType::RawPtr(renderListItem));
229     heightAnimation->AddListener([listItemWeak](double value) {
230         auto renderListItem = listItemWeak.Upgrade();
231         if (renderListItem) {
232             renderListItem->RunCardTransitionAnimation(value);
233         }
234     });
235     controller_->AddInterpolator(heightAnimation);
236     controller_->AddStopListener([listItemWeak] {
237         auto renderListItem = listItemWeak.Upgrade();
238         if (renderListItem) {
239             renderListItem->StopCardTransitionAnimation();
240         }
241     });
242 }
243 
CreateExternalAnimation(double height,double cardOffsetY)244 void CardTransitionController::CreateExternalAnimation(double height, double cardOffsetY)
245 {
246     auto pageSrc = (event_ == TransitionEvent::PUSH_START) ? pageSrc_.Upgrade() : pageDest_.Upgrade();
247     if (!pageSrc) {
248         return;
249     }
250     const auto& transformMap = pageSrc->GetCardTransitionMap();
251     for (auto& item : transformMap) {
252         auto& transformWeak = item.second;
253         auto transformElement = transformWeak.Upgrade();
254         if (!transformElement) {
255             continue;
256         }
257         auto transformRender = AceType::DynamicCast<RenderTransform>(transformElement->GetRenderNode());
258         if (!transformRender) {
259             continue;
260         }
261         auto transformOffsetY = transformRender->GetGlobalOffset().GetY();
262         if (NearEqual(transformOffsetY, cardOffsetY)) {
263             continue;
264         }
265         auto shiftHeight = height;
266         if (transformOffsetY < cardOffsetY) {
267             shiftHeight = -height;
268         }
269         auto start = (event_ == TransitionEvent::PUSH_START) ? 0.0 : shiftHeight;
270         auto end = (event_ == TransitionEvent::PUSH_START) ? shiftHeight : 0.0;
271         auto positionAnimation = AceType::MakeRefPtr<CurveAnimation<double>>(start, end, Curves::FRICTION);
272         auto weakTransform = AceType::WeakClaim(AceType::RawPtr(transformRender));
273         positionAnimation->AddListener([weakTransform](double value) {
274             auto transform = weakTransform.Upgrade();
275             if (transform) {
276                 transform->ResetTransform();
277                 transform->Translate(Dimension(), Dimension(value));
278             }
279         });
280         controller_->AddInterpolator(positionAnimation);
281         controller_->AddStopListener([weakTransform, event = event_] {
282             auto transform = weakTransform.Upgrade();
283             if (transform && event == TransitionEvent::PUSH_START) {
284                 transform->ResetTransform();
285             }
286         });
287     }
288 }
289 
AddPrepareListener(const WeakPtr<RenderTransform> & weakTransform)290 void CardTransitionController::AddPrepareListener(const WeakPtr<RenderTransform>& weakTransform)
291 {
292     controller_->AddPrepareListener([weakTransform, weakContext = context_, weak = AceType::WeakClaim(this)]() {
293         auto context = weakContext.Upgrade();
294         auto cardTransition = weak.Upgrade();
295         auto transform = weakTransform.Upgrade();
296         if (context && cardTransition && transform) {
297             auto currentTimestamp = context->GetTimeFromExternalTimer();
298             if (cardTransition->currentTimestamp_ != currentTimestamp || cardTransition->currentTimestamp_ == 0) {
299                 transform->ResetTransform();
300                 cardTransition->currentTimestamp_ = currentTimestamp;
301             }
302         }
303     });
304 }
305 
AddStopListener(const WeakPtr<RenderTransform> & weakTransform)306 void CardTransitionController::AddStopListener(const WeakPtr<RenderTransform>& weakTransform)
307 {
308     controller_->AddStopListener([weakTransform] {
309         auto transform = weakTransform.Upgrade();
310         if (transform) {
311             transform->ResetTransform();
312             transform->SetTransformOrigin(Dimension(transform->GetLayoutSize().Width() * 0.5),
313                 Dimension(transform->GetLayoutSize().Height() * 0.5));
314             transform->MarkNeedUpdateOrigin();
315             transform->SetDisableClickEffect(false);
316         }
317     });
318 }
319 
GetCardTransformElement(const RefPtr<Element> & element)320 RefPtr<Element> CardTransitionController::GetCardTransformElement(const RefPtr<Element>& element)
321 {
322     auto childElement = element->GetFirstChild();
323     while (childElement) {
324         auto composedElement =  AceType::DynamicCast<ComposedElement>(childElement);
325         if (composedElement) {
326             return nullptr;
327         }
328         auto transformElement =  AceType::DynamicCast<TransformElement>(childElement);
329         if (transformElement) {
330             return transformElement;
331         }
332         childElement = childElement->GetFirstChild();
333     }
334     return nullptr;
335 }
336 
GetCardRect(const ComposeId & composeId) const337 RRect CardTransitionController::GetCardRect(const ComposeId& composeId) const
338 {
339     auto pipelineContext = context_.Upgrade();
340     if (!pipelineContext) {
341         LOGE("get card rect failed. pipeline is null.");
342         return RRect();
343     }
344     auto cardComposeElement = pipelineContext->GetComposedElementById(composeId);
345     if (!cardComposeElement) {
346         return RRect();
347     }
348     auto renderListItem = AceType::DynamicCast<RenderListItem>(cardComposeElement->GetRenderNode());
349     if (!renderListItem) {
350         return RRect();
351     }
352     return renderListItem->GetRRect();
353 }
354 
355 } // namespace OHOS::Ace
356