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