From b4a2ced180a9ec7a140a40ca661b28c767567724 Mon Sep 17 00:00:00 2001 From: Leon Richardt Date: Sat, 18 Apr 2020 11:09:22 +0200 Subject: [PATCH] Scroll Using Click-and-Drag with Middle Mouse Button (#1559) * ChannelView: Rename mouse event related members This is more in line with the naming of the other members as well as future members. * ChannelView: Add ability to scroll with middle mouse button * Add scrolling resources * Use custom icons for scroll cursor * Slightly refactor scrolling logic * Respect screen scaling when calculating scroll offset * Nicer scrolling UX This change allows scrolling to be feel smoother when close to the starting point. * Add scrolling with keeping middle mouse pressed This mimics the behavior of browsers as well. * Refactor ChannelView::enableScrolling * Disable drag-scrolling on left or right click --- resources/resources_autogenerated.qrc | 9 +- resources/scrolling/downScroll.png | Bin 0 -> 1157 bytes resources/scrolling/downScroll.svg | 96 +++++++++++++++++++ resources/scrolling/neutralScroll.png | Bin 0 -> 1158 bytes resources/scrolling/neutralScroll.svg | 122 +++++++++++++++++++++++++ resources/scrolling/upScroll.png | Bin 0 -> 1191 bytes resources/scrolling/upScroll.svg | 96 +++++++++++++++++++ src/autogenerated/ResourcesAutogen.cpp | 5 +- src/autogenerated/ResourcesAutogen.hpp | 8 +- src/widgets/helper/ChannelView.cpp | 105 +++++++++++++++++++-- src/widgets/helper/ChannelView.hpp | 20 +++- 11 files changed, 448 insertions(+), 13 deletions(-) create mode 100644 resources/scrolling/downScroll.png create mode 100644 resources/scrolling/downScroll.svg create mode 100644 resources/scrolling/neutralScroll.png create mode 100644 resources/scrolling/neutralScroll.svg create mode 100644 resources/scrolling/upScroll.png create mode 100644 resources/scrolling/upScroll.svg 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 0000000000000000000000000000000000000000..cc46bb5c81af6a7ad4cabdccf43bf07654cf1c08 GIT binary patch literal 1157 zcmV;01bX|4P)2yAgL?UNbS63vR5s5_391h1*lgad; zVz5%FykoUm=VP(hJG$WF5sSsnEEdbWQmMSd2yQeQn|(eXQ7O211cO1}^z`)oax8Z7 z#QgmHBb`pS*3r=+uW<27Boc}4<2e4UL?Q_g1ab1e0mjD0?v0F$JjZdo^;5xbg`y}X z5{bUa<#MiIFj&~2g#dE7oHsQ!WtYihpRDs@VHk!9g+h5O7W?D)_&EDsfaT@oAA5Uy zzyDnDTS+96W`RJ!vDs`d3IRq(NAcm|;b#pE4fUTp004kUBzkyrbK?jE0&frifWg7R z8L?Q5RRazHfMHlee}Dft005#=sj#-TwxMdlZ`In``kh*>MiH%6tCdJ3cdH$sv9VF8 z(P%UXj^mH34ZjtR;|c^tQE4T`KA#VCIvwbAI@sRct|$PX&zIKJ*48#v0#~cmkk97< z0Kjgy!^Xx2^z`&F^5Js1_YgLl-M}<}*=#Pr0RZy(JebX9rZ#{e2p?fw1pq7-i`E<+ zA0(H{T{0~+7z}_Q|7K^i*3oJnDKcQ| z*=+W8O(v6h1pv%;4*)0>3Q#B%jG_NkDwTSL9336`Kg#7J6b^@9A~u`t&v-okw%YKM zlasUc_4PHx>w z^m;udJef>hy4~*I-kY>qtv}JZU@k5$k^oS4EG;e3x}>hIuKrSzN-mf42!ilZ6{pi_ zvc0{%1TK|I$y6#u3HST`K8;4hE4wsCqp>*@3LR3JxLmH10BdV&lCtyWXQ_xAR}3kwThQ*ICdfSH*YiQn&kL19R8xm=RN;gF$F zi0WbDot&I(F2^c)m;eCm?CfMtPfzW2b#+{!Q1}&($Kw=#gQ6&;(`o4J?1Z7A@;AC< zGI>c5#ILinvp?8uws*9;FV@7wgv{-BKZ`^nZ;CrhBobsIktpf-;Nak`%jNoge0;o} z%AfWb+Sk|j#XryU#{z+%y}rJ_2|*A(Ns>U4Waj$%I-bd7_M_40ON+(g@Or%$bbS8? XnZ$=@U}m6900000NkvXXu0mjf9%LG) literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..99ad1e7f221928fbf892c05cb17212556ace9eaa GIT binary patch literal 1158 zcmV;11bO?3P)`Gvk&#@Lt}T|MPq1gX9?r2$aEKxIZ*BG^EjJK2@vLEi##`4gjho5(&l_ zpXGA77pJGEe{dWZ_4#~1GYoSg)caQ1?e+%0-~Tj`NE{=C@arp?OyXoRiLdS_5{YA< z&-c`Bw>R7wtk>)BQ4}@Haonq`fZ}pGop^kFyyP*25au}Un4+l1dcFRhD7ejLs}G06 zp__~mLO33erb1__F+kQdwaVw z6bh~LnPf5<+}hfDEkH|43r?rgydffy$get`?yYM9D2kfp8%7A>xw*M(@Zxg2-MsS% zA-uf2JXhpa004}PjA%MKIvxpmM-&PL%*@P`JSLaRg;+p$cemZ)aQwKiun?C30Qh{q zZ~OcE?SiKNC#|oq|6nv49}xzF;eK;-^TTq&0RWnsnm#v~Olop+a`MZ*zP{md2T-Y0 z%8QGO9ip+Z@jSASF?l}hXG0ymjV zP$(1t0Dz`xSY2I(-rinOK2oW)o*+r``i}ySJRZ*_8~~tDD1gV~5o-e^Nmdb}rvN}A zkqFuoA|jv9pNSo^TCG43|7Isi60BCMXm~!K&sOAexdQ;uh#BnZ>4CMiwaeRXu~?w1 zt4nl%TrT&bBAd-V2LN$z4*+PjTF`2>qM@%VolZX|_V)IoZ{+d@;yCU%!t3?^bZ~I+ zSGnPbhlj^BO+O=o!C-oKch_6)04&SW!C)|50RX@-%#*gZwl8XHYv17;TwGj)latbo zTdh`u#bV)wr&6i2Kp^nVe>N$KdMu<&xZUm>3(5Tayr4p|w6yf(TB>qYvS>6K9vmE0 zmb^7Ko2@<;i}99HhGFhpN-Y-42e&>Sm&?@|kH=X)Go#UX2{)NcLTWk7Tg!`@nwoky z8jb$KV<_gsWZmg>)|XinudJ!5`9!Hy%C4THR;wYC$v|gkCp>&u zy3wUlsk2Zh^!4=g^jBW5_m!Zlzbc2r(G~~@>Eo1T*_RB%d^a{W*2?EE z_&-#q)4e@BJp5ThLqnHJrD|0u6m}K3We1V-UFfGLQ YZ_Qk!_a4R9-T(jq07*qoM6N<$g6=OEM*si- literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4336429782d7df50734b8429c680b9e38bca065d GIT binary patch literal 1191 zcmV;Y1X%ltP)rfH% zOt5gfDc1T0I1VQ{n{&_k|NgnT@7-JuWEm=zs#vSlYHMn08bu;eJ&xm201$CF9E1=$ zO(YU;j*pLDGYs=9MNvO5EG+z$t@fQ^G#aZYih3Rh1ddpiMVB)i4x?~5jIP!LfxwZ? zW_xZl8msONRw|W`W@l$7g2CY1tAKP43=E*b!ND8XSe8Y>VDN|}$qA)W`6ws2!C-je za=9G039~GVwzjqqj^n7Hpa5-dZr<9*vMlm=Jg%Xkp%1fyPfScS`h32?U4ksjqR!4v z1ONyCkXo(2vzKLAwCPty}gr3jH0Lucsl3i<}#Y_dcC^_gQ5CH zctb-&p~K;DW)e9$IYDx{{F;Hfx;hk##WI?3yWOj8ZEZ!@49v{Te3faKWmz;iIe879 zPLs)$ah_#aM3UrWn#%(K=;`T^H8wUHvNx3?R!ntq^gI-TFE)#@)Xl}c4yUtj-uKH&fWwY9aMYBU-Nrqyb-6%`dF z`8H5lSy|fE)zyW`WU^=ZhNnX&lPNHXL{fL3VVBDVI-L%5Ivu#(?t2*!i9~fgKA$hW z4_u?sKq{320067i3QJ2%(AwIXlMasKk1;Nnd;O=7AXUOeRlr9n|affMNf6=W@BA*XwgOkW41yyhI`q0svVqgDovBu(Gmp zvF+X6-O$w3l(T_EBJqY7kH`H0klX120EI#U3WXwP=;esTVt#CIZ|~JRsk}qbH2o5@ zSS&w+IIGUfIpTq2Sdu(TC$C7UYG)-IWc6$s109amL{yGwgyv-Lp8jYSV zE-rox0Du7i5Crl2`uh5J`7*G&y84132p<3dH(g{H=9OG7Z;?u+Rqqq-^?Ka|LG;oz zef~cKhlhvfIF382tE)97B_;3WdfwgL-6II%Gm<3#NY}WR8$2FQOe&TBR$gA-CXq-U z)C9vY0g9r&Fqurg%XRM6Mu)?3P*hZ8EiNv8T2@x}(L=!fe!t6XHh(fcKJLG|cFh0) zz}nhcJQ9gm1p)zHT3Y(Fu&}V;KHz9Hdg^dEz8M)A>9traH-2R|8UO&GY5Lq^vHX%u zCg*uP9*@uGKM{+?S-tW0_xF!BHa32ko}TU<9v=QN6bk))TW{8PXh%oK2me0LpNYj{ zxlkySVi+bu2mwMUes*>iipS$VhGAZkBx!RxoyXbe{sW}ME#j5*y~h9m002ovPDHLk FV1jP@5 + + + + + + + + + + + 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