diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f8c167cb1c4d42fbf28b218c9250cd8834e6b2b..c15093adddc25540270e6f12340676d4c6e2b9dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -171,6 +171,7 @@ set(SRC_FILES src/ui/Badge.cc src/ui/LoadingIndicator.cc src/ui/FlatButton.cc + src/ui/FloatingButton.cc src/ui/Label.cc src/ui/OverlayModal.cc src/ui/ScrollBar.cc @@ -224,6 +225,7 @@ qt5_wrap_cpp(MOC_HEADERS include/EmojiItemDelegate.h include/EmojiPanel.h include/EmojiPickButton.h + include/ui/FloatingButton.h include/ImageItem.h include/ImageOverlayDialog.h include/JoinRoomDialog.h diff --git a/include/TimelineView.h b/include/TimelineView.h index 400b0db0ea1d9842d879fb48fc2e7a541f2803d7..8324794824df13f1889ee716ca9ae886c1b53f71 100644 --- a/include/TimelineView.h +++ b/include/TimelineView.h @@ -34,6 +34,8 @@ #include "RoomInfoListItem.h" #include "Text.h" +class FloatingButton; + namespace msgs = matrix::events::messages; namespace events = matrix::events; @@ -155,6 +157,8 @@ private: int oldPosition_; int oldHeight_; + FloatingButton *scrollDownBtn_; + TimelineDirection lastMessageDirection_; // The events currently rendered. Used for duplicate detection. diff --git a/include/ui/FloatingButton.h b/include/ui/FloatingButton.h new file mode 100644 index 0000000000000000000000000000000000000000..91e99ebb3b0479b954b48b00d2b8e02425f98514 --- /dev/null +++ b/include/ui/FloatingButton.h @@ -0,0 +1,26 @@ +#pragma once + +#include "RaisedButton.h" + +constexpr int DIAMETER = 40; +constexpr int ICON_SIZE = 18; + +constexpr int OFFSET_X = 30; +constexpr int OFFSET_Y = 20; + +class FloatingButton : public RaisedButton +{ + Q_OBJECT + +public: + FloatingButton(const QIcon &icon, QWidget *parent = nullptr); + + QSize sizeHint() const override { return QSize(DIAMETER, DIAMETER); }; + QRect buttonGeometry() const; + +protected: + bool event(QEvent *event) override; + bool eventFilter(QObject *obj, QEvent *event) override; + + void paintEvent(QPaintEvent *event) override; +}; diff --git a/resources/icons/ui/angle-arrow-down.png b/resources/icons/ui/angle-arrow-down.png new file mode 100644 index 0000000000000000000000000000000000000000..e40ebca54e3d7c27820e67ec65d850a3bafbfd2a --- /dev/null +++ b/resources/icons/ui/angle-arrow-down.png @@ -0,0 +1,13 @@ +‰PNG + +��� IHDR��� ��� ���szzô���sRGB�®Îé��� pHYs��ê��ê¿/G��diTXtXML:com.adobe.xmp�����<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0"> + <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> + <rdf:Description rdf:about="" + xmlns:xmp="http://ns.adobe.com/xap/1.0/"> + <xmp:CreatorTool>www.inkscape.org</xmp:CreatorTool> + </rdf:Description> + </rdf:RDF> +</x:xmpmeta> +ÜǦm���ïIDATX í”±Â0D³‚øÄ�‰oçCX€‰ >î*NmRBjw²¥«“4õ»:USŠˆDú,0\õS·de±ÆìÝ¡cvÇvÂÚdEf\/Ð룲‡ ÖdmqÈ$;‹ºimâ.Ùé�±-ZT¶2Q‚“Iv[\=LŒÁÉÌÂÚÄ_p9±2Ñ—‰SŽc\&Z;ao5a +—‰ÚãpËÄØq챉þáôáwÄgM¢dâ‰ê” ʦp½AÉ„ Ê.ðZ®ð_&f—LÌ +š¸ar…̾v¯ÍKl¤"¢Ñæ¼&o«x´����IEND®B`‚ \ No newline at end of file diff --git a/resources/icons/ui/angle-arrow-down@2x.png b/resources/icons/ui/angle-arrow-down@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ed095bfea1777f62cf4d99d4832a9371f801bba4 --- /dev/null +++ b/resources/icons/ui/angle-arrow-down@2x.png @@ -0,0 +1,12 @@ +‰PNG + +��� IHDR���@���@���ªiqÞ���sRGB�®Îé��� pHYs��ê��ê¿/G��diTXtXML:com.adobe.xmp�����<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 5.4.0"> + <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> + <rdf:Description rdf:about="" + xmlns:xmp="http://ns.adobe.com/xap/1.0/"> + <xmp:CreatorTool>www.inkscape.org</xmp:CreatorTool> + </rdf:Description> + </rdf:RDF> +</x:xmpmeta> +ÜǦm��*IDATxí˜=NÄ0FƒBB\€Šp*@Ã5(à,GàHÐPÒR±âçû£õz'ö&Ž';–;ëìÆïyâ843`Ì€0fÀ˜3`ÌÀØ 0á³Ä!â-Яñ£SúñŽø@´–ô<#~Ÿˆ+„öB²‰ldv¼"x¢Ä7Úš%pìdÖd\“‚—/i•‚¦ Ç0òâY’¥Ö&!/Ld&{óÔ/_øÂynŽ‘c•qÇj²77=OæÕž }fÞBöfqp;bmJ¸FÔV8&Ž-6v·Ìdÿ+ø›"¡¶Û!%í)¬d^)Z%/&R%L};ä¤ýÚ̼Ô<ááÞ/±öTcê‚L{öëœLà€J•Qá¢V Eàs%Œýt |ßMNëj/p}ëZ2¡èÌûrr$¹Yeµ÷!»ŽS%u;L’öm2JK¨ +^¤¤JÈÝ,U‘öí×”0æfiÒχm;ÎÉ‚uð1´Uð¹Úž„/¾ÉˆMëM3AåÌûÒr$\âG©ÿÉᵪ,©b¯Ù¡¾¤WÚ©%A¼HZ‚*xWBÊf)”òƒ½ÒÊ J×›f‚Ê™÷%çJ˜¼ÈH•0+øT ³„ï+aÖð]¶Þ•p‡yôÝ¢Íu¢xÙ)~Åå÷Ð<ÿ?|D½XvY˘3`Ì€0fÀ˜3`F5ðe‚Æ¢´o����IEND®B`‚ \ No newline at end of file diff --git a/resources/res.qrc b/resources/res.qrc index 59d6559d974413c9c5295d2c65b922589bed2b45..55962275cf143021e9c5af4d7364eb34600837f5 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -22,6 +22,8 @@ <file>icons/ui/paper-clip-outline@2x.png</file> <file>icons/ui/angle-pointing-to-left.png</file> <file>icons/ui/angle-pointing-to-left@2x.png</file> + <file>icons/ui/angle-arrow-down.png</file> + <file>icons/ui/angle-arrow-down@2x.png</file> <file>icons/emoji-categories/people.png</file> <file>icons/emoji-categories/people@2x.png</file> diff --git a/src/TimelineView.cc b/src/TimelineView.cc index 2142f546db85c449d051d3a5cafdc45c517ffb98..132090627108e7fcc2d084c6d9ab17066def431e 100644 --- a/src/TimelineView.cc +++ b/src/TimelineView.cc @@ -27,6 +27,7 @@ #include "MessageEvent.h" #include "MessageEventContent.h" +#include "FloatingButton.h" #include "ImageItem.h" #include "TimelineItem.h" #include "TimelineView.h" @@ -140,6 +141,16 @@ TimelineView::sliderMoved(int position) if (!scroll_area_->verticalScrollBar()->isVisible()) return; + const int maxScroll = scroll_area_->verticalScrollBar()->maximum(); + const int currentScroll = scroll_area_->verticalScrollBar()->value(); + + if (maxScroll - currentScroll > SCROLL_BAR_GAP) { + scrollDownBtn_->show(); + scrollDownBtn_->raise(); + } else { + scrollDownBtn_->hide(); + } + // The scrollbar is high enough so we can start retrieving old events. if (position < SCROLL_BAR_GAP) { if (isTimelineFinished) @@ -376,6 +387,18 @@ TimelineView::init() QSettings settings; local_user_ = settings.value("auth/user_id").toString(); + QIcon icon; + icon.addFile(":/icons/icons/ui/angle-arrow-down.png"); + scrollDownBtn_ = new FloatingButton(icon, this); + scrollDownBtn_->setBackgroundColor(QColor("#F5F5F5")); + scrollDownBtn_->setForegroundColor(QColor("black")); + scrollDownBtn_->hide(); + + connect(scrollDownBtn_, &QPushButton::clicked, this, [=]() { + const int max = scroll_area_->verticalScrollBar()->maximum(); + scroll_area_->verticalScrollBar()->setValue(max); + }); + top_layout_ = new QVBoxLayout(this); top_layout_->setSpacing(0); top_layout_->setMargin(0); diff --git a/src/ui/FloatingButton.cc b/src/ui/FloatingButton.cc new file mode 100644 index 0000000000000000000000000000000000000000..74dcd482ffbf4a9e9fdd8be75438424aed9867e9 --- /dev/null +++ b/src/ui/FloatingButton.cc @@ -0,0 +1,95 @@ +#include <QPainterPath> + +#include "FloatingButton.h" + +FloatingButton::FloatingButton(const QIcon &icon, QWidget *parent) + : RaisedButton(parent) +{ + setFixedSize(DIAMETER, DIAMETER); + setGeometry(buttonGeometry()); + + if (parentWidget()) + parentWidget()->installEventFilter(this); + + setFixedRippleRadius(50); + setIcon(icon); + raise(); +} + +QRect +FloatingButton::buttonGeometry() const +{ + QWidget *parent = parentWidget(); + + if (!parent) + return QRect(); + + return QRect(parent->width() - (OFFSET_X + DIAMETER), + parent->height() - (OFFSET_Y + DIAMETER), + DIAMETER, + DIAMETER); +} + +bool +FloatingButton::event(QEvent *event) +{ + if (!parent()) + return RaisedButton::event(event); + + switch (event->type()) { + case QEvent::ParentChange: { + parent()->installEventFilter(this); + setGeometry(buttonGeometry()); + break; + } + case QEvent::ParentAboutToChange: { + parent()->installEventFilter(this); + break; + } + default: + break; + } + + return RaisedButton::event(event); +} + +bool +FloatingButton::eventFilter(QObject *obj, QEvent *event) +{ + const QEvent::Type type = event->type(); + + if (QEvent::Move == type || QEvent::Resize == type) + setGeometry(buttonGeometry()); + + return RaisedButton::eventFilter(obj, event); +} + +void +FloatingButton::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + + QRect square = QRect(0, 0, DIAMETER, DIAMETER); + square.moveCenter(rect().center()); + + QPainter p(this); + p.setRenderHints(QPainter::Antialiasing); + + QBrush brush; + brush.setStyle(Qt::SolidPattern); + brush.setColor(backgroundColor()); + + p.setBrush(brush); + p.setPen(Qt::NoPen); + p.drawEllipse(square); + + QRect iconGeometry(0, 0, ICON_SIZE, ICON_SIZE); + iconGeometry.moveCenter(square.center()); + + QPixmap pixmap = icon().pixmap(QSize(ICON_SIZE, ICON_SIZE)); + QPainter icon(&pixmap); + icon.setCompositionMode(QPainter::CompositionMode_SourceIn); + icon.fillRect(pixmap.rect(), foregroundColor()); + + p.drawPixmap(iconGeometry, pixmap); +}