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