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