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