update icons

This commit is contained in:
dranik
2026-06-05 09:00:14 +03:00
parent 0db1e52468
commit e61b1dfa11
19 changed files with 83 additions and 145 deletions
+5 -4
View File
@@ -65,10 +65,11 @@
<file>controls/text-cursor.svg</file> <file>controls/text-cursor.svg</file>
<file>controls/trash.svg</file> <file>controls/trash.svg</file>
<file>controls/x-circle.svg</file> <file>controls/x-circle.svg</file>
<file>tray/active.png</file> <file>tray/off-black.svg</file>
<file>tray/default.png</file> <file>tray/off-light.svg</file>
<file>tray/error.png</file> <file>tray/on-black.svg</file>
<file>tray/icon.svg</file> <file>tray/on-white.svg</file>
<file>tray/error.svg</file>
<file>controls/monitor.svg</file> <file>controls/monitor.svg</file>
</qresource> </qresource>
</RCC> </RCC>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.6 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.5 KiB

@@ -32,9 +32,7 @@ void LinuxTrayIconBackend::show()
void LinuxTrayIconBackend::applyVisual(const TrayIconVisual &visual) void LinuxTrayIconBackend::applyVisual(const TrayIconVisual &visual)
{ {
const qreal opacity = TrayIconCommon::opacityForState(visual.connectionState); const QIcon icon = buildTrayIcon(visual.connectionState, visual.darkTheme);
const QColor indicatorColor = TrayIconCommon::indicatorColorForState(visual.connectionState);
const QIcon icon = buildTrayIcon(opacity, visual.darkTheme, indicatorColor);
// Some tray implementations cache the first icon; clear before applying an update. // Some tray implementations cache the first icon; clear before applying an update.
if (m_trayIcon.isVisible()) { if (m_trayIcon.isVisible()) {
@@ -47,8 +45,7 @@ void LinuxTrayIconBackend::showMessage(const QString &title, const QString &mess
int timerMsec) int timerMsec)
{ {
m_trayIcon.showMessage(title, message, m_trayIcon.showMessage(title, message,
buildTrayIcon(TrayIconCommon::kConnectedOpacity, visual.darkTheme, buildTrayIcon(Vpn::ConnectionState::Connected, visual.darkTheme),
TrayIconCommon::indicatorColorForState(Vpn::ConnectionState::Connected)),
timerMsec); timerMsec);
} }
@@ -66,11 +63,11 @@ void LinuxTrayIconBackend::setActivatedHandler(std::function<void(QSystemTrayIco
[handler](QSystemTrayIcon::ActivationReason reason) { handler(reason); }); [handler](QSystemTrayIcon::ActivationReason reason) { handler(reason); });
} }
QIcon LinuxTrayIconBackend::buildTrayIcon(qreal opacity, bool darkTheme, const QColor &indicatorColor) const QIcon LinuxTrayIconBackend::buildTrayIcon(Vpn::ConnectionState state, bool darkTheme) const
{ {
QIcon icon; QIcon icon;
for (int size : kLinuxTrayIconSizes) { for (int size : kLinuxTrayIconSizes) {
icon.addPixmap(TrayIconCommon::buildPixmap(size, opacity, darkTheme, indicatorColor)); icon.addPixmap(TrayIconCommon::buildPixmap(size, state, darkTheme));
} }
return icon; return icon;
} }
@@ -21,7 +21,7 @@ public:
void setActivatedHandler(std::function<void(QSystemTrayIcon::ActivationReason)> handler) override; void setActivatedHandler(std::function<void(QSystemTrayIcon::ActivationReason)> handler) override;
private: private:
QIcon buildTrayIcon(qreal opacity, bool darkTheme, const QColor &indicatorColor) const; QIcon buildTrayIcon(Vpn::ConnectionState state, bool darkTheme) const;
QSystemTrayIcon m_trayIcon; QSystemTrayIcon m_trayIcon;
}; };
-1
View File
@@ -23,7 +23,6 @@ class MacOSStatusIcon final : public QObject {
public: public:
void setIcon(const QString& iconUrl); void setIcon(const QString& iconUrl);
void setIconFromData(const QByteArray& imageData); void setIconFromData(const QByteArray& imageData);
void setIndicatorColor(const QColor& indicatorColor);
void setMenu(QMenu* menu); void setMenu(QMenu* menu);
void rebuildNativeMenu(); void rebuildNativeMenu();
void setToolTip(const QString& tooltip); void setToolTip(const QString& tooltip);
+4 -53
View File
@@ -29,21 +29,17 @@
@end @end
/** /**
* Creates a NSStatusItem with that can hold an icon. Additionally a NSView is * Creates a NSStatusItem that holds the tray icon. The icon is set as a
* set as a subview to the button item of the status item. The view serves as * template image, so the system controls its effective appearance for the
* an indicator that can be displayed in color eventhough the icon is set as a * current menu bar theme. The connection status is baked into the artwork, so
* template. In that way we give the system control over its effective * no separate colored indicator is drawn.
* appearance.
*/ */
@interface MacOSStatusIconDelegate : NSObject @interface MacOSStatusIconDelegate : NSObject
@property(assign) NSStatusItem* statusItem; @property(assign) NSStatusItem* statusItem;
@property(assign) NSView* statusIndicator;
@property(retain) NSMenu* nativeMenu; @property(retain) NSMenu* nativeMenu;
@property(retain) NSMutableArray* menuActionTargets; @property(retain) NSMutableArray* menuActionTargets;
- (void)setIcon:(NSData*)imageData; - (void)setIcon:(NSData*)imageData;
- (void)setIndicator;
- (void)setIndicatorColor:(NSColor*)color;
- (void)setToolTip:(NSString*)tooltip; - (void)setToolTip:(NSString*)tooltip;
- (void)rebuildMenuFromQMenu:(QMenu*)menu; - (void)rebuildMenuFromQMenu:(QMenu*)menu;
@end @end
@@ -62,8 +58,6 @@
self.statusItem = self.statusItem =
[[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain]; [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
self.statusItem.visible = true; self.statusItem.visible = true;
// Add the indicator as a subview
[self setIndicator];
return self; return self;
} }
@@ -87,34 +81,6 @@
[image release]; [image release];
} }
/**
* Adds status indicator as a subview to the status item button.
*/
- (void)setIndicator {
float viewHeight = NSHeight([self.statusItem.button bounds]);
float dotSize = viewHeight * 0.35;
float dotOrigin = (viewHeight - dotSize) * 0.8;
NSView* dot = [[NSView alloc] initWithFrame:NSMakeRect(dotOrigin, dotOrigin, dotSize, dotSize)];
self.statusIndicator = dot;
self.statusIndicator.wantsLayer = true;
self.statusIndicator.layer.cornerRadius = dotSize * 0.5;
[self.statusItem.button addSubview:self.statusIndicator];
[dot release];
}
/**
* Sets the color if the indicator.
*
* @param color The indicator background color.
*/
- (void)setIndicatorColor:(NSColor*)color {
if (self.statusIndicator) {
self.statusIndicator.layer.backgroundColor = color.CGColor;
}
}
/** /**
* Sets the status bar menu to the status item. * Sets the status bar menu to the status item.
* *
@@ -217,21 +183,6 @@ void MacOSStatusIcon::setIconFromData(const QByteArray& imageData) {
[m_statusBarIcon setIcon:data]; [m_statusBarIcon setIcon:data];
} }
void MacOSStatusIcon::setIndicatorColor(const QColor& indicatorColor) {
logger.debug() << "Set indicator color";
if (!indicatorColor.isValid()) {
[m_statusBarIcon setIndicatorColor:[NSColor clearColor]];
return;
}
NSColor* color = [NSColor colorWithCalibratedRed:indicatorColor.red() / 255.0f
green:indicatorColor.green() / 255.0f
blue:indicatorColor.blue() / 255.0f
alpha:indicatorColor.alpha() / 255.0f];
[m_statusBarIcon setIndicatorColor:color];
}
void MacOSStatusIcon::setMenu(QMenu* menu) { void MacOSStatusIcon::setMenu(QMenu* menu) {
m_qtMenu = menu; m_qtMenu = menu;
rebuildNativeMenu(); rebuildNativeMenu();
+1 -3
View File
@@ -23,9 +23,7 @@ void MacTrayIconBackend::show()
void MacTrayIconBackend::applyVisual(const TrayIconVisual &visual) void MacTrayIconBackend::applyVisual(const TrayIconVisual &visual)
{ {
const qreal opacity = TrayIconCommon::opacityForState(visual.connectionState); m_statusIcon.setIconFromData(TrayIconCommon::buildTemplatePng(visual.connectionState));
m_statusIcon.setIconFromData(TrayIconCommon::buildTemplatePng(opacity));
m_statusIcon.setIndicatorColor(TrayIconCommon::indicatorColorForState(visual.connectionState));
} }
void MacTrayIconBackend::showMessage(const QString &title, const QString &message, const TrayIconVisual &visual, int timerMsec) void MacTrayIconBackend::showMessage(const QString &title, const QString &message, const TrayIconVisual &visual, int timerMsec)
+4 -7
View File
@@ -6,22 +6,19 @@
namespace WinTrayIcon namespace WinTrayIcon
{ {
QIcon buildIcon(qreal opacity, bool darkTheme, const QColor &indicatorColor) QIcon buildIcon(Vpn::ConnectionState state, bool darkTheme)
{ {
return TrayIconCommon::buildIcon(opacity, darkTheme, indicatorColor); return TrayIconCommon::buildIcon(state, darkTheme);
} }
void applyTo(QSystemTrayIcon &trayIcon, Vpn::ConnectionState state, bool darkTheme) void applyTo(QSystemTrayIcon &trayIcon, Vpn::ConnectionState state, bool darkTheme)
{ {
const qreal opacity = TrayIconCommon::opacityForState(state); trayIcon.setIcon(buildIcon(state, darkTheme));
const QColor indicatorColor = TrayIconCommon::indicatorColorForState(state);
trayIcon.setIcon(buildIcon(opacity, darkTheme, indicatorColor));
} }
QIcon buildNotifyIcon(bool darkTheme) QIcon buildNotifyIcon(bool darkTheme)
{ {
return buildIcon(TrayIconCommon::kConnectedOpacity, darkTheme, return buildIcon(Vpn::ConnectionState::Connected, darkTheme);
TrayIconCommon::indicatorColorForState(Vpn::ConnectionState::Connected));
} }
void configure(QSystemTrayIcon &trayIcon, QMenu *menu, const QString &tooltip) void configure(QSystemTrayIcon &trayIcon, QMenu *menu, const QString &tooltip)
+1 -1
View File
@@ -12,7 +12,7 @@ class QString;
namespace WinTrayIcon namespace WinTrayIcon
{ {
QIcon buildIcon(qreal opacity, bool darkTheme, const QColor &indicatorColor); QIcon buildIcon(Vpn::ConnectionState state, bool darkTheme);
void applyTo(QSystemTrayIcon &trayIcon, Vpn::ConnectionState state, bool darkTheme); void applyTo(QSystemTrayIcon &trayIcon, Vpn::ConnectionState state, bool darkTheme);
QIcon buildNotifyIcon(bool darkTheme); QIcon buildNotifyIcon(bool darkTheme);
+14 -52
View File
@@ -7,93 +7,55 @@
namespace TrayIconCommon namespace TrayIconCommon
{ {
qreal opacityForState(Vpn::ConnectionState state) QString resourcePathForState(Vpn::ConnectionState state, bool darkTheme)
{ {
switch (state) { switch (state) {
case Vpn::ConnectionState::Error:
return QString::fromLatin1(kIconError);
case Vpn::ConnectionState::Connected: case Vpn::ConnectionState::Connected:
case Vpn::ConnectionState::Error: return kConnectedOpacity; return QString::fromLatin1(darkTheme ? kIconOnWhite : kIconOnBlack);
case Vpn::ConnectionState::Disconnected: case Vpn::ConnectionState::Disconnected:
case Vpn::ConnectionState::Preparing: case Vpn::ConnectionState::Preparing:
case Vpn::ConnectionState::Connecting: case Vpn::ConnectionState::Connecting:
case Vpn::ConnectionState::Disconnecting: case Vpn::ConnectionState::Disconnecting:
case Vpn::ConnectionState::Reconnecting: case Vpn::ConnectionState::Reconnecting:
case Vpn::ConnectionState::Unknown: case Vpn::ConnectionState::Unknown:
default: return kDisconnectedOpacity; default:
return QString::fromLatin1(darkTheme ? kIconOffLight : kIconOffBlack);
} }
} }
QColor indicatorColorForState(Vpn::ConnectionState state) QPixmap renderIcon(const QString &resourcePath, int size)
{
switch (state) {
case Vpn::ConnectionState::Connected: return QColor(52, 199, 89);
case Vpn::ConnectionState::Error: return QColor(235, 87, 87);
default: return QColor();
}
}
QPixmap renderTemplate(const QString &resourcePath, qreal opacity, int size)
{ {
QSvgRenderer renderer(resourcePath); QSvgRenderer renderer(resourcePath);
QPixmap pixmap(size, size); QPixmap pixmap(size, size);
pixmap.fill(Qt::transparent); pixmap.fill(Qt::transparent);
if (!renderer.isValid()) { if (!renderer.isValid()) {
qWarning() << "Failed to load tray icon template:" << resourcePath; qWarning() << "Failed to load tray icon:" << resourcePath;
return pixmap; return pixmap;
} }
QPainter painter(&pixmap); QPainter painter(&pixmap);
painter.setOpacity(opacity);
renderer.render(&painter, QRectF(0, 0, size, size)); renderer.render(&painter, QRectF(0, 0, size, size));
return pixmap; return pixmap;
} }
QPixmap colorizeTemplate(const QPixmap &mask, const QColor &foreground, int size) QPixmap buildPixmap(int size, Vpn::ConnectionState state, bool darkTheme)
{ {
QPixmap result(size, size); return renderIcon(resourcePathForState(state, darkTheme), size);
result.fill(Qt::transparent);
QPainter painter(&result);
painter.fillRect(result.rect(), foreground);
painter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
painter.drawPixmap(0, 0, mask);
return result;
} }
void drawStatusIndicator(QPainter &painter, const QColor &color, int size) QIcon buildIcon(Vpn::ConnectionState state, bool darkTheme)
{
const qreal dotSize = size * 0.35;
const qreal dotOrigin = (size - dotSize) * 0.8;
painter.setPen(Qt::NoPen);
painter.setBrush(color);
painter.drawEllipse(QRectF(dotOrigin, dotOrigin, dotSize, dotSize));
}
QPixmap buildPixmap(int size, qreal opacity, bool darkTheme, const QColor &indicatorColor)
{
const QPixmap mask = renderTemplate(QString::fromLatin1(kTrayTemplateIconPath), opacity, size);
const QColor foreground = darkTheme ? Qt::white : Qt::black;
QPixmap pixmap = colorizeTemplate(mask, foreground, size);
if (indicatorColor.isValid()) {
QPainter painter(&pixmap);
drawStatusIndicator(painter, indicatorColor, size);
}
return pixmap;
}
QIcon buildIcon(qreal opacity, bool darkTheme, const QColor &indicatorColor)
{ {
QIcon icon; QIcon icon;
icon.addPixmap(buildPixmap(kDefaultTrayIconSize, opacity, darkTheme, indicatorColor)); icon.addPixmap(buildPixmap(kDefaultTrayIconSize, state, darkTheme));
return icon; return icon;
} }
QByteArray buildTemplatePng(qreal opacity) QByteArray buildTemplatePng(Vpn::ConnectionState state)
{ {
const QPixmap pixmap = renderTemplate(QString::fromLatin1(kTrayTemplateIconPath), opacity, kDefaultTrayIconSize); const QPixmap pixmap = renderIcon(resourcePathForState(state, /*darkTheme*/ true), kDefaultTrayIconSize);
QByteArray bytes; QByteArray bytes;
QBuffer buffer(&bytes); QBuffer buffer(&bytes);
+11 -13
View File
@@ -2,31 +2,29 @@
#define TRAYICONCOMMON_H #define TRAYICONCOMMON_H
#include <QByteArray> #include <QByteArray>
#include <QColor>
#include <QIcon> #include <QIcon>
#include <QPainter>
#include <QPixmap> #include <QPixmap>
#include <QString>
#include "core/protocols/vpnProtocol.h" #include "core/protocols/vpnProtocol.h"
namespace TrayIconCommon namespace TrayIconCommon
{ {
constexpr int kDefaultTrayIconSize = 128; constexpr int kDefaultTrayIconSize = 128;
constexpr char kTrayTemplateIconPath[] = ":/images/tray/icon.svg";
constexpr qreal kDisconnectedOpacity = 0.5; constexpr char kIconOffBlack[] = ":/images/tray/off-black.svg";
constexpr qreal kConnectedOpacity = 1.0; constexpr char kIconOffLight[] = ":/images/tray/off-light.svg";
constexpr char kIconOnBlack[] = ":/images/tray/on-black.svg";
constexpr char kIconOnWhite[] = ":/images/tray/on-white.svg";
constexpr char kIconError[] = ":/images/tray/error.svg";
qreal opacityForState(Vpn::ConnectionState state); QString resourcePathForState(Vpn::ConnectionState state, bool darkTheme);
QColor indicatorColorForState(Vpn::ConnectionState state);
QPixmap renderTemplate(const QString &resourcePath, qreal opacity, int size); QPixmap renderIcon(const QString &resourcePath, int size);
QPixmap colorizeTemplate(const QPixmap &mask, const QColor &foreground, int size);
void drawStatusIndicator(QPainter &painter, const QColor &color, int size);
QPixmap buildPixmap(int size, qreal opacity, bool darkTheme, const QColor &indicatorColor); QPixmap buildPixmap(int size, Vpn::ConnectionState state, bool darkTheme);
QIcon buildIcon(qreal opacity, bool darkTheme, const QColor &indicatorColor); QIcon buildIcon(Vpn::ConnectionState state, bool darkTheme);
QByteArray buildTemplatePng(qreal opacity); QByteArray buildTemplatePng(Vpn::ConnectionState state);
} // namespace TrayIconCommon } // namespace TrayIconCommon