1 /*
2  * Copyright (c) 2022-2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "core/components_ng/pattern/scroll/scroll_pattern.h"
17 
18 #include "base/geometry/axis.h"
19 #include "base/geometry/dimension.h"
20 #include "base/log/dump_log.h"
21 #include "base/utils/utils.h"
22 #include "core/components_ng/base/inspector_filter.h"
23 #include "core/components_ng/pattern/scrollable/scrollable.h"
24 #include "core/components_ng/pattern/scroll/scroll_edge_effect.h"
25 #include "core/components_ng/pattern/scroll/scroll_event_hub.h"
26 #include "core/components_ng/pattern/scroll/scroll_layout_algorithm.h"
27 #include "core/components_ng/pattern/scroll/scroll_layout_property.h"
28 #include "core/components_ng/pattern/scroll/scroll_spring_effect.h"
29 #include "core/components_ng/pattern/scrollable/scrollable_properties.h"
30 #include "core/components_ng/property/measure_utils.h"
31 #include "core/components_ng/property/property.h"
32 #include "core/pipeline/pipeline_base.h"
33 
34 namespace OHOS::Ace::NG {
35 
36 namespace {
37 constexpr float SCROLL_BY_SPEED = 250.0f; // move 250 pixels per second
38 constexpr float UNIT_CONVERT = 1000.0f;   // 1s convert to 1000ms
39 constexpr Dimension SELECT_SCROLL_MIN_WIDTH = 64.0_vp;
40 constexpr int32_t COLUMN_NUM = 2;
41 constexpr float SCROLL_PAGING_SPEED_THRESHOLD = 1200.0f;
42 constexpr int32_t SCROLL_LAYOUT_INFO_COUNT = 30;
43 constexpr int32_t SCROLL_MEASURE_INFO_COUNT = 30;
44 constexpr double SCROLL_SNAP_INTERVAL_SIZE_MIN_VALUE = 1.0;
45 } // namespace
46 
OnModifyDone()47 void ScrollPattern::OnModifyDone()
48 {
49     Pattern::OnModifyDone();
50     auto host = GetHost();
51     CHECK_NULL_VOID(host);
52     auto layoutProperty = host->GetLayoutProperty<ScrollLayoutProperty>();
53     CHECK_NULL_VOID(layoutProperty);
54     auto paintProperty = host->GetPaintProperty<ScrollablePaintProperty>();
55     CHECK_NULL_VOID(paintProperty);
56     auto axis = layoutProperty->GetAxis().value_or(Axis::VERTICAL);
57     if (axis != GetAxis()) {
58         SetAxis(axis);
59         ResetPosition();
60     }
61     if (!GetScrollableEvent()) {
62         AddScrollEvent();
63     }
64     SetEdgeEffect();
65     SetScrollBar(paintProperty->GetScrollBarProperty());
66     SetAccessibilityAction();
67     if (scrollSnapUpdate_) {
68         host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
69     }
70     Register2DragDropManager();
71     auto overlayNode = host->GetOverlayNode();
72     if (!overlayNode && paintProperty->GetFadingEdge().value_or(false)) {
73         CreateAnalyzerOverlay(host);
74     }
75 }
76 
CreatePaintProperty()77 RefPtr<PaintProperty> ScrollPattern::CreatePaintProperty()
78 {
79     auto defaultDisplayMode = GetDefaultScrollBarDisplayMode();
80     auto property = MakeRefPtr<ScrollPaintProperty>();
81     property->UpdateScrollBarMode(defaultDisplayMode);
82     return property;
83 }
84 
OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper> & dirty,const DirtySwapConfig & config)85 bool ScrollPattern::OnDirtyLayoutWrapperSwap(const RefPtr<LayoutWrapper>& dirty, const DirtySwapConfig& config)
86 {
87     if (config.skipMeasure && config.skipLayout) {
88         return false;
89     }
90     if (!SetScrollProperties(dirty)) {
91         return false;
92     }
93     UpdateScrollBarOffset();
94     if (config.frameSizeChange) {
95         if (GetScrollBar() != nullptr) {
96             GetScrollBar()->ScheduleDisappearDelayTask();
97         }
98     }
99     auto host = GetHost();
100     CHECK_NULL_RETURN(host, false);
101     auto eventHub = host->GetEventHub<ScrollEventHub>();
102     CHECK_NULL_RETURN(eventHub, false);
103     PrintOffsetLog(AceLogTag::ACE_SCROLL, host->GetId(), prevOffset_ - currentOffset_);
104     FireOnDidScroll(prevOffset_ - currentOffset_);
105     auto onReachStart = eventHub->GetOnReachStart();
106     FireOnReachStart(onReachStart);
107     auto onReachEnd = eventHub->GetOnReachEnd();
108     FireOnReachEnd(onReachEnd);
109     OnScrollStop(eventHub->GetOnScrollStop());
110     ScrollSnapTrigger();
111     CheckScrollable();
112     prevOffset_ = currentOffset_;
113     auto geometryNode = host->GetGeometryNode();
114     CHECK_NULL_RETURN(geometryNode, false);
115     auto offsetRelativeToWindow = host->GetOffsetRelativeToWindow();
116     auto globalViewPort = RectF(offsetRelativeToWindow, geometryNode->GetFrameRect().GetSize());
117     host->SetViewPort(globalViewPort);
118     isInitialized_ = true;
119     SetScrollSource(SCROLL_FROM_NONE);
120     auto paintProperty = GetPaintProperty<ScrollablePaintProperty>();
121     CHECK_NULL_RETURN(paintProperty, false);
122     return paintProperty->GetFadingEdge().value_or(false);
123 }
124 
SetScrollProperties(const RefPtr<LayoutWrapper> & dirty)125 bool ScrollPattern::SetScrollProperties(const RefPtr<LayoutWrapper>& dirty)
126 {
127     auto layoutAlgorithmWrapper = DynamicCast<LayoutAlgorithmWrapper>(dirty->GetLayoutAlgorithm());
128     CHECK_NULL_RETURN(layoutAlgorithmWrapper, false);
129     auto layoutAlgorithm = DynamicCast<ScrollLayoutAlgorithm>(layoutAlgorithmWrapper->GetLayoutAlgorithm());
130     CHECK_NULL_RETURN(layoutAlgorithm, false);
131     currentOffset_ = layoutAlgorithm->GetCurrentOffset();
132     auto oldScrollableDistance = scrollableDistance_;
133     scrollableDistance_ = layoutAlgorithm->GetScrollableDistance();
134     if (!NearEqual(oldScrollableDistance, scrollableDistance_)) {
135         CheckScrollToEdge();
136         AddScrollLayoutInfo();
137     }
138 
139     if (LessNotEqual(scrollableDistance_, oldScrollableDistance)) {
140         CheckRestartSpring(true);
141     }
142     auto axis = GetAxis();
143     auto oldMainSize = GetMainAxisSize(viewPort_, axis);
144     auto newMainSize = GetMainAxisSize(layoutAlgorithm->GetViewPort(), axis);
145     auto oldExtentMainSize = GetMainAxisSize(viewPortExtent_, axis);
146     auto newExtentMainSize = GetMainAxisSize(layoutAlgorithm->GetViewPortExtent(), axis);
147     viewPortLength_ = layoutAlgorithm->GetViewPortLength();
148     viewPort_ = layoutAlgorithm->GetViewPort();
149     viewSize_ = layoutAlgorithm->GetViewSize();
150     viewPortExtent_ = layoutAlgorithm->GetViewPortExtent();
151     if (IsEnablePagingValid()) {
152         SetIntervalSize(Dimension(static_cast<double>(viewPortLength_)));
153     }
154     if (scrollSnapUpdate_ || !NearEqual(oldMainSize, newMainSize) || !NearEqual(oldExtentMainSize, newExtentMainSize)) {
155         CaleSnapOffsets();
156         scrollSnapUpdate_ = false;
157     }
158     return true;
159 }
160 
ScrollSnapTrigger()161 bool ScrollPattern::ScrollSnapTrigger()
162 {
163     auto scrollBar = GetScrollBar();
164     auto scrollBarProxy = GetScrollBarProxy();
165     if (scrollBar && scrollBar->IsPressed()) {
166         return false;
167     }
168     if (scrollBarProxy && scrollBarProxy->IsScrollSnapTrigger()) {
169         return false;
170     }
171     if (ScrollableIdle() && !AnimateRunning()) {
172         auto predictSnapOffset = CalePredictSnapOffset(0.0);
173         if (predictSnapOffset.has_value() && !NearZero(predictSnapOffset.value(), SPRING_ACCURACY)) {
174             StartScrollSnapMotion(predictSnapOffset.value(), 0.0f);
175             FireOnScrollStart();
176             return true;
177         }
178     }
179     return false;
180 }
181 
CheckScrollable()182 void ScrollPattern::CheckScrollable()
183 {
184     auto host = GetHost();
185     CHECK_NULL_VOID(host);
186     auto layoutProperty = host->GetLayoutProperty<ScrollLayoutProperty>();
187     CHECK_NULL_VOID(layoutProperty);
188     if (GreatNotEqual(scrollableDistance_, 0.0f)) {
189         SetScrollEnabled(layoutProperty->GetScrollEnabled().value_or(true));
190     } else {
191         SetScrollEnabled(layoutProperty->GetScrollEnabled().value_or(true) && GetAlwaysEnabled());
192     }
193 }
194 
OnScrollCallback(float offset,int32_t source)195 bool ScrollPattern::OnScrollCallback(float offset, int32_t source)
196 {
197     if (source != SCROLL_FROM_START) {
198         if (GetAxis() == Axis::NONE) {
199             return false;
200         }
201         if (!AnimateStoped()) {
202             return false;
203         }
204         auto adjustOffset = static_cast<float>(offset);
205         AdjustOffset(adjustOffset, source);
206         return UpdateCurrentOffset(adjustOffset, source);
207     } else {
208         FireOnScrollStart();
209     }
210     return true;
211 }
212 
OnScrollEndCallback()213 void ScrollPattern::OnScrollEndCallback()
214 {
215     auto host = GetHost();
216     CHECK_NULL_VOID(host);
217     auto eventHub = host->GetEventHub<ScrollEventHub>();
218     CHECK_NULL_VOID(eventHub);
219     auto scrollEndEvent = eventHub->GetScrollEndEvent();
220     if (scrollEndEvent) {
221         scrollEndEvent();
222     }
223     if (AnimateStoped()) {
224         scrollStop_ = true;
225         host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
226     }
227 }
228 
ResetPosition()229 void ScrollPattern::ResetPosition()
230 {
231     currentOffset_ = 0.0f;
232     lastOffset_ = 0.0f;
233 }
234 
IsAtTop() const235 bool ScrollPattern::IsAtTop() const
236 {
237     return GreatOrEqual(currentOffset_, 0.0);
238 }
239 
IsAtBottom() const240 bool ScrollPattern::IsAtBottom() const
241 {
242     if (LessNotEqual(scrollableDistance_, 0.0f)) {
243         return LessOrEqual(currentOffset_, 0.0f);
244     }
245     return LessOrEqual(currentOffset_, -scrollableDistance_);
246 }
247 
GetOverScrollOffset(double delta) const248 OverScrollOffset ScrollPattern::GetOverScrollOffset(double delta) const
249 {
250     OverScrollOffset offset = { 0, 0 };
251     auto startPos = currentOffset_;
252     auto newStartPos = startPos + delta;
253     if (startPos > 0 && newStartPos > 0) {
254         offset.start = delta;
255     }
256     if (startPos > 0 && newStartPos <= 0) {
257         offset.start = -startPos;
258     }
259     if (startPos <= 0 && newStartPos > 0) {
260         offset.start = newStartPos;
261     }
262 
263     auto endPos = currentOffset_;
264     auto newEndPos = endPos + delta;
265     auto endRefences =  GreatOrEqual(scrollableDistance_, 0.0f) ? -scrollableDistance_ : 0;
266     if (endPos < endRefences && newEndPos < endRefences) {
267         offset.end = delta;
268     }
269     if (endPos < endRefences && newEndPos >= endRefences) {
270         offset.end = endRefences - endPos;
271     }
272     if (endPos >= endRefences && newEndPos < endRefences) {
273         offset.end = newEndPos - endRefences;
274     }
275     return offset;
276 }
277 
IsOutOfBoundary(bool useCurrentDelta)278 bool ScrollPattern::IsOutOfBoundary(bool useCurrentDelta)
279 {
280     if (Positive(scrollableDistance_)) {
281         return Positive(currentOffset_) || LessNotEqual(currentOffset_, -scrollableDistance_);
282     } else {
283         return !NearZero(currentOffset_);
284     }
285 }
286 
ScrollPageCheck(float delta,int32_t source)287 bool ScrollPattern::ScrollPageCheck(float delta, int32_t source)
288 {
289     return true;
290 }
291 
AdjustOffset(float & delta,int32_t source)292 void ScrollPattern::AdjustOffset(float& delta, int32_t source)
293 {
294     if (NearZero(delta) || NearZero(viewPortLength_) || source == SCROLL_FROM_ANIMATION ||
295         source == SCROLL_FROM_ANIMATION_SPRING) {
296         return;
297     }
298     // the distance above the top, if lower than top, it is zero
299     float overScrollPastStart = 0.0f;
300     // the distance below the bottom, if higher than bottom, it is zero
301     float overScrollPastEnd = 0.0f;
302     float overScrollPast = 0.0f;
303     // not consider rowReverse or colReverse
304     overScrollPastStart = std::max(currentOffset_, 0.0f);
305     if (Positive(scrollableDistance_)) {
306         overScrollPastEnd = std::max(-scrollableDistance_ - currentOffset_, 0.0f);
307     } else {
308         overScrollPastEnd = std::abs(std::min(currentOffset_, 0.0f));
309     }
310     overScrollPast = std::max(overScrollPastStart, overScrollPastEnd);
311     if (overScrollPast == 0.0f) {
312         return;
313     }
314     float friction = ScrollablePattern::CalculateFriction(overScrollPast / viewPortLength_);
315     delta = delta * friction;
316 }
317 
ValidateOffset(int32_t source,float willScrollOffset)318 float ScrollPattern::ValidateOffset(int32_t source, float willScrollOffset)
319 {
320     if (LessOrEqual(scrollableDistance_, 0.0f) || source == SCROLL_FROM_JUMP) {
321         return willScrollOffset;
322     }
323 
324     // restrict position between top and bottom
325     if (IsRestrictBoundary() || source == SCROLL_FROM_BAR || source == SCROLL_FROM_BAR_FLING ||
326         source == SCROLL_FROM_ROTATE || source == SCROLL_FROM_AXIS) {
327         if (GetAxis() == Axis::HORIZONTAL) {
328             if (IsRowReverse()) {
329                 willScrollOffset = std::clamp(willScrollOffset, 0.0f, scrollableDistance_);
330             } else {
331                 willScrollOffset = std::clamp(willScrollOffset, -scrollableDistance_, 0.0f);
332             }
333         } else {
334             willScrollOffset = std::clamp(willScrollOffset, -scrollableDistance_, 0.0f);
335         }
336     }
337     return willScrollOffset;
338 }
339 
ValidateOffset(int32_t source)340 void ScrollPattern::ValidateOffset(int32_t source)
341 {
342     if (LessOrEqual(scrollableDistance_, 0.0f) || source == SCROLL_FROM_JUMP) {
343         return;
344     }
345 
346     // restrict position between top and bottom
347     if (IsRestrictBoundary() || source == SCROLL_FROM_BAR || source == SCROLL_FROM_BAR_FLING ||
348         source == SCROLL_FROM_ROTATE || source == SCROLL_FROM_AXIS) {
349         if (GetAxis() == Axis::HORIZONTAL) {
350             if (IsRowReverse()) {
351                 currentOffset_ = std::clamp(currentOffset_, 0.0f, scrollableDistance_);
352             } else {
353                 currentOffset_ = std::clamp(currentOffset_, -scrollableDistance_, 0.0f);
354             }
355         } else {
356             currentOffset_ = std::clamp(currentOffset_, -scrollableDistance_, 0.0f);
357         }
358     }
359 }
360 
HandleScrollPosition(float scroll)361 void ScrollPattern::HandleScrollPosition(float scroll)
362 {
363     auto eventHub = GetEventHub<ScrollEventHub>();
364     CHECK_NULL_VOID(eventHub);
365     auto onScroll = eventHub->GetOnScrollEvent();
366     CHECK_NULL_VOID(onScroll);
367     // not consider async call
368     Dimension scrollX(0, DimensionUnit::VP);
369     Dimension scrollY(0, DimensionUnit::VP);
370     Dimension scrollPx(scroll, DimensionUnit::PX);
371     auto scrollVpValue = scrollPx.ConvertToVp();
372     if (GetAxis() == Axis::HORIZONTAL) {
373         scrollX.SetValue(scrollVpValue);
374     } else {
375         scrollY.SetValue(scrollVpValue);
376     }
377     onScroll(scrollX, scrollY);
378 }
379 
FireTwoDimensionOnWillScroll(float scroll)380 float ScrollPattern::FireTwoDimensionOnWillScroll(float scroll)
381 {
382     auto eventHub = GetEventHub<ScrollEventHub>();
383     CHECK_NULL_RETURN(eventHub, scroll);
384     auto onScroll = eventHub->GetOnWillScrollEvent();
385     CHECK_NULL_RETURN(onScroll, scroll);
386     Dimension scrollX(0, DimensionUnit::VP);
387     Dimension scrollY(0, DimensionUnit::VP);
388     Dimension scrollPx(scroll, DimensionUnit::PX);
389     auto scrollVpValue = scrollPx.ConvertToVp();
390     if (GetAxis() == Axis::HORIZONTAL) {
391         scrollX.SetValue(scrollVpValue);
392     } else {
393         scrollY.SetValue(scrollVpValue);
394     }
395     auto scrollRes =
396         onScroll(scrollX, scrollY, GetScrollState(), ScrollablePattern::ConvertScrollSource(GetScrollSource()));
397     auto context = PipelineContext::GetCurrentContextSafely();
398     CHECK_NULL_RETURN(context, scroll);
399     if (GetAxis() == Axis::HORIZONTAL) {
400         return context->NormalizeToPx(scrollRes.xOffset);
401     } else {
402         return context->NormalizeToPx(scrollRes.yOffset);
403     }
404 }
405 
FireOnDidScroll(float scroll)406 void ScrollPattern::FireOnDidScroll(float scroll)
407 {
408     FireObserverOnDidScroll(scroll);
409     auto eventHub = GetEventHub<ScrollEventHub>();
410     CHECK_NULL_VOID(eventHub);
411     auto onScroll = eventHub->GetOnDidScrollEvent();
412     CHECK_NULL_VOID(onScroll);
413     Dimension scrollX(0, DimensionUnit::VP);
414     Dimension scrollY(0, DimensionUnit::VP);
415     Dimension scrollPx(scroll, DimensionUnit::PX);
416     auto scrollVpValue = scrollPx.ConvertToVp();
417     if (GetAxis() == Axis::HORIZONTAL) {
418         scrollX.SetValue(scrollVpValue);
419     } else {
420         scrollY.SetValue(scrollVpValue);
421     }
422     auto scrollState = GetScrollState();
423     bool isTriggered = false;
424     if (!NearZero(scroll)) {
425         onScroll(scrollX, scrollY, scrollState);
426         isTriggered = true;
427     }
428     if (scrollStop_ && !GetScrollAbort()) {
429         if (scrollState != ScrollState::IDLE || !isTriggered) {
430             onScroll(0.0_vp, 0.0_vp, ScrollState::IDLE);
431         }
432     }
433 }
434 
FireOnReachStart(const OnReachEvent & onReachStart)435 void ScrollPattern::FireOnReachStart(const OnReachEvent& onReachStart)
436 {
437     auto host = GetHost();
438     CHECK_NULL_VOID(host);
439     if (ReachStart(!isInitialized_)) {
440         FireObserverOnReachStart();
441         CHECK_NULL_VOID(onReachStart);
442         ACE_SCOPED_TRACE("OnReachStart, id:%d, tag:Scroll", static_cast<int32_t>(host->GetAccessibilityId()));
443         onReachStart();
444         AddEventsFiredInfo(ScrollableEventType::ON_REACH_START);
445     }
446 }
447 
FireOnReachEnd(const OnReachEvent & onReachEnd)448 void ScrollPattern::FireOnReachEnd(const OnReachEvent& onReachEnd)
449 {
450     auto host = GetHost();
451     CHECK_NULL_VOID(host);
452     if (ReachEnd(false)) {
453         FireObserverOnReachEnd();
454         CHECK_NULL_VOID(onReachEnd);
455         ACE_SCOPED_TRACE("OnReachEnd, id:%d, tag:Scroll", static_cast<int32_t>(host->GetAccessibilityId()));
456         onReachEnd();
457         AddEventsFiredInfo(ScrollableEventType::ON_REACH_END);
458     } else if (!isInitialized_ && ReachEnd(true)) {
459         FireObserverOnReachEnd();
460     }
461 }
462 
IsCrashTop() const463 bool ScrollPattern::IsCrashTop() const
464 {
465     bool scrollUpToReachTop = LessNotEqual(lastOffset_, 0.0) && GreatOrEqual(currentOffset_, 0.0);
466     bool scrollDownToReachTop = GreatNotEqual(lastOffset_, 0.0) && LessOrEqual(currentOffset_, 0.0);
467     return scrollUpToReachTop || scrollDownToReachTop;
468 }
469 
IsCrashBottom() const470 bool ScrollPattern::IsCrashBottom() const
471 {
472     float minExtent = -scrollableDistance_;
473     bool scrollDownToReachEnd = GreatNotEqual(lastOffset_, minExtent) && LessOrEqual(currentOffset_, minExtent);
474     bool scrollUpToReachEnd = LessNotEqual(lastOffset_, minExtent) && GreatOrEqual(currentOffset_, minExtent);
475     return (scrollUpToReachEnd || scrollDownToReachEnd);
476 }
477 
ReachStart(bool firstLayout) const478 bool ScrollPattern::ReachStart(bool firstLayout) const
479 {
480     bool scrollUpToReachTop = (LessNotEqual(prevOffset_, 0.0) || firstLayout) && GreatOrEqual(currentOffset_, 0.0);
481     bool scrollDownToReachTop = GreatNotEqual(prevOffset_, 0.0) && LessOrEqual(currentOffset_, 0.0);
482     return scrollUpToReachTop || scrollDownToReachTop;
483 }
484 
ReachEnd(bool firstLayout) const485 bool ScrollPattern::ReachEnd(bool firstLayout) const
486 {
487     float minExtent = -scrollableDistance_;
488     bool scrollDownToReachEnd =
489         (GreatNotEqual(prevOffset_, minExtent) || firstLayout) && LessOrEqual(currentOffset_, minExtent);
490     bool scrollUpToReachEnd = LessNotEqual(prevOffset_, minExtent) && GreatOrEqual(currentOffset_, minExtent);
491     return (scrollUpToReachEnd || scrollDownToReachEnd);
492 }
493 
HandleCrashTop()494 void ScrollPattern::HandleCrashTop()
495 {
496     auto frameNode = GetHost();
497     CHECK_NULL_VOID(frameNode);
498     auto eventHub = frameNode->GetEventHub<ScrollEventHub>();
499     CHECK_NULL_VOID(eventHub);
500     const auto& onScrollEdge = eventHub->GetScrollEdgeEvent();
501     CHECK_NULL_VOID(onScrollEdge);
502     // not consider async call
503     if (GetAxis() == Axis::HORIZONTAL) {
504         onScrollEdge(ScrollEdge::LEFT);
505         AddEventsFiredInfo(ScrollableEventType::ON_SCROLL_EDGE);
506         return;
507     }
508     onScrollEdge(ScrollEdge::TOP);
509     AddEventsFiredInfo(ScrollableEventType::ON_SCROLL_EDGE);
510 }
511 
HandleCrashBottom()512 void ScrollPattern::HandleCrashBottom()
513 {
514     auto frameNode = GetHost();
515     CHECK_NULL_VOID(frameNode);
516     auto eventHub = frameNode->GetEventHub<ScrollEventHub>();
517     CHECK_NULL_VOID(eventHub);
518     const auto& onScrollEdge = eventHub->GetScrollEdgeEvent();
519     CHECK_NULL_VOID(onScrollEdge);
520     if (GetAxis() == Axis::HORIZONTAL) {
521         onScrollEdge(ScrollEdge::RIGHT);
522         AddEventsFiredInfo(ScrollableEventType::ON_SCROLL_EDGE);
523         return;
524     }
525     onScrollEdge(ScrollEdge::BOTTOM);
526     AddEventsFiredInfo(ScrollableEventType::ON_SCROLL_EDGE);
527 }
528 
UpdateCurrentOffset(float delta,int32_t source)529 bool ScrollPattern::UpdateCurrentOffset(float delta, int32_t source)
530 {
531     auto host = GetHost();
532     CHECK_NULL_RETURN(host, false);
533     if (source != SCROLL_FROM_JUMP && !HandleEdgeEffect(delta, source, viewSize_)) {
534         if (IsOutOfBoundary()) {
535             host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
536         }
537         return false;
538     }
539     SetScrollSource(source);
540     FireAndCleanScrollingListener();
541     lastOffset_ = currentOffset_;
542     auto willScrollPosition = currentOffset_ + delta;
543     willScrollPosition = ValidateOffset(source, willScrollPosition);
544     auto userOffset = FireTwoDimensionOnWillScroll(currentOffset_ - willScrollPosition);
545     currentOffset_ -= userOffset;
546     ValidateOffset(source);
547     HandleScrollPosition(userOffset);
548     if (IsCrashTop()) {
549         HandleCrashTop();
550     } else if (IsCrashBottom()) {
551         HandleCrashBottom();
552     }
553     host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
554     return true;
555 }
556 
OnAnimateStop()557 void ScrollPattern::OnAnimateStop()
558 {
559     if (!GetIsDragging() || GetScrollAbort()) {
560         auto host = GetHost();
561         CHECK_NULL_VOID(host);
562         host->MarkDirtyNode(PROPERTY_UPDATE_MEASURE_SELF);
563         scrollStop_ = true;
564     }
565 }
566 
ScrollToEdge(ScrollEdgeType scrollEdgeType,bool smooth)567 void ScrollPattern::ScrollToEdge(ScrollEdgeType scrollEdgeType, bool smooth)
568 {
569     if (scrollEdgeType == ScrollEdgeType::SCROLL_NONE) {
570         return;
571     }
572     if (LessOrEqual(scrollableDistance_, 0.0)) {
573         return;
574     }
575     float distance = scrollEdgeType == ScrollEdgeType::SCROLL_TOP ? -currentOffset_ :
576         (-scrollableDistance_ - currentOffset_);
577     auto host = GetHost();
578     CHECK_NULL_VOID(host);
579     ACE_SCOPED_TRACE("Scroll ScrollToEdge scrollEdgeType:%zu, offset:%f, id:%d", scrollEdgeType, distance,
580         static_cast<int32_t>(host->GetAccessibilityId()));
581     if (!NearZero(distance)) {
582         ScrollBy(distance, distance, smooth);
583         scrollEdgeType_ = scrollEdgeType;
584     }
585 }
586 
CheckScrollToEdge()587 void ScrollPattern::CheckScrollToEdge()
588 {
589     if (scrollEdgeType_ != ScrollEdgeType::SCROLL_NONE) {
590         ScrollToEdge(scrollEdgeType_, true);
591     }
592 }
593 
ScrollBy(float pixelX,float pixelY,bool smooth,const std::function<void ()> & onFinish)594 void ScrollPattern::ScrollBy(float pixelX, float pixelY, bool smooth, const std::function<void()>& onFinish)
595 {
596     float distance = (GetAxis() == Axis::VERTICAL) ? pixelY : pixelX;
597     if (NearZero(distance)) {
598         return;
599     }
600     float position = currentOffset_ + distance;
601     if (smooth) {
602         AnimateTo(-position, fabs(distance) * UNIT_CONVERT / SCROLL_BY_SPEED, Curves::EASE_OUT, true, false, false);
603         return;
604     }
605     JumpToPosition(position);
606 }
607 
ScrollPage(bool reverse,bool smooth,AccessibilityScrollType scrollType)608 void ScrollPattern::ScrollPage(bool reverse, bool smooth, AccessibilityScrollType scrollType)
609 {
610     auto host = GetHost();
611     CHECK_NULL_VOID(host);
612     float distance = reverse ? viewPortLength_ : -viewPortLength_;
613     if (scrollType == AccessibilityScrollType::SCROLL_HALF) {
614         distance = distance / 2.f;
615     }
616     ACE_SCOPED_TRACE(
617         "Scroll ScrollPage distance:%f, id:%d", distance, static_cast<int32_t>(host->GetAccessibilityId()));
618     ScrollBy(distance, distance, smooth);
619 }
620 
JumpToPosition(float position,int32_t source)621 void ScrollPattern::JumpToPosition(float position, int32_t source)
622 {
623     // If an animation is playing, stop it.
624     auto lastAnimateRunning = AnimateRunning();
625     StopAnimate();
626     DoJump(position, source);
627     // AccessibilityEventType::SCROLL_END
628     if (lastAnimateRunning) {
629         SetScrollAbort(false);
630     }
631 }
632 
ScrollTo(float position)633 void ScrollPattern::ScrollTo(float position)
634 {
635     JumpToPosition(-position, SCROLL_FROM_JUMP);
636 }
637 
DoJump(float position,int32_t source)638 void ScrollPattern::DoJump(float position, int32_t source)
639 {
640     float setPosition = (GetAxis() == Axis::HORIZONTAL && IsRowReverse()) ? -position : position;
641     if (!NearEqual(currentOffset_, setPosition) && GreatOrEqual(scrollableDistance_, 0.0f)) {
642         UpdateCurrentOffset(setPosition - currentOffset_, source);
643     }
644 }
645 
SetEdgeEffectCallback(const RefPtr<ScrollEdgeEffect> & scrollEffect)646 void ScrollPattern::SetEdgeEffectCallback(const RefPtr<ScrollEdgeEffect>& scrollEffect)
647 {
648     scrollEffect->SetCurrentPositionCallback([weakScroll = AceType::WeakClaim(this)]() -> double {
649         auto scroll = weakScroll.Upgrade();
650         CHECK_NULL_RETURN(scroll, 0.0);
651         return scroll->GetCurrentPosition();
652     });
653     scrollEffect->SetLeadingCallback([weakScroll = AceType::WeakClaim(this)]() -> double {
654         auto scroll = weakScroll.Upgrade();
655         if (scroll && !scroll->IsRowReverse() && !scroll->IsColReverse() && scroll->GetScrollableDistance() > 0) {
656             return -scroll->GetScrollableDistance();
657         }
658         return 0.0;
659     });
660     scrollEffect->SetTrailingCallback([weakScroll = AceType::WeakClaim(this)]() -> double {
661         auto scroll = weakScroll.Upgrade();
662         if (scroll && (scroll->IsRowReverse() || scroll->IsColReverse())) {
663             return scroll->GetScrollableDistance();
664         }
665         return 0.0;
666     });
667     scrollEffect->SetInitLeadingCallback([weakScroll = AceType::WeakClaim(this)]() -> double {
668         auto scroll = weakScroll.Upgrade();
669         if (scroll && !scroll->IsRowReverse() && !scroll->IsColReverse() && scroll->GetScrollableDistance() > 0) {
670             return -scroll->GetScrollableDistance();
671         }
672         return 0.0;
673     });
674     scrollEffect->SetInitTrailingCallback([weakScroll = AceType::WeakClaim(this)]() -> double {
675         auto scroll = weakScroll.Upgrade();
676         if (scroll && (scroll->IsRowReverse() || scroll->IsColReverse())) {
677             return scroll->GetScrollableDistance();
678         }
679         return 0.0;
680     });
681 }
682 
UpdateScrollBarOffset()683 void ScrollPattern::UpdateScrollBarOffset()
684 {
685     CheckScrollBarOff();
686     if (!GetScrollBar() && !GetScrollBarProxy()) {
687         return;
688     }
689 
690     float scrollBarOutBoundaryExtent = 0.0f;
691     if (currentOffset_ > 0) {
692         scrollBarOutBoundaryExtent = currentOffset_;
693     } else if ((-currentOffset_) >= (GetMainSize(viewPortExtent_) - GetMainSize(viewPort_))) {
694         scrollBarOutBoundaryExtent = -currentOffset_ - (GetMainSize(viewPortExtent_) - GetMainSize(viewPort_));
695     }
696     HandleScrollBarOutBoundary(scrollBarOutBoundaryExtent);
697 
698     auto host = GetHost();
699     CHECK_NULL_VOID(host);
700     auto layoutProperty = host->GetLayoutProperty<ScrollLayoutProperty>();
701     CHECK_NULL_VOID(layoutProperty);
702     auto padding = layoutProperty->CreatePaddingAndBorder();
703     auto contentEndOffset = layoutProperty->GetScrollContentEndOffsetValue(.0f);
704     Size size(viewSize_.Width(), viewSize_.Height() - contentEndOffset);
705     auto viewPortExtent = viewPortExtent_;
706     AddPaddingToSize(padding, viewPortExtent);
707     auto estimatedHeight = (GetAxis() == Axis::HORIZONTAL) ? viewPortExtent.Width() : viewPortExtent.Height();
708     UpdateScrollBarRegion(-currentOffset_, estimatedHeight, size, Offset(0.0f, 0.0f));
709 }
710 
SetAccessibilityAction()711 void ScrollPattern::SetAccessibilityAction()
712 {
713     auto host = GetHost();
714     CHECK_NULL_VOID(host);
715     auto accessibilityProperty = host->GetAccessibilityProperty<AccessibilityProperty>();
716     CHECK_NULL_VOID(accessibilityProperty);
717     accessibilityProperty->SetActionScrollForward([weakPtr = WeakClaim(this)](AccessibilityScrollType scrollType) {
718         const auto& pattern = weakPtr.Upgrade();
719         CHECK_NULL_VOID(pattern);
720         auto host = pattern->GetHost();
721         CHECK_NULL_VOID(host);
722         ACE_SCOPED_TRACE("accessibility action, scroll forward, isScrollable:%u, IsPositiveScrollableDistance:%u, "
723                          "scrollType:%d, id:%d, tag:Scroll",
724             pattern->IsScrollable(), pattern->IsPositiveScrollableDistance(), scrollType,
725             static_cast<int32_t>(host->GetAccessibilityId()));
726         if (pattern->IsScrollable() && pattern->IsPositiveScrollableDistance()) {
727             pattern->ScrollPage(false, true, scrollType);
728         }
729     });
730 
731     accessibilityProperty->SetActionScrollBackward([weakPtr = WeakClaim(this)](AccessibilityScrollType scrollType) {
732         const auto& pattern = weakPtr.Upgrade();
733         CHECK_NULL_VOID(pattern);
734         auto host = pattern->GetHost();
735         CHECK_NULL_VOID(host);
736         ACE_SCOPED_TRACE("accessibility action, scroll backward, isScrollable:%u, IsPositiveScrollableDistance:%u, "
737                          "scrollType:%d, id:%d, tag:Scroll",
738             pattern->IsScrollable(), pattern->IsPositiveScrollableDistance(), scrollType,
739             static_cast<int32_t>(host->GetAccessibilityId()));
740         if (pattern->IsScrollable() && pattern->IsPositiveScrollableDistance()) {
741             pattern->ScrollPage(true, true, scrollType);
742         }
743     });
744 }
745 
GetOffsetToScroll(const RefPtr<FrameNode> & childFrame) const746 OffsetF ScrollPattern::GetOffsetToScroll(const RefPtr<FrameNode>& childFrame) const
747 {
748     auto frameNode = GetHost();
749     CHECK_NULL_RETURN(frameNode, OffsetF());
750     CHECK_NULL_RETURN(childFrame, OffsetF());
751     auto childGeometryNode = childFrame->GetGeometryNode();
752     CHECK_NULL_RETURN(childGeometryNode, OffsetF());
753     OffsetF result = childGeometryNode->GetFrameOffset();
754     auto parent = childFrame->GetParent();
755     while (parent) {
756         auto parentFrame = AceType::DynamicCast<FrameNode>(parent);
757         if (!parentFrame) {
758             parent = parent->GetParent();
759             continue;
760         }
761         if (parentFrame == frameNode) {
762             return result;
763         }
764         auto parentGeometryNode = parentFrame->GetGeometryNode();
765         if (!parentGeometryNode) {
766             parent = parent->GetParent();
767             continue;
768         }
769         result += parentGeometryNode->GetFrameOffset();
770         parent = parent->GetParent();
771     }
772     return OffsetF(0.0, 0.0);
773 }
774 
ScrollToNode(const RefPtr<FrameNode> & focusFrameNode)775 bool ScrollPattern::ScrollToNode(const RefPtr<FrameNode>& focusFrameNode)
776 {
777     CHECK_NULL_RETURN(focusFrameNode, false);
778     auto focusGeometryNode = focusFrameNode->GetGeometryNode();
779     CHECK_NULL_RETURN(focusGeometryNode, false);
780     auto focusNodeSize = focusGeometryNode->GetFrameSize();
781     auto focusNodeOffsetToScrolll = GetOffsetToScroll(focusFrameNode);
782     auto scrollFrame = GetHost();
783     CHECK_NULL_RETURN(scrollFrame, false);
784     auto scrollGeometry = scrollFrame->GetGeometryNode();
785     CHECK_NULL_RETURN(scrollGeometry, false);
786     auto scrollFrameSize = scrollGeometry->GetFrameSize();
787     float focusNodeDiffToScroll =
788         GetAxis() == Axis::VERTICAL ? focusNodeOffsetToScrolll.GetY() : focusNodeOffsetToScrolll.GetX();
789     if (NearZero(focusNodeDiffToScroll)) {
790         return false;
791     }
792     float focusNodeLength = GetAxis() == Axis::VERTICAL ? focusNodeSize.Height() : focusNodeSize.Width();
793     float scrollFrameLength = GetAxis() == Axis::VERTICAL ? scrollFrameSize.Height() : scrollFrameSize.Width();
794     float moveOffset = 0.0;
795     if (LessNotEqual(focusNodeDiffToScroll, 0)) {
796         moveOffset = -focusNodeDiffToScroll;
797     } else if (GreatNotEqual(focusNodeDiffToScroll + focusNodeLength, scrollFrameLength)) {
798         moveOffset = scrollFrameLength - focusNodeDiffToScroll - focusNodeLength;
799     }
800     if (!NearZero(moveOffset)) {
801         return OnScrollCallback(moveOffset, SCROLL_FROM_FOCUS_JUMP);
802     }
803     return false;
804 }
805 
GetScrollOffsetAbility()806 ScrollOffsetAbility ScrollPattern::GetScrollOffsetAbility()
807 {
808     return { [wp = WeakClaim(this)](float moveOffset) -> bool {
809                 auto pattern = wp.Upgrade();
810                 CHECK_NULL_RETURN(pattern, false);
811                 return pattern->OnScrollCallback(moveOffset, SCROLL_FROM_FOCUS_JUMP);
812             },
813         GetAxis() };
814 }
815 
CalePredictSnapOffset(float delta,float dragDistance,float velocity)816 std::optional<float> ScrollPattern::CalePredictSnapOffset(float delta, float dragDistance, float velocity)
817 {
818     std::optional<float> predictSnapOffset;
819     CHECK_NULL_RETURN(IsScrollSnap(), predictSnapOffset);
820     float finalPosition = currentOffset_ + delta;
821     if (IsEnablePagingValid()) {
822         finalPosition = GetPagingOffset(delta, dragDistance, velocity);
823     }
824     if (!IsSnapToInterval()) {
825         if (!enableSnapToSide_.first) {
826             if (GreatNotEqual(finalPosition, *(snapOffsets_.begin() + 1)) ||
827                 GreatNotEqual(currentOffset_, *(snapOffsets_.begin() + 1))) {
828                 return predictSnapOffset;
829             }
830         }
831         if (!enableSnapToSide_.second) {
832             if (LessNotEqual(finalPosition, *(snapOffsets_.rbegin() + 1)) ||
833                 LessNotEqual(currentOffset_, *(snapOffsets_.rbegin() + 1))) {
834                 return predictSnapOffset;
835             }
836         }
837     }
838     float head = 0.0f;
839     float tail = -scrollableDistance_;
840     if (GreatOrEqual(finalPosition, head) || LessOrEqual(finalPosition, tail)) {
841         return predictSnapOffset;
842     } else if (LessNotEqual(finalPosition, head) && GreatOrEqual(finalPosition, *(snapOffsets_.begin()))) {
843         predictSnapOffset = *(snapOffsets_.begin());
844     } else if (GreatNotEqual(finalPosition, tail) && LessOrEqual(finalPosition, *(snapOffsets_.rbegin()))) {
845         predictSnapOffset = *(snapOffsets_.rbegin());
846     } else {
847         auto iter = snapOffsets_.begin() + 1;
848         float start = *(iter - 1);
849         float end = *(iter);
850         for (; iter != snapOffsets_.end(); ++iter) {
851             if (GreatOrEqual(finalPosition, *iter)) {
852                 start = *(iter - 1);
853                 end = *(iter);
854                 predictSnapOffset = (LessNotEqual(start - finalPosition, finalPosition - end) ? start : end);
855                 break;
856             }
857         }
858     }
859     if (predictSnapOffset.has_value()) {
860         predictSnapOffset = predictSnapOffset.value() - currentOffset_;
861     }
862     return predictSnapOffset;
863 }
864 
CaleSnapOffsets()865 void ScrollPattern::CaleSnapOffsets()
866 {
867     auto scrollSnapAlign = GetScrollSnapAlign();
868     std::vector<float>().swap(snapOffsets_);
869     if (scrollSnapAlign == ScrollSnapAlign::NONE) {
870         CHECK_NULL_VOID(enablePagingStatus_ == ScrollPagingStatus::VALID);
871         scrollSnapAlign = ScrollSnapAlign::START;
872     }
873     if (IsSnapToInterval()) {
874         CaleSnapOffsetsByInterval(scrollSnapAlign);
875     } else {
876         CaleSnapOffsetsByPaginations(scrollSnapAlign);
877     }
878 }
879 
CaleSnapOffsetsByInterval(ScrollSnapAlign scrollSnapAlign)880 void ScrollPattern::CaleSnapOffsetsByInterval(ScrollSnapAlign scrollSnapAlign)
881 {
882     CHECK_NULL_VOID(GreatOrEqual(intervalSize_.Value(), SCROLL_SNAP_INTERVAL_SIZE_MIN_VALUE));
883     auto mainSize = GetMainAxisSize(viewPort_, GetAxis());
884     auto extentMainSize = GetMainAxisSize(viewPortExtent_, GetAxis());
885     auto start = 0.0f;
886     auto end = -scrollableDistance_;
887     auto snapOffset = 0.0f;
888     auto sizeDelta = 0.0f;
889     auto intervalSize = intervalSize_.Unit() == DimensionUnit::PERCENT ?
890                         intervalSize_.Value() * mainSize : intervalSize_.ConvertToPx();
891     float temp = static_cast<int32_t>(extentMainSize / intervalSize) * intervalSize;
892     switch (scrollSnapAlign) {
893         case ScrollSnapAlign::START:
894             end = -temp;
895             break;
896         case ScrollSnapAlign::CENTER:
897             sizeDelta = (mainSize - intervalSize) / 2;
898             start = Positive(sizeDelta) ? sizeDelta - static_cast<int32_t>(sizeDelta / intervalSize) * intervalSize
899                                         : sizeDelta;
900             end = -temp + (mainSize - extentMainSize + temp) / 2;
901             break;
902         case ScrollSnapAlign::END:
903             sizeDelta = mainSize - intervalSize;
904             start = Positive(sizeDelta) ? mainSize - static_cast<int32_t>(mainSize / intervalSize) * intervalSize
905                                         : sizeDelta;
906             end = -scrollableDistance_;
907             break;
908         default:
909             break;
910     }
911     if (!Positive(start)) {
912         snapOffsets_.emplace_back(start);
913     }
914     snapOffset = start - intervalSize;
915     while (GreatOrEqual(snapOffset, -scrollableDistance_) && GreatOrEqual(snapOffset, end)) {
916         snapOffsets_.emplace_back(snapOffset);
917         snapOffset -= intervalSize;
918     }
919     if (GreatNotEqual(end, -scrollableDistance_)) {
920         snapOffsets_.emplace_back(end);
921     }
922     if (IsEnablePagingValid()) {
923         if (NearEqual(snapOffset + intervalSize, -scrollableDistance_)) {
924             lastPageLength_ = 0.f;
925             return;
926         }
927         lastPageLength_ = scrollableDistance_ + snapOffset + intervalSize;
928         snapOffsets_.emplace_back(-scrollableDistance_);
929     }
930 }
931 
CaleSnapOffsetsByPaginations(ScrollSnapAlign scrollSnapAlign)932 void ScrollPattern::CaleSnapOffsetsByPaginations(ScrollSnapAlign scrollSnapAlign)
933 {
934     auto mainSize = GetMainAxisSize(viewPort_, GetAxis());
935     auto extentMainSize = GetMainAxisSize(viewPortExtent_, GetAxis());
936     auto start = 0.0f;
937     auto end = -scrollableDistance_;
938     auto snapOffset = 0.0f;
939     snapOffsets_.emplace_back(start);
940     int32_t length = 0;
941     auto snapPaginations = snapPaginations_;
942     snapPaginations.emplace(snapPaginations.begin(), Dimension(0.f));
943     auto current = 0.0f;
944     auto next = 0.0f;
945     auto size = static_cast<int32_t>(snapPaginations.size());
946     auto element = snapPaginations[length];
947     auto nextElement = snapPaginations[length + 1];
948     for (; length < size; length++) {
949         element = snapPaginations[length];
950         nextElement = snapPaginations[length + 1];
951         current = element.Unit() == DimensionUnit::PERCENT ? element.Value() * mainSize : element.ConvertToPx();
952         if (length == size - 1) {
953             next = extentMainSize;
954         } else {
955             next = nextElement.Unit() == DimensionUnit::PERCENT ? nextElement.Value() * mainSize
956                                                                 : nextElement.ConvertToPx();
957         }
958         switch (scrollSnapAlign) {
959             case ScrollSnapAlign::START:
960                 snapOffset = -current;
961                 break;
962             case ScrollSnapAlign::CENTER:
963                 snapOffset = (mainSize - (current + next)) / 2.0f;
964                 break;
965             case ScrollSnapAlign::END:
966                 snapOffset = mainSize - next;
967                 break;
968             default:
969                 break;
970         }
971         if (!Negative(snapOffset)) {
972             continue;
973         }
974         if (GreatNotEqual(snapOffset, -scrollableDistance_)) {
975             snapOffsets_.emplace_back(snapOffset);
976         } else {
977             break;
978         }
979     }
980     snapOffsets_.emplace_back(end);
981 }
982 
NeedScrollSnapToSide(float delta)983 bool ScrollPattern::NeedScrollSnapToSide(float delta)
984 {
985     CHECK_NULL_RETURN(GetScrollSnapAlign() != ScrollSnapAlign::NONE, false);
986     CHECK_NULL_RETURN(!IsSnapToInterval(), false);
987     auto finalPosition = currentOffset_ + delta;
988     CHECK_NULL_RETURN(static_cast<int32_t>(snapOffsets_.size()) > 2, false);
989     if (!enableSnapToSide_.first) {
990         if (GreatOrEqual(currentOffset_, *(snapOffsets_.begin() + 1)) &&
991             LessOrEqual(finalPosition, *(snapOffsets_.begin() + 1))) {
992             return true;
993         }
994     }
995     if (!enableSnapToSide_.second) {
996         if (LessOrEqual(currentOffset_, *(snapOffsets_.rbegin() + 1)) &&
997             GreatOrEqual(finalPosition, *(snapOffsets_.rbegin() + 1))) {
998             return true;
999         }
1000     }
1001     return false;
1002 }
1003 
ProvideRestoreInfo()1004 std::string ScrollPattern::ProvideRestoreInfo()
1005 {
1006     Dimension dimension(currentOffset_);
1007     return StringUtils::DoubleToString(dimension.ConvertToVp());
1008 }
1009 
OnRestoreInfo(const std::string & restoreInfo)1010 void ScrollPattern::OnRestoreInfo(const std::string& restoreInfo)
1011 {
1012     Dimension dimension = StringUtils::StringToDimension(restoreInfo, true);
1013     currentOffset_ = dimension.ConvertToPx();
1014 }
1015 
GetItemRect(int32_t index) const1016 Rect ScrollPattern::GetItemRect(int32_t index) const
1017 {
1018     auto host = GetHost();
1019     CHECK_NULL_RETURN(host, Rect());
1020     if (index != 0 || host->TotalChildCount() != 1) {
1021         return Rect();
1022     }
1023     auto item = host->GetChildByIndex(index);
1024     CHECK_NULL_RETURN(item, Rect());
1025     auto itemGeometry = item->GetGeometryNode();
1026     CHECK_NULL_RETURN(itemGeometry, Rect());
1027     return Rect(itemGeometry->GetFrameRect().GetX(), itemGeometry->GetFrameRect().GetY(),
1028         itemGeometry->GetFrameRect().Width(), itemGeometry->GetFrameRect().Height());
1029 }
1030 
GetSelectScrollWidth()1031 float ScrollPattern::GetSelectScrollWidth()
1032 {
1033     RefPtr<GridColumnInfo> columnInfo = GridSystemManager::GetInstance().GetInfoByType(GridColumnType::MENU);
1034     auto parent = columnInfo->GetParent();
1035     CHECK_NULL_RETURN(parent, SELECT_SCROLL_MIN_WIDTH.ConvertToPx());
1036     parent->BuildColumnWidth();
1037     auto defaultWidth = static_cast<float>(columnInfo->GetWidth(COLUMN_NUM));
1038     auto scrollNode = GetHost();
1039     CHECK_NULL_RETURN(scrollNode, SELECT_SCROLL_MIN_WIDTH.ConvertToPx());
1040     float finalWidth = SELECT_SCROLL_MIN_WIDTH.ConvertToPx();
1041 
1042     if (IsWidthModifiedBySelect()) {
1043         auto scrollLayoutProperty = scrollNode->GetLayoutProperty<ScrollLayoutProperty>();
1044         CHECK_NULL_RETURN(scrollLayoutProperty, SELECT_SCROLL_MIN_WIDTH.ConvertToPx());
1045         auto selectModifiedWidth = scrollLayoutProperty->GetScrollWidth();
1046         finalWidth = selectModifiedWidth.value();
1047     } else {
1048         finalWidth = defaultWidth;
1049     }
1050 
1051     if (finalWidth < SELECT_SCROLL_MIN_WIDTH.ConvertToPx()) {
1052         finalWidth = defaultWidth;
1053     }
1054 
1055     return finalWidth;
1056 }
1057 
GetPagingOffset(float delta,float dragDistance,float velocity) const1058 float ScrollPattern::GetPagingOffset(float delta, float dragDistance, float velocity)  const
1059 {
1060     // handle last page
1061     auto currentOffset = currentOffset_;
1062     if (GreatNotEqual(lastPageLength_, 0.f) &&
1063         LessNotEqual(currentOffset - dragDistance, -scrollableDistance_ + lastPageLength_)) {
1064         if (LessOrEqual(dragDistance, lastPageLength_)) {
1065             return currentOffset - dragDistance + GetPagingDelta(dragDistance, velocity, lastPageLength_);
1066         }
1067         if (GreatNotEqual(dragDistance, lastPageLength_)) {
1068             dragDistance -= lastPageLength_;
1069         }
1070     }
1071     // handle other pages
1072     float head = 0.0f;
1073     float tail = -scrollableDistance_;
1074     dragDistance = fmod(dragDistance, viewPortLength_);
1075     auto pagingPosition = currentOffset - dragDistance + GetPagingDelta(dragDistance, velocity, viewPortLength_);
1076     auto finalPosition = currentOffset + delta;
1077     auto useFinalPosition = (GreatOrEqual(pagingPosition, head) && !GreatOrEqual(finalPosition, head)) ||
1078                       (LessOrEqual(pagingPosition, tail) && !LessOrEqual(finalPosition, tail));
1079     return useFinalPosition ? finalPosition : pagingPosition;
1080 }
1081 
GetPagingDelta(float dragDistance,float velocity,float pageLength) const1082 float ScrollPattern::GetPagingDelta(float dragDistance, float velocity, float pageLength)  const
1083 {
1084     auto dragDistanceThreshold = pageLength * 0.5f;
1085     // dragDistance and velocity have not reached the threshold
1086     if (LessNotEqual(std::abs(dragDistance), dragDistanceThreshold) &&
1087         LessNotEqual(std::abs(velocity), SCROLL_PAGING_SPEED_THRESHOLD)) {
1088         return 0.f;
1089     }
1090     // The direction of dragDistance is the same as the direction of velocity
1091     if (GreatOrEqual(dragDistance * velocity, 0.f)) {
1092         auto direction = NearZero(dragDistance) ? velocity : dragDistance;
1093         return GreatNotEqual(direction, 0.f) ? pageLength : -pageLength;
1094     }
1095     // The direction of dragDistance is opposite to the direction of velocity
1096     if (GreatOrEqual(std::abs(dragDistance), dragDistanceThreshold) &&
1097         LessNotEqual(std::abs(velocity), SCROLL_PAGING_SPEED_THRESHOLD)) {
1098         return GreatNotEqual(dragDistance, 0.f) ? pageLength : -pageLength;
1099     }
1100     return 0.f;
1101 }
1102 
TriggerModifyDone()1103 void ScrollPattern::TriggerModifyDone()
1104 {
1105     OnModifyDone();
1106 }
1107 
AddScrollMeasureInfo(const std::optional<LayoutConstraintF> & parentConstraint,const std::optional<LayoutConstraintF> & childConstraint,const SizeF & selfSize,const SizeF & childSize)1108 void ScrollPattern::AddScrollMeasureInfo(const std::optional<LayoutConstraintF>& parentConstraint,
1109     const std::optional<LayoutConstraintF>& childConstraint, const SizeF& selfSize, const SizeF& childSize)
1110 {
1111     if (scrollMeasureInfos_.size() >= SCROLL_MEASURE_INFO_COUNT) {
1112         scrollMeasureInfos_.pop_front();
1113     }
1114     scrollMeasureInfos_.push_back(ScrollMeasureInfo({
1115         .changedTime_ = GetSysTimestamp(),
1116         .parentConstraint_ = parentConstraint,
1117         .childConstraint_ = childConstraint,
1118         .selfSize_ = selfSize,
1119         .childSize_ = childSize,
1120     }));
1121 }
1122 
AddScrollLayoutInfo()1123 void ScrollPattern::AddScrollLayoutInfo()
1124 {
1125     if (scrollLayoutInfos_.size() >= SCROLL_LAYOUT_INFO_COUNT) {
1126         scrollLayoutInfos_.pop_front();
1127     }
1128     scrollLayoutInfos_.push_back(ScrollLayoutInfo({
1129         .changedTime_ = GetSysTimestamp(),
1130         .scrollableDistance_ = scrollableDistance_,
1131         .scrollSize_ = viewSize_,
1132         .viewPort_ = viewPort_,
1133         .childSize_ = viewPortExtent_,
1134     }));
1135 }
1136 
GetScrollSnapAlignDumpInfo()1137 void ScrollPattern::GetScrollSnapAlignDumpInfo()
1138 {
1139     switch (GetScrollSnapAlign()) {
1140         case ScrollSnapAlign::NONE: {
1141             DumpLog::GetInstance().AddDesc("snapAlign: ScrollSnapAlign::NONE");
1142             break;
1143         }
1144         case ScrollSnapAlign::START: {
1145             DumpLog::GetInstance().AddDesc("snapAlign: ScrollSnapAlign::START");
1146             break;
1147         }
1148         case ScrollSnapAlign::CENTER: {
1149             DumpLog::GetInstance().AddDesc("snapAlign: ScrollSnapAlign::CENTER");
1150             break;
1151         }
1152         case ScrollSnapAlign::END: {
1153             DumpLog::GetInstance().AddDesc("snapAlign: ScrollSnapAlign::END");
1154             break;
1155         }
1156         default: {
1157             break;
1158         }
1159     }
1160 }
1161 
GetScrollPagingStatusDumpInfo()1162 void ScrollPattern::GetScrollPagingStatusDumpInfo()
1163 {
1164     switch (enablePagingStatus_) {
1165         case ScrollPagingStatus::NONE: {
1166             DumpLog::GetInstance().AddDesc("enablePaging: ScrollPagingStatus::NONE");
1167             break;
1168         }
1169         case ScrollPagingStatus::INVALID: {
1170             DumpLog::GetInstance().AddDesc("enablePaging: ScrollPagingStatus::INVALID");
1171             break;
1172         }
1173         case ScrollPagingStatus::VALID: {
1174             DumpLog::GetInstance().AddDesc("enablePaging: ScrollPagingStatus::VALID");
1175             break;
1176         }
1177         default: {
1178             break;
1179         }
1180     }
1181 }
1182 
DumpAdvanceInfo()1183 void ScrollPattern::DumpAdvanceInfo()
1184 {
1185     auto host = GetHost();
1186     CHECK_NULL_VOID(host);
1187     auto hub = host->GetEventHub<ScrollEventHub>();
1188     CHECK_NULL_VOID(hub);
1189     ScrollablePattern::DumpAdvanceInfo();
1190     DumpLog::GetInstance().AddDesc(std::string("currentOffset: ").append(std::to_string(currentOffset_)));
1191     GetScrollSnapAlignDumpInfo();
1192     auto snapPaginationStr = std::string("snapPagination: ");
1193     DumpLog::GetInstance().AddDesc(snapPaginationStr.append(GetScrollSnapPagination()));
1194     enableSnapToSide_.first ? DumpLog::GetInstance().AddDesc("enableSnapToStart: true")
1195                             : DumpLog::GetInstance().AddDesc("enableSnapToStart: false");
1196     enableSnapToSide_.second ? DumpLog::GetInstance().AddDesc("enableSnapToEnd: true")
1197                              : DumpLog::GetInstance().AddDesc("enableSnapToEnd: false");
1198     GetScrollPagingStatusDumpInfo();
1199     auto snapOffsetsStr = std::string("snapOffsets: [");
1200     for (const auto& iter : snapPaginations_) {
1201         snapOffsetsStr = snapOffsetsStr.append(iter.ToString()).append(" ");
1202     }
1203     DumpLog::GetInstance().AddDesc(snapOffsetsStr.append("]"));
1204     initialOffset_.has_value() ? DumpLog::GetInstance().AddDesc(std::string("initialOffset: ")
1205         .append(initialOffset_->GetMainOffset(GetAxis()).ToString()))
1206         : DumpLog::GetInstance().AddDesc("initialOffset: None");
1207     auto onScrollEdge = hub->GetScrollEdgeEvent();
1208     onScrollEdge ? DumpLog::GetInstance().AddDesc("hasOnScrollEdge: true")
1209                  : DumpLog::GetInstance().AddDesc("hasOnScrollEdge: false");
1210     DumpLog::GetInstance().AddDesc("==========================scrollLayoutInfos==========================");
1211     for (const auto& info : scrollLayoutInfos_) {
1212         DumpLog::GetInstance().AddDesc(info.ToString());
1213     }
1214     DumpLog::GetInstance().AddDesc("==========================scrollLayoutInfos==========================");
1215     DumpLog::GetInstance().AddDesc("==========================scrollMeasureInfos==========================");
1216     for (const auto& info : scrollMeasureInfos_) {
1217         DumpLog::GetInstance().AddDesc(info.ToString());
1218     }
1219     DumpLog::GetInstance().AddDesc("==========================scrollMeasureInfos==========================");
1220 }
1221 
ToJsonValue(std::unique_ptr<JsonValue> & json,const InspectorFilter & filter) const1222 void ScrollPattern::ToJsonValue(std::unique_ptr<JsonValue>& json, const InspectorFilter& filter) const
1223 {
1224     ScrollablePattern::ToJsonValue(json, filter);
1225     /* no fixed attr below, just return */
1226     if (filter.IsFastFilter()) {
1227         return;
1228     }
1229     auto initialOffset = JsonUtil::Create(true);
1230     initialOffset->Put("xOffset", GetInitialOffset().GetX().ToString().c_str());
1231     initialOffset->Put("yOffset", GetInitialOffset().GetY().ToString().c_str());
1232     json->PutExtAttr("initialOffset", initialOffset, filter);
1233     if (enablePagingStatus_ != ScrollPagingStatus::NONE) {
1234         json->PutExtAttr("enablePaging", enablePagingStatus_ == ScrollPagingStatus::VALID, filter);
1235     }
1236 
1237     auto scrollSnapOptions = JsonUtil::Create(true);
1238     if (IsSnapToInterval()) {
1239         scrollSnapOptions->Put("snapPagination", intervalSize_.ToString().c_str());
1240     } else {
1241         auto snapPaginationArr = JsonUtil::CreateArray(true);
1242         auto iter = snapPaginations_.begin();
1243         for (auto i = 0; iter != snapPaginations_.end(); ++iter, ++i) {
1244             snapPaginationArr->Put(std::to_string(i).c_str(), (*iter).ToString().c_str());
1245         }
1246         scrollSnapOptions->Put("snapPagination", snapPaginationArr);
1247     }
1248     scrollSnapOptions->Put("enableSnapToStart", enableSnapToSide_.first);
1249     scrollSnapOptions->Put("enableSnapToEnd", enableSnapToSide_.second);
1250     json->PutExtAttr("scrollSnap", scrollSnapOptions, filter);
1251 }
1252 
GetScrollSnapPagination() const1253 std::string ScrollPattern::GetScrollSnapPagination() const
1254 {
1255     auto snapPaginationStr = std::string("");
1256     if (IsSnapToInterval()) {
1257         snapPaginationStr = intervalSize_.ToString();
1258     } else {
1259         snapPaginationStr.append("[");
1260         auto iter = snapPaginations_.begin();
1261         for (; iter != snapPaginations_.end(); ++iter) {
1262             snapPaginationStr = snapPaginationStr.append((*iter).ToString()).append(" ");
1263         }
1264         snapPaginationStr.append("]");
1265     }
1266     return snapPaginationStr;
1267 }
1268 
OnScrollSnapCallback(double targetOffset,double velocity)1269 bool ScrollPattern::OnScrollSnapCallback(double targetOffset, double velocity)
1270 {
1271     return ScrollSnapTrigger();
1272 }
1273 
GetChildrenExpandedSize()1274 SizeF ScrollPattern::GetChildrenExpandedSize()
1275 {
1276     auto axis = GetAxis();
1277     if (axis == Axis::VERTICAL) {
1278         return SizeF(viewPort_.Width(), viewPortExtent_.Height());
1279     } else if (axis == Axis::HORIZONTAL) {
1280         return SizeF(viewPortExtent_.Width(), viewPort_.Height());
1281     }
1282     return SizeF();
1283 }
1284 } // namespace OHOS::Ace::NG
1285