1 /*
2 * Copyright (c) 2022-2023 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_ng/pattern/scroll/scroll_pattern.h"
17
18 #include "base/geometry/axis.h"
19 #include "base/geometry/dimension.h"
20 #include "base/utils/utils.h"
21 #include "core/components/scroll/scrollable.h"
22 #include "core/components_ng/pattern/scroll/scroll_edge_effect.h"
23 #include "core/components_ng/pattern/scroll/scroll_event_hub.h"
24 #include "core/components_ng/pattern/scroll/scroll_layout_algorithm.h"
25 #include "core/components_ng/pattern/scroll/scroll_layout_property.h"
26 #include "core/components_ng/pattern/scroll/scroll_paint_property.h"
27 #include "core/components_ng/pattern/scroll/scroll_spring_effect.h"
28 #include "core/components_ng/pattern/scrollable/scrollable_properties.h"
29 #include "core/components_ng/property/measure_utils.h"
30 #include "core/components_ng/property/property.h"
31 #include "core/pipeline/pipeline_base.h"
32
33 namespace OHOS::Ace::NG {
34
35 namespace {
36
37 constexpr int32_t SCROLL_NONE = 0;
38 constexpr int32_t SCROLL_TOUCH_DOWN = 1;
39 constexpr int32_t SCROLL_TOUCH_UP = 2;
40 constexpr float SCROLL_RATIO = 0.52f;
41 constexpr float SCROLL_BY_SPEED = 250.0f; // move 250 pixels per second
42 constexpr float UNIT_CONVERT = 1000.0f; // 1s convert to 1000ms
43
CalculateFriction(float gamma)44 float CalculateFriction(float gamma)
45 {
46 return static_cast<float>(SCROLL_RATIO * std::pow(1.0 - gamma, SQUARE));
47 }
48
CalculateOffsetByFriction(float extentOffset,float delta,float friction)49 float CalculateOffsetByFriction(float extentOffset, float delta, float friction)
50 {
51 if (NearZero(friction)) {
52 return delta;
53 }
54 float deltaToLimit = extentOffset / friction;
55 if (delta < deltaToLimit) {
56 return delta * friction;
57 }
58 return extentOffset + delta - deltaToLimit;
59 }
60
61 } // namespace
62
OnModifyDone()63 void ScrollPattern::OnModifyDone()
64 {
65 auto host = GetHost();
66 CHECK_NULL_VOID_NOLOG(host);
67 auto layoutProperty = host->GetLayoutProperty<ScrollLayoutProperty>();
68 CHECK_NULL_VOID_NOLOG(layoutProperty);
69 auto paintProperty = host->GetPaintProperty<ScrollPaintProperty>();
70 CHECK_NULL_VOID_NOLOG(paintProperty);
71 auto axis = layoutProperty->GetAxis().value_or(Axis::VERTICAL);
72 if (axis != GetAxis()) {
73 SetAxis(axis);
74 ResetPosition();
75 }
76 if (!GetScrollableEvent()) {
77 AddScrollEvent();
78 RegisterScrollEventTask();
79 }
80 SetEdgeEffect(layoutProperty->GetEdgeEffect().value_or(EdgeEffect::NONE));
81 SetScrollBar(paintProperty->GetScrollBarProperty());
82 SetAccessibilityAction();
83 if (scrollSnapUpdate_) {
84 host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
85 }
86 }
87
RegisterScrollEventTask()88 void ScrollPattern::RegisterScrollEventTask()
89 {
90 auto eventHub = GetHost()->GetEventHub<ScrollEventHub>();
91 CHECK_NULL_VOID(eventHub);
92 auto scrollFrameBeginEvent = eventHub->GetScrollFrameBeginEvent();
93 SetScrollFrameBeginCallback(scrollFrameBeginEvent);
94 }
95
OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper> & dirty,const DirtySwapConfig & config)96 bool ScrollPattern::OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper>& dirty, const DirtySwapConfig& config)
97 {
98 if (config.skipMeasure && config.skipLayout) {
99 return false;
100 }
101 auto layoutAlgorithmWrapper = DynamicCast<LayoutAlgorithmWrapper>(dirty->GetLayoutAlgorithm());
102 CHECK_NULL_RETURN_NOLOG(layoutAlgorithmWrapper, false);
103 auto layoutAlgorithm = DynamicCast<ScrollLayoutAlgorithm>(layoutAlgorithmWrapper->GetLayoutAlgorithm());
104 CHECK_NULL_RETURN_NOLOG(layoutAlgorithm, false);
105 currentOffset_ = layoutAlgorithm->GetCurrentOffset();
106 scrollableDistance_ = layoutAlgorithm->GetScrollableDistance();
107 auto axis = GetAxis();
108 auto oldMainSize = GetMainAxisSize(viewPort_, axis);
109 auto newMainSize = GetMainAxisSize(layoutAlgorithm->GetViewPort(), axis);
110 auto oldExtentMainSize = GetMainAxisSize(viewPortExtent_, axis);
111 auto newExtentMainSize = GetMainAxisSize(layoutAlgorithm->GetViewPortExtent(), axis);
112 viewPortLength_ = layoutAlgorithm->GetViewPortLength();
113 viewPort_ = layoutAlgorithm->GetViewPort();
114 viewSize_ = layoutAlgorithm->GetViewSize();
115 viewPortExtent_ = layoutAlgorithm->GetViewPortExtent();
116 if (scrollSnapUpdate_ || !NearEqual(oldMainSize, newMainSize) || !NearEqual(oldExtentMainSize, newExtentMainSize)) {
117 CaleSnapOffsets();
118 scrollSnapUpdate_ = false;
119 }
120 UpdateScrollBarOffset();
121 if (config.frameSizeChange) {
122 if (GetScrollBar() != nullptr) {
123 GetScrollBar()->ScheduleDisapplearDelayTask();
124 }
125 }
126 if (scrollStop_) {
127 FireOnScrollStop();
128 scrollStop_ = false;
129 }
130 if (ScrollableIdle() && !AnimateRunning()) {
131 auto predictSnapOffset = CalePredictSnapOffset(0.0);
132 if (predictSnapOffset.has_value() && !NearZero(predictSnapOffset.value())) {
133 StartScrollSnapMotion(predictSnapOffset.value(), 0.0f);
134 FireOnScrollStart();
135 }
136 }
137 CheckScrollable();
138 auto host = GetHost();
139 CHECK_NULL_RETURN(host, false);
140 auto geometryNode = host->GetGeometryNode();
141 CHECK_NULL_RETURN(geometryNode, false);
142 auto offsetRelativeToWindow = host->GetOffsetRelativeToWindow();
143 auto globalViewPort = RectF(offsetRelativeToWindow, geometryNode->GetFrameRect().GetSize());
144 host->SetViewPort(globalViewPort);
145 return false;
146 }
147
CheckScrollable()148 void ScrollPattern::CheckScrollable()
149 {
150 auto host = GetHost();
151 CHECK_NULL_VOID(host);
152 auto layoutProperty = host->GetLayoutProperty<ScrollLayoutProperty>();
153 CHECK_NULL_VOID(layoutProperty);
154 SetScrollEnable(layoutProperty->GetScrollEnabled().value_or(true));
155 }
156
FireOnScrollStart()157 void ScrollPattern::FireOnScrollStart()
158 {
159 if (GetScrollAbort()) {
160 return;
161 }
162 auto scrollBar = GetScrollBar();
163 if (scrollBar) {
164 scrollBar->PlayScrollBarStartAnimation();
165 }
166 StopScrollBarAnimatorByProxy();
167 auto host = GetHost();
168 CHECK_NULL_VOID(host);
169 auto hub = host->GetEventHub<ScrollEventHub>();
170 CHECK_NULL_VOID_NOLOG(hub);
171 auto onScrollStart = hub->GetScrollStartEvent();
172 CHECK_NULL_VOID_NOLOG(onScrollStart);
173 onScrollStart();
174 }
175
FireOnScrollStop()176 void ScrollPattern::FireOnScrollStop()
177 {
178 if (GetScrollAbort()) {
179 SetScrollAbort(false);
180 return;
181 }
182 auto scrollBar = GetScrollBar();
183 if (scrollBar) {
184 scrollBar->ScheduleDisapplearDelayTask();
185 }
186 StartScrollBarAnimatorByProxy();
187 auto host = GetHost();
188 CHECK_NULL_VOID(host);
189 auto hub = host->GetEventHub<ScrollEventHub>();
190 CHECK_NULL_VOID_NOLOG(hub);
191 auto onScrollStop = hub->GetScrollStopEvent();
192 CHECK_NULL_VOID_NOLOG(onScrollStop);
193 onScrollStop();
194 }
195
OnScrollCallback(float offset,int32_t source)196 bool ScrollPattern::OnScrollCallback(float offset, int32_t source)
197 {
198 if (source != SCROLL_FROM_START) {
199 if (GetAxis() == Axis::NONE) {
200 return false;
201 }
202 if (!AnimateStoped()) {
203 return false;
204 }
205 auto adjustOffset = static_cast<float>(offset);
206 AdjustOffset(adjustOffset, source);
207 return UpdateCurrentOffset(adjustOffset, source);
208 } else {
209 FireOnScrollStart();
210 }
211 return true;
212 }
213
ToJsonValue(std::unique_ptr<JsonValue> & json) const214 void ScrollPattern::ToJsonValue(std::unique_ptr<JsonValue>& json) const
215 {
216 json->Put("friction", GetFriction());
217 }
218
OnScrollEndCallback()219 void ScrollPattern::OnScrollEndCallback()
220 {
221 auto host = GetHost();
222 CHECK_NULL_VOID(host);
223 auto eventHub = host->GetEventHub<ScrollEventHub>();
224 CHECK_NULL_VOID(eventHub);
225 auto scrollEndEvent = eventHub->GetScrollEndEvent();
226 if (scrollEndEvent) {
227 scrollEndEvent();
228 }
229 scrollStop_ = true;
230 host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
231 }
232
ResetPosition()233 void ScrollPattern::ResetPosition()
234 {
235 currentOffset_ = 0.0f;
236 lastOffset_ = 0.0f;
237 }
238
IsAtTop() const239 bool ScrollPattern::IsAtTop() const
240 {
241 return GreatOrEqual(currentOffset_, 0.0);
242 }
243
IsAtBottom() const244 bool ScrollPattern::IsAtBottom() const
245 {
246 bool atBottom = LessOrEqual(currentOffset_, -scrollableDistance_);
247 // TODO: ignore ReachMaxCount
248 return atBottom;
249 }
250
GetOverScrollOffset(double delta) const251 OverScrollOffset ScrollPattern::GetOverScrollOffset(double delta) const
252 {
253 OverScrollOffset offset = { 0, 0 };
254 auto startPos = currentOffset_;
255 auto newStartPos = startPos + delta;
256 if (startPos > 0 && newStartPos > 0) {
257 offset.start = delta;
258 }
259 if (startPos > 0 && newStartPos <= 0) {
260 offset.start = -startPos;
261 }
262 if (startPos <= 0 && newStartPos > 0) {
263 offset.start = newStartPos;
264 }
265
266 auto endPos = currentOffset_;
267 auto newEndPos = endPos + delta;
268 auto endRefences = -scrollableDistance_;
269 if (endPos < endRefences && newEndPos < endRefences) {
270 offset.end = delta;
271 }
272 if (endPos < endRefences && newEndPos >= endRefences) {
273 offset.end = endRefences - endPos;
274 }
275 if (endPos >= endRefences && newEndPos < endRefences) {
276 offset.end = newEndPos - endRefences;
277 }
278 return offset;
279 }
280
IsOutOfBoundary() const281 bool ScrollPattern::IsOutOfBoundary() const
282 {
283 return Positive(currentOffset_) || LessNotEqual(currentOffset_, -scrollableDistance_);
284 }
285
ScrollPageCheck(float delta,int32_t source)286 bool ScrollPattern::ScrollPageCheck(float delta, int32_t source)
287 {
288 return true;
289 }
290
AdjustOffset(float & delta,int32_t source)291 void ScrollPattern::AdjustOffset(float& delta, int32_t source)
292 {
293 if (NearZero(delta) || NearZero(viewPortLength_) || source == SCROLL_FROM_ANIMATION ||
294 source == SCROLL_FROM_ANIMATION_SPRING) {
295 return;
296 }
297 // the distance above the top, if lower than top, it is zero
298 float overScrollPastStart = 0.0f;
299 // the distance below the bottom, if higher than bottom, it is zero
300 float overScrollPastEnd = 0.0f;
301 float overScrollPast = 0.0f;
302 // TODO: not consider rowReverse or colReverse
303 overScrollPastStart = std::max(currentOffset_, 0.0f);
304 if (Positive(scrollableDistance_)) {
305 overScrollPastEnd = std::max(-scrollableDistance_ - currentOffset_, 0.0f);
306 } else {
307 overScrollPastEnd = std::abs(std::min(currentOffset_, 0.0f));
308 }
309 // do not adjust offset if direction opposite from the overScroll direction when out of boundary
310 if ((overScrollPastStart > 0.0f && delta < 0.0f) || (overScrollPastEnd > 0.0f && delta > 0.0f)) {
311 return;
312 }
313 overScrollPast = std::max(overScrollPastStart, overScrollPastEnd);
314 if (overScrollPast == 0.0f) {
315 return;
316 }
317 float friction = CalculateFriction((overScrollPast - std::abs(delta)) / viewPortLength_);
318 float direction = delta > 0.0f ? 1.0f : -1.0f;
319 delta = direction * CalculateOffsetByFriction(overScrollPast, std::abs(delta), friction);
320 }
321
ValidateOffset(int32_t source)322 void ScrollPattern::ValidateOffset(int32_t source)
323 {
324 if (scrollableDistance_ <= 0.0f || source == SCROLL_FROM_JUMP) {
325 return;
326 }
327
328 // restrict position between top and bottom
329 if (IsRestrictBoundary() || source == SCROLL_FROM_BAR || source == SCROLL_FROM_BAR_FLING ||
330 source == SCROLL_FROM_ROTATE) {
331 if (GetAxis() == Axis::HORIZONTAL) {
332 if (IsRowReverse()) {
333 currentOffset_ = std::clamp(currentOffset_, 0.0f, scrollableDistance_);
334 } else {
335 currentOffset_ = std::clamp(currentOffset_, -scrollableDistance_, 0.0f);
336 }
337 } else {
338 currentOffset_ = std::clamp(currentOffset_, -scrollableDistance_, 0.0f);
339 }
340 } else {
341 float scrollBarOutBoundaryExtent = 0.0f;
342 if (currentOffset_ > 0) {
343 scrollBarOutBoundaryExtent = currentOffset_;
344 } else if ((-currentOffset_) >= (GetMainSize(viewPortExtent_) - GetMainSize(viewPort_)) && ReachMaxCount()) {
345 scrollBarOutBoundaryExtent = -currentOffset_ - (GetMainSize(viewPortExtent_) - GetMainSize(viewPort_));
346 }
347 HandleScrollBarOutBoundary(scrollBarOutBoundaryExtent);
348 }
349 }
350
HandleScrollPosition(float scroll,int32_t scrollState)351 void ScrollPattern::HandleScrollPosition(float scroll, int32_t scrollState)
352 {
353 auto eventHub = GetEventHub<ScrollEventHub>();
354 CHECK_NULL_VOID(eventHub);
355 auto onScroll = eventHub->GetOnScrollEvent();
356 CHECK_NULL_VOID_NOLOG(onScroll);
357 // not consider async call
358 Dimension scrollX(0, DimensionUnit::VP);
359 Dimension scrollY(0, DimensionUnit::VP);
360 Dimension scrollPx(scroll, DimensionUnit::PX);
361 auto scrollVpValue = scrollPx.ConvertToVp();
362 if (GetAxis() == Axis::HORIZONTAL) {
363 scrollX.SetValue(scrollVpValue);
364 } else {
365 scrollY.SetValue(scrollVpValue);
366 }
367 onScroll(scrollX, scrollY);
368 }
369
IsCrashTop() const370 bool ScrollPattern::IsCrashTop() const
371 {
372 bool scrollUpToReachTop = LessNotEqual(lastOffset_, 0.0) && GreatOrEqual(currentOffset_, 0.0);
373 bool scrollDownToReachTop = GreatNotEqual(lastOffset_, 0.0) && LessOrEqual(currentOffset_, 0.0);
374 return scrollUpToReachTop || scrollDownToReachTop;
375 }
376
IsCrashBottom() const377 bool ScrollPattern::IsCrashBottom() const
378 {
379 float minExtent = -scrollableDistance_;
380 bool scrollDownToReachEnd = GreatNotEqual(lastOffset_, minExtent) && LessOrEqual(currentOffset_, minExtent);
381 bool scrollUpToReachEnd = LessNotEqual(lastOffset_, minExtent) && GreatOrEqual(currentOffset_, minExtent);
382 return (scrollUpToReachEnd || scrollDownToReachEnd) && ReachMaxCount();
383 }
384
HandleCrashTop() const385 void ScrollPattern::HandleCrashTop() const
386 {
387 auto frameNode = GetHost();
388 CHECK_NULL_VOID(frameNode);
389 auto eventHub = frameNode->GetEventHub<ScrollEventHub>();
390 CHECK_NULL_VOID(eventHub);
391 const auto& onScrollEdge = eventHub->GetScrollEdgeEvent();
392 CHECK_NULL_VOID_NOLOG(onScrollEdge);
393 // not consider async call
394 if (GetAxis() == Axis::HORIZONTAL) {
395 onScrollEdge(ScrollEdge::LEFT);
396 return;
397 }
398 onScrollEdge(ScrollEdge::TOP);
399 }
400
HandleCrashBottom() const401 void ScrollPattern::HandleCrashBottom() const
402 {
403 auto frameNode = GetHost();
404 CHECK_NULL_VOID(frameNode);
405 auto eventHub = frameNode->GetEventHub<ScrollEventHub>();
406 CHECK_NULL_VOID(eventHub);
407 const auto& onScrollEdge = eventHub->GetScrollEdgeEvent();
408 CHECK_NULL_VOID_NOLOG(onScrollEdge);
409 if (GetAxis() == Axis::HORIZONTAL) {
410 onScrollEdge(ScrollEdge::RIGHT);
411 return;
412 }
413 onScrollEdge(ScrollEdge::BOTTOM);
414 }
415
UpdateCurrentOffset(float delta,int32_t source)416 bool ScrollPattern::UpdateCurrentOffset(float delta, int32_t source)
417 {
418 SetScrollSource(source);
419 auto host = GetHost();
420 CHECK_NULL_RETURN(host, false);
421 // TODO: ignore handle refresh
422 if (source != SCROLL_FROM_JUMP && !HandleEdgeEffect(delta, source, viewSize_)) {
423 if (IsOutOfBoundary()) {
424 host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
425 }
426 return false;
427 }
428 // TODO: scrollBar effect!!
429 lastOffset_ = currentOffset_;
430 currentOffset_ += delta;
431 ValidateOffset(source);
432 int32_t touchState = SCROLL_NONE;
433 if (source == SCROLL_FROM_UPDATE) {
434 touchState = SCROLL_TOUCH_DOWN;
435 } else if (source == SCROLL_FROM_ANIMATION || source == SCROLL_FROM_ANIMATION_SPRING) {
436 touchState = SCROLL_TOUCH_UP;
437 }
438 HandleScrollPosition(-delta, touchState);
439 if (IsCrashTop()) {
440 HandleCrashTop();
441 } else if (IsCrashBottom()) {
442 HandleCrashBottom();
443 }
444 host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
445 return true;
446 }
447
OnAnimateStop()448 void ScrollPattern::OnAnimateStop()
449 {
450 auto host = GetHost();
451 CHECK_NULL_VOID_NOLOG(host);
452 host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
453 host->OnAccessibilityEvent(AccessibilityEventType::SCROLL_END);
454 scrollStop_ = true;
455 }
456
AnimateTo(float position,float duration,const RefPtr<Curve> & curve,bool smooth)457 void ScrollPattern::AnimateTo(float position, float duration, const RefPtr<Curve>& curve, bool smooth)
458 {
459 auto host = GetHost();
460 CHECK_NULL_VOID(host);
461 host->OnAccessibilityEvent(AccessibilityEventType::SCROLL_START);
462 ScrollablePattern::AnimateTo(position, duration, curve, smooth);
463 FireOnScrollStart();
464 }
465
ScrollToEdge(ScrollEdgeType scrollEdgeType,bool smooth)466 void ScrollPattern::ScrollToEdge(ScrollEdgeType scrollEdgeType, bool smooth)
467 {
468 if (scrollEdgeType == ScrollEdgeType::SCROLL_NONE) {
469 return;
470 }
471 float distance = scrollEdgeType == ScrollEdgeType::SCROLL_TOP ? -currentOffset_ :
472 (-scrollableDistance_ - currentOffset_);
473 ScrollBy(distance, distance, smooth);
474 }
475
ScrollBy(float pixelX,float pixelY,bool smooth,const std::function<void ()> & onFinish)476 void ScrollPattern::ScrollBy(float pixelX, float pixelY, bool smooth, const std::function<void()>& onFinish)
477 {
478 float distance = (GetAxis() == Axis::VERTICAL) ? pixelY : pixelX;
479 if (NearZero(distance)) {
480 return;
481 }
482 float position = currentOffset_ + distance;
483 if (smooth) {
484 AnimateTo(-position, fabs(distance) * UNIT_CONVERT / SCROLL_BY_SPEED, Curves::EASE_OUT, true);
485 return;
486 }
487 JumpToPosition(position);
488 }
489
ScrollPage(bool reverse,bool smooth,const std::function<void ()> & onFinish)490 bool ScrollPattern::ScrollPage(bool reverse, bool smooth, const std::function<void()>& onFinish)
491 {
492 float distance = reverse ? viewPortLength_ : -viewPortLength_;
493 ScrollBy(distance, distance, smooth, onFinish);
494 return true;
495 }
496
JumpToPosition(float position,int32_t source)497 void ScrollPattern::JumpToPosition(float position, int32_t source)
498 {
499 // If an animation is playing, stop it.
500 StopAnimate();
501 float cachePosition = currentOffset_;
502 DoJump(position, source);
503 if (cachePosition != currentOffset_) {
504 auto host = GetHost();
505 CHECK_NULL_VOID(host);
506 host->OnAccessibilityEvent(AccessibilityEventType::SCROLL_END);
507 }
508 }
509
ScrollTo(float position)510 void ScrollPattern::ScrollTo(float position)
511 {
512 JumpToPosition(-position, SCROLL_FROM_JUMP);
513 }
514
DoJump(float position,int32_t source)515 void ScrollPattern::DoJump(float position, int32_t source)
516 {
517 float setPosition = (GetAxis() == Axis::HORIZONTAL && IsRowReverse()) ? -position : position;
518 if (!NearEqual(currentOffset_, setPosition)) {
519 UpdateCurrentOffset(setPosition - currentOffset_, source);
520 }
521 }
522
SetEdgeEffectCallback(const RefPtr<ScrollEdgeEffect> & scrollEffect)523 void ScrollPattern::SetEdgeEffectCallback(const RefPtr<ScrollEdgeEffect>& scrollEffect)
524 {
525 scrollEffect->SetCurrentPositionCallback([weakScroll = AceType::WeakClaim(this)]() -> double {
526 auto scroll = weakScroll.Upgrade();
527 CHECK_NULL_RETURN_NOLOG(scroll, 0.0);
528 return scroll->GetCurrentPosition();
529 });
530 scrollEffect->SetLeadingCallback([weakScroll = AceType::WeakClaim(this)]() -> double {
531 auto scroll = weakScroll.Upgrade();
532 if (scroll && !scroll->IsRowReverse() && !scroll->IsColReverse() && scroll->GetScrollableDistance() > 0) {
533 return -scroll->GetScrollableDistance();
534 }
535 return 0.0;
536 });
537 scrollEffect->SetTrailingCallback([weakScroll = AceType::WeakClaim(this)]() -> double {
538 auto scroll = weakScroll.Upgrade();
539 if (scroll && (scroll->IsRowReverse() || scroll->IsColReverse())) {
540 return scroll->GetScrollableDistance();
541 }
542 return 0.0;
543 });
544 scrollEffect->SetInitLeadingCallback([weakScroll = AceType::WeakClaim(this)]() -> double {
545 auto scroll = weakScroll.Upgrade();
546 if (scroll && !scroll->IsRowReverse() && !scroll->IsColReverse() && scroll->GetScrollableDistance() > 0) {
547 return -scroll->GetScrollableDistance();
548 }
549 return 0.0;
550 });
551 scrollEffect->SetInitTrailingCallback([weakScroll = AceType::WeakClaim(this)]() -> double {
552 auto scroll = weakScroll.Upgrade();
553 if (scroll && (scroll->IsRowReverse() || scroll->IsColReverse())) {
554 return scroll->GetScrollableDistance();
555 }
556 return 0.0;
557 });
558 }
559
UpdateScrollBarOffset()560 void ScrollPattern::UpdateScrollBarOffset()
561 {
562 if (!GetScrollBar() && !GetScrollBarProxy()) {
563 return;
564 }
565 auto host = GetHost();
566 CHECK_NULL_VOID_NOLOG(host);
567 auto layoutProperty = host->GetLayoutProperty<ScrollLayoutProperty>();
568 CHECK_NULL_VOID_NOLOG(layoutProperty);
569 auto padding = layoutProperty->CreatePaddingAndBorder();
570 Size size(viewSize_.Width(), viewSize_.Height());
571 auto viewPortExtent = viewPortExtent_;
572 AddPaddingToSize(padding, viewPortExtent);
573 auto estimatedHeight = (GetAxis() == Axis::HORIZONTAL) ? viewPortExtent.Width() : viewPortExtent.Height();
574 UpdateScrollBarRegion(-currentOffset_, estimatedHeight, size, Offset(0.0f, 0.0f));
575 }
576
SetAccessibilityAction()577 void ScrollPattern::SetAccessibilityAction()
578 {
579 auto host = GetHost();
580 CHECK_NULL_VOID(host);
581 auto accessibilityProperty = host->GetAccessibilityProperty<AccessibilityProperty>();
582 CHECK_NULL_VOID(accessibilityProperty);
583 accessibilityProperty->SetActionScrollForward([weakPtr = WeakClaim(this)]() {
584 const auto& pattern = weakPtr.Upgrade();
585 CHECK_NULL_VOID(pattern);
586 if (pattern->IsScrollable() && pattern->GetScrollableDistance() > 0.0f) {
587 pattern->ScrollPage(false, true);
588 }
589 });
590
591 accessibilityProperty->SetActionScrollBackward([weakPtr = WeakClaim(this)]() {
592 const auto& pattern = weakPtr.Upgrade();
593 CHECK_NULL_VOID(pattern);
594 if (pattern->IsScrollable() && pattern->GetScrollableDistance() > 0.0f) {
595 pattern->ScrollPage(true, true);
596 }
597 });
598 }
599
GetOffsetToScroll(const RefPtr<FrameNode> & childFrame) const600 OffsetF ScrollPattern::GetOffsetToScroll(const RefPtr<FrameNode>& childFrame) const
601 {
602 auto frameNode = GetHost();
603 CHECK_NULL_RETURN(frameNode, OffsetF());
604 CHECK_NULL_RETURN(childFrame, OffsetF());
605 auto childGeometryNode = childFrame->GetGeometryNode();
606 CHECK_NULL_RETURN(childGeometryNode, OffsetF());
607 OffsetF result = childGeometryNode->GetFrameOffset();
608 auto parent = childFrame->GetParent();
609 while (parent) {
610 auto parentFrame = AceType::DynamicCast<FrameNode>(parent);
611 if (!parentFrame) {
612 parent = parent->GetParent();
613 continue;
614 }
615 if (parentFrame == frameNode) {
616 return result;
617 }
618 auto parentGeometryNode = parentFrame->GetGeometryNode();
619 if (!parentGeometryNode) {
620 parent = parent->GetParent();
621 continue;
622 }
623 result += parentGeometryNode->GetFrameOffset();
624 parent = parent->GetParent();
625 }
626 return OffsetF(0.0, 0.0);
627 }
628
ScrollToNode(const RefPtr<FrameNode> & focusFrameNode)629 bool ScrollPattern::ScrollToNode(const RefPtr<FrameNode>& focusFrameNode)
630 {
631 CHECK_NULL_RETURN(focusFrameNode, false);
632 auto focusGeometryNode = focusFrameNode->GetGeometryNode();
633 CHECK_NULL_RETURN(focusGeometryNode, false);
634 auto focusNodeSize = focusGeometryNode->GetFrameSize();
635 auto focusNodeOffsetToScrolll = GetOffsetToScroll(focusFrameNode);
636 auto scrollFrame = GetHost();
637 CHECK_NULL_RETURN(scrollFrame, false);
638 auto scrollGeometry = scrollFrame->GetGeometryNode();
639 CHECK_NULL_RETURN(scrollGeometry, false);
640 auto scrollFrameSize = scrollGeometry->GetFrameSize();
641 LOGD("Child: %{public}s/%{public}d on focus. Size is (%{public}f,%{public}f). Offset to Scroll is "
642 "(%{public}f,%{public}f). Scroll size is (%{public}f,%{public}f)",
643 focusFrameNode->GetTag().c_str(), focusFrameNode->GetId(), focusNodeSize.Width(), focusNodeSize.Height(),
644 focusNodeOffsetToScrolll.GetX(), focusNodeOffsetToScrolll.GetY(), scrollFrameSize.Width(),
645 scrollFrameSize.Height());
646
647 float focusNodeDiffToScroll =
648 GetAxis() == Axis::VERTICAL ? focusNodeOffsetToScrolll.GetY() : focusNodeOffsetToScrolll.GetX();
649 if (NearZero(focusNodeDiffToScroll)) {
650 return false;
651 }
652 float focusNodeLength = GetAxis() == Axis::VERTICAL ? focusNodeSize.Height() : focusNodeSize.Width();
653 float scrollFrameLength = GetAxis() == Axis::VERTICAL ? scrollFrameSize.Height() : scrollFrameSize.Width();
654 float moveOffset = 0.0;
655 if (LessNotEqual(focusNodeDiffToScroll, 0)) {
656 moveOffset = -focusNodeDiffToScroll;
657 } else if (GreatNotEqual(focusNodeDiffToScroll + focusNodeLength, scrollFrameLength)) {
658 moveOffset = scrollFrameLength - focusNodeDiffToScroll - focusNodeLength;
659 }
660 if (!NearZero(moveOffset)) {
661 LOGD("Scroll offset: %{public}f on axis: %{public}d", moveOffset, GetAxis());
662 return OnScrollCallback(moveOffset, SCROLL_FROM_FOCUS_JUMP);
663 }
664 return false;
665 }
666
CalePredictSnapOffset(float delta)667 std::optional<float> ScrollPattern::CalePredictSnapOffset(float delta)
668 {
669 std::optional<float> predictSnapOffset;
670 CHECK_NULL_RETURN_NOLOG(!snapOffsets_.empty(), predictSnapOffset);
671 CHECK_NULL_RETURN_NOLOG(GetScrollSnapAlign() != ScrollSnapAlign::NONE, predictSnapOffset);
672 float finalPosition = currentOffset_ + delta;
673 if (!IsSnapToInterval()) {
674 if (!enableSnapToSide_.first) {
675 if (GreatNotEqual(finalPosition, *(snapOffsets_.begin() + 1)) ||
676 GreatNotEqual(currentOffset_, *(snapOffsets_.begin() + 1))) {
677 return predictSnapOffset;
678 }
679 }
680 if (!enableSnapToSide_.second) {
681 if (LessNotEqual(finalPosition, *(snapOffsets_.rbegin() + 1)) ||
682 LessNotEqual(currentOffset_, *(snapOffsets_.rbegin() + 1))) {
683 return predictSnapOffset;
684 }
685 }
686 }
687 float head = 0.0f;
688 float tail = -scrollableDistance_;
689 if (GreatOrEqual(finalPosition, head) || LessOrEqual(finalPosition, tail)) {
690 return predictSnapOffset;
691 } else if (LessNotEqual(finalPosition, head) && GreatOrEqual(finalPosition, *(snapOffsets_.begin()))) {
692 predictSnapOffset = *(snapOffsets_.begin());
693 } else if (GreatNotEqual(finalPosition, tail) && LessOrEqual(finalPosition, *(snapOffsets_.rbegin()))) {
694 predictSnapOffset = *(snapOffsets_.rbegin());
695 } else {
696 auto iter = snapOffsets_.begin() + 1;
697 float start = *(iter - 1);
698 float end = *(iter);
699 for (; iter != snapOffsets_.end(); ++iter) {
700 if (GreatOrEqual(finalPosition, *iter)) {
701 start = *(iter - 1);
702 end = *(iter);
703 predictSnapOffset = (LessNotEqual(start - finalPosition, finalPosition - end) ? start : end);
704 break;
705 }
706 }
707 }
708 if (predictSnapOffset.has_value()) {
709 predictSnapOffset = predictSnapOffset.value() - currentOffset_;
710 LOGD("CalePredictSnapOffset predictSnapOffset:%{public}f", predictSnapOffset.value());
711 }
712 return predictSnapOffset;
713 }
714
CaleSnapOffsets()715 void ScrollPattern::CaleSnapOffsets()
716 {
717 auto scrollSnapAlign = GetScrollSnapAlign();
718 std::vector<float>().swap(snapOffsets_);
719 CHECK_NULL_VOID_NOLOG(scrollSnapAlign != ScrollSnapAlign::NONE);
720 if (IsSnapToInterval()) {
721 CaleSnapOffsetsByInterval(scrollSnapAlign);
722 } else {
723 CaleSnapOffsetsByPaginations();
724 }
725 }
726
CaleSnapOffsetsByInterval(ScrollSnapAlign scrollSnapAlign)727 void ScrollPattern::CaleSnapOffsetsByInterval(ScrollSnapAlign scrollSnapAlign)
728 {
729 CHECK_NULL_VOID_NOLOG(Positive(intervalSize_.Value()));
730 auto mainSize = GetMainAxisSize(viewPort_, GetAxis());
731 auto extentMainSize = GetMainAxisSize(viewPortExtent_, GetAxis());
732 auto start = 0.0f;
733 auto end = -scrollableDistance_;
734 auto snapOffset = 0.0f;
735 auto intervalSize = 0.0f;
736 auto sizeDelta = 0.0f;
737 if (intervalSize_.Unit() == DimensionUnit::PERCENT) {
738 intervalSize = intervalSize_.Value() * mainSize;
739 } else {
740 intervalSize = intervalSize_.ConvertToPx();
741 }
742 float temp = static_cast<int32_t>(extentMainSize / intervalSize) * intervalSize;
743 switch (scrollSnapAlign) {
744 case ScrollSnapAlign::START:
745 start = 0.0f;
746 end = -temp;
747 break;
748 case ScrollSnapAlign::CENTER:
749 sizeDelta = (mainSize - intervalSize) / 2;
750 start = Positive(sizeDelta) ? sizeDelta - static_cast<int32_t>(sizeDelta / intervalSize) * intervalSize
751 : sizeDelta;
752 end = -temp + (mainSize - extentMainSize + temp) / 2;
753 break;
754 case ScrollSnapAlign::END:
755 sizeDelta = mainSize - intervalSize;
756 start = Positive(sizeDelta) ? mainSize - static_cast<int32_t>(mainSize / intervalSize) * intervalSize
757 : sizeDelta;
758 end = -scrollableDistance_;
759 break;
760 default:
761 break;
762 }
763 if (!Positive(start)) {
764 snapOffsets_.emplace_back(start);
765 }
766 snapOffset = start - intervalSize;
767 while (GreatOrEqual(snapOffset, -scrollableDistance_) && GreatOrEqual(snapOffset, end)) {
768 snapOffsets_.emplace_back(snapOffset);
769 snapOffset -= intervalSize;
770 }
771 if (GreatNotEqual(end, -scrollableDistance_)) {
772 snapOffsets_.emplace_back(end);
773 }
774 }
775
CaleSnapOffsetsByPaginations()776 void ScrollPattern::CaleSnapOffsetsByPaginations()
777 {
778 auto mainSize = GetMainAxisSize(viewPort_, GetAxis());
779 auto start = 0.0f;
780 auto end = -scrollableDistance_;
781 auto snapOffset = 0.0f;
782 snapOffsets_.emplace_back(start);
783 int32_t length = 0;
784 if (static_cast<int32_t>(snapPaginations_.size()) > 0 && NearZero(snapPaginations_[length].Value())) {
785 length++;
786 }
787 for (; length < static_cast<int32_t>(snapPaginations_.size()); length++) {
788 if (snapPaginations_[length].Unit() == DimensionUnit::PERCENT) {
789 snapOffset = -(snapPaginations_[length].Value() * mainSize);
790 } else {
791 snapOffset = -snapPaginations_[length].ConvertToPx();
792 }
793 if (GreatNotEqual(snapOffset, -scrollableDistance_)) {
794 snapOffsets_.emplace_back(snapOffset);
795 } else {
796 break;
797 }
798 }
799 snapOffsets_.emplace_back(end);
800 }
801
NeedScrollSnapToSide(float delta)802 bool ScrollPattern::NeedScrollSnapToSide(float delta)
803 {
804 CHECK_NULL_RETURN_NOLOG(GetScrollSnapAlign() != ScrollSnapAlign::NONE, false);
805 CHECK_NULL_RETURN_NOLOG(!IsSnapToInterval(), false);
806 auto finalPosition = currentOffset_ + delta;
807 CHECK_NULL_RETURN_NOLOG(static_cast<int32_t>(snapOffsets_.size()) > 2, false);
808 if (!enableSnapToSide_.first) {
809 if (GreatOrEqual(currentOffset_, *(snapOffsets_.begin() + 1)) &&
810 LessOrEqual(finalPosition, *(snapOffsets_.begin() + 1))) {
811 return true;
812 }
813 }
814 if (!enableSnapToSide_.second) {
815 if (LessOrEqual(currentOffset_, *(snapOffsets_.rbegin() + 1)) &&
816 GreatOrEqual(finalPosition, *(snapOffsets_.rbegin() + 1))) {
817 return true;
818 }
819 }
820 return false;
821 }
822 } // namespace OHOS::Ace::NG
823