1 /*
2  * Copyright (c) 2020-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 "components/ui_abstract_scroll.h"
17 
18 #include "securec.h"
19 
20 #include "animator/interpolation.h"
21 #include "common/screen.h"
22 #include "components/ui_abstract_scroll_bar.h"
23 #include "components/ui_arc_scroll_bar.h"
24 #include "components/ui_box_scroll_bar.h"
25 #if DEFAULT_ANIMATION
26 #include "graphic_timer.h"
27 #endif
28 
29 namespace OHOS {
30 #if DEFAULT_ANIMATION
31 class BarEaseInOutAnimator final : public AnimatorCallback {
32 public:
33     BarEaseInOutAnimator() = delete;
34     BarEaseInOutAnimator(const BarEaseInOutAnimator&) = delete;
35     BarEaseInOutAnimator& operator=(const BarEaseInOutAnimator&) = delete;
36     BarEaseInOutAnimator(BarEaseInOutAnimator&&) = delete;
37     BarEaseInOutAnimator& operator=(BarEaseInOutAnimator&&) = delete;
38 
BarEaseInOutAnimator(UIAbstractScroll & scrollView)39     explicit BarEaseInOutAnimator(UIAbstractScroll& scrollView)
40         : scrollView_(scrollView),
41           timer_(APPEAR_PERIOD, TimerCb, this),
42           animator_(this, nullptr, ANIMATOR_DURATION, false)
43     {
44     }
45 
~BarEaseInOutAnimator()46     ~BarEaseInOutAnimator()
47     {
48         timer_.Stop();
49         animator_.Stop();
50     }
51 
RefreshBar()52     void RefreshBar()
53     {
54         if (animator_.GetState() == Animator::START) {
55             if (!isEaseIn_) {
56                 animator_.SetRunTime(ANIMATOR_DURATION - animator_.GetRunTime());
57             }
58         } else if (scrollView_.yScrollBar_->GetOpacity() == OPA_TRANSPARENT) {
59             animator_.Start();
60         } else {
61             timer_.Start(); // updates the start time of timer, ensuring that timer is triggered two seconds after the
62                             // last operation
63         }
64         isEaseIn_ = true;
65     }
66 
67 private:
Callback(UIView * view)68     void Callback(UIView* view) override
69     {
70         uint8_t opa = OPA_OPAQUE * animator_.GetRunTime() / ANIMATOR_DURATION;
71         if (!isEaseIn_) {
72             opa = OPA_OPAQUE - opa;
73         }
74         float bezielY = opa;
75         bezielY =
76             Interpolation::GetBezierY(bezielY / OPA_OPAQUE, BEZIER_CONTROL_POINT_X_1, 0, BEZIER_CONTROL_POINT_X_2, 1);
77         opa = static_cast<uint8_t>(bezielY * opa);
78         if (scrollView_.yScrollBarVisible_) {
79             scrollView_.yScrollBar_->SetOpacity(opa);
80         }
81         if (scrollView_.xScrollBarVisible_) {
82             scrollView_.xScrollBar_->SetOpacity(opa);
83         }
84         scrollView_.Invalidate();
85     }
86 
OnStop(UIView & view)87     void OnStop(UIView& view) override
88     {
89         if (isEaseIn_) {
90             if (scrollView_.yScrollBarVisible_) {
91                 scrollView_.yScrollBar_->SetOpacity(OPA_OPAQUE);
92             }
93             if (Screen::GetInstance().GetScreenShape() == ScreenShape::RECTANGLE && scrollView_.xScrollBarVisible_) {
94                 scrollView_.xScrollBar_->SetOpacity(OPA_OPAQUE);
95             }
96             timer_.Start(); // The timer is triggered when animation stops.
97         } else {
98             if (scrollView_.yScrollBarVisible_) {
99                 scrollView_.yScrollBar_->SetOpacity(OPA_TRANSPARENT);
100             }
101             if (scrollView_.xScrollBarVisible_) {
102                 scrollView_.xScrollBar_->SetOpacity(OPA_TRANSPARENT);
103             }
104         }
105         scrollView_.Invalidate();
106     }
107 
TimerCb(void * arg)108     static void TimerCb(void* arg)
109     {
110         BarEaseInOutAnimator* barAnimator = reinterpret_cast<BarEaseInOutAnimator*>(arg);
111         barAnimator->isEaseIn_ = false;
112         barAnimator->animator_.Start();
113     }
114     static constexpr uint16_t ANIMATOR_DURATION = 250;
115     static constexpr uint16_t APPEAR_PERIOD = 2000;
116     static constexpr float BEZIER_CONTROL_POINT_X_1 = 0.33f;
117     static constexpr float BEZIER_CONTROL_POINT_X_2 = 0.67f;
118     UIAbstractScroll& scrollView_;
119     GraphicTimer timer_;
120     Animator animator_;
121     bool isEaseIn_ = true;
122 };
123 #endif
124 
UIAbstractScroll()125 UIAbstractScroll::UIAbstractScroll()
126     : direction_(VERTICAL),
127       deltaIndex_(0),
128       rotateIndex_(0),
129       reserve_(0),
130       easingFunc_(EasingEquation::CubicEaseOut),
131       scrollAnimator_(&animatorCallback_, this, 0, true),
132       scrollBarSide_(SCROLL_BAR_RIGHT_SIDE),
133       scrollBarCenter_({0, 0}),
134       scrollBarCenterSetFlag_(false)
135 {
136 #if defined(ENABLE_FOCUS_MANAGER) && ENABLE_FOCUS_MANAGER
137     focusable_ = true;
138 #endif
139 #if defined(ENABLE_ROTATE_INPUT) && ENABLE_ROTATE_INPUT
140     rotateFactor_ = DEFAULT_SCROLL_VIEW_ROTATE_FACTOR;
141     rotateThrowthreshold_ = ABSTRACT_ROTATE_THROW_THRESHOLD;
142     rotateAccCoefficient_ = ABSTRACT_ROTATE_DISTANCE_COEFF;
143     isRotating_ = false;
144 #endif
145     isViewGroup_ = true;
146     touchable_ = true;
147     draggable_ = true;
148     dragParentInstead_ = false;
149 }
150 
~UIAbstractScroll()151 UIAbstractScroll::~UIAbstractScroll()
152 {
153 #if defined(DEFAULT_ANIMATION) && DEFAULT_ANIMATION
154     if (barEaseInOutAnimator_ != nullptr) {
155         delete barEaseInOutAnimator_;
156         barEaseInOutAnimator_ = nullptr;
157     }
158 #endif
159     if (xScrollBar_ != nullptr) {
160         delete xScrollBar_;
161         xScrollBar_ = nullptr;
162     }
163     if (yScrollBar_ != nullptr) {
164         delete yScrollBar_;
165         yScrollBar_ = nullptr;
166     }
167 }
168 
MoveChildByOffset(int16_t offsetX,int16_t offsetY)169 void UIAbstractScroll::MoveChildByOffset(int16_t offsetX, int16_t offsetY)
170 {
171     if ((offsetX == 0) && (offsetY == 0)) {
172         return;
173     }
174     UIView* view = GetChildrenHead();
175     while (view != nullptr) {
176         int16_t x = view->GetX() + offsetX;
177         int16_t y = view->GetY() + offsetY;
178         view->SetPosition(x, y);
179         view = view->GetNextSibling();
180     }
181     Invalidate();
182 }
183 
GetMaxDelta() const184 int16_t UIAbstractScroll::GetMaxDelta() const
185 {
186     int16_t result = 0;
187     for (int16_t i = 0; i < MAX_DELTA_SIZE; i++) {
188         if (result < MATH_ABS(lastDelta_[i])) {
189             result = MATH_ABS(lastDelta_[i]);
190         }
191     }
192     return result;
193 }
194 
GetMaxRotate() const195 int16_t UIAbstractScroll::GetMaxRotate() const
196 {
197     int16_t result = 0;
198     for (int16_t i = 0; i < MAX_DELTA_SIZE; i++) {
199         if (MATH_ABS(result) < MATH_ABS(lastRotate_[i])) {
200             result = lastRotate_[i];
201         }
202     }
203     return result;
204 }
205 
InitDelta()206 void UIAbstractScroll::InitDelta()
207 {
208     if (memset_s(lastDelta_, sizeof(lastDelta_), 0, sizeof(lastDelta_)) != EOK) {
209         GRAPHIC_LOGE("memset_s error");
210     }
211 }
212 
InitRotate()213 void UIAbstractScroll::InitRotate()
214 {
215     if (memset_s(lastRotate_, sizeof(lastRotate_), 0, sizeof(lastRotate_)) != EOK) {
216         GRAPHIC_LOGE("memset_s error");
217     }
218 }
219 
StopAnimator()220 void UIAbstractScroll::StopAnimator()
221 {
222     scrollAnimator_.Stop();
223     animatorCallback_.ResetCallback();
224     isDragging_ = false;
225 }
226 
DragThrowAnimator(Point currentPos,Point lastPos,uint8_t dragDirection,bool dragBack)227 bool UIAbstractScroll::DragThrowAnimator(Point currentPos, Point lastPos, uint8_t dragDirection, bool dragBack)
228 {
229     if (!throwDrag_ && (reboundSize_ == 0)) {
230         return false;
231     }
232     int16_t dragDistanceX = 0;
233     int16_t dragDistanceY = 0;
234     if (throwDrag_) {
235         CalculateDragDistance(currentPos, lastPos, dragDirection, dragDistanceX, dragDistanceY);
236     }
237     if (reboundSize_ != 0) {
238         CalculateReboundDistance(dragDistanceX, dragDistanceY);
239     }
240 
241     if (!dragBack) {
242         FixDistance(dragDistanceX, dragDistanceY);
243     }
244 
245     StartAnimator(dragDistanceX, dragDistanceY);
246     return true;
247 }
248 
StartAnimator(int16_t dragDistanceX,int16_t dragDistanceY)249 void UIAbstractScroll::StartAnimator(int16_t dragDistanceX, int16_t dragDistanceY)
250 {
251     int16_t dragTimes = MATH_MAX(MATH_ABS(dragDistanceX), MATH_ABS(dragDistanceY)) / DRAG_TIMES_COEFFICIENT;
252     if (dragTimes < MIN_DRAG_TIMES) {
253         dragTimes = MIN_DRAG_TIMES;
254     }
255     animatorCallback_.ResetCallback();
256     animatorCallback_.SetDragStartValue(0, 0);
257     animatorCallback_.SetDragEndValue(dragDistanceX, dragDistanceY);
258     animatorCallback_.SetDragTimes(dragTimes * DRAG_ACC_FACTOR / GetDragACCLevel());
259     scrollAnimator_.Start();
260 }
261 
CalculateDragDistance(Point currentPos,Point lastPos,uint8_t dragDirection,int16_t & dragDistanceX,int16_t & dragDistanceY)262 void UIAbstractScroll::CalculateDragDistance(Point currentPos,
263                                              Point lastPos,
264                                              uint8_t dragDirection,
265                                              int16_t& dragDistanceX,
266                                              int16_t& dragDistanceY)
267 {
268     if ((direction_ == VERTICAL) || (direction_ == HORIZONTAL_AND_VERTICAL)) {
269         dragDistanceY = currentPos.y - lastPos.y;
270         if (isRotating_) {
271             dragDistanceY *= rotateAccCoefficient_;
272         } else {
273             dragDistanceY *= DRAG_DISTANCE_COEFFICIENT;
274             if (dragDistanceY > 0 || (dragDistanceY == 0 && dragDirection == DragEvent::DIRECTION_TOP_TO_BOTTOM)) {
275                 dragDistanceY += GetMaxDelta() * GetSwipeACCLevel() / DRAG_ACC_FACTOR;
276             } else if (dragDistanceY < 0 ||
277                        (dragDistanceY == 0 && dragDirection == DragEvent::DIRECTION_BOTTOM_TO_TOP)) {
278                 dragDistanceY -= GetMaxDelta() * GetSwipeACCLevel() / DRAG_ACC_FACTOR;
279             }
280         }
281     }
282 
283     if ((direction_ == HORIZONTAL) || (direction_ == HORIZONTAL_AND_VERTICAL)) {
284         dragDistanceX = currentPos.x - lastPos.x;
285         if (isRotating_) {
286             dragDistanceX *= rotateAccCoefficient_;
287         } else {
288             dragDistanceX *= DRAG_DISTANCE_COEFFICIENT;
289             if (dragDistanceX > 0 || (dragDistanceX == 0 && dragDirection == DragEvent::DIRECTION_LEFT_TO_RIGHT)) {
290                 dragDistanceX += GetMaxDelta() * GetSwipeACCLevel() / DRAG_ACC_FACTOR;
291             } else if (dragDistanceX < 0 ||
292                        (dragDistanceX == 0 && dragDirection == DragEvent::DIRECTION_RIGHT_TO_LEFT)) {
293                 dragDistanceX -= GetMaxDelta() * GetSwipeACCLevel() / DRAG_ACC_FACTOR;
294             }
295         }
296     }
297 
298     if (maxScrollDistance_ != 0) {
299         if (MATH_ABS(dragDistanceY) > maxScrollDistance_) {
300             int16_t calculatedValue = (dragDistanceY > 0) ? 1 : -1;
301             dragDistanceY = calculatedValue * maxScrollDistance_;
302         }
303         if (MATH_ABS(dragDistanceX) > maxScrollDistance_) {
304             int16_t calculatedValue = (dragDistanceX > 0) ? 1 : -1;
305             dragDistanceX = calculatedValue * maxScrollDistance_;
306         }
307     }
308 }
309 
Callback(UIView * view)310 void UIAbstractScroll::ListAnimatorCallback::Callback(UIView* view)
311 {
312     if (view == nullptr) {
313         return;
314     }
315 
316     UIAbstractScroll* scrollView = static_cast<UIAbstractScroll*>(view);
317     scrollView->isDragging_ = true;
318     curtTime_++;
319     if (curtTime_ <= dragTimes_) {
320         bool needStopX = false;
321         bool needStopY = false;
322         if (startValueY_ != endValueY_) {
323             int16_t actY = scrollView->easingFunc_(startValueY_, endValueY_, curtTime_, dragTimes_);
324             if (!scrollView->DragYInner(actY - previousValueY_)) {
325                 needStopY = true;
326             }
327             previousValueY_ = actY;
328         } else {
329             needStopY = true;
330         }
331         if (startValueX_ != endValueX_) {
332             int16_t actX = scrollView->easingFunc_(startValueX_, endValueX_, curtTime_, dragTimes_);
333             if (!scrollView->DragXInner(actX - previousValueX_)) {
334                 needStopX = true;
335             }
336             previousValueX_ = actX;
337         } else {
338             needStopX = true;
339         }
340         if (needStopX && needStopY) {
341             scrollView->StopAnimator();
342         }
343     } else {
344         scrollView->StopAnimator();
345     }
346 }
347 
348 #if ENABLE_ROTATE_INPUT
OnRotateStartEvent(const RotateEvent & event)349 bool UIAbstractScroll::OnRotateStartEvent(const RotateEvent& event)
350 {
351     isRotating_ = true;
352     if (scrollAnimator_.GetState() != Animator::STOP) {
353         UIAbstractScroll::StopAnimator();
354     }
355     return UIView::OnRotateStartEvent(event);
356 }
357 
OnRotateEvent(const RotateEvent & event)358 bool UIAbstractScroll::OnRotateEvent(const RotateEvent& event)
359 {
360     int16_t rotateLen = static_cast<int16_t>(event.GetRotate() * rotateFactor_);
361     RefreshRotate(rotateLen);
362     if (direction_ == HORIZONTAL) {
363         DragXInner(rotateLen);
364     } else {
365         DragYInner(rotateLen);
366     }
367     return UIView::OnRotateEvent(event);
368 }
369 
OnRotateEndEvent(const RotateEvent & event)370 bool UIAbstractScroll::OnRotateEndEvent(const RotateEvent& event)
371 {
372     InitDelta();
373 
374     uint8_t dir;
375     int16_t lastRotateLen = GetMaxRotate();
376     if (direction_ == HORIZONTAL) {
377         dir = (lastRotateLen >= 0) ? DragEvent::DIRECTION_LEFT_TO_RIGHT : DragEvent::DIRECTION_RIGHT_TO_LEFT;
378     } else {
379         dir = (lastRotateLen >= 0) ? DragEvent::DIRECTION_TOP_TO_BOTTOM : DragEvent::DIRECTION_BOTTOM_TO_TOP;
380     }
381     bool triggerAnimator = (MATH_ABS(lastRotateLen) >= rotateThrowthreshold_);
382     if (throwDrag_ && triggerAnimator) {
383         Point current;
384         if (direction_ == HORIZONTAL) {
385             current = {lastRotateLen, 0};
386         } else {
387             current = {0, lastRotateLen};
388         }
389         DragThrowAnimator(current, {0, 0}, dir, dragBack_);
390     } else {
391         DragThrowAnimator({0, 0}, {0, 0}, dir, dragBack_);
392     }
393     isRotating_ = false;
394     InitRotate();
395     return UIView::OnRotateEndEvent(event);
396 }
397 #endif
398 
SetXScrollBarVisible(bool visible)399 void UIAbstractScroll::SetXScrollBarVisible(bool visible)
400 {
401     if (Screen::GetInstance().GetScreenShape() == ScreenShape::CIRCLE) {
402         return;
403     } else if (visible && xScrollBar_ == nullptr) {
404         xScrollBar_ = new UIBoxScrollBar();
405     }
406     xScrollBarVisible_ = visible;
407 #if DEFAULT_ANIMATION
408     if (xScrollBarVisible_ && barEaseInOutAnimator_ == nullptr) {
409         barEaseInOutAnimator_ = new BarEaseInOutAnimator(*this);
410     }
411 #endif
412 }
413 
SetYScrollBarVisible(bool visible)414 void UIAbstractScroll::SetYScrollBarVisible(bool visible)
415 {
416     yScrollBarVisible_ = visible;
417     if (yScrollBarVisible_ && yScrollBar_ == nullptr) {
418         if (Screen::GetInstance().GetScreenShape() == ScreenShape::CIRCLE) {
419             yScrollBar_ = new UIArcScrollBar();
420         } else {
421             yScrollBar_ = new UIBoxScrollBar();
422         }
423     }
424 #if DEFAULT_ANIMATION
425     if (yScrollBarVisible_ && barEaseInOutAnimator_ == nullptr) {
426         barEaseInOutAnimator_ = new BarEaseInOutAnimator(*this);
427     }
428 #endif
429 }
430 
OnPostDraw(BufferInfo & gfxDstBuffer,const Rect & invalidatedArea)431 void UIAbstractScroll::OnPostDraw(BufferInfo& gfxDstBuffer, const Rect& invalidatedArea)
432 {
433     Rect scrollRect = GetRect();
434     uint8_t opa = GetMixOpaScale();
435     if (Screen::GetInstance().GetScreenShape() == ScreenShape::RECTANGLE) {
436         if (yScrollBarVisible_) {
437             if (scrollBarSide_ == SCROLL_BAR_RIGHT_SIDE) {
438                 yScrollBar_->SetPosition(scrollRect.GetRight() - SCROLL_BAR_WIDTH + 1, scrollRect.GetTop(),
439                                          SCROLL_BAR_WIDTH, scrollRect.GetHeight());
440             } else {
441                 yScrollBar_->SetPosition(scrollRect.GetLeft(), scrollRect.GetTop(), SCROLL_BAR_WIDTH,
442                                          scrollRect.GetHeight());
443             }
444             yScrollBar_->OnDraw(gfxDstBuffer, invalidatedArea, opa);
445         }
446         if (xScrollBarVisible_) {
447             if (scrollBarSide_ == SCROLL_BAR_RIGHT_SIDE) {
448                 xScrollBar_->SetPosition(scrollRect.GetLeft(), scrollRect.GetBottom() - SCROLL_BAR_WIDTH + 1,
449                                          scrollRect.GetWidth() - SCROLL_BAR_WIDTH, SCROLL_BAR_WIDTH);
450             } else {
451                 xScrollBar_->SetPosition(scrollRect.GetLeft() + SCROLL_BAR_WIDTH,
452                                          scrollRect.GetBottom() - SCROLL_BAR_WIDTH + 1,
453                                          scrollRect.GetWidth() - SCROLL_BAR_WIDTH, SCROLL_BAR_WIDTH);
454             }
455             xScrollBar_->OnDraw(gfxDstBuffer, invalidatedArea, opa);
456         }
457     } else {
458         if (yScrollBarVisible_) {
459             yScrollBar_->SetScrollBarSide(scrollBarSide_);
460             int16_t x;
461             int16_t y;
462             if (scrollBarCenterSetFlag_) {
463                 x = scrollRect.GetX() + scrollBarCenter_.x;
464                 y = scrollRect.GetY() + scrollBarCenter_.y;
465             } else {
466                 x = scrollRect.GetX() + (GetWidth() / 2);  // 2: half
467                 y = scrollRect.GetY() + (GetHeight() / 2); // 2: half
468             }
469             yScrollBar_->SetPosition(x, y, SCROLL_BAR_WIDTH, GetWidth() / 2); // 2: half
470             yScrollBar_->OnDraw(gfxDstBuffer, invalidatedArea, opa);
471         }
472     }
473     UIView::OnPostDraw(gfxDstBuffer, invalidatedArea);
474 }
475 
RefreshAnimator()476 void UIAbstractScroll::RefreshAnimator()
477 {
478 #if DEFAULT_ANIMATION
479     barEaseInOutAnimator_->RefreshBar();
480 #endif
481 }
482 } // namespace OHOS
483