diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/mainwindow.cpp | 11 | ||||
| -rw-r--r-- | src/tagchip.cpp | 43 | ||||
| -rw-r--r-- | src/tagchip.h | 8 | ||||
| -rw-r--r-- | src/threadlistmodel.cpp | 46 | ||||
| -rw-r--r-- | src/threadlistmodel.h | 9 |
5 files changed, 109 insertions, 8 deletions
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index bb11261..30de89c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -514,9 +514,14 @@ void MainWindow::buildUi() column, QHeaderView::Interactive); } - // The subject cell carries the account chip in front of its text. - m_threadView->setItemDelegateForColumn(ThreadListModel::SubjectColumn, - new SubjectDelegate(this)); + // The subject cell carries the account chip in front of its text, and + // every cell needs the delegate's selection handling: the read/unread + // dimming arrives as a Qt::ForegroundRole, which Qt's default painting + // prefers over the highlight, leaving a selected read row grey on the + // selection colour. SubjectDelegate::initStyleOption reverses that, and + // its paint() falls through to the base class wherever there is no chip, + // so the other columns keep their ordinary rendering. + m_threadView->setItemDelegate(new SubjectDelegate(this)); // Widening a column past the viewport scrolls rather than squeezing the // others. Per-pixel so the scroll does not jump a whole column at a time. m_threadView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); diff --git a/src/tagchip.cpp b/src/tagchip.cpp index 2e21419..1ac7623 100644 --- a/src/tagchip.cpp +++ b/src/tagchip.cpp @@ -49,6 +49,31 @@ void paint(QPainter *painter, const QRect &rect, const QString &text, } // namespace TagChip +void SubjectDelegate::initStyleOption(QStyleOptionViewItem *option, + const QModelIndex &index) const +{ + QStyledItemDelegate::initStyleOption(option, index); + + // Qt resolves Qt::ForegroundRole into the palette's Text roles, and its + // own painting then prefers those over HighlightedText: a model that + // supplies a foreground wins even on a selected row. + // + // That is wrong for the read/unread dimming. A read thread's colour is + // blended against the UNSELECTED background, so over the selection + // highlight it lands as grey on purple, near unreadable. The highlight + // already says "this row", so the dimming can yield to it while selected. + // + // Doomed threads are unaffected in practice: their fill is drawn beneath + // the selection and their white is a contrast requirement, which + // HighlightedText also satisfies. + if (option->state & QStyle::State_Selected) { + const QColor highlighted = + option->palette.color(QPalette::HighlightedText); + option->palette.setColor(QPalette::Text, highlighted); + option->palette.setColor(QPalette::WindowText, highlighted); + } +} + void SubjectDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { @@ -87,13 +112,21 @@ void SubjectDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio return; painter->save(); - // The model supplies the row's colours; honouring them keeps a deleted - // thread white-on-red here as everywhere else. + // Selection outranks the model's colour, and that order matters. A read + // thread carries a dimmed foreground blended against the UNSELECTED + // background, so painting it over the highlight leaves grey-on-purple, + // which is close to unreadable. The highlight already carries the "this + // row" signal, so the read/unread distinction can yield to it for as long + // as the row is selected. + // + // A doomed thread is the exception that proves the rule: its white is not + // a dimming but a contrast requirement against its own fill, and the fill + // is drawn under the selection too. const QVariant foreground = index.data(Qt::ForegroundRole); - if (foreground.isValid()) - painter->setPen(foreground.value<QBrush>().color()); - else if (option.state & QStyle::State_Selected) + if (option.state & QStyle::State_Selected) painter->setPen(option.palette.highlightedText().color()); + else if (foreground.isValid()) + painter->setPen(foreground.value<QBrush>().color()); else painter->setPen(option.palette.text().color()); diff --git a/src/tagchip.h b/src/tagchip.h index 9bd4e78..f43e3ff 100644 --- a/src/tagchip.h +++ b/src/tagchip.h @@ -59,4 +59,12 @@ public: const QModelIndex &index) const override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; + +protected: + /// Makes the selection highlight outrank a model-supplied foreground. + /// + /// Qt's own resolution does the opposite, which leaves a dimmed read + /// thread painting grey over the selection colour. + void initStyleOption(QStyleOptionViewItem *option, + const QModelIndex &index) const override; }; diff --git a/src/threadlistmodel.cpp b/src/threadlistmodel.cpp index 2289e6c..7e0477d 100644 --- a/src/threadlistmodel.cpp +++ b/src/threadlistmodel.cpp @@ -20,6 +20,8 @@ #include <QBrush> #include <QFont> +#include <QGuiApplication> +#include <QPalette> #include <QFontDatabase> #include <QFontMetrics> @@ -57,6 +59,29 @@ QColor ThreadListModel::spamColour() return QColor(0xa8, 0x5c, 0x18); } +QColor ThreadListModel::readColour() +{ + // Derived from the palette, never hardcoded: a fixed grey that reads as + // "quiet" on a light theme is nearly invisible on a dark one, which is the + // rule item 12 established for the message pane. + // + // Mixed toward the background rather than simply made transparent, so it + // composites the same over a selected row as over an unselected one. + const QPalette palette = QGuiApplication::palette(); + const QColor text = palette.color(QPalette::Text); + const QColor background = palette.color(QPalette::Base); + + // 0.55 of the text colour: clearly recessive beside an undimmed row, and + // still comfortably readable on its own. A read thread is not disabled, + // it is simply not the thing being pointed at. + constexpr qreal kWeight = 0.55; + const qreal inverse = 1.0 - kWeight; + return QColor::fromRgbF( + text.redF() * kWeight + background.redF() * inverse, + text.greenF() * kWeight + background.greenF() * inverse, + text.blueF() * kWeight + background.blueF() * inverse); +} + ThreadListModel::ThreadListModel(QObject *parent) : QAbstractTableModel(parent) { @@ -145,6 +170,27 @@ QVariant ThreadListModel::data(const QModelIndex &index, int role) const return QBrush(QColor(Qt::white)); } + // Unread's cue, and it deliberately does NOT rely on the bold below. + // + // Bold was the only cue until 2026-08-07, when it turned out to render + // identically to regular on the user's system: confirmed with a bare + // QTableView and a plain QStandardItemModel, so the fault is in Qt or + // fontconfig, below this application, and nothing here can reach it. + // + // So the emphasis is inverted instead. Unread rows are left at the + // palette's own text colour, and READ rows are dimmed toward the + // background. That way the cue rides on ForegroundRole, which the delegate + // already honours, and it costs no column. It also suits the real ratio: + // with a few dozen unread among thousands read, dimming the bulk is calmer + // than highlighting it. + // + // BELOW the doomed branch on purpose, and that ordering is the whole + // protection: a deleted or spam thread has already returned white text for + // this role above, and dimming it because it is also read would drop that + // to unreadable against the crimson. Do not hoist this. + if (role == Qt::ForegroundRole && !thread.isUnread()) + return QBrush(readColour()); + if (role == Qt::FontRole) { QFont font; bool styled = false; diff --git a/src/threadlistmodel.h b/src/threadlistmodel.h index ab9378e..152730f 100644 --- a/src/threadlistmodel.h +++ b/src/threadlistmodel.h @@ -75,6 +75,15 @@ public: static QColor deletedColour(); static QColor spamColour(); + /// The dimmed text colour a READ thread carries. + /// + /// Unread rows are left at the palette's own colour and read ones recede, + /// rather than unread being emphasised. Bold used to be the only cue and + /// cannot be relied on: on at least one system it renders identically to + /// regular, which is a Qt or fontconfig matter this application cannot + /// reach. Derived from the palette, never hardcoded. + static QColor readColour(); + explicit ThreadListModel(QObject *parent = nullptr); /// Supplies the account chip colours. Not owned; must outlive the model. |
