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