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 @@
+
+
+
+
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 @@
+
+
+
+
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 @@
+
+
+
+
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