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/tab_bar/render_tab_bar.h"
17 
18 #include "base/log/event_report.h"
19 #include "core/common/container.h"
20 
21 namespace OHOS::Ace {
22 namespace {
23 
24 constexpr int32_t GRADIENT_POINT_SIZE = 4;
25 constexpr Dimension FOCUS_ANIMATION_WIDTH = 2.0_vp;
26 constexpr Dimension OFFSET_FOR_FOCUS = 4.0_vp;
27 constexpr double DOUBLE_FACTOR = 2.0;
28 
29 } // namespace
30 
RenderTabBar()31 RenderTabBar::RenderTabBar() : RenderNode(true) {}
32 
Update(const RefPtr<Component> & component)33 void RenderTabBar::Update(const RefPtr<Component>& component)
34 {
35     RefPtr<TabBarComponent> tabBar = AceType::DynamicCast<TabBarComponent>(component);
36     if (!tabBar) {
37         LOGE("TabBarComponent is nullptr");
38         EventReport::SendRenderException(RenderExcepType::RENDER_COMPONENT_ERR);
39         return;
40     }
41     tabsSize_ = static_cast<int32_t>(tabBar->GetChildren().size());
42     auto tabController = tabBar->GetController();
43     FlushIndex(tabController);
44     if (initialUpdate_) {
45         auto barIndicator = tabBar->GetIndicator();
46         if (barIndicator) {
47             indicator_ = barIndicator->CreateRenderNode();
48             if (indicator_) {
49                 AddChild(indicator_);
50                 indicator_->Attach(GetContext());
51                 indicator_->Update(barIndicator);
52                 indicatorPadding_ = barIndicator->GetPadding();
53                 indicatorStyle_ = GetIndicatorStyle(barIndicator);
54             }
55         }
56         initialUpdate_ = false;
57     }
58     InitScrollableOffset(tabBar->GetMode());
59     mode_ = tabBar->GetMode();
60     isVertical_ = tabBar->IsVertical();
61     indicatorSize_ = tabBar->GetIndicatorSize();
62     padding_ = tabBar->GetPadding();
63     activeIndicatorMinWidth_ = tabBar->GetActiveIndicatorMinWidth();
64     focusAnimationColor_ = tabBar->GetFocusAnimationColor();
65     focusRadiusDimension_ = tabBar->GetFocusRadiusDimension();
66     gradientWidth_ = tabBar->GetGradientWidth();
67     barPosition_ = tabBar->GetBarPosition();
68     SetTextDirection(tabBar->GetTextDirection());
69     Initialize();
70     MarkNeedLayout();
71 }
72 
FlushIndex(const RefPtr<TabController> & controller)73 void RenderTabBar::FlushIndex(const RefPtr<TabController>& controller)
74 {
75     if (!controller) {
76         return;
77     }
78     int32_t index = 0;
79     if (controller->IsIndexDefined()) {
80         index = controller->GetIndex();
81     } else {
82         auto initialIndex = controller->GetInitialIndex();
83         auto pendingIndex = controller->GetPendingIndex();
84         if (initialUpdate_ && pendingIndex < 0) {
85             index = initialIndex < 0 ? 0 : initialIndex;
86         } else {
87             index = pendingIndex < 0 ? 0 : pendingIndex;
88         }
89     }
90     if (!Container::IsCurrentUsePartialUpdate()) {
91         index_ = index < tabsSize_ ? index : tabsSize_ - 1;
92     } else {
93         // In partial update we have zero tabs yet here, so just keep the index
94         index_ = (index < tabsSize_ || tabsSize_ == 0) ? index : tabsSize_ - 1;
95     }
96 
97     ApplyRestoreInfo(controller);
98 
99     controller->SetIndexWithoutChangeContent(index_);
100 }
101 
PerformLayout()102 void RenderTabBar::PerformLayout()
103 {
104     tabsSize_ = 0;
105     const std::list<RefPtr<RenderNode>>& children = GetChildren();
106     if (indicator_) {
107         // At least one child(include indicator)
108         if (children.size() <= 1) {
109             return;
110         }
111         tabsSize_ = static_cast<int32_t>(children.size()) - 1;
112     } else {
113         if (children.empty()) {
114             return;
115         }
116         tabsSize_ = static_cast<int32_t>(children.size());
117     }
118 
119     index_ = std::clamp(index_, 0, std::max(0, tabsSize_ - 1));
120     if (!IsRightToLeft()) {
121         if (tabBarWidth_ > 0 && !NearEqual(tabBarWidth_, GetLayoutParam().GetMaxSize().Width())) {
122             if (!isVertical_ && actualWidth_ > GetLayoutSize().Width() &&
123                 GreatNotEqual(
124                     GetLayoutParam().GetMaxSize().Width(), actualWidth_ - std::abs(scrollableOffset_.GetX()))) {
125                 scrollableOffset_.SetX(scrollableOffset_.GetX() + GetLayoutParam().GetMaxSize().Width() - tabBarWidth_);
126             }
127         }
128     }
129     tabBarWidth_ = GetLayoutParam().GetMaxSize().Width();
130     // Layout children and indicator
131     LayoutChildren();
132     UpdatePosition();
133     Size layoutSize = GetLayoutParam().Constrain(GetLayoutParam().GetMaxSize());
134     SetLayoutSize(layoutSize);
135     ApplyGradientColor();
136     if (isFirstLayout_) {
137         if (index_ != 0) {
138             SetIndex(index_, true);
139         }
140         isFirstLayout_ = false;
141     }
142 }
143 
PerformLayoutChildren(const LayoutParam & innerLayoutParam)144 void RenderTabBar::PerformLayoutChildren(const LayoutParam& innerLayoutParam)
145 {
146     actualWidth_ = NormalizeToPx(padding_.Left());
147     actualHeight_ = NormalizeToPx(padding_.Top());
148 
149     tabsWidth_.clear();
150     tabsHeight_.clear();
151     tabItemOffsets_.clear();
152     if (!isVertical_ && scrollableOffset_.GetX() > tabBarWidth_) {
153         scrollableOffset_.Reset();
154     }
155     auto context = context_.Upgrade();
156     if (!context) {
157         LOGE("context is null");
158         return;
159     }
160 
161     if (isVertical_) {
162         tabItemOffsets_.emplace_back(0.0, 0.0);
163     } else {
164         if (!IsRightToLeft()) {
165             tabItemOffsets_.emplace_back(padding_.GetOffsetInPx(context->GetDipScale()));
166         }
167     }
168 
169     auto children = GetChildren();
170     auto item = children.begin();
171     // skip indicator
172     if (indicator_) {
173         ++item;
174     }
175     // First time layout all children
176     for (; item != children.end(); ++item) {
177         (*item)->Layout(innerLayoutParam);
178         if (isVertical_) {
179             tabsHeight_.push_back((*item)->GetLayoutSize().Height());
180             actualHeight_ += tabsHeight_.back();
181             tabItemOffsets_.emplace_back(0.0, actualHeight_);
182         } else {
183             tabsWidth_.push_back((*item)->GetLayoutSize().Width());
184             actualWidth_ += tabsWidth_.back();
185             if (IsRightToLeft()) {
186                 tabItemOffsets_.emplace_back(tabBarWidth_ - actualWidth_, 0.0);
187             } else {
188                 tabItemOffsets_.emplace_back(actualWidth_, 0.0);
189             }
190         }
191     }
192 
193     actualWidth_ += NormalizeToPx(padding_.Right());
194     actualHeight_ += NormalizeToPx(padding_.Bottom());
195 }
196 
LayoutChildren()197 void RenderTabBar::LayoutChildren()
198 {
199     // First time layout all children and update relative position.
200     LayoutParam innerLayoutParam = MakeInnerLayoutParam();
201 
202     // First time layout all children
203     PerformLayoutChildren(innerLayoutParam);
204 
205     if (mode_ == TabBarMode::FIXED_START && !isVertical_ && needUpdateOffset_) {
206         double padding;
207         if (index_ == 0) {
208             padding = NormalizeToPx(Dimension(16, DimensionUnit::VP));
209         } else {
210             padding = NormalizeToPx(Dimension(24, DimensionUnit::VP));
211         }
212         if (!IsRightToLeft()) {
213             scrollableOffset_.SetX(std::clamp(padding - tabItemOffsets_[index_].GetX(), -MaxScrollableWidth(), 0.0));
214         } else {
215             scrollableOffset_.SetX(std::clamp(padding - tabItemOffsets_[index_].GetX(), 0.0, MaxScrollableWidth()));
216         }
217     } else if (mode_ == TabBarMode::SCROLLABLE && actualWidth_ < GetLayoutParam().GetMaxSize().Width() &&
218                !isVertical_) {
219         // In scrollable mod: the sum of Tab's width can less then TabBar width
220         double halfWidth = GetLayoutParam().GetMaxSize().Width() / DOUBLE_FACTOR;
221         if (actualWidth_ < halfWidth) {
222             // when items total width less than half of tab bar, make items average harf width
223             double averageWidth = halfWidth / tabsSize_;
224             innerLayoutParam.SetMinSize(Size(averageWidth, innerLayoutParam.GetMinSize().Height()));
225             // relayout all children
226             PerformLayoutChildren(innerLayoutParam);
227         }
228         if (IsRightToLeft()) {
229             scrollableOffset_ = Offset((actualWidth_ - GetLayoutParam().GetMaxSize().Width()) / DOUBLE_FACTOR, 0.0);
230         } else {
231             scrollableOffset_ = Offset((GetLayoutParam().GetMaxSize().Width() - actualWidth_) / DOUBLE_FACTOR, 0.0);
232         }
233     } else if (mode_ == TabBarMode::SCROLLABLE && actualHeight_ < GetLayoutParam().GetMaxSize().Height() &&
234                isVertical_) {
235         // In scrollable mod: the sum of Tab's width can less then TabBar width
236         scrollableOffset_ = Offset(0.0, (GetLayoutParam().GetMaxSize().Height() - actualHeight_) / DOUBLE_FACTOR);
237     }
238 }
239 
UpdatePosition()240 void RenderTabBar::UpdatePosition()
241 {
242     const std::list<RefPtr<RenderNode>>& children = GetChildren();
243 
244     // At least one child
245     if (children.size() <= 1) {
246         return;
247     }
248     auto item = children.begin();
249     // skip indicator
250     if (indicator_) {
251         ++item;
252     }
253     // Second update relative position.
254     for (int32_t i = 0; item != children.end(); ++item) {
255         (*item)->SetPosition(scrollableOffset_ + tabItemOffsets_[i]);
256         if (i == index_ && indicator_) {
257             // update indicator layout and position
258             indicator_->Layout(MakeIndicatorLayoutParam(*item));
259             Offset offset = MakeIndicatorOffset(*item);
260             Offset indiOffset = Offset(indicatorPlusX_, 0.0);
261             Offset posOffset = scrollableOffset_ + tabItemOffsets_[index_] + offset - indiOffset;
262             indicator_->SetPosition(posOffset);
263         }
264         i++;
265     }
266 }
267 
SetScrollIndicator(double percent,int32_t newIndex,bool needChange)268 void RenderTabBar::SetScrollIndicator(double percent, int32_t newIndex, bool needChange)
269 {
270     indicatorPlusX_ = 0.0;
271     indicatorPlusWidth_ = 0.0;
272     if (newIndex < 0 || newIndex >= tabsSize_) {
273         LOGW("boundary index = %{public}d", newIndex);
274         return;
275     }
276 
277     if (isVertical_) {
278         return;
279     }
280     if (tabsWidth_.empty()) {
281         return;
282     }
283     auto maxIndex = static_cast<int32_t>(tabsWidth_.size()) - 1;
284     if (newIndex > maxIndex || newIndex < 0 || index_ > maxIndex || index_ < 0) {
285         return;
286     }
287     double newItemsWidth = tabsWidth_[newIndex];
288     double curItemsWidth = tabsWidth_[index_];
289     indicatorPlusX_ = curItemsWidth * percent;
290     if (needChange) {
291         if (percent<0) {
292             indicatorPlusX_ = curItemsWidth * (1.0 - std::abs(percent));
293         } else {
294             indicatorPlusX_ = curItemsWidth * (1.0 - std::abs(percent)) * -1.0;
295         }
296     }
297     indicatorPlusWidth_ = (curItemsWidth - newItemsWidth) * std::abs(percent);
298     MarkNeedLayout();
299 }
300 
SetIndex(int32_t index,bool force)301 void RenderTabBar::SetIndex(int32_t index, bool force)
302 {
303     indicatorPlusX_ = 0.0;
304     indicatorPlusWidth_ = 0.0;
305 
306     if (Container::IsCurrentUsePartialUpdate()) {
307         tabsSize_ = static_cast<int32_t>(indicator_? GetChildren().size() - 1 : GetChildren().size());
308     }
309 
310     if (index < 0 || index >= tabsSize_) {
311         LOGW("illegal index = %{public}d", index);
312         return;
313     }
314 
315     if (index_ != index || force) {
316         if (mode_ == TabBarMode::FIXED_START && tabBarSizeAnimation_) {
317             needUpdateOffset_ = true;
318             auto tabBar = AceType::WeakClaim(this);
319             tabBarSizeAnimation_->Start(tabBar, index_, index);
320         }
321         index_ = index;
322         if (mode_ == TabBarMode::SCROLLABLE) {
323             if (actualWidth_ > GetLayoutParam().GetMaxSize().Width() && !isVertical_) {
324                 // In scrollable mod: the select tab must in middle of tabBar
325                 Offset centerViewPort(GetLayoutParam().GetMaxSize().Width() / DOUBLE_FACTOR, 0.0);
326                 Offset centerTabItem = tabItemOffsets_[index_] + Offset(tabsWidth_[index_] / DOUBLE_FACTOR, 0.0);
327                 scrollableOffset_ = centerViewPort - centerTabItem;
328                 if (!IsRightToLeft()) {
329                     scrollableOffset_.SetX(std::clamp(scrollableOffset_.GetX(), -MaxScrollableWidth(), 0.0));
330                 } else {
331                     scrollableOffset_.SetX(std::clamp(scrollableOffset_.GetX(), 0.0, MaxScrollableWidth()));
332                 }
333             }
334             if (actualHeight_ > GetLayoutParam().GetMaxSize().Height() && isVertical_) {
335                 // In scrollable mod: the select tab must in middle of tabBar
336                 Offset centerViewPort(0.0, GetLayoutParam().GetMaxSize().Height() / DOUBLE_FACTOR);
337                 Offset centerTabItem = tabItemOffsets_[index_] + Offset(0.0, tabsHeight_[index_] / DOUBLE_FACTOR);
338                 scrollableOffset_ = centerViewPort - centerTabItem;
339                 scrollableOffset_.SetY(std::clamp(scrollableOffset_.GetY(), -MaxScrollableHeight(), 0.0));
340             }
341         }
342     }
343 }
344 
Initialize()345 void RenderTabBar::Initialize()
346 {
347     if (!clickRecognizer_) {
348         clickRecognizer_ = AceType::MakeRefPtr<ClickRecognizer>();
349         clickRecognizer_->SetOnClick([weakBar = AceType::WeakClaim(this)](const ClickInfo& info) {
350             auto tabBar = weakBar.Upgrade();
351             if (tabBar) {
352                 tabBar->HandleClickedEvent(info);
353             }
354         });
355     }
356     if (!scrollable_) {
357         scrollable_ = AceType::MakeRefPtr<Scrollable>(
358             [weak = AceType::WeakClaim(this)](double offset, int32_t source) {
359                 auto tabBar = weak.Upgrade();
360                 if (tabBar && (source != SCROLL_FROM_START)) {
361                     return tabBar->HandleScrollablePosition(offset);
362                 } else {
363                     return false;
364                 }
365             },
366             isVertical_ ? Axis::VERTICAL : Axis::HORIZONTAL);
367         scrollable_->Initialize(GetContext());
368         scrollable_->SetNodeId(GetAccessibilityNodeId());
369         scrollable_->SetScrollableNode(AceType::WeakClaim(this));
370     }
371 
372     if (!tabBarSizeAnimation_ && !indicator_) {
373         tabBarSizeAnimation_ = AceType::MakeRefPtr<TabBarSizeAnimation>();
374         tabBarSizeAnimation_->Initialize(GetContext());
375     }
376     InitAccessibilityEventListener();
377 }
378 
InitAccessibilityEventListener()379 void RenderTabBar::InitAccessibilityEventListener()
380 {
381     auto refNode = accessibilityNode_.Upgrade();
382     if (!refNode) {
383         return;
384     }
385 
386     refNode->AddSupportAction(AceAction::ACTION_SCROLL_FORWARD);
387     refNode->AddSupportAction(AceAction::ACTION_SCROLL_BACKWARD);
388     refNode->AddSupportAction(AceAction::ACTION_CLICK);
389 
390     auto weakPtr = AceType::WeakClaim(this);
391     refNode->SetActionClickImpl([weakPtr]() {
392         auto tabBar = weakPtr.Upgrade();
393         if (tabBar) {
394             tabBar->AccessibilityClick();
395             return true;
396         }
397         return false;
398     });
399 
400     refNode->SetActionScrollForward([weakPtr]() {
401         auto tabBar = weakPtr.Upgrade();
402         if (tabBar) {
403             tabBar->AccessibilityScroll(true);
404             return true;
405         }
406         return false;
407     });
408 
409     refNode->SetActionScrollBackward([weakPtr]() {
410         auto tabBar = weakPtr.Upgrade();
411         if (tabBar) {
412             tabBar->AccessibilityScroll(false);
413             return true;
414         }
415         return false;
416     });
417 }
418 
AccessibilityScroll(bool isAdd)419 void RenderTabBar::AccessibilityScroll(bool isAdd)
420 {
421     if (tabItemOffsets_.empty()) {
422         return;
423     }
424     if (isAdd) {
425         accessibilityIndex_++;
426     } else {
427         accessibilityIndex_--;
428     }
429 }
430 
AccessibilityClick()431 void RenderTabBar::AccessibilityClick()
432 {
433     if (callback_) {
434         callback_(accessibilityIndex_);
435     }
436 }
437 
HandleClickedEvent(const ClickInfo & info)438 void RenderTabBar::HandleClickedEvent(const ClickInfo& info)
439 {
440     LOGI("Click event x is %{public}lf", info.GetLocalLocation().GetX());
441     if (tabItemOffsets_.empty()) {
442         LOGW("tabItemOffsets is empty");
443         return;
444     }
445     Offset local = info.GetLocalLocation() - scrollableOffset_;
446     if (isVertical_) {
447         auto clickRange = std::make_pair(tabItemOffsets_[0].GetY(), tabItemOffsets_[tabItemOffsets_.size() - 1].GetY());
448         if (local.GetY() < clickRange.first || local.GetY() > clickRange.second) {
449             LOGW("clicked (%{public}lf) position out of range [%{public}lf, %{public}lf]", local.GetY(),
450                 clickRange.first, clickRange.second);
451             return;
452         }
453     } else {
454         auto clickRange = std::make_pair(tabItemOffsets_[0].GetX(), tabItemOffsets_[tabItemOffsets_.size() - 1].GetX());
455         if (!IsRightToLeft()) {
456             if (local.GetX() < clickRange.first || local.GetX() > clickRange.second) {
457                 LOGW("clicked (%{public}lf) position out of range [%{public}lf, %{public}lf]", local.GetX(),
458                     clickRange.first, clickRange.second);
459                 return;
460             }
461         } else {
462             if (local.GetX() > tabBarWidth_ || local.GetX() < clickRange.second) {
463                 LOGW("clicked (%{public}lf) position out of range [%{public}lf, %{public}lf]", local.GetX(),
464                     clickRange.first, clickRange.second);
465                 return;
466             }
467         }
468     }
469 
470     auto pos = std::lower_bound(tabItemOffsets_.begin(), tabItemOffsets_.end(), local,
471         [weakBar = AceType::WeakClaim(this)](const Offset& a, const Offset& b) {
472             auto tabBar = weakBar.Upgrade();
473             if (tabBar) {
474                 return tabBar->IsRightToLeft() ? a.GetX() > b.GetX()
475                                                : (tabBar->isVertical_ ? a.GetY() < b.GetY() : a.GetX() < b.GetX());
476             } else {
477                 return false;
478             }
479         });
480     if (pos != tabItemOffsets_.end()) {
481         int32_t index = IsRightToLeft() ? std::distance(tabItemOffsets_.begin(), pos)
482                                         : std::distance(tabItemOffsets_.begin(), pos) - 1;
483         if (index >= 0 && index < tabsSize_ && index != index_) {
484             if (callback_) {
485                 callback_(index);
486             }
487         }
488     }
489 }
490 
OnTouchTestHit(const Offset & coordinateOffset,const TouchRestrict & touchRestrict,TouchTestResult & result)491 void RenderTabBar::OnTouchTestHit(
492     const Offset& coordinateOffset, const TouchRestrict& touchRestrict, TouchTestResult& result)
493 {
494     if (!clickRecognizer_ || !scrollable_) {
495         return;
496     }
497     clickRecognizer_->SetCoordinateOffset(coordinateOffset);
498     result.emplace_back(clickRecognizer_);
499     scrollable_->SetCoordinateOffset(coordinateOffset);
500     result.emplace_back(scrollable_);
501 }
502 
HandleScrollablePosition(double value)503 bool RenderTabBar::HandleScrollablePosition(double value)
504 {
505     if (IsScrollable()) {
506         if (!isVertical_ && actualWidth_ > GetLayoutSize().Width()) {
507             Offset delta(value, 0.0);
508             scrollableOffset_ += delta;
509             if (!IsRightToLeft()) {
510                 scrollableOffset_.SetX(std::clamp(scrollableOffset_.GetX(), -MaxScrollableWidth(), 0.0));
511             } else {
512                 scrollableOffset_.SetX(std::clamp(scrollableOffset_.GetX(), 0.0, MaxScrollableWidth()));
513             }
514             needUpdateOffset_ = false;
515         }
516         if (isVertical_ && actualHeight_ > GetLayoutSize().Height()) {
517             Offset delta(0.0, value);
518             scrollableOffset_ += delta;
519             scrollableOffset_.SetY(std::clamp(scrollableOffset_.GetY(), -MaxScrollableHeight(), 0.0));
520         }
521         if (!NearZero(value)) {
522             MarkNeedLayout();
523             return true;
524         }
525     }
526     return false;
527 }
528 
MakeInnerLayoutParam() const529 LayoutParam RenderTabBar::MakeInnerLayoutParam() const
530 {
531     LayoutParam innerLayout = GetLayoutParam();
532     if (mode_ == TabBarMode::FIXED) {
533         double paddingHorizontal = NormalizeToPx(padding_.Left()) + NormalizeToPx(padding_.Right());
534         double paddingVertical = NormalizeToPx(padding_.Top()) + NormalizeToPx(padding_.Bottom());
535         double tabMinWidth = std::max(0.0, innerLayout.GetMinSize().Width() - paddingHorizontal);
536         double tabMaxWidth = std::max(0.0, innerLayout.GetMaxSize().Width() - paddingHorizontal);
537         double tabMinHeight = std::max(0.0, innerLayout.GetMinSize().Height() - paddingVertical);
538         double tabMaxHeight = std::max(0.0, innerLayout.GetMaxSize().Height() - paddingVertical);
539         if (isVertical_) {
540             if (tabsSize_ > 1) {
541                 tabMinHeight = tabMinHeight / tabsSize_;
542                 tabMaxHeight = tabMaxHeight / tabsSize_;
543             }
544             innerLayout.SetMinSize(Size(std::max(innerLayout.GetMinSize().Width(), 0.0), std::max(tabMinHeight, 0.0)));
545             innerLayout.SetMaxSize(
546                 Size(std::max(GetLayoutParam().GetMaxSize().Width(), 0.0), std::max(tabMaxHeight, 0.0)));
547         } else {
548             if (tabsSize_ > 1) {
549                 tabMinWidth = tabMinWidth / tabsSize_;
550                 tabMaxWidth = tabMaxWidth / tabsSize_;
551             }
552             innerLayout.SetMinSize(Size(std::max(tabMinWidth, 0.0), std::max(innerLayout.GetMinSize().Height(), 0.0)));
553             innerLayout.SetMaxSize(
554                 Size(std::max(tabMaxWidth, 0.0), std::max(GetLayoutParam().GetMaxSize().Height(), 0.0)));
555         }
556     } else {
557         if (isVertical_) {
558             innerLayout.SetMinSize(
559                 Size(std::max(innerLayout.GetMinSize().Width(), 0.0), innerLayout.GetMinSize().Height()));
560             innerLayout.SetMaxSize(Size(std::max(innerLayout.GetMaxSize().Width(), 0.0), Size::INFINITE_SIZE));
561         } else {
562             innerLayout.SetMinSize(
563                 Size(innerLayout.GetMinSize().Width(), std::max(innerLayout.GetMinSize().Height(), 0.0)));
564             innerLayout.SetMaxSize(Size(Size::INFINITE_SIZE, std::max(innerLayout.GetMaxSize().Height(), 0.0)));
565         }
566     }
567     return innerLayout;
568 }
569 
MakeIndicatorLayoutParam(const RefPtr<RenderNode> & item) const570 LayoutParam RenderTabBar::MakeIndicatorLayoutParam(const RefPtr<RenderNode>& item) const
571 {
572     LayoutParam innerLayout;
573     auto context = context_.Upgrade();
574     if (!context) {
575         LOGE("context is null");
576         return innerLayout;
577     }
578     innerLayout.SetMinSize(Size(NormalizeToPx(activeIndicatorMinWidth_), 0.0));
579     Size maxLayoutParam = GetLayoutParam().GetMaxSize();
580     if (isVertical_) {
581         innerLayout.SetMaxSize(Size(
582             std::max(0.0, std::max(0.0, maxLayoutParam.Width()) - NormalizeToPx(FOCUS_ANIMATION_WIDTH) * DOUBLE_FACTOR),
583             std::max(0.0, maxLayoutParam.Height()) - NormalizeToPx(FOCUS_ANIMATION_WIDTH) * DOUBLE_FACTOR));
584     } else {
585         innerLayout.SetMaxSize(
586             Size(std::max(
587                 0.0, tabsWidth_[index_] - indicatorPlusWidth_ - NormalizeToPx(FOCUS_ANIMATION_WIDTH) * DOUBLE_FACTOR),
588                 std::max(0.0, maxLayoutParam.Height()) - NormalizeToPx(FOCUS_ANIMATION_WIDTH) * DOUBLE_FACTOR));
589     }
590 
591     if (indicatorSize_ == TabBarIndicatorType::LABEL) {
592         auto childSize = GetTabItemChildLayoutSize(item);
593         childSize += indicatorPadding_.GetLayoutSizeInPx(context->GetDipScale());
594 
595         innerLayout.SetMaxSize(
596             Size(std::min(
597                 innerLayout.GetMaxSize().Width() - indicatorPlusWidth_, childSize.Width() - indicatorPlusWidth_),
598                 std::min(innerLayout.GetMaxSize().Height(), childSize.Height())));
599     }
600     return innerLayout;
601 }
602 
MakeIndicatorOffset(const RefPtr<RenderNode> & item) const603 Offset RenderTabBar::MakeIndicatorOffset(const RefPtr<RenderNode>& item) const
604 {
605     auto context = context_.Upgrade();
606     if (!context) {
607         LOGE("context is null");
608         return Offset(0.0, 0.0);
609     }
610     Offset offset;
611     if (isVertical_) {
612         offset = Alignment::GetAlignPosition(Size(GetLayoutParam().GetMaxSize().Width(), tabsHeight_[index_]),
613             indicator_->GetLayoutSize(), Alignment::CENTER);
614     } else {
615         offset = Alignment::GetAlignPosition(Size(tabsWidth_[index_], GetLayoutParam().GetMaxSize().Height()),
616             indicator_->GetLayoutSize(), Alignment::CENTER);
617     }
618     if (indicatorStyle_ == TabBarIndicatorStyle::DEFAULT &&
619         (!onFocused_ || SystemProperties::GetDeviceType() == DeviceType::PHONE ||
620         SystemProperties::GetDeviceType() == DeviceType::TABLET ||
621         SystemProperties::GetDeviceType() == DeviceType::TWO_IN_ONE)) {
622         Size childSize = GetTabItemChildLayoutSize(item);
623         offset +=
624             Offset(0.0, childSize.Height() / DOUBLE_FACTOR) + indicatorPadding_.GetOffsetInPx(context->GetDipScale());
625     }
626     return offset;
627 }
628 
MaxScrollableWidth() const629 double RenderTabBar::MaxScrollableWidth() const
630 {
631     if (IsScrollable() && actualWidth_ > GetLayoutParam().GetMaxSize().Width() && !isVertical_) {
632         return actualWidth_ - GetLayoutParam().GetMaxSize().Width();
633     }
634     return 0.0;
635 }
636 
MaxScrollableHeight() const637 double RenderTabBar::MaxScrollableHeight() const
638 {
639     if (IsScrollable() && actualHeight_ > GetLayoutParam().GetMaxSize().Height() && isVertical_) {
640         return actualHeight_ - GetLayoutParam().GetMaxSize().Height();
641     }
642     return 0.0;
643 }
644 
GetTabItemChildLayoutSize(const RefPtr<RenderNode> & item) const645 Size RenderTabBar::GetTabItemChildLayoutSize(const RefPtr<RenderNode>& item) const
646 {
647     auto children = item->GetChildren();
648     if (children.empty()) {
649         return Size(0.0, 0.0);
650     }
651     return children.front()->GetLayoutSize();
652 }
653 
UpdateIndicatorStyle(const RefPtr<Component> & component)654 void RenderTabBar::UpdateIndicatorStyle(const RefPtr<Component>& component)
655 {
656     if (indicator_) {
657         indicator_->Update(component);
658         RefPtr<BoxComponent> boxComponent = AceType::DynamicCast<BoxComponent>(component);
659         if (boxComponent) {
660             indicatorStyle_ = GetIndicatorStyle(boxComponent);
661             indicatorPadding_ = boxComponent->GetPadding();
662         }
663     }
664 }
665 
HandleFocusEvent(bool focus)666 void RenderTabBar::HandleFocusEvent(bool focus)
667 {
668     onFocused_ = focus;
669     if (!onFocused_) {
670         auto context = context_.Upgrade();
671         if (context) {
672             context->CancelFocusAnimation();
673         }
674     }
675 }
676 
OnPaintFinish()677 void RenderTabBar::OnPaintFinish()
678 {
679     auto context = context_.Upgrade();
680     if (!context || !onFocused_) {
681         return;
682     }
683 
684     auto deviceType = SystemProperties::GetDeviceType();
685     if (deviceType == DeviceType::TV) {
686         if (indicator_) {
687             context->ShowFocusAnimation(RRect::MakeRRect(Rect(Offset(0, 0), indicator_->GetLayoutSize()),
688                 Radius(NormalizeToPx(focusRadiusDimension_))),
689                 focusAnimationColor_, indicator_->GetGlobalOffset());
690         }
691     } else {
692         int32_t focusedChildIndex = indicator_ ? index_ + 1 : index_;
693         auto focusedItem = GetChildByIndex(focusedChildIndex);
694         if (!focusedItem) {
695             return;
696         }
697 
698         auto layoutSize = focusedItem->GetLayoutSize();
699         auto position = focusedItem->GetGlobalOffset();
700 
701         double offsetForFocus = NormalizeToPx(OFFSET_FOR_FOCUS);
702         Offset offset = Offset(offsetForFocus, offsetForFocus);
703         layoutSize -= Size(offsetForFocus * 2.0, offsetForFocus * 2.0);
704         context->ShowFocusAnimation(
705             RRect::MakeRRect(Rect(offset, layoutSize), Radius(NormalizeToPx(focusRadiusDimension_))),
706             focusAnimationColor_, position + offset);
707     }
708 }
709 
ApplyGradientColor()710 void RenderTabBar::ApplyGradientColor()
711 {
712     auto parent = GetParent().Upgrade();
713     RefPtr<RenderBox> box = AceType::DynamicCast<RenderBox>(parent);
714     if (box) {
715         Color colorA = box->GetColor();
716         Color colorB = colorA.ChangeAlpha(0);
717 
718         double viewWidth = GetLayoutSize().Width();
719         double gradientWidthPx = NormalizeToPx(gradientWidth_);
720         // only box's width is big enough will add gradient
721         if (viewWidth > DOUBLE_FACTOR * gradientWidthPx && !isVertical_) {
722             auto frontDecoration = box->GetFrontDecoration();
723             if (!frontDecoration) {
724                 frontDecoration = AceType::MakeRefPtr<Decoration>();
725             }
726             auto backDecoration = box->GetBackDecoration();
727             if (backDecoration) {
728                 frontDecoration->SetBorder(backDecoration->GetBorder());
729             }
730             Gradient gradient = Gradient();
731             gradient.SetDirection(GradientDirection::RIGHT);
732             std::vector<GradientColor> gradientColors = std::vector<GradientColor>(GRADIENT_POINT_SIZE);
733 
734             gradientColors[0].SetColor(colorA);
735             gradientColors[0].SetDimension(Dimension(0.0, DimensionUnit::PX));
736 
737             gradientColors[1].SetColor(colorB);
738             gradientColors[1].SetDimension(Dimension(gradientWidthPx, DimensionUnit::PX));
739 
740             gradientColors[2].SetColor(colorB);
741             gradientColors[2].SetDimension(Dimension(viewWidth - gradientWidthPx, DimensionUnit::PX));
742 
743             gradientColors[3].SetColor(colorA);
744             gradientColors[3].SetDimension(Dimension(viewWidth, DimensionUnit::PX));
745 
746             for (const auto& gradientColor : gradientColors) {
747                 gradient.AddColor(gradientColor);
748             }
749 
750             frontDecoration->SetGradient(gradient);
751 
752             box->SetFrontDecoration(frontDecoration);
753         }
754     }
755 }
756 
GetIndicatorStyle(const RefPtr<BoxComponent> & component) const757 TabBarIndicatorStyle RenderTabBar::GetIndicatorStyle(const RefPtr<BoxComponent>& component) const
758 {
759     RefPtr<TabBarIndicatorComponent> indicatorComponent = AceType::DynamicCast<TabBarIndicatorComponent>(component);
760     if (indicatorComponent) {
761         return indicatorComponent->GetIndicatorStyle();
762     }
763     return TabBarIndicatorStyle::CUSTOM;
764 }
765 
InitScrollableOffset(TabBarMode mode)766 void RenderTabBar::InitScrollableOffset(TabBarMode mode)
767 {
768     if (mode != mode_) {
769         scrollableOffset_.Reset();
770     }
771 }
772 
GetChildByIndex(int32_t index) const773 RefPtr<RenderNode> RenderTabBar::GetChildByIndex(int32_t index) const
774 {
775     int32_t size = static_cast<int32_t>(GetChildren().size());
776     if (index < 0 || index >= size) {
777         return nullptr;
778     }
779     auto pos = GetChildren().begin();
780     std::advance(pos, index);
781     return *pos;
782 }
783 
ProvideRestoreInfo()784 std::string RenderTabBar::ProvideRestoreInfo()
785 {
786     auto jsonObj = JsonUtil::Create(true);
787     jsonObj->Put("index", index_);
788     jsonObj->Put("OffsetX", scrollableOffset_.GetX());
789     jsonObj->Put("OffsetY", scrollableOffset_.GetY());
790     return jsonObj->ToString();
791 }
792 
ApplyRestoreInfo(const RefPtr<TabController> & controller)793 void RenderTabBar::ApplyRestoreInfo(const RefPtr<TabController>& controller)
794 {
795     auto parent = GetParent().Upgrade();
796     if (!parent) {
797         LOGE("parent is nullptr");
798         return;
799     }
800     auto grandParent = parent->GetParent().Upgrade();
801     if (!grandParent) {
802         LOGE("grandParent is nullptr");
803         return;
804     }
805     std::string restoreInfo = grandParent->GetRestoreInfo();
806     if (restoreInfo.empty()) {
807         return;
808     }
809     auto info = JsonUtil::ParseJsonString(restoreInfo);
810     if (!info->IsValid() || !info->IsObject()) {
811         LOGW("RenderTabBar:: restore info is invalid");
812         return;
813     }
814 
815     auto jsonIndex = info->GetValue("index");
816     auto jsonOffsetX = info->GetValue("OffsetX");
817     auto jsonOffsetY = info->GetValue("OffsetY");
818 
819     index_ = jsonIndex->GetInt();
820     SetIndex(index_, true);
821     controller->SetIndexByController(index_, false);
822     controller->SetPendingIndex(index_);
823     controller->SetInitialIndex(index_);
824     scrollableOffset_.SetX(jsonOffsetX->GetDouble());
825     scrollableOffset_.SetY(jsonOffsetY->GetDouble());
826     grandParent->SetRestoreInfo("");
827 }
828 
829 } // namespace OHOS::Ace
830