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
];
# 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 {