aboutsummaryrefslogtreecommitdiffstats
path: root/src/carddelegate.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/carddelegate.cpp')
-rw-r--r--src/carddelegate.cpp78
1 files changed, 78 insertions, 0 deletions
diff --git a/src/carddelegate.cpp b/src/carddelegate.cpp
index 9a98c69..2dd29a2 100644
--- a/src/carddelegate.cpp
+++ b/src/carddelegate.cpp
@@ -18,6 +18,8 @@
#include "carddelegate.h"
+#include "avatar.h"
+#include "businesssenders.h"
#include "cardlayout.h"
#include "marks.h"
#include "tagchip.h"
@@ -26,6 +28,7 @@
#include <QApplication>
#include <QDateTime>
#include <QGuiApplication>
+#include <QLinearGradient>
#include <QPainter>
#include <QRegularExpression>
#include <QStyle>
@@ -100,6 +103,26 @@ QColor CardDelegate::mutedChipColour(const QColor &chipColour)
return QColor::fromHslF(h, s * kSaturationScale, l, a);
}
+QRect CardDelegate::fadeRectFor(const QRect &card, const QRect &innermostSpine)
+{
+ // The EXCLUSIVE right edge, then a rect built from it: QRect::right() is
+ // inclusive, which is the trap CardLayout already documents.
+ // Right to left: the wash is opaque at the card's RIGHT edge, where a
+ // hard stop is the card's own boundary, and fades out before reaching the
+ // accent bar, which already states the account. Drawing it the other way
+ // put the hard stop mid-card and read as a slab.
+ //
+ // A reply's left limit is its own spine rather than the card's edge, so
+ // the wash steps right with the nesting and stays shorter.
+ const int left = innermostSpine.isEmpty() ? card.left()
+ : innermostSpine.left();
+ const int start = card.right() + 1 - int(card.width() * kFadeFraction);
+ if (card.right() + 1 <= qMax(start, left))
+ return QRect();
+ return QRect(qMax(start, left), card.top(),
+ card.right() + 1 - qMax(start, left), card.height());
+}
+
QColor CardDelegate::accentLineColour(const QColor &accountColour)
{
if (!accountColour.isValid())
@@ -147,6 +170,21 @@ QSize CardDelegate::sizeHint(const QStyleOptionViewItem &option,
return QSize(option.rect.width(), CardLayout::heightFor(option.font));
}
+QPixmap CardDelegate::avatarFor(const QString &senderAddress,
+ const QString &senderName,
+ const QString &accountAddress,
+ const QString &accountLabel,
+ bool isBusinessSender, int side,
+ const QFont &font)
+{
+ const bool haveSender = !senderAddress.trimmed().isEmpty();
+ const QString seed = haveSender ? senderAddress : accountAddress;
+ const QString initials =
+ Avatar::initialsFor(senderName, senderAddress, accountLabel);
+ const Avatar::Fill fill = Avatar::fillFor(senderName, isBusinessSender);
+ return Avatar::pixmapFor(seed, initials, fill, side, font);
+}
+
void CardDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
@@ -178,6 +216,30 @@ void CardDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
root.data(ThreadListModel::AccountColourRole).value<QColor>();
const QColor lineColour = accentLineColour(accountColour);
+ // The account's fade. Under everything but the chrome, so the selection
+ // highlight and the doomed-row tint still cover it: a selected row reading
+ // mostly as selection is expected, not a fault.
+ const QRect fade =
+ fadeRectFor(option.rect,
+ card.spines.isEmpty() ? QRect() : card.spines.last());
+ if (!fade.isEmpty() && accountColour.isValid()) {
+ QColor from = lineColour;
+ // A reply's wash is weaker than its root's, so an expanded thread
+ // reads as one block with the root leading it.
+ from.setAlphaF(card.accentRect.isEmpty() ? 0.14 : 0.30);
+ // Right to left: opaque at the fade's far end, transparent where the
+ // accent bar already carries the colour. Drawing it the other way put
+ // the strongest wash under the bar, which is the one place the account
+ // is already stated.
+ QLinearGradient gradient(fade.topRight(), fade.topLeft());
+ // Opaque at the right edge, gone at the left, so the only hard stop
+ // is the card's own boundary.
+ gradient.setColorAt(0.0, from);
+ from.setAlphaF(0.0);
+ gradient.setColorAt(1.0, from);
+ painter->fillRect(fade, gradient);
+ }
+
// The accent bar, thread cards only. Drawn after the chrome so the
// selection highlight cannot cover it: which account a card belongs to
// must stay readable on the row the user is looking at.
@@ -201,6 +263,22 @@ void CardDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
painter->fillRect(spine, spineColour);
}
+ // The sender's squircle, in the layout's reserved gutter. Drawn before the
+ // text so a wide avatar can never overprint the sender line.
+ if (!card.avatarRect.isEmpty()) {
+ const QString senderAddress =
+ index.data(ThreadListModel::SenderAddressRole).toString();
+ const QString senderName =
+ index.data(ThreadListModel::SenderNameRole).toString();
+ painter->drawPixmap(
+ card.avatarRect,
+ avatarFor(senderAddress, senderName, m_accountAddress,
+ m_accountLabel,
+ BusinessSenders::contains(m_businessSenders,
+ senderAddress),
+ card.avatarRect.width(), option.font));
+ }
+
// Selection outranks the model's foreground, and the order matters: a read
// card carries a dimmed colour blended against the UNSELECTED background,
// so over the highlight it lands grey-on-highlight and close to unreadable.