* 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();
}
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();
} 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);
y = scrollRect.GetY() + (GetHeight() / 2);
}
yScrollBar_->SetPosition(x, y, SCROLL_BAR_WIDTH, GetWidth() / 2);
yScrollBar_->OnDraw(gfxDstBuffer, invalidatedArea, opa);
}
}
UIView::OnPostDraw(gfxDstBuffer, invalidatedArea);
}
void UIAbstractScroll::RefreshAnimator()
{
#if DEFAULT_ANIMATION
barEaseInOutAnimator_->RefreshBar();
#endif
}
}