/* * Copyright (c) 2020-2021 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "components/ui_abstract_scroll.h" #include "securec.h" #include "animator/interpolation.h" #include "common/screen.h" #include "components/ui_abstract_scroll_bar.h" #include "components/ui_arc_scroll_bar.h" #include "components/ui_box_scroll_bar.h" #if DEFAULT_ANIMATION #include "graphic_timer.h" #endif namespace OHOS { #if DEFAULT_ANIMATION class BarEaseInOutAnimator final : public AnimatorCallback { public: BarEaseInOutAnimator() = delete; BarEaseInOutAnimator(const BarEaseInOutAnimator&) = delete; BarEaseInOutAnimator& operator=(const BarEaseInOutAnimator&) = delete; BarEaseInOutAnimator(BarEaseInOutAnimator&&) = delete; BarEaseInOutAnimator& operator=(BarEaseInOutAnimator&&) = delete; explicit BarEaseInOutAnimator(UIAbstractScroll& scrollView) : scrollView_(scrollView), timer_(APPEAR_PERIOD, TimerCb, this), animator_(this, nullptr, ANIMATOR_DURATION, false) { } ~BarEaseInOutAnimator() { timer_.Stop(); animator_.Stop(); } void RefreshBar() { if (animator_.GetState() == Animator::START) { if (!isEaseIn_) { animator_.SetRunTime(ANIMATOR_DURATION - animator_.GetRunTime()); } } else if (scrollView_.yScrollBar_->GetOpacity() == OPA_TRANSPARENT) { animator_.Start(); } else { timer_.Start(); // updates the start time of timer, ensuring that timer is triggered two seconds after the // last operation } isEaseIn_ = true; } private: void Callback(UIView* view) override { uint8_t opa = OPA_OPAQUE * animator_.GetRunTime() / ANIMATOR_DURATION; if (!isEaseIn_) { opa = OPA_OPAQUE - opa; } float bezielY = opa; bezielY = Interpolation::GetBezierY(bezielY / OPA_OPAQUE, BEZIER_CONTROL_POINT_X_1, 0, BEZIER_CONTROL_POINT_X_2, 1); opa = static_cast<uint8_t>(bezielY * opa); if (scrollView_.yScrollBarVisible_) { scrollView_.yScrollBar_->SetOpacity(opa); } if (scrollView_.xScrollBarVisible_) { scrollView_.xScrollBar_->SetOpacity(opa); } scrollView_.Invalidate(); } void OnStop(UIView& view) override { if (isEaseIn_) { if (scrollView_.yScrollBarVisible_) { scrollView_.yScrollBar_->SetOpacity(OPA_OPAQUE); } if (Screen::GetInstance().GetScreenShape() == ScreenShape::RECTANGLE && scrollView_.xScrollBarVisible_) { scrollView_.xScrollBar_->SetOpacity(OPA_OPAQUE); } timer_.Start(); // The timer is triggered when animation stops. } else { if (scrollView_.yScrollBarVisible_) { scrollView_.yScrollBar_->SetOpacity(OPA_TRANSPARENT); } if (scrollView_.xScrollBarVisible_) { scrollView_.xScrollBar_->SetOpacity(OPA_TRANSPARENT); } } scrollView_.Invalidate(); } static void TimerCb(void* arg) { BarEaseInOutAnimator* barAnimator = reinterpret_cast<BarEaseInOutAnimator*>(arg); barAnimator->isEaseIn_ = false; barAnimator->animator_.Start(); } static constexpr uint16_t ANIMATOR_DURATION = 250; static constexpr uint16_t APPEAR_PERIOD = 2000; static constexpr float BEZIER_CONTROL_POINT_X_1 = 0.33f; static constexpr float BEZIER_CONTROL_POINT_X_2 = 0.67f; UIAbstractScroll& scrollView_; GraphicTimer timer_; Animator animator_; bool isEaseIn_ = true; }; #endif UIAbstractScroll::UIAbstractScroll() : direction_(VERTICAL), deltaIndex_(0), rotateIndex_(0), reserve_(0), easingFunc_(EasingEquation::CubicEaseOut), scrollAnimator_(&animatorCallback_, this, 0, true), scrollBarSide_(SCROLL_BAR_RIGHT_SIDE), scrollBarCenter_({0, 0}), scrollBarCenterSetFlag_(false) { #if defined(ENABLE_FOCUS_MANAGER) && ENABLE_FOCUS_MANAGER focusable_ = true; #endif #if defined(ENABLE_ROTATE_INPUT) && ENABLE_ROTATE_INPUT rotateFactor_ = DEFAULT_SCROLL_VIEW_ROTATE_FACTOR; rotateThrowthreshold_ = ABSTRACT_ROTATE_THROW_THRESHOLD; rotateAccCoefficient_ = ABSTRACT_ROTATE_DISTANCE_COEFF; isRotating_ = false; #endif isViewGroup_ = true; touchable_ = true; draggable_ = true; dragParentInstead_ = false; } UIAbstractScroll::~UIAbstractScroll() { #if defined(DEFAULT_ANIMATION) && DEFAULT_ANIMATION if (barEaseInOutAnimator_ != nullptr) { delete barEaseInOutAnimator_; barEaseInOutAnimator_ = nullptr; } #endif if (xScrollBar_ != nullptr) { delete xScrollBar_; xScrollBar_ = nullptr; } if (yScrollBar_ != nullptr) { delete yScrollBar_; yScrollBar_ = nullptr; } } void UIAbstractScroll::MoveChildByOffset(int16_t offsetX, int16_t offsetY) { if ((offsetX == 0) && (offsetY == 0)) { return; } UIView* view = GetChildrenHead(); while (view != nullptr) { int16_t x = view->GetX() + offsetX; int16_t y = view->GetY() + offsetY; view->SetPosition(x, y); view = view->GetNextSibling(); } Invalidate(); } int16_t UIAbstractScroll::GetMaxDelta() const { int16_t result = 0; for (int16_t i = 0; i < MAX_DELTA_SIZE; i++) { if (result < MATH_ABS(lastDelta_[i])) { result = MATH_ABS(lastDelta_[i]); } } return result; } int16_t UIAbstractScroll::GetMaxRotate() const { int16_t result = 0; for (int16_t i = 0; i < MAX_DELTA_SIZE; i++) { if (MATH_ABS(result) < MATH_ABS(lastRotate_[i])) { result = lastRotate_[i]; } } return result; } void UIAbstractScroll::InitDelta() { if (memset_s(lastDelta_, sizeof(lastDelta_), 0, sizeof(lastDelta_)) != EOK) { GRAPHIC_LOGE("memset_s error"); } } void UIAbstractScroll::InitRotate() { if (memset_s(lastRotate_, sizeof(lastRotate_), 0, sizeof(lastRotate_)) != EOK) { GRAPHIC_LOGE("memset_s error"); } } void UIAbstractScroll::StopAnimator() { scrollAnimator_.Stop(); animatorCallback_.ResetCallback(); isDragging_ = false; } bool UIAbstractScroll::DragThrowAnimator(Point currentPos, Point lastPos, uint8_t dragDirection, bool dragBack) { if (!throwDrag_ && (reboundSize_ == 0)) { return false; } int16_t dragDistanceX = 0; int16_t dragDistanceY = 0; if (throwDrag_) { CalculateDragDistance(currentPos, lastPos, dragDirection, dragDistanceX, dragDistanceY); } if (reboundSize_ != 0) { CalculateReboundDistance(dragDistanceX, dragDistanceY); } if (!dragBack) { FixDistance(dragDistanceX, dragDistanceY); } StartAnimator(dragDistanceX, dragDistanceY); return true; } void UIAbstractScroll::StartAnimator(int16_t dragDistanceX, int16_t dragDistanceY) { int16_t dragTimes = MATH_MAX(MATH_ABS(dragDistanceX), MATH_ABS(dragDistanceY)) / DRAG_TIMES_COEFFICIENT; if (dragTimes < MIN_DRAG_TIMES) { dragTimes = MIN_DRAG_TIMES; } animatorCallback_.ResetCallback(); animatorCallback_.SetDragStartValue(0, 0); animatorCallback_.SetDragEndValue(dragDistanceX, dragDistanceY); animatorCallback_.SetDragTimes(dragTimes * DRAG_ACC_FACTOR / GetDragACCLevel()); scrollAnimator_.Start(); } void UIAbstractScroll::CalculateDragDistance(Point currentPos, Point lastPos, uint8_t dragDirection, int16_t& dragDistanceX, int16_t& dragDistanceY) { if ((direction_ == VERTICAL) || (direction_ == HORIZONTAL_AND_VERTICAL)) { dragDistanceY = currentPos.y - lastPos.y; if (isRotating_) { dragDistanceY *= rotateAccCoefficient_; } else { dragDistanceY *= DRAG_DISTANCE_COEFFICIENT; if (dragDistanceY > 0 || (dragDistanceY == 0 && dragDirection == DragEvent::DIRECTION_TOP_TO_BOTTOM)) { dragDistanceY += GetMaxDelta() * GetSwipeACCLevel() / DRAG_ACC_FACTOR; } else if (dragDistanceY < 0 || (dragDistanceY == 0 && dragDirection == DragEvent::DIRECTION_BOTTOM_TO_TOP)) { dragDistanceY -= GetMaxDelta() * GetSwipeACCLevel() / DRAG_ACC_FACTOR; } } } if ((direction_ == HORIZONTAL) || (direction_ == HORIZONTAL_AND_VERTICAL)) { dragDistanceX = currentPos.x - lastPos.x; if (isRotating_) { dragDistanceX *= rotateAccCoefficient_; } else { dragDistanceX *= DRAG_DISTANCE_COEFFICIENT; if (dragDistanceX > 0 || (dragDistanceX == 0 && dragDirection == DragEvent::DIRECTION_LEFT_TO_RIGHT)) { dragDistanceX += GetMaxDelta() * GetSwipeACCLevel() / DRAG_ACC_FACTOR; } else if (dragDistanceX < 0 || (dragDistanceX == 0 && dragDirection == DragEvent::DIRECTION_RIGHT_TO_LEFT)) { dragDistanceX -= GetMaxDelta() * GetSwipeACCLevel() / DRAG_ACC_FACTOR; } } } if (maxScrollDistance_ != 0) { if (MATH_ABS(dragDistanceY) > maxScrollDistance_) { int16_t calculatedValue = (dragDistanceY > 0) ? 1 : -1; dragDistanceY = calculatedValue * maxScrollDistance_; } if (MATH_ABS(dragDistanceX) > maxScrollDistance_) { int16_t calculatedValue = (dragDistanceX > 0) ? 1 : -1; dragDistanceX = calculatedValue * maxScrollDistance_; } } } void UIAbstractScroll::ListAnimatorCallback::Callback(UIView* view) { if (view == nullptr) { return; } UIAbstractScroll* scrollView = static_cast<UIAbstractScroll*>(view); scrollView->isDragging_ = true; curtTime_++; if (curtTime_ <= dragTimes_) { bool needStopX = false; bool needStopY = false; if (startValueY_ != endValueY_) { int16_t actY = scrollView->easingFunc_(startValueY_, endValueY_, curtTime_, dragTimes_); if (!scrollView->DragYInner(actY - previousValueY_)) { needStopY = true; } previousValueY_ = actY; } else { needStopY = true; } if (startValueX_ != endValueX_) { int16_t actX = scrollView->easingFunc_(startValueX_, endValueX_, curtTime_, dragTimes_); if (!scrollView->DragXInner(actX - previousValueX_)) { needStopX = true; } previousValueX_ = actX; } else { needStopX = true; } if (needStopX && needStopY) { scrollView->StopAnimator(); } } else { scrollView->StopAnimator(); } } #if ENABLE_ROTATE_INPUT bool UIAbstractScroll::OnRotateStartEvent(const RotateEvent& event) { isRotating_ = true; if (scrollAnimator_.GetState() != Animator::STOP) { UIAbstractScroll::StopAnimator(); } return UIView::OnRotateStartEvent(event); } bool UIAbstractScroll::OnRotateEvent(const RotateEvent& event) { int16_t rotateLen = static_cast<int16_t>(event.GetRotate() * rotateFactor_); RefreshRotate(rotateLen); if (direction_ == HORIZONTAL) { DragXInner(rotateLen); } else { DragYInner(rotateLen); } return UIView::OnRotateEvent(event); } bool UIAbstractScroll::OnRotateEndEvent(const RotateEvent& event) { InitDelta(); uint8_t dir; int16_t lastRotateLen = GetMaxRotate(); if (direction_ == HORIZONTAL) { dir = (lastRotateLen >= 0) ? DragEvent::DIRECTION_LEFT_TO_RIGHT : DragEvent::DIRECTION_RIGHT_TO_LEFT; } else { dir = (lastRotateLen >= 0) ? DragEvent::DIRECTION_TOP_TO_BOTTOM : DragEvent::DIRECTION_BOTTOM_TO_TOP; } bool triggerAnimator = (MATH_ABS(lastRotateLen) >= rotateThrowthreshold_); if (throwDrag_ && triggerAnimator) { Point current; if (direction_ == HORIZONTAL) { current = {lastRotateLen, 0}; } else { current = {0, lastRotateLen}; } DragThrowAnimator(current, {0, 0}, dir, dragBack_); } else { DragThrowAnimator({0, 0}, {0, 0}, dir, dragBack_); } isRotating_ = false; InitRotate(); return UIView::OnRotateEndEvent(event); } #endif void UIAbstractScroll::SetXScrollBarVisible(bool visible) { if (Screen::GetInstance().GetScreenShape() == ScreenShape::CIRCLE) { return; } else if (visible && xScrollBar_ == nullptr) { xScrollBar_ = new UIBoxScrollBar(); } xScrollBarVisible_ = visible; #if DEFAULT_ANIMATION if (xScrollBarVisible_ && barEaseInOutAnimator_ == nullptr) { barEaseInOutAnimator_ = new BarEaseInOutAnimator(*this); } #endif } void UIAbstractScroll::SetYScrollBarVisible(bool visible) { yScrollBarVisible_ = visible; if (yScrollBarVisible_ && yScrollBar_ == nullptr) { if (Screen::GetInstance().GetScreenShape() == ScreenShape::CIRCLE) { yScrollBar_ = new UIArcScrollBar(); } else { yScrollBar_ = new UIBoxScrollBar(); } } #if DEFAULT_ANIMATION if (yScrollBarVisible_ && barEaseInOutAnimator_ == nullptr) { barEaseInOutAnimator_ = new BarEaseInOutAnimator(*this); } #endif } void UIAbstractScroll::OnPostDraw(BufferInfo& gfxDstBuffer, const Rect& invalidatedArea) { Rect scrollRect = GetRect(); uint8_t opa = GetMixOpaScale(); if (Screen::GetInstance().GetScreenShape() == ScreenShape::RECTANGLE) { if (yScrollBarVisible_) { if (scrollBarSide_ == SCROLL_BAR_RIGHT_SIDE) { yScrollBar_->SetPosition(scrollRect.GetRight() - SCROLL_BAR_WIDTH + 1, scrollRect.GetTop(), SCROLL_BAR_WIDTH, scrollRect.GetHeight()); } else { yScrollBar_->SetPosition(scrollRect.GetLeft(), scrollRect.GetTop(), SCROLL_BAR_WIDTH, scrollRect.GetHeight()); } yScrollBar_->OnDraw(gfxDstBuffer, invalidatedArea, opa); } if (xScrollBarVisible_) { if (scrollBarSide_ == SCROLL_BAR_RIGHT_SIDE) { xScrollBar_->SetPosition(scrollRect.GetLeft(), scrollRect.GetBottom() - SCROLL_BAR_WIDTH + 1, scrollRect.GetWidth() - SCROLL_BAR_WIDTH, SCROLL_BAR_WIDTH); } else { xScrollBar_->SetPosition(scrollRect.GetLeft() + SCROLL_BAR_WIDTH, scrollRect.GetBottom() - SCROLL_BAR_WIDTH + 1, scrollRect.GetWidth() - SCROLL_BAR_WIDTH, SCROLL_BAR_WIDTH); } xScrollBar_->OnDraw(gfxDstBuffer, invalidatedArea, opa); } } else { if (yScrollBarVisible_) { yScrollBar_->SetScrollBarSide(scrollBarSide_); int16_t x; int16_t y; if (scrollBarCenterSetFlag_) { x = scrollRect.GetX() + scrollBarCenter_.x; y = scrollRect.GetY() + scrollBarCenter_.y; } else { x = scrollRect.GetX() + (GetWidth() / 2); // 2: half y = scrollRect.GetY() + (GetHeight() / 2); // 2: half } yScrollBar_->SetPosition(x, y, SCROLL_BAR_WIDTH, GetWidth() / 2); // 2: half yScrollBar_->OnDraw(gfxDstBuffer, invalidatedArea, opa); } } UIView::OnPostDraw(gfxDstBuffer, invalidatedArea); } void UIAbstractScroll::RefreshAnimator() { #if DEFAULT_ANIMATION barEaseInOutAnimator_->RefreshBar(); #endif } } // namespace OHOS