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/button/render_button.h"
17 
18 #include "base/log/event_report.h"
19 
20 namespace OHOS::Ace {
21 namespace {
22 
23 // Watch button definitions
24 constexpr Dimension TEXT_BUTTON_MAX_WIDTH = 116.5_vp;
25 
26 // Download button definitions
27 constexpr double DOWNLOAD_FULL_PERCENT = 100.0;
28 constexpr double MAX_TRANSITION_TIME = 5000.0;
29 constexpr double MIN_TRANSITION_TIME = 200.0;
30 constexpr double MILLISECOND_PER_PERCENT = 20.0;
31 constexpr double SECOND_TO_MILLISECOND = 1000.0;
32 
33 // Definition for animation
34 constexpr double TV_EXPAND_SCALE = 1.05;
35 constexpr double TV_REDUCE_SCALE = 0.95;
36 constexpr double WATCH_SCALE = 0.8;
37 constexpr double MASKING_ANIMATION_RATIO = 10.0;
38 constexpr float KEY_TIME_START = 0.0f;
39 constexpr float KEY_TIME_MID = 0.5f;
40 constexpr float KEY_TIME_END = 1.0f;
41 constexpr float PRESS_UP_OPACITY = 1.0f;
42 constexpr float PRESS_DOWN_OPACITY = 0.95f;
43 constexpr int32_t WATCH_DURATION_DOWN = 200;
44 constexpr int32_t WATCH_DURATION_UP = 250;
45 constexpr int32_t TV_CLICK_DURATION = 200;
46 constexpr int32_t TV_FOCUS_SCALE_DURATION = 100;
47 constexpr int32_t HOVER_ANIMATION_DURATION = 250;
48 constexpr int32_t PRESS_ANIMATION_DURATION = 100;
49 
50 } // namespace
51 
RenderButton()52 RenderButton::RenderButton()
53 {
54     Initialize();
55 }
56 
Initialize()57 void RenderButton::Initialize()
58 {
59     auto wp = AceType::WeakClaim(this);
60     touchRecognizer_ = AceType::MakeRefPtr<RawRecognizer>();
61     touchRecognizer_->SetOnTouchDown([wp](const TouchEventInfo&) {
62         auto button = wp.Upgrade();
63         if (button) {
64             button->HandleTouchEvent(true);
65         }
66     });
67     touchRecognizer_->SetOnTouchUp([wp](const TouchEventInfo&) {
68         auto button = wp.Upgrade();
69         if (button) {
70             button->HandleTouchEvent(false);
71         }
72     });
73     touchRecognizer_->SetOnTouchCancel([wp](const TouchEventInfo&) {
74         auto button = wp.Upgrade();
75         if (button) {
76             button->HandleTouchEvent(false);
77         }
78     });
79     touchRecognizer_->SetOnTouchMove([wp](const TouchEventInfo& info) {
80         auto button = wp.Upgrade();
81         if (button) {
82             button->HandleMoveEvent(info);
83         }
84     });
85 
86     clickRecognizer_ = AceType::MakeRefPtr<ClickRecognizer>();
87     clickRecognizer_->SetOnClick([wp](const ClickInfo& info) {
88         auto button = wp.Upgrade();
89         if (button) {
90             const auto context = button->GetContext().Upgrade();
91             if (context && context->GetIsDeclarative()) {
92                 button->HandleClickEvent(info);
93             } else {
94                 button->HandleClickEvent();
95             }
96         }
97     });
98     clickRecognizer_->SetRemoteMessage([wp](const ClickInfo& info) {
99         auto button = wp.Upgrade();
100         if (button) {
101             const auto context = button->GetContext().Upgrade();
102             if (context && context->GetIsDeclarative()) {
103                 button->HandleRemoteMessageEvent(info);
104             } else {
105                 button->HandleRemoteMessageEvent();
106             }
107         }
108     });
109 }
110 
InitAccessibilityEventListener()111 void RenderButton::InitAccessibilityEventListener()
112 {
113     auto refNode = accessibilityNode_.Upgrade();
114     if (!refNode) {
115         return;
116     }
117     refNode->AddSupportAction(AceAction::ACTION_ACCESSIBILITY_FOCUS);
118 
119     auto weakPtr = AceType::WeakClaim(this);
120     refNode->SetActionFocusImpl([weakPtr]() {
121         auto button = weakPtr.Upgrade();
122         if (button) {
123             button->HandleFocusEvent(true);
124         }
125     });
126 }
127 
OnPaintFinish()128 void RenderButton::OnPaintFinish()
129 {
130     if (isFocus_) {
131         if (isTv_) {
132             DisplayFocusAnimation();
133         }
134         if (isPhone_) {
135             UpdateFocusAnimation(INIT_SCALE);
136         }
137     }
138     UpdateAccessibility();
139     InitAccessibilityEventListener();
140 }
141 
UpdateAccessibility()142 void RenderButton::UpdateAccessibility()
143 {
144     const auto& context = context_.Upgrade();
145     if (!context) {
146         return;
147     }
148     auto viewScale = context->GetViewScale();
149     if (NearZero(viewScale)) {
150         return;
151     }
152     auto accessibilityNode = GetAccessibilityNode().Upgrade();
153     if (!accessibilityNode) {
154         return;
155     }
156 #if defined(PREVIEW)
157     Offset globalOffset = GetGlobalOffset();
158     if (isTv_ && isFocus_) {
159         Size size = GetLayoutSize();
160         Offset scaleCenter =
161             Offset(globalOffset.GetX() + size.Width() / 2.0, globalOffset.GetY() + size.Height() / 2.0);
162         accessibilityNode->SetScaleCenter(scaleCenter);
163         accessibilityNode->SetScale(TV_EXPAND_SCALE);
164     }
165 #else
166     // control button without box
167     UpdateAccessibilityPosition();
168 #endif
169     accessibilityNode->SetMarginSize(Size());
170     if (!GetAccessibilityText().empty()) {
171         accessibilityNode->SetAccessibilityLabel(GetAccessibilityText());
172     }
173 }
174 
HandleTouchEvent(bool isTouch)175 void RenderButton::HandleTouchEvent(bool isTouch)
176 {
177     isClicked_ = isTouch;
178     if (isClicked_) {
179         OnStatusStyleChanged(VisualState::PRESSED);
180         isMoveEventValid_ = true;
181     } else {
182         OnStatusStyleChanged(VisualState::NORMAL);
183     }
184     if (isMoveEventValid_ || isWatch_) {
185         PlayTouchAnimation();
186     }
187 }
188 
HandleMoveEvent(const TouchEventInfo & info)189 void RenderButton::HandleMoveEvent(const TouchEventInfo& info)
190 {
191     if (!isMoveEventValid_) {
192         return;
193     }
194     if (info.GetTouches().empty()) {
195         return;
196     }
197     const auto& locationInfo = info.GetTouches().front();
198     double moveDeltaX = locationInfo.GetLocalLocation().GetX();
199     double moveDeltaY = locationInfo.GetLocalLocation().GetY();
200     if ((moveDeltaX < 0 || moveDeltaX > buttonSize_.Width()) || (moveDeltaY < 0 || moveDeltaY > buttonSize_.Height())) {
201         isClicked_ = false;
202         MarkNeedRender();
203         OnStatusStyleChanged(VisualState::NORMAL);
204         isMoveEventValid_ = false;
205     }
206 }
207 
HandleClickEvent(const ClickInfo & info)208 void RenderButton::HandleClickEvent(const ClickInfo& info)
209 {
210     if (!buttonComponent_) {
211         return;
212     }
213     auto onClickWithInfo =
214         AceAsyncEvent<void(const ClickInfo&)>::Create(buttonComponent_->GetClickedEventId(), context_);
215     if (onClickWithInfo) {
216         onClickWithInfo(info);
217     }
218     PlayClickAnimation();
219 }
220 
HandleClickEvent()221 void RenderButton::HandleClickEvent()
222 {
223     if (!buttonComponent_) {
224         return;
225     }
226     auto onClick = AceAsyncEvent<void()>::Create(buttonComponent_->GetClickedEventId(), context_);
227     if (onClick) {
228         onClick();
229     }
230     PlayClickAnimation();
231 }
232 
HandleKeyEnterEvent(const ClickInfo & info)233 bool RenderButton::HandleKeyEnterEvent(const ClickInfo& info)
234 {
235     if (!buttonComponent_) {
236         return false;
237     }
238     auto onEnterWithInfo =
239         AceAsyncEvent<void(const ClickInfo&)>::Create(buttonComponent_->GetKeyEnterEventId(), context_);
240     if (onEnterWithInfo) {
241         onEnterWithInfo(info);
242         return true;
243     }
244     return false;
245 }
246 
HandleKeyEnterEvent()247 void RenderButton::HandleKeyEnterEvent()
248 {
249     if (!buttonComponent_) {
250         return;
251     }
252     auto onEnter = AceAsyncEvent<void()>::Create(buttonComponent_->GetKeyEnterEventId(), context_);
253     if (onEnter) {
254         onEnter();
255     }
256 }
257 
HandleRemoteMessageEvent(const ClickInfo & info)258 void RenderButton::HandleRemoteMessageEvent(const ClickInfo& info)
259 {
260     if (!buttonComponent_) {
261         return;
262     }
263     auto onRemoteMessagekWithInfo =
264         AceAsyncEvent<void(const ClickInfo&)>::Create(buttonComponent_->GetRemoteMessageEventId(), context_);
265     if (onRemoteMessagekWithInfo) {
266         onRemoteMessagekWithInfo(info);
267     }
268     PlayClickAnimation();
269 }
270 
HandleRemoteMessageEvent()271 void RenderButton::HandleRemoteMessageEvent()
272 {
273     if (!buttonComponent_) {
274         return;
275     }
276     auto onRemoteMessage = AceAsyncEvent<void()>::Create(buttonComponent_->GetRemoteMessageEventId(), context_);
277     if (onRemoteMessage) {
278         onRemoteMessage();
279     }
280     PlayClickAnimation();
281 }
282 
OnTouchTestHit(const Offset & coordinateOffset,const TouchRestrict & touchRestrict,TouchTestResult & result)283 void RenderButton::OnTouchTestHit(
284     const Offset& coordinateOffset, const TouchRestrict& touchRestrict, TouchTestResult& result)
285 {
286     if ((!touchRecognizer_) || (!clickRecognizer_)) {
287         return;
288     }
289     touchRecognizer_->SetCoordinateOffset(coordinateOffset);
290     result.emplace_back(touchRecognizer_);
291     result.emplace_back(clickRecognizer_);
292 }
293 
HandleFocusEvent(bool isFocus)294 void RenderButton::HandleFocusEvent(bool isFocus)
295 {
296     isFocus_ = isFocus;
297     needFocusColor_ = isFocus_ && isTv_;
298     MarkNeedRender();
299 }
300 
DisplayFocusAnimation()301 void RenderButton::DisplayFocusAnimation()
302 {
303     if (!animationRunning_ && isTv_) {
304         UpdateAnimationParam(TV_EXPAND_SCALE);
305     }
306 }
307 
AnimateMouseHoverEnter()308 void RenderButton::AnimateMouseHoverEnter()
309 {
310     OnMouseHoverEnterTest();
311 }
OnMouseHoverEnterTest()312 void RenderButton::OnMouseHoverEnterTest()
313 {
314     if (!buttonComponent_) {
315         return;
316     }
317     if (hoverAnimationType_ == HoverAnimationType::NONE) {
318         if (buttonComponent_->IsPopupButton()) {
319             needHoverColor_ = true;
320             MarkNeedRender();
321         }
322         LOGW("HoverAnimationType: %{public}d is not supported in this component.", hoverAnimationType_);
323         return;
324     }
325     ButtonType type = buttonComponent_->GetType();
326     if ((isPhone_ || isTablet_) && ((type == ButtonType::TEXT) || (type == ButtonType::NORMAL))) {
327         needHoverColor_ = true;
328         MarkNeedRender();
329     } else {
330         ResetController(hoverControllerExit_);
331         if (!hoverControllerEnter_) {
332             hoverControllerEnter_ = CREATE_ANIMATOR(context_);
333         }
334         scaleAnimationEnter_ = AceType::MakeRefPtr<KeyframeAnimation<float>>();
335         CreateFloatAnimation(scaleAnimationEnter_, 1.0, 1.05);
336         hoverControllerEnter_->AddInterpolator(scaleAnimationEnter_);
337         hoverControllerEnter_->SetDuration(HOVER_ANIMATION_DURATION);
338         hoverControllerEnter_->Play();
339         hoverControllerEnter_->SetFillMode(FillMode::FORWARDS);
340     }
341 }
342 
AnimateMouseHoverExit()343 void RenderButton::AnimateMouseHoverExit()
344 {
345     OnMouseHoverExitTest();
346 }
OnMouseHoverExitTest()347 void RenderButton::OnMouseHoverExitTest()
348 {
349     if (hoverAnimationType_ == HoverAnimationType::NONE && !buttonComponent_->IsPopupButton()) {
350         LOGW("HoverAnimationType: %{public}d is not supported in this component.", hoverAnimationType_);
351         return;
352     }
353     if (needHoverColor_) {
354         needHoverColor_ = false;
355         MarkNeedRender();
356     } else {
357         ResetController(hoverControllerEnter_);
358         if (!hoverControllerExit_) {
359             hoverControllerExit_ = CREATE_ANIMATOR(context_);
360         }
361         scaleAnimationExit_ = AceType::MakeRefPtr<KeyframeAnimation<float>>();
362         auto begin = scale_;
363         CreateFloatAnimation(scaleAnimationExit_, begin, 1.0);
364         hoverControllerExit_->AddInterpolator(scaleAnimationExit_);
365         hoverControllerExit_->SetDuration(HOVER_ANIMATION_DURATION);
366         hoverControllerExit_->Play();
367         hoverControllerExit_->SetFillMode(FillMode::FORWARDS);
368     }
369 }
370 
OnMouseClickDownAnimation()371 void RenderButton::OnMouseClickDownAnimation()
372 {
373     if (!needHoverColor_) {
374         ResetController(clickControllerUp_);
375         if (!clickControllerDown_) {
376             clickControllerDown_ = CREATE_ANIMATOR(context_);
377         }
378         scaleAnimationDown_ = AceType::MakeRefPtr<KeyframeAnimation<float>>();
379         auto begin = scale_;
380         CreateFloatAnimation(scaleAnimationDown_, begin, 1.0);
381         clickControllerDown_->AddInterpolator(scaleAnimationDown_);
382         clickControllerDown_->SetDuration(HOVER_ANIMATION_DURATION);
383         clickControllerDown_->Play();
384         clickControllerDown_->SetFillMode(FillMode::FORWARDS);
385     }
386 }
387 
OnMouseClickUpAnimation()388 void RenderButton::OnMouseClickUpAnimation()
389 {
390     if (!needHoverColor_) {
391         ResetController(clickControllerDown_);
392         if (!clickControllerUp_) {
393             clickControllerUp_ = CREATE_ANIMATOR(context_);
394         }
395         scaleAnimationUp_ = AceType::MakeRefPtr<KeyframeAnimation<float>>();
396         auto begin = scale_;
397         CreateFloatAnimation(scaleAnimationUp_, begin, 1.05);
398         clickControllerUp_->AddInterpolator(scaleAnimationUp_);
399         clickControllerUp_->SetDuration(HOVER_ANIMATION_DURATION);
400         clickControllerUp_->Play();
401         clickControllerUp_->SetFillMode(FillMode::FORWARDS);
402     }
403 }
404 
CreateFloatAnimation(RefPtr<KeyframeAnimation<float>> & floatAnimation,float beginValue,float endValue)405 void RenderButton::CreateFloatAnimation(
406     RefPtr<KeyframeAnimation<float>>& floatAnimation, float beginValue, float endValue)
407 {
408     if (!floatAnimation) {
409         return;
410     }
411     auto keyframeBegin = AceType::MakeRefPtr<Keyframe<float>>(0.0, beginValue);
412     auto keyframeEnd = AceType::MakeRefPtr<Keyframe<float>>(1.0, endValue);
413     floatAnimation->AddKeyframe(keyframeBegin);
414     floatAnimation->AddKeyframe(keyframeEnd);
415     floatAnimation->AddListener([weakButton = AceType::WeakClaim(this)](float value) {
416         auto button = weakButton.Upgrade();
417         if (button) {
418             button->isHover_ = true;
419             button->scale_ = value;
420             button->MarkNeedRender();
421         }
422     });
423 }
424 
ResetController(RefPtr<Animator> & controller)425 void RenderButton::ResetController(RefPtr<Animator>& controller)
426 {
427     if (controller) {
428         if (!controller->IsStopped()) {
429             controller->Stop();
430         }
431         controller->ClearInterpolators();
432     }
433 }
434 
Update(const RefPtr<Component> & component)435 void RenderButton::Update(const RefPtr<Component>& component)
436 {
437     const RefPtr<ButtonComponent> button = AceType::DynamicCast<ButtonComponent>(component);
438     if (!button) {
439         LOGE("Update error, button component is null");
440         EventReport::SendRenderException(RenderExcepType::RENDER_COMPONENT_ERR);
441         return;
442     }
443     buttonComponent_ = button;
444     if (!controller_) {
445         controller_ = CREATE_ANIMATOR(GetContext());
446     }
447     auto theme = GetTheme<ButtonTheme>();
448     if (theme) {
449         defaultClickedColor_ = theme->GetClickedColor();
450     }
451 
452     hoverAnimationType_ = buttonComponent_->GetMouseAnimationType();
453     width_ = buttonComponent_->GetWidth();
454     height_ = buttonComponent_->GetHeight();
455     aspectRatio_ = buttonComponent_->GetAspectRatio();
456     buttonComponent_->FitTextHeight(height_);
457     layoutFlag_ = button->GetLayoutFlag();
458     // No animation happens on first setting, will animate from background color on click
459     clickedColor_ = AnimatableColor(button->GetClickedColor());
460     backgroundColor_.SetValue(button->GetBackgroundColor().GetValue());
461     stateEffect_ = button->GetStateEffect();
462     isWatch_ = (SystemProperties::GetDeviceType() == DeviceType::WATCH);
463     isTv_ = (SystemProperties::GetDeviceType() == DeviceType::TV);
464     isPhone_ = (SystemProperties::GetDeviceType() == DeviceType::PHONE);
465     isTablet_ = (SystemProperties::GetDeviceType() == DeviceType::TABLET ||
466         SystemProperties::GetDeviceType() == DeviceType::TWO_IN_ONE);
467     auto catchMode =
468         buttonComponent_->GetClickedEventId().IsEmpty() || buttonComponent_->GetClickedEventId().GetCatchMode();
469     static const int32_t bubbleModeVersion = 6;
470     auto pipeline = context_.Upgrade();
471     if (!catchMode) {
472         if (pipeline && pipeline->GetMinPlatformVersion() >= bubbleModeVersion) {
473             catchMode = false;
474         } else {
475             catchMode = true;
476         }
477     }
478     auto catchModeButton = buttonComponent_->GetCatchMode();
479     clickRecognizer_->SetUseCatchMode(catchMode && catchModeButton);
480     SetAccessibilityText(button->GetAccessibilityText());
481     // for control button in container modal
482     if (buttonComponent_->GetClickedEventId().HasPreFunction()) {
483         SetAccessibilityClick(clickRecognizer_);
484     }
485     UpdateDownloadStyles(button);
486 
487     OnStatusStyleChanged(disabled_ ? VisualState::DISABLED : VisualState::NORMAL);
488     MarkNeedLayout();
489 }
490 
PerformLayout()491 void RenderButton::PerformLayout()
492 {
493     if (!buttonComponent_) {
494         LOGE("Fail to perform layout due to buttonComponent is null");
495         return;
496     }
497     minWidth_ = buttonComponent_->GetMinWidth();
498     type_ = buttonComponent_->GetType();
499     widthDefined_ = GreatOrEqual(buttonComponent_->GetWidth().Value(), 0.0);
500     heightDefined_ = GreatOrEqual(buttonComponent_->GetHeight().Value(), 0.0);
501     if (type_ == ButtonType::ARC) {
502         width_ = ARC_BUTTON_WIDTH;
503         height_ = ARC_BUTTON_HEIGHT;
504     }
505     buttonSize_ = Size(NormalizePercentToPx(width_, false), NormalizePercentToPx(height_, true));
506     rrectRadius_ = NormalizeToPx(buttonComponent_->GetRectRadius());
507     layoutSize_ = Measure();
508     SetChildrenLayoutSize();
509     SetLayoutSize(CalculateLayoutSize());
510     SetChildrenAlignment();
511     buttonSize_ = GetLayoutSize() - Size(widthDelta_, widthDelta_);
512     if (type_ == ButtonType::CAPSULE) {
513         rrectRadius_ = buttonSize_.Height() / 2;
514     }
515 }
516 
SetChildrenLayoutSize()517 void RenderButton::SetChildrenLayoutSize()
518 {
519     LayoutParam innerLayoutParam;
520     bool isWatchText = (isWatch_ && (type_ == ButtonType::TEXT));
521     double maxWidth = buttonSize_.Width();
522     if (NearEqual(buttonSize_.Width(), 0.0)) {
523         maxWidth = isWatchText ? NormalizeToPx(TEXT_BUTTON_MAX_WIDTH) : GetLayoutParam().GetMaxSize().Width();
524         maxWidth -= widthDelta_;
525     }
526     double height = buttonSize_.Height();
527     if (buttonComponent_->GetDeclarativeFlag()) {
528         if (!heightDefined_ && type_ != ButtonType::CIRCLE) {
529             height = GetLayoutParam().GetMaxSize().Height();
530         }
531     }
532     innerLayoutParam.SetMaxSize(Size(maxWidth, height));
533     if (GetChildren().empty()) {
534         childrenSize_ = Size();
535     }
536     for (const auto& child : GetChildren()) {
537         child->Layout(innerLayoutParam);
538         childrenSize_.SetWidth(child->GetLayoutSize().Width());
539         childrenSize_.SetHeight(child->GetLayoutSize().Height());
540     }
541 }
542 
CalculateLayoutSize()543 Size RenderButton::CalculateLayoutSize()
544 {
545     Size layoutSize;
546     if (NeedAdaptiveChild()) {
547         double layoutWidth = widthDefined_ ? layoutSize_.Width() : childrenSize_.Width();
548         double layoutHeight = heightDefined_ ? layoutSize_.Height() : childrenSize_.Height();
549         if (GreatNotEqual(aspectRatio_, 0.0)) {
550             // only when height is determined and width is not determined, aspectRatio is calculated base on height
551             if (heightDefined_ && !widthDefined_) {
552                 layoutWidth = layoutHeight * aspectRatio_;
553             } else {
554                 layoutHeight = layoutWidth / aspectRatio_;
555             }
556         }
557         layoutSize = Size(layoutWidth, layoutHeight);
558     } else {
559         if (NearEqual(buttonSize_.Width(), 0.0)) {
560             double width =
561                 (childrenSize_.Width() > NormalizeToPx(minWidth_)) ? childrenSize_.Width() : NormalizeToPx(minWidth_);
562             layoutSize = Size(width, buttonSize_.Height()) + Size(widthDelta_, widthDelta_);
563         } else {
564             layoutSize = layoutSize_;
565         }
566     }
567     if (NeedConstrain()) {
568         layoutSize = GetLayoutParam().Constrain(layoutSize);
569     }
570     return layoutSize;
571 }
572 
NeedAdaptiveChild()573 bool RenderButton::NeedAdaptiveChild()
574 {
575     if (buttonComponent_->GetDeclarativeFlag() && type_ != ButtonType::CIRCLE) {
576         return true;
577     }
578     if ((type_ == ButtonType::TEXT) && isWatch_) {
579         return true;
580     }
581     if ((type_ == ButtonType::ICON) || (type_ == ButtonType::NORMAL)) {
582         return true;
583     }
584     return false;
585 }
586 
NeedConstrain()587 bool RenderButton::NeedConstrain()
588 {
589     if (type_ == ButtonType::CIRCLE) {
590         return false;
591     }
592     if (isWatch_) {
593         if ((type_ == ButtonType::DOWNLOAD) || (type_ == ButtonType::ARC)) {
594             return false;
595         }
596     }
597     return true;
598 }
599 
SetChildrenAlignment()600 void RenderButton::SetChildrenAlignment()
601 {
602     if (GetChildren().empty()) {
603         return;
604     }
605     auto& child = GetChildren().front();
606     Alignment alignment = (type_ == ButtonType::ARC) ? Alignment::TOP_CENTER : Alignment::CENTER;
607     child->SetPosition(Alignment::GetAlignPosition(GetLayoutSize(), child->GetLayoutSize(), alignment));
608 }
609 
SetProgress(uint32_t progress)610 void RenderButton::SetProgress(uint32_t progress)
611 {
612     if (isWatch_) {
613         if (progress >= static_cast<uint32_t>(round(DOWNLOAD_FULL_PERCENT))) {
614             progressDisplay_ = false;
615             return;
616         }
617         progressDisplay_ = progress > 0 ? true : false;
618         progressPercent_ = progress / DOWNLOAD_FULL_PERCENT;
619         progressWidth_ = buttonSize_.Width() * progressPercent_;
620         MarkNeedRender();
621         return;
622     }
623     needUpdateAnimation_ = false;
624     if (!NearEqual(progress, previousValue_)) {
625         animationDuring_ = std::chrono::steady_clock::now() - previousUpdateTime_;
626         percentChange_ = progress - previousValue_;
627         previousValue_ = progress;
628         previousUpdateTime_ = std::chrono::steady_clock::now();
629         needUpdateAnimation_ = true;
630     }
631     UpdateProgressAnimation();
632     MarkNeedLayout();
633 }
634 
UpdateDownloadStyles(const RefPtr<ButtonComponent> & button)635 void RenderButton::UpdateDownloadStyles(const RefPtr<ButtonComponent>& button)
636 {
637     if (button->GetType() != ButtonType::DOWNLOAD) {
638         return;
639     }
640     auto pipelineContext = GetContext().Upgrade();
641     if (pipelineContext) {
642         if (!progressController_) {
643             progressController_ = CREATE_ANIMATOR(pipelineContext);
644         }
645     }
646     progressColor_ = button->GetProgressColor();
647     progressFocusColor_ = button->GetProgressFocusColor();
648     progressDiameter_ = NormalizeToPx(button->GetProgressDiameter());
649     const auto& buttonController = button->GetButtonController();
650     if (buttonController) {
651         buttonController->SetProgressCallback([weak = AceType::WeakClaim(this)](uint32_t progress) {
652             auto renderButton = weak.Upgrade();
653             if (renderButton) {
654                 renderButton->SetProgress(progress);
655             }
656         });
657     }
658 }
659 
UpdateProgressAnimation()660 void RenderButton::UpdateProgressAnimation()
661 {
662     if (!needUpdateAnimation_) {
663         return;
664     }
665     auto animation = AceType::MakeRefPtr<CurveAnimation<double>>(progressPercent_, previousValue_, Curves::EASE_OUT);
666     animation->AddListener([weak = WeakClaim(this)](const double& value) {
667         auto renderButton = weak.Upgrade();
668         if (!renderButton) {
669             return;
670         }
671         renderButton->progressDisplay_ = GreatNotEqual(value, 0.0) ? true : false;
672         renderButton->progressPercent_ = value;
673         renderButton->progressWidth_ =
674             renderButton->buttonSize_.Width() * renderButton->progressPercent_ / DOWNLOAD_FULL_PERCENT;
675         if (GreatOrEqual(renderButton->progressPercent_, DOWNLOAD_FULL_PERCENT)) {
676             renderButton->progressDisplay_ = false;
677         }
678         renderButton->MarkNeedRender();
679     });
680     if (!progressController_) {
681         return;
682     }
683     double change = percentChange_ * MILLISECOND_PER_PERCENT;
684     double during = animationDuring_.count() * SECOND_TO_MILLISECOND;
685     double duration = GreatNotEqual(std::abs(change), during) ? std::abs(change) : during;
686     if (LessNotEqual(duration, MIN_TRANSITION_TIME) || (previousValue_ == DOWNLOAD_FULL_PERCENT)) {
687         duration = MIN_TRANSITION_TIME;
688     }
689     if (GreatNotEqual(duration, MAX_TRANSITION_TIME)) {
690         duration = MAX_TRANSITION_TIME;
691     }
692     progressController_->ClearInterpolators();
693     progressController_->AddInterpolator(animation);
694     progressController_->SetDuration(static_cast<int32_t>(round(duration)));
695     progressController_->SetIteration(1);
696     progressController_->Stop();
697     progressController_->Play();
698 }
699 
UpdateAnimationParam(double value)700 void RenderButton::UpdateAnimationParam(double value)
701 {
702     UpdateFocusAnimation(value);
703     if (!animationRunning_) {
704         return;
705     }
706 
707     if (isWatch_ || isTv_) {
708         scale_ = value;
709     }
710     if (isOpacityAnimation_) {
711         opacity_ = fabs((value - INIT_SCALE) * ratio_);
712     }
713     if (isTouchAnimation_) {
714         maskingOpacity_ = fabs((value - INIT_SCALE) * ratio_ / MASKING_ANIMATION_RATIO);
715     }
716     if (!valueChanged_ && (!NearEqual(value, startValue_))) {
717         valueChanged_ = true;
718     }
719     if ((!isClickAnimation_ && NearEqual(value, endValue_)) || (valueChanged_ && NearEqual(value, startValue_))) {
720         isLastFrame_ = true;
721         valueChanged_ = false;
722         isClickAnimation_ = false;
723     }
724     MarkNeedRender();
725 }
726 
UpdateFocusAnimation(double value)727 void RenderButton::UpdateFocusAnimation(double value)
728 {
729     auto context = context_.Upgrade();
730     if (!context || !isFocus_) {
731         return;
732     }
733     Size sizeDelta = buttonSize_ * (value - INIT_SCALE);
734     Size layoutSize = GetLayoutSize() + sizeDelta;
735     Offset buttonOffset = Offset(sizeDelta.Width() / 2.0, sizeDelta.Height() / 2.0);
736     double radius = rrectRadius_;
737     if (!buttonComponent_) {
738         return;
739     }
740     ButtonType type = buttonComponent_->GetType();
741     if ((type == ButtonType::CIRCLE) || (type == ButtonType::CAPSULE)) {
742         radius = layoutSize.Height() / 2.0;
743     }
744     if (isPhone_ && ((type == ButtonType::TEXT) || (type == ButtonType::NORMAL))) {
745         context->ShowFocusAnimation(RRect::MakeRRect(Rect(Offset(0, 0), layoutSize), Radius(radius)),
746             buttonComponent_->GetFocusAnimationColor(), GetGlobalOffset() - buttonOffset, true);
747         return;
748     }
749     context->ShowFocusAnimation(RRect::MakeRRect(Rect(Offset(0, 0), layoutSize), Radius(radius)),
750         buttonComponent_->GetFocusAnimationColor(), GetGlobalOffset() - buttonOffset);
751     context->ShowShadow(
752         RRect::MakeRRect(Rect(Offset(0, 0), layoutSize), Radius(radius)), GetGlobalOffset() - buttonOffset);
753 }
754 
PlayAnimation(double start,double end,int32_t duration,const FillMode & fillMode)755 void RenderButton::PlayAnimation(double start, double end, int32_t duration, const FillMode& fillMode)
756 {
757     animationRunning_ = true;
758     startValue_ = start;
759     endValue_ = end;
760     if (!NearEqual(start, end)) {
761         ratio_ = INIT_SCALE / (end - start);
762     }
763     auto animation = AceType::MakeRefPtr<CurveAnimation<double>>(start, end, Curves::FRICTION);
764     animation->AddListener(Animation<double>::ValueCallback([weak = AceType::WeakClaim(this)](double value) {
765         auto button = weak.Upgrade();
766         if (button) {
767             button->UpdateAnimationParam(value);
768         }
769     }));
770     if (!controller_) {
771         return;
772     }
773     controller_->ClearInterpolators();
774     controller_->SetDuration(duration);
775     controller_->SetFillMode(fillMode);
776     controller_->AddInterpolator(animation);
777     controller_->Play();
778 }
779 
PlayTouchAnimation()780 void RenderButton::PlayTouchAnimation()
781 {
782     if (isWatch_) {
783         isTouchAnimation_ = true;
784         if (isClicked_) {
785             PlayAnimation(INIT_SCALE, WATCH_SCALE, WATCH_DURATION_DOWN);
786         } else {
787             PlayAnimation(WATCH_SCALE, INIT_SCALE, WATCH_DURATION_UP);
788         }
789     } else {
790         isTouchAnimation_ = true;
791         if (isClicked_) {
792             PlayAnimation(PRESS_UP_OPACITY, PRESS_DOWN_OPACITY, PRESS_ANIMATION_DURATION);
793         } else {
794             PlayAnimation(PRESS_DOWN_OPACITY, PRESS_UP_OPACITY, PRESS_ANIMATION_DURATION);
795         }
796     }
797 }
798 
PlayClickScaleAnimation(float keyTime,int32_t duration)799 void RenderButton::PlayClickScaleAnimation(float keyTime, int32_t duration)
800 {
801     auto startFrame = AceType::MakeRefPtr<Keyframe<double>>(KEY_TIME_START, TV_EXPAND_SCALE);
802     auto midFrame = AceType::MakeRefPtr<Keyframe<double>>(keyTime, TV_REDUCE_SCALE);
803     auto endFrame = AceType::MakeRefPtr<Keyframe<double>>(KEY_TIME_END, TV_EXPAND_SCALE);
804     midFrame->SetCurve(Curves::FRICTION);
805     endFrame->SetCurve(Curves::FRICTION);
806     auto animation = AceType::MakeRefPtr<KeyframeAnimation<double>>();
807     animation->AddKeyframe(startFrame);
808     animation->AddKeyframe(midFrame);
809     animation->AddKeyframe(endFrame);
810     animation->AddListener([weak = AceType::WeakClaim(this)](double value) {
811         auto button = weak.Upgrade();
812         if (button) {
813             button->UpdateAnimationParam(value);
814         }
815     });
816 
817     if (!controller_) {
818         return;
819     }
820     controller_->ClearInterpolators();
821     controller_->AddInterpolator(animation);
822     controller_->SetDuration(duration);
823     controller_->Play();
824 }
825 
PlayClickAnimation()826 void RenderButton::PlayClickAnimation()
827 {
828     if (isPhone_ || isWatch_ || isTablet_) {
829         return;
830     }
831     if (!isFocus_) {
832         return;
833     }
834     animationRunning_ = true;
835     isClickAnimation_ = true;
836     startValue_ = TV_EXPAND_SCALE;
837     endValue_ = TV_REDUCE_SCALE;
838     PlayClickScaleAnimation(KEY_TIME_MID, TV_CLICK_DURATION);
839 }
840 
PlayFocusAnimation(bool isFocus)841 void RenderButton::PlayFocusAnimation(bool isFocus)
842 {
843     if (isWatch_) {
844         return;
845     }
846     if (isPhone_) {
847         UpdateFocusAnimation(INIT_SCALE);
848         return;
849     }
850     if (!isOpacityAnimation_ && isTv_) {
851         isOpacityAnimation_ = true;
852     }
853     if (isTv_) {
854         if (isFocus) {
855             PlayAnimation(INIT_SCALE, TV_EXPAND_SCALE, TV_FOCUS_SCALE_DURATION);
856         } else {
857             PlayAnimation(TV_EXPAND_SCALE, INIT_SCALE, TV_FOCUS_SCALE_DURATION);
858         }
859     }
860 }
861 
OnStatusStyleChanged(const VisualState state)862 void RenderButton::OnStatusStyleChanged(const VisualState state)
863 {
864     RenderNode::OnStatusStyleChanged(state);
865     if (buttonComponent_ == nullptr || !buttonComponent_->HasStateAttributes()) {
866         return;
867     }
868 
869     for (auto attribute : buttonComponent_->GetStateAttributes()->GetAttributesForState(state)) {
870         switch (attribute->id_) {
871             case ButtonStateAttribute::COLOR: {
872                 auto colorState =
873                     AceType::DynamicCast<StateAttributeValue<ButtonStateAttribute, AnimatableColor>>(attribute);
874                 if (state == VisualState::PRESSED) {
875                     SetClickedColor(backgroundColor_);  // starting animation color
876                     clickedColor_ = colorState->value_; // End color
877                     setClickColor_ = true;
878                 } else {
879                     backgroundColor_.SetValue(clickedColor_.GetValue()); // Start value
880                     backgroundColor_ = colorState->value_;               // End value and animate
881                 }
882             } break;
883 
884             case ButtonStateAttribute::RADIUS: {
885                 auto radiusState =
886                     AceType::DynamicCast<StateAttributeValue<ButtonStateAttribute, Dimension>>(attribute);
887                 buttonComponent_->SetRectRadius(radiusState->value_);
888             } break;
889 
890             case ButtonStateAttribute::HEIGHT: {
891                 auto valueState =
892                     AceType::DynamicCast<StateAttributeValue<ButtonStateAttribute, AnimatableDimension>>(attribute);
893                 buttonComponent_->SetHeight(valueState->value_);
894                 height_ = valueState->value_;
895             } break;
896 
897             case ButtonStateAttribute::WIDTH: {
898                 auto valueState =
899                     AceType::DynamicCast<StateAttributeValue<ButtonStateAttribute, AnimatableDimension>>(attribute);
900                 buttonComponent_->SetWidth(valueState->value_);
901                 width_ = valueState->value_;
902             } break;
903             default:
904                 break;
905         }
906     }
907     MarkNeedLayout();
908 }
909 
SendAccessibilityEvent()910 void RenderButton::SendAccessibilityEvent()
911 {
912     auto accessibilityNode = GetAccessibilityNode().Upgrade();
913     if (!accessibilityNode) {
914         return;
915     }
916 
917     auto context = context_.Upgrade();
918     if (context) {
919         AccessibilityEvent radioEvent;
920         radioEvent.nodeId = accessibilityNode->GetNodeId();
921         radioEvent.eventType = "click";
922         context->SendEventToAccessibility(radioEvent);
923     }
924 }
925 
926 } // namespace OHOS::Ace
927