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