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/option/render_option.h"
17 
18 #include "core/components/select_popup/render_select_popup.h"
19 
20 namespace OHOS::Ace {
21 namespace {
22 
23 const Dimension VERTICAL_INTERVAL_PHONE = 14.4_vp;
24 const Dimension HORIZONTAL_INTERVAL_PHONE = 12.0_vp;
25 const Dimension HORIZONTAL_DISTANCE_PHONE = 8.0_vp;
26 const Dimension ROUND_RADIUS_PHONE = 12.0_vp;
27 const Dimension ROUND_RADIUS_TV = 8.0_vp;
28 
29 } // namespace
30 
GetRenderText(const RefPtr<RenderNode> & render) const31 RefPtr<RenderText> RenderOption::GetRenderText(const RefPtr<RenderNode>& render) const
32 {
33     if (!render) {
34         return nullptr;
35     }
36     if (AceType::InstanceOf<RenderText>(render)) {
37         return AceType::DynamicCast<RenderText>(render);
38     }
39     for (const auto& child : render->GetChildren()) {
40         auto text = GetRenderText(child);
41         if (text) {
42             return text;
43         }
44     }
45     return nullptr;
46 }
47 
GetRenderImage(const RefPtr<RenderNode> & render) const48 RefPtr<RenderImage> RenderOption::GetRenderImage(const RefPtr<RenderNode>& render) const
49 {
50     if (!render) {
51         return nullptr;
52     }
53     if (AceType::InstanceOf<RenderImage>(render)) {
54         return AceType::DynamicCast<RenderImage>(render);
55     }
56     for (const auto& child : render->GetChildren()) {
57         auto image = GetRenderImage(child);
58         if (image) {
59             return image;
60         }
61     }
62     return nullptr;
63 }
64 
OnBack()65 bool RenderOption::OnBack()
66 {
67     if (!data_) {
68         return false;
69     }
70 
71     auto clickCallback = data_->GetClickedCallback();
72     if (!clickCallback) {
73         return false;
74     }
75 
76     clickCallback(SELECT_INVALID_INDEX);
77     return true;
78 }
79 
OnFocus(bool focus)80 void RenderOption::OnFocus(bool focus)
81 {
82     if (!data_) {
83         return;
84     }
85 
86     // lost focus => just update status.
87     if (!focus) {
88         data_->SetFocused(false);
89         data_->SetSelected(false);
90         UpdateStatus();
91         return;
92     }
93 
94     // is not auto focus on popup dialog
95     if (data_->GetIndex() != 0 || focusJumped_) {
96         data_->SetFocused(true);
97         UpdateStatus();
98         AdjustScrollPosition();
99         return;
100     }
101 
102     // auto focus on popup dialog
103     focusJumped_ = true;
104     auto options = GetAllOptions();
105     for (const auto& option : options) {
106         if (!option->GetSelected()) {
107             continue;
108         }
109         // auto focus on selected option.
110         if (AceType::RawPtr(option) == this) {
111             // auto jump to self which index is 0 and selected.
112             data_->SetFocused(true);
113             UpdateStatus();
114             return;
115         }
116         option->RequestFocus();
117         return;
118     }
119 
120     data_->SetFocused(true);
121     UpdateStatus();
122 }
123 
RequestFocus()124 void RenderOption::RequestFocus()
125 {
126     auto node = weakNode_.Upgrade();
127     if (!node) {
128         return;
129     }
130     node->RequestFocus();
131 }
132 
GetAllOptions(std::list<RefPtr<RenderOption>> & result,const RefPtr<RenderNode> & parent) const133 void RenderOption::GetAllOptions(std::list<RefPtr<RenderOption>>& result, const RefPtr<RenderNode>& parent) const
134 {
135     if (!parent) {
136         return;
137     }
138 
139     auto option = AceType::DynamicCast<RenderOption>(parent);
140     if (option) {
141         result.emplace_back(option);
142     }
143 
144     for (const auto& child : parent->GetChildren()) {
145         GetAllOptions(result, child);
146     }
147 }
148 
GetAllOptions() const149 std::list<RefPtr<RenderOption>> RenderOption::GetAllOptions() const
150 {
151     std::list<RefPtr<RenderOption>> result;
152     RefPtr<RenderNode> parent = GetParent().Upgrade();
153     while (parent && !AceType::InstanceOf<RenderBox>(parent)) {
154         parent = parent->GetParent().Upgrade();
155     }
156     GetAllOptions(result, parent);
157     return result;
158 }
159 
OnClick(bool focus)160 void RenderOption::OnClick(bool focus)
161 {
162     if (!data_ || data_->GetDisabled()) {
163         return;
164     }
165 
166     auto options = GetAllOptions();
167     for (const auto& option : options) {
168         option->OnSelect(data_->GetIndex());
169     }
170 
171     auto clickCallback = data_->GetClickedCallback();
172     if (!clickCallback) {
173         return;
174     }
175 
176     clickCallback(data_->GetIndex());
177 
178     if (onClickEvent_) {
179         onClickEvent_();
180     }
181 }
182 
OnSelect(uint32_t selectIndex)183 void RenderOption::OnSelect(uint32_t selectIndex)
184 {
185     if (!data_) {
186         return;
187     }
188 
189     if (data_->GetIndex() == selectIndex) {
190         data_->SetClicked(false);
191         data_->SetSelected(true);
192     } else {
193         data_->SetSelected(false);
194     }
195     UpdateStatus();
196 }
197 
OnTouch(bool down)198 void RenderOption::OnTouch(bool down)
199 {
200     if (!data_ || data_->GetDisabled() || data_->GetCustomComponent()) {
201         return;
202     }
203 
204     data_->SetClicked(down);
205     UpdateStatus();
206     Color endColor;
207     if (down) {
208         endColor = clickedColor_;
209     } else if (hovered_) {
210         endColor = hoveredColor_;
211     } else {
212         endColor = Color::TRANSPARENT;
213     }
214     PlayEventEffectAnimation(endColor, PRESS_DURATION);
215 }
216 
HandleMouseHoverEvent(const MouseState mouseState)217 void RenderOption::HandleMouseHoverEvent(const MouseState mouseState)
218 {
219     if (!data_ || data_->IsDisabledStatus() || data_->GetCustomComponent()) {
220         return;
221     }
222     Color color;
223     if (mouseState == MouseState::HOVER) {
224         color = hoveredColor_;
225     } else {
226         color = Color::TRANSPARENT;
227     }
228     PlayEventEffectAnimation(color, PRESS_DURATION);
229 }
230 
HandleMouseEvent(const MouseEvent & event)231 bool RenderOption::HandleMouseEvent(const MouseEvent& event)
232 {
233     if (!data_ || data_->IsDisabledStatus() || data_->GetCustomComponent()) {
234         return false;
235     }
236     if (event.button == MouseButton::LEFT_BUTTON) {
237         if (event.action == MouseAction::PRESS || event.action == MouseAction::MOVE) {
238             PlayEventEffectAnimation(clickedColor_, PRESS_DURATION);
239         } else if (event.action == MouseAction::RELEASE) {
240             PlayEventEffectAnimation(data_->GetSelectedBackgroundColor(), PRESS_DURATION);
241         }
242         return true;
243     }
244     return false;
245 }
246 
OnMouseHoverEnterTest()247 void RenderOption::OnMouseHoverEnterTest()
248 {
249     if (!data_ || data_->GetDisabled()) {
250         return;
251     }
252 
253     hovered_ = true;
254     UpdateStatus();
255     PlayEventEffectAnimation(hoveredColor_, HOVER_DURATION);
256 }
257 
OnMouseHoverExitTest()258 void RenderOption::OnMouseHoverExitTest()
259 {
260     if (!data_ || data_->GetDisabled()) {
261         return;
262     }
263 
264     hovered_ = false;
265     UpdateStatus();
266     PlayEventEffectAnimation(Color::TRANSPARENT, HOVER_DURATION, true);
267 }
268 
UpdateStatus()269 void RenderOption::UpdateStatus()
270 {
271     UpdateSelfStatus();
272     UpdateDownStatus();
273 }
274 
UpdateSelfStatus()275 void RenderOption::UpdateSelfStatus()
276 {
277     if (!data_ || data_->GetDisabled()) {
278         return;
279     }
280     // tv focus > press(clicked) > hover > select > normal
281     if (isTv_ && data_->GetFocused()) {
282         UpdateTvFocusedStatus();
283         return;
284     }
285     if (data_->GetClicked()) {
286         UpdateClickedStatus();
287         return;
288     }
289     if (hovered_) {
290         UpdateHoveredStatus();
291         return;
292     }
293     if (data_->GetSelected()) {
294         UpdateSelectedStatus();
295         return;
296     }
297 
298     UpdateOthersStatus();
299     return;
300 }
301 
UpdateDownStatus()302 void RenderOption::UpdateDownStatus()
303 {
304     auto downOption = GetDownOption();
305     if (!downOption) {
306         return;
307     }
308     downOption->UpdateSelfStatus();
309 }
310 
UpdateClickedStatus()311 void RenderOption::UpdateClickedStatus()
312 {
313     needLine_ = false;
314     if (isTv_) {
315         UpdateFocusedText();
316     } else {
317         UpdateNormalText();
318     }
319     MarkNeedRender();
320 }
321 
UpdateHoveredStatus()322 void RenderOption::UpdateHoveredStatus()
323 {
324     needLine_ = false;
325     UpdateNormalText();
326     MarkNeedRender();
327 }
328 
UpdateSelectedStatus()329 void RenderOption::UpdateSelectedStatus()
330 {
331     if (!data_ || !data_->GetTheme()) {
332         return;
333     }
334     backColor_ = data_->GetSelectedBackgroundColor();
335     needLine_ = false;
336     UpdateSelectedText();
337     MarkNeedRender();
338 }
339 
UpdateTvFocusedStatus()340 void RenderOption::UpdateTvFocusedStatus()
341 {
342     if (!data_ || !data_->GetTheme()) {
343         return;
344     }
345     auto theme = data_->GetTheme();
346     backColor_ = theme->GetClickedColor();
347     UpdateFocusedText();
348     MarkNeedRender();
349 }
350 
UpdateOthersStatus()351 void RenderOption::UpdateOthersStatus()
352 {
353     auto pipe = context_.Upgrade();
354     if (!data_ || !pipe) {
355         return;
356     }
357     backColor_ = isTv_ ? Color(0x33FFFFFF) : data_->GetBackgroundColor();
358     auto upOption = GetUpOption();
359     needLine_ = (!(data_->GetFocused() && pipe->IsKeyEvent()) && upOption && upOption->IsNormalStatus()) &&
360                 needDrawDividerLine_;
361     UpdateNormalText();
362     MarkNeedRender();
363 }
364 
GetUpOption() const365 RefPtr<RenderOption> RenderOption::GetUpOption() const
366 {
367     auto options = GetAllOptions();
368     RefPtr<RenderOption> topOption;
369     for (auto it = options.begin(); it != options.end(); ++it) {
370         if (AceType::RawPtr(*it) == this) {
371             return topOption;
372         }
373         topOption = *it;
374     }
375     return nullptr;
376 }
377 
GetDownOption() const378 RefPtr<RenderOption> RenderOption::GetDownOption() const
379 {
380     auto options = GetAllOptions();
381     for (auto it = options.begin(); it != options.end(); ++it) {
382         if (AceType::RawPtr(*it) != this) {
383             continue;
384         }
385         ++it;
386         if (it == options.end()) {
387             return nullptr;
388         }
389         return *it;
390     }
391     return nullptr;
392 }
393 
IsNormalStatus() const394 bool RenderOption::IsNormalStatus() const
395 {
396     auto pipe = context_.Upgrade();
397     if (!data_ || !pipe) {
398         return false;
399     }
400 
401     return (!data_->GetClicked() && !hovered_ && !data_->GetSelected() && !(data_->GetFocused() && pipe->IsKeyEvent()));
402 }
403 
UpdateNormalText()404 void RenderOption::UpdateNormalText()
405 {
406     UpdateTextColor(false, false);
407 }
408 
UpdateSelectedText()409 void RenderOption::UpdateSelectedText()
410 {
411     UpdateTextColor(true, false);
412 }
413 
UpdateFocusedText()414 void RenderOption::UpdateFocusedText()
415 {
416     UpdateTextColor(false, true);
417 }
418 
UpdateTextColor(bool selected,bool focused)419 void RenderOption::UpdateTextColor(bool selected, bool focused)
420 {
421     if (!data_) {
422         return;
423     }
424     auto theme = data_->GetTheme();
425     if (!theme) {
426         return;
427     }
428     auto component = data_->GetText();
429     if (!component) {
430         return;
431     }
432     auto render = GetRenderText(AceType::Claim(this));
433     if (!render) {
434         return;
435     }
436     auto style = component->GetTextStyle();
437 
438     auto context = context_.Upgrade();
439     if (context->GetIsDeclarative()) {
440         if (focused) {
441             style.SetTextColor(Color(0xE6000000));
442             component->SetFocusColor(style.GetTextColor());
443         }
444     } else {
445         if (focused) {
446             style.SetTextColor(Color(0xE6000000));
447             component->SetFocusColor(style.GetTextColor());
448         } else if (selected) {
449             style.SetTextColor(theme->GetSelectedColorText());
450             component->SetFocusColor(style.GetTextColor());
451         } else {
452             style.SetTextColor(theme->GetFontColor());
453             component->SetFocusColor(style.GetTextColor());
454         }
455     }
456     component->SetTextStyle(style);
457     render->Update(component);
458 }
459 
AdjustScrollPosition()460 void RenderOption::AdjustScrollPosition()
461 {
462     RefPtr<RenderNode> render = GetParent().Upgrade();
463     while (render && !AceType::InstanceOf<RenderScroll>(render)) {
464         render = render->GetParent().Upgrade();
465     }
466     if (!render) {
467         return;
468     }
469     auto scroll = AceType::DynamicCast<RenderScroll>(render);
470     while (render && !AceType::InstanceOf<RenderSelectPopup>(render)) {
471         render = render->GetParent().Upgrade();
472     }
473     if (!render) {
474         return;
475     }
476     auto popup = AceType::DynamicCast<RenderSelectPopup>(render);
477     auto scrollTop = scroll->GetCurrentPosition();
478     auto scrollBottom = scrollTop + scroll->GetLayoutSize().Height();
479     auto scrollHeight = scroll->GetLayoutSize().Height();
480     auto optionTop = GetPosition().GetY();
481     auto optionBottom = optionTop + GetLayoutSize().Height();
482     auto optionHeight = GetLayoutSize().Height();
483     if (scrollHeight < optionHeight) {
484         auto center = (optionTop + optionBottom) / 2.0;
485         scroll->JumpToPosition(center - scrollHeight / 2.0);
486         popup->MarkNeedRender();
487         return;
488     }
489     double pos = 0.0;
490     if (optionTop < scrollTop + optionHeight) {
491         pos = optionTop >= optionHeight ? optionTop - optionHeight : 0;
492     } else if (scrollBottom - optionHeight < optionBottom) {
493         pos = optionBottom + optionHeight - scrollHeight;
494     } else {
495         return;
496     }
497     if (pos <= optionTop && optionBottom <= pos + scrollHeight) {
498         scroll->JumpToPosition(pos);
499         popup->MarkNeedRender();
500         return;
501     }
502     if (pos > optionTop) {
503         scroll->JumpToPosition(optionTop);
504         popup->MarkNeedRender();
505         return;
506     }
507     scroll->JumpToPosition(optionBottom - scrollHeight);
508     popup->MarkNeedRender();
509 }
510 
~RenderOption()511 RenderOption::~RenderOption()
512 {
513     UpdateAccessibilityInfo(Size(0.0, 0.0), Offset(0.0, 0.0), false);
514 }
515 
InitClickEvent()516 void RenderOption::InitClickEvent()
517 {
518     if (click_) {
519         return;
520     }
521     click_ = AceType::MakeRefPtr<ClickRecognizer>();
522     auto weak = AceType::WeakClaim(this);
523     click_->SetOnClick([weak](const ClickInfo&) {
524         auto ref = weak.Upgrade();
525         if (!ref) {
526             return;
527         }
528         ref->OnClick(false);
529     });
530 }
531 
InitTouchEvent()532 void RenderOption::InitTouchEvent()
533 {
534     if (touch_) {
535         return;
536     }
537     touch_ = AceType::MakeRefPtr<RawRecognizer>();
538     auto weak = AceType::WeakClaim(this);
539     touch_->SetOnTouchDown([weak](const TouchEventInfo& info) {
540         auto ref = weak.Upgrade();
541         if (!ref) {
542             return;
543         }
544         ref->OnTouch(true);
545         ref->ProcessTouchDown(info);
546     });
547     touch_->SetOnTouchUp([weak](const TouchEventInfo& info) {
548         auto ref = weak.Upgrade();
549         if (!ref) {
550             return;
551         }
552         ref->OnTouch(false);
553         ref->ProcessTouchUp(info);
554     });
555     touch_->SetOnTouchCancel([weak](const TouchEventInfo&) {
556         auto ref = weak.Upgrade();
557         if (!ref) {
558             return;
559         }
560         ref->OnTouch(false);
561     });
562 }
563 
OnTouchTestHit(const Offset & coordinateOffset,const TouchRestrict & touchRestrict,TouchTestResult & result)564 void RenderOption::OnTouchTestHit(
565     const Offset& coordinateOffset, const TouchRestrict& touchRestrict, TouchTestResult& result)
566 {
567     InitClickEvent();
568     InitTouchEvent();
569     click_->SetCoordinateOffset(coordinateOffset);
570     touch_->SetCoordinateOffset(coordinateOffset);
571     result.emplace_back(click_);
572     result.emplace_back(touch_);
573 }
574 
ProcessTouchDown(const TouchEventInfo & info)575 void RenderOption::ProcessTouchDown(const TouchEventInfo& info)
576 {
577     LOGI("RenderOption ProcessTouchDown");
578     auto touches = info.GetTouches();
579     if (touches.empty()) {
580         LOGW("touch event info is empty.");
581         return;
582     }
583 
584     if (data_->GetCustomComponent()) {
585         return;
586     }
587 
588     auto touchPosition = touches.front().GetLocalLocation();
589     if (!optionRegion_.ContainsInRegion(touchPosition.GetX(), touchPosition.GetY())) {
590         LOGI("Do not contains the touch region.");
591         return;
592     }
593     firstTouchDownOffset_ = touchPosition;
594 }
595 
ProcessTouchUp(const TouchEventInfo & info)596 void RenderOption::ProcessTouchUp(const TouchEventInfo& info)
597 {
598     LOGI("RenderOption ProcessTouchUp");
599     auto touches = info.GetTouches();
600     if (touches.empty()) {
601         LOGW("touch event info is empty.");
602         return;
603     }
604 
605     if (data_->GetCustomComponent()) {
606         return;
607     }
608 
609     auto touchPosition = touches.front().GetLocalLocation();
610     firstTouchUpOffset_ = touchPosition;
611     if (optionRegion_.ContainsInRegion(touchPosition.GetX(), touchPosition.GetY()) &&
612         (firstTouchDownOffset_ != firstTouchUpOffset_)) {
613         OnClick(false);
614     }
615     firstTouchDownOffset_ = Offset();
616 }
617 
Update(const RefPtr<Component> & component)618 void RenderOption::Update(const RefPtr<Component>& component)
619 {
620     data_ = AceType::DynamicCast<OptionComponent>(component);
621     if (!data_ || !data_->GetTheme()) {
622         return;
623     }
624     if (!eventEffectController_) {
625         eventEffectController_ = CREATE_ANIMATOR(context_);
626     }
627     auto theme = data_->GetTheme();
628     lineColor_ = theme->GetLineColor();
629     clickedColor_ = theme->GetClickedColor();
630     hoveredColor_ = theme->GetHoverColor();
631     onClickEvent_ = AceAsyncEvent<void()>::Create(data_->GetClickEvent(), context_);
632     needDrawDividerLine_ = data_->GetNeedDrawDividerLine();
633     UpdateStatus();
634     MarkNeedLayout();
635 }
636 
OnPaintFinish()637 void RenderOption::OnPaintFinish()
638 {
639     Size size = GetLayoutSize();
640     Offset offset = GetGlobalOffsetExternal();
641     bool isSelected = false;
642     if (data_) {
643         isSelected = data_->GetSelected();
644     }
645     UpdateAccessibilityInfo(size, offset, isSelected);
646 
647     // update focus
648     auto pipeline = context_.Upgrade();
649     if (!pipeline || !data_ || !data_->GetTheme()) {
650         LOGE("pipeline or box or data component or theme is null.");
651         return;
652     }
653     if (!data_->GetFocused()) {
654         return;
655     }
656     auto theme = data_->GetTheme();
657     Radius radius(NormalizeToPx((isTv_ ? ROUND_RADIUS_TV : ROUND_RADIUS_PHONE)));
658     auto diff = NormalizeToPx(theme->GetOptionInterval());
659     offset = GetGlobalOffset() + Size(diff, diff);
660     size = GetLayoutSize() - Size(diff, diff) * 2; // left top diff and right bottom diff.
661     pipeline->ShowFocusAnimation(
662         RRect::MakeRRect(Rect(Offset(0, 0), size), radius), theme->GetClickedColor(), offset, true);
663 }
664 
UpdateAccessibilityInfo(Size size,Offset offset,bool isSelected)665 void RenderOption::UpdateAccessibilityInfo(Size size, Offset offset, bool isSelected)
666 {
667     auto context = context_.Upgrade();
668     if (!context) {
669         return;
670     }
671     if (!data_) {
672         return;
673     }
674     auto viewScale = context->GetViewScale();
675     if (NearZero(viewScale)) {
676         LOGW("GetGlobalPositionById viewScale is zero.");
677         return;
678     }
679     auto accessibilityManager = context->GetAccessibilityManager();
680     if (!accessibilityManager) {
681         LOGW("RenderOption accessibilityManager is null.");
682         return;
683     }
684     auto nodeId = StringUtils::StringToInt(data_->GetId());
685     auto accessibilityNode = accessibilityManager->GetAccessibilityNodeById(nodeId);
686     if (!accessibilityNode) {
687         LOGW("RenderOption accessibilityNode is null.");
688         return;
689     }
690 
691     PositionInfo positionInfo = { (size.Width()) * viewScale, (size.Height()) * viewScale, (offset.GetX()) * viewScale,
692         (offset.GetY()) * viewScale };
693     accessibilityNode->SetPositionInfo(positionInfo);
694     if (accessibilityNode->GetParentNode()) {
695         bool visible = accessibilityNode->GetRect().IsIntersectWith(accessibilityNode->GetParentNode()->GetRect());
696         accessibilityNode->SetVisible(visible);
697     }
698     if (data_ && data_->GetText()) {
699         auto text = data_->GetText();
700         accessibilityNode->SetText(text->GetData());
701     }
702     accessibilityNode->SetSelectedState(isSelected);
703 }
704 
LayoutText(const RefPtr<RenderText> & text)705 void RenderOption::LayoutText(const RefPtr<RenderText>& text)
706 {
707     double verInterval = NormalizeToPx(VERTICAL_INTERVAL_PHONE);
708     double horInterval = NormalizeToPx(HORIZONTAL_INTERVAL_PHONE);
709 
710     double minWidth = minWidth_ - horInterval * 2.0; // left + right interval
711     if (LessOrEqual(minWidth, 0.0)) {
712         minWidth = 0.0;
713     }
714     double maxWidth = maxWidth_ - horInterval * 2.0; // left + right interval
715     if (LessOrEqual(maxWidth, 0.0)) {
716         maxWidth = 0.0;
717     }
718 
719     LayoutParam layout;
720     layout.SetMinWidth(minWidth);
721     layout.SetMaxWidth(maxWidth);
722     text->Layout(layout);
723     auto size = text->GetLayoutSize();
724 
725     size.AddWidth(horInterval * 2.0);
726     size.AddHeight(verInterval * 2.0);
727     text->SetPosition(Offset(horInterval, verInterval));
728     SetLayoutSize(size);
729 }
730 
LayoutTextImage(const RefPtr<RenderText> & text,const RefPtr<RenderImage> & image)731 void RenderOption::LayoutTextImage(const RefPtr<RenderText>& text, const RefPtr<RenderImage>& image)
732 {
733     double verInterval = NormalizeToPx(VERTICAL_INTERVAL_PHONE);
734     double horInterval = NormalizeToPx(HORIZONTAL_INTERVAL_PHONE);
735     double horDistance = NormalizeToPx(HORIZONTAL_DISTANCE_PHONE);
736 
737     image->Layout(LayoutParam());
738     auto imageSize = image->GetLayoutSize();
739 
740     // left interval + right interval + distance between elements
741     double minWidth = minWidth_ - horInterval * 2.0 - horDistance - imageSize.Width();
742     if (LessOrEqual(minWidth, 0.0)) {
743         minWidth = 0.0;
744     }
745     double maxWidth = maxWidth_ - horInterval * 2.0 - horDistance - imageSize.Width();
746     if (LessOrEqual(maxWidth, 0.0)) {
747         maxWidth = 0.0;
748     }
749 
750     LayoutParam layout;
751     layout.SetMinWidth(minWidth);
752     layout.SetMaxWidth(maxWidth);
753     text->Layout(layout);
754     auto textSize = text->GetLayoutSize();
755 
756     auto size = textSize;
757     size.AddWidth(horInterval * 2.0 + horDistance + imageSize.Width());
758     size.AddHeight(verInterval * 2.0);
759     SetLayoutSize(size);
760 
761     double yImage = (size.Height() - imageSize.Height()) / 2.0; // place center
762     if (IsRTL()) {
763         text->SetPosition(Offset(horInterval, verInterval));
764         image->SetPosition(Offset(size.Width() - horInterval - imageSize.Width(), yImage));
765     } else {
766         image->SetPosition(Offset(horInterval, yImage));
767         text->SetPosition(Offset(size.Width() - horInterval - textSize.Width(), verInterval));
768     }
769 }
770 
IsRTL() const771 bool RenderOption::IsRTL() const
772 {
773     if (!data_) {
774         return false;
775     }
776 
777     if (data_->GetTextDirection() == TextDirection::RTL) {
778         return true;
779     }
780 
781     return false;
782 }
783 
PerformLayout()784 void RenderOption::PerformLayout()
785 {
786     if (data_->GetCustomComponent()) {
787         auto child = GetLastChild();
788         if (!child) {
789             LOGE("child is null.");
790             return;
791         }
792 
793         auto layoutParam = LayoutParam(GetLayoutParam().GetMaxSize(), Size());
794         child->Layout(layoutParam);
795         SetLayoutSize(child->GetLayoutSize());
796         return;
797     }
798 
799     auto text = GetRenderText(AceType::Claim(this));
800     if (!text) {
801         LOGE("render text is null.");
802         return;
803     }
804 
805     auto image = GetRenderImage(AceType::Claim(this));
806     if (image) {
807         LayoutTextImage(text, image);
808     } else {
809         LayoutText(text);
810     }
811 
812     optionRegion_ = TouchRegion(Offset(), Offset(GetLayoutSize().Width(), GetLayoutSize().Height()));
813 }
814 
PlayEventEffectAnimation(const Color & endColor,int32_t duration,bool isHoverExists)815 void RenderOption::PlayEventEffectAnimation(const Color& endColor, int32_t duration, bool isHoverExists)
816 {
817     if (!eventEffectController_->IsStopped()) {
818         eventEffectController_->Stop();
819     }
820     RefPtr<KeyframeAnimation<Color>> colorAnimation = AceType::MakeRefPtr<KeyframeAnimation<Color>>();
821     CreateMouseAnimation(colorAnimation, GetEventEffectColor(), endColor);
822     if (duration == HOVER_DURATION) {
823         colorAnimation->SetCurve(Curves::FRICTION);
824     }
825     if (isHoverExists && GetEventEffectColor().GetValue() < hoveredColor_.GetValue()) {
826         colorAnimation->SetCurve(Curves::FAST_OUT_SLOW_IN);
827     }
828     eventEffectController_->ClearInterpolators();
829     eventEffectController_->ClearStopListeners();
830     eventEffectController_->AddInterpolator(colorAnimation);
831     eventEffectController_->SetDuration(duration);
832     eventEffectController_->SetFillMode(FillMode::FORWARDS);
833     eventEffectController_->AddStopListener([weakNode = AceType::WeakClaim(this)]() {
834         auto renderOption = weakNode.Upgrade();
835         if (renderOption) {
836             renderOption->UpdateStatus();
837         }
838     });
839     eventEffectController_->Forward();
840 }
841 
842 } // namespace OHOS::Ace
843