mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-25 12:38:48 +03:00
Modified the code that generates user's colors so that it will
work regardless of the theme choices the user makes. The code now incorporates the contrast between the background color and the color generated by the user_name when picking colors. It currently has two 'big' issues: 1. Colors are not cached. I am planning on adding a QHash for this a little later. This should improve performance by not calculating the color for the same users over and over and over again. 2. Theme changes do not trigger the colors to get refreshed. Currently, you will have to switch to a different room and back to get the colors to refresh.
This commit is contained in:
parent
1882198e4b
commit
50e382f554
7 changed files with 152 additions and 10 deletions
|
@ -3,6 +3,10 @@ QLabel {
|
|||
color: #caccd1;
|
||||
}
|
||||
|
||||
TimelineItem {
|
||||
qproperty-backgroundColor: #202228;
|
||||
}
|
||||
|
||||
#chatPage,
|
||||
#chatPage > * {
|
||||
background-color: #202228;
|
||||
|
|
|
@ -3,6 +3,10 @@ QLabel {
|
|||
color: #333;
|
||||
}
|
||||
|
||||
TimelineItem {
|
||||
qproperty-backgroundColor: white;
|
||||
}
|
||||
|
||||
#chatPage,
|
||||
#chatPage > * {
|
||||
background-color: white;
|
||||
|
|
|
@ -3,6 +3,10 @@ TypingDisplay {
|
|||
qproperty-backgroundColor: palette(window);
|
||||
}
|
||||
|
||||
TimelineItem {
|
||||
qproperty-backgroundColor: palette(window);
|
||||
}
|
||||
|
||||
TimelineView,
|
||||
TimelineView > * {
|
||||
border: none;
|
||||
|
|
112
src/Utils.cpp
112
src/Utils.cpp
|
@ -383,20 +383,120 @@ utils::linkColor()
|
|||
}
|
||||
|
||||
QString
|
||||
utils::generateHexColor(const QString &input)
|
||||
utils::generateHexColor(const int hash)
|
||||
{
|
||||
QString colour("#");
|
||||
for (int i = 0; i < 3; i++) {
|
||||
int value = (hash >> (i * 8)) & 0xFF;
|
||||
colour.append(("00" + QString::number(value, 16)).right(2));
|
||||
}
|
||||
// nhlog::ui()->debug("Hex Generated {} -> {}", QString::number(hash).toStdString(),
|
||||
// colour.toStdString());
|
||||
return colour.toUpper();
|
||||
}
|
||||
|
||||
int
|
||||
utils::hashQString(const QString &input)
|
||||
{
|
||||
auto hash = 0;
|
||||
|
||||
for (int i = 0; i < input.length(); i++) {
|
||||
hash = input.at(i).digitValue() + ((hash << 5) - hash);
|
||||
}
|
||||
|
||||
hash *= 13;
|
||||
QString colour("#");
|
||||
for (int i = 0; i < 3; i++) {
|
||||
int value = (hash >> (i * 8)) & 0xFF;
|
||||
colour.append(("00" + QString::number(value, 16)).right(2));
|
||||
|
||||
return hash;
|
||||
}
|
||||
return colour;
|
||||
|
||||
QString
|
||||
utils::generateContrastingHexColor(const QString &input, const QString &background)
|
||||
{
|
||||
nhlog::ui()->debug("Background hex {}", background.toStdString());
|
||||
const QColor backgroundCol(background);
|
||||
const qreal backgroundLum = luminance(background);
|
||||
|
||||
// Create a color for the input
|
||||
auto hash = hashQString(input);
|
||||
auto colorHex = generateHexColor(hash);
|
||||
|
||||
// converting to a QColor makes the luminance calc easier.
|
||||
QColor inputColor = QColor(colorHex);
|
||||
|
||||
// attempt to score both the luminance and the contrast.
|
||||
// contrast should have a higher precedence, but luminance
|
||||
// helps dictate how exciting the colors are.
|
||||
auto colorLum = luminance(inputColor);
|
||||
auto contrast = computeContrast(colorLum, backgroundLum);
|
||||
|
||||
// If the contrast or luminance don't meet our criteria,
|
||||
// try again and again until they do. After 10 tries,
|
||||
// the best-scoring color will be chosen.
|
||||
int att = 0;
|
||||
while ((contrast < 5 || (colorLum < 0.05 || colorLum > 0.95)) && ++att < 10) {
|
||||
hash = hashQString(input) + ((hash << 2) * 13);
|
||||
auto newHex = generateHexColor(hash);
|
||||
inputColor.setNamedColor(newHex);
|
||||
auto tmpLum = luminance(inputColor);
|
||||
auto tmpContrast = computeContrast(tmpLum, backgroundLum);
|
||||
|
||||
// Prioritize contrast over luminance
|
||||
// If both values are better, it's a no brainer.
|
||||
if (tmpContrast > contrast && (tmpLum > 0.05 && tmpLum < 0.95)) {
|
||||
contrast = tmpContrast;
|
||||
colorHex = newHex;
|
||||
colorLum = tmpLum;
|
||||
}
|
||||
// Otherwise, if we still can get a more
|
||||
// vibrant color and have met our contrast
|
||||
// threshold, pick the more vibrant color,
|
||||
// even if contrast will drop somewhat.
|
||||
// choosing 50% luminance as ideal.
|
||||
else if ((qAbs(tmpLum - 0.50) < qAbs(colorLum - 0.50)) && tmpContrast >= 5) {
|
||||
contrast = tmpContrast;
|
||||
colorHex = newHex;
|
||||
colorLum = tmpLum;
|
||||
}
|
||||
// Otherwise, just take the better contrast.
|
||||
else if (tmpContrast > contrast) {
|
||||
contrast = tmpContrast;
|
||||
colorHex = newHex;
|
||||
colorLum = tmpLum;
|
||||
}
|
||||
}
|
||||
|
||||
nhlog::ui()->debug("Hex Generated for {}: [hex: {}, contrast: {}, luminance: {}]",
|
||||
input.toStdString(),
|
||||
colorHex.toStdString(),
|
||||
QString::number(contrast).toStdString(),
|
||||
QString::number(colorLum).toStdString());
|
||||
return colorHex;
|
||||
}
|
||||
|
||||
qreal
|
||||
utils::computeContrast(const qreal &one, const qreal &two)
|
||||
{
|
||||
auto ratio = (one + 0.05) / (two + 0.05);
|
||||
|
||||
if (two > one) {
|
||||
ratio = 1 / ratio;
|
||||
}
|
||||
|
||||
return ratio;
|
||||
}
|
||||
|
||||
qreal
|
||||
utils::luminance(const QColor &col)
|
||||
{
|
||||
int colRgb[3] = {col.red(), col.green(), col.blue()};
|
||||
qreal lumRgb[3];
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
qreal v = colRgb[i] / 255.0;
|
||||
v <= 0.03928 ? lumRgb[i] = v / 12.92 : lumRgb[i] = qPow((v + 0.055) / 1.055, 2.4);
|
||||
}
|
||||
|
||||
return lumRgb[0] * 0.2126 + lumRgb[1] * 0.7152 + lumRgb[2] * 0.0722;
|
||||
}
|
||||
|
||||
void
|
||||
|
|
23
src/Utils.h
23
src/Utils.h
|
@ -14,6 +14,8 @@
|
|||
#include <mtx/events/collections.hpp>
|
||||
#include <mtx/events/common.hpp>
|
||||
|
||||
#include <qmath.h>
|
||||
|
||||
class QComboBox;
|
||||
|
||||
namespace utils {
|
||||
|
@ -227,9 +229,26 @@ markdownToHtml(const QString &text);
|
|||
QString
|
||||
linkColor();
|
||||
|
||||
//! Given an input string, create a color string
|
||||
//! Given an input integer, create a color string in #RRGGBB format
|
||||
QString
|
||||
generateHexColor(const QString &string);
|
||||
generateHexColor(const int hash);
|
||||
|
||||
//! Returns the hash code of the input QString
|
||||
int
|
||||
hashQString(const QString &input);
|
||||
|
||||
//! Generate a color (matching #RRGGBB) that has an acceptable contrast to background that is based
|
||||
//! on the input string.
|
||||
QString
|
||||
generateContrastingHexColor(const QString &input, const QString &background);
|
||||
|
||||
//! Given two luminance values, compute the contrast ratio between them.
|
||||
qreal
|
||||
computeContrast(const qreal &one, const qreal &two);
|
||||
|
||||
//! Compute the luminance of a single color. Based on https://stackoverflow.com/a/9733420
|
||||
qreal
|
||||
luminance(const QColor &col);
|
||||
|
||||
//! Center a widget in relation to another widget.
|
||||
void
|
||||
|
|
|
@ -622,8 +622,6 @@ TimelineItem::generateUserName(const QString &user_id, const QString &displaynam
|
|||
sender = displayname.split(":")[0].split("@")[1];
|
||||
}
|
||||
|
||||
auto userColor = utils::generateHexColor(user_id);
|
||||
|
||||
QFont usernameFont;
|
||||
usernameFont.setPointSizeF(usernameFont.pointSizeF() * 1.1);
|
||||
usernameFont.setWeight(QFont::Medium);
|
||||
|
@ -639,6 +637,12 @@ TimelineItem::generateUserName(const QString &user_id, const QString &displaynam
|
|||
userName_->setAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||||
userName_->setFixedWidth(QFontMetrics(userName_->font()).width(userName_->text()));
|
||||
|
||||
// TimelineItem isn't displayed. This forces the QSS to get
|
||||
// loaded.
|
||||
qApp->style()->polish(this);
|
||||
// generate user's unique color.
|
||||
auto backCol = backgroundColor().name();
|
||||
auto userColor = utils::generateContrastingHexColor(user_id, backCol);
|
||||
userName_->setStyleSheet("QLabel { color : " + userColor + "; }");
|
||||
|
||||
auto filter = new UserProfileFilter(user_id, userName_);
|
||||
|
|
|
@ -132,6 +132,8 @@ private:
|
|||
class TimelineItem : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor)
|
||||
|
||||
public:
|
||||
TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Notice> &e,
|
||||
bool with_sender,
|
||||
|
@ -202,6 +204,9 @@ public:
|
|||
const QString &room_id,
|
||||
QWidget *parent);
|
||||
|
||||
void setBackgroundColor(const QColor &color) { backgroundColor_ = color; }
|
||||
QColor backgroundColor() const { return backgroundColor_; }
|
||||
|
||||
void setUserAvatar(const QImage &pixmap);
|
||||
DescInfo descriptionMessage() const { return descriptionMsg_; }
|
||||
QString eventId() const { return event_id_; }
|
||||
|
@ -282,6 +287,8 @@ private:
|
|||
QLabel *timestamp_;
|
||||
QLabel *userName_;
|
||||
TextLabel *body_;
|
||||
|
||||
QColor backgroundColor_;
|
||||
};
|
||||
|
||||
template<class Widget>
|
||||
|
|
Loading…
Reference in a new issue