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/progress/render_loading_progress.h"
17
18 #include "base/log/event_report.h"
19 #include "base/log/log.h"
20 #include "core/animation/curve_animation.h"
21 #include "core/animation/keyframe_animation.h"
22 #include "core/components/progress/progress_theme.h"
23
24 namespace OHOS::Ace {
25 namespace {
26
27 constexpr double LOOP_DEGREES = 360.0;
28 constexpr double TAIL_ALPHA_RATIO = 0.82;
29 constexpr int32_t LOOP_DURATION = 1200;
30 constexpr int32_t MOVE_DURATION = LOOP_DURATION / 12; // Comet move without tail.
31 constexpr int32_t TAIL_DURATION = LOOP_DURATION / 4; // Comet move with tail getting longer.
32 constexpr double DRAG_ANGLE_BEGIN = -15.0;
33 constexpr double DRAG_ANGLE_RANGE = 30.0;
34 constexpr double RING_SCALE_BEGIN = 0.5;
35 constexpr double RING_SCALE_RANGE = 0.5;
36 constexpr double MOVE_START = DRAG_ANGLE_BEGIN + DRAG_ANGLE_RANGE;
37 constexpr double MOVE_END = MOVE_START + LOOP_DEGREES / LOOP_DURATION * MOVE_DURATION;
38 constexpr double TAIL_END = MOVE_END + LOOP_DEGREES / LOOP_DURATION * TAIL_DURATION;
39 constexpr double LOOP_END = TAIL_END + LOOP_DEGREES;
40 constexpr int32_t START_POINT = 0;
41 constexpr int32_t MIDDLE_POINT = 1;
42 constexpr int32_t END_POINT = 2;
43 constexpr double CENTER_POINT = 2.0;
44 const Dimension MODE_SMALL = 16.0_vp;
45 const Dimension MODE_MIDDLE = 40.0_vp;
46 const Dimension MODE_LARGE = 76.0_vp;
47 const Dimension MODE_COMET_RADIUS[] = { 3.0_vp, 3.0_vp, 2.2_vp };
48 const Dimension MODE_RING_WIDTH[] = { 2.8_vp, 1.9_vp, 1.2_vp };
49 const Dimension MODE_RING_BLUR_RADIUS[] = { 0.5_vp, 0.2_vp, 0.1_vp };
50 const Dimension MODE_RING_BG_WIDTH[] = { 3.0_vp, 3.0_vp, 2.0_vp };
51 const Dimension MODE_RING_BG_BLUR_RADIUS[] = { 2.0_vp, 2.0_vp, 2.0_vp };
52
53 } // namespace
54
RenderLoadingProgress()55 RenderLoadingProgress::RenderLoadingProgress() : RenderNode(true) {}
56
Update(const RefPtr<Component> & component)57 void RenderLoadingProgress::Update(const RefPtr<Component>& component)
58 {
59 auto loadingProgress = AceType::DynamicCast<LoadingProgressComponent>(component);
60 if (!loadingProgress) {
61 LOGE("Update with nullptr");
62 EventReport::SendRenderException(RenderExcepType::RENDER_COMPONENT_ERR);
63 return;
64 }
65 progressColor_ = loadingProgress->GetProgressColor();
66 moveRatio_ = loadingProgress->GetMoveRatio();
67 cometTailLen_ = loadingProgress->GetCometTailLen();
68 diameterDimension_ = loadingProgress->GetDiameter();
69 ringRadiusDimension_ = loadingProgress->GetRingRadius();
70 orbitRadiusDimension_ = loadingProgress->GetOrbitRadius();
71 MarkNeedLayout();
72 }
73
UpdateRingAnimation()74 void RenderLoadingProgress::UpdateRingAnimation()
75 {
76 auto ringMove = AceType::MakeRefPtr<KeyframeAnimation<float>>();
77 ringMove->AddListener([weak = WeakClaim(this)](double value) {
78 auto loading = weak.Upgrade();
79 if (loading) {
80 loading->ringOffset_.SetY(value * loading->scale_);
81 if (loading->GetVisible() && !loading->GetHidden()) {
82 loading->MarkNeedRender();
83 }
84 }
85 });
86 double moveRange = ringRadius_ * moveRatio_ * 2.0;
87 auto keyframe1 = AceType::MakeRefPtr<Keyframe<float>>(0.0f, 0.0f);
88 auto keyframe2 = AceType::MakeRefPtr<Keyframe<float>>(0.25f, -moveRange);
89 auto keyframe3 = AceType::MakeRefPtr<Keyframe<float>>(0.75f, moveRange);
90 auto keyframe4 = AceType::MakeRefPtr<Keyframe<float>>(1.0f, 0.0f);
91 keyframe2->SetCurve(AceType::MakeRefPtr<CubicCurve>(0.0f, 0.0f, 0.67f, 1.0f));
92 keyframe3->SetCurve(AceType::MakeRefPtr<CubicCurve>(0.33f, 0.0f, 0.67f, 1.0f));
93 keyframe4->SetCurve(AceType::MakeRefPtr<CubicCurve>(0.33f, 0.0f, 1.0f, 1.0f));
94 ringMove->AddKeyframe(keyframe1);
95 ringMove->AddKeyframe(keyframe2);
96 ringMove->AddKeyframe(keyframe3);
97 ringMove->AddKeyframe(keyframe4);
98 ringController_->ClearInterpolators();
99 ringController_->AddInterpolator(ringMove);
100 ringController_->SetIteration(ANIMATION_REPEAT_INFINITE);
101 ringController_->SetDuration(LOOP_DURATION);
102 if (GetVisible() && !GetHidden()) {
103 ringController_->Play();
104 }
105 }
106
UpdateCometAnimation()107 void RenderLoadingProgress::UpdateCometAnimation()
108 {
109 auto cometMoveStart = AceType::MakeRefPtr<CurveAnimation<float>>(MOVE_START, MOVE_END,
110 AceType::MakeRefPtr<CubicCurve>(0.6f, 0.2f, 1.0f, 1.0f));
111 cometMoveStart->AddListener([weak = AceType::WeakClaim(this)](double value) {
112 auto loading = weak.Upgrade();
113 if (loading) {
114 CometParam para;
115 para.angular = value;
116 loading->cometParams_.clear();
117 loading->cometParams_.emplace_back(para);
118 loading->UpdateCometParams();
119 }
120 });
121 cometController_->ClearInterpolators();
122 cometController_->AddInterpolator(cometMoveStart);
123 cometController_->SetIteration(1);
124 cometController_->SetDuration(MOVE_DURATION);
125 cometController_->SetFillMode(FillMode::FORWARDS);
126 cometController_->ClearStopListeners();
127 moveStopId_ = cometController_->AddStopListener([weak = AceType::WeakClaim(this)]() {
128 auto loading = weak.Upgrade();
129 if (loading) {
130 loading->DoCometTailAnimation();
131 }
132 });
133 if (GetVisible() && !GetHidden()) {
134 cometController_->Play();
135 }
136 }
137
DoCometTailAnimation()138 void RenderLoadingProgress::DoCometTailAnimation()
139 {
140 auto cometMoveTail = AceType::MakeRefPtr<CurveAnimation<float>>(0.0, cometTailLen_, Curves::LINEAR);
141 cometMoveTail->AddListener([weak = AceType::WeakClaim(this)](double value) {
142 auto loading = weak.Upgrade();
143 if (loading) {
144 loading->cometCurTail_ = value;
145 if (loading->moveStopId_ != 0 && loading->cometController_) {
146 loading->cometController_->RemoveStopListener(loading->moveStopId_);
147 loading->moveStopId_ = 0;
148 }
149 }
150 });
151 auto cometMoveDegree = AceType::MakeRefPtr<CurveAnimation<float>>(MOVE_END, TAIL_END, Curves::LINEAR);
152 cometMoveDegree->AddListener([weak = AceType::WeakClaim(this)](double value) {
153 auto loading = weak.Upgrade();
154 if (loading) {
155 double step = loading->cometTailLen_ / loading->cometCount_;
156 int32_t count = 0;
157 while (count < loading->cometCount_) {
158 double curStep = std::min(count * step, value);
159 if (count < (int32_t)loading->cometParams_.size()) {
160 loading->cometParams_[count].angular = value - curStep;
161 } else {
162 CometParam para;
163 para.angular = value - curStep;
164 loading->cometParams_.emplace_back(para);
165 }
166 if (count * step >= loading->cometCurTail_) {
167 break;
168 }
169 count++;
170 }
171 loading->UpdateCometParams();
172 }
173 });
174 cometController_->ClearInterpolators();
175 cometController_->AddInterpolator(cometMoveTail);
176 cometController_->AddInterpolator(cometMoveDegree);
177 cometController_->SetIteration(1);
178 cometController_->SetDuration(TAIL_DURATION);
179 cometController_->SetFillMode(FillMode::FORWARDS);
180 tailStopId_ = cometController_->AddStopListener([weak = AceType::WeakClaim(this)]() {
181 auto loading = weak.Upgrade();
182 if (loading) {
183 loading->DoCometLoopAnimation();
184 }
185 });
186 if (GetVisible() && !GetHidden()) {
187 cometController_->Play();
188 }
189 }
190
DoCometLoopAnimation()191 void RenderLoadingProgress::DoCometLoopAnimation()
192 {
193 auto cometLoopDegree = AceType::MakeRefPtr<CurveAnimation<float>>(TAIL_END, LOOP_END, Curves::LINEAR);
194 cometLoopDegree->AddListener([weak = AceType::WeakClaim(this)](double value) {
195 auto loading = weak.Upgrade();
196 if (loading) {
197 int32_t count = 0;
198 double step = loading->cometTailLen_ / loading->cometCount_;
199 for (auto& para : loading->cometParams_) {
200 para.angular = value - count * step;
201 count++;
202 }
203 loading->UpdateCometParams();
204 if (loading->tailStopId_ != 0 && loading->cometController_) {
205 loading->cometController_->RemoveStopListener(loading->tailStopId_);
206 loading->tailStopId_ = 0;
207 }
208 }
209 });
210 cometController_->ClearInterpolators();
211 cometController_->AddInterpolator(cometLoopDegree);
212 cometController_->SetIteration(ANIMATION_REPEAT_INFINITE);
213 cometController_->SetDuration(LOOP_DURATION);
214 if (GetVisible() && !GetHidden()) {
215 cometController_->Play();
216 }
217 }
218
UpdateCometParams()219 void RenderLoadingProgress::UpdateCometParams()
220 {
221 if (cometParams_.empty()) {
222 return;
223 }
224 int32_t count = 0;
225 float alpha = 0.0f;
226 for (auto& para : cometParams_) {
227 if (count == 0) { // Update Head Comet Parameter.
228 para.scale = GetCometScaleByDegree(para.angular);
229 para.alpha = floor(UINT8_MAX * GetCometAlphaByDegree(cometParams_[0].angular));
230 } else { // Update Tail Comets Parameter.
231 para.scale = GetCometScaleByDegree(para.angular);
232 para.alpha = floor(alpha);
233 }
234 alpha = para.alpha * TAIL_ALPHA_RATIO;
235 count++;
236 }
237 }
238
GetCometScaleByDegree(double degree)239 float RenderLoadingProgress::GetCometScaleByDegree(double degree)
240 {
241 // Scale Curve::LINEAR Degrees( 0 - 180) --> Scale(100% - 65%)
242 // Scale Curve::LINEAR Degrees(180 - 360) --> Scale( 65% - 100%)
243 if (degree > 360.0) {
244 degree = degree - 360.0;
245 }
246 if (degree >= 0.0 && degree <= 180.0) {
247 return 1.0 - 0.35 * degree / 180.0;
248 }
249 if (degree >= 180.0 && degree <= 360.0) {
250 return 0.65 + 0.35 * (degree - 180.0) / 180.0;
251 }
252 return 1.0f;
253 }
254
GetCometAlphaByDegree(double degree)255 float RenderLoadingProgress::GetCometAlphaByDegree(double degree)
256 {
257 // Alpha Curve::LINEAR Degrees( 0 - 15) --> Alpha(100% - 100%)
258 // Alpha Curve::LINEAR Degrees( 15 - 180) --> Scale(100% - 20%)
259 // Alpha Curve::LINEAR Degrees(180 - 345) --> Scale( 20% - 100%)
260 // Alpha Curve::LINEAR Degrees(345 - 360) --> Scale(100% - 100%)
261 if (degree > 360.0) {
262 degree = degree - 360.0;
263 }
264 if (degree >= 15.0 && degree <= 180.0) {
265 return 1.0 - 0.8 * (degree - 15.0) / (180.0 - 15.0);
266 } else if (degree >= 180.0 && degree <= 345.0) {
267 return 0.2 + 0.8 * (degree - 180.0) / (345.0 - 180.0);
268 } else {
269 return 1.0f;
270 }
271 }
272
SetLoadingMode(int32_t mode)273 void RenderLoadingProgress::SetLoadingMode(int32_t mode)
274 {
275 if (loadingMode_ == mode) {
276 return;
277 }
278
279 LOGI("SetLoadingMode to %{public}d", mode);
280 loadingMode_ = mode;
281 MarkNeedLayout();
282 if (loadingMode_ != MODE_DRAG) {
283 return;
284 }
285 if (ringController_ && cometController_) {
286 ringController_->Stop();
287 cometController_->Stop();
288 ringController_->ClearStopListeners();
289 cometController_->ClearStopListeners();
290 ringController_->Finish();
291 cometController_->Finish();
292 ringController_ = nullptr;
293 cometController_ = nullptr;
294 moveStopId_ = 0;
295 tailStopId_ = 0;
296 }
297 }
298
SetDragRange(double minDistance,double maxDistance)299 void RenderLoadingProgress::SetDragRange(double minDistance, double maxDistance)
300 {
301 minDistance_ = minDistance;
302 maxDistance_ = maxDistance;
303 }
304
SetDragDistance(double distance)305 void RenderLoadingProgress::SetDragDistance(double distance)
306 {
307 distance = std::clamp(distance, minDistance_, maxDistance_);
308 if (NearEqual(curDistance_, distance)) {
309 return;
310 }
311 curDistance_ = distance;
312 double percent = (curDistance_ - minDistance_) / (maxDistance_ - minDistance_);
313 double scale = RING_SCALE_BEGIN + RING_SCALE_RANGE * percent;
314 switch (loadingMode_) {
315 case MODE_LOOP: {
316 return;
317 }
318 case MODE_DRAG: {
319 exitScale_ = 1.0;
320 exitAlpha_ = 1.0;
321 dragScale_ = scale;
322 dragAlpha_ = percent;
323 // Update Comet Para when drag distance changed.
324 CometParam para;
325 para.alpha = floor(UINT8_MAX * dragAlpha_);
326 para.angular = DRAG_ANGLE_BEGIN + DRAG_ANGLE_RANGE * percent;
327 if (para.angular < 0.0) {
328 para.angular = para.angular + 360.0;
329 }
330 cometParams_.clear();
331 cometParams_.emplace_back(para);
332 break;
333 }
334 case MODE_EXIT: {
335 dragScale_ = 1.0;
336 dragAlpha_ = 1.0;
337 exitScale_ = scale;
338 exitAlpha_ = percent;
339 break;
340 }
341 default: {
342 LOGW("Unsupported loading mode:%{public}d.", loadingMode_);
343 break;
344 }
345 }
346 if (GetVisible() && !GetHidden()) {
347 MarkNeedRender();
348 }
349 }
350
PerformLayout()351 void RenderLoadingProgress::PerformLayout()
352 {
353 // the diameter will be constrain by layout size.
354 diameter_ = NormalizeToPx(diameterDimension_);
355 ringRadius_ = NormalizeToPx(ringRadiusDimension_);
356 orbitRadius_ = NormalizeToPx(orbitRadiusDimension_);
357 Size layoutSize;
358 if (!NearEqual(diameter_, 0.0)) {
359 layoutSize = GetLayoutParam().Constrain(Size(diameter_, diameter_));
360 } else {
361 if (GetLayoutParam().GetMaxSize().IsInfinite()) {
362 double defaultDiameter = 0.0;
363 auto theme = GetTheme<ProgressTheme>();
364 if (theme) {
365 defaultDiameter = NormalizeToPx(theme->GetLoadingDiameter());
366 }
367 layoutSize = Size(defaultDiameter, defaultDiameter);
368 } else {
369 layoutSize = GetLayoutParam().GetMaxSize();
370 }
371 }
372 SetLayoutSize(layoutSize);
373 UpdateLoadingSize(std::min(layoutSize.Width(), layoutSize.Height()));
374 center_ = Offset(layoutSize.Width() / CENTER_POINT, layoutSize.Height() / CENTER_POINT);
375 scale_ = std::min(layoutSize.Width() / (orbitRadius_ + cometRadius_) / CENTER_POINT,
376 layoutSize.Height() / ringRadius_ / CENTER_POINT);
377 auto pipelineContext = GetContext().Upgrade();
378 if (pipelineContext && loadingMode_ != MODE_DRAG && !ringController_ && !cometController_) {
379 ringController_ = AceType::MakeRefPtr<Animator>(pipelineContext);
380 cometController_ = AceType::MakeRefPtr<Animator>(pipelineContext);
381 UpdateRingAnimation();
382 UpdateCometAnimation();
383 AnimationChanged();
384 }
385 }
386
UpdateLoadingSize(double diameter)387 void RenderLoadingProgress::UpdateLoadingSize(double diameter)
388 {
389 if (diameter <= NormalizeToPx(MODE_SMALL)) {
390 CalculateValue(START_POINT, START_POINT);
391 } else if (diameter <= NormalizeToPx(MODE_MIDDLE)) {
392 CalculateValue(START_POINT, MIDDLE_POINT,
393 (diameter - NormalizeToPx(MODE_SMALL)) / (NormalizeToPx(MODE_MIDDLE) - NormalizeToPx(MODE_SMALL)));
394 } else if (diameter <= NormalizeToPx(MODE_LARGE)) {
395 CalculateValue(MIDDLE_POINT, END_POINT,
396 (diameter - NormalizeToPx(MODE_MIDDLE)) / (NormalizeToPx(MODE_LARGE) - NormalizeToPx(MODE_MIDDLE)));
397 } else {
398 CalculateValue(END_POINT, END_POINT);
399 }
400 }
401
CalculateValue(int32_t start,int32_t end,double percent)402 void RenderLoadingProgress::CalculateValue(int32_t start, int32_t end, double percent)
403 {
404 if (start == end) {
405 ringWidth_ = NormalizeToPx(MODE_RING_WIDTH[start]);
406 cometRadius_ = NormalizeToPx(MODE_COMET_RADIUS[start]);
407 ringBlurRadius_ = NormalizeToPx(MODE_RING_BLUR_RADIUS[start]);
408 ringBgWidth_ = NormalizeToPx(MODE_RING_BG_WIDTH[start]);
409 ringBgBlurRadius_ = NormalizeToPx(MODE_RING_BG_BLUR_RADIUS[start]);
410 } else {
411 ringWidth_ = NormalizeToPx(MODE_RING_WIDTH[start] +
412 (MODE_RING_WIDTH[end] - MODE_RING_WIDTH[start]) * percent);
413 cometRadius_ = NormalizeToPx(MODE_COMET_RADIUS[start] +
414 (MODE_COMET_RADIUS[end] - MODE_COMET_RADIUS[start]) * percent);
415 ringBlurRadius_ = NormalizeToPx(MODE_RING_BLUR_RADIUS[start] +
416 (MODE_RING_BLUR_RADIUS[end] - MODE_RING_BLUR_RADIUS[start]) * percent);
417 ringBgWidth_ = NormalizeToPx(MODE_RING_BG_WIDTH[start] +
418 (MODE_RING_BG_WIDTH[end] - MODE_RING_BG_WIDTH[start]) * percent);
419 ringBgBlurRadius_ = NormalizeToPx(MODE_RING_BG_BLUR_RADIUS[start] +
420 (MODE_RING_BG_BLUR_RADIUS[end] - MODE_RING_BG_BLUR_RADIUS[start]) * percent);
421 }
422 }
423
OnVisibleChanged()424 void RenderLoadingProgress::OnVisibleChanged()
425 {
426 AnimationChanged();
427 }
428
OnHiddenChanged(bool hidden)429 void RenderLoadingProgress::OnHiddenChanged(bool hidden)
430 {
431 AnimationChanged();
432 }
433
AnimationChanged()434 void RenderLoadingProgress::AnimationChanged()
435 {
436 if (GetVisible() && !GetHidden()) {
437 if (ringController_) {
438 ringController_->Play();
439 }
440 if (cometController_) {
441 cometController_->Play();
442 }
443 } else {
444 if (ringController_) {
445 ringController_->Pause();
446 }
447 if (cometController_) {
448 cometController_->Pause();
449 }
450 }
451 }
452
453 } // namespace OHOS::Ace
454