1 /*
2  * Copyright (c) 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 #include "base/geometry/ng/offset_t.h"
16 #include "base/utils/utils.h"
17 #include "core/common/container.h"
18 #include "core/components/checkable/checkable_theme.h"
19 #include "core/components/common/properties/color.h"
20 #include "core/components_ng/pattern/radio/radio_modifier.h"
21 #include "core/components_ng/render/animation_utils.h"
22 #include "core/components_ng/render/drawing.h"
23 #include "core/components_ng/render/drawing_prop_convertor.h"
24 #include "core/pipeline/pipeline_base.h"
25 
26 namespace OHOS::Ace::NG {
27 namespace {
28 constexpr uint8_t ENABLED_ALPHA = 255;
29 constexpr uint8_t DISABLED_ALPHA = 102;
30 constexpr float CALC_RADIUS = 2.0f;
31 constexpr float DEFAULT_POINT_SCALE = 0.5f;
32 constexpr float DEFAULT_TOTAL_SCALE = 1.0f;
33 constexpr float DEFAULT_SHRINK_SCALE = 0.9f;
34 constexpr float DEFAULT_SHRINK_SCALE_VER_TWELVE = 0.95f;
35 constexpr int32_t DEFAULT_RADIO_ANIMATION_DURATION = 300;
36 constexpr float DEFAULT_OPACITY_SCALE = 1.0f;
37 constexpr float DEFAULT_OPACITY_BORDER_SCALE = 0.0f;
38 constexpr float DEFAULT_INTERPOLATINGSPRING_VELOCITY = 0.0f;
39 constexpr float DEFAULT_INTERPOLATINGSPRING_MASS = 1.0f;
40 constexpr float DEFAULT_INTERPOLATINGSPRING_STIFFNESS = 728.0f;
41 constexpr float DEFAULT_INTERPOLATINGSPRING_DAMPING = 46.0f;
42 constexpr int32_t DEFAULT_INDICATOR_ANIMATION_DURATION = 150;
43 } // namespace
44 
RadioModifier()45 RadioModifier::RadioModifier()
46 {
47     auto pipeline = PipelineBase::GetCurrentContext();
48     CHECK_NULL_VOID(pipeline);
49     auto radioTheme = pipeline->GetTheme<RadioTheme>();
50 
51     pointColor_ = AceType::MakeRefPtr<AnimatablePropertyColor>(LinearColor(radioTheme->GetPointColor()));
52     AttachProperty(pointColor_);
53 
54     activeColor_ = AceType::MakeRefPtr<AnimatablePropertyColor>(LinearColor(radioTheme->GetActiveColor()));
55     AttachProperty(activeColor_);
56 
57     inactiveColor_ = AceType::MakeRefPtr<AnimatablePropertyColor>(LinearColor(radioTheme->GetInactiveColor()));
58     AttachProperty(inactiveColor_);
59     isOnAnimationFlag_ = AceType::MakeRefPtr<PropertyBool>(false);
60     enabled_ = AceType::MakeRefPtr<PropertyBool>(true);
61     isCheck_ = AceType::MakeRefPtr<PropertyBool>(false);
62     uiStatus_ = AceType::MakeRefPtr<PropertyInt>(static_cast<int32_t>(UIStatus::UNSELECTED));
63     offset_ = AceType::MakeRefPtr<AnimatablePropertyOffsetF>(OffsetF());
64     size_ = AceType::MakeRefPtr<AnimatablePropertySizeF>(SizeF());
65     totalScale_ = AceType::MakeRefPtr<AnimatablePropertyFloat>(DEFAULT_TOTAL_SCALE);
66     opacityScale_ = AceType::MakeRefPtr<AnimatablePropertyFloat>(DEFAULT_OPACITY_SCALE);
67     borderOpacityScale_ = AceType::MakeRefPtr<AnimatablePropertyFloat>(DEFAULT_OPACITY_BORDER_SCALE);
68     pointScale_ = AceType::MakeRefPtr<AnimatablePropertyFloat>(DEFAULT_POINT_SCALE);
69     ringPointScale_ = AceType::MakeRefPtr<AnimatablePropertyFloat>(0.0f);
70     animateTouchHoverColor_ = AceType::MakeRefPtr<AnimatablePropertyColor>(LinearColor(Color::TRANSPARENT));
71     useContentModifier_ = AceType::MakeRefPtr<PropertyBool>(false);
72 
73     AttachProperty(enabled_);
74     AttachProperty(isCheck_);
75     AttachProperty(uiStatus_);
76     AttachProperty(offset_);
77     AttachProperty(size_);
78     AttachProperty(totalScale_);
79     AttachProperty(opacityScale_);
80     AttachProperty(borderOpacityScale_);
81     AttachProperty(pointScale_);
82     AttachProperty(ringPointScale_);
83     AttachProperty(animateTouchHoverColor_);
84 
85     InitializeParam();
86 }
87 
InitializeParam()88 void RadioModifier::InitializeParam()
89 {
90     auto pipeline = PipelineBase::GetCurrentContext();
91     CHECK_NULL_VOID(pipeline);
92     auto radioTheme = pipeline->GetTheme<RadioTheme>();
93     shadowWidth_ = radioTheme->GetShadowWidth().ConvertToPx();
94     borderWidth_ = radioTheme->GetBorderWidth().ConvertToPx();
95     inactivePointColor_ = radioTheme->GetInactivePointColor();
96     shadowColor_ = radioTheme->GetShadowColor();
97     clickEffectColor_ = radioTheme->GetClickEffectColor();
98     hoverColor_ = radioTheme->GetHoverColor();
99     hotZoneHorizontalPadding_ = radioTheme->GetHotZoneHorizontalPadding();
100     hoverDuration_ = radioTheme->GetHoverDuration();
101     hoverToTouchDuration_ = radioTheme->GetHoverToTouchDuration();
102     touchDuration_ = radioTheme->GetTouchDuration();
103 }
104 
UpdateAnimatableProperty()105 void RadioModifier::UpdateAnimatableProperty()
106 {
107     switch (touchHoverType_) {
108         case TouchHoverAnimationType::HOVER:
109             SetBoardColor(LinearColor(hoverColor_), hoverDuration_, Curves::FRICTION);
110             break;
111         case TouchHoverAnimationType::PRESS_TO_HOVER:
112             SetBoardColor(LinearColor(hoverColor_), hoverToTouchDuration_, Curves::SHARP);
113             break;
114         case TouchHoverAnimationType::NONE:
115             SetBoardColor(LinearColor(hoverColor_.BlendOpacity(0)), hoverDuration_, Curves::FRICTION);
116             break;
117         case TouchHoverAnimationType::HOVER_TO_PRESS:
118             SetBoardColor(LinearColor(clickEffectColor_), hoverToTouchDuration_, Curves::SHARP);
119             break;
120         case TouchHoverAnimationType::PRESS:
121             SetBoardColor(LinearColor(clickEffectColor_), hoverDuration_, Curves::FRICTION);
122             break;
123         default:
124             break;
125     }
126 }
UpdateTotalScaleOnAnimatable(bool isCheck,const AnimationOption & delayOption,const AnimationOption & halfDurationOption)127 void RadioModifier::UpdateTotalScaleOnAnimatable(
128     bool isCheck, const AnimationOption& delayOption, const AnimationOption& halfDurationOption)
129 {
130     totalScale_->Set(DEFAULT_TOTAL_SCALE);
131     AnimationUtils::Animate(halfDurationOption, [&]() { totalScale_->Set(DEFAULT_SHRINK_SCALE_VER_TWELVE); });
132     totalScale_->Set(DEFAULT_SHRINK_SCALE_VER_TWELVE);
133     AnimationUtils::Animate(delayOption, [&]() { totalScale_->Set(DEFAULT_TOTAL_SCALE); });
134 }
135 
UpdateIsOnAnimatableProperty(bool isCheck)136 void RadioModifier::UpdateIsOnAnimatableProperty(bool isCheck)
137 {
138     AnimationOption delayOption;
139     delayOption.SetDelay(DEFAULT_RADIO_ANIMATION_DURATION / 2);
140     delayOption.SetDuration(DEFAULT_RADIO_ANIMATION_DURATION / 2);
141     delayOption.SetCurve(Curves::FRICTION);
142 
143     AnimationOption halfDurationOption;
144     halfDurationOption.SetDuration(DEFAULT_RADIO_ANIMATION_DURATION / 2);
145     halfDurationOption.SetCurve(Curves::FRICTION);
146 
147     if (isOnAnimationFlag_->Get()) {
148         pointScale_->Set(0);
149         AnimationUtils::Animate(delayOption, [&]() { pointScale_->Set(DEFAULT_POINT_SCALE); });
150         ringPointScale_->Set(1);
151         AnimationUtils::Animate(halfDurationOption, [&]() { ringPointScale_->Set(0); });
152     } else {
153         pointScale_->Set(DEFAULT_POINT_SCALE);
154         AnimationUtils::Animate(halfDurationOption, [&]() { pointScale_->Set(0); });
155         ringPointScale_->Set(0);
156         AnimationUtils::Animate(delayOption, [&]() { ringPointScale_->Set(1); });
157     }
158 
159     totalScale_->Set(DEFAULT_TOTAL_SCALE);
160     AnimationUtils::Animate(halfDurationOption, [&]() { totalScale_->Set(DEFAULT_SHRINK_SCALE); });
161     totalScale_->Set(DEFAULT_SHRINK_SCALE);
162     AnimationUtils::Animate(
163         delayOption, [&]() { totalScale_->Set(1); },
164         [isCheck, weakUiStatus = AceType::WeakClaim(AceType::RawPtr(uiStatus_))]() {
165             auto uiStatus = weakUiStatus.Upgrade();
166             if (uiStatus) {
167                 uiStatus->Set(static_cast<int32_t>(isCheck ? UIStatus::SELECTED : UIStatus::UNSELECTED));
168             }
169             auto context = PipelineBase::GetCurrentContext();
170             if (context) {
171                 context->RequestFrame();
172             }
173         });
174 }
175 
UpdateIndicatorAnimation(bool isCheck)176 void RadioModifier::UpdateIndicatorAnimation(bool isCheck)
177 {
178     auto springCurve = AceType::MakeRefPtr<InterpolatingSpring>(DEFAULT_INTERPOLATINGSPRING_VELOCITY,
179         DEFAULT_INTERPOLATINGSPRING_MASS, DEFAULT_INTERPOLATINGSPRING_STIFFNESS, DEFAULT_INTERPOLATINGSPRING_DAMPING);
180     AnimationOption halfDurationOption;
181     halfDurationOption.SetCurve(Curves::FRICTION);
182     halfDurationOption.SetDuration(DEFAULT_INDICATOR_ANIMATION_DURATION);
183     AnimationOption delayOption;
184     delayOption.SetCurve(springCurve);
185     delayOption.SetDelay(DEFAULT_INDICATOR_ANIMATION_DURATION);
186     delayOption.SetDuration(DEFAULT_INDICATOR_ANIMATION_DURATION);
187     if (isCheck) {
188         AnimationUtils::Animate(
189             delayOption,
190             [&]() {
191                 opacityScale_->Set(1);
192                 borderOpacityScale_->Set(0);
193             },
194             nullptr);
195     } else {
196         AnimationUtils::Animate(
197             halfDurationOption,
198             [&]() {
199                 opacityScale_->Set(0);
200                 borderOpacityScale_->Set(1);
201             },
202             nullptr);
203     }
204     UpdateTotalScaleOnAnimatable(isCheck, delayOption, halfDurationOption);
205 }
206 
SetBoardColor(LinearColor color,int32_t duratuion,const RefPtr<CubicCurve> & curve)207 void RadioModifier::SetBoardColor(LinearColor color, int32_t duratuion, const RefPtr<CubicCurve>& curve)
208 {
209     if (animateTouchHoverColor_) {
210         AnimationOption option = AnimationOption();
211         option.SetDuration(duratuion);
212         option.SetCurve(curve);
213         AnimationUtils::Animate(option, [&]() { animateTouchHoverColor_->Set(color); });
214     }
215 }
216 
PaintRadio(RSCanvas & canvas,bool,const SizeF & contentSize,const OffsetF & contentOffset) const217 void RadioModifier::PaintRadio(
218     RSCanvas& canvas, bool /* checked */, const SizeF& contentSize, const OffsetF& contentOffset) const
219 {
220     DrawTouchAndHoverBoard(canvas, contentSize, contentOffset);
221     float outCircleRadius = contentSize.Width() / CALC_RADIUS;
222     float centerX = contentOffset.GetX() + outCircleRadius;
223     float centerY = contentOffset.GetY() + outCircleRadius;
224     RSPen pen;
225     RSPen outPen;
226     RSBrush brush;
227     RSBrush shadowBrush;
228     pen.SetAntiAlias(true);
229     pen.SetWidth(borderWidth_);
230     outPen.SetAntiAlias(true);
231     brush.SetAntiAlias(true);
232     shadowBrush.SetAntiAlias(true);
233     shadowBrush.SetColor(ToRSColor(shadowColor_));
234     if (uiStatus_->Get() == static_cast<int32_t>(UIStatus::SELECTED)) {
235         if (!enabled_->Get()) {
236             brush.SetColor(
237                 ToRSColor(pointColor_->Get().BlendOpacity(static_cast<float>(DISABLED_ALPHA) / ENABLED_ALPHA)));
238         } else {
239             brush.SetColor(ToRSColor(pointColor_->Get()));
240         }
241         if (!NearZero(pointScale_->Get())) {
242             // draw shadow
243             canvas.AttachBrush(shadowBrush);
244             canvas.DrawCircle(RSPoint(centerX, centerY), outCircleRadius * pointScale_->Get() + shadowWidth_);
245             canvas.DetachBrush();
246             // draw inner circle
247             canvas.AttachBrush(brush);
248             canvas.DrawCircle(RSPoint(centerX, centerY), outCircleRadius * pointScale_->Get());
249             canvas.DetachBrush();
250         }
251         // draw ring circle
252         if (!enabled_->Get()) {
253             brush.SetColor(
254                 ToRSColor(inactivePointColor_.BlendOpacity(static_cast<float>(DISABLED_ALPHA) / ENABLED_ALPHA)));
255         } else {
256             brush.SetColor(ToRSColor(inactivePointColor_));
257         }
258         if (!NearZero(ringPointScale_->Get())) {
259             canvas.AttachBrush(brush);
260             canvas.DrawCircle(RSPoint(centerX, centerY), outCircleRadius * ringPointScale_->Get());
261             canvas.DetachBrush();
262         }
263         // draw out circular ring
264         if (!enabled_->Get()) {
265             outPen.SetColor(
266                 ToRSColor(activeColor_->Get().BlendOpacity(static_cast<float>(DISABLED_ALPHA) / ENABLED_ALPHA)));
267         } else {
268             outPen.SetColor(ToRSColor(activeColor_->Get()));
269         }
270         auto outWidth = outCircleRadius * (totalScale_->Get() - pointScale_->Get() - ringPointScale_->Get());
271         if (outWidth < borderWidth_) {
272             outWidth = borderWidth_;
273         }
274         outPen.SetWidth(outWidth);
275         canvas.AttachPen(outPen);
276         canvas.DrawCircle(RSPoint(centerX, centerY), outCircleRadius * totalScale_->Get() - outWidth / CALC_RADIUS);
277         canvas.DetachPen();
278     } else if (uiStatus_->Get() == static_cast<int32_t>(UIStatus::UNSELECTED)) {
279         auto alphaCalculate = static_cast<float>(DISABLED_ALPHA) / ENABLED_ALPHA;
280         if (!enabled_->Get()) {
281             brush.SetColor(ToRSColor(inactivePointColor_.BlendOpacity(alphaCalculate)));
282             pen.SetColor(ToRSColor(inactiveColor_->Get().BlendOpacity(alphaCalculate)));
283         } else {
284             brush.SetColor(ToRSColor(inactivePointColor_));
285             pen.SetColor(ToRSColor(inactiveColor_->Get()));
286         }
287         canvas.AttachBrush(brush);
288         canvas.DrawCircle(RSPoint(centerX, centerY), outCircleRadius - borderWidth_);
289         canvas.DetachBrush();
290         // draw border with unselected color
291         canvas.AttachPen(pen);
292         canvas.DrawCircle(RSPoint(centerX, centerY), outCircleRadius - borderWidth_ / CALC_RADIUS);
293         canvas.DetachPen();
294     }
295 }
296 
PaintUnselectedIndicator(RSCanvas & canvas,float outCircleRadius,float centerX,float centerY) const297 void RadioModifier::PaintUnselectedIndicator(
298     RSCanvas& canvas, float outCircleRadius, float centerX, float centerY) const
299 {
300     RSPen pen;
301     RSBrush brush;
302     pen.SetAntiAlias(true);
303     pen.SetWidth(borderWidth_);
304     brush.SetAntiAlias(true);
305     auto alphaCalculate = static_cast<float>(DISABLED_ALPHA) / ENABLED_ALPHA;
306     if (!enabled_->Get()) {
307         brush.SetColor(ToRSColor(inactivePointColor_.BlendOpacity(alphaCalculate)));
308         pen.SetColor(ToRSColor(inactiveColor_->Get().BlendOpacity(alphaCalculate)));
309     } else {
310         brush.SetColor(ToRSColor(inactivePointColor_));
311         pen.SetColor(ToRSColor(inactiveColor_->Get()));
312     }
313     canvas.AttachBrush(brush);
314     canvas.DrawCircle(RSPoint(centerX, centerY), outCircleRadius - borderWidth_);
315     canvas.DetachBrush();
316     // draw border with unselected color
317     canvas.AttachPen(pen);
318     canvas.DrawCircle(RSPoint(centerX, centerY), outCircleRadius * totalScale_->Get() - borderWidth_ / CALC_RADIUS);
319     canvas.DetachPen();
320 }
321 
PaintIndicator(RSCanvas & canvas,bool,const SizeF & contentSize,const OffsetF & contentOffset) const322 void RadioModifier::PaintIndicator(
323     RSCanvas& canvas, bool /* checked */, const SizeF& contentSize, const OffsetF& contentOffset) const
324 {
325     DrawTouchAndHoverBoard(canvas, contentSize, contentOffset);
326     float outCircleRadius = contentSize.Width() / CALC_RADIUS;
327     float centerX = contentOffset.GetX() + outCircleRadius;
328     float centerY = contentOffset.GetY() + outCircleRadius;
329     RSPen pen;
330     RSPen outPen;
331     RSBrush brush;
332     pen.SetAntiAlias(true);
333     pen.SetWidth(borderWidth_ * borderOpacityScale_->Get());
334     outPen.SetAntiAlias(true);
335     brush.SetAntiAlias(true);
336     auto alphaCalculate = static_cast<float>(enabled_->Get() ? ENABLED_ALPHA : DISABLED_ALPHA) / ENABLED_ALPHA;
337     outPen.SetColor(ToRSColor(activeColor_->Get().BlendOpacity(opacityScale_->Get()).BlendOpacity(alphaCalculate)));
338     pen.SetColor(
339         ToRSColor(inactiveColor_->Get().BlendOpacity(borderOpacityScale_->Get()).BlendOpacity(alphaCalculate)));
340     brush.SetColor(
341         ToRSColor(inactivePointColor_.BlendOpacity(borderOpacityScale_->Get()).BlendOpacity(alphaCalculate)));
342     auto outWidth = outCircleRadius * totalScale_->Get();
343     if (outWidth < borderWidth_) {
344         outWidth = borderWidth_;
345     }
346     canvas.AttachPen(pen);
347     canvas.DrawCircle(RSPoint(centerX, centerY), outCircleRadius * totalScale_->Get() - borderWidth_ / CALC_RADIUS);
348     canvas.DetachPen();
349 
350     canvas.AttachBrush(brush);
351     canvas.DrawCircle(RSPoint(centerX, centerY), outCircleRadius * totalScale_->Get() - borderWidth_);
352     canvas.DetachBrush();
353 
354     outPen.SetWidth(outWidth);
355     canvas.AttachPen(outPen);
356     canvas.DrawCircle(RSPoint(centerX, centerY), outCircleRadius * totalScale_->Get() - outWidth / CALC_RADIUS);
357     canvas.DetachPen();
358 }
359 
DrawTouchAndHoverBoard(RSCanvas & canvas,const SizeF & contentSize,const OffsetF & offset) const360 void RadioModifier::DrawTouchAndHoverBoard(RSCanvas& canvas, const SizeF& contentSize, const OffsetF& offset) const
361 {
362     CHECK_NULL_VOID(showHoverEffect_);
363     float outCircleRadius = contentSize.Width() / CALC_RADIUS;
364     float centerX = outCircleRadius + offset.GetX();
365     float centerY = outCircleRadius + offset.GetY();
366     if (Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE)) {
367         outCircleRadius += defaultPadding_.ConvertToPx();
368     } else {
369         outCircleRadius += hotZoneHorizontalPadding_.ConvertToPx();
370     }
371     RSBrush brush;
372     brush.SetColor(ToRSColor(animateTouchHoverColor_->Get()));
373     brush.SetAntiAlias(true);
374     canvas.AttachBrush(brush);
375     canvas.DrawCircle(RSPoint(centerX, centerY), outCircleRadius);
376     canvas.DetachBrush();
377 }
378 
379 } // namespace OHOS::Ace::NG
380