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