From 8a2658e4fcf0f6e8559057d05152982c21a90e41 Mon Sep 17 00:00:00 2001 From: blue Date: Sun, 9 Jan 2022 01:28:29 +0300 Subject: [PATCH] message bubbles, avatar rounding, roster adjusments --- ui/squawk.cpp | 6 +- ui/squawk.ui | 23 +++- ui/widgets/conversation.cpp | 24 +++- ui/widgets/conversation.h | 8 ++ ui/widgets/messageline/feedview.cpp | 4 - ui/widgets/messageline/messagedelegate.cpp | 133 +++++++++++++++------ ui/widgets/messageline/messagedelegate.h | 9 +- 7 files changed, 158 insertions(+), 49 deletions(-) diff --git a/ui/squawk.cpp b/ui/squawk.cpp index 406ee45..800f02a 100644 --- a/ui/squawk.cpp +++ b/ui/squawk.cpp @@ -39,7 +39,11 @@ Squawk::Squawk(QWidget *parent) : m_ui->setupUi(this); m_ui->roster->setModel(&rosterModel); m_ui->roster->setContextMenuPolicy(Qt::CustomContextMenu); - m_ui->roster->setColumnWidth(1, 30); + if (QApplication::style()->styleHint(QStyle::SH_ScrollBar_Transient) == 1) { + m_ui->roster->setColumnWidth(1, 52); + } else { + m_ui->roster->setColumnWidth(1, 26); + } m_ui->roster->setIconSize(QSize(20, 20)); m_ui->roster->header()->setStretchLastSection(false); m_ui->roster->header()->setSectionResizeMode(0, QHeaderView::Stretch); diff --git a/ui/squawk.ui b/ui/squawk.ui index a4d0258..01ffbba 100644 --- a/ui/squawk.ui +++ b/ui/squawk.ui @@ -73,6 +73,9 @@ 0 + + 0 + @@ -96,16 +99,31 @@ true + + true + false false + + 10 + + + false + + + + 0 + 30 + + false @@ -115,6 +133,9 @@ -1 + + true + @@ -162,7 +183,7 @@ 0 0 718 - 27 + 32 diff --git a/ui/widgets/conversation.cpp b/ui/widgets/conversation.cpp index ea2f722..69eac19 100644 --- a/ui/widgets/conversation.cpp +++ b/ui/widgets/conversation.cpp @@ -25,12 +25,16 @@ #include #include #include -#include #include #include #include #include #include +#include + +#include + +constexpr QSize avatarSize(50, 50); Conversation::Conversation(bool muc, Models::Account* acc, Models::Element* el, const QString pJid, const QString pRes, QWidget* parent): QWidget(parent), @@ -348,11 +352,25 @@ void Conversation::onClearButton() void Conversation::setAvatar(const QString& path) { + QPixmap pixmap; if (path.size() == 0) { - m_ui->avatar->setPixmap(Shared::icon("user", true).pixmap(QSize(50, 50))); + pixmap = Shared::icon("user", true).pixmap(avatarSize); } else { - m_ui->avatar->setPixmap(path); + pixmap = QPixmap(path).scaled(avatarSize); } + + + QPixmap result(avatarSize); + result.fill(Qt::transparent); + QPainter painter(&result); + painter.setRenderHint(QPainter::Antialiasing); + painter.setRenderHint(QPainter::SmoothPixmapTransform); + QPainterPath maskPath; + maskPath.addEllipse(0, 0, avatarSize.width(), avatarSize.height()); + painter.setClipPath(maskPath); + painter.drawPixmap(0, 0, pixmap); + + m_ui->avatar->setPixmap(result); } void Conversation::onTextEditDocSizeChanged(const QSizeF& size) diff --git a/ui/widgets/conversation.h b/ui/widgets/conversation.h index 6b5b4bb..a758b2c 100644 --- a/ui/widgets/conversation.h +++ b/ui/widgets/conversation.h @@ -141,6 +141,14 @@ protected: ShadowOverlay shadow; QMenu* contextMenu; + +private: + static bool painterInitialized; + static QPainterPath* avatarMask; + static QPixmap* avatarPixmap; + static QPainter* avatarPainter; + + }; #endif // CONVERSATION_H diff --git a/ui/widgets/messageline/feedview.cpp b/ui/widgets/messageline/feedview.cpp index 1296324..d83ca6b 100644 --- a/ui/widgets/messageline/feedview.cpp +++ b/ui/widgets/messageline/feedview.cpp @@ -31,10 +31,6 @@ constexpr int approximateSingleMessageHeight = 20; constexpr int progressSize = 70; constexpr int dateDeviderMargin = 10; -constexpr int avatarHeight = 50; -constexpr int margin = 6; -constexpr int halfMargin = 3; - const std::set FeedView::geometryChangingRoles = { Models::MessageFeed::Attach, Models::MessageFeed::Text, diff --git a/ui/widgets/messageline/messagedelegate.cpp b/ui/widgets/messageline/messagedelegate.cpp index 0cf449a..5dd0dce 100644 --- a/ui/widgets/messageline/messagedelegate.cpp +++ b/ui/widgets/messageline/messagedelegate.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -29,6 +30,11 @@ constexpr int avatarHeight = 50; constexpr int margin = 6; constexpr int textMargin = 2; constexpr int statusIconSize = 16; +constexpr float nickFontMultiplier = 1.1; +constexpr float dateFontMultiplier = 0.8; + +constexpr int bubbleMargin = 6; +constexpr int bubbleBorderRadius = 3; MessageDelegate::MessageDelegate(QObject* parent): QStyledItemDelegate(parent), @@ -101,9 +107,10 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio painter->save(); painter->setRenderHint(QPainter::Antialiasing, true); - if (option.state & QStyle::State_MouseOver) { - painter->fillRect(option.rect, option.palette.brush(QPalette::Inactive, QPalette::Highlight)); - } +// if (option.state & QStyle::State_MouseOver) { +// painter->fillRect(option.rect, option.palette.brush(QPalette::Inactive, QPalette::Highlight)); +// } + bool ntds = needToDrawSender(index, data); if (ntds || option.rect.y() < 1) { @@ -118,6 +125,9 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio } else { opt.displayAlignment = Qt::AlignRight | Qt::AlignTop; } + + QPoint bubbleBegin = messageRect.topLeft(); + messageRect.adjust(bubbleMargin, bubbleMargin, -bubbleMargin, -bubbleMargin / 2); opt.rect = messageRect; QSize messageSize(0, 0); @@ -127,9 +137,6 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio bodySize = messageSize; } - if (ntds) { - messageSize.rheight() += nickMetrics.lineSpacing(); - } messageSize.rheight() += dateMetrics.height(); QString dateString = data.date.toLocalTime().toString("hh:mm"); @@ -155,46 +162,64 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio } else { messageSize.setWidth(opt.rect.width()); } - - QRect rect; - if (ntds) { - painter->setFont(nickFont); - painter->drawText(opt.rect, opt.displayAlignment, data.sender, &rect); - opt.rect.adjust(0, rect.height() + textMargin, 0, 0); - } + painter->save(); + + int storedY = opt.rect.y(); + if (ntds) { + opt.rect.adjust(0, nickMetrics.lineSpacing() + textMargin, 0, 0); + } + + int attWidth(0); switch (data.attach.state) { case Models::none: clearHelperWidget(data); //i can't imagine the situation where it's gonna be needed break; //but it's a possible performance problem case Models::uploading: - paintPreview(data, painter, opt); + attWidth = std::max(paintPreview(data, painter, opt), attWidth); [[fallthrough]]; case Models::downloading: - paintBar(getBar(data), painter, data.sentByMe, opt); + messageSize.setWidth(opt.rect.width()); + messageSize.rheight() += barHeight + textMargin + opt.rect.y() - storedY; + paintBubble(data, painter, messageSize, opt, bubbleBegin); + attWidth = std::max(paintBar(getBar(data), painter, data.sentByMe, opt), attWidth); break; case Models::remote: - paintButton(getButton(data), painter, data.sentByMe, opt); + attWidth = std::max(paintButton(getButton(data), painter, data.sentByMe, opt), attWidth); break; case Models::ready: case Models::local: clearHelperWidget(data); - paintPreview(data, painter, opt); + attWidth = std::max(paintPreview(data, painter, opt), attWidth); break; case Models::errorDownload: { - paintButton(getButton(data), painter, data.sentByMe, opt); - paintComment(data, painter, opt); + attWidth = std::max(paintButton(getButton(data), painter, data.sentByMe, opt), attWidth); + attWidth = std::max(paintComment(data, painter, opt), attWidth); } break; case Models::errorUpload:{ clearHelperWidget(data); - paintPreview(data, painter, opt); - paintComment(data, painter, opt); + attWidth = std::max(paintPreview(data, painter, opt), attWidth); + attWidth = std::max(paintComment(data, painter, opt), attWidth); } break; } painter->restore(); + if (data.attach.state != Models::uploading && data.attach.state != Models::downloading) { + messageSize.rheight() += opt.rect.y() - storedY; + messageSize.setWidth(std::max(attWidth, messageSize.width())); + paintBubble(data, painter, messageSize, opt, bubbleBegin); + } + + QRect rect; + if (ntds) { + painter->setFont(nickFont); + int storedY2 = opt.rect.y(); + opt.rect.setY(storedY); + painter->drawText(opt.rect, opt.displayAlignment, data.sender, &rect); + opt.rect.setY(storedY2); + } int messageLeft = INT16_MAX; int messageRight = opt.rect.x() + messageSize.width(); @@ -256,6 +281,23 @@ void MessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optio } } +void MessageDelegate::paintBubble(const Models::FeedItem& data, QPainter* painter, const QSize& messageSize, QStyleOptionViewItem& option, QPoint bubbleBegin) const +{ + painter->save(); + if (data.sentByMe) { + bubbleBegin.setX(option.rect.topRight().x() - messageSize.width() - bubbleMargin); + painter->setBrush(option.palette.brush(QPalette::Inactive, QPalette::Highlight)); + } else { + painter->setBrush(option.palette.brush(QPalette::Window)); + } + QSize bubbleAddition(2 * bubbleMargin, 1.5 * bubbleMargin); + QRect bubble(bubbleBegin, messageSize + bubbleAddition); + painter->setPen(Qt::NoPen); + painter->drawRoundedRect(bubble, bubbleBorderRadius, bubbleBorderRadius); + painter->restore(); +} + + void MessageDelegate::paintAvatar(const Models::FeedItem& data, const QModelIndex& index, const QStyleOptionViewItem& option, QPainter* painter) const { int currentRow = index.row(); @@ -282,11 +324,22 @@ void MessageDelegate::paintAvatar(const Models::FeedItem& data, const QModelInde y = std::min(0, rect.bottom() - margin - avatarHeight); --currentRow; } + + QPixmap pixmap = icon.pixmap(avatarHeight, avatarHeight); + QPainterPath path; + int ax; + if (data.sentByMe) { - painter->drawPixmap(option.rect.width() - avatarHeight - margin, y + margin / 2, icon.pixmap(avatarHeight, avatarHeight)); + ax = option.rect.width() - avatarHeight - margin; } else { - painter->drawPixmap(margin, y + margin / 2, icon.pixmap(avatarHeight, avatarHeight)); + ax = margin; } + + path.addEllipse(ax, y + margin / 2, avatarHeight, avatarHeight); + painter->save(); + painter->setClipPath(path); + painter->drawPixmap(ax, y + margin / 2, pixmap); + painter->restore(); } bool MessageDelegate::needToDrawAvatar(const QModelIndex& index, const Models::FeedItem& data, const QStyleOptionViewItem& option) const @@ -309,7 +362,7 @@ bool MessageDelegate::needToDrawSender(const QModelIndex& index, const Models::F QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { - QRect messageRect = option.rect.adjusted(0, margin / 2, -(avatarHeight + 3 * margin), -margin / 2); + QRect messageRect = option.rect.adjusted(bubbleMargin, margin / 2 + bubbleMargin, -(avatarHeight + 3 * margin + bubbleMargin), -(margin + bubbleMargin) / 2); QStyleOptionViewItem opt = option; opt.rect = messageRect; QVariant vi = index.data(Models::MessageFeed::Bulk); @@ -347,10 +400,10 @@ QSize MessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModel } if (needToDrawSender(index, data)) { - messageSize.rheight() += nickMetrics.lineSpacing(); - messageSize.rheight() += textMargin; + messageSize.rheight() += nickMetrics.lineSpacing() + textMargin; } + messageSize.rheight() += bubbleMargin + bubbleMargin / 2; messageSize.rheight() += dateMetrics.height() > statusIconSize ? dateMetrics.height() : statusIconSize; // if (messageSize.height() < avatarHeight) { @@ -372,17 +425,17 @@ void MessageDelegate::initializeFonts(const QFont& font) float ndps = nickFont.pointSizeF(); if (ndps != -1) { - nickFont.setPointSizeF(ndps * 1.2); + nickFont.setPointSizeF(ndps * nickFontMultiplier); } else { - nickFont.setPointSize(nickFont.pointSize() + 2); + nickFont.setPointSize(nickFont.pointSize() * nickFontMultiplier); } dateFont.setItalic(true); float dps = dateFont.pointSizeF(); if (dps != -1) { - dateFont.setPointSizeF(dps * 0.8); + dateFont.setPointSizeF(dps * dateFontMultiplier); } else { - dateFont.setPointSize(dateFont.pointSize() - 2); + dateFont.setPointSize(dateFont.pointSize() * dateFontMultiplier); } bodyMetrics = QFontMetrics(bodyFont); @@ -400,11 +453,11 @@ bool MessageDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, cons return QStyledItemDelegate::editorEvent(event, model, option, index); } -void MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const +int MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const { QPoint start; if (sentByMe) { - start = {option.rect.width() - btn->width(), option.rect.top()}; + start = {option.rect.x() + option.rect.width() - btn->width(), option.rect.top()}; } else { start = option.rect.topLeft(); } @@ -415,9 +468,10 @@ void MessageDelegate::paintButton(QPushButton* btn, QPainter* painter, bool sent btn->show(); option.rect.adjust(0, buttonHeight + textMargin, 0, 0); + return btn->width(); } -void MessageDelegate::paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const +int MessageDelegate::paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const { painter->setFont(dateFont); QColor q = painter->pen().color(); @@ -426,9 +480,11 @@ void MessageDelegate::paintComment(const Models::FeedItem& data, QPainter* paint QRect rect; painter->drawText(option.rect, option.displayAlignment, data.attach.error, &rect); option.rect.adjust(0, rect.height() + textMargin, 0, 0); + + return rect.width(); } -void MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const +int MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const { QPoint start = option.rect.topLeft(); bar->resize(option.rect.width(), barHeight); @@ -437,9 +493,11 @@ void MessageDelegate::paintBar(QProgressBar* bar, QPainter* painter, bool sentBy bar->render(painter, QPoint(), QRegion(), QWidget::DrawChildren); option.rect.adjust(0, barHeight + textMargin, 0, 0); + + return option.rect.width(); } -void MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const +int MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const { Preview* preview = 0; std::map::iterator itr = previews->find(data.id); @@ -458,7 +516,10 @@ void MessageDelegate::paintPreview(const Models::FeedItem& data, QPainter* paint emit invalidPath(data.id); //or deleted. This signal notifies the model, and the model notifies the core, preview can } //handle being invalid for as long as I need and can be even become valid again with a new path - option.rect.adjust(0, preview->size().height() + textMargin, 0, 0); + QSize pSize(preview->size()); + option.rect.adjust(0, pSize.height() + textMargin, 0, 0); + + return pSize.width(); } QPushButton * MessageDelegate::getButton(const Models::FeedItem& data) const diff --git a/ui/widgets/messageline/messagedelegate.h b/ui/widgets/messageline/messagedelegate.h index 5792e01..87a79c9 100644 --- a/ui/widgets/messageline/messagedelegate.h +++ b/ui/widgets/messageline/messagedelegate.h @@ -61,11 +61,12 @@ signals: void invalidPath(const QString& messageId) const; protected: - void paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const; - void paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const; - void paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const; - void paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const; + int paintButton(QPushButton* btn, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const; + int paintBar(QProgressBar* bar, QPainter* painter, bool sentByMe, QStyleOptionViewItem& option) const; + int paintPreview(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const; + int paintComment(const Models::FeedItem& data, QPainter* painter, QStyleOptionViewItem& option) const; void paintAvatar(const Models::FeedItem& data, const QModelIndex& index, const QStyleOptionViewItem& option, QPainter* painter) const; + void paintBubble(const Models::FeedItem& data, QPainter* painter, const QSize& messageSize, QStyleOptionViewItem& option, QPoint bubbleBegin) const; QPushButton* getButton(const Models::FeedItem& data) const; QProgressBar* getBar(const Models::FeedItem& data) const; QLabel* getStatusIcon(const Models::FeedItem& data) const;