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_scroll.h"
17 
18 #include "core/pipeline/base/composed_element.h"
19 
20 namespace OHOS::Ace {
21 namespace {
22 
23 constexpr int32_t SCROLL_NONE = 0;
24 constexpr int32_t SCROLL_TOUCH_DOWN = 1;
25 constexpr int32_t SCROLL_TOUCH_UP = 2;
26 constexpr double SCROLL_RATIO = 0.52;
27 constexpr float SCROLL_BY_SPEED = 250.0f; // move 250 pixels per second
28 constexpr double UNIT_CONVERT = 1000.0;   // 1s convert to 1000ms
29 constexpr double ROTATE_FACTOR = -1.0;    // pixels factor per angle
30 
31 } // namespace
32 
RenderScroll()33 RenderScroll::RenderScroll() : RenderNode(true)
34 {
35     Initialize();
36 }
37 
~RenderScroll()38 RenderScroll::~RenderScroll()
39 {
40     if (scrollBarProxy_) {
41         scrollBarProxy_->UnRegisterScrollableNode(AceType::WeakClaim(this));
42     }
43 }
44 
Initialize()45 void RenderScroll::Initialize()
46 {
47     touchRecognizer_ = AceType::MakeRefPtr<RawRecognizer>();
48     touchRecognizer_->SetOnTouchCancel([weakItem = AceType::WeakClaim(this)](const TouchEventInfo&) {
49         auto item = weakItem.Upgrade();
50         if (!item) {
51             return;
52         }
53         // check out of boundary
54         if (!item->IsOutOfBoundary()) {
55             return;
56         }
57         auto scrollEffect = item->scrollEffect_;
58         if (scrollEffect) {
59             scrollEffect->ProcessScrollOver(0.0);
60         }
61     });
62 }
63 
ValidateOffset(int32_t source)64 bool RenderScroll::ValidateOffset(int32_t source)
65 {
66     if (mainScrollExtent_ <= GetMainSize(viewPort_)) {
67         return true;
68     }
69 
70     outBoundaryExtent_ = 0.0;
71     scrollBarOutBoundaryExtent_ = 0.0;
72 
73     // restrict position between top and bottom
74     if (!scrollEffect_ || scrollEffect_->IsRestrictBoundary() || source == SCROLL_FROM_JUMP ||
75         source == SCROLL_FROM_BAR || source == SCROLL_FROM_ROTATE || refreshParent_.Upgrade()) {
76         if (axis_ == Axis::HORIZONTAL) {
77             currentOffset_.SetX(std::clamp(currentOffset_.GetX(), 0.0, mainScrollExtent_ - viewPort_.Width()));
78         } else {
79             // Refresh support spring when pulling up.
80 #ifdef WEARABLE_PRODUCT
81             if (refreshParent_.Upgrade() &&
82                 GetMainOffset(currentOffset_) >= (mainScrollExtent_ - GetMainSize(viewPort_)) && ReachMaxCount()) {
83                 scrollBarOutBoundaryExtent_ = GetMainOffset(currentOffset_) -
84                     (mainScrollExtent_ - GetMainSize(viewPort_));
85             } else {
86                 currentOffset_.SetY(std::clamp(currentOffset_.GetY(), 0.0, mainScrollExtent_ - viewPort_.Height()));
87             }
88 #else
89             currentOffset_.SetY(std::clamp(currentOffset_.GetY(), 0.0, mainScrollExtent_ - viewPort_.Height()));
90 #endif
91         }
92     } else {
93         if (GetMainOffset(currentOffset_) < 0) {
94             outBoundaryExtent_ = -GetMainOffset(currentOffset_);
95             scrollBarOutBoundaryExtent_ = -GetMainOffset(currentOffset_);
96         } else if (GetMainOffset(currentOffset_) >= (mainScrollExtent_ - GetMainSize(viewPort_)) &&
97             ReachMaxCount()) {
98             scrollBarOutBoundaryExtent_ =
99             GetMainOffset(currentOffset_) - (mainScrollExtent_ - GetMainSize(viewPort_));
100         }
101         HandleScrollBarOutBoundary();
102     }
103 
104     axis_ == Axis::HORIZONTAL ? currentOffset_.SetY(0.0) : currentOffset_.SetX(0.0);
105     return true;
106 }
107 
HandleScrollBarOutBoundary()108 void RenderScroll::HandleScrollBarOutBoundary()
109 {
110     if (scrollBar_ && scrollBar_->NeedScrollBar()) {
111         scrollBar_->SetOutBoundary(std::abs(scrollBarOutBoundaryExtent_));
112     }
113 }
114 
HandleScrollPosition(double scrollX,double scrollY,int32_t scrollState) const115 void RenderScroll::HandleScrollPosition(double scrollX, double scrollY, int32_t scrollState) const
116 {
117     if (!positionController_) {
118         LOGW("positionController is null");
119         return;
120     }
121 
122     if (GetMainOffset(currentOffset_) > 0.0 &&
123         GetMainOffset(currentOffset_) < mainScrollExtent_ - GetMainSize(viewPort_)) {
124         positionController_->SetMiddle();
125     }
126 
127     positionController_->HandleScrollEvent(
128         std::make_shared<ScrollEventInfo>(ScrollEvent::SCROLL_POSITION, scrollX, scrollY, scrollState));
129 }
130 
HandleCrashBottom()131 bool RenderScroll::HandleCrashBottom()
132 {
133     if (positionController_) {
134         positionController_->SetBottom();
135         positionController_->HandleScrollEvent(
136             std::make_shared<ScrollEventInfo>(ScrollEvent::SCROLL_BOTTOM, 0.0, 0.0, -1));
137     }
138     if (axis_ == Axis::HORIZONTAL) {
139         OnReachEnd();
140     } else {
141         OnReachBottom();
142     }
143     return false;
144 }
145 
HandleCrashTop()146 bool RenderScroll::HandleCrashTop()
147 {
148     if (positionController_) {
149         positionController_->SetTop();
150         positionController_->HandleScrollEvent(
151             std::make_shared<ScrollEventInfo>(ScrollEvent::SCROLL_TOP, 0.0, 0.0, -1));
152     }
153     if (axis_ == Axis::HORIZONTAL) {
154         OnReachStart();
155     } else {
156         OnReachTop();
157     }
158     return false;
159 }
160 
UpdateOffset(Offset & delta,int32_t source)161 bool RenderScroll::UpdateOffset(Offset& delta, int32_t source)
162 {
163     if (source == SCROLL_FROM_ROTATE) {
164         isFromRotate_ = true;
165     } else {
166         isFromRotate_ = false;
167     }
168     if (!scrollable_->Available()) {
169         return false;
170     }
171     if (delta.IsZero()) {
172         return false;
173     }
174     if (IsAtTop() && HandleRefreshEffect(-delta.GetY(), source, currentOffset_.GetY())) {
175         return true;
176     }
177     if ((IsAtBottom() && GetMainOffset(delta) > 0.0) || (IsAtTop() && GetMainOffset(delta) < 0.0)) {
178         if (!scrollEffect_) {
179             return false;
180         }
181         if (scrollEffect_->IsNoneEffect()) {
182             return false;
183         }
184     }
185     if (!ScrollPageCheck(delta, source)) {
186         return false;
187     }
188     if (GetMainOffset(currentOffset_) <= 0.0) {
189         if (scrollable_->RelatedScrollEventDoing(delta)) {
190             return false;
191         }
192     }
193     if (scrollBar_ && scrollBar_->NeedScrollBar()) {
194         scrollBar_->SetOutBoundary(std::abs(scrollBarOutBoundaryExtent_));
195         scrollBar_->SetActive(SCROLL_FROM_CHILD != source);
196     }
197     currentOffset_ += delta;
198     currentDeltaInMain_ += GetMainOffset(delta);
199     // handle edge effect
200     HandleScrollEffect();
201 
202     if (!ValidateOffset(source)) {
203         currentOffset_ = currentOffset_ - delta;
204         return false;
205     }
206 
207     bool next = true;
208     Offset correctedDelta = currentOffset_ - lastOffset_;
209     if (!correctedDelta.IsZero()) {
210         int32_t touchState = SCROLL_NONE;
211         if (source == SCROLL_FROM_UPDATE) {
212             touchState = SCROLL_TOUCH_DOWN;
213         } else if (source == SCROLL_FROM_ANIMATION || source == SCROLL_FROM_ANIMATION_SPRING) {
214             touchState = SCROLL_TOUCH_UP;
215         }
216         HandleScrollPosition(correctedDelta.GetX(), correctedDelta.GetY(), touchState);
217 
218         if (IsCrashTop()) {
219             // scroll to top
220             next = HandleCrashTop();
221         } else if (IsCrashBottom()) {
222             // scroll to bottom
223             next = HandleCrashBottom();
224         }
225         if (scrollEffect_ && !scrollEffect_->IsRestrictBoundary()) {
226             next = true;
227             MarkNeedPredictLayout();
228         }
229     } else {
230         next = false;
231     }
232 
233     lastOffset_ = currentOffset_;
234     currentBottomOffset_ = axis_ == Axis::VERTICAL ? currentOffset_ + Offset(0.0, viewPort_.Height())
235                                                    : currentOffset_ + Offset(viewPort_.Width(), 0.0);
236     correctedDelta_ = correctedDelta;
237     MarkNeedLayout(true);
238     return next;
239 }
240 
HandleScrollEffect()241 void RenderScroll::HandleScrollEffect()
242 {
243     // handle edge effect
244     if (scrollEffect_) {
245         overScroll_ = scrollEffect_->CalculateOverScroll(GetMainOffset(lastOffset_), ReachMaxCount());
246         if (!NearZero(overScroll_)) {
247             scrollEffect_->HandleOverScroll(axis_, overScroll_, viewPort_);
248         }
249     }
250 }
251 
IsCrashTop()252 bool RenderScroll::IsCrashTop()
253 {
254     double current = GetMainOffset(currentOffset_);
255     double last = GetMainOffset(lastOffset_);
256     bool scrollUpToReachTop = GreatNotEqual(last, 0.0) && LessOrEqual(current, 0.0);
257     bool scrollDownToReachTop = LessNotEqual(last, 0.0) && GreatOrEqual(current, 0.0);
258     return scrollUpToReachTop || scrollDownToReachTop;
259 }
260 
IsCrashBottom()261 bool RenderScroll::IsCrashBottom()
262 {
263     double maxExtent = mainScrollExtent_ - GetMainSize(viewPort_);
264     double current = GetMainOffset(currentOffset_);
265     double last = GetMainOffset(lastOffset_);
266     bool scrollDownToReachEnd = LessNotEqual(last, maxExtent) && GreatOrEqual(current, maxExtent);
267     bool scrollUpToReachEnd = GreatNotEqual(last, maxExtent) && LessOrEqual(current, maxExtent);
268     return (scrollUpToReachEnd || scrollDownToReachEnd) && ReachMaxCount();
269 }
270 
CanScrollVertically(const Offset & delta)271 bool RenderScroll::CanScrollVertically(const Offset& delta)
272 {
273     if (axis_ != Axis::VERTICAL) {
274         return false;
275     }
276 
277     if (delta.IsZero()) {
278         return false;
279     }
280     Offset currentOffset = currentOffset_ + delta;
281 
282     if (currentOffset.GetY() < 0.0) {
283         currentOffset.SetY(0.0);
284     } else if (currentOffset.GetY() > mainScrollExtent_ - viewPort_.Height()) {
285         currentOffset.SetY(mainScrollExtent_ - viewPort_.Height());
286     }
287 
288     if (mainScrollExtent_ <= GetMainSize(viewPort_)) {
289         return false;
290     }
291 
292     bool next = true;
293     Offset correctedDelta = currentOffset - lastOffset_;
294     if (!correctedDelta.IsZero()) {
295         double mainOffset = GetMainOffset(currentOffset);
296         if (NearZero(mainOffset)) {
297             // scroll to top
298             next = false;
299         } else if (NearEqual(mainOffset, mainScrollExtent_ - GetMainSize(viewPort_)) && ReachMaxCount()) {
300             // scroll to bottom
301             next = false;
302         }
303     } else {
304         next = false;
305     }
306     return next;
307 }
308 
ScrollPageCheck(Offset & delta,int32_t source)309 bool RenderScroll::ScrollPageCheck(Offset& delta, int32_t source)
310 {
311     if (source == SCROLL_FROM_ANIMATION_SPRING || source == SCROLL_FROM_BAR) {
312         return true;
313     }
314     if (axis_ != Axis::VERTICAL) {
315         return true;
316     }
317     if (scrollPage_) {
318         if (delta.GetY() > 0.0) {
319             // scroll up
320             bool selfCanScroll = RenderNode::ScrollPageByChild(delta, SCROLL_FROM_CHILD);
321             if (!selfCanScroll) {
322                 return false;
323             }
324         } else {
325             // scroll down
326             if (!CanScrollVertically(delta)) {
327                 bool selfCanScroll = RenderNode::ScrollPageByChild(delta, SCROLL_FROM_CHILD);
328                 if (!selfCanScroll) {
329                     return false;
330                 }
331             }
332         }
333     }
334     return true;
335 }
336 
ScrollPageByChild(Offset & delta,int32_t source)337 bool RenderScroll::ScrollPageByChild(Offset& delta, int32_t source)
338 {
339     // scroll up
340     if (delta.GetY() > 0.0) {
341         bool selfCanScroll = RenderNode::ScrollPageByChild(delta, source);
342         if (selfCanScroll) {
343             AdjustOffset(delta, source);
344             return !UpdateOffset(delta, source);
345         }
346         return false;
347     } else {
348         // scroll down
349         AdjustOffset(delta, source);
350         if (UpdateOffset(delta, source)) {
351             return false;
352         } else {
353             return RenderNode::ScrollPageByChild(delta, source);
354         }
355     }
356 }
357 
IsOutOfBottomBoundary()358 bool RenderScroll::IsOutOfBottomBoundary()
359 {
360     return GreatOrEqual(GetMainOffset(currentOffset_), (mainScrollExtent_ - GetMainSize(viewPort_))) &&
361                        ReachMaxCount();
362 }
363 
IsOutOfTopBoundary()364 bool RenderScroll::IsOutOfTopBoundary()
365 {
366     return LessOrEqual(GetMainOffset(currentOffset_), 0.0);
367 }
368 
IsOutOfBoundary()369 bool RenderScroll::IsOutOfBoundary()
370 {
371     return (IsOutOfTopBoundary() || IsOutOfBottomBoundary());
372 }
373 
AdjustOffset(Offset & delta,int32_t source)374 void RenderScroll::AdjustOffset(Offset& delta, int32_t source)
375 {
376     if (delta.IsZero() || source == SCROLL_FROM_ANIMATION || source == SCROLL_FROM_ANIMATION_SPRING) {
377         return;
378     }
379 
380     double viewPortSize = GetMainSize(viewPort_);
381     double offset = GetMainOffset(delta);
382     if (NearZero(viewPortSize) || NearZero(offset)) {
383         return;
384     }
385 
386     double maxScrollExtent = mainScrollExtent_ - viewPortSize;
387     double overscrollPastStart = 0.0;
388     double overscrollPastEnd = 0.0;
389     double overscrollPast = 0.0;
390     bool easing = false;
391     overscrollPastStart = std::max(-GetCurrentPosition(), 0.0);
392     overscrollPastEnd = std::max(GetCurrentPosition() - maxScrollExtent, 0.0);
393     // do not adjust offset if direction oppsite from the overScroll direction when out of boundary
394     if ((overscrollPastStart > 0.0 && offset > 0.0) || (overscrollPastEnd > 0.0 && offset < 0.0)) {
395         return;
396     }
397     easing = (overscrollPastStart > 0.0 && offset < 0.0) || (overscrollPastEnd > 0.0 && offset > 0.0);
398     overscrollPast = std::max(overscrollPastStart, overscrollPastEnd);
399     double friction = easing ? CalculateFriction((overscrollPast - std::abs(offset)) / viewPortSize)
400                              : CalculateFriction(overscrollPast / viewPortSize);
401     double direction = offset / std::abs(offset);
402     offset = direction * CalculateOffsetByFriction(overscrollPast, std::abs(offset), friction);
403     axis_ == Axis::VERTICAL ? delta.SetY(offset) : delta.SetX(offset);
404 }
405 
CalculateFriction(double gamma)406 double RenderScroll::CalculateFriction(double gamma)
407 {
408     return SCROLL_RATIO * std::pow(1.0 - gamma, SQUARE);
409 }
410 
CalculateOffsetByFriction(double extentOffset,double delta,double friction)411 double RenderScroll::CalculateOffsetByFriction(double extentOffset, double delta, double friction)
412 {
413     double offset = 0.0;
414     if (extentOffset > 0.0 && !NearZero(friction)) {
415         double deltaToLimit = extentOffset / friction;
416         if (delta < deltaToLimit) {
417             return delta * friction;
418         }
419         offset += extentOffset;
420         delta -= deltaToLimit;
421     }
422     return offset + delta;
423 }
424 
ResetEdgeEffect()425 void RenderScroll::ResetEdgeEffect()
426 {
427     if (scrollEffect_) {
428         scrollEffect_->SetCurrentPositionCallback([weakScroll = AceType::WeakClaim(this)]() {
429             auto scroll = weakScroll.Upgrade();
430             if (scroll) {
431                 return -scroll->GetCurrentPosition();
432             }
433             return 0.0;
434         });
435         scrollEffect_->SetLeadingCallback([weakScroll = AceType::WeakClaim(this)]() {
436             auto scroll = weakScroll.Upgrade();
437             if (scroll) {
438                 if (!scroll->IsRowReverse() && !scroll->IsColReverse()) {
439                     return scroll->GetMainSize(scroll->viewPort_) - scroll->mainScrollExtent_;
440                 }
441             }
442             return 0.0;
443         });
444         scrollEffect_->SetTrailingCallback([weakScroll = AceType::WeakClaim(this)]() {
445             auto scroll = weakScroll.Upgrade();
446             if (scroll) {
447                 if (scroll->IsRowReverse() || scroll->IsColReverse()) {
448                     return scroll->mainScrollExtent_ - scroll->GetMainSize(scroll->viewPort_);
449                 }
450             }
451             return 0.0;
452         });
453         scrollEffect_->SetInitLeadingCallback([weakScroll = AceType::WeakClaim(this)]() {
454             auto scroll = weakScroll.Upgrade();
455             if (scroll) {
456                 if (!scroll->IsRowReverse() && !scroll->IsColReverse()) {
457                     return scroll->GetMainSize(scroll->viewPort_) - scroll->GetMainScrollExtent();
458                 }
459             }
460             return 0.0;
461         });
462         scrollEffect_->SetInitTrailingCallback([weakScroll = AceType::WeakClaim(this)]() {
463             auto scroll = weakScroll.Upgrade();
464             if (scroll) {
465                 if (scroll->IsRowReverse() || scroll->IsColReverse()) {
466                     return scroll->GetMainScrollExtent() - scroll->GetMainSize(scroll->viewPort_);
467                 }
468             }
469             return 0.0;
470         });
471         scrollEffect_->SetScrollNode(AceType::WeakClaim(this));
472 
473         SetEdgeEffectAttribute();
474         scrollEffect_->InitialEdgeEffect();
475     }
476 }
477 
ResetScrollEventCallBack()478 void RenderScroll::ResetScrollEventCallBack()
479 {
480     scrollable_->SetScrollEndCallback([weakScroll = AceType::WeakClaim(this)]() {
481         auto scroll = weakScroll.Upgrade();
482         if (scroll) {
483             if (scroll->positionController_) {
484                 scroll->positionController_->HandleScrollEvent(
485                     std::make_shared<ScrollEventInfo>(ScrollEvent::SCROLL_END, 0.0, 0.0, -1));
486             }
487             // Send scroll none when scroll is end.
488             auto context = scroll->GetContext().Upgrade();
489             if (!(context && context->GetIsDeclarative())) {
490                 scroll->HandleScrollPosition(0.0, 0.0, SCROLL_NONE);
491             }
492             scroll->HandleScrollBarEnd();
493 
494             auto proxy = scroll->scrollBarProxy_;
495             if (proxy) {
496                 proxy->StartScrollBarAnimator();
497             }
498         }
499     });
500     scrollable_->SetScrollTouchUpCallback([weakScroll = AceType::WeakClaim(this)]() {
501         auto scroll = weakScroll.Upgrade();
502         if (scroll && scroll->positionController_) {
503             scroll->positionController_->HandleScrollEvent(
504                 std::make_shared<ScrollEventInfo>(ScrollEvent::SCROLL_TOUCHUP, 0.0, 0.0, -1));
505         }
506     });
507     if (positionController_) {
508         positionController_->SetMiddle();
509         double mainOffset = GetMainOffset(currentOffset_);
510         // No need to set bottom, because if scrollable, it must not be at the bottom.
511         if (NearZero(mainOffset)) {
512             positionController_->SetTop();
513         }
514     }
515 }
516 
InitScrollBar(const RefPtr<ScrollBar> & scrollBar)517 void RenderScroll::InitScrollBar(const RefPtr<ScrollBar>& scrollBar)
518 {
519     if (scrollBar_ == scrollBar) {
520         return;
521     }
522 
523     scrollBar_ = scrollBar;
524     if (!scrollBar_) {
525         scrollBar_ = AceType::MakeRefPtr<ScrollBar>(DisplayMode::OFF);
526     }
527     if (axis_ == Axis::HORIZONTAL) {
528         scrollBar_->SetPositionMode(PositionMode::BOTTOM);
529     }
530     if (axis_ == Axis::VERTICAL) {
531         if (rightToLeft_) {
532             scrollBar_->SetPositionMode(PositionMode::LEFT);
533         }
534     }
535     scrollBar_->InitScrollBar(AceType::WeakClaim(this), GetContext());
536     SetBarCallBack(axis_ == Axis::VERTICAL);
537 }
538 
ResetScrollable()539 void RenderScroll::ResetScrollable()
540 {
541     const auto isVertical = (axis_ == Axis::VERTICAL);
542     auto&& callback = [weakScroll = AceType::WeakClaim(this), isVertical](double value, int32_t source) {
543         auto scroll = weakScroll.Upgrade();
544         if (!scroll) {
545             LOGE("render scroll is released");
546             return false;
547         }
548         if (source == SCROLL_FROM_START) {
549             scroll->NotifyDragStart(value);
550             scroll->currentDeltaInMain_ = 0.0;
551             return true;
552         }
553         Offset delta;
554         if (isVertical) {
555             delta.SetX(0.0);
556             delta.SetY(-value);
557         } else {
558             delta.SetX(-value);
559             delta.SetY(0.0);
560         }
561         scroll->AdjustOffset(delta, source);
562         scroll->NotifyDragUpdate(scroll->GetMainOffset(delta), source);
563 
564         // Stop animator of scroll bar.
565         auto scrollBarProxy = scroll->scrollBarProxy_;
566         if (scrollBarProxy) {
567             scrollBarProxy->StopScrollBarAnimator();
568         }
569         return scroll->UpdateOffset(delta, source);
570     };
571     // Initializes scrollable with different direction.
572     if (scrollable_) {
573         scrollable_->SetAxis(axis_ == Axis::VERTICAL ? Axis::VERTICAL : Axis::HORIZONTAL);
574         scrollable_->SetCallback(callback);
575     } else {
576         if (isVertical) {
577             scrollable_ = AceType::MakeRefPtr<Scrollable>(callback, Axis::VERTICAL);
578             scrollable_->InitRelatedParent(GetParent());
579         } else {
580             scrollable_ = AceType::MakeRefPtr<Scrollable>(callback, Axis::HORIZONTAL);
581         }
582         scrollable_->SetScrollableNode(AceType::WeakClaim(this));
583     }
584     scrollable_->SetOnScrollBegin(onScrollBegin_);
585     scrollable_->SetNotifyScrollOverCallBack([weak = AceType::WeakClaim(this)](double velocity) {
586         auto scroll = weak.Upgrade();
587         if (!scroll) {
588             return;
589         }
590         scroll->ProcessScrollOverCallback(velocity);
591     });
592     scrollable_->SetWatchFixCallback([weak = AceType::WeakClaim(this)](double final, double current) {
593         auto scroll = weak.Upgrade();
594         if (scroll) {
595             return scroll->GetFixPositionOnWatch(final, current);
596         }
597         return final;
598     });
599     scrollable_->Initialize(GetContext());
600     scrollable_->SetNodeId(GetAccessibilityNodeId());
601     scrollable_->SetScrollEnd([weakScroll = AceType::WeakClaim(this)]() {
602         auto scroll = weakScroll.Upgrade();
603         if (scroll) {
604             scroll->MarkNeedLayout(true);
605         }
606     });
607     InitializeScrollable(scrollable_);
608 
609     currentBottomOffset_ = Offset::Zero();
610     currentOffset_ = Offset::Zero();
611     lastOffset_ = Offset::Zero();
612     ResetScrollEventCallBack();
613     SetEdgeEffectAttribute();
614 }
615 
SetEdgeEffectAttribute()616 void RenderScroll::SetEdgeEffectAttribute()
617 {
618     if (scrollEffect_ && scrollable_) {
619         scrollEffect_->SetScrollable(scrollable_);
620         scrollEffect_->RegisterSpringCallback();
621         if (scrollEffect_->IsSpringEffect()) {
622             scrollable_->SetOutBoundaryCallback([weakScroll = AceType::WeakClaim(this)]() {
623                 auto scroll = weakScroll.Upgrade();
624                 if (scroll) {
625                     return scroll->IsOutOfBoundary();
626                 }
627                 return false;
628             });
629         }
630     }
631 }
632 
OnTouchTestHit(const Offset & coordinateOffset,const TouchRestrict & touchRestrict,TouchTestResult & result)633 void RenderScroll::OnTouchTestHit(
634     const Offset& coordinateOffset, const TouchRestrict& touchRestrict, TouchTestResult& result)
635 {
636     if (!GetVisible() || axis_ == Axis::NONE) {
637         return;
638     }
639     if (!scrollable_) {
640         return;
641     }
642     if (scrollable_->Available() && scrollBar_ && scrollBar_->InBarRegion(globalPoint_ - coordinateOffset)) {
643         scrollBar_->AddScrollBarController(coordinateOffset, result);
644     } else {
645         scrollable_->SetCoordinateOffset(coordinateOffset);
646         auto newTouchRestrict = touchRestrict;
647         AdjustTouchRestrict(newTouchRestrict);
648         scrollable_->SetDragTouchRestrict(newTouchRestrict);
649         result.emplace_back(scrollable_);
650     }
651     touchRecognizer_->SetCoordinateOffset(coordinateOffset);
652     result.emplace_back(touchRecognizer_);
653 }
654 
HandleMouseHoverEvent(const MouseState mouseState)655 void RenderScroll::HandleMouseHoverEvent(const MouseState mouseState)
656 {
657     LOGI("scroll hover state: %{public}d", mouseState);
658     if (scrollBar_ && mouseState == MouseState::NONE) {
659         scrollBar_->SetIsHover(false);
660     }
661 }
662 
HandleMouseEvent(const MouseEvent & event)663 bool RenderScroll::HandleMouseEvent(const MouseEvent& event)
664 {
665     if (!scrollBar_) {
666         return RenderNode::HandleMouseEvent(event);
667     }
668     auto globalOffset = GetGlobalOffset();
669     auto localPoint = Point(event.x - globalOffset.GetX(), event.y - globalOffset.GetY());
670     bool isInBarRegion = scrollBar_->InBarRegion(localPoint);
671     scrollBar_->SetIsHover(isInBarRegion);
672     return isInBarRegion;
673 }
674 
JumpToIndex(int32_t index)675 void RenderScroll::JumpToIndex(int32_t index)
676 {
677     LOGE("Do not support in base RenderScroll");
678 }
679 
ScrollToEdge(ScrollEdgeType scrollEdgeType,bool smooth)680 void RenderScroll::ScrollToEdge(ScrollEdgeType scrollEdgeType, bool smooth)
681 {
682     if ((IsRowReverse() && scrollEdgeType == ScrollEdgeType::SCROLL_BOTTOM) ||
683         (!IsRowReverse() && scrollEdgeType == ScrollEdgeType::SCROLL_TOP)) {
684         double distance = -GetMainOffset(currentOffset_);
685         ScrollBy(distance, distance, smooth);
686     } else {
687         double distance = mainScrollExtent_ - GetMainOffset(currentOffset_);
688         ScrollBy(distance, distance, smooth);
689     }
690 }
691 
ScrollPage(bool reverse,bool smooth,const std::function<void ()> & onFinish)692 bool RenderScroll::ScrollPage(bool reverse, bool smooth, const std::function<void()>& onFinish)
693 {
694     if ((IsRowReverse() && !reverse) || (!IsRowReverse() && reverse)) {
695         double distance = -GetMainSize(viewPort_);
696         ScrollBy(distance, distance, smooth, onFinish);
697     } else {
698         double distance = GetMainSize(viewPort_);
699         ScrollBy(distance, distance, smooth, onFinish);
700     }
701     return true;
702 }
703 
JumpToPosition(double position,int32_t source)704 void RenderScroll::JumpToPosition(double position, int32_t source)
705 {
706     // If an animation is playing, stop it.
707     if (animator_) {
708         if (!animator_->IsStopped()) {
709             animator_->Stop();
710         }
711         animator_->ClearInterpolators();
712     }
713     DoJump(position, source);
714     HandleScrollBarEnd();
715 }
716 
DoJump(double position,int32_t source)717 void RenderScroll::DoJump(double position, int32_t source)
718 {
719     if (!NearEqual(GetCurrentPosition(), position)) {
720         Offset delta;
721         if (axis_ == Axis::VERTICAL) {
722             delta.SetY(position - GetMainOffset(currentOffset_));
723         } else {
724             delta.SetX(position - GetMainOffset(currentOffset_));
725         }
726 
727         UpdateOffset(delta, source);
728     }
729 }
730 
AnimateTo(double position,float duration,const RefPtr<Curve> & curve,bool limitDuration,const std::function<void ()> & onFinish)731 void RenderScroll::AnimateTo(double position, float duration, const RefPtr<Curve>& curve, bool limitDuration,
732     const std::function<void()>& onFinish)
733 {
734     if (!animator_) {
735         animator_ = CREATE_ANIMATOR(GetContext());
736         CHECK_NULL_VOID(animator_);
737     }
738     if (!animator_->IsStopped()) {
739         animator_->Stop();
740     }
741     animator_->ClearInterpolators();
742 
743     // Send event to accessibility when scroll start.
744     auto context = GetContext().Upgrade();
745     if (context) {
746         AccessibilityEvent scrollEvent;
747         scrollEvent.nodeId = GetAccessibilityNodeId();
748         scrollEvent.eventType = "scrollstart";
749         context->SendEventToAccessibility(scrollEvent);
750     }
751     auto animation = AceType::MakeRefPtr<CurveAnimation<double>>(GetCurrentPosition(), position, curve);
752     animation->AddListener([weakScroll = AceType::WeakClaim(this)](double value) {
753         auto scroll = weakScroll.Upgrade();
754         if (scroll) {
755             scroll->DoJump(value);
756         }
757     });
758     animator_->AddInterpolator(animation);
759     animator_->SetDuration(duration);
760     animator_->ClearStopListeners();
761     animator_->Play();
762     auto weakScroll = AceType::WeakClaim(this);
763     auto weakScrollBar = AceType::WeakClaim(AceType::RawPtr(scrollBar_));
764     animator_->AddStopListener([weakScroll, weakScrollBar, onFinish, context = context_]() {
765         auto scrollBar = weakScrollBar.Upgrade();
766         if (scrollBar) {
767             scrollBar->HandleScrollBarEnd();
768         }
769         // Send event to accessibility when scroll end.
770         auto scroll = weakScroll.Upgrade();
771         if (scroll) {
772             auto context = scroll->GetContext().Upgrade();
773             if (context) {
774                 AccessibilityEvent scrollEvent;
775                 scrollEvent.nodeId = scroll->GetAccessibilityNodeId();
776                 scrollEvent.eventType = "scrollend";
777                 context->SendEventToAccessibility(scrollEvent);
778                 if (context->GetIsDeclarative() && scroll->scrollable_) {
779                     scroll->scrollable_->ChangeMoveStatus(true);
780                     scroll->scrollable_->ProcessScrollMotionStop();
781                 }
782             }
783         }
784 
785         if (onFinish) {
786             onFinish();
787         }
788     });
789 }
790 
AnimateToTarget(const ComposeId & targetId,float duration,const RefPtr<Curve> & curve,bool limitDuration,const std::function<void ()> & onFinish)791 bool RenderScroll::AnimateToTarget(const ComposeId& targetId, float duration, const RefPtr<Curve>& curve,
792     bool limitDuration, const std::function<void()>& onFinish)
793 {
794     auto context = GetContext().Upgrade();
795     if (!context) {
796         return false;
797     }
798     auto targetElement = context->GetComposedElementById(targetId);
799     if (!targetElement) {
800         return false;
801     }
802     auto targetRender = targetElement->GetRenderNode();
803     if (!targetRender) {
804         return false;
805     }
806 
807     auto globalOffset = targetRender->GetGlobalOffset() - GetPosition();
808     double distance = ((axis_ == Axis::VERTICAL) ? globalOffset.GetY() : globalOffset.GetX()) + GetCurrentPosition();
809     AnimateTo(distance, duration, curve, limitDuration, onFinish);
810     return true;
811 }
812 
ScrollBy(double pixelX,double pixelY,bool smooth,const std::function<void ()> & onFinish)813 void RenderScroll::ScrollBy(double pixelX, double pixelY, bool smooth, const std::function<void()>& onFinish)
814 {
815     double distance = (axis_ == Axis::VERTICAL) ? pixelY : pixelX;
816     if (NearZero(distance)) {
817         return;
818     }
819     double position = GetMainOffset(currentOffset_) + distance;
820     if (smooth) {
821         AnimateTo(position, fabs(distance) * UNIT_CONVERT / SCROLL_BY_SPEED, Curves::EASE_OUT, true, onFinish);
822     } else {
823         JumpToPosition(position);
824     }
825 }
826 
GetCurrentPosition() const827 double RenderScroll::GetCurrentPosition() const
828 {
829     double position = 0.0;
830     if (axis_ == Axis::VERTICAL) {
831         position = currentOffset_.GetY();
832     } else {
833         position = currentOffset_.GetX();
834     }
835     return position;
836 }
837 
Update(const RefPtr<Component> & component)838 void RenderScroll::Update(const RefPtr<Component>& component)
839 {
840     auto scroll = AceType::DynamicCast<ScrollComponent>(component);
841     if (scroll == nullptr) {
842         LOGI("scroll component is null, which it is multi scroll, not single scroll");
843         return;
844     }
845     hasHeight_ = scroll->GetHasHeight();
846     hasWidth_ = scroll->GetHasWidth();
847     if (!animator_) {
848         animator_ = CREATE_ANIMATOR(GetContext());
849     }
850     // ApplyRestoreInfo maybe change currentOffset_
851     ApplyRestoreInfo();
852     lastOffset_ = currentOffset_;
853     // Send scroll none when first build.
854     HandleScrollPosition(0.0, 0.0, SCROLL_NONE);
855     MarkNeedLayout();
856     onReachStart_ = AceAsyncEvent<void(const std::string&)>::Create(scroll->GetOnReachStart(), context_);
857     onReachEnd_ = AceAsyncEvent<void(const std::string&)>::Create(scroll->GetOnReachEnd(), context_);
858     onReachTop_ = AceAsyncEvent<void(const std::string&)>::Create(scroll->GetOnReachTop(), context_);
859     onReachBottom_ = AceAsyncEvent<void(const std::string&)>::Create(scroll->GetOnReachBottom(), context_);
860     scrollBarProxy_ = scroll->GetScrollBarProxy();
861     InitScrollBarProxy();
862 }
863 
InitScrollBarProxy()864 void RenderScroll::InitScrollBarProxy()
865 {
866     if (!scrollBarProxy_) {
867         return;
868     }
869     auto isVertical = (axis_ == Axis::VERTICAL);
870     auto&& scrollCallback = [weakScroll = AceType::WeakClaim(this), isVertical](double value, int32_t source) {
871         auto scroll = weakScroll.Upgrade();
872         if (!scroll) {
873             LOGE("render scroll is released");
874             return false;
875         }
876         Offset delta;
877         if (isVertical) {
878             delta.SetX(0.0);
879             delta.SetY(-value);
880         } else {
881             delta.SetX(-value);
882             delta.SetY(0.0);
883         }
884         scroll->AdjustOffset(delta, source);
885         return scroll->UpdateOffset(delta, source);
886     };
887     scrollBarProxy_->UnRegisterScrollableNode(AceType::WeakClaim(this));
888     scrollBarProxy_->RegisterScrollableNode({ AceType::WeakClaim(this), scrollCallback });
889 }
890 
SetBarCallBack(bool isVertical)891 void RenderScroll::SetBarCallBack(bool isVertical)
892 {
893     if (scrollBar_ && scrollBar_->NeedScrollBar()) {
894         auto&& barEndCallback = [weakScroll = AceType::WeakClaim(this), isVertical](int32_t value) {
895             auto scroll = weakScroll.Upgrade();
896             if (!scroll) {
897                 LOGE("render scroll is released");
898                 return;
899             }
900             scroll->scrollBarOpacity_ = value;
901             scroll->MarkNeedRender();
902         };
903         auto&& scrollEndCallback = [weakScroll = AceType::WeakClaim(this), isVertical]() {
904             auto scroll = weakScroll.Upgrade();
905             if (!scroll) {
906                 LOGE("render scroll is released");
907                 return;
908             }
909             if (scroll->positionController_) {
910                 scroll->positionController_->HandleScrollEvent(
911                     std::make_shared<ScrollEventInfo>(ScrollEvent::SCROLL_END, 0.0, 0.0, -1));
912             }
913             // Send scroll none when scroll is end.
914             scroll->HandleScrollPosition(0.0, 0.0, SCROLL_NONE);
915         };
916         auto&& scrollCallback = [weakScroll = AceType::WeakClaim(this), isVertical](double value, int32_t source) {
917             auto scroll = weakScroll.Upgrade();
918             if (!scroll) {
919                 LOGE("render scroll is released");
920                 return false;
921             }
922             Offset delta;
923             if (isVertical) {
924                 delta.SetX(0.0);
925                 delta.SetY(-value);
926             } else {
927                 delta.SetX(-value);
928                 delta.SetY(0.0);
929             }
930             scroll->AdjustOffset(delta, source);
931 
932             return scroll->UpdateOffset(delta, source);
933         };
934         scrollBar_->SetCallBack(scrollCallback, barEndCallback, scrollEndCallback);
935     }
936 }
937 
GetEstimatedHeight()938 double RenderScroll::GetEstimatedHeight()
939 {
940     if (ReachMaxCount()) {
941         estimatedHeight_ = scrollBarExtent_;
942     } else {
943         estimatedHeight_ = std::max(estimatedHeight_, scrollBarExtent_);
944     }
945     return estimatedHeight_;
946 }
947 
HandleScrollOverByBar(double velocity)948 void RenderScroll::HandleScrollOverByBar(double velocity)
949 {
950     // the direction of bar and scroll is opposite, so it need be negative
951     if (scrollEffect_) {
952         scrollEffect_->ProcessScrollOver(-velocity);
953     }
954 }
955 
HandleScrollBarEnd()956 void RenderScroll::HandleScrollBarEnd()
957 {
958     if (scrollBar_) {
959         scrollBar_->HandleScrollBarEnd();
960     }
961 }
962 
HandleRotate(double rotateValue,bool isVertical)963 void RenderScroll::HandleRotate(double rotateValue, bool isVertical)
964 {
965     auto context = GetContext().Upgrade();
966     if (!context) {
967         LOGE("context is null");
968         return;
969     }
970     double value = context->NormalizeToPx(Dimension(rotateValue, DimensionUnit::VP)) * ROTATE_FACTOR;
971 
972     // Vertical or horizontal, different axis
973     Offset delta;
974     if (isVertical) {
975         delta.SetX(0.0);
976         delta.SetY(value);
977     } else {
978         delta.SetX(value);
979         delta.SetY(0.0);
980     }
981     UpdateOffset(delta, SCROLL_FROM_ROTATE);
982 }
983 
OnChildAdded(const RefPtr<RenderNode> & child)984 void RenderScroll::OnChildAdded(const RefPtr<RenderNode>& child)
985 {
986     child->SetSlipFactorSetting([weakScroll = AceType::WeakClaim(this)](double slipFactor) {
987         auto scroll = weakScroll.Upgrade();
988         if (scroll) {
989             scroll->scrollable_->SetSlipFactor(slipFactor);
990         }
991     });
992 }
993 
OnReachStart() const994 void RenderScroll::OnReachStart() const
995 {
996     if (onReachStart_) {
997         onReachStart_(std::string("\"reachstart\",null"));
998     }
999 }
1000 
OnReachEnd() const1001 void RenderScroll::OnReachEnd() const
1002 {
1003     if (onReachEnd_) {
1004         onReachEnd_(std::string("\"reachend\",null"));
1005     }
1006 }
1007 
OnReachTop() const1008 void RenderScroll::OnReachTop() const
1009 {
1010     if (onReachTop_) {
1011         onReachTop_(std::string("\"reachtop\",null"));
1012     }
1013 }
1014 
OnReachBottom() const1015 void RenderScroll::OnReachBottom() const
1016 {
1017     if (onReachBottom_) {
1018         onReachBottom_(std::string("\"reachbottom\",null"));
1019     }
1020 }
1021 
UpdateTouchRect()1022 void RenderScroll::UpdateTouchRect()
1023 {
1024     RenderNode::UpdateTouchRect();
1025     if (!scrollBar_) {
1026         return;
1027     }
1028     double scrollBarPosition = NormalizeToPx(scrollBar_->GetPosition());
1029     if (!NearZero(scrollBarPosition)) {
1030         touchRect_.SetWidth(touchRect_.Width() + scrollBarPosition);
1031         if (scrollBar_->GetPositionMode() == PositionMode::LEFT) {
1032             touchRect_.SetLeft(touchRect_.Left() - scrollBarPosition);
1033         }
1034     }
1035 }
1036 
IsAxisScrollable(AxisDirection direction)1037 bool RenderScroll::IsAxisScrollable(AxisDirection direction)
1038 {
1039     return (((AxisEvent::IsDirectionUp(direction) || AxisEvent::IsDirectionLeft(direction)) && !IsAtTop()) ||
1040             ((AxisEvent::IsDirectionDown(direction) || AxisEvent::IsDirectionRight(direction)) && !IsAtBottom()));
1041 }
1042 
HandleAxisEvent(const AxisEvent & event)1043 void RenderScroll::HandleAxisEvent(const AxisEvent& event)
1044 {
1045 }
1046 
ProvideRestoreInfo()1047 std::string RenderScroll::ProvideRestoreInfo()
1048 {
1049     if (!NearZero(currentOffset_.GetY()) && axis_ == Axis::VERTICAL) {
1050         return std::to_string(currentOffset_.GetY());
1051     } else if (!NearZero(currentOffset_.GetX()) && axis_ == Axis::HORIZONTAL) {
1052         return std::to_string(currentOffset_.GetX());
1053     }
1054     return "";
1055 }
1056 
ApplyRestoreInfo()1057 void RenderScroll::ApplyRestoreInfo()
1058 {
1059     if (GetRestoreInfo().empty()) {
1060         return;
1061     }
1062     if (axis_ == Axis::VERTICAL) {
1063         currentOffset_.SetY(StringUtils::StringToDouble(GetRestoreInfo()));
1064     } else {
1065         currentOffset_.SetX(StringUtils::StringToDouble(GetRestoreInfo()));
1066     }
1067     SetRestoreInfo("");
1068 }
1069 
1070 } // namespace OHOS::Ace
1071