Allow customizing foreground color of status icons

This commit is contained in:
Martchus 2019-05-21 17:56:08 +02:00
parent 0e7cc8ba9b
commit 96ed888e20
5 changed files with 120 additions and 88 deletions

View File

@ -8,10 +8,28 @@
namespace Data { namespace Data {
/*! /*!
* \brief Generates the SVG code for the Syncthing icon with the specified colors and status emblem. * \brief Generates the SVG code for the Syncthing icon with the specified \a colors and status emblem.
*/ */
QByteArray makeSyncthingIcon(const GradientColor &gradientColor, StatusEmblem statusEmblem) QByteArray makeSyncthingIcon(const StatusIconColorSet &colors, StatusEmblem statusEmblem)
{ {
// serialize colors
auto gradientStartColor = colors.backgroundStart.name(QColor::HexRgb);
auto gradientEndColor = colors.backgroundEnd.name(QColor::HexRgb);
if (colors.backgroundStart.alphaF() < 1.0) {
gradientStartColor += QStringLiteral(";stop-opacity:") + QString::number(colors.backgroundStart.alphaF());
}
if (colors.backgroundEnd.alphaF() < 1.0) {
gradientEndColor += QStringLiteral(";stop-opacity:") + QString::number(colors.backgroundEnd.alphaF());
}
auto fillColor = colors.foreground.name(QColor::HexRgb);
auto strokeColor = fillColor;
if (colors.foreground.alphaF() < 1.0) {
const auto alpha = QString::number(colors.foreground.alphaF());
fillColor += QStringLiteral(";fill-opacity:") + alpha;
strokeColor += QStringLiteral(";stroke-opacity:") + alpha;
}
// make SVG document
// clang-format off // clang-format off
static const QString emblems[] = { static const QString emblems[] = {
QString(), QString(),
@ -60,21 +78,13 @@ QByteArray makeSyncthingIcon(const GradientColor &gradientColor, StatusEmblem st
), ),
}; };
const auto &emblemData = emblems[static_cast<int>(statusEmblem)]; const auto &emblemData = emblems[static_cast<int>(statusEmblem)];
auto gradientStart = gradientColor.start.name(QColor::HexRgb);
auto gradientEnd = gradientColor.end.name(QColor::HexRgb);
if (gradientColor.start.alphaF() < 1.0) {
gradientStart += QStringLiteral(";stop-opacity:") + QString::number(gradientColor.start.alphaF());
}
if (gradientColor.end.alphaF() < 1.0) {
gradientEnd += QStringLiteral(";stop-opacity:") + QString::number(gradientColor.end.alphaF());
}
return (QStringLiteral( return (QStringLiteral(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
"<svg xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\">" "<svg xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 16 16\">"
"<defs>" "<defs>"
"<linearGradient id=\"grad\" gradientUnits=\"userSpaceOnUse\" x1=\"8\" y1=\"0\" x2=\"8\" y2=\"16\">" "<linearGradient id=\"grad\" gradientUnits=\"userSpaceOnUse\" x1=\"8\" y1=\"0\" x2=\"8\" y2=\"16\">"
"<stop offset=\"0\" style=\"stop-color:") % gradientStart % QStringLiteral("\"/>" "<stop offset=\"0\" style=\"stop-color:") % gradientStartColor % QStringLiteral("\"/>"
"<stop offset=\"1\" style=\"stop-color:") % gradientEnd % QStringLiteral("\"/>" "<stop offset=\"1\" style=\"stop-color:") % gradientEndColor % QStringLiteral("\"/>"
"</linearGradient>" "</linearGradient>"
"<mask id=\"bitemask\" maskUnits=\"userSpaceOnUse\">" "<mask id=\"bitemask\" maskUnits=\"userSpaceOnUse\">"
"<g>" "<g>"
@ -85,14 +95,14 @@ QByteArray makeSyncthingIcon(const GradientColor &gradientColor, StatusEmblem st
"</defs>" "</defs>"
"<g id=\"syncthing-logo\" mask=\"url(#bitemask)\">" "<g id=\"syncthing-logo\" mask=\"url(#bitemask)\">"
"<circle id=\"outer\" cx=\"8\" cy=\"8\" r=\"8\" style=\"fill:url(#grad)\"/>" "<circle id=\"outer\" cx=\"8\" cy=\"8\" r=\"8\" style=\"fill:url(#grad)\"/>"
"<circle id=\"inner\" cx=\"8\" cy=\"7.9727402\" r=\"5.9557071\" style=\"fill:none;stroke:#ffffff;stroke-width:0.81771719\"/>" "<circle id=\"inner\" cx=\"8\" cy=\"7.9727402\" r=\"5.9557071\" style=\"fill:none;stroke:") % strokeColor % QStringLiteral(";stroke-width:0.81771719\"/>"
"<line id=\"arm-l\" x1=\"9.1993189\" y1=\"8.776825\" x2=\"2.262351\" y2=\"9.4173737\" style=\"stroke:#ffffff;stroke-width:0.81771719\"/>" "<line id=\"arm-l\" x1=\"9.1993189\" y1=\"8.776825\" x2=\"2.262351\" y2=\"9.4173737\" style=\"stroke:") % strokeColor % QStringLiteral(";stroke-width:0.81771719\"/>"
"<line id=\"arm-tr\" x1=\"9.1993189\" y1=\"8.776825\" x2=\"13.301533\" y2=\"5.3696747\" style=\"stroke:#ffffff;stroke-width:0.81771719\"/>" "<line id=\"arm-tr\" x1=\"9.1993189\" y1=\"8.776825\" x2=\"13.301533\" y2=\"5.3696747\" style=\"stroke:") % strokeColor % QStringLiteral(";stroke-width:0.81771719\"/>"
"<line id=\"arm-br\" x1=\"9.1993189\" y1=\"8.776825\" x2=\"11.788756\" y2=\"12.51107\" style=\"stroke:#ffffff;stroke-width:0.81771719\"/>" "<line id=\"arm-br\" x1=\"9.1993189\" y1=\"8.776825\" x2=\"11.788756\" y2=\"12.51107\" style=\"stroke:") % strokeColor % QStringLiteral(";stroke-width:0.81771719\"/>"
"<circle id=\"node-c\" cx=\"9.1993189\" cy=\"8.776825\" r=\"1.22\" style=\"fill:#ffffff\"/>" "<circle id=\"node-c\" cx=\"9.1993189\" cy=\"8.776825\" r=\"1.22\" style=\"fill:") % fillColor % QStringLiteral("\"/>"
"<circle id=\"node-l\" cx=\"2.262351\" cy=\"9.4173737\" r=\"1.22\" style=\"fill:#ffffff\"/>" "<circle id=\"node-l\" cx=\"2.262351\" cy=\"9.4173737\" r=\"1.22\" style=\"fill:") % fillColor % QStringLiteral("\"/>"
"<circle id=\"node-tr\" cx=\"13.301533\" cy=\"5.3696747\" r=\"1.22\" style=\"fill:#ffffff\"/>" "<circle id=\"node-tr\" cx=\"13.301533\" cy=\"5.3696747\" r=\"1.22\" style=\"fill:") % fillColor % QStringLiteral("\"/>"
"<circle id=\"node-br\" cx=\"11.788756\" cy=\"12.51107\" r=\"1.22\" style=\"fill:#ffffff\"/>" "<circle id=\"node-br\" cx=\"11.788756\" cy=\"12.51107\" r=\"1.22\" style=\"fill:") % fillColor % QStringLiteral("\"/>"
"</g>") % "</g>") %
(emblemData.isEmpty() ? QString() : emblemData) % QStringLiteral( (emblemData.isEmpty() ? QString() : emblemData) % QStringLiteral(
"</svg>" "</svg>"
@ -165,14 +175,14 @@ QByteArray loadFontAwesomeIcon(const QString &iconName, const QColor &color, boo
} }
StatusIconSettings::StatusIconSettings() StatusIconSettings::StatusIconSettings()
: defaultColor({ QStringLiteral("#26B6DB"), QStringLiteral("#0882C8") }) : defaultColor({ QStringLiteral("#26B6DB"), QStringLiteral("#0882C8"), QStringLiteral("#FFFFFF") })
, errorColor({ QStringLiteral("#DB3C26"), QStringLiteral("#C80828") }) , errorColor({ QStringLiteral("#DB3C26"), QStringLiteral("#C80828"), QStringLiteral("#FFFFFF") })
, warningColor({ QStringLiteral("#c9ce3b"), QStringLiteral("#ebb83b") }) , warningColor({ QStringLiteral("#c9ce3b"), QStringLiteral("#ebb83b"), QStringLiteral("#FFFFFF") })
, idleColor({ QStringLiteral("#2D9D69"), QStringLiteral("#2D9D69") }) , idleColor({ QStringLiteral("#2D9D69"), QStringLiteral("#2D9D69"), QStringLiteral("#FFFFFF") })
, scanningColor({ QStringLiteral("#26B6DB"), QStringLiteral("#0882C8") }) , scanningColor({ QStringLiteral("#26B6DB"), QStringLiteral("#0882C8"), QStringLiteral("#FFFFFF") })
, synchronizingColor({ QStringLiteral("#26B6DB"), QStringLiteral("#0882C8") }) , synchronizingColor({ QStringLiteral("#26B6DB"), QStringLiteral("#0882C8"), QStringLiteral("#FFFFFF") })
, pausedColor({ QStringLiteral("#A9A9A9"), QStringLiteral("#58656C") }) , pausedColor({ QStringLiteral("#A9A9A9"), QStringLiteral("#58656C"), QStringLiteral("#FFFFFF") })
, disconnectedColor({ QStringLiteral("#A9A9A9"), QStringLiteral("#58656C") }) , disconnectedColor({ QStringLiteral("#A9A9A9"), QStringLiteral("#58656C"), QStringLiteral("#FFFFFF") })
{ {
} }
@ -202,8 +212,11 @@ StatusIconSettings::StatusIconSettings(const QString &str)
} }
const auto colors = parts[index].split(QChar(',')); const auto colors = parts[index].split(QChar(','));
if (colors.size() >= 2) { if (colors.size() >= 2) {
field->start = colors[0].toString(); field->backgroundStart = colors[0].toString();
field->end = colors[1].toString(); field->backgroundEnd = colors[1].toString();
}
if (colors.size() >= 3) {
field->foreground = colors[2].toString();
} }
++index; ++index;
} }
@ -218,7 +231,8 @@ QString StatusIconSettings::toString() const
if (!res.isEmpty()) { if (!res.isEmpty()) {
res += QChar(';'); res += QChar(';');
} }
res += field->start.name(QColor::HexArgb) % QChar(',') % field->end.name(QColor::HexArgb); res += field->backgroundStart.name(QColor::HexArgb) % QChar(',') % field->backgroundEnd.name(QColor::HexArgb) % QChar(',')
% field->foreground.name(QColor::HexArgb);
} }
return res; return res;
} }

View File

@ -22,35 +22,39 @@ enum class StatusEmblem {
Add, Add,
}; };
struct GradientColor { struct StatusIconColorSet {
GradientColor(const QColor &start, const QColor &end); StatusIconColorSet(const QColor &backgroundStart, const QColor &backgroundEnd, const QColor &foreground);
GradientColor(QColor &&start, QColor &&end); StatusIconColorSet(QColor &&backgroundStart, QColor &&backgroundEnd, QColor &&foreground);
GradientColor(const QString &start, const QString &end); StatusIconColorSet(const QString &backgroundStart, const QString &backgroundEnd, const QString &foreground);
QColor start; QColor backgroundStart;
QColor end; QColor backgroundEnd;
QColor foreground;
}; };
inline GradientColor::GradientColor(const QColor &start, const QColor &end) inline StatusIconColorSet::StatusIconColorSet(const QColor &backgroundStart, const QColor &backgroundEnd, const QColor &foreground)
: start(start) : backgroundStart(backgroundStart)
, end(end) , backgroundEnd(backgroundEnd)
, foreground(foreground)
{ {
} }
inline GradientColor::GradientColor(QColor &&start, QColor &&end) inline StatusIconColorSet::StatusIconColorSet(QColor &&backgroundStart, QColor &&backgroundEnd, QColor &&foreground)
: start(start) : backgroundStart(backgroundStart)
, end(end) , backgroundEnd(backgroundEnd)
, foreground(foreground)
{ {
} }
inline GradientColor::GradientColor(const QString &start, const QString &end) inline StatusIconColorSet::StatusIconColorSet(const QString &backgroundStart, const QString &backgroundEnd, const QString &foreground)
: start(start) : backgroundStart(backgroundStart)
, end(end) , backgroundEnd(backgroundEnd)
, foreground(foreground)
{ {
} }
QByteArray LIB_SYNCTHING_MODEL_EXPORT makeSyncthingIcon( QByteArray LIB_SYNCTHING_MODEL_EXPORT makeSyncthingIcon(
const GradientColor &gradientColor = GradientColor{ QStringLiteral("#26B6DB"), QStringLiteral("#0882C8") }, const StatusIconColorSet &colors = StatusIconColorSet{ QStringLiteral("#26B6DB"), QStringLiteral("#0882C8"), QStringLiteral("#FFFFFF") },
StatusEmblem statusEmblem = StatusEmblem::None); StatusEmblem statusEmblem = StatusEmblem::None);
QPixmap LIB_SYNCTHING_MODEL_EXPORT renderSvgImage(const QString &path, const QSize &size = QSize(128, 128), int margin = 0); QPixmap LIB_SYNCTHING_MODEL_EXPORT renderSvgImage(const QString &path, const QSize &size = QSize(128, 128), int margin = 0);
QPixmap LIB_SYNCTHING_MODEL_EXPORT renderSvgImage(const QByteArray &contents, const QSize &size = QSize(128, 128), int margin = 0); QPixmap LIB_SYNCTHING_MODEL_EXPORT renderSvgImage(const QByteArray &contents, const QSize &size = QSize(128, 128), int margin = 0);
@ -60,21 +64,21 @@ struct LIB_SYNCTHING_MODEL_EXPORT StatusIconSettings {
explicit StatusIconSettings(); explicit StatusIconSettings();
explicit StatusIconSettings(const QString &str); explicit StatusIconSettings(const QString &str);
GradientColor defaultColor; StatusIconColorSet defaultColor;
GradientColor errorColor; StatusIconColorSet errorColor;
GradientColor warningColor; StatusIconColorSet warningColor;
GradientColor idleColor; StatusIconColorSet idleColor;
GradientColor scanningColor; StatusIconColorSet scanningColor;
GradientColor synchronizingColor; StatusIconColorSet synchronizingColor;
GradientColor pausedColor; StatusIconColorSet pausedColor;
GradientColor disconnectedColor; StatusIconColorSet disconnectedColor;
static constexpr auto distinguishableColorCount = 8; static constexpr auto distinguishableColorCount = 8;
struct ColorMapping { struct ColorMapping {
QString colorName; QString colorName;
StatusEmblem defaultEmblem; StatusEmblem defaultEmblem;
GradientColor &setting; StatusIconColorSet &setting;
}; };
std::vector<ColorMapping> colorMapping(); std::vector<ColorMapping> colorMapping();
QString toString() const; QString toString() const;

View File

@ -2,14 +2,6 @@
<ui version="4.0"> <ui version="4.0">
<class>QtGui::IconsOptionPage</class> <class>QtGui::IconsOptionPage</class>
<widget class="QWidget" name="QtGui::IconsOptionPage"> <widget class="QWidget" name="QtGui::IconsOptionPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>538</width>
<height>167</height>
</rect>
</property>
<property name="windowTitle"> <property name="windowTitle">
<string>Icons</string> <string>Icons</string>
</property> </property>
@ -19,17 +11,17 @@
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<widget class="QGroupBox" name="statusColorsGroupBox"> <widget class="QGroupBox" name="statusIconsGroupBox">
<property name="title"> <property name="title">
<string>Status colors</string> <string>Status icons</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_2">
<item> <item>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="0" column="1"> <item row="0" column="1">
<widget class="QLabel" name="color1Label"> <widget class="QLabel" name="bgColor1Label">
<property name="text"> <property name="text">
<string>Color 1</string> <string>Background color 1</string>
</property> </property>
<property name="alignment"> <property name="alignment">
<set>Qt::AlignCenter</set> <set>Qt::AlignCenter</set>
@ -37,16 +29,16 @@
</widget> </widget>
</item> </item>
<item row="0" column="2"> <item row="0" column="2">
<widget class="QLabel" name="color2Label"> <widget class="QLabel" name="bgColor2Label">
<property name="text"> <property name="text">
<string>Color 2</string> <string>Background color 2</string>
</property> </property>
<property name="alignment"> <property name="alignment">
<set>Qt::AlignCenter</set> <set>Qt::AlignCenter</set>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="3"> <item row="0" column="4">
<widget class="QLabel" name="previewLabel"> <widget class="QLabel" name="previewLabel">
<property name="text"> <property name="text">
<string>Preview</string> <string>Preview</string>
@ -59,6 +51,16 @@
<item row="1" column="0"> <item row="1" column="0">
<widget class="QWidget" name="widget" native="true"/> <widget class="QWidget" name="widget" native="true"/>
</item> </item>
<item row="0" column="3">
<widget class="QLabel" name="foregroundColorLabel">
<property name="text">
<string>Foreground color</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
<item> <item>

View File

@ -508,40 +508,47 @@ QWidget *IconsOptionPage::setupWidget()
// populate form for status icon colors // populate form for status icon colors
auto *const gridLayout = ui()->gridLayout; auto *const gridLayout = ui()->gridLayout;
auto *const statusColorsGroupBox = ui()->statusColorsGroupBox; auto *const statusIconsGroupBox = ui()->statusIconsGroupBox;
int index = 0; int index = 0;
for (auto &colorMapping : m_settings.colorMapping()) { for (auto &colorMapping : m_settings.colorMapping()) {
// populate widgets array // populate widgets array
auto &widgetsForColor = m_widgets[index++] = { auto &widgetsForColor = m_widgets[index++] = {
{ {
new Widgets::ColorButton(statusColorsGroupBox), new Widgets::ColorButton(statusIconsGroupBox),
new Widgets::ColorButton(statusColorsGroupBox), new Widgets::ColorButton(statusIconsGroupBox),
new Widgets::ColorButton(statusIconsGroupBox),
}, },
new QLabel(statusColorsGroupBox), new QLabel(statusIconsGroupBox),
&colorMapping.setting, &colorMapping.setting,
colorMapping.defaultEmblem, colorMapping.defaultEmblem,
}; };
// add label for color name // add label for color name
gridLayout->addWidget(new QLabel(colorMapping.colorName, statusColorsGroupBox), index, 0, Qt::AlignRight | Qt::AlignVCenter); gridLayout->addWidget(new QLabel(colorMapping.colorName, statusIconsGroupBox), index, 0, Qt::AlignRight | Qt::AlignVCenter);
// setup preview // setup preview
gridLayout->addWidget(widgetsForColor.previewLabel, index, 3, Qt::AlignCenter); gridLayout->addWidget(widgetsForColor.previewLabel, index, 4, Qt::AlignCenter);
const auto updatePreview = [&widgetsForColor] { const auto updatePreview = [&widgetsForColor] {
widgetsForColor.previewLabel->setPixmap( widgetsForColor.previewLabel->setPixmap(renderSvgImage(makeSyncthingIcon(
renderSvgImage(makeSyncthingIcon(GradientColor{ widgetsForColor.colorButtons[0]->color(), widgetsForColor.colorButtons[1]->color() }, StatusIconColorSet{
widgetsForColor.statusEmblem), widgetsForColor.colorButtons[0]->color(),
QSize(32, 32))); widgetsForColor.colorButtons[1]->color(),
widgetsForColor.colorButtons[2]->color(),
},
widgetsForColor.statusEmblem),
QSize(32, 32)));
}; };
for (const auto &colorButton : widgetsForColor.colorButtons) { for (const auto &colorButton : widgetsForColor.colorButtons) {
QObject::connect(colorButton, &Widgets::ColorButton::colorChanged, updatePreview); QObject::connect(colorButton, &Widgets::ColorButton::colorChanged, updatePreview);
} }
// setup color buttons // setup color buttons
widgetsForColor.colorButtons[0]->setColor(colorMapping.setting.start); widgetsForColor.colorButtons[0]->setColor(colorMapping.setting.backgroundStart);
widgetsForColor.colorButtons[1]->setColor(colorMapping.setting.end); widgetsForColor.colorButtons[1]->setColor(colorMapping.setting.backgroundEnd);
widgetsForColor.colorButtons[2]->setColor(colorMapping.setting.foreground);
gridLayout->addWidget(widgetsForColor.colorButtons[0], index, 1); gridLayout->addWidget(widgetsForColor.colorButtons[0], index, 1);
gridLayout->addWidget(widgetsForColor.colorButtons[1], index, 2); gridLayout->addWidget(widgetsForColor.colorButtons[1], index, 2);
gridLayout->addWidget(widgetsForColor.colorButtons[2], index, 3);
if (index >= StatusIconSettings::distinguishableColorCount) { if (index >= StatusIconSettings::distinguishableColorCount) {
break; break;
@ -561,7 +568,11 @@ QWidget *IconsOptionPage::setupWidget()
bool IconsOptionPage::apply() bool IconsOptionPage::apply()
{ {
for (auto &widgetsForColor : m_widgets) { for (auto &widgetsForColor : m_widgets) {
*widgetsForColor.setting = GradientColor{ widgetsForColor.colorButtons[0]->color(), widgetsForColor.colorButtons[1]->color() }; *widgetsForColor.setting = StatusIconColorSet{
widgetsForColor.colorButtons[0]->color(),
widgetsForColor.colorButtons[1]->color(),
widgetsForColor.colorButtons[2]->color(),
};
} }
values().statusIcons = m_settings; values().statusIcons = m_settings;
return true; return true;
@ -570,8 +581,9 @@ bool IconsOptionPage::apply()
void IconsOptionPage::update() void IconsOptionPage::update()
{ {
for (auto &widgetsForColor : m_widgets) { for (auto &widgetsForColor : m_widgets) {
widgetsForColor.colorButtons[0]->setColor(widgetsForColor.setting->start); widgetsForColor.colorButtons[0]->setColor(widgetsForColor.setting->backgroundStart);
widgetsForColor.colorButtons[1]->setColor(widgetsForColor.setting->end); widgetsForColor.colorButtons[1]->setColor(widgetsForColor.setting->backgroundEnd);
widgetsForColor.colorButtons[2]->setColor(widgetsForColor.setting->foreground);
} }
} }

View File

@ -84,9 +84,9 @@ private:
void update(); void update();
Data::StatusIconSettings m_settings; Data::StatusIconSettings m_settings;
struct { struct {
Widgets::ColorButton *colorButtons[2] = {}; Widgets::ColorButton *colorButtons[3] = {};
QLabel *previewLabel = nullptr; QLabel *previewLabel = nullptr;
Data::GradientColor *setting = nullptr; Data::StatusIconColorSet *setting = nullptr;
Data::StatusEmblem statusEmblem = Data::StatusEmblem::None; Data::StatusEmblem statusEmblem = Data::StatusEmblem::None;
} m_widgets[Data::StatusIconSettings::distinguishableColorCount]; } m_widgets[Data::StatusIconSettings::distinguishableColorCount];
END_DECLARE_OPTION_PAGE END_DECLARE_OPTION_PAGE