From 6a933aa2601b7cf2c204485b9b4371c61047d5fa Mon Sep 17 00:00:00 2001 From: 28allday Date: Sun, 14 Jun 2026 11:01:44 +0100 Subject: [PATCH] =?UTF-8?q?feat(nosignal):=20Nerd=20Font=20icon=20remap=20?= =?UTF-8?q?=E2=80=94=20bar=20+=20popouts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Route the redesign's icons through JetBrains Mono Nerd Font instead of Material Symbols, for visual consistency with the JetBrains Mono text. - services/Glyphs.qml: Material-Symbol-name -> Nerd Font (Font Awesome range) codepoint map (via String.fromCharCode), covering bar/panel icons + the dynamic Icons.getX() returns; get() falls back to a "?" glyph. - components/NsIcon.qml: reactive icon component (input `icon` = name -> mapped glyph), so dynamic icons (volume/battery/play-pause) stay live. Uses the "Mono" NF variant so glyphs center in a fixed cell. - Converted all MaterialIcon usages in NsBar + the 9 panels to NsIcon. caelestia core (nexus/launcher/osd/lock) still uses Material Symbols — no breakage (mixed), to be converted next with an expanded Glyphs map. Co-Authored-By: Claude Opus 4.8 (1M context) --- components/NsIcon.qml | 27 ++++++ modules/nsbar/NsBar.qml | 20 ++--- modules/nsbar/panels/NsAudio.qml | 12 +-- modules/nsbar/panels/NsBluetooth.qml | 4 +- modules/nsbar/panels/NsCalendar.qml | 4 +- modules/nsbar/panels/NsNetwork.qml | 8 +- modules/nsbar/panels/NsNotifications.qml | 8 +- modules/nsbar/panels/NsOverview.qml | 4 +- modules/nsbar/panels/NsPowerMenu.qml | 4 +- modules/nsbar/panels/NsQuickSettings.qml | 20 ++--- services/Glyphs.qml | 108 +++++++++++++++++++++++ 11 files changed, 177 insertions(+), 42 deletions(-) create mode 100644 components/NsIcon.qml create mode 100644 services/Glyphs.qml diff --git a/components/NsIcon.qml b/components/NsIcon.qml new file mode 100644 index 00000000..aec06e87 --- /dev/null +++ b/components/NsIcon.qml @@ -0,0 +1,27 @@ +pragma ComponentBehavior: Bound + +// NoSignal icon: renders a Nerd Font (JetBrainsMono NF) glyph from a Material +// Symbol name via the Glyphs map. Drop-in for MaterialIcon but the input is +// `icon` (the name) instead of `text`, so dynamic bindings stay reactive. + +import QtQuick +import qs.services + +Text { + id: root + + property string icon: "" + property int size: 16 + property real fill: 0 // API parity with MaterialIcon (unused for Nerd Font) + + renderType: Text.NativeRendering + textFormat: Text.PlainText + text: Glyphs.get(root.icon) + // "Mono" variant centers each icon glyph in a fixed cell (the plain NF + // variant uses a wide advance, which left/right-shifts centered icons). + font.family: "JetBrainsMono Nerd Font Mono" + font.pixelSize: root.size + color: Theme.text + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter +} diff --git a/modules/nsbar/NsBar.qml b/modules/nsbar/NsBar.qml index de032336..d88e730f 100644 --- a/modules/nsbar/NsBar.qml +++ b/modules/nsbar/NsBar.qml @@ -80,8 +80,8 @@ Variants { active: NsShell.open === "overview" onTriggered: NsShell.toggle("overview", mapToItem(null, width / 2, 0).x) - MaterialIcon { - text: "desktop_windows" + NsIcon { + icon: "desktop_windows" color: titlePill.active ? Theme.accent : Theme.textMuted } @@ -123,8 +123,8 @@ Variants { active: NsShell.open === "quicksettings" onTriggered: NsShell.toggle("quicksettings", mapToItem(null, width / 2, 0).x) - MaterialIcon { - text: Players.active?.isPlaying ? "pause" : "music_note" + NsIcon { + icon: Players.active?.isPlaying ? "pause" : "music_note" color: mediaPill.active ? Theme.accent : Theme.green } @@ -152,8 +152,8 @@ Variants { active: NsShell.open === "notifications" onTriggered: NsShell.toggle("notifications", mapToItem(null, width / 2, 0).x) - MaterialIcon { - text: Notifs.notClosed.length > 0 ? "notifications" : "notifications_none" + NsIcon { + icon: Notifs.notClosed.length > 0 ? "notifications" : "notifications_none" color: bellPill.active ? Theme.accent : Theme.text Rectangle { @@ -203,8 +203,8 @@ Variants { active: NsShell.open === "quicksettings" onTriggered: NsShell.toggle("quicksettings", mapToItem(null, width / 2, 0).x) - MaterialIcon { - text: batteryPill.laptop ? Icons.getBatteryIcon(UPower.displayDevice.percentage, [UPowerDeviceState.Charging, UPowerDeviceState.FullyCharged, UPowerDeviceState.PendingCharge].includes(UPower.displayDevice.state)) : "tune" + NsIcon { + icon: batteryPill.laptop ? Icons.getBatteryIcon(UPower.displayDevice.percentage, [UPowerDeviceState.Charging, UPowerDeviceState.FullyCharged, UPowerDeviceState.PendingCharge].includes(UPower.displayDevice.state)) : "tune" color: batteryPill.active ? Theme.accent : !batteryPill.laptop || !UPower.onBattery || UPower.displayDevice.percentage > 0.2 ? Theme.text : Theme.danger fill: 1 } @@ -248,8 +248,8 @@ Variants { active: NsShell.open === panel onTriggered: NsShell.toggle(panel, mapToItem(null, width / 2, 0).x) - MaterialIcon { - text: p.icon + NsIcon { + icon: p.icon color: p.active ? Theme.accent : p.iconColour } } diff --git a/modules/nsbar/panels/NsAudio.qml b/modules/nsbar/panels/NsAudio.qml index 1bd42291..c39e86c0 100644 --- a/modules/nsbar/panels/NsAudio.qml +++ b/modules/nsbar/panels/NsAudio.qml @@ -20,8 +20,8 @@ NsPanel { Layout.fillWidth: true spacing: 8 - MaterialIcon { - text: "speaker" + NsIcon { + icon: "speaker" color: Theme.textMuted } @@ -34,8 +34,8 @@ NsPanel { font.pixelSize: Theme.font.bodySmall } - MaterialIcon { - text: "expand_more" + NsIcon { + icon: "expand_more" color: Theme.textFaint } } @@ -114,8 +114,8 @@ NsPanel { Layout.fillWidth: true spacing: 10 - MaterialIcon { - text: s.icon + NsIcon { + icon: s.icon color: Theme.textMuted } diff --git a/modules/nsbar/panels/NsBluetooth.qml b/modules/nsbar/panels/NsBluetooth.qml index 0c8bf95c..015a5d15 100644 --- a/modules/nsbar/panels/NsBluetooth.qml +++ b/modules/nsbar/panels/NsBluetooth.qml @@ -72,9 +72,9 @@ NsPanel { radius: 0 color: row.connected ? Theme.accentSoft : Theme.fillSubtle - MaterialIcon { + NsIcon { anchors.centerIn: parent - text: Icons.getBluetoothIcon(row.modelData?.icon ?? "") + icon: Icons.getBluetoothIcon(row.modelData?.icon ?? "") color: row.connected ? Theme.accent : Theme.text } } diff --git a/modules/nsbar/panels/NsCalendar.qml b/modules/nsbar/panels/NsCalendar.qml index 552a3e2d..8be5b4d6 100644 --- a/modules/nsbar/panels/NsCalendar.qml +++ b/modules/nsbar/panels/NsCalendar.qml @@ -157,9 +157,9 @@ NsPanel { radius: Theme.radius.button color: ma.containsMouse ? Theme.hover : "transparent" - MaterialIcon { + NsIcon { anchors.centerIn: parent - text: parent.icon + icon: parent.icon color: Theme.textMuted } diff --git a/modules/nsbar/panels/NsNetwork.qml b/modules/nsbar/panels/NsNetwork.qml index 92703351..9bb4cbb6 100644 --- a/modules/nsbar/panels/NsNetwork.qml +++ b/modules/nsbar/panels/NsNetwork.qml @@ -63,8 +63,8 @@ NsPanel { anchors.rightMargin: 10 spacing: 10 - MaterialIcon { - text: Icons.getNetworkIcon(row.modelData?.strength ?? 0) + NsIcon { + icon: Icons.getNetworkIcon(row.modelData?.strength ?? 0) color: row.connected ? Theme.accent : Theme.text } @@ -89,9 +89,9 @@ NsPanel { } } - MaterialIcon { + NsIcon { visible: row.modelData?.isSecure ?? false - text: "lock" + icon: "lock" color: Theme.textFaint } } diff --git a/modules/nsbar/panels/NsNotifications.qml b/modules/nsbar/panels/NsNotifications.qml index 17220980..75c8c259 100644 --- a/modules/nsbar/panels/NsNotifications.qml +++ b/modules/nsbar/panels/NsNotifications.qml @@ -43,9 +43,9 @@ NsPanel { radius: 13 color: Notifs.dnd ? Theme.accentSoft : dndMa.containsMouse ? Theme.hover : "transparent" - MaterialIcon { + NsIcon { anchors.centerIn: parent - text: Notifs.dnd ? "do_not_disturb_on" : "dark_mode" + icon: Notifs.dnd ? "do_not_disturb_on" : "dark_mode" color: Notifs.dnd ? Theme.accent : Theme.textMuted } @@ -113,9 +113,9 @@ NsPanel { radius: 0 color: Theme.accentSoft - MaterialIcon { + NsIcon { anchors.centerIn: parent - text: Icons.getNotifIcon(card.modelData?.summary ?? "", card.modelData?.urgency ?? 1) + icon: Icons.getNotifIcon(card.modelData?.summary ?? "", card.modelData?.urgency ?? 1) color: Theme.accent } } diff --git a/modules/nsbar/panels/NsOverview.qml b/modules/nsbar/panels/NsOverview.qml index 3db395d3..02eb672c 100644 --- a/modules/nsbar/panels/NsOverview.qml +++ b/modules/nsbar/panels/NsOverview.qml @@ -71,10 +71,10 @@ NsPanel { border.width: cell.active ? 2 : 1 border.color: cell.active ? Theme.accent : Theme.panelBorder - MaterialIcon { + NsIcon { anchors.centerIn: parent visible: cell.occupied - text: "web_asset" + icon: "web_asset" color: Theme.textFaint } diff --git a/modules/nsbar/panels/NsPowerMenu.qml b/modules/nsbar/panels/NsPowerMenu.qml index f657d9c3..ec154032 100644 --- a/modules/nsbar/panels/NsPowerMenu.qml +++ b/modules/nsbar/panels/NsPowerMenu.qml @@ -117,9 +117,9 @@ Item { anchors.centerIn: parent spacing: 10 - MaterialIcon { + NsIcon { Layout.alignment: Qt.AlignHCenter - text: tile.icon + icon: tile.icon color: tile.danger ? Theme.danger : Theme.text } diff --git a/modules/nsbar/panels/NsQuickSettings.qml b/modules/nsbar/panels/NsQuickSettings.qml index aeafd188..069e66e8 100644 --- a/modules/nsbar/panels/NsQuickSettings.qml +++ b/modules/nsbar/panels/NsQuickSettings.qml @@ -78,9 +78,9 @@ NsPanel { radius: 17 color: pwrMa.containsMouse ? Theme.accentSoft : Theme.fillSubtle - MaterialIcon { + NsIcon { anchors.centerIn: parent - text: "power_settings_new" + icon: "power_settings_new" color: Theme.danger } @@ -189,9 +189,9 @@ NsPanel { color: Theme.blue } } - MaterialIcon { + NsIcon { anchors.centerIn: parent - text: "music_note" + icon: "music_note" color: Theme.onAccent } } @@ -267,9 +267,9 @@ NsPanel { radius: 15 color: tile.on ? Theme.accent : Qt.rgba(1, 1, 1, 0.06) - MaterialIcon { + NsIcon { anchors.centerIn: parent - text: tile.icon + icon: tile.icon color: tile.on ? Theme.onAccent : Theme.text } } @@ -309,8 +309,8 @@ NsPanel { Layout.fillWidth: true spacing: 10 - MaterialIcon { - text: s.icon + NsIcon { + icon: s.icon color: Theme.textMuted } @@ -365,9 +365,9 @@ NsPanel { radius: 14 color: mbm.containsMouse ? Theme.hover : "transparent" - MaterialIcon { + NsIcon { anchors.centerIn: parent - text: parent.icon + icon: parent.icon color: Theme.text } diff --git a/services/Glyphs.qml b/services/Glyphs.qml new file mode 100644 index 00000000..9daa972c --- /dev/null +++ b/services/Glyphs.qml @@ -0,0 +1,108 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +// NoSignal icon remap: Material Symbol name -> Nerd Font (JetBrainsMono NF) +// codepoint (Font Awesome range), via String.fromCharCode so the source stays +// pure ASCII. Used by components/NsIcon. Covers bar/panel icons + the dynamic +// Icons.getX() returns. get() falls back to a question glyph so anything +// unmapped is visible (easy to spot + add a mapping). + +import QtQuick +import Quickshell + +Singleton { + id: root + + function cp(code: int): string { + return String.fromCharCode(code); + } + + readonly property var map: { + // network (no per-strength glyphs in FA range -> all wifi) + "wifi": root.cp(0xf1eb), + "wifi_off": root.cp(0xf1eb), + "network_wifi": root.cp(0xf1eb), + "network_wifi_1_bar": root.cp(0xf1eb), + "network_wifi_2_bar": root.cp(0xf1eb), + "network_wifi_3_bar": root.cp(0xf1eb), + "network_wifi_locked": root.cp(0xf023), + "network_wifi_1_bar_locked": root.cp(0xf023), + "network_wifi_2_bar_locked": root.cp(0xf023), + "network_wifi_3_bar_locked": root.cp(0xf023), + "signal_wifi_0_bar": root.cp(0xf1eb), + "cable": root.cp(0xf0e8), + // bluetooth + "bluetooth": root.cp(0xf293), + "bluetooth_connected": root.cp(0xf293), + "bluetooth_disabled": root.cp(0xf294), + // audio + "volume_up": root.cp(0xf028), + "volume_down": root.cp(0xf027), + "volume_mute": root.cp(0xf6a9), + "volume_off": root.cp(0xf026), + "no_sound": root.cp(0xf026), + "speaker": root.cp(0xf028), + "mic": root.cp(0xf130), + "mic_off": root.cp(0xf131), + "graphic_eq": root.cp(0xf001), + "headphones": root.cp(0xf025), + "headset": root.cp(0xf025), + "audio": root.cp(0xf025), + "keyboard": root.cp(0xf11c), + "mouse": root.cp(0xf109), + "phone": root.cp(0xf095), + "smartphone": root.cp(0xf10b), + // battery + "battery_full": root.cp(0xf240), + "battery_charging_full": root.cp(0xf240), + "battery": root.cp(0xf242), + "tune": root.cp(0xf1de), + // notifications / toggles + "notifications": root.cp(0xf0f3), + "notifications_none": root.cp(0xf0a2), + "do_not_disturb_on": root.cp(0xf05e), + "dark_mode": root.cp(0xf186), + "nightlight": root.cp(0xf186), + "bedtime": root.cp(0xf186), + "airplanemode_active": root.cp(0xf072), + "vpn_key": root.cp(0xf084), + // media + "music_note": root.cp(0xf001), + "music": root.cp(0xf001), + "pause": root.cp(0xf04c), + "play_arrow": root.cp(0xf04b), + "skip_next": root.cp(0xf051), + "skip_previous": root.cp(0xf048), + // power / session + "power_settings_new": root.cp(0xf011), + "power": root.cp(0xf011), + "lock": root.cp(0xf023), + "logout": root.cp(0xf08b), + "restart_alt": root.cp(0xf021), + "reboot": root.cp(0xf021), + // chrome / nav + "desktop_windows": root.cp(0xf108), + "web_asset": root.cp(0xf2d0), + "widgets": root.cp(0xf009), + "settings": root.cp(0xf013), + "expand_more": root.cp(0xf078), + "chevron_left": root.cp(0xf053), + "chevron_right": root.cp(0xf054), + "calendar_month": root.cp(0xf073), + "schedule": root.cp(0xf017), + "brightness_6": root.cp(0xf185), + // notif category icons + "chat": root.cp(0xf075), + "file": root.cp(0xf15b), + "folder_copy": root.cp(0xf07b), + "download": root.cp(0xf019), + "person": root.cp(0xf007), + "profile": root.cp(0xf007), + "installed": root.cp(0xf058), + "deployed_code_alert": root.cp(0xf071) + } + + function get(name: string): string { + return root.map[name] ?? root.cp(0xf128); + } +}