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
29namespace OHOS {
30#if DEFAULT_ANIMATION
31class BarEaseInOutAnimator final : public AnimatorCallback {
32public:
33    BarEaseInOutAnimator() = delete;
34    BarEaseInOutAnimator(const BarEaseInOutAnimator&) = delete;
35    BarEaseInOutAnimator& operator=(const BarEaseInOutAnimator&) = delete;
36    BarEaseInOutAnimator(BarEaseInOutAnimator&&) = delete;
37    BarEaseInOutAnimator& operator=(BarEaseInOutAnimator&&) = delete;
38
39    explicit BarEaseInOutAnimator(UIAbstractScroll& scrollView)
40        : scrollView_(scrollView),
41          timer_(APPEAR_PERIOD, TimerCb, this),
42          animator_(this, nullptr, ANIMATOR_DURATION, false)
43    {
44    }
45
46    ~BarEaseInOutAnimator()
47    {
48        timer_.Stop();
49        animator_.Stop();
50    }
51
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
67private:
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
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
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
125UIAbstractScroll::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
151UIAbstractScroll::~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
169void 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
184int16_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
195int16_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
206void UIAbstractScroll::InitDelta()
207{
208    if (memset_s(lastDelta_, sizeof(lastDelta_), 0, sizeof(lastDelta_)) != EOK) {
209        GRAPHIC_LOGE("memset_s error");
210    }
211}
212
213void UIAbstractScroll::InitRotate()
214{
215    if (memset_s(lastRotate_, sizeof(lastRotate_), 0, sizeof(lastRotate_)) != EOK) {
216        GRAPHIC_LOGE("memset_s error");
217    }
218}
219
220void UIAbstractScroll::StopAnimator()
221{
222    scrollAnimator_.Stop();
223    animatorCallback_.ResetCallback();
224    isDragging_ = false;
225}
226
227bool 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
249void 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
262void 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
310void 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
349bool 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
358bool 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
370bool 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
399void 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
414void 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
431void 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
476void UIAbstractScroll::RefreshAnimator()
477{
478#if DEFAULT_ANIMATION
479    barEaseInOutAnimator_->RefreshBar();
480#endif
481}
482} // namespace OHOS
483