diff --git a/resources/resources_autogenerated.qrc b/resources/resources_autogenerated.qrc index dbb639bbc..4fe4cf72e 100644 --- a/resources/resources_autogenerated.qrc +++ b/resources/resources_autogenerated.qrc @@ -28,8 +28,9 @@ buttons/unmod.png buttons/update.png buttons/updateError.png - com.chatterino.chatterino.desktop chatterino.icns + com.chatterino.chatterino.appdata.xml + com.chatterino.chatterino.desktop contributors.txt emoji.json emojidata.txt @@ -50,6 +51,12 @@ licenses/websocketpp.txt pajaDank.png qss/settings.qss + scrolling/downScroll.png + scrolling/downScroll.svg + scrolling/neutralScroll.png + scrolling/neutralScroll.svg + scrolling/upScroll.png + scrolling/upScroll.svg settings/about.svg settings/aboutlogo.png settings/accounts.svg diff --git a/resources/scrolling/downScroll.png b/resources/scrolling/downScroll.png new file mode 100644 index 000000000..cc46bb5c8 Binary files /dev/null and b/resources/scrolling/downScroll.png differ diff --git a/resources/scrolling/downScroll.svg b/resources/scrolling/downScroll.svg new file mode 100644 index 000000000..d2e8992ac --- /dev/null +++ b/resources/scrolling/downScroll.svg @@ -0,0 +1,96 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/resources/scrolling/neutralScroll.png b/resources/scrolling/neutralScroll.png new file mode 100644 index 000000000..99ad1e7f2 Binary files /dev/null and b/resources/scrolling/neutralScroll.png differ diff --git a/resources/scrolling/neutralScroll.svg b/resources/scrolling/neutralScroll.svg new file mode 100644 index 000000000..6ad76c8ca --- /dev/null +++ b/resources/scrolling/neutralScroll.svg @@ -0,0 +1,122 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/resources/scrolling/upScroll.png b/resources/scrolling/upScroll.png new file mode 100644 index 000000000..433642978 Binary files /dev/null and b/resources/scrolling/upScroll.png differ diff --git a/resources/scrolling/upScroll.svg b/resources/scrolling/upScroll.svg new file mode 100644 index 000000000..d25c3f10a --- /dev/null +++ b/resources/scrolling/upScroll.svg @@ -0,0 +1,96 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/src/autogenerated/ResourcesAutogen.cpp b/src/autogenerated/ResourcesAutogen.cpp index e7aab5d55..b0e5e6ce8 100644 --- a/src/autogenerated/ResourcesAutogen.cpp +++ b/src/autogenerated/ResourcesAutogen.cpp @@ -30,6 +30,9 @@ Resources2::Resources2() this->error = QPixmap(":/error.png"); this->icon = QPixmap(":/icon.png"); this->pajaDank = QPixmap(":/pajaDank.png"); + this->scrolling.downScroll = QPixmap(":/scrolling/downScroll.png"); + this->scrolling.neutralScroll = QPixmap(":/scrolling/neutralScroll.png"); + this->scrolling.upScroll = QPixmap(":/scrolling/upScroll.png"); this->settings.aboutlogo = QPixmap(":/settings/aboutlogo.png"); this->split.down = QPixmap(":/split/down.png"); this->split.left = QPixmap(":/split/left.png"); @@ -50,4 +53,4 @@ Resources2::Resources2() this->twitch.vip = QPixmap(":/twitch/vip.png"); } -} // namespace chatterino +} // namespace chatterino \ No newline at end of file diff --git a/src/autogenerated/ResourcesAutogen.hpp b/src/autogenerated/ResourcesAutogen.hpp index cb3423631..cbd6c0bfb 100644 --- a/src/autogenerated/ResourcesAutogen.hpp +++ b/src/autogenerated/ResourcesAutogen.hpp @@ -1,5 +1,4 @@ #include - #include "common/Singleton.hpp" namespace chatterino { @@ -39,6 +38,11 @@ public: QPixmap error; QPixmap icon; QPixmap pajaDank; + struct { + QPixmap downScroll; + QPixmap neutralScroll; + QPixmap upScroll; + } scrolling; struct { QPixmap aboutlogo; } settings; @@ -65,4 +69,4 @@ public: } twitch; }; -} // namespace chatterino +} // namespace chatterino \ No newline at end of file diff --git a/src/widgets/helper/ChannelView.cpp b/src/widgets/helper/ChannelView.cpp index 4fab7a689..ff29dc1aa 100644 --- a/src/widgets/helper/ChannelView.cpp +++ b/src/widgets/helper/ChannelView.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,7 @@ #include "messages/layouts/MessageLayoutElement.hpp" #include "providers/twitch/TwitchChannel.hpp" #include "providers/twitch/TwitchIrcServer.hpp" +#include "singletons/Resources.hpp" #include "singletons/Settings.hpp" #include "singletons/Theme.hpp" #include "singletons/TooltipPreviewImage.hpp" @@ -114,6 +116,10 @@ ChannelView::ChannelView(BaseWidget *parent) this->initializeScrollbar(); this->initializeSignals(); + this->cursors_.neutral = QCursor(getResources().scrolling.neutralScroll); + this->cursors_.up = QCursor(getResources().scrolling.upScroll); + this->cursors_.down = QCursor(getResources().scrolling.downScroll); + this->pauseTimer_.setSingleShot(true); QObject::connect(&this->pauseTimer_, &QTimer::timeout, this, [this] { /// remove elements that are finite @@ -131,6 +137,10 @@ ChannelView::ChannelView(BaseWidget *parent) this->clickTimer_->setSingleShot(true); this->clickTimer_->setInterval(500); + this->scrollTimer_.setInterval(20); + QObject::connect(&this->scrollTimer_, &QTimer::timeout, this, + &ChannelView::scrollUpdateRequested); + this->setFocusPolicy(Qt::FocusPolicy::StrongFocus); } @@ -1060,8 +1070,13 @@ void ChannelView::mouseMoveEvent(QMouseEvent *event) return; } + if (this->isScrolling_) + { + this->currentMousePosition_ = event->screenPos(); + } + // is selecting - if (this->isMouseDown_) + if (this->isLeftMouseDown_) { // this->pause(PauseReason::Selecting, 300); int index = layout->getSelectionIndex(relativePos); @@ -1326,8 +1341,11 @@ void ChannelView::mousePressEvent(QMouseEvent *event) switch (event->button()) { case Qt::LeftButton: { - this->lastPressPosition_ = event->screenPos(); - this->isMouseDown_ = true; + if (this->isScrolling_) + this->disableScrolling(); + + this->lastLeftPressPosition_ = event->screenPos(); + this->isLeftMouseDown_ = true; if (layout->flags.has(MessageLayoutFlag::Collapsed)) return; @@ -1344,11 +1362,22 @@ void ChannelView::mousePressEvent(QMouseEvent *event) break; case Qt::RightButton: { + if (this->isScrolling_) + this->disableScrolling(); + this->lastRightPressPosition_ = event->screenPos(); this->isRightMouseDown_ = true; } break; + case Qt::MiddleButton: { + if (this->isScrolling_) + this->disableScrolling(); + else + this->enableScrolling(event->screenPos()); + } + break; + default:; } @@ -1373,11 +1402,11 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event) return; } } - else if (this->isMouseDown_) + else if (this->isLeftMouseDown_) { - this->isMouseDown_ = false; + this->isLeftMouseDown_ = false; - if (fabsf(distanceBetweenPoints(this->lastPressPosition_, + if (fabsf(distanceBetweenPoints(this->lastLeftPressPosition_, event->screenPos())) > 15.f) { return; @@ -1405,6 +1434,13 @@ void ChannelView::mouseReleaseEvent(QMouseEvent *event) return; } } + else if (event->button() == Qt::MiddleButton) + { + if (event->screenPos() == this->lastMiddlePressPosition_) + this->enableScrolling(event->screenPos()); + else + this->disableScrolling(); + } else { // not left or right button @@ -1638,7 +1674,7 @@ void ChannelView::mouseDoubleClickEvent(QMouseEvent *event) return; } - if (!this->isMouseDown_) + if (!this->isLeftMouseDown_) { this->isDoubleClick_ = true; @@ -1803,4 +1839,59 @@ void ChannelView::getWordBounds(MessageLayout *layout, wordEnd = wordStart + length; } +void ChannelView::enableScrolling(const QPointF &scrollStart) +{ + this->isScrolling_ = true; + this->lastMiddlePressPosition_ = scrollStart; + // The line below prevents a sudden jerk at the beginning + this->currentMousePosition_ = scrollStart; + + this->scrollTimer_.start(); + + if (!QGuiApplication::overrideCursor()) + QGuiApplication::setOverrideCursor(this->cursors_.neutral); +} + +void ChannelView::disableScrolling() +{ + this->isScrolling_ = false; + this->scrollTimer_.stop(); + QGuiApplication::restoreOverrideCursor(); +} + +void ChannelView::scrollUpdateRequested() +{ + const qreal dpi = + QGuiApplication::screenAt(this->pos())->devicePixelRatio(); + const qreal delta = dpi * (this->currentMousePosition_.y() - + this->lastMiddlePressPosition_.y()); + const int cursorHeight = this->cursors_.neutral.pixmap().height(); + + if (fabs(delta) <= cursorHeight * dpi) + { + /* + * If within an area close to the initial position, don't do any + * scrolling at all. + */ + QGuiApplication::changeOverrideCursor(this->cursors_.neutral); + return; + } + + qreal offset; + if (delta > 0) + { + QGuiApplication::changeOverrideCursor(this->cursors_.down); + offset = delta - cursorHeight; + } + else + { + QGuiApplication::changeOverrideCursor(this->cursors_.up); + offset = delta + cursorHeight; + } + + // "Good" feeling multiplier found by trial-and-error + const qreal multiplier = qreal(0.02); + this->scrollBar_->offset(multiplier * offset); +} + } // namespace chatterino diff --git a/src/widgets/helper/ChannelView.hpp b/src/widgets/helper/ChannelView.hpp index 6ea7659e3..3b02dfdd8 100644 --- a/src/widgets/helper/ChannelView.hpp +++ b/src/widgets/helper/ChannelView.hpp @@ -148,6 +148,9 @@ private: void updatePauses(); void unpaused(); + void enableScrolling(const QPointF &scrollStart); + void disableScrolling(); + QTimer *layoutCooldown_; bool layoutQueued_; @@ -183,15 +186,26 @@ private: bool onlyUpdateEmotes_ = false; // Mouse event variables - bool isMouseDown_ = false; + bool isLeftMouseDown_ = false; bool isRightMouseDown_ = false; bool isDoubleClick_ = false; DoubleClickSelection doubleClickSelection_; - QPointF lastPressPosition_; + QPointF lastLeftPressPosition_; QPointF lastRightPressPosition_; QPointF lastDClickPosition_; QTimer *clickTimer_; + bool isScrolling_ = false; + QPointF lastMiddlePressPosition_; + QPointF currentMousePosition_; + QTimer scrollTimer_; + + struct { + QCursor neutral; + QCursor up; + QCursor down; + } cursors_; + Selection selection_; bool selecting_ = false; @@ -211,6 +225,8 @@ private slots: queueLayout(); update(); } + + void scrollUpdateRequested(); }; } // namespace chatterino