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