#include "scrolllistwidget.h" ScrollListWidget::ScrollListWidget(QWidget* parent) : QWidget(parent) { //initialize list container and timer container = new ScrollListContainer(this); container->move(0, 0); container->resize(this->width(), 3); getCord = new QTimer; getCord->setSingleShot(true); rfrshView = new QTimer; getCord->setSingleShot(true); indicator = new ScrollListIndicator(this); indicator->resize(indicator->width(), (int)((double)this->height() * this->height() / (double)container->height())); indicator->move(this->width() - indicator->width() - 3, 0); this->setMouseTracking(true); container->setMouseTracking(true); indicator->setMouseTracking(true); bounce = new QPropertyAnimation(container, "pos"); QObject::connect(getCord, SIGNAL(timeout()), this, SLOT(updateSpd())); QObject::connect(rfrshView, SIGNAL(timeout()), this, SLOT(scrollContainer())); QObject::connect(indicator, SIGNAL(scrollPage(int)), this, SLOT(scrollIndicator(int))); } void ScrollListWidget::paintEvent(QPaintEvent* event) { container->resize(this->width(), container->height()); if (container->height() > this->height() && container->y() < this->height() - container->height() && curSpd == 0 && bounce->state() == QAbstractAnimation::Stopped) container->move(container->x(), this->height() - container->height()); if (container->height() <= this->height()) { container->move(container->x(), 0); indicator->hide(); } else { indicator->show(); } indicator->resize(indicator->width(), (int)((double)this->height() * this->height() / (double)container->height())); indicator->move(this->width() - indicator->width() - 3, -container->y() * this->height() / container->height()); } void ScrollListWidget::mousePressEvent(QMouseEvent* event) { if (container->height() > this->height()) { if (container->y() <= 0 && container->y() + container->height() >= this->height()) pressed = true; lastY = event->pos().y(); } getCord->stop(); rfrshView->stop(); curSpd = 0; outOfEdge = false; moveStored = 0; nextMove = 1; } void ScrollListWidget::mouseMoveEvent(QMouseEvent* event) { setCursor(Qt::ArrowCursor); if (pressed) { //start scroll if (!getCord->isActive() && event->pos().y() - lastY != 0) { //start 30ms timer getCord->start(30); strtY = event->pos().y(); } if (container->y() <= 0 && container->y() + container->height() >= this->height()) container->move(container->x(), container->y() + event->pos().y() - lastY); else { if (!outOfEdge) { bfEdgeY = event->pos().y(); container->move(container->x(), container->y() + event->pos().y() - lastY); outOfEdge = true; } else { int pos = container->y() >= 0 ? 1 : -1; int dp = event->pos().y() - bfEdgeY; if (dp == 0) { outOfEdge = false; nextMove = 1; moveStored = 0; if (container->y() >= 0) container->move(container->x(), 0); else container->move(container->x(), this->height() - container->height()); } else if (dp / abs(dp) != pos) { outOfEdge = false; container->move(container->x(), this->y() + event->pos().y() - bfEdgeY); nextMove = 1; moveStored = 0; } else { while (abs(moveStored) + nextMove <= abs(event->pos().y() - bfEdgeY)) { moveStored += nextMove * pos; container->move(container->x(), container->y() + pos); nextMove++; } while (nextMove > 1 && abs(moveStored) > abs(event->pos().y() - bfEdgeY)) { nextMove--; moveStored -= nextMove * pos; container->move(container->x(), container->y() - pos); } if (moveStored == 0) { outOfEdge = false; if (container->y() >= 0) container->move(container->x(), 0); else container->move(container->x(), this->height() - container->height()); nextMove = 1; moveStored = 0; } } } } lastY = event->pos().y(); } } void ScrollListWidget::mouseReleaseEvent(QMouseEvent* event) { //start scrolling if (container->y() > 0 || container->y() + container->height() < this->height()) bounceBack(); else rfrshView->start(30); pressed = false; } void ScrollListWidget::bounceBack() { rfrshView->stop(); getCord->stop(); bounce->setDuration(500); bounce->setStartValue(container->pos()); if (container->y() > 0) bounce->setEndValue(QPoint(container->x(), 0)); else bounce->setEndValue(QPoint(container->x(), this->height() - container->height())); bounce->setEasingCurve(QEasingCurve::OutQuad); bounce->start(); } void ScrollListWidget::scrollContainer() { //scroll if (curSpd > 0) { if (curSpd > MAXSPEED && !ignoreMaxSpeed) curSpd = MAXSPEED; else if (curSpd <= MAXSPEED) ignoreMaxSpeed = false; int dp = scrollDown ? curSpd : -curSpd; container->move(container->x(), container->y() + dp); } else return; if (container->y() <= 0 && container->y() + container->height() >= this->height()) { curSpd -= damp; curSpd = curSpd < 0 ? 0 : curSpd; } else curSpd /= 2; if (curSpd == 0 && (container->y() > 0 || container->y() + container->height() < this->height())) bounceBack(); else rfrshView->start(30); } void ScrollListWidget::updateSpd() { int spd = lastY - strtY; scrollDown = spd >= 0; strtY = lastY; curSpd = abs(spd); } void ScrollListWidget::addWidget(QWidget* newWidget, bool setAnimation) { newWidget->setParent(container); container->AddWidget(newWidget, setAnimation); } void ScrollListWidget::removeWidget(QWidget* w) { container->RemoveWidget(w); } void ScrollListWidget::scrollToTop() { curSpd = sqrt(8 * (-container->pos().y()) + 2) / 2; scrollDown = true; getCord->stop(); rfrshView->stop(); outOfEdge = false; moveStored = 0; nextMove = 1; ignoreMaxSpeed = true; rfrshView->start(30); } void ScrollListWidget::updateHeight() { container->updateHeight(); } void ScrollListWidget::clear() { container->clear(); } void ScrollListWidget::scrollIndicator(int dp) { int newY = container->y() - dp * container->height() / this->height(); if (newY > 0) newY = 0; else if (newY < this->height() - container->height()) newY = this->height() - container->height(); container->move(container->x(), newY); update(); } void ScrollListWidget::wheelEvent(QWheelEvent* event) { if (container->y() > 0 || container->y() + container->height() < this->height()) return; curSpd += 5; bool newDirection = event->angleDelta().y() > 0; if (newDirection != scrollDown) curSpd = 5; if (curSpd > MAXSPEED) curSpd = MAXSPEED; scrollDown = newDirection; if (!rfrshView->isActive()) rfrshView->start(30); update(); } ScrollListContainer::ScrollListContainer(QWidget* parent) : QWidget(parent) {} void ScrollListContainer::paintEvent(QPaintEvent* event) { for (int i = 0; i < widgets.size(); i++) { widgets[i]->resize(this->width(), widgets[i]->height()); } } void ScrollListContainer::AddWidget(QWidget* widget, bool setAnimation) { //Add animation for all widgets current this->resize(this->width(), this->height() + widget->height() + spacing); widgets.push_back(widget); size++; ys.push_back(0); widget->resize(this->width(), widget->height()); widget->show(); if (setAnimation) { QGraphicsOpacityEffect* widgetOpac = new QGraphicsOpacityEffect(widget); widgetOpac->setOpacity(0); widget->setGraphicsEffect(widgetOpac); QParallelAnimationGroup* dpGroup = new QParallelAnimationGroup; QSequentialAnimationGroup* newWidgetFadeIn = new QSequentialAnimationGroup; for (int i = 0; i < size - 1; i++) { ys[i] += widget->height() + spacing; QPropertyAnimation* move = new QPropertyAnimation(widgets[i], "pos"); move->setDuration(750); move->setStartValue(widgets[i]->pos()); move->setEndValue(QPoint(widgets[i]->x(), ys[i])); move->setEasingCurve(QEasingCurve::InOutQuart); dpGroup->addAnimation(move); } newWidgetFadeIn->addPause(300); QPropertyAnimation* fade = new QPropertyAnimation(widgetOpac, "opacity", widget); fade->setDuration(300); fade->setStartValue(0); fade->setEndValue(0.99); newWidgetFadeIn->addAnimation(fade); dpGroup->addAnimation(newWidgetFadeIn); dpGroup->start(); connect(dpGroup, &QPropertyAnimation::stateChanged, [=]() { if (dpGroup->state() == QAbstractAnimation::Stopped) { if (widgetOpac->opacity() != 0.99) { fade->start(QAbstractAnimation::DeleteWhenStopped); connect(fade, &QPropertyAnimation::finished, [=]() {widgetOpac->deleteLater(); }); } else { dpGroup->deleteLater(); widgetOpac->deleteLater(); } } }); } else { for (int i = 0; i < size - 1; i++) { ys[i] += widget->height() + spacing; widgets[i]->move(QPoint(widgets[i]->pos().x(), ys[i])); } } } void ScrollListContainer::RemoveWidget(QWidget* widget) { int index; if (widget == nullptr) { index = size - 1; if (index != -1) widget = widgets[index]; } else index = widgets.indexOf(widget); if (index == -1 || widget == nullptr) { return; } this->resize(this->width(), this->height() - widget->height() - spacing); this->parentWidget()->update(); widget->hide(); widget->setParent(nullptr); QParallelAnimationGroup* dpGroup = new QParallelAnimationGroup; for (int i = index - 1; i >= 0; i--) { ys[i] -= (widget->height() + spacing); QPropertyAnimation* move = new QPropertyAnimation(widgets[i], "pos"); move->setDuration(750); move->setStartValue(widgets[i]->pos()); move->setEndValue(QPoint(widgets[i]->x(), ys[i])); move->setEasingCurve(QEasingCurve::InOutQuart); dpGroup->addAnimation(move); } dpGroup->start(QAbstractAnimation::DeleteWhenStopped); widgets.remove(index); size--; ys.remove(index); } void ScrollListContainer::updateHeight() { for (int i = size - 2; i >= 0; i--) { ys[i] = ys[i + 1] + widgets[i + 1]->height() + spacing; widgets[i]->move(widgets[i]->pos().x(), ys[i]); } this->resize(this->width(), ys[0] + widgets[0]->height() + 3); } void ScrollListContainer::clear() { int n = size; for (int i = 0; i < n; i++) RemoveWidget(); } ScrollListIndicator::ScrollListIndicator(QWidget* parent) : QWidget(parent) { this->resize(defaultWidth, 0); hovTimer = new QTimer(this); hovTimer->setSingleShot(true); aniPause = new QTimer(this); aniPause->setSingleShot(true); QObject::connect(hovTimer, SIGNAL(timeout()), this, SLOT(setHoverActive())); this->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); this->curColor = defaultColor; this->setMouseTracking(true); } void ScrollListIndicator::paintEvent(QPaintEvent* event) { QPainter painter(this); painter.setPen(Qt::NoPen); painter.setBrush(curColor); painter.drawRect(this->rect()); } void ScrollListIndicator::enterEvent(QEnterEvent* event) { if (!pressed) { hovTimer->start(100); curColor = hoverColor; update(); } } void ScrollListIndicator::leaveEvent(QEvent* event) { hovTimer->stop(); curColor = defaultColor; QPropertyAnimation* narrow = new QPropertyAnimation(this, "geometry"); narrow->setDuration(300); narrow->setStartValue(QRect(this->x(), this->y(), this->width(), this->height())); narrow->setEndValue(QRect(this->parentWidget()->width() - margin - defaultWidth, this->y(), defaultWidth, this->height())); narrow->setEasingCurve(QEasingCurve::InOutQuad); narrow->start(QAbstractAnimation::DeleteWhenStopped); aniPause->start(300); update(); } void ScrollListIndicator::mousePressEvent(QMouseEvent* event) { curColor = pressColor; pressed = true; //>note: globalPos -> globalPosition here due to deprecation //> may cause issues #if (QT_VERSION >= QT_VERSION_CHECK(6,0,0)) lastY = event->globalPosition().y(); #else lastY = event->globalPos().y(); #endif update(); } void ScrollListIndicator::mouseMoveEvent(QMouseEvent* event) { if (pressed && !aniPause->isActive()) { //>note: globalPos -> globalPosition here due to deprecation //> may cause issues #if (QT_VERSION >= QT_VERSION_CHECK(6,0,0)) int dp = event->globalPosition().y() - lastY; #else int dp = event->globalPos().y() - lastY; #endif emit scrollPage(dp); //>note: globalPos -> globalPosition here due to deprecation //> may cause issues #if (QT_VERSION >= QT_VERSION_CHECK(6,0,0)) lastY = event->globalPosition().y(); #else lastY = event->globalPos().y(); #endif } } void ScrollListIndicator::mouseReleaseEvent(QMouseEvent* event) { pressed = false; curColor = hoverColor; update(); } void ScrollListIndicator::setHoverActive() { QPropertyAnimation* widen = new QPropertyAnimation(this, "geometry"); widen->setDuration(300); widen->setStartValue(QRect(this->x(), this->y(), this->width(), this->height())); widen->setEndValue(QRect(this->parentWidget()->width() - margin - defaultWidthAtFocus, this->y(), defaultWidthAtFocus, this->height())); widen->setEasingCurve(QEasingCurve::InOutQuad); widen->start(QAbstractAnimation::DeleteWhenStopped); aniPause->start(300); }