Render clickable compact replies for text and media messages
This commit is contained in:
1 parent
68df26d4a5
commit
eebef4d14f
2 files changed
+51
-19
No files matched your search
@@ -139,21 +139,27 @@ bool MessageDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
|
||||
const auto* mouse = static_cast<QMouseEvent*>(event);
|
||||
if (mouse->button() != Qt::LeftButton) return false;
|
||||
const QPoint point = mouse->pos() - option.rect.topLeft();
|
||||
const bool onQuote = L.quoteRect.contains(point);
|
||||
int reaction = L.reactionHeart.contains(point) ? -1 : -2;
|
||||
for (const auto& chip : L.reactionChips) if (chip.rect.contains(point)) { reaction = chip.id; break; }
|
||||
const bool onReaction = mouse->button() == Qt::LeftButton && reaction != -2;
|
||||
const bool onAvatar=mouse->button()==Qt::LeftButton && !L.narrow && L.avatarRect.contains(mouse->pos()-option.rect.topLeft());
|
||||
const bool onPoll = index.data(MsgContentTypeRole).toString() == "poll"
|
||||
const bool onPoll = !onQuote && index.data(MsgContentTypeRole).toString() == "poll"
|
||||
&& !index.data(MsgChannelIdRole).toString().startsWith("dm:")
|
||||
&& (L.bubbleRect.contains(point) || L.emojiArea.contains(point))
|
||||
&& (!L.reactionHeart.isValid() || point.y() < L.reactionHeart.top());
|
||||
if (event->type()==QEvent::MouseButtonPress) {
|
||||
m_quotePress = onQuote ? index : QModelIndex(); m_quotePressPosition = mouse->pos();
|
||||
m_reactionPress = onReaction ? index : QModelIndex(); m_pressedReaction = reaction; m_reactionPressPosition = mouse->pos();
|
||||
m_avatarPress=onAvatar ? index : QModelIndex(); m_avatarPressPosition=mouse->pos();
|
||||
m_pollPress = onPoll ? index : QModelIndex(); m_pollPressPosition = mouse->pos();
|
||||
return onAvatar || onReaction || onPoll;
|
||||
return onQuote || onAvatar || onReaction || onPoll;
|
||||
}
|
||||
const auto* view = qobject_cast<const ChatView*>(option.widget);
|
||||
const bool hadQuotePress = m_quotePress.isValid();
|
||||
const bool activateQuote = onQuote && m_quotePress == index
|
||||
&& (mouse->pos() - m_quotePressPosition).manhattanLength() < 5 && (!view || !view->isSelecting());
|
||||
m_quotePress = QPersistentModelIndex();
|
||||
const bool activatePoll = onPoll && m_pollPress == index && (mouse->pos() - m_pollPressPosition).manhattanLength() < 5
|
||||
&& (!view || !view->textSelection().active);
|
||||
m_pollPress = QPersistentModelIndex();
|
||||
@@ -163,6 +169,8 @@ bool MessageDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
|
||||
const bool activateReaction = onReaction && m_reactionPress == index && m_pressedReaction == reaction
|
||||
&& (mouse->pos() - m_reactionPressPosition).manhattanLength() < 5;
|
||||
m_reactionPress = QPersistentModelIndex(); m_pressedReaction = -2;
|
||||
if (activateQuote) { emit replyRequested(index.data(MsgReplyToRole).toString()); return true; }
|
||||
if (hadQuotePress || onQuote) return true;
|
||||
if (activateReaction) { emit reactionRequested(index, reaction); return true; }
|
||||
if (hadReactionPress || onReaction) return true;
|
||||
if (activateAvatar) {
|
||||
@@ -428,6 +436,24 @@ static int calcMaxLineWidth(const QFont &font, const QString &text, int availW)
|
||||
MessageDelegate::Layout MessageDelegate::calcLayout(
|
||||
const QStyleOptionViewItem& option, const QModelIndex& index) const {
|
||||
Layout layout = calcBodyLayout(option, index);
|
||||
// Медиа и крупные emoji получают ту же цитату над своим содержимым.
|
||||
if (index.data(MsgHasQuoteRole).toBool() && !layout.quoteRect.isValid()) {
|
||||
const int line = QFontMetrics(option.font).height(), height = 2 * line + 2 * kQuotePadV + 2;
|
||||
const QRect body = layout.emojiOnly ? layout.emojiArea : layout.bubbleRect;
|
||||
const int width = qMin(qMax(200, body.width()), qMax(40, layout.viewWidth - 2 * kMarginH));
|
||||
const int x = layout.isOutgoing ? body.right() - width + 1 : body.left();
|
||||
layout.quoteRect = QRect(qMax(kMarginH, x), 0, width, height);
|
||||
layout.quoteBarRect = QRect(layout.quoteRect.left(), 0, kQuoteBarW, height);
|
||||
layout.quoteAuthorRect = QRect(layout.quoteRect.left() + kQuotePadH + kQuoteBarW, kQuotePadV,
|
||||
width - 2 * kQuotePadH - kQuoteBarW, line);
|
||||
layout.quoteTextRect = layout.quoteAuthorRect.translated(0, line + 2);
|
||||
for (QRect* rect : {&layout.bubbleRect, &layout.avatarRect, &layout.authorRect, &layout.textRect, &layout.statusRect,
|
||||
&layout.deliveryRect, &layout.emojiArea, &layout.voicePlayBtnRect, &layout.voiceWaveformRect,
|
||||
&layout.voiceDurationRect, &layout.voiceTimeRect, &layout.mediaBarRect, &layout.progressBarRect,
|
||||
&layout.videoThumbRect, &layout.videoPlayBtnRect, &layout.videoDurBadgeRect, &layout.imageThumbRect,
|
||||
&layout.imageBadgeRect}) if (rect->isValid()) rect->translate(0, height + kQuoteGap);
|
||||
layout.totalHeight += height + kQuoteGap;
|
||||
}
|
||||
const QString channel = index.data(MsgChannelIdRole).toString();
|
||||
if (layout.emojiOnly || channel.isEmpty() || channel.startsWith("dm:")
|
||||
|| index.data(MsgMessageKeyRole).toString().size() != 64 || layout.bubbleRect.isEmpty()) return layout;
|
||||
@@ -458,7 +484,7 @@ MessageDelegate::Layout MessageDelegate::calcLayout(
|
||||
}
|
||||
layout.reactionTime = QRect(layout.bubbleRect.right() - kPadH - timeWidth + 1, y, timeWidth, height);
|
||||
layout.bubbleRect.setBottom(y + height + kPadBot - 1);
|
||||
layout.totalHeight = qMax(layout.bubbleRect.height(), kAvatar) + kItemGap;
|
||||
layout.totalHeight = qMax(layout.bubbleRect.bottom() + 1, kAvatar) + kItemGap;
|
||||
if (layout.avatarRect.isValid()) layout.avatarRect.moveTop(qMax(0, layout.bubbleRect.bottom() + 1 - kAvatar));
|
||||
return layout;
|
||||
}
|
||||
@@ -705,8 +731,7 @@ MessageDelegate::Layout MessageDelegate::calcBodyLayout(
|
||||
|
||||
int qH = 0;
|
||||
if (hasQuote) {
|
||||
int qTextW = bubbleW - 2 * kPadH - kQuoteBarW - kQuotePadH;
|
||||
int qTextH = measureTextHeight(option.font, qText, qTextW);
|
||||
int qTextH = fm.height();
|
||||
int qOneLineH = fm.height();
|
||||
qH = kQuotePadV + qOneLineH + (qText.isEmpty() ? 0 : 2 + qTextH) + kQuotePadV;
|
||||
}
|
||||
@@ -973,10 +998,15 @@ void MessageDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio
|
||||
const QModelIndex &index) const {
|
||||
const Layout layout = calcLayout(option, index);
|
||||
paintBody(painter, option, index, layout);
|
||||
if (!layout.reactionHeart.isValid()) return;
|
||||
painter->save(); painter->translate(option.rect.topLeft());
|
||||
painter->setClipRect(QRect(QPoint(0, 0), option.rect.size()));
|
||||
drawReactions(painter, layout, option); painter->restore();
|
||||
if (layout.quoteRect.isValid()) drawQuote(painter, layout, index, option);
|
||||
if (layout.reactionHeart.isValid()) drawReactions(painter, layout, option);
|
||||
if (index.data(MsgHighlightRole).toBool()) {
|
||||
QColor color = option.palette.highlight().color(); color.setAlpha(55);
|
||||
painter->fillRect(QRect(QPoint(0, 0), option.rect.size()), color);
|
||||
}
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
void MessageDelegate::paintBody(QPainter* painter, const QStyleOptionViewItem& option,
|
||||
@@ -1388,7 +1418,7 @@ void MessageDelegate::paintBody(QPainter* painter, const QStyleOptionViewItem& o
|
||||
iconFont.setPointSize(iconFont.pointSize() + 6);
|
||||
painter->setFont(iconFont);
|
||||
painter->setPen(option.palette.text().color());
|
||||
QRect iconRect(L.bubbleRect.left() + kPadH, kPadTop, 24, 24);
|
||||
QRect iconRect(L.bubbleRect.left() + kPadH, L.bubbleRect.top() + kPadTop, 24, 24);
|
||||
if (dlState == 2)
|
||||
painter->drawText(iconRect, Qt::AlignCenter, QString::fromUtf8("\xE2\x9C\x85"));
|
||||
else
|
||||
@@ -1457,10 +1487,6 @@ void MessageDelegate::paintBody(QPainter* painter, const QStyleOptionViewItem& o
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
if (index.data(MsgHasQuoteRole).toBool()) {
|
||||
drawQuote(painter, L, index, option);
|
||||
}
|
||||
|
||||
if (!L.narrow)
|
||||
drawAvatar(painter, L, index);
|
||||
|
||||
@@ -1547,24 +1573,25 @@ void MessageDelegate::drawQuote(QPainter *p, const Layout &L,
|
||||
p->save();
|
||||
|
||||
p->setPen(Qt::NoPen);
|
||||
p->setBrush(QColor(0xFF, 0xEE, 0xEE));
|
||||
QColor background = opt.palette.highlight().color(); background.setAlpha(28);
|
||||
p->setBrush(background);
|
||||
p->drawRoundedRect(L.quoteRect, 5, 5);
|
||||
|
||||
p->setPen(Qt::NoPen);
|
||||
p->setBrush(QColor(0xE0, 0x40, 0x40));
|
||||
p->setBrush(opt.palette.highlight());
|
||||
p->drawRect(L.quoteBarRect);
|
||||
|
||||
QFont boldFont = opt.font;
|
||||
boldFont.setBold(true);
|
||||
p->setFont(boldFont);
|
||||
p->setPen(QColor(0xE0, 0x40, 0x40));
|
||||
p->setPen(opt.palette.text().color());
|
||||
p->drawText(L.quoteAuthorRect, Qt::AlignLeft | Qt::AlignVCenter,
|
||||
index.data(MsgQuoteAuthorRole).toString());
|
||||
QFontMetrics(boldFont).elidedText(index.data(MsgQuoteAuthorRole).toString(), Qt::ElideRight, L.quoteAuthorRect.width()));
|
||||
|
||||
p->setFont(opt.font);
|
||||
p->setPen(QColor(0x99, 0x55, 0x55));
|
||||
p->drawText(L.quoteTextRect, Qt::AlignLeft | Qt::TextWordWrap,
|
||||
index.data(MsgQuoteTextRole).toString());
|
||||
p->setPen(opt.palette.text().color());
|
||||
p->drawText(L.quoteTextRect, Qt::AlignLeft | Qt::AlignVCenter,
|
||||
QFontMetrics(opt.font).elidedText(index.data(MsgQuoteTextRole).toString(), Qt::ElideRight, L.quoteTextRect.width()));
|
||||
|
||||
p->restore();
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@ enum MessageDataRole {
|
||||
MsgInviteLinkRole = Qt::UserRole + 37, /* полная ссылка; MsgTextRole содержит локализованный заголовок и ссылку */
|
||||
MsgMessageKeyRole = Qt::UserRole + 38,
|
||||
MsgRawTimestampRole = Qt::UserRole + 39,
|
||||
MsgReplyToRole = Qt::UserRole + 40,
|
||||
MsgHighlightRole = Qt::UserRole + 41,
|
||||
};
|
||||
|
||||
class MessageDelegate : public QStyledItemDelegate {
|
||||
@@ -76,6 +78,7 @@ signals:
|
||||
void avatarRequested(const QString& channel, quint64 owner);
|
||||
void reactionRequested(const QModelIndex& index, int emoji); /* -1 открывает выбор реакции */
|
||||
void pollRequested(const QString& messageId);
|
||||
void replyRequested(const QString& messageId);
|
||||
|
||||
private:
|
||||
struct ReactionChip { QRect rect; int id; QString emoji; int count; bool mine; };
|
||||
@@ -131,6 +134,8 @@ private:
|
||||
int m_pressedReaction = -2;
|
||||
QPersistentModelIndex m_pollPress;
|
||||
QPoint m_pollPressPosition;
|
||||
QPersistentModelIndex m_quotePress;
|
||||
QPoint m_quotePressPosition;
|
||||
std::function<bool(const QModelIndex&)> m_downloadCb;
|
||||
|
||||
Layout calcLayout(const QStyleOptionViewItem &option,
|
||||
|
||||
Reference in new issue
Block a user