From ce2c31424c8ff6b723c12133de418f1bb7e3859d Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Wed, 12 Aug 2026 15:52:49 +0200 Subject: [PATCH] [Chat] Scroll chat view to bottom when loading chat history (#7079) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Chat] Scroll chat view to bottom when loading chat history (#2725) Took 4 minutes Took 38 seconds * Harden scrolling to bottom Took 11 minutes * Implement stick-to-bottom flag --------- Co-authored-by: Lukas BrĂ¼bach --- .../widgets/server/chat_view/chat_view.cpp | 35 +++++++++++++++++-- .../widgets/server/chat_view/chat_view.h | 4 +++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp b/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp index e97d25e64..869df4cf3 100644 --- a/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp +++ b/cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -51,6 +52,9 @@ ChatView::ChatView(TabSupervisor *_tabSupervisor, AbstractGame *_game, bool _sho setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse); setOpenLinks(false); connect(this, &ChatView::anchorClicked, this, &ChatView::openLink); + + connect(verticalScrollBar(), &QScrollBar::rangeChanged, this, &ChatView::onScrollBarRangeChanged); + connect(verticalScrollBar(), &QScrollBar::valueChanged, this, &ChatView::onScrollBarValueChanged); } void ChatView::adjustColorsToPalette() @@ -151,7 +155,7 @@ void ChatView::appendHtml(const QString &html) bool atBottom = verticalScrollBar()->value() >= verticalScrollBar()->maximum(); prepareBlock().insertHtml(html); if (atBottom) { - verticalScrollBar()->setValue(verticalScrollBar()->maximum()); + scrollToBottom(); } } @@ -169,7 +173,7 @@ void ChatView::appendHtmlServerMessage(const QString &html, bool optionalIsBold, prepareBlock().insertHtml(htmlText); if (atBottom) { - verticalScrollBar()->setValue(verticalScrollBar()->maximum()); + scrollToBottom(); } } @@ -338,11 +342,36 @@ void ChatView::appendMessage(QString message, } } - if (atBottom) { + // ChatHistory messages are only ever sent once per room, right after joining, before the user can + // interact with the view. Always scroll to the bottom so the whole history is visible on join. + if (atBottom || messageType.testFlag(Event_RoomSay::ChatHistory)) { + scrollToBottom(); + } +} + +void ChatView::scrollToBottom() +{ + // The document layout, and therefore the scrollbar range, may be updated asynchronously (e.g. while + // the chat history is loaded into a view that has not been laid out yet). Setting the value once is + // not enough: keep stickToBottom set so any later range change scrolls to the new maximum as well. + stickToBottom = true; + verticalScrollBar()->setValue(verticalScrollBar()->maximum()); +} + +void ChatView::onScrollBarRangeChanged() +{ + if (stickToBottom) { verticalScrollBar()->setValue(verticalScrollBar()->maximum()); } } +void ChatView::onScrollBarValueChanged(int value) +{ + if (value < verticalScrollBar()->maximum()) { + stickToBottom = false; + } +} + void ChatView::checkTag(QTextCursor &cursor, QString &message) { if (message.startsWith("[card]")) { diff --git a/cockatrice/src/interface/widgets/server/chat_view/chat_view.h b/cockatrice/src/interface/widgets/server/chat_view/chat_view.h index 8d5894613..646aa6a80 100644 --- a/cockatrice/src/interface/widgets/server/chat_view/chat_view.h +++ b/cockatrice/src/interface/widgets/server/chat_view/chat_view.h @@ -60,6 +60,7 @@ private: QStringList highlightedWords; bool evenNumber; bool showTimestamps; + bool stickToBottom = false; HoveredItemType hoveredItemType; QString hoveredContent; QAction *messageClicked; @@ -67,6 +68,7 @@ private: [[nodiscard]] QTextFragment getFragmentUnderMouse(const QPoint &pos) const; QTextCursor prepareBlock(bool same = false); + void scrollToBottom(); void appendCardTag(QTextCursor &cursor, const QString &cardName); void appendUrlTag(QTextCursor &cursor, QString url); static QColor getCustomMentionColor(); @@ -88,6 +90,8 @@ private slots: void actMessageClicked(); void adjustColorsToPalette(); void refreshBlockColors(); + void onScrollBarRangeChanged(); + void onScrollBarValueChanged(int value); public: ChatView(TabSupervisor *_tabSupervisor, AbstractGame *_game, bool _showTimestamps, QWidget *parent = nullptr);