1 /*
2 * Copyright (c) 2021 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/scroll/scroll_bar_controller.h"
17
18 #include "core/common/vibrator/vibrator_proxy.h"
19 #include "core/components/scroll/render_scroll.h"
20
21 namespace OHOS::Ace {
22 namespace {
23
24 constexpr int32_t STOP_DURATION = 2000; // 2000ms
25 constexpr double FRICTION = 1.0;
26 constexpr double MIN_VELOCITY = -10000.0;
27 constexpr double MAX_VELOCITY = 10000.0;
28
29 constexpr double KEYTIME_START = 0.0;
30 constexpr double KEYTIME_MIDDLE = 0.7;
31 constexpr double KEYTIME_END = 1.0;
32 constexpr int32_t VIBRATE_DURATION = 100; // 100ms
33 constexpr int32_t BAR_EXPAND_DURATION = 150; // 150ms, scroll bar width expands from 4dp to 8dp
34 constexpr int32_t BAR_SHRINK_DURATION = 250; // 250ms, scroll bar width shrinks from 4dp to 8dp
35
36 } // namespace
37
Initialize(const WeakPtr<PipelineContext> & context,bool isVertical)38 void ScrollBarController::Initialize(const WeakPtr<PipelineContext>& context, bool isVertical)
39 {
40 context_ = context;
41 isVertical_ = isVertical;
42
43 PanDirection panDirection;
44 panDirection.type = isVertical ? PanDirection::VERTICAL : PanDirection::HORIZONTAL;
45 panRecognizer_ = AceType::MakeRefPtr<PanRecognizer>(
46 context, DEFAULT_PAN_FINGER, panDirection, DEFAULT_PAN_DISTANCE.ConvertToPx());
47 panRecognizer_->SetOnActionUpdate([weakBar = AceType::WeakClaim(this)](const GestureEvent& info) {
48 auto scrollBar = weakBar.Upgrade();
49 if (scrollBar) {
50 scrollBar->HandleDragUpdate(info);
51 }
52 });
53 panRecognizer_->SetOnActionEnd([weakBar = AceType::WeakClaim(this)](const GestureEvent& info) {
54 auto scrollBar = weakBar.Upgrade();
55 if (scrollBar) {
56 scrollBar->HandleDragEnd(info);
57 }
58 });
59
60 // use RawRecognizer to receive next touch down event to stop animation.
61 rawRecognizer_ = AceType::MakeRefPtr<RawRecognizer>();
62 rawRecognizer_->SetOnTouchDown([weakBar = AceType::WeakClaim(this)](const TouchEventInfo&) {
63 auto scrollBar = weakBar.Upgrade();
64 if (scrollBar) {
65 scrollBar->HandleTouchDown();
66 }
67 });
68 rawRecognizer_->SetOnTouchUp([weakBar = AceType::WeakClaim(this)](const TouchEventInfo&) {
69 auto scrollBar = weakBar.Upgrade();
70 if (scrollBar) {
71 scrollBar->HandleTouchUp();
72 }
73 });
74
75 touchAnimator_ = CREATE_ANIMATOR(context);
76 dragEndAnimator_ = CREATE_ANIMATOR(context);
77 InitBarEndAnimation(context);
78
79 // when touching down, it need vibrate, last 100ms
80 auto vibratorContext = context.Upgrade();
81 if (!vibrator_ && vibratorContext) {
82 vibrator_ = VibratorProxy::GetInstance().GetVibrator(vibratorContext->GetTaskExecutor());
83 }
84 }
85
PlayGrowAnimation()86 void ScrollBarController::PlayGrowAnimation()
87 {
88 if (!touchDownCallback_) {
89 return;
90 }
91 if (!touchAnimator_->IsStopped()) {
92 touchAnimator_->Stop();
93 }
94 touchAnimator_->ClearInterpolators();
95 auto activeWidth = activeWidth_.Value();
96 auto inactiveWidth = inactiveWidth_.Value();
97 auto pipeline = context_.Upgrade();
98 if (pipeline) {
99 activeWidth = pipeline->NormalizeToPx(activeWidth_);
100 inactiveWidth = pipeline->NormalizeToPx(inactiveWidth_);
101 }
102
103 auto animation = AceType::MakeRefPtr<CurveAnimation<double>>(inactiveWidth, activeWidth, Curves::SHARP);
104 animation->AddListener([weakBar = AceType::WeakClaim(this)](double value) {
105 auto scrollBar = weakBar.Upgrade();
106 if (scrollBar && scrollBar->touchDownCallback_) {
107 scrollBar->touchDownCallback_(value);
108 scrollBar->MarkScrollRender();
109 }
110 });
111 touchAnimator_->AddInterpolator(animation);
112 touchAnimator_->SetDuration(BAR_EXPAND_DURATION);
113 touchAnimator_->Play();
114 if (barEndCallback_) {
115 barEndCallback_(UINT8_MAX);
116 }
117 }
118
PlayShrinkAnimation()119 void ScrollBarController::PlayShrinkAnimation()
120 {
121 if (!touchUpCallback_) {
122 return;
123 }
124 if (!touchAnimator_->IsStopped()) {
125 touchAnimator_->Stop();
126 }
127 touchAnimator_->ClearInterpolators();
128 auto activeWidth = activeWidth_.Value();
129 auto inactiveWidth = inactiveWidth_.Value();
130 auto pipeline = context_.Upgrade();
131 if (pipeline) {
132 activeWidth = pipeline->NormalizeToPx(activeWidth_);
133 inactiveWidth = pipeline->NormalizeToPx(inactiveWidth_);
134 }
135
136 auto animation = AceType::MakeRefPtr<CurveAnimation<double>>(activeWidth, inactiveWidth, Curves::SHARP);
137 animation->AddListener([weakBar = AceType::WeakClaim(this)](double value) {
138 auto scrollBar = weakBar.Upgrade();
139 if (scrollBar && scrollBar->touchUpCallback_) {
140 scrollBar->touchUpCallback_(value);
141 }
142 });
143 touchAnimator_->AddInterpolator(animation);
144 touchAnimator_->SetDuration(BAR_SHRINK_DURATION);
145 touchAnimator_->Play();
146 }
147
HandleTouchDown()148 void ScrollBarController::HandleTouchDown()
149 {
150 LOGI("handle touch down");
151 isPressed_ = true;
152 if (CheckScroll()) {
153 isActive_ = true;
154 if (vibrator_) {
155 vibrator_->Vibrate(VIBRATE_DURATION);
156 }
157 if (!isHover_) {
158 PlayGrowAnimation();
159 }
160 }
161 }
162
MarkScrollRender()163 void ScrollBarController::MarkScrollRender()
164 {
165 auto scroll = AceType::DynamicCast<RenderScroll>(scroll_.Upgrade());
166 if (scroll) {
167 scroll->MarkNeedRender();
168 }
169 }
170
HandleTouchUp()171 void ScrollBarController::HandleTouchUp()
172 {
173 LOGI("handle touch up");
174 isPressed_ = false;
175 if (!isInBar_) {
176 isHover_ = false;
177 PlayShrinkAnimation();
178 }
179 if (isActive_) {
180 HandleScrollBarEnd();
181 }
182 isActive_ = false;
183 }
184
HandleDragUpdate(const GestureEvent & info)185 void ScrollBarController::HandleDragUpdate(const GestureEvent& info)
186 {
187 if (info.GetInputEventType() == InputEventType::AXIS) {
188 UpdateScrollPosition(info.GetMainDelta(), SCROLL_FROM_AXIS);
189 } else {
190 UpdateScrollPosition(-info.GetMainDelta(), SCROLL_FROM_BAR);
191 }
192 }
193
HandleDragEnd(const GestureEvent & info)194 void ScrollBarController::HandleDragEnd(const GestureEvent& info)
195 {
196 LOGI("handle drag end, position is %{public}lf and %{public}lf, velocity is %{public}lf",
197 info.GetGlobalLocation().GetX(), info.GetGlobalLocation().GetY(), info.GetMainVelocity());
198 if (scrollEndCallback_) {
199 scrollEndCallback_();
200 }
201 double mainPosition = info.GetGlobalLocation().GetY();
202 double correctVelocity = std::clamp(info.GetMainVelocity(), MIN_VELOCITY, MAX_VELOCITY);
203 if (dragEndMotion_) {
204 dragEndMotion_->Reset(FRICTION, mainPosition, correctVelocity);
205 } else {
206 dragEndMotion_ = AceType::MakeRefPtr<FrictionMotion>(FRICTION, mainPosition, correctVelocity);
207 dragEndMotion_->AddListener([weakScroll = AceType::WeakClaim(this)](double value) {
208 auto scrollBarController = weakScroll.Upgrade();
209 if (scrollBarController) {
210 scrollBarController->ProcessScrollMotion(value);
211 }
212 });
213 }
214
215 currentPos_ = mainPosition;
216 dragEndAnimator_->ClearStopListeners();
217 dragEndAnimator_->AddStopListener([weakScroll = AceType::WeakClaim(this)]() {
218 auto scrollBarController = weakScroll.Upgrade();
219 if (scrollBarController) {
220 scrollBarController->SetActive(false);
221 }
222 });
223 dragEndAnimator_->PlayMotion(dragEndMotion_);
224 }
225
ProcessScrollMotion(double position)226 void ScrollBarController::ProcessScrollMotion(double position)
227 {
228 if ((NearEqual(currentPos_, position))) {
229 UpdateScrollPosition(0.0, SCROLL_FROM_ANIMATION);
230 } else {
231 // UpdateScrollPosition return false, means reach to scroll limit.
232 if (!UpdateScrollPosition(currentPos_ - position, SCROLL_FROM_BAR)) {
233 auto scroll = AceType::DynamicCast<RenderScroll>(scroll_.Upgrade());
234 if (scroll) {
235 scroll->HandleScrollOverByBar(dragEndMotion_->GetCurrentVelocity());
236 }
237 dragEndAnimator_->Stop();
238 }
239 }
240 currentPos_ = position;
241 }
242
UpdateScrollPosition(const double offset,int32_t source)243 bool ScrollBarController::UpdateScrollPosition(const double offset, int32_t source)
244 {
245 bool ret = true;
246 if (callback_) {
247 auto scroll = AceType::DynamicCast<RenderScroll>(scroll_.Upgrade());
248 if (scroll && !NearZero(scroll->GetEstimatedHeight())) {
249 double mainSize = isVertical_ ? scroll->GetLayoutSize().Height() : scroll->GetLayoutSize().Width();
250 double estimatedHeight = scroll->GetEstimatedHeight();
251 double activeHeight = mainSize * mainSize / estimatedHeight;
252 if (!NearEqual(mainSize, activeHeight)) {
253 double value = offset * (estimatedHeight - mainSize) / (mainSize - activeHeight);
254 ret = source == SCROLL_FROM_AXIS ? callback_(offset, source) : callback_(value, source);
255 }
256 }
257 }
258 return ret;
259 }
260
HandleScrollBarEnd()261 void ScrollBarController::HandleScrollBarEnd()
262 {
263 isActive_ = false;
264 if (!scrollEndAnimator_) {
265 LOGE("scrollEndAnimator_ is not exist.");
266 return;
267 }
268 if (!scrollEndAnimator_->IsStopped()) {
269 scrollEndAnimator_->Stop();
270 }
271 scrollEndAnimator_->Play();
272 }
273
InitBarEndAnimation(const WeakPtr<PipelineContext> & context)274 void ScrollBarController::InitBarEndAnimation(const WeakPtr<PipelineContext>& context)
275 {
276 if (scrollEndAnimator_ && !scrollEndAnimator_->IsStopped()) {
277 scrollEndAnimator_->Stop();
278 }
279 if (scrollEndAnimator_) {
280 scrollEndAnimator_->Play();
281 return;
282 }
283
284 scrollEndAnimator_ = CREATE_ANIMATOR(context);
285 auto hiddenStartKeyframe = AceType::MakeRefPtr<Keyframe<int32_t>>(KEYTIME_START, UINT8_MAX);
286 auto hiddenMiddleKeyframe = AceType::MakeRefPtr<Keyframe<int32_t>>(KEYTIME_MIDDLE, UINT8_MAX);
287 auto hiddenEndKeyframe = AceType::MakeRefPtr<Keyframe<int32_t>>(KEYTIME_END, 0);
288 hiddenMiddleKeyframe->SetCurve(Curves::LINEAR);
289 hiddenEndKeyframe->SetCurve(Curves::FRICTION);
290
291 auto animation = AceType::MakeRefPtr<KeyframeAnimation<int32_t>>();
292 animation->AddKeyframe(hiddenStartKeyframe);
293 animation->AddKeyframe(hiddenMiddleKeyframe);
294 animation->AddKeyframe(hiddenEndKeyframe);
295 animation->AddListener([weakBar = AceType::WeakClaim(this)](int32_t value) {
296 auto scrollBar = weakBar.Upgrade();
297 if (scrollBar && scrollBar->barEndCallback_) {
298 scrollBar->barEndCallback_(value);
299 }
300 });
301 scrollEndAnimator_->AddInterpolator(animation);
302 scrollEndAnimator_->SetDuration(STOP_DURATION);
303 scrollEndAnimator_->Play();
304 }
305
Reset()306 void ScrollBarController::Reset()
307 {
308 if (scrollEndAnimator_ && !scrollEndAnimator_->IsStopped()) {
309 scrollEndAnimator_->Stop();
310 }
311 }
312
CheckScroll()313 bool ScrollBarController::CheckScroll()
314 {
315 auto scroll = AceType::DynamicCast<RenderScroll>(scroll_.Upgrade());
316 return scroll != nullptr;
317 }
318
SetIsHover(bool isInBarRegion)319 void ScrollBarController::SetIsHover(bool isInBarRegion)
320 {
321 if (isInBar_ == isInBarRegion) {
322 return;
323 }
324 isInBar_ = isInBarRegion;
325 if (isPressed_) {
326 return;
327 }
328 isHover_ = isInBar_;
329 if (isHover_) {
330 isActive_ = true;
331 PlayGrowAnimation();
332 } else {
333 PlayShrinkAnimation();
334 if (isActive_) {
335 HandleScrollBarEnd();
336 }
337 isActive_ = false;
338 }
339 }
340
OnFlushTouchEventsBegin()341 void ScrollBarController::OnFlushTouchEventsBegin()
342 {
343 if (panRecognizer_) {
344 panRecognizer_->OnFlushTouchEventsBegin();
345 }
346 }
347
OnFlushTouchEventsEnd()348 void ScrollBarController::OnFlushTouchEventsEnd()
349 {
350 if (panRecognizer_) {
351 panRecognizer_->OnFlushTouchEventsEnd();
352 }
353 }
354
355 } // namespace OHOS::Ace
356