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