diff options
Diffstat (limited to 'src/carddelegate.cpp')
| -rw-r--r-- | src/carddelegate.cpp | 78 |
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. |
