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_content.h"
17 
18 #include "core/common/container.h"
19 
20 namespace OHOS::Ace {
21 namespace {
22 
23 constexpr double FRIC_RATIO = 0.5; // default ratio
24 constexpr double MIN_SCROLL_OFFSET = 0.20;
25 
26 } // namespace
27 
RenderTabContent()28 RenderTabContent::RenderTabContent() : RenderNode(true) {}
29 
Update(const RefPtr<Component> & component)30 void RenderTabContent::Update(const RefPtr<Component>& component)
31 {
32     const RefPtr<TabContentComponent> tabContent = AceType::DynamicCast<TabContentComponent>(component);
33     if (!tabContent || !tabContent->GetController()) {
34         LOGW("tabContent is null");
35         return;
36     }
37     controller_ = tabContent->GetController();
38     ACE_DCHECK(controller_);
39     auto context = context_.Upgrade();
40     if (context && !context->GetIsDeclarative()) {
41         contentCount_ = (controller_->GetTotalCount() > 0) ? controller_->GetTotalCount() : 0;
42         int32_t tabIndex = controller_ ? controller_->GetIndex() : 0;
43         if (tabIndex >= contentCount_) {
44             controller_->ValidateIndex((contentCount_ - 1 < 0) ? 0 : (contentCount_ - 1));
45             tabIndex = controller_->GetIndex();
46         }
47         currentIndex_ = tabIndex;
48     }
49 
50     ApplyRestoreInfo();
51 
52     if (scrollable_ != tabContent->IsScrollable()) {
53         if (animator_ && animator_->IsRunning()) {
54             animator_->Finish();
55         }
56         scrollable_ = tabContent->IsScrollable();
57     }
58     scrollDuration_ = tabContent->GetScrollDuration();
59     isVertical_ = tabContent->IsVertical();
60     SetTextDirection(tabContent->GetTextDirection());
61     if (context && context->GetIsDeclarative()) {
62         onChangeEvent_ = AceAsyncEvent<void(const std::shared_ptr<BaseEventInfo>&)>::Create(
63             tabContent->GetChangeEventId(), context_);
64     } else {
65         changeEvent_ = AceAsyncEvent<void(const std::string&)>::Create(tabContent->GetChangeEventId(), context_);
66         domChangeEvent_ = AceAsyncEvent<void(uint32_t)>::Create(tabContent->GetDomChangeEventId(), context_);
67     }
68 
69     MarkNeedLayout();
70     Initialize(GetContext());
71 }
72 
FlushIndex()73 void RenderTabContent::FlushIndex()
74 {
75     contentCount_ = controller_->GetTotalCount() > 0 ? controller_->GetTotalCount() : 0;
76     if (contentCount_ <= 0) {
77         currentIndex_ = 0;
78         return;
79     }
80     do {
81         if (controller_->IsIndexDefined()) {
82             currentIndex_ = controller_->GetIndex();
83             break;
84         }
85         auto initialIndex = controller_->GetInitialIndex();
86         auto pendingIndex = controller_->GetPendingIndex();
87         if (useInitialIndex_ && pendingIndex < 0) {
88             currentIndex_ = initialIndex < 0 ? 0 : initialIndex;
89             break;
90         }
91         currentIndex_ = pendingIndex < 0 ? 0 : pendingIndex;
92     } while (false);
93     currentIndex_ = currentIndex_ < contentCount_ ? currentIndex_ : (contentCount_ - 1);
94     controller_->SetIndexWithoutChangeContent(currentIndex_);
95     useInitialIndex_ = false;
96 }
97 
Initialize(const WeakPtr<PipelineContext> & context)98 void RenderTabContent::Initialize(const WeakPtr<PipelineContext>& context)
99 {
100     if (!animator_) {
101         animator_ = CREATE_ANIMATOR(context);
102     }
103 
104     // if the tab is vertical, use VerticalDragRecognizer
105     if (isVertical_) {
106         dragDetector_ = AceType::MakeRefPtr<VerticalDragRecognizer>();
107     } else {
108         dragDetector_ = AceType::MakeRefPtr<HorizontalDragRecognizer>();
109     }
110 
111     // if scrollable is true, initialize the dragDetector
112     if (scrollable_) {
113         dragDetector_->SetOnDragStart([weakContent = AceType::WeakClaim(this)](const DragStartInfo& info) {
114             auto tabContent = weakContent.Upgrade();
115             if (tabContent) {
116                 tabContent->HandleDragStart();
117             }
118         });
119         dragDetector_->SetOnDragUpdate([weakContent = AceType::WeakClaim(this)](const DragUpdateInfo& info) {
120             auto tabContent = weakContent.Upgrade();
121             if (tabContent) {
122                 tabContent->HandleDragUpdate(info.GetMainDelta());
123             }
124         });
125         dragDetector_->SetOnDragEnd([weakContent = AceType::WeakClaim(this)](const DragEndInfo& info) {
126             auto tabContent = weakContent.Upgrade();
127             if (tabContent) {
128                 tabContent->HandleDragEnd();
129             }
130         });
131         dragDetector_->SetOnDragCancel([weakContent = AceType::WeakClaim(this)]() {
132             auto tabContent = weakContent.Upgrade();
133             if (tabContent) {
134                 tabContent->HandleDragEnd();
135             }
136         });
137     } else {
138         dragDetector_->SetOnDragStart([](const DragStartInfo& info) {});
139         dragDetector_->SetOnDragUpdate([](const DragUpdateInfo& info) {});
140         dragDetector_->SetOnDragEnd([](const DragEndInfo& info) {});
141         dragDetector_->SetOnDragCancel([]() {});
142     }
143 }
144 
FireContentChangeEvent() const145 void RenderTabContent::FireContentChangeEvent() const
146 {
147     auto context = context_.Upgrade();
148     if (context && context->GetIsDeclarative()) {
149         if (onChangeEvent_) {
150             onChangeEvent_(std::make_shared<TabContentChangeEvent>(currentIndex_));
151         }
152         return;
153     }
154     if (changeEvent_) {
155         std::string param = std::string(R"("change",{"index":)").append(std::to_string(currentIndex_).append("},null"));
156         changeEvent_(param);
157     }
158 }
159 
FireDomChangeEvent(int32_t index) const160 void RenderTabContent::FireDomChangeEvent(int32_t index) const
161 {
162     if (domChangeEvent_) {
163         domChangeEvent_(index);
164     }
165 }
166 
HandContentIndicatorEvent(int32_t newIndex,bool needChange) const167 void RenderTabContent::HandContentIndicatorEvent(int32_t newIndex, bool needChange) const
168 {
169     if (indicatorCallback_) {
170         indicatorCallback_(scrollOffset_ / contentWidth_, newIndex, needChange);
171     }
172 }
173 
HandleDragStart()174 void RenderTabContent::HandleDragStart()
175 {
176     if (isInAnimation_) {
177         return;
178     }
179     isDragging_ = true;
180 }
181 
HandleDragUpdate(double offset)182 void RenderTabContent::HandleDragUpdate(double offset)
183 {
184     if (isInAnimation_) {
185         return;
186     }
187     UpdateScrollPosition(offset);
188     if (NearZero(scrollOffset_)) {
189         return;
190     }
191 
192     int32_t newIndex = IsRightToLeft() ? (scrollOffset_ < 0.0 ? GetPrevIndex() : GetNextIndex())
193                                        : (scrollOffset_ > 0.0 ? GetPrevIndex() : GetNextIndex());
194     HandContentIndicatorEvent(newIndex, false);
195 }
196 
HandleDragEnd()197 void RenderTabContent::HandleDragEnd()
198 {
199     if (isInAnimation_) {
200         return;
201     }
202     isDragging_ = false;
203     if (NearZero(scrollOffset_)) {
204         return;
205     }
206     int32_t newIndex = IsRightToLeft() ? (scrollOffset_ < 0.0 ? GetPrevIndex() : GetNextIndex())
207                                        : (scrollOffset_ > 0.0 ? GetPrevIndex() : GetNextIndex());
208     ScrollContents(newIndex, false);
209 }
210 
ChangeScroll(int32_t index,bool fromController)211 void RenderTabContent::ChangeScroll(int32_t index, bool fromController)
212 {
213     if (Container::IsCurrentUsePartialUpdate() && contentMap_.find(currentIndex_) == contentMap_.end()) {
214         // That happens in case we just updated index only
215         // so we needed to keep content as is.
216         // There might be a better way to update index via controller
217         // without triggering of a scrolling
218         currentIndex_ = index;
219         return;
220     }
221     ScrollContents(index, true, fromController);
222 }
223 
ScrollContents(int32_t newIndex,bool isLinkBar,bool fromController)224 void RenderTabContent::ScrollContents(int32_t newIndex, bool isLinkBar, bool fromController)
225 {
226     if (!animator_->IsStopped()) {
227         // clear stop listener and stop
228         if (isInAnimation_) {
229             animator_->Finish();
230         } else {
231             animator_->Stop();
232         }
233     }
234     animator_->ClearStopListeners();
235     animator_->ClearStartListeners();
236 
237     if (isAnimationAdded_) {
238         animator_->RemoveInterpolator(translate_);
239         isAnimationAdded_ = false;
240     }
241 
242     bool needChange = false;
243     int32_t index = currentIndex_;
244     double start = scrollOffset_;
245     double end = 0.0;
246     if (newIndex >= 0 && newIndex < contentCount_) {
247         end = currentIndex_ > newIndex ? nextOffset_ : prevOffset_;
248 
249         // Adjust offset more than MIN_SCROLL_OFFSET at least
250         double minOffset = 0.0;
251         if (isVertical_) {
252             minOffset = MIN_SCROLL_OFFSET * contentHeight_;
253         } else {
254             minOffset = MIN_SCROLL_OFFSET * contentWidth_;
255         }
256         if (!NearZero(scrollOffset_) && std::abs(scrollOffset_) < minOffset) {
257             end = 0.0;
258         }
259     }
260 
261     if (!NearZero(end) || NearZero(scrollOffset_)) {
262         needChange = true;
263     }
264     translate_ = AceType::MakeRefPtr<CurveAnimation<double>>(start, end, Curves::FRICTION);
265     auto weak = AceType::WeakClaim(this);
266     translate_->AddListener(Animation<double>::ValueCallback([weak, index, newIndex, needChange](double value) {
267         auto tabContent = weak.Upgrade();
268         if (tabContent) {
269             tabContent->UpdateChildPosition(value, index, newIndex, needChange);
270             tabContent->HandContentIndicatorEvent(newIndex, needChange);
271         }
272     }));
273 
274     animator_->AddStopListener([weak, newIndex, needChange, fromController]() {
275         auto tabContent = weak.Upgrade();
276         if (tabContent) {
277             tabContent->HandleStopListener(newIndex, needChange, fromController);
278         }
279     });
280     animator_->AddStartListener([weak, newIndex, needChange, isLinkBar]() {
281         auto tabContent = weak.Upgrade();
282         if (tabContent) {
283             tabContent->HandleStartListener(newIndex, needChange, isLinkBar);
284         }
285     });
286     animator_->SetDuration(static_cast<int32_t>(scrollDuration_));
287     animator_->AddInterpolator(translate_);
288     animator_->Play();
289     MarkNeedRender();
290 }
291 
HandleStartListener(int32_t newIndex,bool needChange,bool isLinkBar)292 void RenderTabContent::HandleStartListener(int32_t newIndex, bool needChange, bool isLinkBar)
293 {
294     isInAnimation_ = true;
295     isAnimationAdded_ = true;
296     if (newIndex >= 0 && newIndex < contentCount_ && needChange) {
297         currentIndex_ = newIndex;
298         if (callback_) {
299             callback_(newIndex);
300         }
301         if (!isLinkBar) {
302             FireDomChangeEvent(newIndex);
303         }
304 
305         // Set the new index node not hidden
306         const auto& newItem = contentMap_[newIndex];
307         if (!newItem) {
308             LOGE("no content at index %{public}d", newIndex);
309             return;
310         }
311         newItem->SetHidden(false);
312     }
313 }
314 
HandleStopListener(int32_t newIndex,bool needChange,bool fromController)315 void RenderTabContent::HandleStopListener(int32_t newIndex, bool needChange, bool fromController)
316 {
317     // callback used to notify the change of index
318     if (newIndex >= 0 && newIndex < contentCount_ && needChange) {
319         if (!fromController) {
320             FireContentChangeEvent();
321         }
322         SetHiddenChild();
323     }
324     if (isInAnimation_) {
325         isInAnimation_ = false;
326         scrollOffset_ = 0.0;
327     }
328 }
329 
SetHiddenChild()330 void RenderTabContent::SetHiddenChild()
331 {
332     for (const auto& item : contentMap_) {
333         const auto& childItem = item.second;
334         if (!childItem) {
335             continue;
336         }
337         int32_t index = item.first;
338         childItem->SetHidden(index != currentIndex_);
339     }
340 }
341 
OnTouchTestHit(const Offset & coordinateOffset,const TouchRestrict & touchRestrict,TouchTestResult & result)342 void RenderTabContent::OnTouchTestHit(
343     const Offset& coordinateOffset, const TouchRestrict& touchRestrict, TouchTestResult& result)
344 {
345     if (dragDetector_) {
346         dragDetector_->SetCoordinateOffset(coordinateOffset);
347         result.emplace_back(dragDetector_);
348     }
349 }
350 
GetOffset(double delta)351 double RenderTabContent::GetOffset(double delta)
352 {
353     if (isVertical_) {
354         if (!NearZero(contentHeight_)) {
355             double friction = GetFriction(std::abs(scrollOffset_) / contentHeight_);
356             return friction * delta;
357         }
358     } else {
359         if (!NearZero(contentWidth_)) {
360             double friction = GetFriction(std::abs(scrollOffset_) / contentWidth_);
361             return friction * delta;
362         }
363     }
364     return delta;
365 }
366 
GetFriction(double percentage)367 double RenderTabContent::GetFriction(double percentage)
368 {
369     return FRIC_RATIO * std::pow(1.0 - percentage, SQUARE);
370 }
371 
UpdateScrollPosition(double dragDelta)372 void RenderTabContent::UpdateScrollPosition(double dragDelta)
373 {
374     double newDragOffset = scrollOffset_ + dragDelta;
375     int32_t newIndex = IsRightToLeft() ? (newDragOffset < 0.0 ? GetPrevIndex() : GetNextIndex())
376                                        : (newDragOffset > 0.0 ? GetPrevIndex() : GetNextIndex());
377     if ((currentIndex_ == 0 && newIndex == -1) || (currentIndex_ == (contentCount_ - 1) && newIndex == contentCount_)) {
378         scrollOffset_ += GetOffset(dragDelta);
379     } else {
380         scrollOffset_ = newDragOffset;
381     }
382 
383     if (contentMap_.find(newIndex) == contentMap_.end() && newIndex >= 0 && newIndex < contentCount_) {
384         if (requireCallback_) {
385             requireCallback_(newIndex);
386         }
387         return;
388     }
389     UpdateChildPosition(scrollOffset_, currentIndex_, newIndex, true);
390 }
391 
UpdateDragPosition(int32_t index)392 void RenderTabContent::UpdateDragPosition(int32_t index)
393 {
394     UpdateChildPosition(scrollOffset_, currentIndex_, index, true);
395 }
396 
UpdateChildPosition(double offset,int32_t currentIndex,int32_t newIndex,bool needChange)397 void RenderTabContent::UpdateChildPosition(double offset, int32_t currentIndex, int32_t newIndex, bool needChange)
398 {
399     scrollOffset_ = offset;
400     if (currentIndex < 0 || currentIndex >= contentCount_) {
401         LOGE("currentIndex out of range, currentIndex is %{public}d, %{public}d", currentIndex, contentCount_);
402         return;
403     }
404     const auto& fromItem = contentMap_[currentIndex];
405     if (!fromItem) {
406         LOGE("no content at index %{public}d", currentIndex);
407         return;
408     }
409     fromItem->SetPosition(GetMainAxisOffset(offset));
410     fromItem->MarkNeedRender();
411 
412     for (const auto& item : contentMap_) {
413         const auto& childItem = item.second;
414         if (!childItem) {
415             continue;
416         }
417         int32_t index = item.first;
418         childItem->SetHidden(index != currentIndex && index != newIndex);
419     }
420     // at the first one item or the last one item, no more switching
421     if (newIndex < 0 || newIndex >= contentCount_) {
422         return;
423     }
424 
425     // scroll between left and right
426     const auto& toItem = contentMap_[newIndex];
427     if (!toItem) {
428         LOGE("no content at index %{public}d", newIndex);
429         return;
430     }
431     toItem->SetHidden(false);
432     auto toItemPosValue = offset + (newIndex < currentIndex ? prevOffset_ : nextOffset_);
433     Offset toItemPos = GetMainAxisOffset(toItemPosValue);
434     toItem->SetPosition(toItemPos);
435     toItem->MarkNeedRender();
436     MarkNeedRender();
437 }
438 
GetPrevIndex() const439 inline int32_t RenderTabContent::GetPrevIndex() const
440 {
441     return currentIndex_ - 1;
442 }
443 
GetNextIndex() const444 inline int32_t RenderTabContent::GetNextIndex() const
445 {
446     return currentIndex_ + 1;
447 }
448 
PerformLayout()449 void RenderTabContent::PerformLayout()
450 {
451     const std::list<RefPtr<RenderNode>>& children = GetChildren();
452     if (!children.empty()) {
453         LayoutParam innerLayout = GetLayoutParam();
454         Size maxSize = GetLayoutParam().GetMaxSize();
455         for (const auto& item : contentMap_) {
456             const auto& childItem = item.second;
457             if (childItem) {
458                 childItem->Layout(innerLayout);
459             }
460         }
461         SetLayoutSize(maxSize);
462         contentWidth_ = GetLayoutSize().Width();
463         contentHeight_ = GetLayoutSize().Height();
464         if (isVertical_) {
465             prevOffset_ = -contentHeight_;
466             nextOffset_ = contentHeight_;
467         } else {
468             prevOffset_ = IsRightToLeft() ? contentWidth_ : -contentWidth_;
469             nextOffset_ = IsRightToLeft() ? -contentWidth_ : contentWidth_;
470         }
471         Offset prevPosition = GetMainAxisOffset(prevOffset_);
472         Offset nextPosition = GetMainAxisOffset(nextOffset_);
473 
474         for (const auto& item : contentMap_) {
475             const auto& childItem = item.second;
476             if (!childItem) {
477                 LOGW("The childItem is null");
478                 continue;
479             }
480             int32_t index = item.first;
481             // make sure the new requested tab start with right position, when in animation
482             if ((!isDragging_ && !isInAnimation_) || (index == requestedIndex_) || forceUpdate_) {
483                 if (index < currentIndex_) {
484                     childItem->SetPosition(prevPosition);
485                     childItem->SetHidden(true);
486                 } else if (index == currentIndex_) {
487                     childItem->SetPosition(Offset::Zero());
488                     childItem->SetHidden(false);
489                 } else {
490                     childItem->SetPosition(nextPosition);
491                     childItem->SetHidden(true);
492                 }
493             }
494         }
495         forceUpdate_ = false;
496         requestedIndex_ = -1;
497     }
498 }
499 
IsUseOnly()500 bool RenderTabContent::IsUseOnly()
501 {
502     return true;
503 }
504 
ProvideRestoreInfo()505 std::string RenderTabContent::ProvideRestoreInfo()
506 {
507     return std::to_string(currentIndex_);
508 }
509 
ApplyRestoreInfo()510 void RenderTabContent::ApplyRestoreInfo()
511 {
512     auto parent = GetParent().Upgrade();
513     auto grandParent = parent->GetParent().Upgrade();
514     std::string restoreInfo = grandParent->GetRestoreInfo();
515     if (restoreInfo.empty()) {
516         return;
517     }
518     currentIndex_ = StringUtils::StringToInt(GetRestoreInfo());
519     grandParent->SetRestoreInfo("");
520 }
521 
522 } // namespace OHOS::Ace
523