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