message bubbles, avatar rounding, roster adjusments

This commit is contained in:
Blue 2022-01-09 01:28:29 +03:00
parent 9ac0ca10f3
commit 8a2658e4fc
Signed by untrusted user: blue
GPG Key ID: 9B203B252A63EE38
7 changed files with 158 additions and 49 deletions

View File

@ -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);

View File

@ -73,6 +73,9 @@
<property name="bottomMargin">
<number>0</number>
</property>
<property name="spacing">
<number>0</number>
</property>
<item row="2" column="1">
<widget class="QTreeView" name="roster">
<property name="frameShape">
@ -96,16 +99,31 @@
<property name="allColumnsShowFocus">
<bool>true</bool>
</property>
<property name="headerHidden">
<bool>true</bool>
</property>
<property name="expandsOnDoubleClick">
<bool>false</bool>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<attribute name="headerMinimumSectionSize">
<number>10</number>
</attribute>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="comboBox">
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</property>
<property name="editable">
<bool>false</bool>
</property>
@ -115,6 +133,9 @@
<property name="currentIndex">
<number>-1</number>
</property>
<property name="frame">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
@ -162,7 +183,7 @@
<x>0</x>
<y>0</y>
<width>718</width>
<height>27</height>
<height>32</height>
</rect>
</property>
<widget class="QMenu" name="menuSettings">

View File

@ -25,12 +25,16 @@
#include <QTimer>
#include <QFileDialog>
#include <QMimeDatabase>
#include <unistd.h>
#include <QAbstractTextDocumentLayout>
#include <QCoreApplication>
#include <QTemporaryFile>
#include <QDir>
#include <QMenu>
#include <QBitmap>
#include <unistd.h>
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)

View File

@ -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

View File

@ -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<int> FeedView::geometryChangingRoles = {
Models::MessageFeed::Attach,
Models::MessageFeed::Text,

View File

@ -18,6 +18,7 @@
#include <QDebug>
#include <QPainter>
#include <QPainterPath>
#include <QApplication>
#include <QMouseEvent>
#include <QAbstractItemView>
@ -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<QString, Preview*>::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

View File

@ -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;