summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mainwindow.cpp11
-rw-r--r--src/tagchip.cpp43
-rw-r--r--src/tagchip.h8
-rw-r--r--src/threadlistmodel.cpp46
-rw-r--r--src/threadlistmodel.h9
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.