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