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