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