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