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