quickshell: swap icon font to Lucide

This commit is contained in:
rope 2026-07-30 09:29:04 +01:00
parent 1e94ad7ae9
commit c7d518ab6a

View file

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