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