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