1 /*
2  * Copyright (c) 2022-2023 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_ng/pattern/radio/radio_pattern.h"
17 
18 #include "base/utils/utils.h"
19 #include "core/common/recorder/node_data_cache.h"
20 #include "core/components/checkable/checkable_theme.h"
21 #include "core/components/theme/icon_theme.h"
22 #include "core/components_ng/pattern/image/image_layout_property.h"
23 #include "core/components_ng/pattern/image/image_pattern.h"
24 #include "core/components_ng/pattern/radio/radio_paint_property.h"
25 #include "core/components_ng/pattern/stage/page_event_hub.h"
26 #include "core/components_ng/property/property.h"
27 #include "core/event/touch_event.h"
28 #include "core/pipeline_ng/pipeline_context.h"
29 
30 namespace OHOS::Ace::NG {
31 
32 namespace {
33 constexpr int FOR_HOTZONESIZE_CALCULATE_MULTIPLY_TWO = 2;
34 const Color ITEM_FILL_COLOR = Color::TRANSPARENT;
35 
36 constexpr int32_t DEFAULT_RADIO_ANIMATION_DURATION = 200;
37 constexpr float DEFAULT_CUSTOM_SCALE = 0.7F;
38 constexpr float INDICATOR_MIN_SCALE = 0.8F;
39 constexpr float INDICATOR_MAX_SCALE = 1.0F;
40 constexpr float INDICATOR_MIN_OPACITY = 0.0F;
41 constexpr float INDICATOR_MAX_OPACITY = 1.0F;
42 constexpr int32_t RADIO_PADDING_COUNT = 2;
43 
44 constexpr float DEFAULT_INTERPOLATINGSPRING_VELOCITY = 0.0f;
45 constexpr float DEFAULT_INTERPOLATINGSPRING_MASS = 1.0f;
46 constexpr float DEFAULT_INTERPOLATINGSPRING_STIFFNESS = 728.0f;
47 constexpr float DEFAULT_INTERPOLATINGSPRING_DAMPING = 46.0f;
48 
49 enum class RadioIndicatorType {
50     TICK = 0,
51     DOT,
52     CUSTOM,
53 };
54 } // namespace
55 
OnAttachToFrameNode()56 void RadioPattern::OnAttachToFrameNode()
57 {
58     auto host = GetHost();
59     CHECK_NULL_VOID(host);
60     host->GetLayoutProperty()->UpdateAlignment(Alignment::CENTER);
61 }
62 
OnDetachFromFrameNode(FrameNode * frameNode)63 void RadioPattern::OnDetachFromFrameNode(FrameNode* frameNode)
64 {
65     CHECK_NULL_VOID(frameNode);
66     auto groupManager = GetGroupManager();
67     CHECK_NULL_VOID(groupManager);
68     auto radioEventHub = frameNode->GetEventHub<NG::RadioEventHub>();
69     CHECK_NULL_VOID(radioEventHub);
70     groupManager->RemoveRadioFromGroup(radioEventHub->GetGroup(), frameNode->GetId());
71 }
72 
SetBuilderState()73 void RadioPattern::SetBuilderState()
74 {
75     CHECK_NULL_VOID(builderChildNode_);
76     auto renderContext = builderChildNode_->GetRenderContext();
77     CHECK_NULL_VOID(renderContext);
78     renderContext->UpdateOpacity(0);
79     auto layoutProperty = builderChildNode_->GetLayoutProperty();
80     CHECK_NULL_VOID(layoutProperty);
81     layoutProperty->UpdateVisibility(VisibleType::GONE);
82 }
83 
UpdateIndicatorType()84 void RadioPattern::UpdateIndicatorType()
85 {
86     auto radioPaintProperty = GetHost()->GetPaintProperty<RadioPaintProperty>();
87     auto radioIndicatorType = radioPaintProperty->GetRadioIndicator().value_or(0);
88     if (radioIndicatorType == static_cast<int32_t>(RadioIndicatorType::CUSTOM)) {
89         LoadBuilder();
90     } else {
91         ImageNodeCreate();
92     }
93     CHECK_NULL_VOID(builderChildNode_);
94     auto renderContext = builderChildNode_->GetRenderContext();
95     CHECK_NULL_VOID(renderContext);
96     renderContext->UpdateTransformScale({ INDICATOR_MAX_SCALE, INDICATOR_MAX_SCALE });
97     renderContext->UpdateOpacity(1);
98     if (!radioModifier_) {
99         radioModifier_ = AceType::MakeRefPtr<RadioModifier>();
100     }
101     if (!radioPaintProperty->HasRadioCheck()) {
102         radioPaintProperty->UpdateRadioCheck(false);
103     }
104     if (!radioPaintProperty->GetRadioCheckValue()) {
105         radioModifier_->InitOpacityScale(false);
106         SetBuilderState();
107     }
108 }
109 
OnModifyDone()110 void RadioPattern::OnModifyDone()
111 {
112     Pattern::OnModifyDone();
113     FireBuilder();
114     if (!makeFunc_.has_value() && Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE)) {
115         UpdateIndicatorType();
116     }
117     UpdateState();
118     auto host = GetHost();
119     CHECK_NULL_VOID(host);
120     auto* pipeline = host->GetContextWithCheck();
121     CHECK_NULL_VOID(pipeline);
122     auto radioTheme = pipeline->GetTheme<RadioTheme>();
123     CHECK_NULL_VOID(radioTheme);
124     auto layoutProperty = host->GetLayoutProperty();
125     CHECK_NULL_VOID(layoutProperty);
126     MarginProperty margin;
127     margin.left = CalcLength(radioTheme->GetHotZoneHorizontalPadding().Value());
128     margin.right = CalcLength(radioTheme->GetHotZoneHorizontalPadding().Value());
129     margin.top = CalcLength(radioTheme->GetHotZoneVerticalPadding().Value());
130     margin.bottom = CalcLength(radioTheme->GetHotZoneVerticalPadding().Value());
131     auto& setMargin = layoutProperty->GetMarginProperty();
132     if (setMargin) {
133         if (setMargin->left.has_value()) {
134             margin.left = setMargin->left;
135         }
136         if (setMargin->right.has_value()) {
137             margin.right = setMargin->right;
138         }
139         if (setMargin->top.has_value()) {
140             margin.top = setMargin->top;
141         }
142         if (setMargin->bottom.has_value()) {
143             margin.bottom = setMargin->bottom;
144         }
145     }
146     layoutProperty->UpdateMargin(margin);
147     hotZoneHorizontalPadding_ = radioTheme->GetHotZoneHorizontalPadding();
148     hotZoneVerticalPadding_ = radioTheme->GetHotZoneVerticalPadding();
149     HandleEnabled();
150     InitClickEvent();
151     InitTouchEvent();
152     InitMouseEvent();
153     auto focusHub = host->GetFocusHub();
154     CHECK_NULL_VOID(focusHub);
155     InitOnKeyEvent(focusHub);
156     SetAccessibilityAction();
157 }
158 
ImageNodeCreate()159 void RadioPattern::ImageNodeCreate()
160 {
161     auto host = GetHost();
162     CHECK_NULL_VOID(host);
163     auto childNode = DynamicCast<FrameNode>(host->GetFirstChild());
164     if (preTypeIsBuilder_) {
165         host->RemoveChild(childNode);
166     }
167     if (!childNode || preTypeIsBuilder_) {
168         auto node = FrameNode::GetOrCreateFrameNode(V2::IMAGE_ETS_TAG, ElementRegister::GetInstance()->MakeUniqueId(),
169             []() { return AceType::MakeRefPtr<ImagePattern>(); });
170         CHECK_NULL_VOID(node);
171         builderChildNode_ = AceType::DynamicCast<FrameNode>(node);
172         CHECK_NULL_VOID(builderChildNode_);
173         auto gesturehub = builderChildNode_->GetOrCreateGestureEventHub();
174         CHECK_NULL_VOID(gesturehub);
175         gesturehub->SetHitTestMode(HitTestMode::HTMNONE);
176     }
177     CHECK_NULL_VOID(builderChildNode_);
178     auto radioPaintProperty = host->GetPaintProperty<RadioPaintProperty>();
179     CHECK_NULL_VOID(radioPaintProperty);
180     auto imageProperty = builderChildNode_->GetLayoutProperty<ImageLayoutProperty>();
181     CHECK_NULL_VOID(imageProperty);
182     imageProperty->UpdateUserDefinedIdealSize(GetChildContentSize());
183     auto imageSourceInfo = GetImageSourceInfoFromTheme(radioPaintProperty->GetRadioIndicator().value_or(0));
184     UpdateInternalResource(imageSourceInfo);
185     auto* pipeline = host->GetContextWithCheck();
186     CHECK_NULL_VOID(pipeline);
187     auto radioTheme = pipeline->GetTheme<RadioTheme>();
188     CHECK_NULL_VOID(radioTheme);
189     auto indicatorColor = radioPaintProperty->GetRadioIndicatorColor().value_or(Color(radioTheme->GetPointColor()));
190     auto imageRenderProperty = builderChildNode_->GetPaintProperty<ImageRenderProperty>();
191     CHECK_NULL_VOID(imageRenderProperty);
192     imageRenderProperty->UpdateSvgFillColor(indicatorColor);
193     imageProperty->UpdateImageSourceInfo(imageSourceInfo);
194     preTypeIsBuilder_ = false;
195     builderChildNode_->MountToParent(host);
196     builderChildNode_->MarkModifyDone();
197     host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
198 }
199 
SetAccessibilityAction()200 void RadioPattern::SetAccessibilityAction()
201 {
202     auto host = GetHost();
203     CHECK_NULL_VOID(host);
204     auto accessibilityProperty = host->GetAccessibilityProperty<AccessibilityProperty>();
205     CHECK_NULL_VOID(accessibilityProperty);
206     accessibilityProperty->SetActionSelect([weakPtr = WeakClaim(this)]() {
207         const auto& pattern = weakPtr.Upgrade();
208         CHECK_NULL_VOID(pattern);
209         pattern->UpdateSelectStatus(true);
210     });
211 
212     accessibilityProperty->SetActionClearSelection([weakPtr = WeakClaim(this)]() {
213         const auto& pattern = weakPtr.Upgrade();
214         CHECK_NULL_VOID(pattern);
215         pattern->UpdateSelectStatus(false);
216     });
217 }
218 
UpdateSelectStatus(bool isSelected)219 void RadioPattern::UpdateSelectStatus(bool isSelected)
220 {
221     auto host = GetHost();
222     CHECK_NULL_VOID(host);
223     TAG_LOGD(AceLogTag::ACE_SELECT_COMPONENT, "radio node %{public}d update status %d", host->GetId(), isSelected);
224     auto context = host->GetRenderContext();
225     CHECK_NULL_VOID(context);
226     MarkIsSelected(isSelected);
227     context->OnMouseSelectUpdate(isSelected, ITEM_FILL_COLOR, ITEM_FILL_COLOR);
228 }
229 
MarkIsSelected(bool isSelected)230 void RadioPattern::MarkIsSelected(bool isSelected)
231 {
232     if (preCheck_ == isSelected) {
233         return;
234     }
235     preCheck_ = isSelected;
236     auto eventHub = GetEventHub<RadioEventHub>();
237     CHECK_NULL_VOID(eventHub);
238     eventHub->UpdateChangeEvent(isSelected);
239     auto host = GetHost();
240     CHECK_NULL_VOID(host);
241     TAG_LOGD(AceLogTag::ACE_SELECT_COMPONENT, "radio node %{public}d fire change event %{public}d", host->GetId(),
242         isSelected);
243     if (isSelected) {
244         eventHub->UpdateCurrentUIState(UI_STATE_SELECTED);
245         host->OnAccessibilityEvent(AccessibilityEventType::SELECTED);
246     } else {
247         eventHub->ResetCurrentUIState(UI_STATE_SELECTED);
248         host->OnAccessibilityEvent(AccessibilityEventType::CHANGE);
249     }
250 }
251 
OnAfterModifyDone()252 void RadioPattern::OnAfterModifyDone()
253 {
254     auto host = GetHost();
255     CHECK_NULL_VOID(host);
256     auto inspectorId = host->GetInspectorId().value_or("");
257     if (inspectorId.empty()) {
258         return;
259     }
260     auto eventHub = host->GetEventHub<RadioEventHub>();
261     CHECK_NULL_VOID(eventHub);
262     Recorder::NodeDataCache::Get().PutMultiple(host, inspectorId, eventHub->GetValue(), preCheck_);
263 }
264 
InitClickEvent()265 void RadioPattern::InitClickEvent()
266 {
267     if (clickListener_) {
268         return;
269     }
270     auto host = GetHost();
271     CHECK_NULL_VOID(host);
272     auto gesture = host->GetOrCreateGestureEventHub();
273     CHECK_NULL_VOID(gesture);
274     auto clickCallback = [weak = WeakClaim(this)](GestureEvent& info) {
275         auto radioPattern = weak.Upgrade();
276         CHECK_NULL_VOID(radioPattern);
277         radioPattern->OnClick();
278     };
279     clickListener_ = MakeRefPtr<ClickEvent>(std::move(clickCallback));
280     gesture->AddClickEvent(clickListener_);
281 }
282 
InitTouchEvent()283 void RadioPattern::InitTouchEvent()
284 {
285     if (UseContentModifier()) {
286         return;
287     }
288     if (touchListener_) {
289         return;
290     }
291     auto host = GetHost();
292     CHECK_NULL_VOID(host);
293     auto gesture = host->GetOrCreateGestureEventHub();
294     CHECK_NULL_VOID(gesture);
295     auto touchCallback = [weak = WeakClaim(this)](const TouchEventInfo& info) {
296         auto radioPattern = weak.Upgrade();
297         CHECK_NULL_VOID(radioPattern);
298         if (info.GetTouches().front().GetTouchType() == TouchType::DOWN) {
299             radioPattern->OnTouchDown();
300         }
301         if (info.GetTouches().front().GetTouchType() == TouchType::UP ||
302             info.GetTouches().front().GetTouchType() == TouchType::CANCEL) {
303             radioPattern->OnTouchUp();
304         }
305     };
306     touchListener_ = MakeRefPtr<TouchEventImpl>(std::move(touchCallback));
307     gesture->AddTouchEvent(touchListener_);
308 }
309 
InitMouseEvent()310 void RadioPattern::InitMouseEvent()
311 {
312     if (UseContentModifier()) {
313         return;
314     }
315     if (mouseEvent_) {
316         return;
317     }
318     auto host = GetHost();
319     CHECK_NULL_VOID(host);
320     auto gesture = host->GetOrCreateGestureEventHub();
321     CHECK_NULL_VOID(gesture);
322     auto eventHub = host->GetEventHub<RadioEventHub>();
323     auto inputHub = eventHub->GetOrCreateInputEventHub();
324 
325     auto mouseTask = [weak = WeakClaim(this)](bool isHover) {
326         auto pattern = weak.Upgrade();
327         if (pattern) {
328             pattern->HandleMouseEvent(isHover);
329         }
330     };
331     mouseEvent_ = MakeRefPtr<InputEvent>(std::move(mouseTask));
332     inputHub->AddOnHoverEvent(mouseEvent_);
333 }
334 
HandleMouseEvent(bool isHover)335 void RadioPattern::HandleMouseEvent(bool isHover)
336 {
337     if (UseContentModifier()) {
338         return;
339     }
340     isHover_ = isHover;
341     if (isHover) {
342         touchHoverType_ = TouchHoverAnimationType::HOVER;
343     } else {
344         touchHoverType_ = TouchHoverAnimationType::NONE;
345     }
346     auto host = GetHost();
347     CHECK_NULL_VOID(host);
348     TAG_LOGD(
349         AceLogTag::ACE_SELECT_COMPONENT, "radio node %{public}d handle mouse hover %{public}d", host->GetId(), isHover);
350     host->MarkNeedRenderOnly();
351 }
352 
OnClick()353 void RadioPattern::OnClick()
354 {
355     if (UseContentModifier()) {
356         return;
357     }
358     auto host = GetHost();
359     CHECK_NULL_VOID(host);
360     TAG_LOGD(AceLogTag::ACE_SELECT_COMPONENT, "radio node %{public}d handle click event", host->GetId());
361     auto paintProperty = host->GetPaintProperty<RadioPaintProperty>();
362     CHECK_NULL_VOID(paintProperty);
363     bool check = false;
364     if (paintProperty->HasRadioCheck()) {
365         check = paintProperty->GetRadioCheckValue();
366     } else {
367         paintProperty->UpdateRadioCheck(false);
368     }
369     if (!preCheck_ && !check) {
370         paintProperty->UpdateRadioCheck(true);
371         UpdateState();
372     }
373 }
374 
OnTouchDown()375 void RadioPattern::OnTouchDown()
376 {
377     if (UseContentModifier()) {
378         return;
379     }
380     if (isHover_) {
381         touchHoverType_ = TouchHoverAnimationType::HOVER_TO_PRESS;
382     } else {
383         touchHoverType_ = TouchHoverAnimationType::PRESS;
384     }
385     auto host = GetHost();
386     CHECK_NULL_VOID(host);
387     TAG_LOGD(AceLogTag::ACE_SELECT_COMPONENT, "radio node %{public}d onTouch Down", host->GetId());
388     isTouch_ = true;
389     host->MarkNeedRenderOnly();
390 }
391 
OnTouchUp()392 void RadioPattern::OnTouchUp()
393 {
394     if (UseContentModifier()) {
395         return;
396     }
397     if (isHover_) {
398         touchHoverType_ = TouchHoverAnimationType::PRESS_TO_HOVER;
399     } else {
400         touchHoverType_ = TouchHoverAnimationType::NONE;
401     }
402     auto host = GetHost();
403     CHECK_NULL_VOID(host);
404     TAG_LOGD(AceLogTag::ACE_SELECT_COMPONENT, "radio node %{public}d onTouch Up", host->GetId());
405     isTouch_ = false;
406     host->MarkNeedRenderOnly();
407 }
408 
CheckPageNode()409 void RadioPattern::CheckPageNode()
410 {
411     if (Container::IsInSubContainer()) {
412         return;
413     }
414     auto host = GetHost();
415     CHECK_NULL_VOID(host);
416     auto prePageId = GetPrePageId();
417     auto pipelineContext = PipelineContext::GetCurrentContext();
418     CHECK_NULL_VOID(pipelineContext);
419     auto stageManager = pipelineContext->GetStageManager();
420     CHECK_NULL_VOID(stageManager);
421     auto pageNode = stageManager->GetPageById(host->GetPageId());
422     CHECK_NULL_VOID(pageNode);
423     if (pageNode->GetId() != prePageId) {
424         auto eventHub = host->GetEventHub<RadioEventHub>();
425         CHECK_NULL_VOID(eventHub);
426         auto groupManager = GetGroupManager();
427         CHECK_NULL_VOID(groupManager);
428         auto group = eventHub->GetGroup();
429         groupManager->AddRadioToGroup(group, host->GetId());
430         auto paintProperty = host->GetPaintProperty<RadioPaintProperty>();
431         CHECK_NULL_VOID(paintProperty);
432         bool check = false;
433         if (paintProperty->HasRadioCheck()) {
434             check = paintProperty->GetRadioCheckValue();
435         }
436         UpdateGroupCheckStatus(host, groupManager, check);
437     }
438 }
439 
UpdateState()440 void RadioPattern::UpdateState()
441 {
442     auto host = GetHost();
443     CHECK_NULL_VOID(host);
444     auto eventHub = host->GetEventHub<RadioEventHub>();
445     CHECK_NULL_VOID(eventHub);
446 
447     auto pipelineContext = PipelineContext::GetCurrentContext();
448     CHECK_NULL_VOID(pipelineContext);
449     auto groupManager = GetGroupManager();
450     CHECK_NULL_VOID(groupManager);
451     auto preGroup = GetPreGroup();
452     auto group = eventHub->GetGroup();
453     if (!preGroup.has_value()) {
454         groupManager->AddRadioToGroup(group, host->GetId());
455         SetPrePageIdToLastPageId();
456         auto callback = [weak = WeakClaim(this)]() {
457             auto radio = weak.Upgrade();
458             if (radio) {
459                 radio->CheckPageNode();
460             }
461         };
462         pipelineContext->AddBuildFinishCallBack(callback);
463     }
464     if (preGroup.has_value() && preGroup.value() != group) {
465         groupManager->RemoveRadioFromGroup(preGroup.value(), host->GetId());
466         groupManager->AddRadioToGroup(group, host->GetId());
467         SetPrePageIdToLastPageId();
468         isGroupChanged_ = true;
469     }
470     SetPreGroup(group);
471 
472     auto paintProperty = host->GetPaintProperty<RadioPaintProperty>();
473     CHECK_NULL_VOID(paintProperty);
474 
475     bool check = false;
476     if (paintProperty->HasRadioCheck()) {
477         check = paintProperty->GetRadioCheckValue();
478         /*
479          * Do not set isFirstCreated_ to false if the radio is set to true at creation time. The isFirstCreated_ is set
480          * to false in UpdateGroupCheckStatus because isFirstCreated_ is also required to determine if an onChange event
481          * needs to be triggered.
482          */
483         if (check) {
484             UpdateUIStatus(true);
485             isOnAnimationFlag_ = true;
486         } else {
487             // If the radio is set to false, set isFirstCreated_ to false.
488             isFirstCreated_ = false;
489         }
490     } else {
491         paintProperty->UpdateRadioCheck(false);
492         // If the radio check is not set, set isFirstCreated_ to false.
493         isFirstCreated_ = false;
494     }
495     if (preCheck_ != check || isGroupChanged_) {
496         UpdateGroupCheckStatus(host, groupManager, check);
497     }
498     preCheck_ = check;
499     isGroupChanged_ = false;
500 }
501 
UpdateUncheckStatus(const RefPtr<FrameNode> & frameNode)502 void RadioPattern::UpdateUncheckStatus(const RefPtr<FrameNode>& frameNode)
503 {
504     auto radioPaintProperty = frameNode->GetPaintProperty<RadioPaintProperty>();
505     CHECK_NULL_VOID(radioPaintProperty);
506     if (radioPaintProperty->GetRadioCheckValue(false)) {
507         radioPaintProperty->UpdateRadioCheck(false);
508         FireBuilder();
509     }
510     frameNode->MarkNeedRenderOnly();
511     if (Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE)) {
512         startExitAnimation();
513     }
514     if (preCheck_) {
515         auto radioEventHub = GetEventHub<RadioEventHub>();
516         CHECK_NULL_VOID(radioEventHub);
517         TAG_LOGD(AceLogTag::ACE_SELECT_COMPONENT, "radio node %{public}d fire unselect event", frameNode->GetId());
518         radioEventHub->UpdateChangeEvent(false);
519         isOnAnimationFlag_ = false;
520     }
521     preCheck_ = false;
522 }
523 
startEnterAnimation()524 void RadioPattern::startEnterAnimation()
525 {
526     auto springCurve = AceType::MakeRefPtr<InterpolatingSpring>(DEFAULT_INTERPOLATINGSPRING_VELOCITY,
527         DEFAULT_INTERPOLATINGSPRING_MASS, DEFAULT_INTERPOLATINGSPRING_STIFFNESS, DEFAULT_INTERPOLATINGSPRING_DAMPING);
528     AnimationOption delayOption;
529     delayOption.SetCurve(springCurve);
530     delayOption.SetDelay(DEFAULT_RADIO_ANIMATION_DURATION);
531     CHECK_NULL_VOID(builderChildNode_);
532     auto renderContext = builderChildNode_->GetRenderContext();
533     CHECK_NULL_VOID(renderContext);
534     renderContext->UpdateOpacity(INDICATOR_MIN_OPACITY);
535     renderContext->UpdateTransformScale({ INDICATOR_MIN_SCALE, INDICATOR_MIN_SCALE });
536     auto layoutProperty = builderChildNode_->GetLayoutProperty();
537     CHECK_NULL_VOID(layoutProperty);
538     layoutProperty->UpdateVisibility(VisibleType::VISIBLE);
539     auto eventHub = builderChildNode_->GetEventHub<EventHub>();
540     if (eventHub) {
541         eventHub->SetEnabled(true);
542     }
543     AnimationUtils::Animate(
544         delayOption,
545         [&]() {
546             renderContext->UpdateTransformScale({ INDICATOR_MAX_SCALE, INDICATOR_MAX_SCALE });
547             renderContext->UpdateOpacity(INDICATOR_MAX_OPACITY);
548         },
549         nullptr);
550 }
551 
startExitAnimation()552 void RadioPattern::startExitAnimation()
553 {
554     auto springCurve = AceType::MakeRefPtr<InterpolatingSpring>(DEFAULT_INTERPOLATINGSPRING_VELOCITY,
555         DEFAULT_INTERPOLATINGSPRING_MASS, DEFAULT_INTERPOLATINGSPRING_STIFFNESS, DEFAULT_INTERPOLATINGSPRING_DAMPING);
556     AnimationOption delayOption;
557     delayOption.SetCurve(springCurve);
558     CHECK_NULL_VOID(builderChildNode_);
559     auto renderContext = builderChildNode_->GetRenderContext();
560     CHECK_NULL_VOID(renderContext);
561     AnimationUtils::Animate(
562         delayOption,
563         [&]() {
564             renderContext->UpdateTransformScale({ INDICATOR_MIN_SCALE, INDICATOR_MIN_SCALE });
565             renderContext->UpdateOpacity(INDICATOR_MIN_OPACITY);
566         },
567         nullptr);
568     auto eventHub = builderChildNode_->GetEventHub<EventHub>();
569     if (eventHub) {
570         eventHub->SetEnabled(false);
571     }
572 }
573 
GetImageSourceInfoFromTheme(int32_t RadioIndicator)574 ImageSourceInfo RadioPattern::GetImageSourceInfoFromTheme(int32_t RadioIndicator)
575 {
576     ImageSourceInfo imageSourceInfo;
577     auto host = GetHost();
578     CHECK_NULL_RETURN(host, imageSourceInfo);
579     auto* pipeline = host->GetContextWithCheck();
580     CHECK_NULL_RETURN(pipeline, imageSourceInfo);
581     auto radioTheme = pipeline->GetTheme<RadioTheme>();
582     CHECK_NULL_RETURN(radioTheme, imageSourceInfo);
583     switch (RadioIndicator) {
584         case static_cast<int32_t>(RadioIndicatorType::TICK):
585             imageSourceInfo.SetResourceId(radioTheme->GetTickResourceId());
586             break;
587         case static_cast<int32_t>(RadioIndicatorType::DOT):
588             imageSourceInfo.SetResourceId(radioTheme->GetDotResourceId());
589             break;
590         default:
591             imageSourceInfo.SetResourceId(radioTheme->GetTickResourceId());
592             break;
593     }
594     return imageSourceInfo;
595 }
596 
UpdateInternalResource(ImageSourceInfo & sourceInfo)597 void RadioPattern::UpdateInternalResource(ImageSourceInfo& sourceInfo)
598 {
599     CHECK_NULL_VOID(sourceInfo.IsInternalResource());
600     auto host = GetHost();
601     CHECK_NULL_VOID(host);
602     auto* pipeline = host->GetContextWithCheck();
603     CHECK_NULL_VOID(pipeline);
604     auto iconTheme = pipeline->GetTheme<IconTheme>();
605     CHECK_NULL_VOID(iconTheme);
606     auto radioTheme = pipeline->GetTheme<RadioTheme>();
607     CHECK_NULL_VOID(radioTheme);
608     auto iconPath = iconTheme->GetIconPath(sourceInfo.GetResourceId());
609     if (iconPath.empty()) {
610         return;
611     }
612     sourceInfo.SetSrc(iconPath);
613 }
614 
LoadBuilder()615 void RadioPattern::LoadBuilder()
616 {
617     RefPtr<UINode> customNode;
618     if (builder_) {
619         auto host = GetHost();
620         CHECK_NULL_VOID(host);
621         auto childNode = DynamicCast<FrameNode>(host->GetFirstChild());
622         if (preTypeIsBuilder_) {
623             return;
624         } else {
625             if (childNode) {
626                 host->RemoveChild(childNode);
627             }
628         }
629         NG::ScopedViewStackProcessor builderViewStackProcessor;
630         builder_();
631         customNode = NG::ViewStackProcessor::GetInstance()->Finish();
632         CHECK_NULL_VOID(customNode);
633         builderChildNode_ = AceType::DynamicCast<FrameNode>(customNode);
634         CHECK_NULL_VOID(builderChildNode_);
635         preTypeIsBuilder_ = true;
636         builderChildNode_->MountToParent(host);
637         host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE);
638     }
639 }
640 
InitializeParam(Dimension & defaultWidth,Dimension & defaultHeight,Dimension & horizontalPadding,Dimension & verticalPadding)641 void RadioPattern::InitializeParam(
642     Dimension& defaultWidth, Dimension& defaultHeight, Dimension& horizontalPadding, Dimension& verticalPadding)
643 {
644     auto host = GetHost();
645     CHECK_NULL_VOID(host);
646     auto* pipeline = host->GetContextWithCheck();
647     CHECK_NULL_VOID(pipeline);
648     auto radioTheme = pipeline->GetTheme<RadioTheme>();
649     CHECK_NULL_VOID(radioTheme);
650     defaultWidth = radioTheme->GetWidth();
651     defaultHeight = radioTheme->GetHeight();
652     horizontalPadding = radioTheme->GetDefaultPaddingSize();
653     verticalPadding = radioTheme->GetDefaultPaddingSize();
654 }
655 
GetChildContentSize()656 CalcSize RadioPattern::GetChildContentSize()
657 {
658     auto host = GetHost();
659     auto layoutProperty = host->GetLayoutProperty<LayoutProperty>();
660     auto &&layoutConstraint = layoutProperty->GetCalcLayoutConstraint();
661     if (layoutConstraint && layoutConstraint->selfIdealSize) {
662         auto selfIdealSize = layoutConstraint->selfIdealSize;
663         if (selfIdealSize->IsValid()) {
664             auto height = selfIdealSize->Height()->GetDimension() * DEFAULT_CUSTOM_SCALE;
665             auto width = selfIdealSize->Width()->GetDimension() * DEFAULT_CUSTOM_SCALE;
666             auto length = std::min(width, height);
667             return CalcSize(NG::CalcLength(length), NG::CalcLength(length));
668         }
669         if (selfIdealSize->Width().has_value()) {
670             auto width = selfIdealSize->Width()->GetDimension() * DEFAULT_CUSTOM_SCALE;
671             return CalcSize(NG::CalcLength(width), NG::CalcLength(width));
672         }
673         if (selfIdealSize->Height().has_value()) {
674             auto height = selfIdealSize->Height()->GetDimension() * DEFAULT_CUSTOM_SCALE;
675             return CalcSize(NG::CalcLength(height), NG::CalcLength(height));
676         }
677     }
678     Dimension defaultWidth;
679     Dimension defaultHeight;
680     Dimension horizontalPadding;
681     Dimension verticalPadding;
682     InitializeParam(defaultWidth, defaultHeight, horizontalPadding, verticalPadding);
683     auto width = (defaultWidth - horizontalPadding * RADIO_PADDING_COUNT) * DEFAULT_CUSTOM_SCALE;
684     auto height = (defaultHeight - verticalPadding * RADIO_PADDING_COUNT) * DEFAULT_CUSTOM_SCALE;
685     return CalcSize(NG::CalcLength(width), NG::CalcLength(height));
686 }
687 
UpdateGroupCheckStatus(const RefPtr<FrameNode> & frameNode,const RefPtr<GroupManager> & groupManager,bool check)688 void RadioPattern::UpdateGroupCheckStatus(
689     const RefPtr<FrameNode>& frameNode, const RefPtr<GroupManager>& groupManager, bool check)
690 {
691     frameNode->MarkNeedRenderOnly();
692     if (Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE)) {
693         if (!isFirstCreated_ && check) {
694             startEnterAnimation();
695         }
696     }
697 
698     auto radioEventHub = GetEventHub<RadioEventHub>();
699     CHECK_NULL_VOID(radioEventHub);
700     if (check) {
701         groupManager->UpdateRadioGroupValue(radioEventHub->GetGroup(), frameNode->GetId());
702     } else {
703         auto radioPaintProperty = frameNode->GetPaintProperty<RadioPaintProperty>();
704         CHECK_NULL_VOID(radioPaintProperty);
705         radioPaintProperty->UpdateRadioCheck(check);
706         if (!isGroupChanged_) {
707             isOnAnimationFlag_ = false;
708         }
709     }
710 
711     if (!isFirstCreated_) {
712         TAG_LOGD(AceLogTag::ACE_SELECT_COMPONENT, "radio node %{public}d fire group change event %{public}d",
713             frameNode->GetId(), check);
714         radioEventHub->UpdateChangeEvent(check);
715     }
716 }
717 
UpdateUIStatus(bool check)718 void RadioPattern::UpdateUIStatus(bool check)
719 {
720     uiStatus_ = check ? UIStatus::SELECTED : UIStatus::UNSELECTED;
721     auto host = GetHost();
722     CHECK_NULL_VOID(host);
723     host->MarkNeedRenderOnly();
724 }
725 
InitOnKeyEvent(const RefPtr<FocusHub> & focusHub)726 void RadioPattern::InitOnKeyEvent(const RefPtr<FocusHub>& focusHub)
727 {
728     auto getInnerPaintRectCallback = [wp = WeakClaim(this)](RoundRect& paintRect) {
729         auto pattern = wp.Upgrade();
730         if (pattern) {
731             pattern->GetInnerFocusPaintRect(paintRect);
732         }
733     };
734     focusHub->SetInnerFocusPaintRectCallback(getInnerPaintRectCallback);
735 }
736 
GetInnerFocusPaintRect(RoundRect & paintRect)737 void RadioPattern::GetInnerFocusPaintRect(RoundRect& paintRect)
738 {
739     auto host = GetHost();
740     CHECK_NULL_VOID(host);
741     auto* pipeline = host->GetContextWithCheck();
742     CHECK_NULL_VOID(pipeline);
743     auto radioTheme = pipeline->GetTheme<RadioTheme>();
744     CHECK_NULL_VOID(radioTheme);
745     auto focusPaintPadding = radioTheme->GetFocusPaintPadding().ConvertToPx();
746     float outCircleRadius = size_.Width() / 2 + focusPaintPadding;
747     float originX = offset_.GetX() - focusPaintPadding;
748     float originY = offset_.GetY() - focusPaintPadding;
749     float width = size_.Width() + 2 * focusPaintPadding;
750     float height = size_.Height() + 2 * focusPaintPadding;
751     paintRect.SetRect({ originX, originY, width, height });
752     paintRect.SetCornerRadius(RoundRect::CornerPos::TOP_LEFT_POS, outCircleRadius, outCircleRadius);
753     paintRect.SetCornerRadius(RoundRect::CornerPos::TOP_RIGHT_POS, outCircleRadius, outCircleRadius);
754     paintRect.SetCornerRadius(RoundRect::CornerPos::BOTTOM_LEFT_POS, outCircleRadius, outCircleRadius);
755     paintRect.SetCornerRadius(RoundRect::CornerPos::BOTTOM_RIGHT_POS, outCircleRadius, outCircleRadius);
756 }
757 
GetFocusPattern() const758 FocusPattern RadioPattern::GetFocusPattern() const
759 {
760     auto pipeline = PipelineBase::GetCurrentContext();
761     CHECK_NULL_RETURN(pipeline, FocusPattern());
762     auto radioTheme = pipeline->GetTheme<RadioTheme>();
763     CHECK_NULL_RETURN(radioTheme, FocusPattern());
764     FocusPaintParam focusPaintParam;
765     if (Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE)) {
766         auto focusColor = radioTheme->GetFocusColor();
767         focusPaintParam.SetPaintColor(focusColor);
768     } else {
769         auto activeColor = radioTheme->GetActiveColor();
770         focusPaintParam.SetPaintColor(activeColor);
771     }
772     return { FocusType::NODE, true, FocusStyleType::CUSTOM_REGION, focusPaintParam };
773 }
774 
OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper> & dirty,const DirtySwapConfig &)775 bool RadioPattern::OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper>& dirty, const DirtySwapConfig& /*config*/)
776 {
777     auto geometryNode = dirty->GetGeometryNode();
778     offset_ = geometryNode->GetContentOffset();
779     size_ = geometryNode->GetContentSize();
780     if (!isUserSetResponseRegion_) {
781         AddHotZoneRect();
782     }
783     return true;
784 }
785 
786 // Set the default hot zone for the component.
AddHotZoneRect()787 void RadioPattern::AddHotZoneRect()
788 {
789     hotZoneOffset_.SetX(offset_.GetX() - hotZoneHorizontalPadding_.ConvertToPx());
790     hotZoneOffset_.SetY(offset_.GetY() - hotZoneVerticalPadding_.ConvertToPx());
791     hotZoneSize_.SetWidth(
792         size_.Width() + FOR_HOTZONESIZE_CALCULATE_MULTIPLY_TWO * hotZoneHorizontalPadding_.ConvertToPx());
793     hotZoneSize_.SetHeight(
794         size_.Height() + FOR_HOTZONESIZE_CALCULATE_MULTIPLY_TWO * hotZoneVerticalPadding_.ConvertToPx());
795     DimensionRect hotZoneRegion;
796     hotZoneRegion.SetSize(DimensionSize(Dimension(hotZoneSize_.Width()), Dimension(hotZoneSize_.Height())));
797     hotZoneRegion.SetOffset(DimensionOffset(Dimension(hotZoneOffset_.GetX()), Dimension(hotZoneOffset_.GetY())));
798     auto host = GetHost();
799     CHECK_NULL_VOID(host);
800     auto gestureHub = host->GetOrCreateGestureEventHub();
801     CHECK_NULL_VOID(gestureHub);
802     gestureHub->SetResponseRegion(std::vector<DimensionRect>({ hotZoneRegion }));
803 }
804 
RemoveLastHotZoneRect() const805 void RadioPattern::RemoveLastHotZoneRect() const
806 {
807     auto host = GetHost();
808     CHECK_NULL_VOID(host);
809     host->RemoveLastHotZoneRect();
810 }
811 
ProvideRestoreInfo()812 std::string RadioPattern::ProvideRestoreInfo()
813 {
814     auto jsonObj = JsonUtil::Create(true);
815     auto radioPaintProperty = GetPaintProperty<RadioPaintProperty>();
816     CHECK_NULL_RETURN(radioPaintProperty, "");
817     jsonObj->Put("checked", radioPaintProperty->GetRadioCheck().value_or(false));
818     return jsonObj->ToString();
819 }
820 
OnRestoreInfo(const std::string & restoreInfo)821 void RadioPattern::OnRestoreInfo(const std::string& restoreInfo)
822 {
823     auto radioPaintProperty = GetPaintProperty<RadioPaintProperty>();
824     CHECK_NULL_VOID(radioPaintProperty);
825     auto info = JsonUtil::ParseJsonString(restoreInfo);
826     if (!info->IsValid() || !info->IsObject()) {
827         return;
828     }
829     auto jsonChecked = info->GetValue("checked");
830     radioPaintProperty->UpdateRadioCheck(jsonChecked->GetBool());
831     OnModifyDone();
832 }
833 
HandleEnabled()834 void RadioPattern::HandleEnabled()
835 {
836     auto host = GetHost();
837     CHECK_NULL_VOID(host);
838     auto eventHub = host->GetEventHub<EventHub>();
839     CHECK_NULL_VOID(eventHub);
840     auto enabled = eventHub->IsEnabled();
841     auto radioPaintProperty = GetHost()->GetPaintProperty<RadioPaintProperty>();
842     if (enabled_ != enabled) {
843         enabled_ = enabled;
844         if (!enabled_ && Container::GreatOrEqualAPITargetVersion(PlatformVersion::VERSION_TWELVE)) {
845             if (!radioModifier_) {
846                 radioModifier_ = AceType::MakeRefPtr<RadioModifier>();
847             }
848             if (!radioPaintProperty->HasRadioCheck() || !radioPaintProperty->GetRadioCheckValue()) {
849                 radioModifier_->SetUIStatus(UIStatus::UNSELECTED);
850             }
851         }
852         auto paintProperty = GetPaintProperty<RadioPaintProperty>();
853         CHECK_NULL_VOID(paintProperty);
854         paintProperty->UpdatePropertyChangeFlag(PROPERTY_UPDATE_RENDER);
855     }
856 }
857 
SetRadioChecked(bool check)858 void RadioPattern::SetRadioChecked(bool check)
859 {
860     auto host = GetHost();
861     CHECK_NULL_VOID(host);
862     auto eventHub = host->GetEventHub<EventHub>();
863     CHECK_NULL_VOID(eventHub);
864     auto enabled = eventHub->IsEnabled();
865     if (!enabled) {
866         return;
867     }
868     auto paintProperty = host->GetPaintProperty<RadioPaintProperty>();
869     CHECK_NULL_VOID(paintProperty);
870     paintProperty->UpdateRadioCheck(check);
871     UpdateState();
872     OnModifyDone();
873 }
874 
FireBuilder()875 void RadioPattern::FireBuilder()
876 {
877     auto host = GetHost();
878     CHECK_NULL_VOID(host);
879     host->RemoveChildAndReturnIndex(customNode_);
880     if (makeFunc_.has_value()) {
881         customNode_ = BuildContentModifierNode();
882         CHECK_NULL_VOID(customNode_);
883         host->AddChild(customNode_, 0);
884     }
885     host->MarkNeedFrameFlushDirty(PROPERTY_UPDATE_MEASURE);
886 }
887 
BuildContentModifierNode()888 RefPtr<FrameNode> RadioPattern::BuildContentModifierNode()
889 {
890     CHECK_NULL_RETURN(makeFunc_, nullptr);
891     auto host = GetHost();
892     CHECK_NULL_RETURN(host, nullptr);
893     auto eventHub = host->GetEventHub<RadioEventHub>();
894     CHECK_NULL_RETURN(eventHub, nullptr);
895     auto value = eventHub->GetValue();
896     auto enabled = eventHub->IsEnabled();
897     auto paintProperty = host->GetPaintProperty<RadioPaintProperty>();
898     CHECK_NULL_RETURN(paintProperty, nullptr);
899     bool isChecked = false;
900     if (paintProperty->HasRadioCheck()) {
901         isChecked = paintProperty->GetRadioCheckValue();
902     } else {
903         isChecked = false;
904     }
905     RadioConfiguration radioConfiguration(value, isChecked, enabled);
906     return (makeFunc_.value())(radioConfiguration);
907 }
908 
GetGroupManager()909 RefPtr<GroupManager> RadioPattern::GetGroupManager()
910 {
911     auto manager = groupManager_.Upgrade();
912     if (manager) {
913         return manager;
914     }
915     groupManager_ = GroupManager::GetGroupManager();
916     return groupManager_.Upgrade();
917 }
918 
SetPrePageIdToLastPageId()919 void RadioPattern::SetPrePageIdToLastPageId()
920 {
921     if (!Container::IsInSubContainer()) {
922         auto pipelineContext = PipelineContext::GetCurrentContext();
923         CHECK_NULL_VOID(pipelineContext);
924         auto stageManager = pipelineContext->GetStageManager();
925         CHECK_NULL_VOID(stageManager);
926         auto pageNode = stageManager->GetLastPage();
927         CHECK_NULL_VOID(pageNode);
928         SetPrePageId(pageNode->GetId());
929     }
930 }
931 } // namespace OHOS::Ace::NG
932