Shoehorn AccentColor into QPalette and keep existing 64bit resolve mask

It is necessary to add an AccentColor role to QPalette.
QPalette currently has 21 color roles and 3 color groups, which
require 63 bits to resolve. The resolve mask is implemented with a
qint64, which doesn't provide spare bits for another color role.

The color role NoRole is used as a default value, marking that a role
has not (yet) been defined. The enum value does not represent a valid
brush, even though it can theoretically be stored in QPalette's shared
data.

This patch adds the enum value AccentColor to QPalette::ColorRole,
increasing the available color roles to 22.
To keep the resolve mask at 63 bits, AccentColor is mapped to NoRole
in static constexpr bitPosition.

As the enum range would exceed 64 bits without this tweak, 3 additional
bits are substracted in the respective static assertion.

With NoRole having no bit in the resolve mask, the following adaptions
have been implemented:
- QPalette::resolve() is adapted to explicitly ignore NoRole.
- QPalette::isBrushSet() always returns false for NoRole.
- tst_QPalette::setAllPossibleBrushes() to verify the latter
- operator== ignores NoRole (documentation updated)

AccentColor is added in tst_QPalette::roleValues and enum documentation
is adapted.

In QPalette's default constructor, the AccentColor brush is defaulting
to the Highlight brush, it this is available. Otherwise it is made 30%
darker or lighter than the Base brush, depending on dark/light mode
heuristics.

QPalette's data stram functions have been extended from QDataStream
Version Qt_6_6. If earlier versions are de-serialised, the AccentColor
defaults to Highlight. An autotest function dataStream() has been added
to tst_QPalette.

The QDataStream Version Qt_6_6 has been bumped to 21.
tst_QDataStream has been adapted to the new version and the new
color Role.

Change-Id: I98bbf9de95fb83bda921e9614a0db3a3c0ebdf75
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
Axel Spoerl 2023-05-15 17:32:28 +02:00 committed by Volker Hilsheimer
parent 0328e4297e
commit 17c589df94
6 changed files with 126 additions and 11 deletions

View File

@ -68,7 +68,7 @@ public:
Qt_6_3 = Qt_6_0,
Qt_6_4 = Qt_6_0,
Qt_6_5 = Qt_6_0,
Qt_6_6 = Qt_6_0,
Qt_6_6 = 21,
Qt_DefaultCompiledVersion = Qt_6_6
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
#error Add the datastream version for this Qt version and update Qt_DefaultCompiledVersion

View File

@ -16,12 +16,17 @@ static int qt_palette_count = 1;
static constexpr QPalette::ResolveMask colorRoleOffset(QPalette::ColorGroup colorGroup)
{
return qToUnderlying(QPalette::NColorRoles) * qToUnderlying(colorGroup);
// Exclude NoRole; that bit is used for AccentColor
return (qToUnderlying(QPalette::NColorRoles) - 1) * qToUnderlying(colorGroup);
}
static constexpr QPalette::ResolveMask bitPosition(QPalette::ColorGroup colorGroup,
QPalette::ColorRole colorRole)
{
// Map AccentColor into NoRole for resolving purposes
if (colorRole == QPalette::AccentColor)
colorRole = QPalette::NoRole;
return colorRole + colorRoleOffset(colorGroup);
}
@ -100,6 +105,24 @@ static void qt_placeholder_from_text(QPalette &pal, int alpha = 50)
}
}
static void qt_ensure_default_accent_color(QPalette &pal)
{
// have a lighter/darker factor handy, depending on dark/light heuristics
const int lighter = pal.base().color().lightness() > pal.text().color().lightness() ? 130 : 70;
// Act only for color groups where no accent color is set
for (int i = 0; i < QPalette::NColorGroups; ++i) {
const QPalette::ColorGroup group = static_cast<QPalette::ColorGroup>(i);
if (!pal.isBrushSet(group, QPalette::AccentColor)) {
// Default to highlight if available, otherwise use a shade of base
const QBrush accentBrush = pal.isBrushSet(group, QPalette::Highlight)
? pal.brush(group, QPalette::Highlight)
: pal.brush(group, QPalette::Base).color().lighter(lighter);
pal.setBrush(group, QPalette::AccentColor, accentBrush);
}
}
}
static void qt_palette_from_color(QPalette &pal, const QColor &button)
{
int h, s, v;
@ -124,6 +147,7 @@ static void qt_palette_from_color(QPalette &pal, const QColor &button)
whiteBrush, buttonBrush, buttonBrush);
qt_placeholder_from_text(pal);
qt_ensure_default_accent_color(pal);
}
/*!
@ -524,6 +548,13 @@ static void qt_palette_from_color(QPalette &pal, const QColor &button)
item. By default, the highlight color is
Qt::darkBlue.
\value AccentColor A color that typically contrasts or compliments
Base, Window and Button colors. It usually represents
the users' choice of desktop personalisation.
Styling of interactive components is a typical use case.
Unless explicitly set, it defaults to Highlight.
This enum value has been introduced in Qt 6.6.
\value HighlightedText A text color that contrasts with \c Highlight.
By default, the highlighted text color is Qt::white.
@ -613,6 +644,7 @@ QPalette::QPalette(const QBrush &windowText, const QBrush &button,
base, window);
qt_placeholder_from_text(*this);
qt_ensure_default_accent_color(*this);
}
@ -670,6 +702,7 @@ QPalette::QPalette(const QColor &button, const QColor &window)
whiteBrush, baseBrush, windowBrush);
qt_placeholder_from_text(*this);
qt_ensure_default_accent_color(*this);
}
/*!
@ -837,6 +870,10 @@ void QPalette::setBrush(ColorGroup cg, ColorRole cr, const QBrush &b)
*/
bool QPalette::isBrushSet(ColorGroup cg, ColorRole cr) const
{
// NoRole has no resolve mask and should never be set anyway
if (cr == NoRole)
return false;
if (cg == Current)
cg = currentGroup;
@ -885,8 +922,11 @@ void QPalette::detach()
Returns \c true (usually quickly) if this palette is equal to \a p;
otherwise returns \c false (slowly).
\note The current ColorGroup is not taken into account when
comparing palettes
\note The following is not taken into account when comparing palettes:
\list
\li the \c current ColorGroup
\li ColorRole NoRole \since 6.6
\endlist
\sa operator!=()
*/
@ -896,6 +936,9 @@ bool QPalette::operator==(const QPalette &p) const
return true;
for(int grp = 0; grp < (int)NColorGroups; grp++) {
for(int role = 0; role < (int)NColorRoles; role++) {
// Dont't verify NoRole, because it has no resolve bit
if (role == NoRole)
continue;
if (d->data->br[grp][role] != p.d->data->br[grp][role])
return false;
}
@ -979,6 +1022,10 @@ QPalette QPalette::resolve(const QPalette &other) const
palette.detach();
for (int role = 0; role < int(NColorRoles); ++role) {
// Don't resolve NoRole, its bits are needed for AccentColor (see bitPosition)
if (role == NoRole)
continue;
for (int grp = 0; grp < int(NColorGroups); ++grp) {
if (!(d->resolveMask & (ResolveMask(1) << bitPosition(ColorGroup(grp), ColorRole(role))))) {
palette.d->data.detach();
@ -1054,6 +1101,9 @@ QDataStream &operator<<(QDataStream &s, const QPalette &p)
max = QPalette::AlternateBase + 1;
else if (s.version() <= QDataStream::Qt_5_11)
max = QPalette::ToolTipText + 1;
else if (s.version() <= QDataStream::Qt_6_5)
max = QPalette::PlaceholderText + 1;
for (int r = 0; r < max; r++)
s << p.d->data->br[grp][r];
}
@ -1097,15 +1147,25 @@ QDataStream &operator>>(QDataStream &s, QPalette &p)
} else if (s.version() <= QDataStream::Qt_5_11) {
p = QPalette();
max = QPalette::ToolTipText + 1;
} else if (s.version() <= QDataStream::Qt_6_5) {
p = QPalette();
max = QPalette::PlaceholderText + 1;
}
QBrush tmp;
for(int grp = 0; grp < (int)QPalette::NColorGroups; ++grp) {
const QPalette::ColorGroup group = static_cast<QPalette::ColorGroup>(grp);
for(int role = 0; role < max; ++role) {
s >> tmp;
p.setBrush((QPalette::ColorGroup)grp, (QPalette::ColorRole)role, tmp);
p.setBrush(group, (QPalette::ColorRole)role, tmp);
}
// AccentColor defaults to Highlight for stream versions that don't have it.
if (s.version() < QDataStream::Qt_6_6)
p.setBrush(group, QPalette::AccentColor, p.brush(group, QPalette::Highlight));
}
}
return s;
}

View File

@ -55,7 +55,8 @@ public:
NoRole,
ToolTipBase, ToolTipText,
PlaceholderText,
NColorRoles = PlaceholderText + 1,
AccentColor,
NColorRoles = AccentColor + 1,
};
Q_ENUM(ColorRole)
@ -98,6 +99,7 @@ public:
inline const QBrush &link() const { return brush(Link); }
inline const QBrush &linkVisited() const { return brush(LinkVisited); }
inline const QBrush &placeholderText() const { return brush(PlaceholderText); }
inline const QBrush &accentColor() const { return brush(AccentColor); }
bool operator==(const QPalette &p) const;
inline bool operator!=(const QPalette &p) const { return !(operator==(p)); }

View File

@ -374,6 +374,7 @@ Q_GUI_EXPORT QPalette qt_fusionPalette()
const QColor button = backGround;
const QColor shadow = dark.darker(135);
const QColor disabledShadow = shadow.lighter(150);
const QColor disabledHighlight(145, 145, 145);
QColor placeholder = text;
placeholder.setAlpha(128);
@ -392,7 +393,11 @@ Q_GUI_EXPORT QPalette qt_fusionPalette()
fusionPalette.setBrush(QPalette::Active, QPalette::Highlight, highlight);
fusionPalette.setBrush(QPalette::Inactive, QPalette::Highlight, highlight);
fusionPalette.setBrush(QPalette::Disabled, QPalette::Highlight, QColor(145, 145, 145));
fusionPalette.setBrush(QPalette::Disabled, QPalette::Highlight, disabledHighlight);
fusionPalette.setBrush(QPalette::Active, QPalette::AccentColor, highlight);
fusionPalette.setBrush(QPalette::Inactive, QPalette::AccentColor, highlight);
fusionPalette.setBrush(QPalette::Disabled, QPalette::AccentColor, disabledHighlight);
fusionPalette.setBrush(QPalette::PlaceholderText, placeholder);

View File

@ -295,6 +295,7 @@ static int NColorRoles[] = {
QPalette::PlaceholderText + 1, // Qt_5_12
QPalette::PlaceholderText + 1, // Qt_5_13, Qt_5_14, Qt_5_15
QPalette::PlaceholderText + 1, // Qt_6_0
QPalette::AccentColor + 1, // Qt_6_6
0 // add the correct value for Qt_5_14 here later
};
@ -2392,8 +2393,8 @@ void tst_QDataStream::setVersion()
*/
// revise the test if new color roles or color groups are added
QVERIFY(QPalette::NColorRoles == QPalette::PlaceholderText + 1);
QCOMPARE(int(QPalette::NColorGroups), 3);
QCOMPARE(QPalette::NColorRoles, QPalette::AccentColor + 1);
QCOMPARE(static_cast<int>(QPalette::NColorGroups), 3);
QByteArray ba2;
QPalette pal1, pal2;

View File

@ -23,6 +23,7 @@ private Q_SLOTS:
void cannotCheckIfInvalidBrushSet();
void checkIfBrushForCurrentGroupSet();
void cacheKey();
void dataStream();
};
void tst_QPalette::roleValues_data()
@ -51,9 +52,10 @@ void tst_QPalette::roleValues_data()
QTest::newRow("QPalette::ToolTipBase") << int(QPalette::ToolTipBase) << 18;
QTest::newRow("QPalette::ToolTipText") << int(QPalette::ToolTipText) << 19;
QTest::newRow("QPalette::PlaceholderText") << int(QPalette::PlaceholderText) << 20;
QTest::newRow("QPalette::AccentColor") << int(QPalette::AccentColor) << 21;
// Change this value as you add more roles.
QTest::newRow("QPalette::NColorRoles") << int(QPalette::NColorRoles) << 21;
QTest::newRow("QPalette::NColorRoles") << int(QPalette::NColorRoles) << 22;
}
void tst_QPalette::roleValues()
@ -236,8 +238,14 @@ void tst_QPalette::setAllPossibleBrushes()
}
for (int r = 0; r < QPalette::NColorRoles; ++r) {
const QPalette::ColorRole role = static_cast<QPalette::ColorRole>(r);
for (int g = 0; g < QPalette::NColorGroups; ++g) {
QVERIFY(p.isBrushSet(QPalette::ColorGroup(g), QPalette::ColorRole(r)));
const QPalette::ColorGroup group = static_cast<QPalette::ColorGroup>(g);
// NoRole has no resolve bit => isBrushSet returns false
if (role == QPalette::NoRole)
QVERIFY(!p.isBrushSet(group, role));
else
QVERIFY(p.isBrushSet(group, role));
}
}
}
@ -341,5 +349,44 @@ void tst_QPalette::cacheKey()
loggerShallowDetach.dismiss();
}
void tst_QPalette::dataStream()
{
const QColor highlight(42, 42, 42);
const QColor accent(13, 13, 13);
QPalette palette;
palette.setBrush(QPalette::Highlight, highlight);
palette.setBrush(QPalette::AccentColor, accent);
// When saved with Qt_6_5 or earlier, AccentColor defaults to Highlight
{
QByteArray b;
{
QDataStream stream(&b, QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_6_5);
stream << palette;
}
QPalette test;
QDataStream stream (&b, QIODevice::ReadOnly);
stream.setVersion(QDataStream::Qt_6_5);
stream >> test;
QCOMPARE(test.accentColor().color(), highlight);
}
// When saved with Qt_6_6 or later, AccentColor is saved explicitly
{
QByteArray b;
{
QDataStream stream(&b, QIODevice::WriteOnly);
stream.setVersion(QDataStream::Qt_6_6);
stream << palette;
}
QPalette test;
QDataStream stream (&b, QIODevice::ReadOnly);
stream.setVersion(QDataStream::Qt_6_6);
stream >> test;
QCOMPARE(test.accentColor().color(), accent);
}
}
QTEST_MAIN(tst_QPalette)
#include "tst_qpalette.moc"