1 /*
2  * Copyright (c) 2021-2022 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/marquee/render_marquee.h"
17 
18 #include "base/log/event_report.h"
19 #include "core/components/marquee/marquee_component.h"
20 
21 namespace OHOS::Ace {
22 namespace {
23 
24 // Defines how long delay will be between each jump.
25 constexpr double DEFAULT_MARQUEE_SCROLL_DELAY = 85.0;
26 
IsPlayingAnimation(const RefPtr<Animator> & controller)27 bool IsPlayingAnimation(const RefPtr<Animator>& controller)
28 {
29     return (controller->GetStatus() == Animator::Status::RUNNING);
30 }
31 
32 } // namespace
33 
Start()34 void RenderMarquee::Start()
35 {
36     if (!NeedMarquee()) {
37         return;
38     }
39     if ((!childText_) || (!controller_)) {
40         startAfterLayout_ = true;
41         LOGW("Node has not built yet, animation will start after layout.");
42         return;
43     }
44     if (isHidden_) {
45         startAfterShowed_ = true;
46         LOGW("Marquee is hidden, animation will start when showed.");
47         return;
48     }
49     if (controller_->GetStatus() == Animator::Status::PAUSED) {
50         controller_->Resume();
51     } else if (controller_->GetStatus() != Animator::Status::RUNNING) {
52         // Start first loop
53         currentLoop_ = 1;
54         UpdateAnimation();
55         if (needAnimation_) {
56             controller_->Play();
57         }
58     }
59 }
60 
Stop()61 void RenderMarquee::Stop()
62 {
63     startAfterLayout_ = false;
64     startAfterShowed_ = false;
65     if (!controller_) {
66         LOGE("Node has not initialized.");
67         return;
68     }
69     if (!IsPlayingAnimation(controller_)) {
70         return;
71     }
72     controller_->Pause();
73 }
74 
OnHiddenChanged(bool hidden)75 void RenderMarquee::OnHiddenChanged(bool hidden)
76 {
77     isHidden_ = hidden;
78     if (!controller_) {
79         return;
80     }
81     if (hidden) {
82         if (IsPlayingAnimation(controller_)) {
83             startAfterShowed_ = true;
84             controller_->Pause();
85         }
86     } else {
87         if (startAfterShowed_) {
88             startAfterShowed_ = false;
89             Start();
90         }
91     }
92 }
93 
UpdateAnimation()94 void RenderMarquee::UpdateAnimation()
95 {
96     double from = 0.0;
97     double to = 0.0;
98     if (direction_ == MarqueeDirection::LEFT) {
99         from = GetLayoutSize().Width();
100         to = -childText_->GetLayoutSize().Width();
101     } else {
102         from = -childText_->GetLayoutSize().Width();
103         to = GetLayoutSize().Width();
104     }
105     // "scrollAmount_" won't be zero, since it's initialized at Update().
106     int32_t duration = static_cast<int32_t>(std::abs(to - from) * DEFAULT_MARQUEE_SCROLL_DELAY / scrollAmount_);
107     if (duration <= 0) {
108         needAnimation_ = false;
109         LOGI("Animation duration is negative, don't need animation.");
110         return;
111     }
112     needAnimation_ = true;
113     if (translate_) {
114         controller_->RemoveInterpolator(translate_);
115     }
116     translate_ = AceType::MakeRefPtr<CurveAnimation<double>>(from, to, Curves::LINEAR);
117     auto weak = AceType::WeakClaim(this);
118     translate_->AddListener(Animation<double>::ValueCallback([weak](double value) {
119         auto marquee = weak.Upgrade();
120         if (marquee) {
121             marquee->UpdateChildPosition(value);
122         }
123     }));
124     controller_->SetDuration(duration);
125     controller_->AddInterpolator(translate_);
126 }
127 
UpdateChildPosition(double position)128 void RenderMarquee::UpdateChildPosition(double position)
129 {
130     childPosition_ = Offset(position, 0.0);
131     childText_->SetPosition(childPosition_);
132     MarkNeedRender();
133 }
134 
OnAnimationStart()135 void RenderMarquee::OnAnimationStart()
136 {
137     if (startEvent_) {
138         startEvent_();
139     }
140     if (onStartEvent_) {
141         onStartEvent_();
142     }
143 }
144 
OnAnimationStop()145 void RenderMarquee::OnAnimationStop()
146 {
147     if (bounceEvent_) {
148         bounceEvent_();
149     }
150     if (onBounceEvent_) {
151         onBounceEvent_();
152     }
153     bool continueAnimation = false;
154     if (loop_ <= 0) {
155         // Infinite loop
156         continueAnimation = true;
157     } else {
158         if (currentLoop_ < loop_) {
159             currentLoop_++;
160             continueAnimation = true;
161         } else {
162             // All loop finished
163             if (finishEvent_) {
164                 finishEvent_();
165             }
166             if (onFinishEvent_) {
167                 onFinishEvent_();
168                 playerFinishControl_ = true;
169             }
170         }
171     }
172     if (continueAnimation) {
173         UpdateAnimation();
174         if (needAnimation_) {
175             controller_->Play();
176         }
177     }
178 }
179 
Update(const RefPtr<Component> & component)180 void RenderMarquee::Update(const RefPtr<Component>& component)
181 {
182     const RefPtr<MarqueeComponent> marquee = AceType::DynamicCast<MarqueeComponent>(component);
183     if (!marquee) {
184         LOGE("marquee component is null");
185         EventReport::SendRenderException(RenderExcepType::RENDER_COMPONENT_ERR);
186         return;
187     }
188     GetMarqueeCallback(component);
189     value_ = marquee->GetValue();
190     start_ = marquee->GetPlayerStatus();
191     if (start_) {
192         startAfterLayout_ = true;
193     }
194     textStyle_ = marquee->GetTextStyle();
195     auto context = GetContext().Upgrade();
196     if (!context) {
197         LOGE("context is null");
198         return;
199     }
200     if (context->UseLiteStyle()) {
201         // lite loop time is 1000ms, while default marquee loop is 85ms.
202         scrollAmount_ = marquee->GetScrollAmount() * DEFAULT_MARQUEE_SCROLL_DELAY / 1000;
203     } else {
204         scrollAmount_ = marquee->GetScrollAmount();
205     }
206     if (LessOrEqual(scrollAmount_, 0.0)) {
207         scrollAmount_ = DEFAULT_MARQUEE_SCROLL_AMOUNT;
208     }
209     currentLoop_ = 1;
210     loop_ = marquee->GetLoop();
211     if (loop_ <= 0) {
212         loop_ = ANIMATION_REPEAT_INFINITE;
213     }
214     auto textDir = GetTextDirection(marquee->GetValue());
215     bool systemRTL = context != nullptr ? context->IsRightToLeft() : false;
216     bool isRTL = textDir == TextDirection::INHERIT ? systemRTL : (textDir == TextDirection::RTL);
217     direction_ =
218         isRTL ? (marquee->GetDirection() == MarqueeDirection::LEFT ? MarqueeDirection::RIGHT : MarqueeDirection::LEFT)
219               : marquee->GetDirection();
220 
221     bounceEvent_ = AceAsyncEvent<void()>::Create(marquee->GetBounceEventId(), context_);
222     finishEvent_ = AceAsyncEvent<void()>::Create(marquee->GetFinishEventId(), context_);
223     startEvent_ = AceAsyncEvent<void()>::Create(marquee->GetStartEventId(), context_);
224     if (playerFinishControl_) {
225         playerFinishControl_ = false;
226     }
227     bool playStatus = playerFinishControl_ ? false : marquee->GetPlayerStatus();
228     const auto& methodController = marquee->GetController();
229     if (methodController) {
230         auto weak = AceType::WeakClaim(this);
231         methodController->SetFunction(
232             [weak]() {
233                 auto marquee = weak.Upgrade();
234                 if (marquee) {
235                     marquee->Start();
236                 }
237             },
238             [weak]() {
239                 auto marquee = weak.Upgrade();
240                 if (marquee) {
241                     marquee->Stop();
242                 }
243             });
244         GetPlayerCtr(component, context->GetIsDeclarative(), playStatus);
245     }
246     if (!controller_) {
247         controller_ = CREATE_ANIMATOR(GetContext());
248         auto weak = AceType::WeakClaim(this);
249         controller_->AddStartListener(Animator::StatusCallback([weak]() {
250             auto marquee = weak.Upgrade();
251             if (marquee) {
252                 marquee->OnAnimationStart();
253             }
254         }));
255         controller_->AddStopListener(Animator::StatusCallback([weak]() {
256             auto marquee = weak.Upgrade();
257             if (marquee) {
258                 marquee->OnAnimationStop();
259             }
260         }));
261     }
262     MarkNeedLayout();
263 }
264 
PerformLayout()265 void RenderMarquee::PerformLayout()
266 {
267     const auto& children = GetChildren();
268     if (children.empty()) {
269         LOGW("Marquee has no text child!");
270         return;
271     }
272     // Layout child text, child's width has no limit, marquee's width is constrained by parent.
273     LayoutParam innerLayout;
274     innerLayout.SetMaxSize(Size(Size::INFINITE_SIZE, GetLayoutParam().GetMaxSize().Height()));
275     innerLayout.SetMinSize(GetLayoutParam().GetMinSize());
276     childText_ = children.front();
277     ACE_DCHECK(childText_);
278     childText_->Layout(innerLayout);
279     lastLayoutSize_ = GetLayoutSize();
280     Size layoutSize = GetLayoutParam().Constrain(Size(Size::INFINITE_SIZE, childText_->GetLayoutSize().Height()));
281     if (childPosition_.IsErrorOffset() || lastLayoutSize_ != layoutSize) {
282         // Initialize child position.
283         if (direction_ == MarqueeDirection::LEFT) {
284             childPosition_ = Offset(layoutSize.Width(), 0.0);
285         } else {
286             childPosition_ = Offset(-childText_->GetLayoutSize().Width(), 0.0);
287         }
288     }
289     childText_->SetPosition(childPosition_);
290     SetLayoutSize(layoutSize);
291     if (!NeedMarquee()) {
292         Stop();
293         childText_->SetPosition(GetTextPosition());
294         return;
295     }
296     if (lastLayoutSize_ != childText_->GetLayoutSize() && IsPlayingAnimation(controller_)) {
297         UpdateAnimation();
298     }
299     if (startAfterLayout_) {
300         startAfterLayout_ = false;
301         Start();
302     }
303 }
304 
GetPlayerCtr(const RefPtr<Component> & component,bool emdit,bool control)305 void RenderMarquee::GetPlayerCtr(const RefPtr<Component>& component, bool emdit, bool control)
306 {
307     const RefPtr<MarqueeComponent> marquee = AceType::DynamicCast<MarqueeComponent>(component);
308     if (!marquee) {
309         LOGE("marquee component is null");
310         EventReport::SendRenderException(RenderExcepType::RENDER_COMPONENT_ERR);
311         return;
312     }
313 
314     const auto& methodController = marquee->GetController();
315     if (emdit) {
316         if (control) {
317             methodController->Start();
318         } else {
319             methodController->Stop();
320             playerFinishControl_ = false;
321         }
322     }
323 }
324 
GetMarqueeCallback(const RefPtr<Component> & component)325 void RenderMarquee::GetMarqueeCallback(const RefPtr<Component>& component)
326 {
327     const RefPtr<MarqueeComponent> marquee = AceType::DynamicCast<MarqueeComponent>(component);
328     if (!marquee) {
329         LOGE("marquee component is null");
330         EventReport::SendRenderException(RenderExcepType::RENDER_COMPONENT_ERR);
331         return;
332     }
333 
334     if (marquee->GetOnStart()) {
335         onStartEvent_ = *marquee->GetOnStart();
336     }
337     if (marquee->GetOnBounce()) {
338         onBounceEvent_ = *marquee->GetOnBounce();
339     }
340     if (marquee->GetOnFinish()) {
341         onFinishEvent_ = *marquee->GetOnFinish();
342     }
343 }
344 
NeedMarquee() const345 bool RenderMarquee::NeedMarquee() const
346 {
347     if (!childText_) {
348         return true;
349     }
350     auto context = GetContext().Upgrade();
351     if (!context) {
352         return true;
353     }
354     // Is width of text longer than container.
355     auto needMarquee = (childText_->GetLayoutSize().Width() > GetLayoutSize().Width());
356     if (context->GetIsDeclarative()) {
357         return needMarquee;
358     }
359     const static int32_t PLATFORM_VERSION_SIX = 6;
360     if (context->GetMinPlatformVersion() >= PLATFORM_VERSION_SIX) {
361         return needMarquee;
362     }
363     return true;
364 }
365 
GetTextPosition() const366 Offset RenderMarquee::GetTextPosition() const
367 {
368     if (!childText_) {
369         return Offset(0.0, 0.0);
370     }
371 
372     auto textWidth = childText_->GetLayoutSize().Width();
373     auto marqueeWidth = GetLayoutSize().Width();
374     if (GreatOrEqual(textWidth, marqueeWidth)) {
375         return Offset(0.0, 0.0);
376     }
377 
378     auto textAlign = textStyle_.GetTextAlign();
379     // adjust START and END
380     if (textAlign == TextAlign::START) {
381         textAlign = RenderNode::GetTextDirection() == TextDirection::LTR ? TextAlign::LEFT : TextAlign::RIGHT;
382     }
383     if (textAlign == TextAlign::END) {
384         textAlign = RenderNode::GetTextDirection() == TextDirection::LTR ? TextAlign::RIGHT : TextAlign::LEFT;
385     }
386 
387     const static double HALF_DIVIDE = 2.0;
388     if (textAlign == TextAlign::CENTER) {
389         return Offset((marqueeWidth - textWidth) / HALF_DIVIDE, 0.0);
390     } else if (textAlign == TextAlign::RIGHT) {
391         return Offset(marqueeWidth - textWidth, 0.0);
392     } else {
393         return Offset(0.0, 0.0);
394     }
395 }
396 
397 } // namespace OHOS::Ace
398