1 /*
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "core/components_ng/pattern/scrollable/scrollable_pattern.h"
17 #include "base/geometry/axis.h"
18 #include "base/geometry/point.h"
19 #include "base/memory/ace_type.h"
20 #include "base/utils/utils.h"
21 #include "core/components/scroll/scrollable.h"
22 #include "core/components_ng/pattern/scroll/effect/scroll_fade_effect.h"
23 #include "core/components_ng/pattern/scroll/scroll_spring_effect.h"
24
25 namespace OHOS::Ace::NG {
SetAxis(Axis axis)26 void ScrollablePattern::SetAxis(Axis axis)
27 {
28 if (axis_ == axis) {
29 return;
30 }
31 axis_ = axis;
32 if (scrollBar_) {
33 scrollBar_->SetPositionMode(axis_ == Axis::HORIZONTAL ? PositionMode::BOTTOM : PositionMode::RIGHT);
34 }
35 auto gestureHub = GetGestureHub();
36 CHECK_NULL_VOID(gestureHub);
37 if (scrollableEvent_) {
38 gestureHub->RemoveScrollableEvent(scrollableEvent_);
39 scrollableEvent_->SetAxis(axis);
40 gestureHub->AddScrollableEvent(scrollableEvent_);
41 }
42 if (scrollEffect_) {
43 gestureHub->RemoveScrollEdgeEffect(scrollEffect_);
44 gestureHub->AddScrollEdgeEffect(GetAxis(), scrollEffect_);
45 }
46 }
47
GetGestureHub()48 const RefPtr<GestureEventHub>& ScrollablePattern::GetGestureHub()
49 {
50 auto host = GetHost();
51 CHECK_NULL_RETURN(host, nullptr);
52 auto hub = host->GetEventHub<EventHub>();
53 CHECK_NULL_RETURN(hub, nullptr);
54 return hub->GetOrCreateGestureEventHub();
55 }
56
GetInputHub()57 const RefPtr<InputEventHub>& ScrollablePattern::GetInputHub()
58 {
59 auto host = GetHost();
60 CHECK_NULL_RETURN(host, nullptr);
61 auto hub = host->GetEventHub<EventHub>();
62 CHECK_NULL_RETURN(host, nullptr);
63 return hub->GetOrCreateInputEventHub();
64 }
65
OnScrollCallback(float offset,int32_t source)66 bool ScrollablePattern::OnScrollCallback(float offset, int32_t source)
67 {
68 if (source == SCROLL_FROM_START) {
69 return true;
70 }
71 if (scrollBar_ && scrollBar_->IsDriving()) {
72 offset = scrollBar_->CalcPatternOffset(offset);
73 source = SCROLL_FROM_BAR;
74 }
75 return UpdateCurrentOffset(offset, source);
76 }
77
OnScrollPosition(double offset,int32_t source)78 bool ScrollablePattern::OnScrollPosition(double offset, int32_t source)
79 {
80 if (coordinationEvent_ && isReactInParentMovement_) {
81 auto onScroll = coordinationEvent_->GetOnScroll();
82 if (onScroll) {
83 onScroll(offset);
84 return false;
85 }
86 }
87 auto isAtTop = (IsAtTop() && Positive(offset));
88 if (isAtTop && source == SCROLL_FROM_UPDATE && !isReactInParentMovement_ && (axis_ == Axis::VERTICAL)) {
89 isReactInParentMovement_ = true;
90 if (coordinationEvent_) {
91 auto onScrollStart = coordinationEvent_->GetOnScrollStartEvent();
92 if (onScrollStart) {
93 onScrollStart();
94 }
95 }
96 }
97 if (source == SCROLL_FROM_START) {
98 if (scrollBarProxy_) {
99 scrollBarProxy_->StopScrollBarAnimator();
100 }
101 }
102 return true;
103 }
104
OnScrollEnd()105 void ScrollablePattern::OnScrollEnd()
106 {
107 if (coordinationEvent_ && isReactInParentMovement_) {
108 isReactInParentMovement_ = false;
109 auto onScrollEnd = coordinationEvent_->GetOnScrollEndEvent();
110 if (onScrollEnd) {
111 onScrollEnd();
112 return;
113 }
114 }
115 if (scrollBar_) {
116 scrollBar_->SetDriving(false);
117 scrollBar_->OnScrollEnd();
118 }
119 if (scrollBarProxy_) {
120 scrollBarProxy_->StartScrollBarAnimator();
121 }
122 }
123
AddScrollEvent()124 void ScrollablePattern::AddScrollEvent()
125 {
126 auto gestureHub = GetGestureHub();
127 CHECK_NULL_VOID(gestureHub);
128 if (scrollableEvent_) {
129 gestureHub->RemoveScrollableEvent(scrollableEvent_);
130 }
131 scrollableEvent_ = MakeRefPtr<ScrollableEvent>(GetAxis());
132 auto scrollCallback = [weak = WeakClaim(this)](double offset, int32_t source) {
133 auto pattern = weak.Upgrade();
134 CHECK_NULL_RETURN(pattern, false);
135 if (!pattern->OnScrollPosition(offset, source)) {
136 return false;
137 }
138 return pattern->OnScrollCallback(static_cast<float>(offset), source);
139 };
140 scrollableEvent_->SetScrollPositionCallback(std::move(scrollCallback));
141 auto scrollEnd = [weak = WeakClaim(this)]() {
142 auto pattern = weak.Upgrade();
143 CHECK_NULL_VOID(pattern);
144 pattern->OnScrollEnd();
145 pattern->OnScrollEndCallback();
146 };
147 scrollableEvent_->SetScrollEndCallback(std::move(scrollEnd));
148 gestureHub->AddScrollableEvent(scrollableEvent_);
149 }
150
SetEdgeEffect(EdgeEffect edgeEffect)151 void ScrollablePattern::SetEdgeEffect(EdgeEffect edgeEffect)
152 {
153 auto gestureHub = GetGestureHub();
154 CHECK_NULL_VOID(gestureHub);
155 if (scrollEffect_ && (edgeEffect != scrollEffect_->GetEdgeEffect())) {
156 gestureHub->RemoveScrollEdgeEffect(scrollEffect_);
157 scrollEffect_.Reset();
158 }
159 if (edgeEffect == EdgeEffect::SPRING && !scrollEffect_) {
160 auto springEffect = AceType::MakeRefPtr<ScrollSpringEffect>();
161 CHECK_NULL_VOID(springEffect);
162 springEffect->SetOutBoundaryCallback([weak = AceType::WeakClaim(this)]() {
163 auto pattern = weak.Upgrade();
164 CHECK_NULL_RETURN_NOLOG(pattern, false);
165 return pattern->IsAtTop() || pattern->IsAtBottom();
166 });
167 // add callback to springEdgeEffect
168 SetEdgeEffectCallback(springEffect);
169 scrollEffect_ = springEffect;
170 gestureHub->AddScrollEdgeEffect(GetAxis(), scrollEffect_);
171 }
172 if (edgeEffect == EdgeEffect::FADE && !scrollEffect_) {
173 auto fadeEdgeEffect = AceType::MakeRefPtr<ScrollFadeEffect>(Color::GRAY);
174 CHECK_NULL_VOID(fadeEdgeEffect);
175 fadeEdgeEffect->SetHandleOverScrollCallback([weakScroll = AceType::WeakClaim(this)]() -> void {
176 auto list = weakScroll.Upgrade();
177 CHECK_NULL_VOID_NOLOG(list);
178 auto host = list->GetHost();
179 CHECK_NULL_VOID_NOLOG(host);
180 host->MarkDirtyNode(PROPERTY_UPDATE_RENDER);
181 });
182 SetEdgeEffectCallback(fadeEdgeEffect);
183 fadeEdgeEffect->InitialEdgeEffect();
184 scrollEffect_ = fadeEdgeEffect;
185 gestureHub->AddScrollEdgeEffect(GetAxis(), scrollEffect_);
186 }
187 }
188
HandleEdgeEffect(float offset,int32_t source,const SizeF & size)189 bool ScrollablePattern::HandleEdgeEffect(float offset, int32_t source, const SizeF& size)
190 {
191 // check edgeEffect is not springEffect
192 if (scrollEffect_ && scrollEffect_->IsFadeEffect() && source != SCROLL_FROM_BAR) { // handle edge effect
193 if ((IsAtTop() && Positive(offset)) || (IsAtBottom() && Negative(offset))) {
194 scrollEffect_->HandleOverScroll(GetAxis(), -offset, size);
195 }
196 }
197 if (!(scrollEffect_ && scrollEffect_->IsSpringEffect()) ||
198 source == SCROLL_FROM_BAR || source == SCROLL_FROM_JUMP) {
199 if (IsAtTop() && Positive(offset)) {
200 return false;
201 }
202 if (IsAtBottom() && Negative(offset)) {
203 return false;
204 }
205 }
206 return true;
207 }
208
RegisterScrollBarEventTask()209 void ScrollablePattern::RegisterScrollBarEventTask()
210 {
211 CHECK_NULL_VOID(scrollBar_);
212 auto host = GetHost();
213 CHECK_NULL_VOID(host);
214 auto gestureHub = GetGestureHub();
215 auto inputHub = GetInputHub();
216 CHECK_NULL_VOID(gestureHub);
217 CHECK_NULL_VOID(inputHub);
218 scrollBar_->SetGestureEvent();
219 scrollBar_->SetMouseEvent();
220 scrollBar_->SetMarkNeedRenderFunc([weak = AceType::WeakClaim(AceType::RawPtr(host))]() {
221 auto host = weak.Upgrade();
222 CHECK_NULL_VOID(host);
223 host->MarkNeedRenderOnly();
224 });
225 gestureHub->AddTouchEvent(scrollBar_->GetTouchEvent());
226 inputHub->AddOnMouseEvent(scrollBar_->GetMouseEvent());
227 }
228
SetScrollBar(DisplayMode displayMode)229 void ScrollablePattern::SetScrollBar(DisplayMode displayMode)
230 {
231 if (displayMode == DisplayMode::OFF) {
232 if (scrollBar_) {
233 auto gestureHub = GetGestureHub();
234 if (gestureHub) {
235 gestureHub->RemoveTouchEvent(scrollBar_->GetTouchEvent());
236 }
237 scrollBar_->MarkNeedRender();
238 scrollBar_.Reset();
239 }
240 } else if (!scrollBar_) {
241 scrollBar_ = AceType::MakeRefPtr<ScrollBar>(displayMode);
242 // set the scroll bar style
243 if (GetAxis() == Axis::HORIZONTAL) {
244 scrollBar_->SetPositionMode(PositionMode::BOTTOM);
245 }
246 RegisterScrollBarEventTask();
247 if (displayMode == DisplayMode::AUTO) {
248 scrollBar_->OnScrollEnd();
249 }
250 } else if (scrollBar_->GetDisplayMode() != displayMode) {
251 scrollBar_->SetDisplayMode(displayMode);
252 } else {
253 return;
254 }
255 if (scrollBar_) {
256 UpdateScrollBarOffset();
257 }
258 }
259
SetScrollBar(const std::unique_ptr<ScrollBarProperty> & property)260 void ScrollablePattern::SetScrollBar(const std::unique_ptr<ScrollBarProperty>& property)
261 {
262 if (!property) {
263 SetScrollBar(DisplayMode::AUTO);
264 return;
265 }
266 auto displayMode = property->GetScrollBarMode().value_or(DisplayMode::AUTO);
267 SetScrollBar(displayMode);
268 if (scrollBar_) {
269 auto barColor = property->GetScrollBarColor();
270 if (barColor) {
271 scrollBar_->SetForegroundColor(barColor.value());
272 }
273 auto barWidth = property->GetScrollBarWidth();
274 if (barWidth) {
275 scrollBar_->SetInactiveWidth(barWidth.value());
276 scrollBar_->SetNormalWidth(barWidth.value());
277 scrollBar_->SetActiveWidth(barWidth.value());
278 scrollBar_->SetTouchWidth(barWidth.value());
279 }
280 }
281 }
282
UpdateScrollBarRegion(float offset,float estimatedHeight,Size viewPort)283 void ScrollablePattern::UpdateScrollBarRegion(float offset, float estimatedHeight, Size viewPort)
284 {
285 // inner scrollbar
286 if (scrollBar_) {
287 auto host = GetHost();
288 CHECK_NULL_VOID(host);
289 auto layoutPriority = host->GetLayoutProperty();
290 CHECK_NULL_VOID(layoutPriority);
291 auto paddingOffset = layoutPriority->CreatePaddingAndBorder().Offset();
292 auto mainSize = axis_ == Axis::VERTICAL ? viewPort.Height() : viewPort.Width();
293 bool scrollable = GreatNotEqual(estimatedHeight, mainSize);
294 scrollBar_->SetScrollable(IsScrollable() && scrollable);
295 Offset scrollOffset = { offset, offset }; // fit for w/h switched.
296 Offset viewOffset = { paddingOffset.GetX(), paddingOffset.GetY() };
297 scrollBar_->UpdateScrollBarRegion(viewOffset, viewPort, scrollOffset, estimatedHeight);
298 scrollBar_->MarkNeedRender();
299 }
300
301 // outer scrollbar
302 if (scrollBarProxy_) {
303 estimatedHeight_ = estimatedHeight - (GetAxis() == Axis::VERTICAL ? viewPort.Height() : viewPort.Width());
304 barOffset_ = -offset;
305 scrollBarProxy_->NotifyScrollBar(AceType::WeakClaim(this));
306 }
307 }
308
SetScrollBarProxy(const RefPtr<ScrollBarProxy> & scrollBarProxy)309 void ScrollablePattern::SetScrollBarProxy(const RefPtr<ScrollBarProxy>& scrollBarProxy)
310 {
311 CHECK_NULL_VOID(scrollBarProxy);
312 auto scrollFunction = [weak = WeakClaim(this)](double offset, int32_t source) {
313 if (source != SCROLL_FROM_START) {
314 auto pattern = weak.Upgrade();
315 if (!pattern || pattern->GetAxis() == Axis::NONE) {
316 return false;
317 }
318 return pattern->UpdateCurrentOffset(offset, SCROLL_FROM_BAR);
319 }
320 return true;
321 };
322 ScrollableNodeInfo nodeInfo = { AceType::WeakClaim(this), std::move(scrollFunction) };
323 scrollBarProxy->RegisterScrollableNode(nodeInfo);
324 scrollBarProxy_ = scrollBarProxy;
325 }
326 } // namespace OHOS::Ace::NG