• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2022-2025 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/log/dump_log.h"
19 #include "core/components_ng/pattern/scrollable/scrollable_animation_consts.h"
20 #include "core/components_ng/property/measure_utils.h"
21 
22 namespace OHOS::Ace::NG {
23 
24 namespace {
25 constexpr float SCROLL_BY_SPEED = 250.0f; // move 250 pixels per second
26 constexpr float UNIT_CONVERT = 1000.0f;   // 1s convert to 1000ms
27 constexpr Dimension SELECT_SCROLL_MIN_WIDTH = 64.0_vp;
28 constexpr int32_t COLUMN_NUM = 2;
29 constexpr float SCROLL_PAGING_SPEED_THRESHOLD = 1200.0f;
30 constexpr int32_t SCROLL_LAYOUT_INFO_COUNT = 30;
31 constexpr int32_t SCROLL_MEASURE_INFO_COUNT = 30;
32 constexpr double SCROLL_SNAP_INTERVAL_SIZE_MIN_VALUE = 1.0;
33 #ifdef SUPPORT_DIGITAL_CROWN
34 constexpr int32_t CROWN_EVENT_NUN_THRESH_MIN = 5;
35 constexpr int64_t CROWN_VIBRATOR_INTERVAL_TIME = 30 * 1000 * 1000;
36 constexpr char CROWN_VIBRATOR_WEAK[] = "watchhaptic.feedback.crown.strength2";
37 #endif
38 } // namespace
39 
OnModifyDone()40 void ScrollPattern::OnModifyDone()
41 {
42     Pattern::OnModifyDone();
43     auto host = GetHost();
44     CHECK_NULL_VOID(host);
45     auto layoutProperty = host->GetLayoutProperty<ScrollLayoutProperty>();
46     CHECK_NULL_VOID(layoutProperty);
47     auto paintProperty = host->GetPaintProperty<ScrollablePaintProperty>();
48     CHECK_NULL_VOID(paintProperty);
49     const auto axis = layoutProperty->GetAxis().value_or(Axis::VERTICAL);
50     const bool axisChanged = axis != GetAxis();
51     if (axisChanged) {
52         SetAxis(axis);
53         ResetPosition();
54     }
55     UpdatePinchGesture();
56     if (!GetScrollableEvent()) {
57         AddScrollEvent();
58 #ifdef SUPPORT_DIGITAL_CROWN
59         SetDigitalCrownEvent();
60 #endif
61     }
62     SetEdgeEffect();
63     if (axisChanged) {
64         // need to init after scrollableEvent
65         if (axis == Axis::FREE) {
66             freeScroll_ = MakeRefPtr<FreeScrollController>(*this);
67             SetScrollEnabled(true); // always enable scrollEvent
68             scrollBar2d_ = MakeRefPtr<ScrollBar2D>(*this);
69             SetScrollBar(DisplayMode::OFF); // turn off single-axis scrollBar
70             auto* ctx = GetRenderContext();
71             CHECK_NULL_VOID(ctx);
72             ctx->RemoveOverlayModifier(GetScrollBarOverlayModifier());
73         } else {
74             freeScroll_.Reset();
75             scrollBar2d_.Reset();
76         }
77     }
78     if (scrollBar2d_) {
79         scrollBar2d_->Update(paintProperty->GetScrollBarProperty());
80     } else {
81         SetScrollBar(paintProperty->GetScrollBarProperty());
82     }
83     SetAccessibilityAction();
84     if (scrollSnapUpdate_) {
85         host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
86     }
87     Register2DragDropManager();
88     auto overlayNode = host->GetOverlayNode();
89     if (!overlayNode && paintProperty->GetFadingEdge().value_or(false)) {
90         CreateAnalyzerOverlay(host);
91     }
92 }
93 
CreatePaintProperty()94 RefPtr<PaintProperty> ScrollPattern::CreatePaintProperty()
95 {
96     auto defaultDisplayMode = GetDefaultScrollBarDisplayMode();
97     auto property = MakeRefPtr<ScrollPaintProperty>();
98     property->UpdateScrollBarMode(defaultDisplayMode);
99     return property;
100 }
101 
CreateNodePaintMethod()102 RefPtr<NodePaintMethod> ScrollPattern::CreateNodePaintMethod()
103 {
104     auto host = GetHost();
105     CHECK_NULL_RETURN(host, nullptr);
106     auto layoutProperty = host->GetLayoutProperty<ScrollLayoutProperty>();
107     CHECK_NULL_RETURN(layoutProperty, nullptr);
108     auto layoutDirection = layoutProperty->GetNonAutoLayoutDirection();
109     auto drawDirection = (layoutDirection == TextDirection::RTL);
110     auto paint = MakeRefPtr<ScrollPaintMethod>(GetAxis() == Axis::HORIZONTAL, drawDirection);
111     if (scrollBar2d_) {
112         paint->Set2DPainter(scrollBar2d_);
113     } else {
114         paint->SetScrollBar(GetScrollBar());
115         paint->SetScrollBarOverlayModifier(GetScrollBarOverlayModifier());
116     }
117     auto scrollEffect = GetScrollEdgeEffect();
118     if (scrollEffect && scrollEffect->IsFadeEffect()) {
119         paint->SetEdgeEffect(scrollEffect);
120     }
121     if (!scrollContentModifier_) {
122         scrollContentModifier_ = AceType::MakeRefPtr<ScrollContentModifier>();
123     }
124     paint->SetContentModifier(scrollContentModifier_);
125     UpdateFadingEdge(paint);
126     return paint;
127 }
128 
OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper> & dirty,const DirtySwapConfig & config)129 bool ScrollPattern::OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper>& dirty, const DirtySwapConfig& config)
130 {
131     if (config.skipMeasure && config.skipLayout) {
132         return false;
133     }
134     auto host = GetHost();
135     CHECK_NULL_RETURN(host, false);
136     if (!SetScrollProperties(dirty, host)) {
137         return false;
138     }
139     UpdateScrollBarOffset();
140     if (config.frameSizeChange && isInitialized_) {
141         if (GetScrollBar() != nullptr) {
142             GetScrollBar()->ScheduleDisappearDelayTask();
143         }
144     }
145     auto eventHub = host->GetOrCreateEventHub<ScrollEventHub>();
146     CHECK_NULL_RETURN(eventHub, false);
147     PrintOffsetLog(AceLogTag::ACE_SCROLL, host->GetId(), prevOffset_ - currentOffset_);
148     FireOnDidScroll(prevOffset_ - currentOffset_);
149     auto onReachStart = eventHub->GetOnReachStart();
150     auto onJSFrameNodeReachStart = eventHub->GetJSFrameNodeOnReachStart();
151     FireOnReachStart(onReachStart, onJSFrameNodeReachStart);
152     auto onReachEnd = eventHub->GetOnReachEnd();
153     auto onJSFrameNodeReachEnd = eventHub->GetJSFrameNodeOnReachEnd();
154     FireOnReachEnd(onReachEnd, onJSFrameNodeReachEnd);
155     auto onScrollStop = eventHub->GetOnScrollStop();
156     auto onJSFrameNodeScrollStop = eventHub->GetJSFrameNodeOnScrollStop();
157     OnScrollStop(onScrollStop, onJSFrameNodeScrollStop);
158     ScrollSnapTrigger();
159     CheckScrollable();
160     prevOffset_ = currentOffset_;
161     auto geometryNode = host->GetGeometryNode();
162     CHECK_NULL_RETURN(geometryNode, false);
163     auto offsetRelativeToWindow = host->GetOffsetRelativeToWindow();
164     auto globalViewPort = RectF(offsetRelativeToWindow, geometryNode->GetFrameRect().GetSize());
165     host->SetViewPort(globalViewPort);
166     isInitialized_ = true;
167     ChangeAnimateOverScroll();
168     SetScrollSource(SCROLL_FROM_NONE);
169     auto paintProperty = GetPaintProperty<ScrollablePaintProperty>();
170     CHECK_NULL_RETURN(paintProperty, false);
171     if (scrollEdgeType_ != ScrollEdgeType::SCROLL_NONE && AnimateStoped()) {
172         scrollEdgeType_ = ScrollEdgeType::SCROLL_NONE;
173     }
174     ChangeCanStayOverScroll();
175     return paintProperty->GetFadingEdge().value_or(false) ||
176             ((config.frameSizeChange || config.contentSizeChange) && paintProperty->GetContentClip().has_value());
177 }
178 
SetScrollProperties(const RefPtr<LayoutWrapper> & dirty,const RefPtr<FrameNode> & host)179 bool ScrollPattern::SetScrollProperties(const RefPtr<LayoutWrapper>& dirty, const RefPtr<FrameNode>& host)
180 {
181     auto layoutAlgorithmWrapper = DynamicCast<LayoutAlgorithmWrapper>(dirty->GetLayoutAlgorithm());
182     CHECK_NULL_RETURN(layoutAlgorithmWrapper, false);
183     auto layoutAlgorithm = DynamicCast<ScrollLayoutAlgorithm>(layoutAlgorithmWrapper->GetLayoutAlgorithm());
184     CHECK_NULL_RETURN(layoutAlgorithm, false);
185     currentOffset_ = layoutAlgorithm->GetCurrentOffset();
186     if (freeScroll_ && scrollBar2d_) {
187         freeScroll_->OnLayoutFinished(layoutAlgorithm->GetFreeOffset(), layoutAlgorithm->GetScrollableArea());
188         scrollBar2d_->SyncLayout(
189             layoutAlgorithm->GetFreeOffset(), layoutAlgorithm->GetViewSize(), layoutAlgorithm->GetViewPortExtent());
190     }
191     auto oldScrollableDistance = scrollableDistance_;
192     scrollableDistance_ = layoutAlgorithm->GetScrollableDistance();
193     if (!NearEqual(oldScrollableDistance, scrollableDistance_)) {
194         CheckScrollToEdge();
195         AddScrollLayoutInfo();
196     }
197 
198     if (LessNotEqual(scrollableDistance_, oldScrollableDistance) && !GetCanStayOverScroll()) {
199         CheckRestartSpring(true);
200     }
201     auto axis = GetAxis();
202     auto oldMainSize = GetMainAxisSize(viewPort_, axis);
203     auto newMainSize = GetMainAxisSize(layoutAlgorithm->GetViewPort(), axis);
204     auto oldExtentMainSize = GetMainAxisSize(viewPortExtent_, axis);
205     auto newExtentMainSize = GetMainAxisSize(layoutAlgorithm->GetViewPortExtent(), axis);
206     viewPortLength_ = layoutAlgorithm->GetViewPortLength();
207     viewPort_ = layoutAlgorithm->GetViewPort();
208     viewSize_ = layoutAlgorithm->GetViewSize();
209     viewPortExtent_ = layoutAlgorithm->GetViewPortExtent();
210     if (IsEnablePagingValid()) {
211         SetIntervalSize(Dimension(static_cast<double>(viewPortLength_)));
212     }
213     if (scrollSnapUpdate_ || !NearEqual(oldMainSize, newMainSize) || !NearEqual(oldExtentMainSize, newExtentMainSize)) {
214         CaleSnapOffsets(host);
215         scrollSnapUpdate_ = false;
216     }
217     ProcessZoomScale();
218     return true;
219 }
220 
ScrollSnapTrigger()221 bool ScrollPattern::ScrollSnapTrigger()
222 {
223     if (ScrollableIdle() && !AnimateRunning()) {
224         SnapAnimationOptions snapAnimationOptions;
225         if (StartSnapAnimation(snapAnimationOptions)) {
226             if (!IsScrolling()) {
227                 FireOnScrollStart();
228             }
229             return true;
230         }
231     }
232     return false;
233 }
234 
CheckScrollable()235 void ScrollPattern::CheckScrollable()
236 {
237     if (freeScroll_) {
238         return;
239     }
240     auto host = GetHost();
241     CHECK_NULL_VOID(host);
242     auto layoutProperty = host->GetLayoutProperty<ScrollLayoutProperty>();
243     CHECK_NULL_VOID(layoutProperty);
244     if (GreatNotEqual(scrollableDistance_, 0.0f)) {
245         SetScrollEnabled(layoutProperty->GetScrollEnabled().value_or(true));
246     } else {
247         SetScrollEnabled(layoutProperty->GetScrollEnabled().value_or(true) && GetAlwaysEnabled());
248     }
249 }
250 
OnScrollCallback(float offset,int32_t source)251 bool ScrollPattern::OnScrollCallback(float offset, int32_t source)
252 {
253     if (source != SCROLL_FROM_START) {
254         if (GetAxis() == Axis::NONE) {
255             return false;
256         }
257         if (!AnimateStoped()) {
258             return false;
259         }
260         auto adjustOffset = static_cast<float>(offset);
261         AdjustOffset(adjustOffset, source);
262         return UpdateCurrentOffset(adjustOffset, source);
263     } else {
264         if (GetSnapType() == SnapType::SCROLL_SNAP) {
265             SetScrollableCurrentPos(currentOffset_);
266         }
267         FireOnScrollStart();
268     }
269     return true;
270 }
271 
OnScrollEndCallback()272 void ScrollPattern::OnScrollEndCallback()
273 {
274     auto host = GetHost();
275     CHECK_NULL_VOID(host);
276     auto eventHub = host->GetOrCreateEventHub<ScrollEventHub>();
277     CHECK_NULL_VOID(eventHub);
278     auto scrollEndEvent = eventHub->GetScrollEndEvent();
279     if (scrollEndEvent) {
280         scrollEndEvent();
281     }
282     if (AnimateStoped()) {
283         scrollStop_ = true;
284         host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
285     }
286 #ifdef SUPPORT_DIGITAL_CROWN
287     crownEventNum_ = 0;
288 #endif
289 }
290 
ResetPosition()291 void ScrollPattern::ResetPosition()
292 {
293     currentOffset_ = 0.0f;
294     lastOffset_ = 0.0f;
295 }
296 
IsAtTop() const297 bool ScrollPattern::IsAtTop() const
298 {
299     return GreatOrEqual(currentOffset_, 0.0);
300 }
301 
IsAtBottom(bool considerRepeat) const302 bool ScrollPattern::IsAtBottom(bool considerRepeat) const
303 {
304     if (LessNotEqual(scrollableDistance_, 0.0f)) {
305         return LessOrEqual(currentOffset_, 0.0f);
306     }
307     return LessOrEqual(currentOffset_, -scrollableDistance_);
308 }
309 
GetOverScrollOffset(double delta) const310 OverScrollOffset ScrollPattern::GetOverScrollOffset(double delta) const
311 {
312     OverScrollOffset offset = { 0, 0 };
313     auto startPos = currentOffset_;
314     auto newStartPos = startPos + delta;
315     if (startPos > 0 && newStartPos > 0) {
316         offset.start = delta;
317     }
318     if (startPos > 0 && newStartPos <= 0) {
319         offset.start = -startPos;
320     }
321     if (startPos <= 0 && newStartPos > 0) {
322         offset.start = newStartPos;
323     }
324 
325     auto endPos = currentOffset_;
326     auto newEndPos = endPos + delta;
327     auto endRefences = GreatOrEqual(scrollableDistance_, 0.0f) ? -scrollableDistance_ : 0;
328     if (endPos < endRefences && newEndPos < endRefences) {
329         offset.end = delta;
330     }
331     if (endPos < endRefences && newEndPos >= endRefences) {
332         offset.end = endRefences - endPos;
333     }
334     if (endPos >= endRefences && newEndPos < endRefences) {
335         offset.end = newEndPos - endRefences;
336     }
337     return offset;
338 }
339 
IsOutOfBoundary(bool useCurrentDelta)340 bool ScrollPattern::IsOutOfBoundary(bool useCurrentDelta)
341 {
342     if (Positive(scrollableDistance_)) {
343         return Positive(currentOffset_) || LessNotEqual(currentOffset_, -scrollableDistance_);
344     } else {
345         return !NearZero(currentOffset_);
346     }
347 }
348 
ScrollPageCheck(float delta,int32_t source)349 bool ScrollPattern::ScrollPageCheck(float delta, int32_t source)
350 {
351     return true;
352 }
353 
AdjustOffset(float & delta,int32_t source)354 void ScrollPattern::AdjustOffset(float& delta, int32_t source)
355 {
356     if (NearZero(delta) || NearZero(viewPortLength_) || source == SCROLL_FROM_ANIMATION ||
357         source == SCROLL_FROM_ANIMATION_SPRING || source == SCROLL_FROM_FOCUS_JUMP) {
358         return;
359     }
360     // the distance above the top, if lower than top, it is zero
361     float overScrollPastStart = 0.0f;
362     // the distance below the bottom, if higher than bottom, it is zero
363     float overScrollPastEnd = 0.0f;
364     float overScrollPast = 0.0f;
365     // not consider rowReverse or colReverse
366     overScrollPastStart = std::max(currentOffset_, 0.0f);
367     if (Positive(scrollableDistance_)) {
368         overScrollPastEnd = std::max(-scrollableDistance_ - currentOffset_, 0.0f);
369     } else {
370         overScrollPastEnd = std::abs(std::min(currentOffset_, 0.0f));
371     }
372     overScrollPast = std::max(overScrollPastStart, overScrollPastEnd);
373     if (overScrollPast == 0.0f) {
374         return;
375     }
376     float friction = CalculateFriction((overScrollPast - std::abs(delta)) / viewPortLength_);
377     delta = delta * friction;
378 }
379 
ValidateOffset(int32_t source,float willScrollOffset)380 float ScrollPattern::ValidateOffset(int32_t source, float willScrollOffset)
381 {
382     if (LessOrEqual(scrollableDistance_, 0.0f) || source == SCROLL_FROM_JUMP) {
383         return willScrollOffset;
384     }
385 
386     // restrict position between top and bottom
387     if (IsRestrictBoundary() || source == SCROLL_FROM_BAR || source == SCROLL_FROM_BAR_FLING ||
388         source == SCROLL_FROM_ROTATE || source == SCROLL_FROM_AXIS) {
389         if (GetAxis() == Axis::HORIZONTAL) {
390             if (IsRowReverse()) {
391                 willScrollOffset = std::clamp(willScrollOffset, 0.0f, scrollableDistance_);
392             } else {
393                 willScrollOffset = std::clamp(willScrollOffset, -scrollableDistance_, 0.0f);
394             }
395         } else {
396             willScrollOffset = std::clamp(willScrollOffset, -scrollableDistance_, 0.0f);
397         }
398     }
399     return willScrollOffset;
400 }
401 
ValidateOffset(int32_t source)402 void ScrollPattern::ValidateOffset(int32_t source)
403 {
404     if (LessOrEqual(scrollableDistance_, 0.0f) || source == SCROLL_FROM_JUMP) {
405         return;
406     }
407 
408     // restrict position between top and bottom
409     if (IsRestrictBoundary() || source == SCROLL_FROM_BAR || source == SCROLL_FROM_BAR_FLING ||
410         source == SCROLL_FROM_ROTATE || source == SCROLL_FROM_AXIS) {
411         if (GetAxis() == Axis::HORIZONTAL) {
412             if (IsRowReverse()) {
413                 currentOffset_ = std::clamp(currentOffset_, 0.0f, scrollableDistance_);
414             } else {
415                 currentOffset_ = std::clamp(currentOffset_, -scrollableDistance_, 0.0f);
416             }
417         } else {
418             currentOffset_ = std::clamp(currentOffset_, -scrollableDistance_, 0.0f);
419         }
420     }
421 }
422 
HandleScrollPosition(float scroll)423 void ScrollPattern::HandleScrollPosition(float scroll)
424 {
425     auto eventHub = GetOrCreateEventHub<ScrollEventHub>();
426     CHECK_NULL_VOID(eventHub);
427     auto onScroll = eventHub->GetOnScrollEvent();
428     CHECK_NULL_VOID(onScroll);
429     // not consider async call
430     Dimension scrollX(0, DimensionUnit::VP);
431     Dimension scrollY(0, DimensionUnit::VP);
432     Dimension scrollPx(scroll, DimensionUnit::PX);
433     auto scrollVpValue = scrollPx.ConvertToVp();
434     if (GetAxis() == Axis::HORIZONTAL) {
435         scrollX.SetValue(scrollVpValue);
436     } else {
437         scrollY.SetValue(scrollVpValue);
438     }
439     onScroll(scrollX, scrollY);
440 }
441 
FireTwoDimensionOnWillScroll(float scroll)442 float ScrollPattern::FireTwoDimensionOnWillScroll(float scroll)
443 {
444     auto eventHub = GetOrCreateEventHub<ScrollEventHub>();
445     CHECK_NULL_RETURN(eventHub, scroll);
446     auto onScroll = eventHub->GetOnWillScrollEvent();
447     auto onJsFrameNodeScroll = eventHub->GetJSFrameNodeOnScrollWillScroll();
448     auto observer = positionController_ ? positionController_->GetObserverManager() : nullptr;
449     CHECK_NULL_RETURN(onScroll || onJsFrameNodeScroll || observer, scroll);
450     Dimension scrollX(0, DimensionUnit::VP);
451     Dimension scrollY(0, DimensionUnit::VP);
452     Dimension scrollPx(scroll, DimensionUnit::PX);
453     auto scrollVpValue = scrollPx.ConvertToVp();
454     if (GetAxis() == Axis::HORIZONTAL) {
455         scrollX.SetValue(scrollVpValue);
456     } else {
457         scrollY.SetValue(scrollVpValue);
458     }
459     TwoDimensionScrollResult scrollRes { .xOffset = scrollX, .yOffset = scrollY };
460     if (onScroll) {
461         scrollRes = onScroll(scrollRes.xOffset, scrollRes.yOffset, GetScrollState(),
462             ScrollablePattern::ConvertScrollSource(GetScrollSource()));
463     }
464     if (onJsFrameNodeScroll) {
465         scrollRes = onJsFrameNodeScroll(scrollRes.xOffset, scrollRes.yOffset, GetScrollState(),
466             ScrollablePattern::ConvertScrollSource(GetScrollSource()));
467     }
468     if (observer) {
469         scrollRes = FireObserverTwoDimensionOnWillScroll(scrollRes.xOffset, scrollRes.yOffset, GetScrollState(),
470             ScrollablePattern::ConvertScrollSource(GetScrollSource()));
471     }
472     auto context = GetContext();
473     CHECK_NULL_RETURN(context, scroll);
474     if (GetAxis() == Axis::HORIZONTAL) {
475         return context->NormalizeToPx(scrollRes.xOffset);
476     } else {
477         return context->NormalizeToPx(scrollRes.yOffset);
478     }
479 }
480 
FireOnDidScroll(float scroll)481 void ScrollPattern::FireOnDidScroll(float scroll)
482 {
483     if (freeScroll_) {
484         return; // using FreeModeFireOnDidScroll
485     }
486     FireObserverOnDidScroll(scroll);
487     FireObserverOnScrollerAreaChange(scroll);
488     auto eventHub = GetOrCreateEventHub<ScrollEventHub>();
489     CHECK_NULL_VOID(eventHub);
490     auto onScroll = eventHub->GetOnDidScrollEvent();
491     auto onJSFrameNodeDidScroll = eventHub->GetJSFrameNodeOnScrollDidScroll();
492     CHECK_NULL_VOID(onScroll || onJSFrameNodeDidScroll);
493     Dimension scrollX(0, DimensionUnit::VP);
494     Dimension scrollY(0, DimensionUnit::VP);
495     Dimension scrollPx(scroll, DimensionUnit::PX);
496     auto scrollVpValue = scrollPx.ConvertToVp();
497     if (GetAxis() == Axis::HORIZONTAL) {
498         scrollX.SetValue(scrollVpValue);
499     } else {
500         scrollY.SetValue(scrollVpValue);
501     }
502     auto scrollState = GetScrollState();
503     bool isTriggered = false;
504     if (!NearZero(scroll)) {
505         if (onScroll) {
506             onScroll(scrollX, scrollY, scrollState);
507         }
508         if (onJSFrameNodeDidScroll) {
509             onJSFrameNodeDidScroll(scrollX, scrollY, scrollState);
510         }
511         isTriggered = true;
512     }
513     if (scrollStop_ && !GetScrollAbort()) {
514         if (scrollState != ScrollState::IDLE || !isTriggered) {
515             if (onScroll) {
516                 onScroll(0.0_vp, 0.0_vp, ScrollState::IDLE);
517             }
518             if (onJSFrameNodeDidScroll) {
519                 onJSFrameNodeDidScroll(0.0_vp, 0.0_vp, ScrollState::IDLE);
520             }
521         }
522     }
523 }
524 
FireOnReachStart(const OnReachEvent & onReachStart,const OnReachEvent & onJSFrameNodeReachStart)525 void ScrollPattern::FireOnReachStart(const OnReachEvent& onReachStart, const OnReachEvent& onJSFrameNodeReachStart)
526 {
527     if (freeScroll_) {
528         return; // not supported in FreeScroll mode
529     }
530     auto host = GetHost();
531     CHECK_NULL_VOID(host);
532     if (ReachStart(!isInitialized_)) {
533         FireObserverOnReachStart();
534         CHECK_NULL_VOID(onReachStart || onJSFrameNodeReachStart);
535         ACE_SCOPED_TRACE("OnReachStart, id:%d, tag:Scroll", static_cast<int32_t>(host->GetAccessibilityId()));
536         if (onReachStart) {
537             onReachStart();
538         }
539         if (onJSFrameNodeReachStart) {
540             onJSFrameNodeReachStart();
541         }
542         AddEventsFiredInfo(ScrollableEventType::ON_REACH_START);
543     }
544 }
545 
FireOnReachEnd(const OnReachEvent & onReachEnd,const OnReachEvent & onJSFrameNodeReachEnd)546 void ScrollPattern::FireOnReachEnd(const OnReachEvent& onReachEnd, const OnReachEvent& onJSFrameNodeReachEnd)
547 {
548     if (freeScroll_) {
549         return; // not supported in FreeScroll mode
550     }
551     auto host = GetHost();
552     CHECK_NULL_VOID(host);
553     if (ReachEnd(false)) {
554         FireObserverOnReachEnd();
555         CHECK_NULL_VOID(onReachEnd || onJSFrameNodeReachEnd);
556         ACE_SCOPED_TRACE("OnReachEnd, id:%d, tag:Scroll", static_cast<int32_t>(host->GetAccessibilityId()));
557         if (onReachEnd) {
558             onReachEnd();
559         }
560         if (onJSFrameNodeReachEnd) {
561             onJSFrameNodeReachEnd();
562         }
563         AddEventsFiredInfo(ScrollableEventType::ON_REACH_END);
564     } else if (!isInitialized_ && ReachEnd(true)) {
565         FireObserverOnReachEnd();
566     }
567 }
568 
IsCrashTop() const569 bool ScrollPattern::IsCrashTop() const
570 {
571     bool scrollUpToReachTop = LessNotEqual(lastOffset_, 0.0) && GreatOrEqual(currentOffset_, 0.0);
572     bool scrollDownToReachTop = GreatNotEqual(lastOffset_, 0.0) && LessOrEqual(currentOffset_, 0.0);
573     return scrollUpToReachTop || scrollDownToReachTop;
574 }
575 
IsCrashBottom() const576 bool ScrollPattern::IsCrashBottom() const
577 {
578     float minExtent = -scrollableDistance_;
579     bool scrollDownToReachEnd = GreatNotEqual(lastOffset_, minExtent) && LessOrEqual(currentOffset_, minExtent);
580     bool scrollUpToReachEnd = LessNotEqual(lastOffset_, minExtent) && GreatOrEqual(currentOffset_, minExtent);
581     return (scrollUpToReachEnd || scrollDownToReachEnd);
582 }
583 
ReachStart(bool firstLayout) const584 bool ScrollPattern::ReachStart(bool firstLayout) const
585 {
586     bool scrollUpToReachTop = (LessNotEqual(prevOffset_, 0.0) || firstLayout) && GreatOrEqual(currentOffset_, 0.0);
587     bool scrollDownToReachTop = GreatNotEqual(prevOffset_, 0.0) && LessOrEqual(currentOffset_, 0.0);
588     return scrollUpToReachTop || scrollDownToReachTop;
589 }
590 
ReachEnd(bool firstLayout) const591 bool ScrollPattern::ReachEnd(bool firstLayout) const
592 {
593     float minExtent = -scrollableDistance_;
594     bool scrollDownToReachEnd =
595         (GreatNotEqual(prevOffset_, minExtent) || firstLayout) && LessOrEqual(currentOffset_, minExtent);
596     bool scrollUpToReachEnd = LessNotEqual(prevOffset_, minExtent) && GreatOrEqual(currentOffset_, minExtent);
597     return (scrollUpToReachEnd || scrollDownToReachEnd);
598 }
599 
HandleCrashTop()600 void ScrollPattern::HandleCrashTop()
601 {
602     auto frameNode = GetHost();
603     CHECK_NULL_VOID(frameNode);
604     auto eventHub = frameNode->GetOrCreateEventHub<ScrollEventHub>();
605     CHECK_NULL_VOID(eventHub);
606     const auto& onScrollEdge = eventHub->GetScrollEdgeEvent();
607     CHECK_NULL_VOID(onScrollEdge);
608     // not consider async call
609     if (GetAxis() == Axis::HORIZONTAL) {
610         onScrollEdge(ScrollEdge::LEFT);
611         AddEventsFiredInfo(ScrollableEventType::ON_SCROLL_EDGE);
612         return;
613     }
614     onScrollEdge(ScrollEdge::TOP);
615     AddEventsFiredInfo(ScrollableEventType::ON_SCROLL_EDGE);
616 }
617 
HandleCrashBottom()618 void ScrollPattern::HandleCrashBottom()
619 {
620     auto frameNode = GetHost();
621     CHECK_NULL_VOID(frameNode);
622     auto eventHub = frameNode->GetOrCreateEventHub<ScrollEventHub>();
623     CHECK_NULL_VOID(eventHub);
624     const auto& onScrollEdge = eventHub->GetScrollEdgeEvent();
625     CHECK_NULL_VOID(onScrollEdge);
626     if (GetAxis() == Axis::HORIZONTAL) {
627         onScrollEdge(ScrollEdge::RIGHT);
628         AddEventsFiredInfo(ScrollableEventType::ON_SCROLL_EDGE);
629         return;
630     }
631     onScrollEdge(ScrollEdge::BOTTOM);
632     AddEventsFiredInfo(ScrollableEventType::ON_SCROLL_EDGE);
633 }
634 
635 #ifdef SUPPORT_DIGITAL_CROWN
StartVibrateFeedback()636 void ScrollPattern::StartVibrateFeedback()
637 {
638     if (!GetCrownEventDragging()) {
639         return;
640     }
641     if (crownEventNum_ < CROWN_EVENT_NUN_THRESH_MIN) {
642         crownEventNum_++;
643     }
644     auto currentTime = GetSysTimestamp();
645     if (!reachBoundary_ &&
646         (crownEventNum_ >= CROWN_EVENT_NUN_THRESH_MIN && currentTime - lastTime_ > CROWN_VIBRATOR_INTERVAL_TIME)) {
647         VibratorUtils::StartVibraFeedback(CROWN_VIBRATOR_WEAK);
648         lastTime_ = GetSysTimestamp();
649     }
650 }
651 #endif
652 
UpdateCurrentOffset(float delta,int32_t source)653 bool ScrollPattern::UpdateCurrentOffset(float delta, int32_t source)
654 {
655     auto host = GetHost();
656     CHECK_NULL_RETURN(host, false);
657     if (source != SCROLL_FROM_JUMP && !HandleEdgeEffect(delta, source, viewSize_)) {
658         if (IsOutOfBoundary()) {
659             host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
660         }
661         return false;
662     }
663     SetScrollSource(source);
664     FireAndCleanScrollingListener();
665     lastOffset_ = currentOffset_;
666     auto willScrollPosition = currentOffset_ + delta;
667     willScrollPosition = ValidateOffset(source, willScrollPosition);
668     auto userOffset = FireTwoDimensionOnWillScroll(currentOffset_ - willScrollPosition);
669     currentOffset_ -= userOffset;
670     ValidateOffset(source);
671     HandleScrollPosition(userOffset);
672     host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
673     MarkScrollBarProxyDirty();
674 #ifdef SUPPORT_DIGITAL_CROWN
675     if (source == SCROLL_FROM_CROWN && !ReachStart(true) && !ReachEnd(true)) {
676         StartVibrateFeedback();
677     }
678 #endif
679     if (IsCrashTop()) {
680         TAG_LOGI(AceLogTag::ACE_SCROLLABLE, "UpdateCurrentOffset==>[HandleCrashTop();]");
681 #ifdef SUPPORT_DIGITAL_CROWN
682         SetReachBoundary(true);
683 #endif
684         HandleCrashTop();
685     } else if (IsCrashBottom()) {
686         TAG_LOGI(AceLogTag::ACE_SCROLLABLE, "UpdateCurrentOffset==>[HandleCrashBottom();]");
687 #ifdef SUPPORT_DIGITAL_CROWN
688         SetReachBoundary(true);
689 #endif
690         HandleCrashBottom();
691     }
692 #ifdef SUPPORT_DIGITAL_CROWN
693     if (!IsCrashBottom() && !IsCrashTop()) {
694         SetReachBoundary(false);
695     }
696 #endif
697     return true;
698 }
699 
OnAnimateStop()700 void ScrollPattern::OnAnimateStop()
701 {
702     if (!GetIsDragging() || GetScrollAbort()) {
703         auto host = GetHost();
704         CHECK_NULL_VOID(host);
705         host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
706         scrollStop_ = true;
707     }
708 }
709 
ScrollToEdge(ScrollEdgeType scrollEdgeType,bool smooth)710 void ScrollPattern::ScrollToEdge(ScrollEdgeType scrollEdgeType, bool smooth)
711 {
712     if (scrollEdgeType == ScrollEdgeType::SCROLL_NONE) {
713         return;
714     }
715     if (LessOrEqual(scrollableDistance_, 0.0)) {
716         return;
717     }
718     float distance =
719         scrollEdgeType == ScrollEdgeType::SCROLL_TOP ? -currentOffset_ : (-scrollableDistance_ - currentOffset_);
720     auto host = GetHost();
721     CHECK_NULL_VOID(host);
722     ACE_SCOPED_TRACE("Scroll ScrollToEdge scrollEdgeType:%zu, offset:%f, id:%d", scrollEdgeType, distance,
723         static_cast<int32_t>(host->GetAccessibilityId()));
724     ScrollBy(distance, distance, smooth);
725     scrollEdgeType_ = scrollEdgeType;
726 }
727 
CheckScrollToEdge()728 void ScrollPattern::CheckScrollToEdge()
729 {
730     if (scrollEdgeType_ != ScrollEdgeType::SCROLL_NONE) {
731         ScrollToEdge(scrollEdgeType_, true);
732     }
733 }
734 
ScrollBy(float pixelX,float pixelY,bool smooth,const std::function<void ()> & onFinish)735 void ScrollPattern::ScrollBy(float pixelX, float pixelY, bool smooth, const std::function<void()>& onFinish)
736 {
737     float distance = (GetAxis() == Axis::VERTICAL) ? pixelY : pixelX;
738     if (NearZero(distance)) {
739         return;
740     }
741     float position = currentOffset_ + distance;
742     SetIsOverScroll(false);
743     if (smooth) {
744         AnimateTo(-position, fabs(distance) * UNIT_CONVERT / SCROLL_BY_SPEED, Curves::EASE_OUT, true, false, false);
745         return;
746     }
747     JumpToPosition(position);
748 }
749 
ScrollPage(bool reverse,bool smooth,AccessibilityScrollType scrollType)750 void ScrollPattern::ScrollPage(bool reverse, bool smooth, AccessibilityScrollType scrollType)
751 {
752     auto host = GetHost();
753     CHECK_NULL_VOID(host);
754     float distance = reverse ? viewPortLength_ : -viewPortLength_;
755     if (scrollType == AccessibilityScrollType::SCROLL_HALF) {
756         distance = distance / 2.f;
757     }
758     ACE_SCOPED_TRACE(
759         "Scroll ScrollPage distance:%f, id:%d", distance, static_cast<int32_t>(host->GetAccessibilityId()));
760     ScrollBy(distance, distance, smooth);
761 }
762 
JumpToPosition(float position,int32_t source)763 void ScrollPattern::JumpToPosition(float position, int32_t source)
764 {
765     // If an animation is playing, stop it.
766     auto lastAnimateRunning = AnimateRunning();
767     StopAnimate();
768     DoJump(position, source);
769     // AccessibilityEventType::SCROLL_END
770     if (lastAnimateRunning) {
771         SetScrollAbort(false);
772     }
773 }
774 
ScrollTo(float position)775 void ScrollPattern::ScrollTo(float position)
776 {
777     SetAnimateCanOverScroll(GetCanStayOverScroll());
778     JumpToPosition(-position, SCROLL_FROM_JUMP);
779     SetIsOverScroll(GetCanStayOverScroll());
780 }
781 
DoJump(float position,int32_t source)782 void ScrollPattern::DoJump(float position, int32_t source)
783 {
784     float setPosition = (GetAxis() == Axis::HORIZONTAL && IsRowReverse()) ? -position : position;
785     if ((!NearEqual(currentOffset_, setPosition) && GreatOrEqual(scrollableDistance_, 0.0f)) ||
786         GetCanStayOverScroll()) {
787         UpdateCurrentOffset(setPosition - currentOffset_, source);
788     }
789 }
790 
SetEdgeEffectCallback(const RefPtr<ScrollEdgeEffect> & scrollEffect)791 void ScrollPattern::SetEdgeEffectCallback(const RefPtr<ScrollEdgeEffect>& scrollEffect)
792 {
793     scrollEffect->SetCurrentPositionCallback([weakScroll = AceType::WeakClaim(this)]() -> double {
794         auto scroll = weakScroll.Upgrade();
795         CHECK_NULL_RETURN(scroll, 0.0);
796         return scroll->GetCurrentPosition();
797     });
798     scrollEffect->SetLeadingCallback([weakScroll = AceType::WeakClaim(this)]() -> double {
799         auto scroll = weakScroll.Upgrade();
800         if (scroll && !scroll->IsRowReverse() && !scroll->IsColReverse() && scroll->GetScrollableDistance() > 0) {
801             return -scroll->GetScrollableDistance();
802         }
803         return 0.0;
804     });
805     scrollEffect->SetTrailingCallback([weakScroll = AceType::WeakClaim(this)]() -> double {
806         auto scroll = weakScroll.Upgrade();
807         if (scroll && (scroll->IsRowReverse() || scroll->IsColReverse())) {
808             return scroll->GetScrollableDistance();
809         }
810         return 0.0;
811     });
812     scrollEffect->SetInitLeadingCallback([weakScroll = AceType::WeakClaim(this)]() -> double {
813         auto scroll = weakScroll.Upgrade();
814         if (scroll && !scroll->IsRowReverse() && !scroll->IsColReverse() && scroll->GetScrollableDistance() > 0) {
815             return -scroll->GetScrollableDistance();
816         }
817         return 0.0;
818     });
819     scrollEffect->SetInitTrailingCallback([weakScroll = AceType::WeakClaim(this)]() -> double {
820         auto scroll = weakScroll.Upgrade();
821         if (scroll && (scroll->IsRowReverse() || scroll->IsColReverse())) {
822             return scroll->GetScrollableDistance();
823         }
824         return 0.0;
825     });
826 }
827 
UpdateScrollBarOffset()828 void ScrollPattern::UpdateScrollBarOffset()
829 {
830     if (freeScroll_) {
831         return;
832     }
833     CheckScrollBarOff();
834     if (!GetScrollBar() && !GetScrollBarProxy()) {
835         return;
836     }
837 
838     float scrollBarOutBoundaryExtent = 0.0f;
839     if (currentOffset_ > 0) {
840         scrollBarOutBoundaryExtent = currentOffset_;
841     } else if ((-currentOffset_) >= (GetMainSize(viewPortExtent_) - GetMainSize(viewPort_))) {
842         scrollBarOutBoundaryExtent = -currentOffset_ - (GetMainSize(viewPortExtent_) - GetMainSize(viewPort_));
843     }
844     HandleScrollBarOutBoundary(scrollBarOutBoundaryExtent);
845 
846     auto host = GetHost();
847     CHECK_NULL_VOID(host);
848     auto layoutProperty = host->GetLayoutProperty<ScrollLayoutProperty>();
849     CHECK_NULL_VOID(layoutProperty);
850     auto padding = layoutProperty->CreatePaddingAndBorder();
851     auto contentEndOffset = layoutProperty->GetScrollContentEndOffsetValue(.0f);
852     Size size(viewSize_.Width(), viewSize_.Height() - contentEndOffset);
853     auto viewPortExtent = viewPortExtent_;
854     AddPaddingToSize(padding, viewPortExtent);
855     auto estimatedHeight = (GetAxis() == Axis::HORIZONTAL) ? viewPortExtent.Width() : viewPortExtent.Height();
856     UpdateScrollBarRegion(-currentOffset_, estimatedHeight, size, Offset(0.0f, 0.0f));
857 }
858 
SetAccessibilityAction()859 void ScrollPattern::SetAccessibilityAction()
860 {
861     auto host = GetHost();
862     CHECK_NULL_VOID(host);
863     auto accessibilityProperty = host->GetAccessibilityProperty<AccessibilityProperty>();
864     CHECK_NULL_VOID(accessibilityProperty);
865     accessibilityProperty->SetActionScrollForward([weakPtr = WeakClaim(this)](AccessibilityScrollType scrollType) {
866         const auto& pattern = weakPtr.Upgrade();
867         CHECK_NULL_VOID(pattern);
868         auto host = pattern->GetHost();
869         CHECK_NULL_VOID(host);
870         ACE_SCOPED_TRACE("accessibility action, scroll forward, isScrollable:%u, IsPositiveScrollableDistance:%u, "
871                          "scrollType:%d, id:%d, tag:Scroll",
872             pattern->IsScrollable(), pattern->IsPositiveScrollableDistance(), scrollType,
873             static_cast<int32_t>(host->GetAccessibilityId()));
874         if (pattern->IsScrollable() && pattern->IsPositiveScrollableDistance()) {
875             pattern->ScrollPage(false, true, scrollType);
876         }
877     });
878 
879     accessibilityProperty->SetActionScrollBackward([weakPtr = WeakClaim(this)](AccessibilityScrollType scrollType) {
880         const auto& pattern = weakPtr.Upgrade();
881         CHECK_NULL_VOID(pattern);
882         auto host = pattern->GetHost();
883         CHECK_NULL_VOID(host);
884         ACE_SCOPED_TRACE("accessibility action, scroll backward, isScrollable:%u, IsPositiveScrollableDistance:%u, "
885                          "scrollType:%d, id:%d, tag:Scroll",
886             pattern->IsScrollable(), pattern->IsPositiveScrollableDistance(), scrollType,
887             static_cast<int32_t>(host->GetAccessibilityId()));
888         if (pattern->IsScrollable() && pattern->IsPositiveScrollableDistance()) {
889             pattern->ScrollPage(true, true, scrollType);
890         }
891     });
892 }
893 
GetOffsetToScroll(const RefPtr<FrameNode> & childFrame) const894 OffsetF ScrollPattern::GetOffsetToScroll(const RefPtr<FrameNode>& childFrame) const
895 {
896     auto frameNode = GetHost();
897     CHECK_NULL_RETURN(frameNode, OffsetF());
898     CHECK_NULL_RETURN(childFrame, OffsetF());
899     auto childGeometryNode = childFrame->GetGeometryNode();
900     CHECK_NULL_RETURN(childGeometryNode, OffsetF());
901     OffsetF result = childGeometryNode->GetFrameOffset();
902     auto parent = childFrame->GetParent();
903     while (parent) {
904         auto parentFrame = AceType::DynamicCast<FrameNode>(parent);
905         if (!parentFrame) {
906             parent = parent->GetParent();
907             continue;
908         }
909         if (parentFrame == frameNode) {
910             return result;
911         }
912         auto parentGeometryNode = parentFrame->GetGeometryNode();
913         if (!parentGeometryNode) {
914             parent = parent->GetParent();
915             continue;
916         }
917         result += parentGeometryNode->GetFrameOffset();
918         parent = parent->GetParent();
919     }
920     return OffsetF(0.0, 0.0);
921 }
922 
ScrollToNode(const RefPtr<FrameNode> & focusFrameNode)923 bool ScrollPattern::ScrollToNode(const RefPtr<FrameNode>& focusFrameNode)
924 {
925     CHECK_NULL_RETURN(focusFrameNode, false);
926     auto focusGeometryNode = focusFrameNode->GetGeometryNode();
927     CHECK_NULL_RETURN(focusGeometryNode, false);
928     auto focusNodeSize = focusGeometryNode->GetFrameSize();
929     auto focusNodeOffsetToScrolll = GetOffsetToScroll(focusFrameNode);
930     auto scrollFrame = GetHost();
931     CHECK_NULL_RETURN(scrollFrame, false);
932     auto scrollGeometry = scrollFrame->GetGeometryNode();
933     CHECK_NULL_RETURN(scrollGeometry, false);
934     auto scrollFrameSize = scrollGeometry->GetFrameSize();
935     float focusNodeDiffToScroll =
936         GetAxis() == Axis::VERTICAL ? focusNodeOffsetToScrolll.GetY() : focusNodeOffsetToScrolll.GetX();
937     if (NearZero(focusNodeDiffToScroll)) {
938         return false;
939     }
940     float focusNodeLength = GetAxis() == Axis::VERTICAL ? focusNodeSize.Height() : focusNodeSize.Width();
941     float scrollFrameLength = GetAxis() == Axis::VERTICAL ? scrollFrameSize.Height() : scrollFrameSize.Width();
942     float moveOffset = 0.0;
943     if (LessNotEqual(focusNodeDiffToScroll, 0)) {
944         moveOffset = -focusNodeDiffToScroll;
945     } else if (GreatNotEqual(focusNodeDiffToScroll + focusNodeLength, scrollFrameLength)) {
946         moveOffset = scrollFrameLength - focusNodeDiffToScroll - focusNodeLength;
947     }
948     if (!NearZero(moveOffset)) {
949         return OnScrollCallback(moveOffset, SCROLL_FROM_FOCUS_JUMP);
950     }
951     return false;
952 }
953 
GetScrollOffsetAbility()954 ScrollOffsetAbility ScrollPattern::GetScrollOffsetAbility()
955 {
956     return { [wp = WeakClaim(this)](float moveOffset) -> bool {
957                 auto pattern = wp.Upgrade();
958                 CHECK_NULL_RETURN(pattern, false);
959                 return pattern->OnScrollCallback(moveOffset, SCROLL_FROM_FOCUS_JUMP);
960             },
961         GetAxis() };
962 }
963 
CalcPredictSnapOffset(float delta,float dragDistance,float velocity,SnapDirection snapDirection)964 std::optional<float> ScrollPattern::CalcPredictSnapOffset(
965     float delta, float dragDistance, float velocity, SnapDirection snapDirection)
966 {
967     std::optional<float> predictSnapOffset;
968     CHECK_NULL_RETURN(IsScrollSnap(), predictSnapOffset);
969     if (snapDirection != SnapDirection::NONE) {
970         return CalcPredictNextSnapOffset(delta, snapDirection);
971     }
972     float finalPosition = currentOffset_ + delta;
973     if (IsEnablePagingValid()) {
974         finalPosition = GetPagingOffset(delta, dragDistance, velocity);
975     }
976     if (!IsSnapToInterval()) {
977         if (!enableSnapToSide_.first) {
978             CHECK_NULL_RETURN(!(GreatNotEqual(finalPosition, *(snapOffsets_.begin() + 1)) ||
979                                   GreatNotEqual(currentOffset_, *(snapOffsets_.begin() + 1))),
980                 predictSnapOffset);
981         }
982         if (!enableSnapToSide_.second) {
983             CHECK_NULL_RETURN(!(LessNotEqual(finalPosition, *(snapOffsets_.rbegin() + 1)) ||
984                                   LessNotEqual(currentOffset_, *(snapOffsets_.rbegin() + 1))),
985                 predictSnapOffset);
986         }
987     }
988     float head = 0.0f;
989     float tail = -scrollableDistance_;
990     if (GreatOrEqual(finalPosition, head) || LessOrEqual(finalPosition, tail)) {
991         predictSnapOffset = finalPosition;
992     } else if (LessNotEqual(finalPosition, head) && GreatOrEqual(finalPosition, *(snapOffsets_.begin()))) {
993         predictSnapOffset = *(snapOffsets_.begin());
994     } else if (GreatNotEqual(finalPosition, tail) && LessOrEqual(finalPosition, *(snapOffsets_.rbegin()))) {
995         predictSnapOffset = *(snapOffsets_.rbegin());
996     } else {
997         auto iter = snapOffsets_.begin() + 1;
998         float start = *(iter - 1);
999         float end = *(iter);
1000         for (; iter != snapOffsets_.end(); ++iter) {
1001             if (GreatOrEqual(finalPosition, *iter)) {
1002                 start = *(iter - 1);
1003                 end = *(iter);
1004                 predictSnapOffset = (LessNotEqual(start - finalPosition, finalPosition - end) ? start : end);
1005                 break;
1006             }
1007         }
1008     }
1009     if (predictSnapOffset.has_value()) {
1010         predictSnapOffset = predictSnapOffset.value() - currentOffset_;
1011     }
1012     return predictSnapOffset;
1013 }
1014 
CalcPredictNextSnapOffset(float delta,SnapDirection snapDirection)1015 std::optional<float> ScrollPattern::CalcPredictNextSnapOffset(float delta, SnapDirection snapDirection)
1016 {
1017     std::optional<float> predictSnapOffset;
1018     int32_t start = 0;
1019     int32_t end = static_cast<int32_t>(snapOffsets_.size()) - 1;
1020     int32_t mid = 0;
1021     auto targetOffset = currentOffset_ + delta;
1022     if (LessOrEqual(targetOffset, snapOffsets_[end]) && snapDirection == SnapDirection::BACKWARD) {
1023         predictSnapOffset = -scrollableDistance_ - currentOffset_;
1024         return predictSnapOffset;
1025     } else if (GreatOrEqual(targetOffset, snapOffsets_[start]) && snapDirection == SnapDirection::FORWARD) {
1026         predictSnapOffset = -currentOffset_;
1027         return predictSnapOffset;
1028     }
1029     while (start < end) {
1030         mid = (start + end) / 2;
1031         if (LessNotEqual(snapOffsets_[mid], targetOffset)) {
1032             end = mid;
1033         } else if (GreatNotEqual(snapOffsets_[mid], targetOffset)) {
1034             start = mid + 1;
1035         } else {
1036             if (snapDirection == SnapDirection::FORWARD && mid > 0) {
1037                 predictSnapOffset = snapOffsets_[mid - 1] - currentOffset_;
1038             } else if (snapDirection == SnapDirection::BACKWARD &&
1039                        (mid + 1) < static_cast<int32_t>(snapOffsets_.size())) {
1040                 predictSnapOffset = snapOffsets_[mid + 1] - currentOffset_;
1041             }
1042             return predictSnapOffset;
1043         }
1044     }
1045     if (snapDirection == SnapDirection::FORWARD) {
1046         predictSnapOffset = snapOffsets_[std::max(start - 1, 0)] - currentOffset_;
1047     } else if (snapDirection == SnapDirection::BACKWARD) {
1048         predictSnapOffset = snapOffsets_[start] - currentOffset_;
1049     }
1050     return predictSnapOffset;
1051 }
1052 
CaleSnapOffsets(const RefPtr<FrameNode> & host)1053 void ScrollPattern::CaleSnapOffsets(const RefPtr<FrameNode>& host)
1054 {
1055     auto scrollSnapAlign = GetScrollSnapAlign(host);
1056     std::vector<float>().swap(snapOffsets_);
1057     if (scrollSnapAlign == ScrollSnapAlign::NONE) {
1058         CHECK_NULL_VOID(enablePagingStatus_ == ScrollPagingStatus::VALID);
1059         scrollSnapAlign = ScrollSnapAlign::START;
1060     }
1061     if (IsSnapToInterval()) {
1062         CaleSnapOffsetsByInterval(scrollSnapAlign, host);
1063     } else {
1064         CaleSnapOffsetsByPaginations(scrollSnapAlign);
1065     }
1066 }
1067 
CaleSnapOffsetsByInterval(ScrollSnapAlign scrollSnapAlign,const RefPtr<FrameNode> & host)1068 void ScrollPattern::CaleSnapOffsetsByInterval(ScrollSnapAlign scrollSnapAlign, const RefPtr<FrameNode>& host)
1069 {
1070     auto mainSize = GetMainAxisSize(viewPort_, GetAxis());
1071     auto pipeline = host->GetContext();
1072     auto intervalSize = intervalSize_.Unit() == DimensionUnit::PERCENT
1073                             ? intervalSize_.Value() * mainSize
1074                             : (pipeline ? pipeline->NormalizeToPx(intervalSize_) : intervalSize_.ConvertToPx());
1075     CHECK_NULL_VOID(GreatOrEqual(intervalSize, SCROLL_SNAP_INTERVAL_SIZE_MIN_VALUE));
1076     auto extentMainSize = GetMainAxisSize(viewPortExtent_, GetAxis());
1077     auto start = 0.0f;
1078     auto end = -scrollableDistance_;
1079     auto snapOffset = 0.0f;
1080     auto sizeDelta = 0.0f;
1081     float temp = static_cast<int32_t>(extentMainSize / intervalSize) * intervalSize;
1082     switch (scrollSnapAlign) {
1083         case ScrollSnapAlign::START:
1084             end = -temp;
1085             break;
1086         case ScrollSnapAlign::CENTER:
1087             sizeDelta = (mainSize - intervalSize) / 2;
1088             start = Positive(sizeDelta) ? sizeDelta - static_cast<int32_t>(sizeDelta / intervalSize) * intervalSize
1089                                         : sizeDelta;
1090             end = -temp + (mainSize - extentMainSize + temp) / 2;
1091             break;
1092         case ScrollSnapAlign::END:
1093             sizeDelta = mainSize - intervalSize;
1094             start = Positive(sizeDelta) ? mainSize - static_cast<int32_t>(mainSize / intervalSize) * intervalSize
1095                                         : sizeDelta;
1096             end = -scrollableDistance_;
1097             break;
1098         default:
1099             break;
1100     }
1101     if (!Positive(start)) {
1102         snapOffsets_.emplace_back(start);
1103     }
1104     snapOffset = start - intervalSize;
1105     while (GreatOrEqual(snapOffset, -scrollableDistance_) && GreatOrEqual(snapOffset, end)) {
1106         snapOffsets_.emplace_back(snapOffset);
1107         snapOffset -= intervalSize;
1108     }
1109     if (GreatNotEqual(end, -scrollableDistance_)) {
1110         snapOffsets_.emplace_back(end);
1111     }
1112     if (IsEnablePagingValid()) {
1113         if (NearEqual(snapOffset + intervalSize, -scrollableDistance_)) {
1114             lastPageLength_ = 0.f;
1115             return;
1116         }
1117         lastPageLength_ = scrollableDistance_ + snapOffset + intervalSize;
1118         snapOffsets_.emplace_back(-scrollableDistance_);
1119     }
1120 }
1121 
CaleSnapOffsetsByPaginations(ScrollSnapAlign scrollSnapAlign)1122 void ScrollPattern::CaleSnapOffsetsByPaginations(ScrollSnapAlign scrollSnapAlign)
1123 {
1124     auto mainSize = GetMainAxisSize(viewPort_, GetAxis());
1125     auto extentMainSize = GetMainAxisSize(viewPortExtent_, GetAxis());
1126     auto start = 0.0f;
1127     auto end = -scrollableDistance_;
1128     auto snapOffset = 0.0f;
1129     snapOffsets_.emplace_back(start);
1130     int32_t length = 0;
1131     auto snapPaginations = snapPaginations_;
1132     snapPaginations.emplace(snapPaginations.begin(), Dimension(0.f));
1133     auto current = 0.0f;
1134     auto next = 0.0f;
1135     auto size = static_cast<int32_t>(snapPaginations.size());
1136     auto element = snapPaginations[length];
1137     auto nextElement = snapPaginations[length + 1];
1138     for (; length < size; length++) {
1139         element = snapPaginations[length];
1140         current = element.Unit() == DimensionUnit::PERCENT ? element.Value() * mainSize : element.ConvertToPx();
1141         if (length == size - 1) {
1142             next = extentMainSize;
1143         } else {
1144             nextElement = snapPaginations[length + 1];
1145             next = nextElement.Unit() == DimensionUnit::PERCENT ? nextElement.Value() * mainSize
1146                                                                 : nextElement.ConvertToPx();
1147         }
1148         switch (scrollSnapAlign) {
1149             case ScrollSnapAlign::START:
1150                 snapOffset = -current;
1151                 break;
1152             case ScrollSnapAlign::CENTER:
1153                 snapOffset = (mainSize - (current + next)) / 2.0f;
1154                 break;
1155             case ScrollSnapAlign::END:
1156                 snapOffset = mainSize - next;
1157                 break;
1158             default:
1159                 break;
1160         }
1161         if (!Negative(snapOffset)) {
1162             continue;
1163         }
1164         if (GreatNotEqual(snapOffset, -scrollableDistance_)) {
1165             snapOffsets_.emplace_back(snapOffset);
1166         } else {
1167             break;
1168         }
1169     }
1170     snapOffsets_.emplace_back(end);
1171 }
1172 
NeedScrollSnapToSide(float delta)1173 bool ScrollPattern::NeedScrollSnapToSide(float delta)
1174 {
1175     CHECK_NULL_RETURN(GetScrollSnapAlign() != ScrollSnapAlign::NONE, false);
1176     CHECK_NULL_RETURN(!IsSnapToInterval(), false);
1177     CHECK_NULL_RETURN(static_cast<int32_t>(snapOffsets_.size()) > 2, false);
1178     auto finalPosition = currentOffset_ + delta;
1179     if (!enableSnapToSide_.first) {
1180         if (GreatOrEqual(currentOffset_, *(snapOffsets_.begin() + 1)) &&
1181             LessOrEqual(finalPosition, *(snapOffsets_.begin() + 1))) {
1182             return true;
1183         }
1184     }
1185     if (!enableSnapToSide_.second) {
1186         if (LessOrEqual(currentOffset_, *(snapOffsets_.rbegin() + 1)) &&
1187             GreatOrEqual(finalPosition, *(snapOffsets_.rbegin() + 1))) {
1188             return true;
1189         }
1190     }
1191     return false;
1192 }
1193 
GetScrollSnapAlign(const RefPtr<FrameNode> & host) const1194 ScrollSnapAlign ScrollPattern::GetScrollSnapAlign(const RefPtr<FrameNode>& host) const
1195 {
1196     CHECK_NULL_RETURN(host, ScrollSnapAlign::NONE);
1197     auto scrollLayoutProperty = host->GetLayoutProperty<ScrollLayoutProperty>();
1198     CHECK_NULL_RETURN(scrollLayoutProperty, ScrollSnapAlign::NONE);
1199     return scrollLayoutProperty->GetScrollSnapAlign().value_or(ScrollSnapAlign::NONE);
1200 }
1201 
ProvideRestoreInfo()1202 std::string ScrollPattern::ProvideRestoreInfo()
1203 {
1204     Dimension dimension(currentOffset_);
1205     return StringUtils::DoubleToString(dimension.ConvertToVp());
1206 }
1207 
OnRestoreInfo(const std::string & restoreInfo)1208 void ScrollPattern::OnRestoreInfo(const std::string& restoreInfo)
1209 {
1210     Dimension dimension = StringUtils::StringToDimension(restoreInfo, true);
1211     currentOffset_ = dimension.ConvertToPx();
1212 }
1213 
GetItemRect(int32_t index) const1214 Rect ScrollPattern::GetItemRect(int32_t index) const
1215 {
1216     auto host = GetHost();
1217     CHECK_NULL_RETURN(host, Rect());
1218     if (index != 0 || host->TotalChildCount() != 1) {
1219         return Rect();
1220     }
1221     auto item = host->GetChildByIndex(index);
1222     CHECK_NULL_RETURN(item, Rect());
1223     auto itemGeometry = item->GetGeometryNode();
1224     CHECK_NULL_RETURN(itemGeometry, Rect());
1225     float scale = GetZoomScale();
1226     if (scale == 1.0f) {
1227         return Rect(itemGeometry->GetFrameRect().GetX(), itemGeometry->GetFrameRect().GetY(),
1228             itemGeometry->GetFrameRect().Width(), itemGeometry->GetFrameRect().Height());
1229     } else {
1230         auto rect = itemGeometry->GetFrameRect();
1231         auto cx = rect.Left() + rect.Width() / 2;
1232         auto cy = rect.Top() + rect.Height() / 2;
1233         auto left = cx - (cx - rect.Left()) * scale;
1234         auto top = cy - (cy - rect.Top()) * scale;
1235         auto size = itemGeometry->GetFrameSize() * scale;
1236         return Rect(left, top, size.Width(), size.Height());
1237     }
1238 }
1239 
GetSelectScrollWidth()1240 float ScrollPattern::GetSelectScrollWidth()
1241 {
1242     RefPtr<GridColumnInfo> columnInfo = GridSystemManager::GetInstance().GetInfoByType(GridColumnType::MENU);
1243     auto parent = columnInfo->GetParent();
1244     CHECK_NULL_RETURN(parent, SELECT_SCROLL_MIN_WIDTH.ConvertToPx());
1245     parent->BuildColumnWidth();
1246     auto defaultWidth = static_cast<float>(columnInfo->GetWidth(COLUMN_NUM));
1247     auto scrollNode = GetHost();
1248     CHECK_NULL_RETURN(scrollNode, SELECT_SCROLL_MIN_WIDTH.ConvertToPx());
1249     float finalWidth = SELECT_SCROLL_MIN_WIDTH.ConvertToPx();
1250 
1251     if (IsWidthModifiedBySelect()) {
1252         auto scrollLayoutProperty = scrollNode->GetLayoutProperty<ScrollLayoutProperty>();
1253         CHECK_NULL_RETURN(scrollLayoutProperty, SELECT_SCROLL_MIN_WIDTH.ConvertToPx());
1254         auto selectModifiedWidth = scrollLayoutProperty->GetScrollWidth();
1255         finalWidth = selectModifiedWidth.value();
1256     } else {
1257         finalWidth = defaultWidth;
1258     }
1259 
1260     if (finalWidth < SELECT_SCROLL_MIN_WIDTH.ConvertToPx()) {
1261         finalWidth = defaultWidth;
1262     }
1263 
1264     return finalWidth;
1265 }
1266 
GetPagingOffset(float delta,float dragDistance,float velocity) const1267 float ScrollPattern::GetPagingOffset(float delta, float dragDistance, float velocity) const
1268 {
1269     // handle last page
1270     if (GreatNotEqual(lastPageLength_, 0.f) && LessNotEqual(currentOffset_, -scrollableDistance_ + lastPageLength_)) {
1271         auto offset = fmod(currentOffset_, viewPortLength_);
1272         return currentOffset_ - offset + GetPagingDelta(offset, velocity, lastPageLength_);
1273     }
1274     // handle other pages
1275     float head = 0.0f;
1276     float tail = -scrollableDistance_;
1277     auto offset = fmod(currentOffset_, viewPortLength_);
1278     auto pagingPosition = currentOffset_ - offset + GetPagingDelta(offset, velocity, viewPortLength_);
1279     auto finalPosition = currentOffset_ + delta;
1280     auto useFinalPosition = (GreatOrEqual(pagingPosition, head) && !GreatOrEqual(finalPosition, head)) ||
1281                             (LessOrEqual(pagingPosition, tail) && !LessOrEqual(finalPosition, tail));
1282     return useFinalPosition ? finalPosition : pagingPosition;
1283 }
1284 
GetPagingDelta(float dragDistance,float velocity,float pageLength) const1285 float ScrollPattern::GetPagingDelta(float dragDistance, float velocity, float pageLength) const
1286 {
1287     auto dragDistanceThreshold = pageLength * 0.5f;
1288     // dragDistance and velocity have not reached the threshold
1289     if (LessNotEqual(std::abs(dragDistance), dragDistanceThreshold) &&
1290         LessNotEqual(std::abs(velocity), SCROLL_PAGING_SPEED_THRESHOLD)) {
1291         return 0.f;
1292     }
1293     // The direction of dragDistance is the same as the direction of velocity
1294     if (GreatOrEqual(dragDistance * velocity, 0.f)) {
1295         auto direction = NearZero(dragDistance) ? velocity : dragDistance;
1296         return GreatNotEqual(direction, 0.f) ? pageLength : -pageLength;
1297     }
1298     // The direction of dragDistance is opposite to the direction of velocity
1299     if (GreatOrEqual(std::abs(dragDistance), dragDistanceThreshold) &&
1300         LessNotEqual(std::abs(velocity), SCROLL_PAGING_SPEED_THRESHOLD)) {
1301         return GreatNotEqual(dragDistance, 0.f) ? pageLength : -pageLength;
1302     }
1303     return 0.f;
1304 }
1305 
TriggerModifyDone()1306 void ScrollPattern::TriggerModifyDone()
1307 {
1308     OnModifyDone();
1309 }
1310 
AddScrollMeasureInfo(const std::optional<LayoutConstraintF> & parentConstraint,const std::optional<LayoutConstraintF> & childConstraint,const SizeF & selfSize,const SizeF & childSize)1311 void ScrollPattern::AddScrollMeasureInfo(const std::optional<LayoutConstraintF>& parentConstraint,
1312     const std::optional<LayoutConstraintF>& childConstraint, const SizeF& selfSize, const SizeF& childSize)
1313 {
1314     if (scrollMeasureInfos_.size() >= SCROLL_MEASURE_INFO_COUNT) {
1315         scrollMeasureInfos_.pop_front();
1316     }
1317     scrollMeasureInfos_.push_back(ScrollMeasureInfo({
1318         .changedTime_ = GetSysTimestamp(),
1319         .parentConstraint_ = parentConstraint,
1320         .childConstraint_ = childConstraint,
1321         .selfSize_ = selfSize,
1322         .childSize_ = childSize,
1323     }));
1324 }
1325 
AddScrollLayoutInfo()1326 void ScrollPattern::AddScrollLayoutInfo()
1327 {
1328     if (scrollLayoutInfos_.size() >= SCROLL_LAYOUT_INFO_COUNT) {
1329         scrollLayoutInfos_.pop_front();
1330     }
1331     scrollLayoutInfos_.push_back(ScrollLayoutInfo({
1332         .changedTime_ = GetSysTimestamp(),
1333         .scrollableDistance_ = scrollableDistance_,
1334         .scrollSize_ = viewSize_,
1335         .viewPort_ = viewPort_,
1336         .childSize_ = viewPortExtent_,
1337     }));
1338 }
1339 
GetScrollSnapAlignDumpInfo()1340 void ScrollPattern::GetScrollSnapAlignDumpInfo()
1341 {
1342     switch (GetScrollSnapAlign()) {
1343         case ScrollSnapAlign::NONE: {
1344             DumpLog::GetInstance().AddDesc("snapAlign: ScrollSnapAlign::NONE");
1345             break;
1346         }
1347         case ScrollSnapAlign::START: {
1348             DumpLog::GetInstance().AddDesc("snapAlign: ScrollSnapAlign::START");
1349             break;
1350         }
1351         case ScrollSnapAlign::CENTER: {
1352             DumpLog::GetInstance().AddDesc("snapAlign: ScrollSnapAlign::CENTER");
1353             break;
1354         }
1355         case ScrollSnapAlign::END: {
1356             DumpLog::GetInstance().AddDesc("snapAlign: ScrollSnapAlign::END");
1357             break;
1358         }
1359         default: {
1360             break;
1361         }
1362     }
1363 }
1364 
GetScrollPagingStatusDumpInfo()1365 void ScrollPattern::GetScrollPagingStatusDumpInfo()
1366 {
1367     switch (enablePagingStatus_) {
1368         case ScrollPagingStatus::NONE: {
1369             DumpLog::GetInstance().AddDesc("enablePaging: ScrollPagingStatus::NONE");
1370             break;
1371         }
1372         case ScrollPagingStatus::INVALID: {
1373             DumpLog::GetInstance().AddDesc("enablePaging: ScrollPagingStatus::INVALID");
1374             break;
1375         }
1376         case ScrollPagingStatus::VALID: {
1377             DumpLog::GetInstance().AddDesc("enablePaging: ScrollPagingStatus::VALID");
1378             break;
1379         }
1380         default: {
1381             break;
1382         }
1383     }
1384 }
1385 
DumpAdvanceInfo()1386 void ScrollPattern::DumpAdvanceInfo()
1387 {
1388     auto host = GetHost();
1389     CHECK_NULL_VOID(host);
1390     auto hub = host->GetOrCreateEventHub<ScrollEventHub>();
1391     CHECK_NULL_VOID(hub);
1392     ScrollablePattern::DumpAdvanceInfo();
1393     DumpLog::GetInstance().AddDesc(std::string("currentOffset: ").append(std::to_string(currentOffset_)));
1394     GetScrollSnapAlignDumpInfo();
1395     auto snapPaginationStr = std::string("snapPagination: ");
1396     DumpLog::GetInstance().AddDesc(snapPaginationStr.append(GetScrollSnapPagination()));
1397     enableSnapToSide_.first ? DumpLog::GetInstance().AddDesc("enableSnapToStart: true")
1398                             : DumpLog::GetInstance().AddDesc("enableSnapToStart: false");
1399     enableSnapToSide_.second ? DumpLog::GetInstance().AddDesc("enableSnapToEnd: true")
1400                              : DumpLog::GetInstance().AddDesc("enableSnapToEnd: false");
1401     GetScrollPagingStatusDumpInfo();
1402     auto snapOffsetsStr = std::string("snapOffsets: [");
1403     for (const auto& iter : snapPaginations_) {
1404         snapOffsetsStr = snapOffsetsStr.append(iter.ToString()).append(" ");
1405     }
1406     DumpLog::GetInstance().AddDesc(snapOffsetsStr.append("]"));
1407     initialOffset_.has_value()
1408         ? DumpLog::GetInstance().AddDesc(
1409               std::string("initialOffset: ").append(initialOffset_->GetMainOffset(GetAxis()).ToString()))
1410         : DumpLog::GetInstance().AddDesc("initialOffset: None");
1411     auto onScrollEdge = hub->GetScrollEdgeEvent();
1412     onScrollEdge ? DumpLog::GetInstance().AddDesc("hasOnScrollEdge: true")
1413                  : DumpLog::GetInstance().AddDesc("hasOnScrollEdge: false");
1414     DumpLog::GetInstance().AddDesc("==========================scrollLayoutInfos==========================");
1415     for (const auto& info : scrollLayoutInfos_) {
1416         DumpLog::GetInstance().AddDesc(info.ToString());
1417     }
1418     DumpLog::GetInstance().AddDesc("==========================scrollLayoutInfos==========================");
1419     DumpLog::GetInstance().AddDesc("==========================scrollMeasureInfos==========================");
1420     for (const auto& info : scrollMeasureInfos_) {
1421         DumpLog::GetInstance().AddDesc(info.ToString());
1422     }
1423     DumpLog::GetInstance().AddDesc("==========================scrollMeasureInfos==========================");
1424 }
1425 
ToJsonValue(std::unique_ptr<JsonValue> & json,const InspectorFilter & filter) const1426 void ScrollPattern::ToJsonValue(std::unique_ptr<JsonValue>& json, const InspectorFilter& filter) const
1427 {
1428     ScrollablePattern::ToJsonValue(json, filter);
1429     /* no fixed attr below, just return */
1430     if (filter.IsFastFilter()) {
1431         return;
1432     }
1433     auto initialOffset = JsonUtil::Create(true);
1434     initialOffset->Put("xOffset", GetInitialOffset().GetX().ToString().c_str());
1435     initialOffset->Put("yOffset", GetInitialOffset().GetY().ToString().c_str());
1436     json->PutExtAttr("initialOffset", initialOffset, filter);
1437     if (enablePagingStatus_ != ScrollPagingStatus::NONE) {
1438         json->PutExtAttr("enablePaging", enablePagingStatus_ == ScrollPagingStatus::VALID, filter);
1439     }
1440 
1441     auto scrollSnapOptions = JsonUtil::Create(true);
1442     if (IsSnapToInterval()) {
1443         scrollSnapOptions->Put("snapPagination", intervalSize_.ToString().c_str());
1444     } else {
1445         auto snapPaginationArr = JsonUtil::CreateArray(true);
1446         auto iter = snapPaginations_.begin();
1447         for (auto i = 0; iter != snapPaginations_.end(); ++iter, ++i) {
1448             snapPaginationArr->Put(std::to_string(i).c_str(), (*iter).ToString().c_str());
1449         }
1450         scrollSnapOptions->Put("snapPagination", snapPaginationArr);
1451     }
1452     scrollSnapOptions->Put("enableSnapToStart", enableSnapToSide_.first);
1453     scrollSnapOptions->Put("enableSnapToEnd", enableSnapToSide_.second);
1454     json->PutExtAttr("scrollSnap", scrollSnapOptions, filter);
1455     json->PutExtAttr("maxZoomScale", maxZoomScale_, filter);
1456     json->PutExtAttr("minZoomScale", minZoomScale_, filter);
1457     json->PutExtAttr("zoomScale", zoomScale_.value_or(1.0f), filter);
1458     json->PutExtAttr("enableBouncesZoom", enableBouncesZoom_, filter);
1459 }
1460 
GetScrollSnapPagination() const1461 std::string ScrollPattern::GetScrollSnapPagination() const
1462 {
1463     auto snapPaginationStr = std::string("");
1464     if (IsSnapToInterval()) {
1465         snapPaginationStr = intervalSize_.ToString();
1466     } else {
1467         snapPaginationStr.append("[");
1468         auto iter = snapPaginations_.begin();
1469         for (; iter != snapPaginations_.end(); ++iter) {
1470             snapPaginationStr = snapPaginationStr.append((*iter).ToString()).append(" ");
1471         }
1472         snapPaginationStr.append("]");
1473     }
1474     return snapPaginationStr;
1475 }
1476 
OnColorModeChange(uint32_t colorMode)1477 void ScrollPattern::OnColorModeChange(uint32_t colorMode)
1478 {
1479     Pattern::OnColorModeChange(colorMode);
1480     if (!SystemProperties::ConfigChangePerform()) {
1481         return;
1482     }
1483     auto host = GetHost();
1484     CHECK_NULL_VOID(host);
1485     if (scrollSnapUpdate_) {
1486         CaleSnapOffsets(host);
1487         host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
1488     }
1489     host->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
1490 }
1491 
StartSnapAnimation(SnapAnimationOptions snapAnimationOptions)1492 bool ScrollPattern::StartSnapAnimation(SnapAnimationOptions snapAnimationOptions)
1493 {
1494     auto scrollBar = GetScrollBar();
1495     auto scrollBarProxy = GetScrollBarProxy();
1496     auto fromScrollBar = snapAnimationOptions.fromScrollBar;
1497     if (!fromScrollBar && scrollBar && scrollBar->IsDriving()) {
1498         return false;
1499     }
1500     if (!fromScrollBar && scrollBarProxy && scrollBarProxy->IsScrollSnapTrigger()) {
1501         return false;
1502     }
1503     auto predictSnapOffset = CalcPredictSnapOffset(snapAnimationOptions.snapDelta, snapAnimationOptions.dragDistance,
1504         snapAnimationOptions.animationVelocity, snapAnimationOptions.snapDirection);
1505     if (predictSnapOffset.has_value() && !NearZero(predictSnapOffset.value(), SPRING_ACCURACY)) {
1506         StartScrollSnapAnimation(predictSnapOffset.value(), snapAnimationOptions.animationVelocity, fromScrollBar);
1507         return true;
1508     }
1509     return false;
1510 }
1511 
StartScrollSnapAnimation(float scrollSnapDelta,float scrollSnapVelocity,bool fromScrollBar)1512 void ScrollPattern::StartScrollSnapAnimation(float scrollSnapDelta, float scrollSnapVelocity, bool fromScrollBar)
1513 {
1514     auto scrollableEvent = GetScrollableEvent();
1515     CHECK_NULL_VOID(scrollableEvent);
1516     auto scrollable = scrollableEvent->GetScrollable();
1517     CHECK_NULL_VOID(scrollable);
1518     if (scrollable->IsSnapAnimationRunning()) {
1519         scrollable->UpdateScrollSnapEndWithOffset(
1520             -(scrollSnapDelta + scrollable->GetCurrentPos() - scrollable->GetSnapFinalPosition()));
1521     } else {
1522         scrollable->StartScrollSnapAnimation(scrollSnapDelta, scrollSnapVelocity, fromScrollBar);
1523         if (!IsScrolling()) {
1524             FireOnScrollStart();
1525         }
1526     }
1527 }
1528 
GetScrollPagingStatusDumpInfo(std::unique_ptr<JsonValue> & json)1529 void ScrollPattern::GetScrollPagingStatusDumpInfo(std::unique_ptr<JsonValue>& json)
1530 {
1531     switch (enablePagingStatus_) {
1532         case ScrollPagingStatus::NONE: {
1533             json->Put("enablePaging", "ScrollPagingStatus::NONE");
1534             break;
1535         }
1536         case ScrollPagingStatus::INVALID: {
1537             json->Put("enablePaging", "ScrollPagingStatus::INVALID");
1538             break;
1539         }
1540         case ScrollPagingStatus::VALID: {
1541             json->Put("enablePaging", "ScrollPagingStatus::VALID");
1542             break;
1543         }
1544         default: {
1545             break;
1546         }
1547     }
1548 }
1549 
GetScrollSnapAlignDumpInfo(std::unique_ptr<JsonValue> & json)1550 void ScrollPattern::GetScrollSnapAlignDumpInfo(std::unique_ptr<JsonValue>& json)
1551 {
1552     switch (GetScrollSnapAlign()) {
1553         case ScrollSnapAlign::NONE: {
1554             json->Put("snapAlign", "ScrollSnapAlign::NONE");
1555             break;
1556         }
1557         case ScrollSnapAlign::START: {
1558             json->Put("snapAlign", "ScrollSnapAlign::START");
1559             break;
1560         }
1561         case ScrollSnapAlign::CENTER: {
1562             json->Put("snapAlign", "ScrollSnapAlign::CENTER");
1563             break;
1564         }
1565         case ScrollSnapAlign::END: {
1566             json->Put("snapAlign", "ScrollSnapAlign::END");
1567             break;
1568         }
1569         default: {
1570             break;
1571         }
1572     }
1573 }
1574 
DumpAdvanceInfo(std::unique_ptr<JsonValue> & json)1575 void ScrollPattern::DumpAdvanceInfo(std::unique_ptr<JsonValue>& json)
1576 {
1577     auto host = GetHost();
1578     CHECK_NULL_VOID(host);
1579     auto hub = host->GetOrCreateEventHub<ScrollEventHub>();
1580     CHECK_NULL_VOID(hub);
1581     ScrollablePattern::DumpAdvanceInfo(json);
1582     json->Put("currentOffset", std::to_string(currentOffset_).c_str());
1583 
1584     GetScrollSnapAlignDumpInfo(json);
1585     auto snapPaginationStr = std::string("snapPagination: ");
1586     json->Put("snapPagination", GetScrollSnapPagination().c_str());
1587     json->Put("enableSnapToStart", enableSnapToSide_.first ? "true" : "false");
1588     json->Put("enableSnapToEnd", enableSnapToSide_.second ? "true" : "false");
1589 
1590     GetScrollPagingStatusDumpInfo(json);
1591     std::string snapOffsetsStr = "";
1592     for (const auto& iter : snapPaginations_) {
1593         snapOffsetsStr = snapOffsetsStr.append(iter.ToString()).append(" ");
1594     }
1595     json->Put("snapOffsets", snapOffsetsStr.c_str());
1596     json->Put("initialOffset",
1597         initialOffset_.has_value() ? initialOffset_->GetMainOffset(GetAxis()).ToString().c_str() : "None");
1598     auto onScrollEdge = hub->GetScrollEdgeEvent();
1599     json->Put("hasOnScrollEdge", onScrollEdge ? "true" : "false");
1600 
1601     std::unique_ptr<JsonValue> children = JsonUtil::CreateArray(true);
1602     for (const auto& info : scrollLayoutInfos_) {
1603         std::unique_ptr<JsonValue> child = JsonUtil::Create(true);
1604         info.ToJson(child);
1605         children->Put(child);
1606     }
1607     json->Put("scrollLayoutInfos", children);
1608     std::unique_ptr<JsonValue> infochildren = JsonUtil::CreateArray(true);
1609     for (const auto& info : scrollMeasureInfos_) {
1610         std::unique_ptr<JsonValue> child = JsonUtil::Create(true);
1611         info.ToJson(child);
1612         infochildren->Put(child);
1613     }
1614     json->Put("scrollMeasureInfos", infochildren);
1615 }
1616 
ProcessZoomScale()1617 void ScrollPattern::ProcessZoomScale()
1618 {
1619     if (childScale_ != zoomScale_) {
1620         if (childScale_.value_or(1.0f) != zoomScale_.value_or(1.0f)) {
1621             auto hub = GetOrCreateEventHub<ScrollEventHub>();
1622             if (hub) {
1623                 hub->FireOnDidZoom(zoomScale_.value_or(1.0f));
1624             }
1625         }
1626         childScale_ = zoomScale_;
1627         SetChildScale(childScale_);
1628     }
1629 }
1630 
SetMaxZoomScale(float scale)1631 void ScrollPattern::SetMaxZoomScale(float scale)
1632 {
1633     if (scale > 0) {
1634         maxZoomScale_ = scale;
1635     } else {
1636         maxZoomScale_ = 1.0f;
1637     }
1638 }
1639 
GetMaxZoomScale() const1640 float ScrollPattern::GetMaxZoomScale() const
1641 {
1642     return maxZoomScale_;
1643 }
1644 
SetMinZoomScale(float scale)1645 void ScrollPattern::SetMinZoomScale(float scale)
1646 {
1647     if (scale > 0) {
1648         minZoomScale_ = scale;
1649     } else {
1650         minZoomScale_ = 1.0f;
1651     }
1652 }
1653 
GetMinZoomScale() const1654 float ScrollPattern::GetMinZoomScale() const
1655 {
1656     return minZoomScale_;
1657 }
1658 
SetZoomScale(std::optional<float> scale)1659 void ScrollPattern::SetZoomScale(std::optional<float> scale)
1660 {
1661     if (scale.has_value() && scale.value() <= 0.0f) {
1662         scale = 1.0f;
1663     }
1664     if (scale != zoomScale_) {
1665         zoomScale_ = scale;
1666         auto host = GetHost();
1667         CHECK_NULL_VOID(host);
1668         auto prop = host->GetLayoutProperty();
1669         CHECK_NULL_VOID(prop);
1670         prop->UpdatePropertyChangeFlag(PROPERTY_UPDATE_MEASURE_SELF);
1671     }
1672 }
1673 
GetZoomScale() const1674 float ScrollPattern::GetZoomScale() const
1675 {
1676     return zoomScale_.value_or(1.0f);
1677 }
1678 
UpdateZoomScale(float scale)1679 void ScrollPattern::UpdateZoomScale(float scale)
1680 {
1681     if (scale <= 0.f) {
1682         scale = 1.f;
1683     }
1684     if (!zoomScale_.has_value() || scale != zoomScale_.value()) {
1685         zoomScale_ = scale;
1686         auto host = GetHost();
1687         CHECK_NULL_VOID(host);
1688         host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
1689         auto eventHub = host->GetOrCreateEventHub<ScrollEventHub>();
1690         CHECK_NULL_VOID(eventHub);
1691         eventHub->FireOnZoomScaleChange(scale);
1692     }
1693 }
1694 
SetEnableBouncesZoom(bool enable)1695 void ScrollPattern::SetEnableBouncesZoom(bool enable)
1696 {
1697     enableBouncesZoom_ = enable;
1698 }
1699 
GetEnableBouncesZoom() const1700 bool ScrollPattern::GetEnableBouncesZoom() const
1701 {
1702     return enableBouncesZoom_;
1703 }
1704 
SetChildScale(std::optional<float> scale)1705 void ScrollPattern::SetChildScale(std::optional<float> scale)
1706 {
1707     auto host = GetHost();
1708     CHECK_NULL_VOID(host);
1709     auto child = AceType::DynamicCast<FrameNode>(host->GetChildByIndex(0));
1710     CHECK_NULL_VOID(child);
1711     auto renderContext = child->GetRenderContext();
1712     if (scale.has_value()) {
1713         renderContext->SetScrollScale(scale.value());
1714     } else {
1715         renderContext->ResetScrollScale();
1716     }
1717 }
1718 
UpdatePinchGesture()1719 void ScrollPattern::UpdatePinchGesture()
1720 {
1721     if (!zoomCtrl_ && (maxZoomScale_ != 1.0f || minZoomScale_ != 1.0f)) {
1722         zoomCtrl_ = MakeRefPtr<ZoomController>(*this);
1723     } else if (zoomCtrl_ && maxZoomScale_ == 1.0f && minZoomScale_ == 1.0f) {
1724         zoomCtrl_.Reset();
1725     }
1726 }
1727 
GetChildrenExpandedSize()1728 SizeF ScrollPattern::GetChildrenExpandedSize()
1729 {
1730     auto axis = GetAxis();
1731     if (axis == Axis::VERTICAL) {
1732         return SizeF(viewPort_.Width(), viewPortExtent_.Height());
1733     } else if (axis == Axis::HORIZONTAL) {
1734         return SizeF(viewPortExtent_.Width(), viewPort_.Height());
1735     } else if (axis == Axis::FREE) {
1736         return SizeF(viewPortExtent_.Width(), viewPortExtent_.Height());
1737     }
1738     return SizeF();
1739 }
1740 
TriggerScrollBarDisplay()1741 void ScrollPattern::TriggerScrollBarDisplay()
1742 {
1743     auto scrollBar = GetScrollBar();
1744     CHECK_NULL_VOID(scrollBar);
1745     scrollBar->PlayScrollBarAppearAnimation();
1746     scrollBar->ScheduleDisappearDelayTask();
1747 }
1748 
GetFreeScrollOffset() const1749 Offset ScrollPattern::GetFreeScrollOffset() const
1750 {
1751     if (freeScroll_) {
1752         auto&& res = freeScroll_->GetLayoutOffset();
1753         return { Dimension(-res.GetX()).ConvertToVp(), Dimension(-res.GetY()).ConvertToVp() };
1754     }
1755     return {};
1756 }
1757 
GetOverrideRecognizer()1758 RefPtr<NGGestureRecognizer> ScrollPattern::GetOverrideRecognizer()
1759 {
1760     if (!freeScroll_) {
1761         return nullptr;
1762     }
1763     if (!zoomCtrl_) {
1764         return freeScroll_->GetFreePanGesture();
1765     }
1766     auto pan = freeScroll_->GetFreePanGesture();
1767     auto pinch = zoomCtrl_->GetPinchGesture();
1768     if (!gestureGroup_) {
1769         std::vector<RefPtr<NGGestureRecognizer>> recognizers = { pan, pinch };
1770         gestureGroup_ = MakeRefPtr<ParallelRecognizer>(recognizers);
1771     } else if (gestureGroup_->GetGroupRecognizer().empty()) {
1772         std::list<RefPtr<NGGestureRecognizer>> recognizers = { pan, pinch };
1773         gestureGroup_->AddChildren(recognizers);
1774     }
1775     return gestureGroup_;
1776 }
1777 
FreeScrollBy(const OffsetF & delta)1778 bool ScrollPattern::FreeScrollBy(const OffsetF& delta)
1779 {
1780     CHECK_NULL_RETURN(freeScroll_, false);
1781     freeScroll_->UpdateOffset(delta);
1782     return true;
1783 }
FreeScrollPage(bool reverse,bool smooth)1784 bool ScrollPattern::FreeScrollPage(bool reverse, bool smooth)
1785 {
1786     CHECK_NULL_RETURN(freeScroll_, false);
1787     const float dy = reverse ? viewSize_.Height() : -viewSize_.Height();
1788     if (smooth) {
1789         freeScroll_->ScrollTo(freeScroll_->GetOffset() + OffsetF { 0, dy }, std::nullopt);
1790     } else {
1791         freeScroll_->UpdateOffset({ 0, dy });
1792     }
1793     return true;
1794 }
FreeScrollToEdge(ScrollEdgeType type,bool smooth,std::optional<float> velocity)1795 bool ScrollPattern::FreeScrollToEdge(ScrollEdgeType type, bool smooth, std::optional<float> velocity)
1796 {
1797     CHECK_NULL_RETURN(freeScroll_, false);
1798     auto pos = freeScroll_->GetOffset();
1799     switch (type) {
1800         case ScrollEdgeType::SCROLL_LEFT:
1801             pos.SetX(0.0f);
1802             break;
1803         case ScrollEdgeType::SCROLL_RIGHT:
1804             pos.SetX(-FLT_MAX);
1805             break;
1806         case ScrollEdgeType::SCROLL_TOP:
1807             pos.SetY(0.0f);
1808             break;
1809         case ScrollEdgeType::SCROLL_BOTTOM:
1810             pos.SetY(-FLT_MAX);
1811             break;
1812         default:
1813             break;
1814     }
1815     if (smooth) {
1816         if (velocity) {
1817             constexpr float VELOCITY_TO_SPRING_RATIO = 100.0f;
1818             *velocity /= VELOCITY_TO_SPRING_RATIO; // Adjust velocity for smooth scrolling
1819         }
1820         freeScroll_->ScrollTo(pos, velocity);
1821     } else {
1822         freeScroll_->SetOffset(pos);
1823     }
1824     return true;
1825 }
FreeScrollTo(const ScrollControllerBase::ScrollToParam & param)1826 void ScrollPattern::FreeScrollTo(const ScrollControllerBase::ScrollToParam& param)
1827 {
1828     CHECK_NULL_VOID(freeScroll_);
1829     if (param.xOffset.Unit() == DimensionUnit::PERCENT || param.yOffset.Unit() == DimensionUnit::PERCENT) {
1830         TAG_LOGE(AceLogTag::ACE_SCROLL, "FreeScrollTo does not support percent offset.");
1831         return;
1832     }
1833     OffsetF pos { -static_cast<float>(param.xOffset.ConvertToPx()), -static_cast<float>(param.yOffset.ConvertToPx()) };
1834     if (param.smooth) {
1835         freeScroll_->ScrollTo(pos, std::nullopt, param.duration, param.curve, param.canOverScroll);
1836     } else {
1837         freeScroll_->SetOffset(pos, param.canOverScroll);
1838     }
1839 }
1840 
FireObserverTwoDimensionOnWillScroll(Dimension xOffset,Dimension yOffset,ScrollState state,ScrollSource source)1841 TwoDimensionScrollResult ScrollPattern::FireObserverTwoDimensionOnWillScroll(Dimension xOffset, Dimension yOffset,
1842     ScrollState state, ScrollSource source)
1843 {
1844     TwoDimensionScrollResult result = { .xOffset = xOffset, .yOffset = yOffset };
1845     CHECK_NULL_RETURN(positionController_, result);
1846     auto obsMgr = positionController_->GetObserverManager();
1847     CHECK_NULL_RETURN(obsMgr, result);
1848     ScrollFrameResult xResult = { .offset = xOffset };
1849     ScrollFrameResult yResult = { .offset = yOffset };
1850     obsMgr->HandleTwoDimensionOnWillScrollEvent(xResult, yResult, state, source);
1851     result.xOffset = xResult.offset;
1852     result.yOffset = yResult.offset;
1853     return result;
1854 }
1855 } // namespace OHOS::Ace::NG
1856