From bc85df04b1f0f5a84a51594f2c05d82465b0c2cf Mon Sep 17 00:00:00 2001 From: nerix Date: Sun, 17 Aug 2025 12:05:52 +0200 Subject: [PATCH] feat: show live indicator in usercard (#6383) --- CHANGELOG.md | 1 + src/CMakeLists.txt | 4 ++ src/widgets/Label.cpp | 50 +++++++--------- src/widgets/Label.hpp | 17 +++--- src/widgets/dialogs/UserInfoPopup.cpp | 34 ++++++++++- src/widgets/dialogs/UserInfoPopup.hpp | 3 + src/widgets/helper/LiveIndicator.cpp | 76 ++++++++++++++++++++++++ src/widgets/helper/LiveIndicator.hpp | 26 ++++++++ src/widgets/helper/ScalingSpacerItem.cpp | 52 ++++++++++++++++ src/widgets/helper/ScalingSpacerItem.hpp | 34 +++++++++++ src/widgets/splits/SplitHeader.cpp | 2 +- 11 files changed, 259 insertions(+), 40 deletions(-) create mode 100644 src/widgets/helper/LiveIndicator.cpp create mode 100644 src/widgets/helper/LiveIndicator.hpp create mode 100644 src/widgets/helper/ScalingSpacerItem.cpp create mode 100644 src/widgets/helper/ScalingSpacerItem.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index ea2f4001..67822ec5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ - Minor: Add a setting under Moderation -> Logs to customize the timestamp used for chat logs. (#6338) - Minor: Add a setting under Moderation -> Logs to use server timestamp from the message instead of the local clock time for logging. (#6346) - Minor: Add feature to search for only zero-width emotes when prepended by `:~`. (#6362) +- Minor: Usercards now show a live indicator if the user is currently streaming. (#6383) - Bugfix: Commands are no longer tab-completable in the middle of messages. (#6273) - Bugfix: Automatic streamer mode detection now works from Flatpak. (#6250) - Bugfix: Don't create native messaging manifest file if browser directory doesn't exist. (#6116) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dea61951..5f25ae5a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -714,12 +714,16 @@ set(SOURCE_FILES widgets/helper/RegExpItemDelegate.hpp widgets/helper/ResizingTextEdit.cpp widgets/helper/ResizingTextEdit.hpp + widgets/helper/ScalingSpacerItem.cpp + widgets/helper/ScalingSpacerItem.hpp widgets/helper/ScrollbarHighlight.cpp widgets/helper/ScrollbarHighlight.hpp widgets/helper/SearchPopup.cpp widgets/helper/SearchPopup.hpp widgets/helper/SettingsDialogTab.cpp widgets/helper/SettingsDialogTab.hpp + widgets/helper/LiveIndicator.cpp + widgets/helper/LiveIndicator.hpp widgets/helper/TableStyles.cpp widgets/helper/TableStyles.hpp widgets/helper/TrimRegExpValidator.cpp diff --git a/src/widgets/Label.cpp b/src/widgets/Label.cpp index 561129ca..074dac53 100644 --- a/src/widgets/Label.cpp +++ b/src/widgets/Label.cpp @@ -15,6 +15,7 @@ Label::Label(BaseWidget *parent, QString text, FontStyle style) : BaseWidget(parent) , text_(std::move(text)) , fontStyle_(style) + , basePadding_(8, 0, 8, 0) { this->connections_.managedConnect(getApp()->getFonts()->fontChanged, [this] { @@ -36,7 +37,7 @@ void Label::setText(const QString &text) if (this->shouldElide_) { this->updateElidedText(this->getFontMetrics(), - this->getInnerWidth()); + this->textRect().width()); } this->updateSize(); this->update(); @@ -59,9 +60,9 @@ void Label::setCentered(bool centered) this->updateSize(); } -void Label::setHasPadding(bool hasPadding) +void Label::setPadding(QMargins padding) { - this->hasPadding_ = hasPadding; + this->basePadding_ = padding; this->updateSize(); } @@ -113,11 +114,8 @@ void Label::paintEvent(QPaintEvent * /*event*/) painter.setFont( getApp()->getFonts()->getFont(this->getFontStyle(), this->scale())); - int padding = this->getPadding(); - // draw text - QRect textRect(this->getPadding(), 0, - static_cast(this->getInnerWidth()), this->height()); + QRectF textRect = this->textRect(); auto text = [this] { if (this->shouldElide_) @@ -128,7 +126,7 @@ void Label::paintEvent(QPaintEvent * /*event*/) return this->text_; }(); - int width = static_cast(metrics.horizontalAdvance(text)); + qreal width = metrics.horizontalAdvance(text); Qt::Alignment alignment = !this->centered_ || width > textRect.width() ? Qt::AlignLeft | Qt::AlignVCenter : Qt::AlignCenter; @@ -157,7 +155,7 @@ void Label::resizeEvent(QResizeEvent *event) if (this->shouldElide_) { auto metrics = this->getFontMetrics(); - if (this->updateElidedText(metrics, this->getInnerWidth())) + if (this->updateElidedText(metrics, this->textRect().width())) { this->update(); } @@ -172,27 +170,26 @@ QFontMetricsF Label::getFontMetrics() const this->scale()); } -qreal Label::getInnerWidth() const -{ - return this->width() - (2 * this->getPadding()); -} - void Label::updateSize() { + this->currentPadding_ = this->basePadding_.toMarginsF() * this->scale(); + auto metrics = this->getFontMetrics(); + auto yPadding = + this->currentPadding_.top() + this->currentPadding_.bottom(); + auto height = metrics.height() + yPadding; if (this->shouldElide_) { - auto height = metrics.height(); - this->updateElidedText(metrics, this->getInnerWidth()); + this->updateElidedText(metrics, this->textRect().width()); this->sizeHint_ = QSizeF(-1, height).toSize(); this->minimumSizeHint_ = this->sizeHint_; } else { - auto width = - metrics.horizontalAdvance(this->text_) + (2 * this->getPadding()); - auto height = metrics.height(); + auto width = metrics.horizontalAdvance(this->text_) + + this->currentPadding_.left() + + this->currentPadding_.right(); this->sizeHint_ = QSizeF(width, height).toSize(); this->minimumSizeHint_ = this->sizeHint_; } @@ -200,16 +197,6 @@ void Label::updateSize() this->updateGeometry(); } -int Label::getPadding() const -{ - if (this->hasPadding_) - { - return static_cast(8 * this->scale()); - } - - return 0; -} - bool Label::updateElidedText(const QFontMetricsF &fontMetrics, qreal width) { assert(this->shouldElide_ == true); @@ -225,4 +212,9 @@ bool Label::updateElidedText(const QFontMetricsF &fontMetrics, qreal width) return false; } +QRectF Label::textRect() const +{ + return this->rect().toRectF().marginsRemoved(this->currentPadding_); +} + } // namespace chatterino diff --git a/src/widgets/Label.hpp b/src/widgets/Label.hpp index 3359dfde..bf068e89 100644 --- a/src/widgets/Label.hpp +++ b/src/widgets/Label.hpp @@ -26,8 +26,7 @@ public: bool getCentered() const; void setCentered(bool centered); - /// Enable or disable horizontal padding - void setHasPadding(bool hasPadding); + void setPadding(QMargins padding); bool getWordWrap() const; void setWordWrap(bool wrap); @@ -46,16 +45,11 @@ protected: private: void updateSize(); - - /// Returns the horizontal padding, or 0 if hasPadding_ is false - int getPadding() const; + QRectF textRect() const; /// Returns the current font style's font metric based on the current scale. QFontMetricsF getFontMetrics() const; - /// Returns the width of this content without padding - qreal getInnerWidth() const; - /// Calculate the new elided text based on text_ /// /// Return true if the elided text changed @@ -65,8 +59,13 @@ private: FontStyle fontStyle_; QSize sizeHint_; QSize minimumSizeHint_; + + /// The user specified padding (scale agnostic) + QMargins basePadding_; + /// The actual scaled padding + QMarginsF currentPadding_; + bool centered_ = false; - bool hasPadding_ = true; bool wordWrap_ = false; bool shouldElide_ = false; /// The text, but elided. Only set if shouldElide_ is true diff --git a/src/widgets/dialogs/UserInfoPopup.cpp b/src/widgets/dialogs/UserInfoPopup.cpp index c5bc193f..5daee0c4 100644 --- a/src/widgets/dialogs/UserInfoPopup.cpp +++ b/src/widgets/dialogs/UserInfoPopup.cpp @@ -32,6 +32,8 @@ #include "widgets/helper/ChannelView.hpp" #include "widgets/helper/InvisibleSizeGrip.hpp" #include "widgets/helper/Line.hpp" +#include "widgets/helper/LiveIndicator.hpp" +#include "widgets/helper/ScalingSpacerItem.hpp" #include "widgets/Label.hpp" #include "widgets/Notebook.hpp" #include "widgets/Scrollbar.hpp" @@ -355,6 +357,13 @@ UserInfoPopup::UserInfoPopup(bool closeAutomatically, Split *split) this->ui_.nameLabel = addCopyableLabel(box, "Copy name"); this->ui_.nameLabel->setFontStyle(FontStyle::UiMediumBold); + this->ui_.nameLabel->setPadding(QMargins(8, 0, 1, 0)); + this->ui_.liveIndicator = new LiveIndicator; + this->ui_.liveIndicator->hide(); + // addCopyableLabel adds the copy button last -> add the indicator before that + box->insertWidget(box->count() - 1, this->ui_.liveIndicator); + box->insertItem(box->count() - 1, + ScalingSpacerItem::horizontal(7)); box->addSpacing(5); box->addStretch(1); @@ -973,6 +982,29 @@ void UserInfoPopup::updateUserData() qCWarning(chatterinoTwitch) << "Error getting followers:" << errorMessage; }); + getHelix()->getStreamById( + user.id, + [this, hack](bool isLive, const auto &stream) { + if (!hack.lock()) + { + return; + } + + if (isLive) + { + this->ui_.liveIndicator->setViewers(stream.viewerCount); + this->ui_.liveIndicator->show(); + } + else + { + this->ui_.liveIndicator->hide(); + } + }, + [id{user.id}]() { + qCWarning(chatterinoWidget) + << "Failed to get stream for user ID" << id; + }, + []() {}); // get ignore state bool isIgnoring = currentUser->blockedUserIds().contains(user.id); @@ -1169,7 +1201,7 @@ UserInfoPopup::TimeoutWidget::TimeoutWidget() title->addStretch(1); auto label = title.emplace