Skip to content

Commit 00f590d

Browse files
PastaPastaPastathepastaclaw
authored andcommitted
Merge dashpay#7180: qt: add Tahoe styled icons for macOS, runtime styling for each network type, update bundle icon, add mask-based tray icon, generation scripts
54cde9b qt: drop unused networkId param in `networkstyle.{cpp,h}` (UdjinM6) 6c23752 qt: generate tray icons for macOS, update script, set at runtime (Kittywhiskers Van Gogh) 4a1e939 qt: apply new runtime icons if running on macOS (Kittywhiskers Van Gogh) 3bc2ee6 qt: generate macOS icon assets, commit resizing and creation script (Kittywhiskers Van Gogh) 0bef769 res: add 8-bit color Icon Composer exports for Tahoe styled icons (Kittywhiskers Van Gogh) Pull request description: ## Additional Information ### Runtime Icons | | mainnet | testnet | regtest | devnet | |-|-|-|-|-| | **`develop`** | ![](https://github.com/user-attachments/assets/85aebf23-6f62-44a6-95ae-36ed95d427e3) | ![](https://github.com/user-attachments/assets/f1c59195-5197-4eae-b5ac-ce44998ccecb) | ![](https://github.com/user-attachments/assets/5f6ed3de-dfe8-4e62-90eb-a8e4733ab206) | ![](https://github.com/user-attachments/assets/19df389a-e551-4c45-8290-5fbba96a282f) | | **This PR** | ![](https://github.com/user-attachments/assets/82446f92-d703-4588-8659-9ed135504a30) | ![](https://github.com/user-attachments/assets/1f679583-dfae-4c98-b2fb-722bd91dd959) | ![](https://github.com/user-attachments/assets/e00894f7-ad79-4731-8223-f7a447930d6c) | ![](https://github.com/user-attachments/assets/d2691dc4-cf0d-4473-94d2-60f6d608e4c8) | ### Tray | | `develop` | This PR | | - | -------- | ------- | | **Dark Mode** | ![](https://github.com/user-attachments/assets/93f4988f-e2fa-4f8e-a8d4-8a24577b2c93) | ![](https://github.com/user-attachments/assets/78c3eb86-531c-4125-9739-c6ffe3273f43) | | **Light Mode** | See above | ![](https://github.com/user-attachments/assets/4e540e9c-775f-43d1-953b-e6a155afed95) | ## Breaking Changes None expected. ## Checklist - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have added or updated relevant unit/integration/functional/e2e tests **(note: N/A)** - [x] I have made corresponding changes to the documentation **(note: N/A)** - [x] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_ ACKs for top commit: PastaPastaPasta: utACK 54cde9b UdjinM6: utACK 54cde9b Tree-SHA512: af23ab7558b724f192f9b4107cbbbe60098274feec53e31e0b4a8e964ae8a05c64315247c8a3a46ae3ef1e957b3301bed12dad69adfbb8c498862ebbb530a87a
1 parent 60dda51 commit 00f590d

18 files changed

+180
-9
lines changed
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2026 The Dash Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
6+
import os
7+
import platform
8+
import shutil
9+
import subprocess
10+
import sys
11+
import tempfile
12+
13+
# Assuming 1024x1024 canvas, the squircle content area is ~864x864 with
14+
# ~80px transparent padding on each side
15+
CONTENT_RATIO = 864 / 1024
16+
17+
DIR_ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", ".."))
18+
DIR_SRC = os.path.join(DIR_ROOT, "src", "qt", "res", "src")
19+
DIR_OUT = os.path.join(DIR_ROOT, "src", "qt", "res", "icons")
20+
21+
# Icon Composer exports to runtime icon map
22+
ICONS = [
23+
("macos_devnet.png", "dash_macos_devnet.png"),
24+
("macos_mainnet.png", "dash_macos_mainnet.png"),
25+
("macos_regtest.png", "dash_macos_regtest.png"),
26+
("macos_testnet.png", "dash_macos_testnet.png"),
27+
]
28+
TRAY = os.path.join(DIR_SRC, "tray.svg")
29+
30+
# Canvas to filename mapping for bundle icon
31+
ICNS_MAP = [
32+
(16, "icon_16x16.png"),
33+
(32, "icon_16x16@2x.png"),
34+
(32, "icon_32x32.png"),
35+
(64, "icon_32x32@2x.png"),
36+
(128, "icon_128x128.png"),
37+
(256, "icon_128x128@2x.png"),
38+
(256, "icon_256x256.png"),
39+
(512, "icon_256x256@2x.png"),
40+
(512, "icon_512x512.png"),
41+
(1024, "icon_512x512@2x.png"),
42+
]
43+
44+
# Maximum height of canvas is 22pt, we use max height instead of recommended
45+
# 16pt canvas to prevent the icon from looking undersized due to icon width.
46+
# See https://bjango.com/articles/designingmenubarextras/
47+
TRAY_MAP = [
48+
(22, "dash_macos_tray.png"),
49+
(44, "dash_macos_tray@2x.png")
50+
]
51+
52+
53+
def sips_resample_padded(src, dst, canvas_size):
54+
content_size = max(round(canvas_size * CONTENT_RATIO), 1)
55+
subprocess.check_call(
56+
["sips", "-z", str(content_size), str(content_size), "-p", str(canvas_size), str(canvas_size), src, "--out", dst],
57+
stdout=subprocess.DEVNULL,
58+
)
59+
60+
61+
def sips_svg_to_png(svg_path, png_path, height):
62+
subprocess.check_call(
63+
["sips", "-s", "format", "png", "--resampleHeight", str(height), svg_path, "--out", png_path],
64+
stdout=subprocess.DEVNULL,
65+
)
66+
67+
68+
def generate_icns(tmpdir):
69+
iconset = os.path.join(tmpdir, "dash.iconset")
70+
os.makedirs(iconset)
71+
72+
src_main = os.path.join(DIR_SRC, ICONS[1][0])
73+
for canvas_px, filename in ICNS_MAP:
74+
sips_resample_padded(src_main, os.path.join(iconset, filename), canvas_px)
75+
76+
icns_out = os.path.join(DIR_OUT, "dash.icns")
77+
subprocess.check_call(["iconutil", "-c", "icns", iconset, "-o", icns_out])
78+
print(f"Created: {icns_out}")
79+
80+
81+
def check_source(path):
82+
if not os.path.isfile(path):
83+
sys.exit(f"Error: Source image not found: {path}")
84+
85+
86+
def main():
87+
if platform.system() != "Darwin":
88+
sys.exit("Error: This script requires macOS (needs sips, iconutil).")
89+
90+
for tool in ("sips", "iconutil"):
91+
if shutil.which(tool) is None:
92+
sys.exit(f"Error: '{tool}' not found. Install Xcode command-line tools.")
93+
94+
check_source(TRAY)
95+
for src_name, _ in ICONS:
96+
check_source(os.path.join(DIR_SRC, src_name))
97+
98+
os.makedirs(DIR_OUT, exist_ok=True)
99+
100+
# Generate bundle icon
101+
with tempfile.TemporaryDirectory(prefix="dash_icons_") as tmpdir:
102+
generate_icns(tmpdir)
103+
104+
# Generate runtime icons
105+
for src_name, dst_name in ICONS:
106+
src = os.path.join(DIR_SRC, src_name)
107+
dst = os.path.join(DIR_OUT, dst_name)
108+
sips_resample_padded(src, dst, 256)
109+
print(f"Created: {dst}")
110+
111+
# Generate tray icons
112+
for canvas_px, filename in TRAY_MAP:
113+
dst = os.path.join(DIR_OUT, filename)
114+
sips_svg_to_png(TRAY, dst, canvas_px)
115+
print(f"Created: {dst}")
116+
117+
if __name__ == "__main__":
118+
main()

src/Makefile.qt.include

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,12 @@ QT_RES_ICONS = \
216216
qt/res/icons/connect4_16.png \
217217
qt/res/icons/dash.ico \
218218
qt/res/icons/dash.png \
219+
qt/res/icons/dash_macos_devnet.png \
220+
qt/res/icons/dash_macos_mainnet.png \
221+
qt/res/icons/dash_macos_regtest.png \
222+
qt/res/icons/dash_macos_testnet.png \
223+
qt/res/icons/dash_macos_tray.png \
224+
qt/res/icons/dash_macos_tray@2x.png \
219225
qt/res/icons/dash_testnet.ico \
220226
qt/res/icons/editcopy.png \
221227
qt/res/icons/editpaste.png \

src/qt/bitcoingui.cpp

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,16 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const NetworkStyle* networkStyle,
116116
#ifdef ENABLE_WALLET
117117
enableWallet = WalletModel::isWalletEnabled();
118118
#endif // ENABLE_WALLET
119-
QApplication::setWindowIcon(m_network_style->getTrayAndWindowIcon());
120-
setWindowIcon(m_network_style->getTrayAndWindowIcon());
119+
120+
QIcon icon{m_network_style->getTrayAndWindowIcon()};
121+
#ifdef Q_OS_MACOS
122+
if (auto macos_icon{m_network_style->getMacIcon()}) {
123+
icon = macos_icon.value();
124+
}
125+
#endif // Q_OS_MACOS
126+
QApplication::setWindowIcon(icon);
127+
setWindowIcon(icon);
128+
121129
updateWindowTitle();
122130

123131
rpcConsole = new RPCConsole(node, this, enableWallet ? Qt::Window : Qt::Widget);
@@ -1113,7 +1121,13 @@ void BitcoinGUI::createTrayIcon()
11131121
assert(QSystemTrayIcon::isSystemTrayAvailable());
11141122

11151123
if (QSystemTrayIcon::isSystemTrayAvailable()) {
1116-
trayIcon = new QSystemTrayIcon(m_network_style->getTrayAndWindowIcon(), this);
1124+
QIcon icon{m_network_style->getTrayAndWindowIcon()};
1125+
#ifdef Q_OS_MACOS
1126+
if (auto macos_tray{m_network_style->getMacTray()}) {
1127+
icon = macos_tray.value();
1128+
}
1129+
#endif // Q_OS_MACOS
1130+
trayIcon = new QSystemTrayIcon(icon, this);
11171131
QString toolTip = tr("%1 client").arg(PACKAGE_NAME) + " " + m_network_style->getTitleAddText();
11181132
trayIcon->setToolTip(toolTip);
11191133
}

src/qt/dash.qrc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
<!DOCTYPE RCC><RCC version="1.0">
22
<qresource prefix="/icons">
33
<file alias="dash">res/icons/dash.png</file>
4+
<file alias="dash_macos_devnet">res/icons/dash_macos_devnet.png</file>
5+
<file alias="dash_macos_mainnet">res/icons/dash_macos_mainnet.png</file>
6+
<file alias="dash_macos_regtest">res/icons/dash_macos_regtest.png</file>
7+
<file alias="dash_macos_testnet">res/icons/dash_macos_testnet.png</file>
8+
<file alias="dash_macos_tray">res/icons/dash_macos_tray.png</file>
9+
<file alias="dash_macos_tray@2x">res/icons/dash_macos_tray@2x.png</file>
410
<file alias="warning">res/icons/warning.png</file>
511
<file alias="address-book">res/icons/address-book.png</file>
612
<file alias="connect_1">res/icons/connect1_16.png</file>

src/qt/networkstyle.cpp

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@ static const struct {
2121
const char *appName;
2222
const int iconColorHueShift;
2323
const int iconColorSaturationReduction;
24+
const char *macIconPath;
2425
const std::string titleAddText;
2526
} network_styles[] = {
26-
{"main", QAPP_APP_NAME_DEFAULT, 0, 0, ""},
27-
{"test", QAPP_APP_NAME_TESTNET, 190, 20, ""},
28-
{"devnet", QAPP_APP_NAME_DEVNET, 35, 15, "[devnet: %s]"},
29-
{"regtest", QAPP_APP_NAME_REGTEST, 160, 30, ""}
27+
{"main", QAPP_APP_NAME_DEFAULT, 0, 0, ":/icons/dash_macos_mainnet", ""},
28+
{"test", QAPP_APP_NAME_TESTNET, 190, 20, ":/icons/dash_macos_testnet", ""},
29+
{"devnet", QAPP_APP_NAME_DEVNET, 35, 15, ":/icons/dash_macos_devnet", "[devnet: %s]"},
30+
{"regtest", QAPP_APP_NAME_REGTEST, 160, 30, ":/icons/dash_macos_regtest", ""},
3031
};
3132

3233
void NetworkStyle::rotateColor(QColor& col, const int iconColorHueShift, const int iconColorSaturationReduction)
@@ -62,7 +63,8 @@ void NetworkStyle::rotateColors(QImage& img, const int iconColorHueShift, const
6263
}
6364

6465
// titleAddText needs to be const char* for tr()
65-
NetworkStyle::NetworkStyle(const QString &_appName, const int iconColorHueShift, const int iconColorSaturationReduction, const char *_titleAddText):
66+
NetworkStyle::NetworkStyle(const QString &_appName, const int iconColorHueShift, const int iconColorSaturationReduction,
67+
const char *_macIconPath, const char *_titleAddText):
6668
appName(_appName),
6769
titleAddText(qApp->translate("SplashScreen", _titleAddText)),
6870
badgeColor(QColor(0, 141, 228)) // default badge color is the original Dash's blue, regardless of the current theme
@@ -86,6 +88,14 @@ NetworkStyle::NetworkStyle(const QString &_appName, const int iconColorHueShift,
8688
appIcon = QIcon(appIconPixmap);
8789
trayAndWindowIcon = QIcon(appIconPixmap.scaled(QSize(256,256)));
8890
splashImage = QPixmap(":/images/splash");
91+
92+
#ifdef Q_OS_MACOS
93+
if (_macIconPath) {
94+
m_macos_icon = QIcon(QPixmap(_macIconPath));
95+
}
96+
m_macos_tray = QIcon(QPixmap(":/icons/dash_macos_tray"));
97+
m_macos_tray->setIsMask(true);
98+
#endif // Q_OS_MACOS
8999
}
90100

91101
const NetworkStyle* NetworkStyle::instantiate(const std::string& networkId)
@@ -107,6 +117,7 @@ const NetworkStyle* NetworkStyle::instantiate(const std::string& networkId)
107117
appName.c_str(),
108118
network_style.iconColorHueShift,
109119
network_style.iconColorSaturationReduction,
120+
network_style.macIconPath,
110121
titleAddText.c_str());
111122
}
112123
}

src/qt/networkstyle.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
#include <QPixmap>
1111
#include <QString>
1212

13+
#include <optional>
14+
1315
/* Coin network-specific GUI style information */
1416
class NetworkStyle
1517
{
@@ -23,16 +25,25 @@ class NetworkStyle
2325
const QIcon &getTrayAndWindowIcon() const { return trayAndWindowIcon; }
2426
const QString &getTitleAddText() const { return titleAddText; }
2527
const QColor &getBadgeColor() const { return badgeColor; }
28+
#ifdef Q_OS_MACOS
29+
std::optional<QIcon> getMacIcon() const { return m_macos_icon; }
30+
std::optional<QIcon> getMacTray() const { return m_macos_tray; }
31+
#endif // Q_OS_MACOS
2632

2733
private:
28-
NetworkStyle(const QString &appName, const int iconColorHueShift, const int iconColorSaturationReduction, const char *titleAddText);
34+
NetworkStyle(const QString &appName, const int iconColorHueShift, const int iconColorSaturationReduction,
35+
const char *macIconPath, const char *titleAddText);
2936

3037
QString appName;
3138
QIcon appIcon;
3239
QPixmap splashImage;
3340
QIcon trayAndWindowIcon;
3441
QString titleAddText;
3542
QColor badgeColor;
43+
#ifdef Q_OS_MACOS
44+
std::optional<QIcon> m_macos_icon;
45+
std::optional<QIcon> m_macos_tray;
46+
#endif // Q_OS_MACOS
3647

3748
void rotateColor(QColor& col, const int iconColorHueShift, const int iconColorSaturationReduction);
3849
void rotateColors(QImage& img, const int iconColorHueShift, const int iconColorSaturationReduction);

src/qt/res/icons/dash.icns

569 KB
Binary file not shown.
30.2 KB
Loading
26.8 KB
Loading
27.8 KB
Loading

0 commit comments

Comments
 (0)