1 /*
2  * Copyright (c) 2021-2024 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/scroll/render_multi_child_scroll.h"
17 
18 #include "core/common/vibrator/vibrator_proxy.h"
19 
20 namespace OHOS::Ace {
21 namespace {
22 
23 constexpr double NONE_SCROLL_POSITION = -1.0;
24 constexpr double MIN_EXTENT = 5.0;
25 constexpr double EXTENT_RATIO = 2.0;
26 constexpr int32_t ANIMATION_DURATION = 200;
27 constexpr double LIST_INCONTINUOUS_ROTATION_SENSITYVITY_NORMAL = 0.6;
28 constexpr double LIST_CONTINUOUS_ROTATION_SENSITYVITY_NORMAL = 1.0;
29 constexpr double LIST_ITEMCENTER_ROTATION_THRESHOLD = 0.7;
30 constexpr int32_t COMPATIBLE_VERSION = 6;
31 
32 #ifdef WEARABLE_PRODUCT
33 const std::string& VIBRATOR_TYPE_WATCH_CROWN_STRENGTH3 = "watchhaptic.crown.strength3";
34 #endif
35 
36 } // namespace
37 
AddChild(const RefPtr<RenderList> & child)38 void RenderMultiChildScroll::AddChild(const RefPtr<RenderList>& child)
39 {
40     RenderNode::AddChild(child);
41 }
42 
MakeInnerLayoutParam() const43 LayoutParam RenderMultiChildScroll::MakeInnerLayoutParam() const
44 {
45     LayoutParam layout;
46     if (axis_ == Axis::VERTICAL) {
47         layout.SetMinSize(Size(viewPort_.Width(), layout.GetMinSize().Height()));
48         layout.SetMaxSize(Size(viewPort_.Width(), layout.GetMaxSize().Height()));
49     } else {
50         layout.SetMinSize(Size(layout.GetMinSize().Width(), viewPort_.Height()));
51         layout.SetMaxSize(Size(layout.GetMaxSize().Width(), viewPort_.Height()));
52     }
53     return layout;
54 }
55 
ProcessScrollExtent()56 void RenderMultiChildScroll::ProcessScrollExtent()
57 {
58     // When the scrollBar is off, return
59     if (!scrollBar_ || !scrollBar_->NeedScrollBar()) {
60         return;
61     }
62 
63     if (NearEqual(scrollBarExtent_, 0.0)) {
64         scrollBarExtent_ = mainScrollExtent_;
65         return;
66     }
67 
68     if (mainScrollExtent_ - scrollBarExtent_ > MIN_EXTENT && !scrollBarExtentFlag_) {
69         scrollBarExtentFlag_ = true;
70         auto animation =
71             AceType::MakeRefPtr<CurveAnimation<double>>(scrollBarExtent_, mainScrollExtent_, Curves::SHARP);
72         animation->AddListener([weakScroll = AceType::WeakClaim(this)](double value) {
73             auto scroll = weakScroll.Upgrade();
74             if (scroll) {
75                 scroll->SetMainScrollExtentForBar(value);
76                 scroll->MarkNeedLayout(true);
77             }
78         });
79         if (animateController_) {
80             animateController_->ClearInterpolators();
81             animateController_->SetDuration(ANIMATION_DURATION);
82 
83             // add the new animation
84             animateController_->AddInterpolator(animation);
85             animateController_->Play();
86         }
87     } else if (!scrollBarExtentFlag_) {
88         scrollBarExtent_ = mainScrollExtent_;
89     }
90 }
91 
CalculateMainScrollExtent()92 bool RenderMultiChildScroll::CalculateMainScrollExtent()
93 {
94     Size itemSize; // Calculate all children layout size.
95     for (const auto& child : GetChildren()) {
96         itemSize += child->GetLayoutSize();
97     }
98 
99     bool isScrollable = false;
100     if (axis_ == Axis::VERTICAL) {
101         double paddingVertical = NormalizeToPx(padding_.Top()) + NormalizeToPx(padding_.Bottom());
102         mainScrollExtent_ = itemSize.Height() + paddingVertical + outBoundaryExtent_;
103         ProcessScrollExtent();
104         if (mainScrollExtent_ > viewPort_.Height()) {
105             isScrollable = true;
106         }
107     } else {
108         double paddingHorizontal = NormalizeToPx(padding_.Left()) + NormalizeToPx(padding_.Right());
109         mainScrollExtent_ = itemSize.Width() + paddingHorizontal + outBoundaryExtent_;
110         scrollBarExtent_ = mainScrollExtent_;
111         if (mainScrollExtent_ > viewPort_.Width()) {
112             isScrollable = true;
113         }
114     }
115 
116     // If not scrollable, reset scrollable_ to null.
117     if (!isScrollable) {
118         if (scrollable_) {
119             scrollable_->MarkAvailable(false);
120             if (scrollable_->Idle() && GetMainOffset(currentOffset_) > 0.0) {
121                 scrollEffect_->ProcessScrollOver(0.0);
122             }
123         }
124         if (scrollBar_) {
125             scrollBar_->SetScrollable(false);
126         }
127         if (positionController_) {
128             positionController_->SetNonScrollable();
129         }
130     } else {
131         if (scrollable_ && scrollable_->Available()) {
132             if (scrollable_->Idle() &&
133                 GreatNotEqual(GetMainOffset(currentOffset_), mainScrollExtent_ - GetMainSize(viewPort_))) {
134                 // scroll to bottom
135                 scrollEffect_->ProcessScrollOver(0.0);
136             }
137         } else {
138             if (scrollable_) {
139                 scrollable_->MarkAvailable(true);
140             }
141         }
142         if (scrollBar_) {
143             scrollBar_->SetScrollable(true);
144         }
145     }
146 
147     return isScrollable;
148 }
149 
JumpToIndex(int32_t index)150 void RenderMultiChildScroll::JumpToIndex(int32_t index)
151 {
152     if (GetChildren().empty()) {
153         LOGE("no list in scroll");
154         return;
155     }
156     auto listBase = AceType::DynamicCast<RenderList>(GetChildren().front());
157     if (!listBase) {
158         LOGE("no list to jump");
159         return;
160     }
161 
162     double position = listBase->CalculateItemPosition(index, ScrollType::SCROLL_INDEX);
163     if (position < 0.0) {
164         LOGE("no this index: %{public}d", index);
165         return;
166     }
167     LOGI("jump to index:%{public}d position:%{public}lf", index, position);
168     if (CalculateMainScrollExtent()) {
169         RenderScroll::JumpToPosition(position, SCROLL_FROM_JUMP);
170     } else {
171         LOGW("Current is not allow to jump index.");
172     }
173 }
174 
JumpToPosition(double position,int32_t source)175 void RenderMultiChildScroll::JumpToPosition(double position, int32_t source)
176 {
177     if (GetChildren().empty()) {
178         LOGE("no list in scroll");
179         return;
180     }
181     auto listBase = AceType::DynamicCast<RenderList>(GetChildren().front());
182     if (!listBase) {
183         LOGE("no list to jump");
184         return;
185     }
186     listBase->CalculateItemPosition(position);
187     LOGI("jump to position:%{public}lf", position);
188     if (CalculateMainScrollExtent()) {
189         RenderScroll::JumpToPosition(position, source);
190     } else {
191         LOGW("Current is not allow to jump position.");
192     }
193 }
194 
UpdateEdgeEffect(const RefPtr<ListComponent> & listComponent)195 void RenderMultiChildScroll::UpdateEdgeEffect(const RefPtr<ListComponent>& listComponent)
196 {
197     auto newEffect = listComponent->GetScrollEffect();
198     if (scrollEffect_ != newEffect) {
199         scrollEffect_ = newEffect;
200         if (scrollEffect_) {
201             ResetEdgeEffect();
202         }
203     }
204 }
205 
UpdateGradient(const RefPtr<ListComponent> & listComponent)206 void RenderMultiChildScroll::UpdateGradient(const RefPtr<ListComponent>& listComponent)
207 {
208     gradientWidth_ = listComponent->GetGradientWidth();
209     backgroundColor_ = listComponent->GetBackgroundColor();
210 }
211 
Update(const RefPtr<Component> & component)212 void RenderMultiChildScroll::Update(const RefPtr<Component>& component)
213 {
214     auto listComponent = AceType::DynamicCast<ListComponent>(component);
215     if (!listComponent) {
216         LOGE("component is not a ListComponent");
217         return;
218     }
219 
220     auto context = GetContext().Upgrade();
221     if (!context) {
222         LOGE("context is nullptr");
223         return;
224     }
225     if (context->IsJsCard()) {
226         cacheExtent_ = (std::numeric_limits<double>::max)();
227     }
228 
229     scrollVibrate_ = listComponent->GetScrollVibrate();
230     if (scrollVibrate_ && !vibrator_ && context) {
231         vibrator_ = VibratorProxy::GetInstance().GetVibrator(context->GetTaskExecutor());
232     }
233 
234     rotationVibrate_ = listComponent->IsRotationVibrate();
235     if (rotationVibrate_ && !vibrator_ && context) {
236         vibrator_ = VibratorProxy::GetInstance().GetVibrator(context->GetTaskExecutor());
237     }
238 
239     if (listComponent->IsInRefresh()) {
240         auto parent = GetParent().Upgrade();
241         while (parent) {
242             auto refresh = AceType::DynamicCast<RenderRefresh>(parent);
243             if (refresh) {
244                 refreshParent_ = AceType::WeakClaim(AceType::RawPtr(refresh));
245                 break;
246             }
247             parent = parent->GetParent().Upgrade();
248         }
249     }
250 
251     bool directionFlag = false;
252     LOGI("RenderMultiChildScroll Update:GetDirection(): %{public}d, listComponent->GetDirection() is: %{public}d",
253         GetDirection(), listComponent->GetDirection());
254     if (GetDirection() != listComponent->GetDirection()) {
255         SetDirection(listComponent->GetDirection());
256         directionFlag = true;
257     }
258 
259     auto axis = (GetDirection() == FlexDirection::COLUMN || GetDirection() == FlexDirection::COLUMN_REVERSE)
260                     ? Axis::VERTICAL
261                     : Axis::HORIZONTAL;
262     if (axis_ != axis) {
263         axis_ = axis;
264         directionFlag = true;
265     }
266     if (directionFlag) {
267         ResetScrollable();
268     }
269 
270     if (scrollable_) {
271         scrollable_->SetOverSpringProperty(listComponent->OverSpringProperty());
272         scrollable_->MarkNeedCenterFix(listComponent->GetSupportItemCenter());
273     }
274 
275     // sync scrollpage from List child
276     SetScrollPage(listComponent->GetScrollPage());
277 
278     // Update its child.
279     auto children = GetChildren();
280     if (!children.empty()) {
281         auto listNode = children.front();
282         if (listNode) {
283             listNode->Update(component);
284             listNode->Attach(GetContext());
285         }
286     }
287 
288     UpdateGradient(listComponent);
289     UpdateEdgeEffect(listComponent);
290 
291     auto newController = listComponent->GetPositionController();
292     if (positionController_ != newController) {
293         positionController_ = newController;
294         if (positionController_) {
295             positionController_->SetScrollEvent(ScrollEvent::SCROLL_TOP,
296                 AceAsyncEvent<void(std::shared_ptr<ScrollEventInfo>&)>::Create(listComponent->GetOnScrollTop(),
297                 GetContext()));
298             positionController_->SetScrollEvent(ScrollEvent::SCROLL_BOTTOM,
299                 AceAsyncEvent<void(std::shared_ptr<ScrollEventInfo>&)>::Create(listComponent->GetOnScrollBottom(),
300                 GetContext()));
301             positionController_->SetScrollEvent(ScrollEvent::SCROLL_TOUCHUP,
302                 AceAsyncEvent<void(std::shared_ptr<ScrollEventInfo>&)>::Create(listComponent->GetOnScrollTouchUp(),
303                 GetContext()));
304             positionController_->SetScrollEvent(ScrollEvent::SCROLL_END,
305                 AceAsyncEvent<void(std::shared_ptr<ScrollEventInfo>&)>::Create(listComponent->GetOnScrollEnd(),
306                 GetContext()));
307             positionController_->SetScrollEvent(ScrollEvent::SCROLL_POSITION,
308                 AceAsyncEvent<void(std::shared_ptr<ScrollEventInfo>&)>::Create(listComponent->GetOnScroll(),
309                 GetContext()));
310             positionController_->SetScrollNode(AceType::WeakClaim(this));
311         }
312     }
313     if (positionController_) {
314         initialIndex_ = positionController_->GetInitialIndex();
315         initialOffset_ = positionController_->GetInitialOffset();
316     }
317 
318     if (!animateController_) {
319         animateController_ = CREATE_ANIMATOR(GetContext());
320         animateController_->AddStopListener([weakScroll = AceType::WeakClaim(this)]() {
321             auto scroll = weakScroll.Upgrade();
322             if (scroll) {
323                 scroll->scrollBarExtentFlag_ = false;
324             }
325         });
326     }
327 
328     auto scrollBar = listComponent->GetScrollBar();
329     rightToLeft_ = listComponent->GetRightToLeft();
330     InitScrollBar(scrollBar);
331 
332     // This should be put after setting positionController_.
333     RenderScroll::Update(component);
334 }
335 
ReachMaxCount() const336 bool RenderMultiChildScroll::ReachMaxCount() const
337 {
338     bool reached = true;
339     for (const auto& child : GetChildren()) {
340         auto listBase = AceType::DynamicCast<RenderList>(child);
341         if (!listBase) {
342             continue;
343         }
344         int32_t currentIndex = listBase->GetCurrentMaxIndex();
345         int32_t maxCount = listBase->GetMaxCount();
346         if (maxCount <= 0 || currentIndex != (maxCount - 1)) {
347             reached = false;
348             break;
349         }
350     }
351     return reached;
352 }
353 
OnPredictLayout(int64_t deadline)354 void RenderMultiChildScroll::OnPredictLayout(int64_t deadline)
355 {
356     int32_t childrenSize = static_cast<int32_t>(GetChildren().size());
357     if (currentIndex_ < 0 || currentIndex_ >= childrenSize || childrenSize == 0) {
358         LOGE("invalid current index: %{public}d, size is: %{public}d", currentIndex_, childrenSize);
359         return;
360     }
361     // only in list widget enabled build next
362     auto child = GetChildren().front();
363     auto listBase = AceType::DynamicCast<RenderList>(child);
364     if (listBase) {
365         Offset lastOffset = Offset::Zero() - currentOffset_ - correctedDelta_;
366         Offset curOffset = lastOffset;
367         double mainOffset = GetMainOffset(curOffset);
368         double mainExtent = GetMainSize(viewPort_);
369         double layoutHead = 0.0;
370         double layoutTail = mainExtent;
371         if (IsRowReverse() || IsColReverse()) {
372             layoutHead = layoutHead - cacheExtent_ + mainOffset;
373             layoutTail = layoutTail + cacheExtent_ + mainOffset;
374         } else {
375             layoutHead = layoutHead - cacheExtent_ - mainOffset;
376             layoutTail = layoutTail + cacheExtent_ - mainOffset;
377         }
378         listBase->BuildNextItem(layoutHead, layoutTail, curOffset, viewPort_);
379     }
380 }
381 
LayoutChild(const RefPtr<RenderNode> & child,const Offset & position,double start,double end)382 void RenderMultiChildScroll::LayoutChild(
383     const RefPtr<RenderNode>& child, const Offset& position, double start, double end)
384 {
385     auto listBase = AceType::DynamicCast<RenderList>(child);
386     if (listBase) {
387         listBase->ResetLayoutRange(start, end, position, viewPort_);
388         listBase->SetLayoutParam(GetLayoutParam());
389         listBase->SetNeedLayout(true);
390         listBase->OnLayout();
391     }
392 }
393 
LayoutChild(const RefPtr<RenderNode> & curChild,int32_t curIndex,const RefPtr<RenderNode> & lastChild)394 bool RenderMultiChildScroll::LayoutChild(
395     const RefPtr<RenderNode>& curChild, int32_t curIndex, const RefPtr<RenderNode>& lastChild)
396 {
397     Offset lastOffset = Offset::Zero() - currentOffset_;
398     Size lastSize;
399     if (lastChild) {
400         lastOffset = lastChild->GetPosition();
401         lastSize = lastChild->GetLayoutSize();
402     }
403 
404     Offset curOffset = lastOffset + lastSize;
405     double mainOffset = GetMainOffset(curOffset);
406     double mainExtent = GetMainSize(viewPort_);
407     // The following children are not visible.
408     if (lastChild && mainOffset >= mainExtent + cacheExtent_) {
409         return false;
410     }
411 
412     // The last child become invisible, change the current index.
413     if (mainOffset <= -cacheExtent_) {
414         currentIndex_ = curIndex;
415     }
416 
417     double layoutHead = 0.0;
418     double layoutTail = mainExtent;
419 
420     if (IsRowReverse() || IsColReverse()) {
421         layoutHead = layoutHead - cacheExtent_ + mainOffset;
422         layoutTail = layoutTail + cacheExtent_ + mainOffset;
423     } else {
424         layoutHead = layoutHead - cacheExtent_ - mainOffset;
425         layoutTail = layoutTail + cacheExtent_ - mainOffset;
426     }
427     LayoutChild(curChild, curOffset, layoutHead, layoutTail);
428 
429     return true;
430 }
431 
LayoutChild()432 void RenderMultiChildScroll::LayoutChild()
433 {
434     int32_t childrenSize = static_cast<int32_t>(GetChildren().size());
435     if (currentIndex_ < 0 || currentIndex_ >= childrenSize) {
436         LOGE("invalid current index: %{public}d", currentIndex_);
437         return;
438     }
439 
440     // currentIndex_ is 0 at the beginning.
441     int32_t currentIndex = 0;
442     RefPtr<RenderNode> lastChild;
443     for (const auto& child : GetChildren()) {
444         if (currentIndex >= currentIndex_) {
445             if (!LayoutChild(child, currentIndex, lastChild)) {
446                 LOGE("layout child failed, index:%{public}d", currentIndex);
447                 break;
448             }
449             lastChild = child;
450         }
451         currentIndex++;
452     }
453 }
454 
PerformLayout()455 void RenderMultiChildScroll::PerformLayout()
456 {
457     auto context = context_.Upgrade();
458     if (context && context->GetMinPlatformVersion() < COMPATIBLE_VERSION) {
459         // List Layout Screen Remaining Space
460         viewPort_ = GetLayoutParam().GetMaxSize();
461         SetLayoutSize(viewPort_);
462         if (NearEqual(viewPort_.Width(), Size::INFINITE_SIZE) || NearEqual(viewPort_.Height(), Size::INFINITE_SIZE)) {
463             LOGW("The main or cross size is INFINITE, wait for the determined value");
464             return;
465         }
466     } else {
467         // List determines its own layout size based on children.
468         if (GetLayoutParam().GetMaxSize().IsInfinite()) {
469             ExtendViewPort(); // Extend the view port for layout more items.
470         } else {
471             viewPort_ = GetLayoutParam().GetMaxSize();
472         }
473     }
474 
475     offsetBeforeLayout_ = GetMainOffset(currentOffset_);
476     LayoutChild();
477     CalculateMainScrollExtent();
478     ApplyGradientColor();
479 
480     if (IsReadyToJump()) {
481         if (!NearEqual(initialOffset_, effectOffset_)) {
482             JumpToPosition(initialOffset_);
483             effectOffset_ = initialOffset_;
484             LOGI("Effect initialOffset_:%{public}lf %{public}s", effectOffset_, currentOffset_.ToString().c_str());
485         }
486         if (NearZero(initialOffset_) && initialIndex_ > 0 && initialIndex_ != effectIndex_) {
487             JumpToIndex(initialIndex_);
488             effectIndex_ = initialIndex_;
489             LOGI("Effect initialIndex_:%{public}d %{public}s", effectIndex_, currentOffset_.ToString().c_str());
490         }
491     }
492 
493     if (scrollable_->Available()) {
494         ValidateOffset(SCROLL_FROM_NONE);
495     } else {
496         currentOffset_ = Offset::Zero();
497     }
498 
499     if (!context || context->GetMinPlatformVersion() >= COMPATIBLE_VERSION) {
500         if (GetLayoutParam().GetMaxSize().IsInfinite()) {
501             // If not set the main axis length: wrap content.
502             Rect rect;
503             for (const auto& child : GetChildren()) {
504                 rect.IsValid() ? rect.CombineRect(child->GetPaintRect()) : rect = child->GetPaintRect();
505             }
506             viewPort_ = rect.GetSize();
507         }
508         SetLayoutSize(viewPort_);
509     }
510 }
511 
ExtendViewPort()512 void RenderMultiChildScroll::ExtendViewPort()
513 {
514     if (GreatNotEqual(GetMainSize(GetLayoutSize()), GetMainSize(viewPort_))) {
515         if (axis_ == Axis::HORIZONTAL) {
516             viewPort_.SetWidth(GetLayoutSize().Width() * EXTENT_RATIO);
517         } else {
518             viewPort_.SetHeight(GetLayoutSize().Height() * EXTENT_RATIO);
519         }
520     } else {
521         if (axis_ == Axis::HORIZONTAL) {
522             viewPort_.SetWidth(viewPort_.Width() * EXTENT_RATIO);
523         } else {
524             viewPort_.SetHeight(viewPort_.Height() * EXTENT_RATIO);
525         }
526     }
527 }
528 
HandleCrashTop()529 bool RenderMultiChildScroll::HandleCrashTop()
530 {
531 #ifdef WEARABLE_PRODUCT
532     if (scrollVibrate_ && vibrator_) {
533         vibrator_->Vibrate(VIBRATOR_TYPE_WATCH_CROWN_STRENGTH3);
534     }
535     if (rotationVibrate_ && IsFromRotate() && vibrator_) {
536         vibrator_->Vibrate(VIBRATOR_TYPE_WATCH_CROWN_STRENGTH3);
537     }
538 #endif
539     return RenderScroll::HandleCrashTop();
540 }
HandleCrashBottom()541 bool RenderMultiChildScroll::HandleCrashBottom()
542 {
543 #ifdef WEARABLE_PRODUCT
544     if (scrollVibrate_ && vibrator_) {
545         vibrator_->Vibrate(VIBRATOR_TYPE_WATCH_CROWN_STRENGTH3);
546     }
547     if (rotationVibrate_ && IsFromRotate() && vibrator_) {
548         vibrator_->Vibrate(VIBRATOR_TYPE_WATCH_CROWN_STRENGTH3);
549     }
550 #endif
551     return RenderScroll::HandleCrashBottom();
552 }
553 
IsReadyToJump() const554 bool RenderMultiChildScroll::IsReadyToJump() const
555 {
556     bool ready = false;
557     for (const auto& child : GetChildren()) {
558         auto listBase = AceType::DynamicCast<RenderList>(child);
559         if (!listBase) {
560             continue;
561         }
562         int32_t maxCount = listBase->GetMaxCount();
563         if (listBase->IsPageReady() || (initialIndex_ != 0 && initialIndex_ < maxCount) ||
564             (!NearZero(initialOffset_) && LessNotEqual(initialOffset_, mainScrollExtent_))) {
565             ready = true;
566             break;
567         }
568     }
569     return ready;
570 }
571 
ApplyGradientColor()572 void RenderMultiChildScroll::ApplyGradientColor()
573 {
574     if (scrollable_ && !scrollable_->Available()) {
575         return;
576     }
577 
578     if (!gradientWidth_.IsValid()) {
579         return;
580     }
581 
582     auto box = AceType::DynamicCast<RenderBox>(GetParent().Upgrade());
583     if (!box) {
584         LOGE("parent is not box");
585         return;
586     }
587 
588     // workaround: set needLayout_ to true to make sure box will not mark flex to be layout
589     // and add box to dirty layout set manually at the end.
590     box->SetNeedLayout(true);
591 
592     Dimension widthPx = Dimension(viewPort_.Width());
593     Dimension heightPx = Dimension(viewPort_.Height());
594     Dimension gradientWidthPx = Dimension(NormalizeToPx(gradientWidth_));
595 
596     Dimension mainPx;
597     Gradient gradient = Gradient();
598     if (axis_ == Axis::HORIZONTAL) {
599         mainPx = widthPx;
600         gradient.SetDirection(GradientDirection::RIGHT);
601     } else {
602         mainPx = heightPx;
603         gradient.SetDirection(GradientDirection::BOTTOM);
604     }
605 
606     if (!IsAtTop()) {
607         GradientColor start;
608         start.SetColor(backgroundColor_);
609         start.SetDimension(Dimension(0.0));
610 
611         GradientColor end;
612         Color endColor = backgroundColor_;
613         endColor = endColor.ChangeAlpha(0);
614         end.SetColor(endColor);
615         end.SetDimension(gradientWidthPx);
616 
617         gradient.AddColor(start);
618         gradient.AddColor(end);
619     }
620 
621     if (!IsAtBottom()) {
622         GradientColor start;
623         Color startColor = backgroundColor_;
624         startColor = startColor.ChangeAlpha(0);
625         start.SetColor(startColor);
626         start.SetDimension(mainPx - gradientWidthPx);
627 
628         GradientColor end;
629         end.SetColor(backgroundColor_);
630         end.SetDimension(mainPx);
631 
632         gradient.AddColor(start);
633         gradient.AddColor(end);
634     }
635 
636     auto frontDecoration = box->GetFrontDecoration();
637     if (!frontDecoration) {
638         frontDecoration = AceType::MakeRefPtr<Decoration>();
639     }
640     frontDecoration->SetGradient(gradient);
641     box->SetFrontDecoration(frontDecoration);
642 
643     auto pipelineContext = context_.Upgrade();
644     if (pipelineContext) {
645         pipelineContext->AddDirtyLayoutNode(box);
646     }
647 }
648 
MoveItemToViewPort(double position,double size,double effectOffset)649 void RenderMultiChildScroll::MoveItemToViewPort(double position, double size, double effectOffset)
650 {
651     if (SystemProperties::GetDeviceType() != DeviceType::TV && SystemProperties::GetDeviceType() != DeviceType::PHONE) {
652         return;
653     }
654     double beginPosition = CalculateBeginPositionInViewPort(position, size, effectOffset);
655     if (beginPosition >= 0.0 && mainScrollExtent_ >= GetMainSize(viewPort_)) {
656         beginPosition = std::clamp(beginPosition, 0.0, mainScrollExtent_ - GetMainSize(viewPort_));
657     }
658     ScrollToPosition(beginPosition, SCROLL_FROM_FOCUS_JUMP, false);
659 }
660 
CalculateBeginPositionInViewPort(double position,double size,double effectOffset)661 double RenderMultiChildScroll::CalculateBeginPositionInViewPort(double position, double size, double effectOffset)
662 {
663     double gradientWidth = NormalizeToPx(gradientWidth_);
664     double viewPortSize = GetMainSize(viewPort_);
665     double offset = GetMainOffset(currentOffset_);
666     double viewMin = offset;
667     double viewMax = offset + viewPortSize - size;
668 
669     if (!IsAtTop()) {
670         viewMin = offset + gradientWidth;
671     }
672     if (!IsAtBottom()) {
673         viewMax = offset + viewPortSize - size - gradientWidth;
674     }
675 
676     if (GreatOrEqual(viewMin, viewMax)) {
677         return NONE_SCROLL_POSITION;
678     }
679 
680     if (position < viewMin) {
681         return std::max(position - gradientWidth - effectOffset, 0.0);
682     }
683     if (position > viewMax) {
684         return std::max(position - viewMax + offset + effectOffset, 0.0);
685     }
686     return NONE_SCROLL_POSITION;
687 }
688 
ScrollToEdge(ScrollEdgeType scrollEdgeType,bool smooth)689 void RenderMultiChildScroll::ScrollToEdge(ScrollEdgeType scrollEdgeType, bool smooth)
690 {
691     if (GetChildren().empty()) {
692         LOGE("no list in scroll");
693         return;
694     }
695     auto renderList = AceType::DynamicCast<RenderList>(GetChildren().front());
696     if (!renderList) {
697         LOGE("no list to jump");
698         return;
699     }
700 
701     double position = 0.0;
702     if (scrollEdgeType == ScrollEdgeType::SCROLL_TOP) {
703         position = renderList->CalculateItemPosition(0, ScrollType::SCROLL_TOP);
704     } else {
705         position = renderList->CalculateItemPosition(0, ScrollType::SCROLL_BOTTOM);
706     }
707     if (position < 0.0) {
708         LOGE("Get edge position failed.");
709         return;
710     }
711     CalculateMainScrollExtent();
712     LOGI("Scroll to position:%{public}lf %{public}d", position, smooth);
713     ScrollToPosition(position, SCROLL_FROM_FOCUS_JUMP, smooth);
714 }
715 
ScrollToPosition(double position,int32_t source,bool smooth)716 bool RenderMultiChildScroll::ScrollToPosition(double position, int32_t source, bool smooth)
717 {
718     if (NearEqual(position, NONE_SCROLL_POSITION)) {
719         return false;
720     }
721 
722     double distance = position - GetMainOffset(currentOffset_);
723     if (NearZero(distance)) {
724         return false;
725     }
726     position = std::max(position, 0.0);
727     if (smooth) {
728         ScrollBy(distance, distance, smooth);
729     } else {
730         JumpToPosition(std::max(position, 0.0), source);
731     }
732     return true;
733 }
734 
ScrollPage(bool reverse,bool smooth,const std::function<void ()> & onFinish)735 bool RenderMultiChildScroll::ScrollPage(bool reverse, bool smooth, const std::function<void()>& onFinish)
736 {
737     if (GetChildren().empty()) {
738         LOGE("no list in scroll");
739         return false;
740     }
741     auto renderList = AceType::DynamicCast<RenderList>(GetChildren().front());
742     if (!renderList) {
743         LOGE("no list to jump");
744         return false;
745     }
746     double position = 0.0;
747     if (reverse) {
748         position = renderList->CalculateItemPosition(0, ScrollType::SCROLL_PAGE_UP);
749     } else {
750         position = renderList->CalculateItemPosition(0, ScrollType::SCROLL_PAGE_DOWN);
751     }
752     if (position < 0.0) {
753         LOGE("Get page:%{public}d position failed.", reverse);
754         return false;
755     }
756     CalculateMainScrollExtent();
757     LOGI("Scroll to position:%{public}lf %{public}d", position, smooth);
758     return ScrollToPosition(position, SCROLL_FROM_FOCUS_JUMP, smooth);
759 }
760 
OnRotation(const RotationEvent & event)761 bool RenderMultiChildScroll::OnRotation(const RotationEvent& event)
762 {
763     if (positionController_ && !positionController_->IsScrollNeedRotation()) {
764         LOGE("OnRotation, current indexer is expand");
765         return false;
766     }
767     float rotateValue = event.value; // value of rotation, means pixels(vp)
768     HandleRotate(rotateValue, axis_ == Axis::VERTICAL);
769     return true;
770 }
771 
HandleRotate(double rotateValue,bool isVertical)772 void RenderMultiChildScroll::HandleRotate(double rotateValue, bool isVertical)
773 {
774     auto context = GetContext().Upgrade();
775     if (!context) {
776         LOGE("context is null");
777         return;
778     }
779     auto listBase = AceType::DynamicCast<RenderList>(GetFirstChild());
780     if (!listBase) {
781         LOGE("no rotatable list");
782         return;
783     }
784 
785     if (listBase->GetOnRotateCallback()) {
786         RotationEvent event = {rotateValue};
787         (listBase->GetOnRotateCallback())(event);
788     }
789 
790     double value = context->NormalizeToPx(Dimension(rotateValue, DimensionUnit::VP)) * (-1.0);
791     if (listBase->IsSupportScale()) {
792         value *= LIST_INCONTINUOUS_ROTATION_SENSITYVITY_NORMAL;
793     } else {
794         value *= LIST_CONTINUOUS_ROTATION_SENSITYVITY_NORMAL;
795     }
796     if (listBase->GetSupportItemCenter()) {
797         auto childItem = listBase->GetChildByIndex(listBase->GetCenterIndex());
798         auto centerItem = RenderListItem::GetRenderListItem(childItem);
799         if (centerItem) {
800             accumulatedRotationValue_ += value;
801             Size itemSize = centerItem->GetLayoutSize();
802             double threshold = 0.0;
803             if (isVertical) {
804                 threshold = itemSize.Height() * LIST_ITEMCENTER_ROTATION_THRESHOLD;
805             } else {
806                 threshold = itemSize.Width() * LIST_ITEMCENTER_ROTATION_THRESHOLD;
807             }
808             if (InRegion(-threshold, threshold, accumulatedRotationValue_)) {
809                 return;
810             }
811             value = accumulatedRotationValue_;
812             accumulatedRotationValue_ = 0.0;
813         }
814 
815         double destPosition = -GetMainOffset(currentOffset_) - value;
816         double fixPosition = GetFixPositionOnWatch(destPosition, -GetMainOffset(currentOffset_));
817         value -= fixPosition - destPosition;
818         AnimateTo(GetCurrentPosition() + value, ANIMATION_DURATION, Curves::FRICTION);
819     } else {
820         // Vertical or horizontal, different axis
821         Offset delta;
822         if (isVertical) {
823             delta.SetX(0.0);
824             delta.SetY(value);
825         } else {
826             delta.SetX(value);
827             delta.SetY(0.0);
828         }
829         UpdateOffset(delta, SCROLL_FROM_ROTATE);
830     }
831 }
832 
833 // notify start position in global main axis
NotifyDragStart(double startPosition)834 void RenderMultiChildScroll::NotifyDragStart(double startPosition)
835 {
836     for (const auto& child : GetChildren()) {
837         auto listBase = AceType::DynamicCast<RenderList>(child);
838         if (!listBase) {
839             continue;
840         }
841         listBase->NotifyDragStart(startPosition);
842     }
843 }
844 
845 // notify drag offset in global main axis
NotifyDragUpdate(double dragOffset,int32_t source)846 void RenderMultiChildScroll::NotifyDragUpdate(double dragOffset, int32_t source)
847 {
848     for (const auto& child : GetChildren()) {
849         auto listBase = AceType::DynamicCast<RenderList>(child);
850         if (!listBase) {
851             continue;
852         }
853         listBase->NotifyDragUpdate(dragOffset);
854         // switch chain control node in flush chain animation
855         double delta = listBase->FlushChainAnimation();
856         // fix currentOffset_ after switch control node.
857         if (axis_ == Axis::HORIZONTAL) {
858             currentOffset_ += Offset(-delta, 0.0);
859         } else {
860             currentOffset_ += Offset(0.0, -delta);
861         }
862     }
863 }
864 
ProcessScrollOverCallback(double velocity)865 void RenderMultiChildScroll::ProcessScrollOverCallback(double velocity)
866 {
867     for (const auto& child : GetChildren()) {
868         auto listBase = AceType::DynamicCast<RenderList>(child);
869         if (!listBase) {
870             continue;
871         }
872         // switch chain control node when notify scroll over
873         listBase->NotifyScrollOver(velocity, IsOutOfTopBoundary(), IsOutOfBottomBoundary());
874         double delta = listBase->FlushChainAnimation();
875         // fix currentOffset_ after switch control node.
876         if (axis_ == Axis::HORIZONTAL) {
877             currentOffset_ += Offset(-delta, 0.0);
878         } else {
879             currentOffset_ += Offset(0.0, -delta);
880         }
881     }
882 }
883 
GetMainScrollExtent() const884 double RenderMultiChildScroll::GetMainScrollExtent() const
885 {
886     const double mainScrollExtent = RenderScroll::GetMainScrollExtent();
887     auto child = GetFirstChild();
888     auto listBase = AceType::DynamicCast<RenderList>(child);
889     if (listBase) {
890         return mainScrollExtent + listBase->GetTailAnimationValue() + listBase->GetHeadAnimationValue();
891     } else {
892         return mainScrollExtent;
893     }
894 }
895 
GetFixPositionOnWatch(double destination,double current)896 double RenderMultiChildScroll::GetFixPositionOnWatch(double destination, double current)
897 {
898     auto listBase = AceType::DynamicCast<RenderList>(GetLastChild());
899     if (!listBase) {
900         return destination;
901     }
902 
903     // find centerIndex
904     int32_t centerIndex = -1;
905     double itemSize = 0.0;
906     double itemPosition = 0.0;
907     double listPosition = GetMainOffset(currentOffset_) - destination + current;
908     if (GreatNotEqual(destination, current)) {
909         // scroll to top direction
910         centerIndex = listBase->EstimateIndexByPosition(listPosition + GetMainSize(viewPort_) * HALF_ITEM_SIZE);
911         itemPosition = listBase->GetItemPosition(centerIndex);
912         itemSize = listBase->GetItemPosition(centerIndex + 1) - itemPosition;
913     } else {
914         // scroll to bottom direction
915         listBase->CalculateItemPosition(listPosition);
916         double centerPosition = listPosition + GetMainSize(viewPort_) * HALF_ITEM_SIZE;
917         for (const auto &itemPair : listBase->GetItems()) {
918             int32_t index = itemPair.first;
919             auto item = RenderListItem::GetRenderListItem(itemPair.second);
920             if (!item) {
921                 LOGW("get render list item is null");
922                 continue;
923             }
924             double start = listBase->GetItemPosition(index);
925             double end = start + listBase->GetMainSize(item->GetLayoutSize());
926             if (start < centerPosition && end > centerPosition) {
927                 centerIndex = index;
928                 itemSize = GetMainSize(item->GetLayoutSize());
929                 itemPosition = item->GetPositionInList();
930                 break;
931             }
932         }
933     }
934     if (centerIndex == -1) {
935         LOGW("invalid center index");
936         return destination;
937     }
938 
939     // calculate destination position after fix
940     double center = listPosition + GetMainSize(viewPort_) * HALF_ITEM_SIZE;
941     double itemCenterPosition = itemPosition + itemSize * HALF_ITEM_SIZE;
942     return destination + center - itemCenterPosition;
943 }
944 
IsOutOfBottomBoundary()945 bool RenderMultiChildScroll::IsOutOfBottomBoundary()
946 {
947     double headOffset = GetMainOffset(currentOffset_);
948     double tailOffset = mainScrollExtent_;
949     auto child = GetLastChild();
950     auto listBase = AceType::DynamicCast<RenderList>(child);
951     if (listBase) {
952         tailOffset = mainScrollExtent_ + listBase->GetTailAnimationValue();
953     }
954     if (IsRowReverse() || IsColReverse()) {
955         return headOffset <= (GetMainSize(viewPort_) - tailOffset) && ReachMaxCount();
956     } else {
957         return headOffset >= (tailOffset - GetMainSize(viewPort_)) && ReachMaxCount();
958     }
959 }
960 
IsOutOfTopBoundary()961 bool RenderMultiChildScroll::IsOutOfTopBoundary()
962 {
963     double headOffset = GetMainOffset(currentOffset_);
964     auto child = GetFirstChild();
965     auto listBase = AceType::DynamicCast<RenderList>(child);
966     if (listBase) {
967         headOffset = GetMainOffset(currentOffset_) - listBase->GetHeadAnimationValue();
968     }
969     if (IsRowReverse() || IsColReverse()) {
970         return headOffset >= 0.0;
971     } else {
972         return headOffset <= 0.0;
973     }
974 }
975 
976 } // namespace OHOS::Ace
977