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_swipe_view.h"
17#include "dock/focus_manager.h"
18#include "dock/vibrator_manager.h"
19#include "gfx_utils/graphic_log.h"
20
21namespace OHOS {
22UISwipeView::UISwipeView(uint8_t direction)
23    : swipeListener_(nullptr), curIndex_(0), blankSize_(DEFAULT_BLANK_SIZE), curView_(nullptr), loop_(false)
24{
25#if defined(ENABLE_ROTATE_INPUT) && ENABLE_ROTATE_INPUT
26    rotateFactor_ = DEFAULT_SWIPE_VIEW_ROTATE_FACTOR;
27#endif
28    direction_ = direction;
29    tickTime_ = ANIMATOR_TIME;
30    swipeAccCoefficient_ = DRAG_ACC_FACTOR;
31}
32
33UISwipeView::~UISwipeView() {}
34
35void UISwipeView::Add(UIView* view)
36{
37    if (view == nullptr) {
38        return;
39    }
40    view->SetDragParentInstead(true);
41    UIViewGroup::Add(view);
42    SortChild();
43    Invalidate();
44}
45
46void UISwipeView::Insert(UIView* prevView, UIView* insertView)
47{
48    if (insertView == nullptr) {
49        return;
50    }
51    insertView->SetDragParentInstead(true);
52    UIViewGroup::Insert(prevView, insertView);
53    SortChild();
54    Invalidate();
55}
56
57void UISwipeView::Remove(UIView* view)
58{
59    if (view == nullptr) {
60        return;
61    }
62    UIViewGroup::Remove(view);
63    if (curView_ == view) {
64        curView_ = nullptr;
65    }
66    SortChild();
67    Invalidate();
68}
69
70void UISwipeView::MoveHeadOrTailChild()
71{
72    if (loop_ && (childrenNum_ != 1)) {
73        if (direction_ == HORIZONTAL) {
74            while (childrenHead_->GetX() >= 0) {
75                MoveLastChildToFirst();
76            }
77            while (childrenTail_->GetX() + childrenTail_->GetWidth() <= GetWidth()) {
78                MoveFirstChildToLast();
79            }
80        } else {
81            while (childrenHead_->GetY() >= 0) {
82                MoveLastChildToFirst();
83            }
84            while (childrenTail_->GetY() + childrenTail_->GetHeight() <= GetHeight()) {
85                MoveFirstChildToLast();
86            }
87        }
88    }
89}
90
91void UISwipeView::SetCurrentPage(uint16_t index, bool needAnimator)
92{
93    if (needAnimator) {
94        MoveHeadOrTailChild();
95    }
96    SwitchToPage(index, needAnimator);
97    Invalidate();
98}
99
100bool UISwipeView::DragXInner(int16_t distance)
101{
102    if (distance == 0) {
103        return true;
104    }
105    if (!loop_) {
106        if ((distance > 0) && (childrenHead_ != nullptr)) {
107            if (childrenHead_->GetX() >= blankSize_) {
108                distance = 0;
109            } else if (childrenHead_ && (childrenHead_->GetX() + distance > blankSize_)) {
110                distance = blankSize_ - childrenHead_->GetX();
111            }
112        } else if (childrenTail_ != nullptr) {
113            int16_t width = GetWidth();
114            if (childrenTail_->GetRelativeRect().GetRight() < width - blankSize_) {
115                distance = 0;
116            } else if (width - (childrenTail_->GetX() + childrenTail_->GetWidth() + distance) > blankSize_) {
117                distance = width - blankSize_ - childrenTail_->GetX() - childrenTail_->GetWidth();
118            }
119        }
120    }
121    CalculateInvalidate();
122    MoveChildByOffset(distance, 0);
123    CalculateInvalidate();
124    return true;
125}
126
127bool UISwipeView::DragYInner(int16_t distance)
128{
129    if (distance == 0) {
130        return true;
131    }
132    if (!loop_) {
133        if ((distance > 0) && (childrenHead_ != nullptr)) {
134            if (childrenHead_->GetY() >= blankSize_) {
135                distance = 0;
136            } else if ((childrenHead_ != nullptr) && (childrenHead_->GetY() + distance > blankSize_)) {
137                distance = blankSize_ - childrenHead_->GetY();
138            }
139        } else if (childrenTail_ != nullptr) {
140            int16_t height = GetHeight();
141            if (childrenTail_->GetRelativeRect().GetBottom() < height - blankSize_) {
142                distance = 0;
143            } else if (height - (childrenTail_->GetY() + childrenTail_->GetHeight() + distance) > blankSize_) {
144                distance = height - blankSize_ - childrenTail_->GetY() - childrenTail_->GetHeight();
145            }
146        }
147    }
148    CalculateInvalidate();
149    MoveChildByOffset(0, distance);
150    CalculateInvalidate();
151    return true;
152}
153
154bool UISwipeView::OnDragEvent(const DragEvent& event)
155{
156    UIView* currentView = GetViewByIndex(curIndex_);
157    if (currentView == nullptr) {
158        return UIView::OnDragEvent(event);
159    }
160    if (scrollAnimator_.GetState() != Animator::STOP) {
161        StopAnimator();
162    }
163
164    if (direction_ == HORIZONTAL) {
165        DragXInner(event.GetDeltaX());
166        RefreshDelta(event.GetDeltaX());
167        RefreshCurrentViewByPosition(&UIView::GetX, &UIView::GetWidthWithMargin);
168    } else {
169        DragYInner(event.GetDeltaY());
170        RefreshDelta(event.GetDeltaY());
171        RefreshCurrentViewByPosition(&UIView::GetY, &UIView::GetHeightWithMargin);
172    }
173    return UIView::OnDragEvent(event);
174}
175
176bool UISwipeView::OnDragEndEvent(const DragEvent& event)
177{
178    int16_t distance = 0;
179    if (direction_ == HORIZONTAL) {
180        distance = event.GetCurrentPos().x - event.GetPreLastPoint().x;
181        RefreshCurrentViewByThrow(distance, event.GetDragDirection(), &UIView::GetX, &UIView::GetWidthWithMargin);
182    } else {
183        distance = event.GetCurrentPos().y - event.GetPreLastPoint().y;
184        RefreshCurrentViewByThrow(distance, event.GetDragDirection(), &UIView::GetY, &UIView::GetHeightWithMargin);
185    }
186
187    if (curView_ == nullptr) {
188        return UIView::OnDragEndEvent(event);
189    }
190
191    SwitchToPage(curIndex_);
192
193    Invalidate();
194    return UIView::OnDragEndEvent(event);
195}
196
197#if defined(ENABLE_ROTATE_INPUT) && ENABLE_ROTATE_INPUT
198
199bool UISwipeView::OnRotateEvent(const RotateEvent& event)
200{
201    int16_t rotateLen = static_cast<int16_t>(event.GetRotate() * rotateFactor_);
202    RefreshRotate(rotateLen);
203    if (direction_ == HORIZONTAL) {
204        DragXInner(rotateLen);
205        RefreshCurrentViewByPosition(&UIView::GetX, &UIView::GetWidthWithMargin);
206    } else {
207        DragYInner(rotateLen);
208        RefreshCurrentViewByPosition(&UIView::GetY, &UIView::GetHeightWithMargin);
209    }
210
211    return UIView::OnRotateEvent(event);
212}
213
214bool UISwipeView::OnRotateEndEvent(const RotateEvent& event)
215{
216    uint8_t dir;
217    int16_t lastRotateLen = GetMaxRotate();
218    if (direction_ == HORIZONTAL) {
219        dir = (lastRotateLen >= 0) ? DragEvent::DIRECTION_LEFT_TO_RIGHT : DragEvent::DIRECTION_RIGHT_TO_LEFT;
220    } else {
221        dir = (lastRotateLen >= 0) ? DragEvent::DIRECTION_TOP_TO_BOTTOM : DragEvent::DIRECTION_BOTTOM_TO_TOP;
222    }
223    if (direction_ == HORIZONTAL) {
224        RefreshCurrentViewByThrow(lastRotateLen, dir, &UIView::GetX, &UIView::GetWidthWithMargin);
225    } else {
226        RefreshCurrentViewByThrow(lastRotateLen, dir, &UIView::GetY, &UIView::GetHeightWithMargin);
227    }
228    if (curView_ == nullptr) {
229        return UIView::OnRotateEndEvent(event);
230    }
231    SwitchToPage(curIndex_);
232    isRotating_ = false;
233    return UIView::OnRotateEndEvent(event);
234}
235#endif
236
237UIView* UISwipeView::GetViewByIndex(uint16_t index) const
238{
239    UIView* child = childrenHead_;
240    while (child != nullptr) {
241        if (child->GetViewIndex() == index) {
242            return child;
243        }
244        child = child->GetNextSibling();
245    }
246    return nullptr;
247}
248
249void UISwipeView::SetAnimatorTime(uint16_t time)
250{
251    tickTime_ = time / DEFAULT_TASK_PERIOD;
252    if (tickTime_ == 0) {
253        tickTime_ = 1;
254    }
255    animatorCallback_.SetDragTimes(tickTime_);
256}
257
258void UISwipeView::SwitchToPage(int16_t dst, bool needAnimator)
259{
260    if (IsNeedLoop()) {
261        dst = (dst + childrenNum_) % childrenNum_;
262    } else if (dst < 0) {
263        dst = 0;
264    } else if (dst >= childrenNum_) {
265        dst = childrenNum_ - 1;
266    }
267
268    UIView* dstView = GetViewByIndex(dst);
269    if (dstView == nullptr) {
270        return;
271    }
272    curIndex_ = dst;
273    curView_ = dstView;
274    int16_t xOffset = 0;
275    int16_t yOffset = 0;
276
277    if (direction_ == HORIZONTAL) {
278        if (alignMode_ == ALIGN_LEFT) {
279            xOffset = -dstView->GetX();
280        } else if (alignMode_ == ALIGN_RIGHT) {
281            xOffset = GetWidth() - (dstView->GetX() + dstView->GetWidthWithMargin());
282        } else {
283            xOffset = (GetWidth() >> 1) - (dstView->GetX() + (dstView->GetWidthWithMargin() >> 1));
284        }
285    } else {
286        yOffset = (GetHeight() >> 1) - (dstView->GetY() + (dstView->GetHeightWithMargin() >> 1));
287    }
288
289    if ((xOffset != 0) || (yOffset != 0)) {
290        if (scrollAnimator_.GetState() != Animator::STOP) {
291            scrollAnimator_.Stop();
292        }
293        if (needAnimator) {
294            animatorCallback_.SetDragTimes(tickTime_);
295            animatorCallback_.SetDragStartValue(0, 0);
296            animatorCallback_.SetDragEndValue(xOffset, yOffset);
297            scrollAnimator_.Start();
298        } else {
299            MoveChildByOffset(xOffset, yOffset);
300        }
301    }
302}
303
304void UISwipeView::StopAnimator()
305{
306    UIAbstractScroll::StopAnimator();
307    if (swipeListener_ != nullptr) {
308        swipeListener_->OnSwipe(*this);
309    }
310}
311
312void UISwipeView::SortChild()
313{
314    if (childrenHead_ == nullptr) {
315        return;
316    }
317    int16_t index = 0;
318    UIView* pre = childrenHead_;
319    UIView* next = childrenHead_->GetNextSibling();
320    if (direction_ == HORIZONTAL) {
321        pre->SetX(0);
322    } else {
323        pre->SetY(0);
324    }
325    pre->SetViewIndex(index);
326    index++;
327
328    while (next != nullptr) {
329        if (direction_ == HORIZONTAL) {
330            next->SetX(pre->GetRelativeRect().GetRight() + pre->GetStyle(STYLE_MARGIN_RIGHT) + 1);
331        } else {
332            next->SetY(pre->GetRelativeRect().GetBottom() + pre->GetStyle(STYLE_MARGIN_BOTTOM) + 1);
333        }
334        pre = next;
335        next->SetViewIndex(index);
336        next = next->GetNextSibling();
337        index++;
338    }
339    bool tmpLoop = loop_;
340    loop_ = false;
341    SwitchToPage(curIndex_, false);
342    loop_ = tmpLoop;
343}
344
345void UISwipeView::RefreshCurrentViewByPosition(int16_t (UIView::*pfnGetXOrY)() const,
346                                               int16_t (UIView::*pfnGetWidthOrHeight)())
347{
348    if (childrenHead_ == nullptr) {
349        curIndex_ = 0;
350        curView_ = nullptr;
351        return;
352    }
353
354#if defined(ENABLE_VIBRATOR) && ENABLE_VIBRATOR
355    uint16_t lastIndex = curIndex_;
356#endif
357    curIndex_ = 0;
358    curView_ = nullptr;
359
360    /*
361     * It needs to be modified that swipemid should be calculated by the width and height of the current
362     * sub view itself, not the width and height of the parent, especially for ALIGN_LEFT and ALIGN_RIGHT.
363     */
364    uint16_t swipeMid;
365    if (alignMode_ == ALIGN_LEFT) {
366        swipeMid = 0;
367    } else if (alignMode_ == ALIGN_RIGHT) {
368        swipeMid = (this->*pfnGetWidthOrHeight)();
369    } else {
370        swipeMid = (this->*pfnGetWidthOrHeight)() >> 1;
371    }
372    UIView* view = childrenHead_;
373
374    if ((childrenHead_->*pfnGetXOrY)() > swipeMid) {
375        curIndex_ = childrenHead_->GetViewIndex();
376        curView_ = childrenHead_;
377    } else if ((childrenTail_->*pfnGetXOrY)() + (childrenHead_->*pfnGetWidthOrHeight)() < swipeMid) {
378        curIndex_ = childrenTail_->GetViewIndex();
379        curView_ = childrenTail_;
380    } else {
381        while (view != nullptr) {
382            if ((swipeMid >= (view->*pfnGetXOrY)()) &&
383                (swipeMid <= (view->*pfnGetXOrY)() + (view->*pfnGetWidthOrHeight)())) {
384                curIndex_ = view->GetViewIndex();
385                curView_ = view;
386                break;
387            }
388            view = view->GetNextSibling();
389        }
390    }
391#if defined(ENABLE_VIBRATOR) && ENABLE_VIBRATOR
392    if (lastIndex != curIndex_) {
393        Vibrator();
394    }
395#endif
396}
397
398void UISwipeView::RefreshCurrentViewByThrow(int16_t distance,
399                                            uint8_t dragDirection,
400                                            int16_t (UIView::*pfnGetXOrY)() const,
401                                            int16_t (UIView::*pfnGetWidthOrHeight)())
402{
403    if (curView_ == nullptr) {
404        return;
405    }
406#if defined(ENABLE_VIBRATOR) && ENABLE_VIBRATOR
407    uint16_t lastIndex = curIndex_;
408#endif
409
410    /*
411     * It needs to be modified that swipemid should be calculated by the width and height of the current
412     * sub view itself, not the width and height of the parent, especially for ALIGN_LEFT and ALIGN_RIGHT.
413     */
414    uint16_t swipeMid;
415    if (alignMode_ == ALIGN_LEFT) {
416        swipeMid = 0;
417    } else if (alignMode_ == ALIGN_RIGHT) {
418        swipeMid = (this->*pfnGetWidthOrHeight)();
419    } else {
420        swipeMid = (this->*pfnGetWidthOrHeight)() >> 1;
421    }
422
423    int16_t accelerationOffset = GetMaxDelta() * GetSwipeACCLevel() / DRAG_ACC_FACTOR;
424    if (distance < 0) {
425        /*
426         * 7, 10 : Check whether the current view is dragged by more than 1/5,
427         * that is, the x or y coordinate plus 7/10 width or height.
428         */
429        if (((curView_->*pfnGetXOrY)() + ((curView_->*pfnGetWidthOrHeight)() >> 1) < swipeMid) &&
430            ((curView_->*pfnGetXOrY)() + ((curView_->*pfnGetWidthOrHeight)() * 7 / 10) - accelerationOffset <
431             swipeMid)) {
432            CurrentIndexInc();
433        }
434    } else if (distance > 0) {
435        /*
436         * 3, 10 : Check whether the current view is dragged by more than 1/5,
437         * that is, the x or y coordinate plus 3/10 width or height.
438         */
439        if (((curView_->*pfnGetXOrY)() + ((curView_->*pfnGetWidthOrHeight)() >> 1) > swipeMid) &&
440            ((curView_->*pfnGetXOrY)() + ((curView_->*pfnGetWidthOrHeight)() * 3 / 10) + accelerationOffset >
441             swipeMid)) {
442            CurrentIndexDec();
443        }
444    } else {
445        if (alignMode_ == ALIGN_LEFT) {
446            if (((curView_->*pfnGetXOrY)() + ((curView_->*pfnGetWidthOrHeight)() >> 1) < swipeMid)) {
447                CurrentIndexInc();
448            }
449        } else if (alignMode_ == ALIGN_RIGHT) {
450            if ((curView_->*pfnGetXOrY)() + ((curView_->*pfnGetWidthOrHeight)() >> 1) > swipeMid) {
451                CurrentIndexDec();
452            }
453        } else {
454            /*
455             * If the absolute value of the offset is greater than the page turning threshold,
456             * page turning is considered.
457             */
458            int16_t offset = (curView_->*pfnGetXOrY)() + ((curView_->*pfnGetWidthOrHeight)() >> 1) - swipeMid;
459            int16_t threshold = (this->*pfnGetWidthOrHeight)() >> 2; // 2: 1/4 width or height
460            if (offset > threshold && (dragDirection == DragEvent::DIRECTION_TOP_TO_BOTTOM ||
461                                       dragDirection == DragEvent::DIRECTION_LEFT_TO_RIGHT)) {
462                CurrentIndexDec();
463            } else if ((offset < -threshold) && (dragDirection == DragEvent::DIRECTION_BOTTOM_TO_TOP ||
464                                                 dragDirection == DragEvent::DIRECTION_RIGHT_TO_LEFT)) {
465                CurrentIndexInc();
466            }
467        }
468    }
469    curView_ = GetViewByIndex(curIndex_);
470#if defined(ENABLE_VIBRATOR) && ENABLE_VIBRATOR
471    if (lastIndex != curIndex_) {
472        Vibrator();
473    }
474#endif
475}
476
477void UISwipeView::MoveChildByOffset(int16_t xOffset, int16_t yOffset)
478{
479    UIViewGroup::MoveChildByOffset(xOffset, yOffset);
480    MoveHeadOrTailChild();
481}
482
483bool UISwipeView::IsNeedLoop()
484{
485    if (!loop_ || (childrenHead_ == nullptr) || (childrenTail_ == nullptr)) {
486        return false;
487    }
488    Rect childRect = GetAllChildRelativeRect();
489    if (direction_ == HORIZONTAL) {
490        if ((childRect.GetWidth() - childrenHead_->GetWidth() >= GetWidth()) &&
491            (childRect.GetWidth() - childrenTail_->GetWidth() >= GetWidth())) {
492            return true;
493        }
494    } else {
495        if ((childRect.GetHeight() - childrenHead_->GetHeight() >= GetHeight()) &&
496            (childRect.GetHeight() - childrenTail_->GetHeight() >= GetHeight())) {
497            return true;
498        }
499    }
500    return false;
501}
502
503void UISwipeView::MoveFirstChildToLast()
504{
505    if ((childrenTail_ == nullptr) || (childrenHead_ == nullptr)) {
506        return;
507    }
508    if (direction_ == HORIZONTAL) {
509        childrenHead_->SetX(childrenTail_->GetX() + childrenTail_->GetWidth());
510    } else {
511        childrenHead_->SetY(childrenTail_->GetY() + childrenTail_->GetHeight());
512    }
513    UIView* head = childrenHead_;
514    UIViewGroup::Remove(childrenHead_);
515    UIViewGroup::Add(head);
516}
517
518void UISwipeView::MoveLastChildToFirst()
519{
520    if ((childrenTail_ == nullptr) || (childrenHead_ == nullptr)) {
521        return;
522    }
523    if (direction_ == HORIZONTAL) {
524        childrenTail_->SetX(childrenHead_->GetX() - childrenTail_->GetWidth());
525    } else {
526        childrenTail_->SetY(childrenHead_->GetY() - childrenTail_->GetHeight());
527    }
528    UIView* last = childrenTail_;
529    UIViewGroup::Remove(childrenTail_);
530    UIViewGroup::Insert(nullptr, last);
531}
532
533void UISwipeView::CalculateInvalidate()
534{
535    Rect swipeRect(0, 0, GetRelativeRect().GetWidth() - 1, GetRelativeRect().GetHeight() - 1);
536    UIView* view = childrenHead_;
537    bool isFound = false;
538    while (view != nullptr) {
539        Rect rect = view->GetRelativeRect();
540        if (rect.IsIntersect(swipeRect)) {
541            if (view->IsVisible() && (view->GetOpaScale() != OPA_TRANSPARENT)) {
542                view->Invalidate();
543            }
544            isFound = true;
545        } else if (isFound) {
546            return;
547        }
548
549        view = view->GetNextSibling();
550    }
551}
552
553void UISwipeView::CurrentIndexInc()
554{
555    curIndex_++;
556    if (curIndex_ > childrenNum_ - 1) {
557        if (IsNeedLoop()) {
558            curIndex_ = curIndex_ % childrenNum_;
559        } else {
560            curIndex_ = childrenNum_ - 1;
561        }
562    }
563}
564
565void UISwipeView::CurrentIndexDec()
566{
567    if (curIndex_ == 0) {
568        if (IsNeedLoop()) {
569            curIndex_ = childrenNum_ - 1;
570        }
571    } else {
572        curIndex_--;
573    }
574}
575
576#if defined(ENABLE_VIBRATOR) && ENABLE_VIBRATOR
577void UISwipeView::Vibrator()
578{
579    VibratorFunc vibratorFunc = VibratorManager::GetInstance()->GetVibratorFunc();
580    if (vibratorFunc != nullptr && isRotating_) {
581        if (!loop_ && (curIndex_ == 0 || curIndex_ == childrenNum_ - 1)) {
582            GRAPHIC_LOGI("UISwipeView::Vibrator calls TYPE_THREE vibrator");
583            vibratorFunc(VibratorType::VIBRATOR_TYPE_THREE);
584        } else {
585            GRAPHIC_LOGI("UISwipeView::Vibrator calls TYPE_ONE vibrator");
586            vibratorFunc(VibratorType::VIBRATOR_TYPE_ONE);
587        }
588    }
589}
590#endif
591} // namespace OHOS
592