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