From c7d518ab6a2aa17c114dae750bd0906cdd75c6d2 Mon Sep 17 00:00:00 2001 From: rope Date: Thu, 30 Jul 2026 09:29:04 +0100 Subject: [PATCH] quickshell: swap icon font to Lucide --- settings/quickshell.nix | 187 +++++++++++++++++++--------------------- 1 file changed, 90 insertions(+), 97 deletions(-) diff --git a/settings/quickshell.nix b/settings/quickshell.nix index 15c05c1..39aaf40 100644 --- a/settings/quickshell.nix +++ b/settings/quickshell.nix @@ -13,9 +13,10 @@ in qt6.qt5compat # Qt5Compat.GraphicalEffects in Bar.qml ]; - # Icon font for the shell (ligature-based: text "volume_up" renders the - # icon) — same font caelestia uses. UI text font comes from stylix. - fonts.packages = [ pkgs.material-symbols ]; + # Icon font for the shell. Lucide is ligature-based like Material Symbols + # was (text "wifi-high" renders the icon), so it drops straight in — only + # the names change. UI text font comes from stylix. + fonts.packages = [ pkgs.lucide ]; home-manager.users.fred = { config, lib, pkgs, osConfig, ... }: let @@ -219,8 +220,8 @@ in // transparent *black* (which reads as an extra flash colour). readonly property color base02t: "#00${c.base02}" readonly property string fontFamily: "${sansFont}" - // Ligature-based icon font: text "volume_up" renders the icon - readonly property string iconFont: "Material Symbols Rounded" + // Ligature-based icon font: text "wifi-high" renders the icon + readonly property string iconFont: "lucide" // Matches hyprland general.border_size (col.inactive_border = base03) readonly property int borderWidth: 2 // Screen frame band; sits inside hyprland's gaps_out (12) @@ -434,7 +435,7 @@ in Text { anchors.centerIn: parent - text: "arrow_forward" + text: "arrow-right" font.family: Theme.iconFont font.pixelSize: 18 color: Theme.base0D @@ -551,10 +552,10 @@ in if (f.length < 5) return; let v = parseInt(f[3]) / 100; // "brightness" is a request to grade the glyph by - // level (caelestia's OSD mapping); the kbd path - // passes a literal icon instead. + // level; the kbd path passes a literal icon. Lucide + // gives three sun steps, not seven. let ic = brightProc.osdIcon === "brightness" - ? "brightness_" + (Math.round(v * 6) + 1) + ? (v >= 0.66 ? "sun" : v >= 0.33 ? "sun-medium" : "sun-dim") : brightProc.osdIcon; root.osdBrightness(ic, v); } @@ -780,11 +781,9 @@ in color: Theme.base05 font.family: Theme.iconFont font.pixelSize: 16 - // Material Symbols variable axes, matching caelestia's - // MaterialIcon: GRAD -25 (their dark-theme value; our - // stylix polarity is always dark) and opsz 24, which is - // what actually makes the strokes read right at 16px. - font.variableAxes: ({ "FILL": 0, "GRAD": -25, "opsz": 24 }) + // No variableAxes here: Lucide.ttf is a static font, so + // FILL/GRAD/opsz (Material Symbols axes) have nothing to + // act on. Stroke weight is fixed by the design. } // ── BarSep: extra whitespace between widget groups. Groups are @@ -823,13 +822,13 @@ in // with a fill that greys when `audioNode.muted`. component VolIcon: SIcon { property var audioNode: null - // caelestia's getVolumeIcon, so the slider's glyph tracks - // its level and not just the mute flag. - text: !audioNode ? "volume_up" - : audioNode.muted ? "no_sound" - : audioNode.volume >= 0.5 ? "volume_up" - : audioNode.volume > 0 ? "volume_down" - : "volume_mute" + // Same mapping as the bar widget, so the slider's glyph + // tracks its level and not just the mute flag. + text: !audioNode ? "volume-2" + : audioNode.muted ? "volume-x" + : audioNode.volume >= 0.5 ? "volume-2" + : audioNode.volume > 0 ? "volume-1" + : "volume" MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor @@ -954,7 +953,7 @@ in Row { spacing: 6 anchors.verticalCenter: parent.verticalCenter - SIcon { anchors.verticalCenter: parent.verticalCenter; text: "nightlight"; font.pixelSize: 15 } + SIcon { anchors.verticalCenter: parent.verticalCenter; text: "moon"; font.pixelSize: 15 } SText { anchors.verticalCenter: parent.verticalCenter; text: "Night Light"; font.weight: Font.Medium } } Row { @@ -1184,16 +1183,16 @@ in property var entries: [] readonly property var catDefs: [ - { key: "All", label: "All apps", icon: "apps" }, - { key: "Network", label: "Internet", icon: "public" }, + { key: "All", label: "All apps", icon: "layout-grid" }, + { key: "Network", label: "Internet", icon: "globe" }, { key: "Development", label: "Development", icon: "code" }, - { key: "Game", label: "Games", icon: "sports_esports" }, - { key: "AudioVideo", label: "Media", icon: "play_circle" }, + { key: "Game", label: "Games", icon: "gamepad-2" }, + { key: "AudioVideo", label: "Media", icon: "circle-play" }, { key: "Graphics", label: "Graphics", icon: "palette" }, - { key: "Office", label: "Office", icon: "description" }, + { key: "Office", label: "Office", icon: "file-text" }, { key: "System", label: "System", icon: "monitor" }, - { key: "Utility", label: "Utilities", icon: "widgets" }, - { key: "Other", label: "Other", icon: "more_horiz" } + { key: "Utility", label: "Utilities", icon: "blocks" }, + { key: "Other", label: "Other", icon: "ellipsis" } ] function inCat(a, key) { @@ -1341,7 +1340,7 @@ in spacing: 8 SIcon { anchors.verticalCenter: parent.verticalCenter - text: "arrow_back" + text: "arrow-left" font.pixelSize: 16 color: Theme.base04 } @@ -1661,7 +1660,7 @@ in // NixOS snowflake, flattened to the theme accent with the // same hidden-Image + ColorOverlay trick the tray uses - // (there is no Material Symbols glyph for it). + // (no icon font ships a NixOS logo). Item { width: Theme.barWidth height: 24 @@ -2010,12 +2009,11 @@ in property int vol: sink && sink.audio ? Math.round(sink.audio.volume * 100) : 0 property bool muted: sink && sink.audio ? sink.audio.muted : false - // caelestia's getVolumeIcon: one threshold at 50%, and - // "no_sound" (not volume_off) for the muted state. - property string volIcon: muted ? "no_sound" - : vol >= 50 ? "volume_up" - : vol > 0 ? "volume_down" - : "volume_mute" + // caelestia's getVolumeIcon shape, in Lucide's names. + property string volIcon: muted ? "volume-x" + : vol >= 50 ? "volume-2" + : vol > 0 ? "volume-1" + : "volume" function openVolDropdown() { bar.toggleDropdown(volDropdown, function() { @@ -2063,17 +2061,17 @@ in property string netConn: "" property string netType: "" property int netStrength: 0 - // caelestia's getNetworkIcon mapping: signal strength - // picks the arc glyph instead of one flat "wifi". + // Signal strength picks the arc glyph. Lucide draws four + // wifi levels where Material Symbols drew five, so the + // bottom two of caelestia's buckets collapse into one. readonly property string netIcon: netState !== "connected" - ? (netType === "ethernet" ? "settings_ethernet" : "wifi_off") + ? (netType === "ethernet" ? "unplug" : "wifi-off") : netType === "ethernet" ? "cable" - : netStrength >= 80 ? "network_wifi" - : netStrength >= 60 ? "network_wifi_3_bar" - : netStrength >= 40 ? "network_wifi_2_bar" - : netStrength >= 20 ? "network_wifi_1_bar" - : "signal_wifi_0_bar" + : netStrength >= 80 ? "wifi" + : netStrength >= 60 ? "wifi-high" + : netStrength >= 40 ? "wifi-low" + : "wifi-zero" property var wifiNetworks: [] property string netDevice: "" @@ -2267,18 +2265,15 @@ in PowerProfiles.profile === PowerProfile.PowerSaver ? "power-saver" : PowerProfiles.profile === PowerProfile.Performance ? "performance" : "balanced" - // caelestia's getBatteryIcon: a 7-step bar scale rather - // than one flat charging glyph. The level-- skips the - // charging variants Material Symbols doesn't ship (40, - // 70). Low-battery warning is carried by colour below. - function battIconFor(pct, chg) { - if (pct === 1) return chg ? "battery_charging_full" : "battery_full"; - let level = Math.floor(pct * 7); - if (chg && (level === 4 || level === 1)) level--; - return chg ? "battery_charging_" + ((level + 3) * 10) - : "battery_" + level + "_bar"; - } - property string batteryIcon: battIconFor(batteryLevel / 100, charging) + // Lucide draws four battery levels and a single charging + // glyph, so caelestia's 7-step scale and its graded + // charging variants don't survive the swap. Low-battery + // warning is carried by colour below. + property string batteryIcon: charging ? "battery-charging" + : batteryLevel >= 75 ? "battery-full" + : batteryLevel >= 50 ? "battery-medium" + : batteryLevel >= 20 ? "battery-low" + : "battery" // Stacked: icon over percentage. Side by side no longer // fits, but the level is worth keeping on a laptop. @@ -2342,7 +2337,7 @@ in // centerIn, not verticalCenter: the cell used to be // qsIcon.width so horizontal centring was implicit. anchors.centerIn: parent - text: "nightlight" + text: "moon" font.pixelSize: 18 } @@ -2467,7 +2462,7 @@ in SIcon { anchors.centerIn: parent - text: "power_settings_new" + text: "power" color: powerMa.containsMouse || sessionMenu.open ? Theme.base08 : Theme.base05 font.pixelSize: 18 Behavior on color { ColorAnimation { duration: Theme.animFade } } @@ -2812,9 +2807,9 @@ in readonly property var actions: [ { icon: "lock", danger: false, act: "lock" }, - { icon: "logout", danger: false, act: "logout" }, - { icon: "restart_alt", danger: true, act: "reboot" }, - { icon: "power_settings_new", danger: true, act: "poweroff" } + { icon: "log-out", danger: false, act: "logout" }, + { icon: "rotate-ccw", danger: true, act: "reboot" }, + { icon: "power", danger: true, act: "poweroff" } ] function activate(act) { @@ -2918,7 +2913,7 @@ in spacing: 6 SIcon { anchors.verticalCenter: parent.verticalCenter - text: "dns" + text: "server" color: srvWidget.connected ? Theme.base0B : Theme.base08 } SText { @@ -2997,7 +2992,7 @@ in spacing: 14 Row { spacing: 4 - SIcon { text: "arrow_downward"; font.pixelSize: 14; color: Theme.base0B; anchors.verticalCenter: parent.verticalCenter } + SIcon { text: "arrow-down"; font.pixelSize: 14; color: Theme.base0B; anchors.verticalCenter: parent.verticalCenter } SText { text: srvWidget.fmtRate(srvWidget.rxBps) font.weight: Font.Medium @@ -3006,7 +3001,7 @@ in } Row { spacing: 4 - SIcon { text: "arrow_upward"; font.pixelSize: 14; color: Theme.base09; anchors.verticalCenter: parent.verticalCenter } + SIcon { text: "arrow-up"; font.pixelSize: 14; color: Theme.base09; anchors.verticalCenter: parent.verticalCenter } SText { text: srvWidget.fmtRate(srvWidget.txBps) font.weight: Font.Medium @@ -3033,7 +3028,7 @@ in Row { spacing: 6 - SIcon { anchors.verticalCenter: parent.verticalCenter; text: "monitor_heart" } + SIcon { anchors.verticalCenter: parent.verticalCenter; text: "activity" } SText { anchors.verticalCenter: parent.verticalCenter text: "Processes" @@ -3267,7 +3262,7 @@ in Row { spacing: 6 - SIcon { anchors.verticalCenter: parent.verticalCenter; text: "graphic_eq" } + SIcon { anchors.verticalCenter: parent.verticalCenter; text: "audio-waveform" } SText { anchors.verticalCenter: parent.verticalCenter text: "Applications" @@ -3438,17 +3433,15 @@ in spacing: 8 SIcon { - // caelestia's getNetworkIcon, secure - // variant included — a padlocked arc - // for encrypted APs. + // Lucide has no padlocked wifi arc, + // so security is shown by the lock + // glyph on the row's right instead. text: { let s = modelData.signal; - if (s < 20) return "signal_wifi_0_bar"; - let lock = modelData.security.length > 0 ? "_locked" : ""; - if (s >= 80) return "network_wifi" + lock; - if (s >= 60) return "network_wifi_3_bar" + lock; - if (s >= 40) return "network_wifi_2_bar" + lock; - return "network_wifi_1_bar" + lock; + if (s >= 80) return "wifi"; + if (s >= 60) return "wifi-high"; + if (s >= 40) return "wifi-low"; + return "wifi-zero"; } color: modelData.active ? Theme.base0B : Theme.base04 font.pixelSize: 16 @@ -3629,9 +3622,9 @@ in Row { width: parent.width spacing: 8 - // caelestia's OSD mapping: the glyph tracks the - // level instead of sitting on one static step. - SIcon { width: 18; anchors.verticalCenter: parent.verticalCenter; text: "brightness_" + (Math.round(brightCard.screenB * 6) + 1); font.pixelSize: 16 } + // Glyph tracks the level instead of sitting on + // one static step. + SIcon { width: 18; anchors.verticalCenter: parent.verticalCenter; text: brightCard.screenB >= 0.66 ? "sun" : brightCard.screenB >= 0.33 ? "sun-medium" : "sun-dim"; font.pixelSize: 16 } PillSlider { width: parent.width - 26 anchors.verticalCenter: parent.verticalCenter @@ -3764,20 +3757,20 @@ in triggeredOnStart: true onTriggered: weatherProc.running = true } - // WMO weather codes → Material Symbols. Ranges per - // open-meteo: 0 clear, 1-2 partly, 3 overcast, 45-48 fog, - // 51-67 drizzle/rain, 71-77 snow, 80-82 rain showers, - // 85-86 snow showers, 95+ thunder. + // WMO weather codes → Lucide. Ranges per open-meteo: 0 + // clear, 1-2 partly, 3 overcast, 45-48 fog, 51-67 + // drizzle/rain, 71-77 snow, 80-82 rain showers, 85-86 snow + // showers, 95+ thunder. function weatherGlyph(code) { - if (code === 0) return "clear_day"; - if (code <= 2) return "partly_cloudy_day"; + if (code === 0) return "sun"; + if (code <= 2) return "cloud-sun"; if (code === 3) return "cloud"; - if (code <= 48) return "foggy"; - if (code <= 67) return "rainy"; - if (code <= 77) return "cloudy_snowing"; - if (code <= 82) return "rainy"; - if (code <= 86) return "cloudy_snowing"; - return "thunderstorm"; + if (code <= 48) return "cloud-fog"; + if (code <= 67) return "cloud-rain"; + if (code <= 77) return "cloud-snow"; + if (code <= 82) return "cloud-rain"; + if (code <= 86) return "cloud-snow"; + return "cloud-lightning"; } // --- Media: one card per MPRIS player so Spotify, a @@ -3835,7 +3828,7 @@ in onClicked: calPopup.shiftMonth(-1) SIcon { anchors.centerIn: parent - text: "chevron_left" + text: "chevron-left" color: Theme.base05 font.pixelSize: 18 } @@ -3860,7 +3853,7 @@ in onClicked: calPopup.shiftMonth(1) SIcon { anchors.centerIn: parent - text: "chevron_right" + text: "chevron-right" color: Theme.base05 font.pixelSize: 18 } @@ -4007,7 +4000,7 @@ in SIcon { anchors.centerIn: parent visible: albumArt.status !== Image.Ready - text: "music_note" + text: "music" color: Theme.base04 font.pixelSize: 48 } @@ -4212,7 +4205,7 @@ in id: dismissBtn anchors.right: parent.right anchors.top: parent.top - text: "close" + text: "x" color: dismissMa.containsMouse ? Theme.base05 : Theme.base03 font.pixelSize: 15 MouseArea { @@ -4244,7 +4237,7 @@ in // a shell restart doesn't flash the slider on screen. property bool armed: false property bool shown: false - property string icon: "volume_up" + property string icon: "volume-2" property real value: 0 property color fill: Theme.base0D @@ -4494,7 +4487,7 @@ in anchors.right: parent.right anchors.top: parent.top anchors.margins: 8 - text: "close" + text: "x" color: toastDismissMa.containsMouse ? Theme.base05 : Theme.base03 font.pixelSize: 15 MouseArea {