From 0e1edcc58836b8f717ee33398b10b6a0d2e991a2 Mon Sep 17 00:00:00 2001 From: Linloir <3145078758@qq.com> Date: Fri, 16 Dec 2022 21:45:11 +0800 Subject: [PATCH] [UI][ADD] Scroll List widget - transplanted from previous work --- FinalProject/scrolllist.cpp | 0 FinalProject/scrolllist.h | 1 - FinalProject/scrolllistwidget.cpp | 422 ++++++++++++++++++++++++++++++ FinalProject/scrolllistwidget.h | 142 ++++++++++ 4 files changed, 564 insertions(+), 1 deletion(-) delete mode 100644 FinalProject/scrolllist.cpp delete mode 100644 FinalProject/scrolllist.h create mode 100644 FinalProject/scrolllistwidget.cpp create mode 100644 FinalProject/scrolllistwidget.h diff --git a/FinalProject/scrolllist.cpp b/FinalProject/scrolllist.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/FinalProject/scrolllist.h b/FinalProject/scrolllist.h deleted file mode 100644 index 6f70f09..0000000 --- a/FinalProject/scrolllist.h +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/FinalProject/scrolllistwidget.cpp b/FinalProject/scrolllistwidget.cpp new file mode 100644 index 0000000..6d12181 --- /dev/null +++ b/FinalProject/scrolllistwidget.cpp @@ -0,0 +1,422 @@ +#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); +} diff --git a/FinalProject/scrolllistwidget.h b/FinalProject/scrolllistwidget.h new file mode 100644 index 0000000..4ace7a1 --- /dev/null +++ b/FinalProject/scrolllistwidget.h @@ -0,0 +1,142 @@ +/* This Module has not yet been rewritten */ +/* Behavior and performance is not guaranteed*/ +/* @Linloir */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAXSPEED 70 + +class ScrollListWidget; +class ScrollListContainer; +class ScrollListIndicator; + +class ScrollListWidget : public QWidget +{ + Q_OBJECT + +private: + QTimer* getCord; + QTimer* rfrshView; + + ScrollListContainer* container; + ScrollListIndicator* indicator; + + QPropertyAnimation* bounce; + + bool pressed = false; + bool scrollDown = true; + bool outOfEdge = false; + bool ignoreMaxSpeed = false; + + int strtY; + int lastY; + int bfEdgeY; //last y value before out of edge + + int curSpd = 0; + int damp = 1; + int moveStored = 0; + int nextMove = 1; + + void paintEvent(QPaintEvent* event); + void mousePressEvent(QMouseEvent* event); + void mouseMoveEvent(QMouseEvent* event); + void mouseReleaseEvent(QMouseEvent* event); + void wheelEvent(QWheelEvent* event); + void bounceBack(); + + +public: + explicit ScrollListWidget(QWidget* parent = nullptr); + void addWidget(QWidget* newWidget, bool setAnimation = true); + void addWidgets(QVector widgets) { for (int i = 0; i < widgets.size(); i++)addWidget(widgets[i], false); } + void removeWidget(QWidget* w = nullptr); + void scrollToTop(); + void updateHeight(); + void clear(); + +signals: + +private slots: + void scrollContainer(); + void updateSpd(); + void scrollIndicator(int dp); + +}; + +class ScrollListContainer : public QWidget +{ + Q_OBJECT +public: + explicit ScrollListContainer(QWidget* parent = nullptr); + void AddWidget(QWidget* widget, bool setAnimation = true); + void RemoveWidget(QWidget* widget = nullptr); + void updateHeight(); + void clear(); + //void RemoveWidget(QWidget* widget); + +private: + //QTimer* newWidgetFade; + int spacing = 3; + QVector widgets; + int size = 0; + QVector ys; + + void paintEvent(QPaintEvent* event); + +signals: + +private slots: + +}; + +class ScrollListIndicator : public QWidget +{ + Q_OBJECT + +private: + QColor curColor; + QColor defaultColor = QColor(100, 100, 100, 130); + QColor hoverColor = QColor(70, 70, 70, 150); + QColor pressColor = QColor(50, 50, 50, 170); + + QTimer* hovTimer; + QTimer* aniPause; + + int lastY; + + int defaultWidth = 2; + int defaultWidthAtFocus = 9; + int margin = 3; + + bool pressed = false; + +public: + explicit ScrollListIndicator(QWidget* parent = nullptr); + +private: + void paintEvent(QPaintEvent* event); + void enterEvent(QEnterEvent* event); + void leaveEvent(QEvent* event); + void mousePressEvent(QMouseEvent* event); + void mouseMoveEvent(QMouseEvent* event); + void mouseReleaseEvent(QMouseEvent* event); + +signals: + void scrollPage(int); + +private slots: + void setHoverActive(); + +};