feat: show live indicator in usercard (#6383)
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
+21
-29
@@ -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<int>(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<int>(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<int>(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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Label>(text);
|
||||
label->setStyleSheet("color: #BBB");
|
||||
label->setHasPadding(false);
|
||||
label->setPadding(QMargins{});
|
||||
title->addStretch(1);
|
||||
|
||||
auto hbox = vbox.emplace<QHBoxLayout>().withoutMargin();
|
||||
|
||||
@@ -18,6 +18,7 @@ class ChannelView;
|
||||
class Split;
|
||||
class LabelButton;
|
||||
class PixmapButton;
|
||||
class LiveIndicator;
|
||||
|
||||
class UserInfoPopup final : public DraggablePopup
|
||||
{
|
||||
@@ -85,6 +86,8 @@ private:
|
||||
Label *followageLabel = nullptr;
|
||||
Label *subageLabel = nullptr;
|
||||
|
||||
LiveIndicator *liveIndicator = nullptr;
|
||||
|
||||
QCheckBox *block = nullptr;
|
||||
QCheckBox *ignoreHighlights = nullptr;
|
||||
Label *notesPreview = nullptr;
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
#include "widgets/helper/LiveIndicator.hpp"
|
||||
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "util/Helpers.hpp"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QString>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
using namespace Qt::Literals;
|
||||
|
||||
LiveIndicator::LiveIndicator(QWidget *parent)
|
||||
: BaseWidget(parent)
|
||||
{
|
||||
this->setMinimumHeight(5); // fixed min height for the circle to fit
|
||||
this->setMouseTracking(true); // for hover and tooltip
|
||||
this->updateScale();
|
||||
}
|
||||
|
||||
void LiveIndicator::setViewers(int viewers)
|
||||
{
|
||||
this->setToolTip(u"Live with %1 viewers"_s.arg(localizeNumbers(viewers)));
|
||||
this->updateScale();
|
||||
}
|
||||
|
||||
void LiveIndicator::scaleChangedEvent(float /*newScale*/)
|
||||
{
|
||||
this->updateScale();
|
||||
}
|
||||
|
||||
void LiveIndicator::paintEvent(QPaintEvent * /*event*/)
|
||||
{
|
||||
QPainter painter(this);
|
||||
QColor color = getTheme()->tabs.liveIndicator;
|
||||
// Indicate that there's a tooltip here
|
||||
if (this->hovered)
|
||||
{
|
||||
if (getTheme()->isLightTheme())
|
||||
{
|
||||
color = color.darker(150);
|
||||
}
|
||||
else
|
||||
{
|
||||
color = color.lighter(150);
|
||||
}
|
||||
}
|
||||
|
||||
painter.setBrush(color);
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
painter.drawEllipse(QRect{
|
||||
QPoint{0, 0},
|
||||
QSize{5, 5} * this->scale(),
|
||||
});
|
||||
}
|
||||
|
||||
void LiveIndicator::enterEvent(QEnterEvent * /*event*/)
|
||||
{
|
||||
this->hovered = true;
|
||||
this->update();
|
||||
}
|
||||
void LiveIndicator::leaveEvent(QEvent * /*event*/)
|
||||
{
|
||||
this->hovered = false;
|
||||
this->update();
|
||||
}
|
||||
|
||||
void LiveIndicator::updateScale()
|
||||
{
|
||||
this->setFixedWidth(qRound(6 * this->scale()));
|
||||
|
||||
this->update();
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "widgets/BaseWidget.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class LiveIndicator : public BaseWidget
|
||||
{
|
||||
public:
|
||||
LiveIndicator(QWidget *parent = nullptr);
|
||||
|
||||
void setViewers(int viewers);
|
||||
|
||||
protected:
|
||||
void scaleChangedEvent(float newScale) override;
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void enterEvent(QEnterEvent *event) override;
|
||||
void leaveEvent(QEvent *event) override;
|
||||
|
||||
private:
|
||||
void updateScale();
|
||||
|
||||
bool hovered = false;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,52 @@
|
||||
#include "widgets/helper/ScalingSpacerItem.hpp"
|
||||
|
||||
#include "singletons/Settings.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
ScalingSpacerItem *ScalingSpacerItem::horizontal(int baseWidth)
|
||||
{
|
||||
return new ScalingSpacerItem({baseWidth, 0}, QSizePolicy::Fixed,
|
||||
QSizePolicy::Minimum);
|
||||
}
|
||||
|
||||
ScalingSpacerItem *ScalingSpacerItem::vertical(int baseHeight)
|
||||
{
|
||||
return new ScalingSpacerItem({0, baseHeight}, QSizePolicy::Minimum,
|
||||
QSizePolicy::Fixed);
|
||||
}
|
||||
|
||||
ScalingSpacerItem::ScalingSpacerItem(QSize baseSize, QSizePolicy::Policy horiz,
|
||||
QSizePolicy::Policy vert)
|
||||
: QSpacerItem(
|
||||
qRound(static_cast<float>(baseSize.width()) * getSettings()->uiScale),
|
||||
qRound(static_cast<float>(baseSize.height()) *
|
||||
getSettings()->uiScale),
|
||||
horiz, vert)
|
||||
, baseSize(baseSize)
|
||||
, scaledSize(baseSize * getSettings()->uiScale)
|
||||
, horizontalPolicy(horiz)
|
||||
, verticalPolicy(vert)
|
||||
{
|
||||
getSettings()->uiScale.connect(
|
||||
[this] {
|
||||
this->refresh();
|
||||
},
|
||||
this->connections, false);
|
||||
}
|
||||
|
||||
void ScalingSpacerItem::refresh()
|
||||
{
|
||||
auto nextSize = this->baseSize * getSettings()->uiScale;
|
||||
if (nextSize == this->scaledSize)
|
||||
{
|
||||
return;
|
||||
}
|
||||
this->scaledSize = nextSize;
|
||||
|
||||
this->changeSize(this->scaledSize.width(), this->scaledSize.height(),
|
||||
this->horizontalPolicy, this->verticalPolicy);
|
||||
this->invalidate();
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <pajlada/signals/scoped-connection.hpp>
|
||||
#include <QSpacerItem>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
/// A QSpacerItem that scales with Chatterino's scale
|
||||
class ScalingSpacerItem : public QSpacerItem
|
||||
{
|
||||
public:
|
||||
static ScalingSpacerItem *horizontal(int baseWidth);
|
||||
static ScalingSpacerItem *vertical(int baseHeight);
|
||||
|
||||
private:
|
||||
ScalingSpacerItem(QSize baseSize, QSizePolicy::Policy horiz,
|
||||
QSizePolicy::Policy vert);
|
||||
|
||||
void refresh();
|
||||
|
||||
QSize baseSize;
|
||||
QSize scaledSize;
|
||||
|
||||
QSizePolicy::Policy horizontalPolicy;
|
||||
QSizePolicy::Policy verticalPolicy;
|
||||
|
||||
std::vector<std::unique_ptr<pajlada::Signals::ScopedConnection>>
|
||||
connections;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -315,7 +315,7 @@ void SplitHeader::initializeLayout()
|
||||
w->setSizePolicy(QSizePolicy::MinimumExpanding,
|
||||
QSizePolicy::Preferred);
|
||||
w->setCentered(true);
|
||||
w->setHasPadding(false);
|
||||
w->setPadding(QMargins{});
|
||||
}),
|
||||
// space
|
||||
makeWidget<BaseWidget>([](auto w) {
|
||||
|
||||
Reference in New Issue
Block a user