quickshell: material symbols icons, network cards, session menu morphs from right edge

- Material Symbols Rounded (ligature names) replaces nerd-font glyphs for
  all shell icons; text stays on the stylix mono font
- network dropdown gets the card treatment like the others
- power menu is now an icon-only session panel melting out of the right
  frame column at screen centre (still Super+L); launcher is apps-only

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-06-12 14:06:20 +01:00
parent 4540e38321
commit a14ccd8c09

View file

@ -12,6 +12,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) — same font caelestia uses; nerd-font glyphs stay for terminals.
fonts.packages = [ pkgs.material-symbols ];
home-manager.users.fred = { config, lib, pkgs, osConfig, ... }: home-manager.users.fred = { config, lib, pkgs, osConfig, ... }:
let let
c = config.lib.stylix.colors; c = config.lib.stylix.colors;
@ -64,6 +68,7 @@ in
vec4 cutout; // cx, cy, hw, hh rounded inner screen cutout vec4 cutout; // cx, cy, hw, hh rounded inner screen cutout
vec4 panel; // cx, cy, hw, hh dropdown panel (hw <= 0: none) vec4 panel; // cx, cy, hw, hh dropdown panel (hw <= 0: none)
vec4 toast; // cx, cy, hw, hh toast (hw <= 0: none) vec4 toast; // cx, cy, hw, hh toast (hw <= 0: none)
vec4 session; // cx, cy, hw, hh session menu (hw <= 0: none)
vec4 fillColor; // straight (non-premultiplied) rgba vec4 fillColor; // straight (non-premultiplied) rgba
vec4 borderColor; vec4 borderColor;
vec2 res; vec2 res;
@ -94,6 +99,8 @@ in
d = smin(d, sdRoundedBox(p, panel.xy, panel.zw, panelR), meltK); d = smin(d, sdRoundedBox(p, panel.xy, panel.zw, panelR), meltK);
if (toast.z > 0.5) if (toast.z > 0.5)
d = smin(d, sdRoundedBox(p, toast.xy, toast.zw, panelR), meltK); d = smin(d, sdRoundedBox(p, toast.xy, toast.zw, panelR), meltK);
if (session.z > 0.5)
d = smin(d, sdRoundedBox(p, session.xy, session.zw, panelR), meltK);
float fw = fwidth(d); float fw = fwidth(d);
// 1 inside the union, 0 outside (antialiased) // 1 inside the union, 0 outside (antialiased)
@ -152,6 +159,8 @@ in
readonly property color barBg: "#B3${c.base00}" readonly property color barBg: "#B3${c.base00}"
readonly property color toastBg: "#E6${c.base00}" readonly property color toastBg: "#E6${c.base00}"
readonly property string fontFamily: "${monoFont}" readonly property string fontFamily: "${monoFont}"
// Ligature-based icon font: text "volume_up" renders the icon
readonly property string iconFont: "Material Symbols Rounded"
// 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)
@ -189,17 +198,19 @@ in
ShellRoot { ShellRoot {
id: root id: root
property var latestNotification: null property var latestNotification: null
property var mainBar: null
signal notificationReceived() signal notificationReceived()
Launcher { Launcher {
id: launcher id: launcher
} }
// Bound in hyprland.nix: Super+R toggle, Super+L powermenu // Bound in hyprland.nix: Super+R app launcher,
// Super+L session menu (in the bar window).
IpcHandler { IpcHandler {
target: "launcher" target: "launcher"
function toggle(): void { launcher.toggleMode("apps"); } function toggle(): void { launcher.toggle(); }
function powermenu(): void { launcher.toggleMode("power"); } function powermenu(): void { if (root.mainBar) root.mainBar.toggleSession(); }
} }
// Soft reload, used by the nix onChange hook keeps the // Soft reload, used by the nix onChange hook keeps the
@ -249,9 +260,6 @@ in
PanelWindow { PanelWindow {
id: root id: root
// "apps" (Super+R) or "power" (Super+L)
property string mode: "apps"
visible: false visible: false
screen: Quickshell.screens[0] screen: Quickshell.screens[0]
WlrLayershell.namespace: "quickshell-launcher" WlrLayershell.namespace: "quickshell-launcher"
@ -267,9 +275,8 @@ in
right: true right: true
} }
function toggleMode(m) { function toggle() {
if (visible && mode === m) { close(); return; } if (visible) { close(); return; }
mode = m;
search.text = ""; search.text = "";
list.currentIndex = 0; list.currentIndex = 0;
visible = true; visible = true;
@ -279,18 +286,6 @@ in
visible = false; visible = false;
} }
// Lock/reboot/shutdown spawn via Quickshell.execDetached fully
// detached, so a quickshell restart can never kill a running
// hyprlock. Logout (empty cmd) goes through Hyprland IPC; with a
// Lua config the dispatch body is evaluated as a Lua dispatcher
// expression, so it must use hl.dsp.* syntax, not hyprlang's.
readonly property var powerActions: [
{ name: "Lock", glyph: "", cmd: [Commands.hyprlock] },
{ name: "Logout", glyph: "", cmd: [] },
{ name: "Reboot", glyph: "", cmd: [Commands.systemctl, "reboot"] },
{ name: "Shutdown", glyph: "", cmd: [Commands.systemctl, "poweroff"] }
]
function score(name, extra, q) { function score(name, extra, q) {
let n = name.toLowerCase(); let n = name.toLowerCase();
if (n.startsWith(q)) return 5; if (n.startsWith(q)) return 5;
@ -313,9 +308,6 @@ in
property var entries: { property var entries: {
let q = search.text.toLowerCase().trim(); let q = search.text.toLowerCase().trim();
if (mode === "power") {
return powerActions.filter(a => q === "" || score(a.name, "", q) > 0);
}
let apps = DesktopEntries.applications.values.filter(a => !a.noDisplay); let apps = DesktopEntries.applications.values.filter(a => !a.noDisplay);
if (q === "") { if (q === "") {
apps.sort((a, b) => a.name.localeCompare(b.name)); apps.sort((a, b) => a.name.localeCompare(b.name));
@ -332,12 +324,7 @@ in
function activate(item) { function activate(item) {
if (!item) return; if (!item) return;
if (mode === "power") {
if (item.cmd.length === 0) Hyprland.dispatch("hl.dsp.exit()");
else Quickshell.execDetached(item.cmd);
} else {
item.execute(); item.execute();
}
close(); close();
} }
@ -399,7 +386,7 @@ in
anchors.fill: search anchors.fill: search
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
visible: search.text === "" visible: search.text === ""
text: root.mode === "power" ? "Power" : "Search" text: "Search"
color: Theme.base03 color: Theme.base03
font.family: Theme.fontFamily font.family: Theme.fontFamily
font.pixelSize: 13 font.pixelSize: 13
@ -429,22 +416,13 @@ in
spacing: 10 spacing: 10
Image { Image {
visible: root.mode === "apps" && source != "" visible: source != ""
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: 18 width: 18
height: 18 height: 18
sourceSize.width: 18 sourceSize.width: 18
sourceSize.height: 18 sourceSize.height: 18
source: root.mode === "apps" ? Quickshell.iconPath(modelData.icon, true) : "" source: Quickshell.iconPath(modelData.icon, true)
}
Text {
visible: root.mode === "power"
anchors.verticalCenter: parent.verticalCenter
text: root.mode === "power" ? modelData.glyph : ""
color: Theme.base0D
font.family: Theme.fontFamily
font.pixelSize: 14
} }
Text { Text {
@ -522,6 +500,12 @@ in
width: toastItem.visible ? toastItem.width : 0 width: toastItem.visible ? toastItem.width : 0
height: toastItem.visible ? toastItem.height : 0 height: toastItem.visible ? toastItem.height : 0
} }
Region {
x: sessionMenu.visible ? sessionMenu.x : 0
y: sessionMenu.visible ? sessionMenu.y : 0
width: sessionMenu.visible ? sessionMenu.width : 0
height: sessionMenu.visible ? sessionMenu.height : 0
}
} }
Item { Item {
@ -532,6 +516,113 @@ in
height: 30 height: 30
} }
// Register the primary bar so shell.qml's IPC handler can
// reach the session menu.
Component.onCompleted: {
if (bar.screen === Quickshell.screens[0])
bar.shellRoot.mainBar = bar;
}
function toggleSession() {
sessionMenu.toggle();
}
// Session menu: icon-only power controls morphing out of
// the right frame column at screen centre (Super+L).
Item {
id: sessionMenu
property bool open: false
property real openW: open ? 56 : 0
Behavior on openW {
NumberAnimation { duration: 280; easing.type: Easing.OutExpo }
}
x: bar.width - Theme.frameWidth - openW
y: Math.round((bar.height - height) / 2)
width: openW
height: sessionCol.height + 24
visible: openW > 0.5
function toggle() {
open = !open;
if (open) _sessionAutoClose.restart();
}
Timer {
id: _sessionAutoClose
interval: 2500
onTriggered: sessionMenu.open = false
}
HoverHandler {
onHoveredChanged: {
if (hovered) _sessionAutoClose.stop();
else if (sessionMenu.open) _sessionAutoClose.restart();
}
}
// Content pinned to the column edge, revealed by the grow
Item {
anchors.fill: parent
clip: true
opacity: sessionMenu.open ? 1 : 0
Behavior on opacity {
NumberAnimation { duration: 200; easing.type: Easing.OutCubic }
}
Column {
id: sessionCol
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: 8
spacing: 4
Repeater {
model: [
{ 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" }
]
Rectangle {
id: sessBtn
required property var modelData
width: 40
height: 40
radius: 8
color: sessBtnMa.containsMouse ? Theme.base02 : "transparent"
Behavior on color { ColorAnimation { duration: 120 } }
Text {
anchors.centerIn: parent
text: sessBtn.modelData.icon
color: sessBtn.modelData.danger && sessBtnMa.containsMouse
? Theme.base08 : Theme.base05
Behavior on color { ColorAnimation { duration: 120 } }
font.family: Theme.iconFont
font.pixelSize: 20
}
MouseArea {
id: sessBtnMa
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
sessionMenu.open = false;
if (sessBtn.modelData.act === "lock") Quickshell.execDetached([Commands.hyprlock]);
else if (sessBtn.modelData.act === "logout") Hyprland.dispatch("hl.dsp.exit()");
else if (sessBtn.modelData.act === "reboot") Quickshell.execDetached([Commands.systemctl, "reboot"]);
else Quickshell.execDetached([Commands.systemctl, "poweroff"]);
}
}
}
}
}
}
}
// Shell chrome: bar, frame, panel and toast rendered as // Shell chrome: bar, frame, panel and toast rendered as
// ONE signed-distance field (caelestia-style). Surfaces merge // ONE signed-distance field (caelestia-style). Surfaces merge
// via circular smooth-min, and the 2px border is the distance // via circular smooth-min, and the 2px border is the distance
@ -555,6 +646,11 @@ in
? Qt.vector4d(toastItem.x + 8 + _toastRect.width / 2, 26 + _toastRect.height / 2, ? Qt.vector4d(toastItem.x + 8 + _toastRect.width / 2, 26 + _toastRect.height / 2,
_toastRect.width / 2, 4 + _toastRect.height / 2) _toastRect.width / 2, 4 + _toastRect.height / 2)
: Qt.vector4d(0, 0, 0, 0) : Qt.vector4d(0, 0, 0, 0)
readonly property real sessRight: bar.width - Theme.frameWidth + 4
property vector4d session: sessionMenu.visible
? Qt.vector4d((sessionMenu.x + sessRight) / 2, sessionMenu.y + sessionMenu.height / 2,
(sessRight - sessionMenu.x) / 2, sessionMenu.height / 2)
: Qt.vector4d(0, 0, 0, 0)
property vector4d fillColor: Qt.vector4d(Theme.barBg.r, Theme.barBg.g, Theme.barBg.b, Theme.barBg.a) property vector4d fillColor: Qt.vector4d(Theme.barBg.r, Theme.barBg.g, Theme.barBg.b, Theme.barBg.a)
property vector4d borderColor: Qt.vector4d(Theme.base03.r, Theme.base03.g, Theme.base03.b, 1) property vector4d borderColor: Qt.vector4d(Theme.base03.r, Theme.base03.g, Theme.base03.b, 1)
property vector2d res: Qt.vector2d(width, height) property vector2d res: Qt.vector2d(width, height)
@ -691,7 +787,7 @@ in
// Volume // Volume
Item { Item {
id: volWidget id: volWidget
width: volText.width width: volRow.width
height: 30 height: 30
property PwNode sink: Pipewire.defaultAudioSink property PwNode sink: Pipewire.defaultAudioSink
@ -702,11 +798,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
property string volIcon: muted ? "\u{f0581}" property string volIcon: muted ? "volume_off"
: vol > 66 ? "\u{f057e}" : vol > 66 ? "volume_up"
: vol > 33 ? "\u{f0580}" : vol > 33 ? "volume_down"
: vol > 0 ? "\u{f057f}" : vol > 0 ? "volume_mute"
: "\u{f0581}" : "volume_off"
function openVolDropdown() { function openVolDropdown() {
bar.toggleDropdown(volDropdown, function() { bar.toggleDropdown(volDropdown, function() {
@ -715,14 +811,27 @@ in
}); });
} }
Text { Row {
id: volText id: volRow
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: volWidget.volIcon + " " + volWidget.vol + "%" spacing: 3
Text {
anchors.verticalCenter: parent.verticalCenter
text: volWidget.volIcon
color: volWidget.muted ? Theme.base03 : Theme.base05
font.family: Theme.iconFont
font.pixelSize: 16
}
Text {
anchors.verticalCenter: parent.verticalCenter
text: volWidget.vol + "%"
color: volWidget.muted ? Theme.base03 : Theme.base05 color: volWidget.muted ? Theme.base03 : Theme.base05
font.family: Theme.fontFamily font.family: Theme.fontFamily
font.pixelSize: 13 font.pixelSize: 13
} }
}
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
@ -754,7 +863,7 @@ in
property string netState: "disconnected" property string netState: "disconnected"
property string netConn: "" property string netConn: ""
property string netType: "" property string netType: ""
property string netIcon: "\u{f0b0}" property string netIcon: "wifi_off"
property var wifiNetworks: [] property var wifiNetworks: []
property string netDevice: "" property string netDevice: ""
@ -832,9 +941,9 @@ in
netWidget.netType = netWidget._pendingType.length > 0 ? netWidget._pendingType : netWidget.netType; netWidget.netType = netWidget._pendingType.length > 0 ? netWidget._pendingType : netWidget.netType;
netWidget.netDevice = netWidget._pendingDevice.length > 0 ? netWidget._pendingDevice : netWidget.netDevice; netWidget.netDevice = netWidget._pendingDevice.length > 0 ? netWidget._pendingDevice : netWidget.netDevice;
if (netWidget.netState === "connected") { if (netWidget.netState === "connected") {
netWidget.netIcon = netWidget.netType === "wifi" ? "\u{f05a9}" : "\u{f0200}"; netWidget.netIcon = netWidget.netType === "wifi" ? "wifi" : "lan";
} else { } else {
netWidget.netIcon = netWidget.netType === "wifi" ? "\u{f05aa}" : "\u{f0201}"; netWidget.netIcon = netWidget.netType === "wifi" ? "wifi_off" : "settings_ethernet";
} }
} }
} }
@ -844,8 +953,8 @@ in
anchors.centerIn: parent anchors.centerIn: parent
text: netWidget.netIcon text: netWidget.netIcon
color: Theme.base05 color: Theme.base05
font.family: Theme.fontFamily font.family: Theme.iconFont
font.pixelSize: 14 font.pixelSize: 16
} }
Timer { Timer {
@ -938,13 +1047,13 @@ 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"
property string batteryIcon: charging ? "\u{f0084}" property string batteryIcon: charging ? "battery_charging_full"
: batteryLevel >= 90 ? "\u{f0079}" : batteryLevel >= 90 ? "battery_full"
: batteryLevel >= 70 ? "\u{f0082}" : batteryLevel >= 70 ? "battery_6_bar"
: batteryLevel >= 50 ? "\u{f007f}" : batteryLevel >= 50 ? "battery_5_bar"
: batteryLevel >= 30 ? "\u{f007c}" : batteryLevel >= 30 ? "battery_3_bar"
: batteryLevel >= 15 ? "\u{f007a}" : batteryLevel >= 15 ? "battery_2_bar"
: "\u{f008e}" : "battery_alert"
Row { Row {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@ -966,8 +1075,8 @@ in
color: batteryWidget.batteryLevel <= 15 ? Theme.base08 color: batteryWidget.batteryLevel <= 15 ? Theme.base08
: batteryWidget.batteryLevel <= 30 ? Theme.base0A : batteryWidget.batteryLevel <= 30 ? Theme.base0A
: Theme.base05 : Theme.base05
font.family: Theme.fontFamily font.family: Theme.iconFont
font.pixelSize: 14 font.pixelSize: 16
} }
} }
@ -1383,13 +1492,24 @@ in
width: parent.width - 16 width: parent.width - 16
spacing: 8 spacing: 8
Row {
spacing: 6
Text { Text {
text: "\u{f057e} Master" anchors.verticalCenter: parent.verticalCenter
text: "volume_up"
color: Theme.base05
font.family: Theme.iconFont
font.pixelSize: 16
}
Text {
anchors.verticalCenter: parent.verticalCenter
text: "Master"
color: Theme.base05 color: Theme.base05
font.family: Theme.fontFamily font.family: Theme.fontFamily
font.pixelSize: 13 font.pixelSize: 13
font.weight: Font.Medium font.weight: Font.Medium
} }
}
Row { Row {
width: parent.width width: parent.width
@ -1456,13 +1576,24 @@ in
Behavior on color { ColorAnimation { duration: 120 } } Behavior on color { ColorAnimation { duration: 120 } }
radius: 4 radius: 4
Text { Row {
anchors.centerIn: parent anchors.centerIn: parent
text: volWidget.muted ? "\u{f0581} Unmute" : "\u{f057e} Mute" spacing: 6
Text {
anchors.verticalCenter: parent.verticalCenter
text: volWidget.muted ? "volume_off" : "volume_up"
color: Theme.base05
font.family: Theme.iconFont
font.pixelSize: 15
}
Text {
anchors.verticalCenter: parent.verticalCenter
text: volWidget.muted ? "Unmute" : "Mute"
color: Theme.base05 color: Theme.base05
font.family: Theme.fontFamily font.family: Theme.fontFamily
font.pixelSize: 12 font.pixelSize: 12
} }
}
MouseArea { MouseArea {
id: masterMuteMa id: masterMuteMa
anchors.fill: parent anchors.fill: parent
@ -1493,13 +1624,24 @@ in
width: parent.width - 16 width: parent.width - 16
spacing: 8 spacing: 8
Row {
spacing: 6
Text { Text {
text: "\u{f0641} Applications" anchors.verticalCenter: parent.verticalCenter
text: "graphic_eq"
color: Theme.base05
font.family: Theme.iconFont
font.pixelSize: 16
}
Text {
anchors.verticalCenter: parent.verticalCenter
text: "Applications"
color: Theme.base05 color: Theme.base05
font.family: Theme.fontFamily font.family: Theme.fontFamily
font.pixelSize: 13 font.pixelSize: 13
font.weight: Font.Medium font.weight: Font.Medium
} }
}
// Per-app streams // Per-app streams
Column { Column {
@ -1602,20 +1744,48 @@ in
Column { Column {
id: netDropdownCol id: netDropdownCol
anchors.centerIn: parent anchors.centerIn: parent
width: 220 width: 228
spacing: 8
// Connection card
Rectangle {
width: parent.width
height: connCardCol.height + 16
radius: 8
color: Theme.base01
Column {
id: connCardCol
anchors.top: parent.top
anchors.topMargin: 8
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width - 16
spacing: 4 spacing: 4
Text { Row {
width: parent.width width: parent.width
spacing: 6
Text {
anchors.verticalCenter: parent.verticalCenter
text: netWidget.netState === "connected" ? "wifi" : "wifi_off"
color: Theme.base05
font.family: Theme.iconFont
font.pixelSize: 16
}
Text {
anchors.verticalCenter: parent.verticalCenter
width: parent.width - 22
text: netWidget.netState === "connected" text: netWidget.netState === "connected"
? "\u{f05a9} " + netWidget.netConn ? netWidget.netConn : "Not connected"
: "\u{f05aa} Not connected"
color: Theme.base05 color: Theme.base05
font.family: Theme.fontFamily font.family: Theme.fontFamily
font.pixelSize: 13 font.pixelSize: 13
font.weight: Font.Medium font.weight: Font.Medium
elide: Text.ElideRight elide: Text.ElideRight
} }
}
Rectangle { Rectangle {
visible: netWidget.netState === "connected" visible: netWidget.netState === "connected"
@ -1637,31 +1807,41 @@ in
id: disconnectMouse id: disconnectMouse
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
netDisconnectProc.targetDevice = netWidget.netDevice; netDisconnectProc.targetDevice = netWidget.netDevice;
netDisconnectProc.running = true; netDisconnectProc.running = true;
netWidget.netState = "disconnected"; netWidget.netState = "disconnected";
netWidget.netConn = ""; netWidget.netConn = "";
netWidget.netIcon = "\u{f05aa}"; netWidget.netIcon = "wifi_off";
bar.closeAllDropdowns(); bar.closeAllDropdowns();
netRefreshDelay.start(); netRefreshDelay.start();
} }
} }
} }
Rectangle {
width: parent.width - 20
anchors.horizontalCenter: parent.horizontalCenter
height: 1
color: Theme.base03
} }
}
// Available networks card
Rectangle {
width: parent.width
height: netsCardCol.height + 16
radius: 8
color: Theme.base01
Column {
id: netsCardCol
anchors.top: parent.top
anchors.topMargin: 8
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width - 16
spacing: 4
Text { Text {
text: "Available networks" text: "Available networks"
color: Theme.base03 color: Theme.base04
font.family: Theme.fontFamily font.family: Theme.fontFamily
font.pixelSize: 11 font.pixelSize: 11
topPadding: 2
} }
Repeater { Repeater {
@ -1669,7 +1849,7 @@ in
Rectangle { Rectangle {
required property var modelData required property var modelData
width: 220 width: netsCardCol.width
height: 32 height: 32
color: netItemMouse.containsMouse ? Theme.base02 : "transparent" color: netItemMouse.containsMouse ? Theme.base02 : "transparent"
Behavior on color { ColorAnimation { duration: 120 } } Behavior on color { ColorAnimation { duration: 120 } }
@ -1686,14 +1866,14 @@ in
Text { Text {
text: { text: {
let s = modelData.signal; let s = modelData.signal;
if (s >= 75) return "\u{f0928}"; // strength 4 if (s >= 75) return "signal_wifi_4_bar";
if (s >= 50) return "\u{f0925}"; // strength 3 if (s >= 50) return "network_wifi_3_bar";
if (s >= 25) return "\u{f0922}"; // strength 2 if (s >= 25) return "network_wifi_2_bar";
return "\u{f091f}"; // strength 1 return "network_wifi_1_bar";
} }
color: modelData.active ? Theme.base0B : Theme.base04 color: modelData.active ? Theme.base0B : Theme.base04
font.family: Theme.fontFamily font.family: Theme.iconFont
font.pixelSize: 13 font.pixelSize: 16
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
@ -1709,10 +1889,10 @@ in
Text { Text {
visible: modelData.security !== "" && modelData.security !== "--" visible: modelData.security !== "" && modelData.security !== "--"
text: "\u{f0341}" text: "lock"
color: Theme.base03 color: Theme.base03
font.family: Theme.fontFamily font.family: Theme.iconFont
font.pixelSize: 10 font.pixelSize: 13
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
@ -1721,6 +1901,7 @@ in
id: netItemMouse id: netItemMouse
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (!modelData.active) { if (!modelData.active) {
wifiConnectProc.targetSsid = modelData.ssid; wifiConnectProc.targetSsid = modelData.ssid;
@ -1734,6 +1915,8 @@ in
} }
} }
} }
}
}
${lib.optionalString isMacbook '' ${lib.optionalString isMacbook ''
// Battery dropdown // Battery dropdown
@ -1770,8 +1953,8 @@ in
Text { Text {
text: batteryWidget.batteryIcon text: batteryWidget.batteryIcon
color: Theme.base05 color: Theme.base05
font.family: Theme.fontFamily font.family: Theme.iconFont
font.pixelSize: 18 font.pixelSize: 22
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
@ -1848,9 +2031,9 @@ in
Repeater { Repeater {
model: [ model: [
{ name: "power-saver", profile: PowerProfile.PowerSaver, label: "\u{f0425}", tip: "Saver" }, { name: "power-saver", profile: PowerProfile.PowerSaver, label: "energy_savings_leaf", tip: "Saver" },
{ name: "balanced", profile: PowerProfile.Balanced, label: "\u{f0376}", tip: "Balanced" }, { name: "balanced", profile: PowerProfile.Balanced, label: "balance", tip: "Balanced" },
{ name: "performance", profile: PowerProfile.Performance, label: "\u{f0e0e}", tip: "Performance" } { name: "performance", profile: PowerProfile.Performance, label: "speed", tip: "Performance" }
] ]
Rectangle { Rectangle {
@ -1871,8 +2054,8 @@ in
color: batteryWidget.powerProfile === modelData.name color: batteryWidget.powerProfile === modelData.name
? Theme.base0D : Theme.base05 ? Theme.base0D : Theme.base05
Behavior on color { ColorAnimation { duration: 200 } } Behavior on color { ColorAnimation { duration: 200 } }
font.family: Theme.fontFamily font.family: Theme.iconFont
font.pixelSize: 14 font.pixelSize: 17
} }
Text { Text {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@ -1963,16 +2146,16 @@ in
onTriggered: weatherProc.running = true onTriggered: weatherProc.running = true
} }
function weatherGlyph(code) { function weatherGlyph(code) {
if (code === 0) return "\u{f0599}"; // sunny if (code === 0) return "clear_day";
if (code <= 2) return "\u{f0595}"; // partly cloudy if (code <= 2) return "partly_cloudy_day";
if (code === 3) return "\u{f0590}"; // overcast if (code === 3) return "cloud";
if (code <= 48) return "\u{f0591}"; // fog if (code <= 48) return "foggy";
if (code <= 57) return "\u{f0597}"; // drizzle if (code <= 57) return "rainy";
if (code <= 67) return "\u{f0596}"; // rain if (code <= 67) return "rainy";
if (code <= 77) return "\u{f0598}"; // snow if (code <= 77) return "cloudy_snowing";
if (code <= 82) return "\u{f0597}"; // showers if (code <= 82) return "rainy";
if (code <= 86) return "\u{f0598}"; // snow showers if (code <= 86) return "cloudy_snowing";
return "\u{f0593}"; // thunder return "thunderstorm";
} }
// --- Media: prefer the actively playing MPRIS player --- // --- Media: prefer the actively playing MPRIS player ---
@ -2029,10 +2212,10 @@ in
Behavior on color { ColorAnimation { duration: 120 } } Behavior on color { ColorAnimation { duration: 120 } }
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: "\u{f0141}" text: "chevron_left"
color: Theme.base05 color: Theme.base05
font.family: Theme.fontFamily font.family: Theme.iconFont
font.pixelSize: 16 font.pixelSize: 18
} }
MouseArea { MouseArea {
id: calPrevMa id: calPrevMa
@ -2064,10 +2247,10 @@ in
Behavior on color { ColorAnimation { duration: 120 } } Behavior on color { ColorAnimation { duration: 120 } }
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: "\u{f0142}" text: "chevron_right"
color: Theme.base05 color: Theme.base05
font.family: Theme.fontFamily font.family: Theme.iconFont
font.pixelSize: 16 font.pixelSize: 18
} }
MouseArea { MouseArea {
id: calNextMa id: calNextMa
@ -2159,8 +2342,8 @@ in
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
text: calPopup.weatherGlyph(modelData.code) text: calPopup.weatherGlyph(modelData.code)
color: Theme.base0C color: Theme.base0C
font.family: Theme.fontFamily font.family: Theme.iconFont
font.pixelSize: 14 font.pixelSize: 16
} }
Text { Text {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@ -2210,10 +2393,10 @@ in
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
visible: albumArt.status !== Image.Ready visible: albumArt.status !== Image.Ready
text: "\u{f0387}" text: "music_note"
color: Theme.base04 color: Theme.base04
font.family: Theme.fontFamily font.family: Theme.iconFont
font.pixelSize: 20 font.pixelSize: 22
} }
Image { Image {
id: albumArt id: albumArt
@ -2251,9 +2434,9 @@ in
spacing: 2 spacing: 2
Repeater { Repeater {
model: [ model: [
{ glyph: "\u{f04ae}", act: "prev" }, { glyph: "skip_previous", act: "prev" },
{ glyph: calPopup.player && calPopup.player.playbackState === MprisPlaybackState.Playing ? "\u{f03e4}" : "\u{f040a}", act: "toggle" }, { glyph: calPopup.player && calPopup.player.playbackState === MprisPlaybackState.Playing ? "pause" : "play_arrow", act: "toggle" },
{ glyph: "\u{f04ad}", act: "next" } { glyph: "skip_next", act: "next" }
] ]
Rectangle { Rectangle {
id: mediaBtn id: mediaBtn
@ -2265,8 +2448,8 @@ in
anchors.centerIn: parent anchors.centerIn: parent
text: mediaBtn.modelData.glyph text: mediaBtn.modelData.glyph
color: Theme.base05 color: Theme.base05
font.family: Theme.fontFamily font.family: Theme.iconFont
font.pixelSize: 16 font.pixelSize: 18
} }
MouseArea { MouseArea {
id: mediaBtnMa id: mediaBtnMa
@ -2425,10 +2608,10 @@ in
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.margins: 6 anchors.margins: 6
text: "\u{f0156}" text: "close"
color: dismissMa.containsMouse ? Theme.base05 : Theme.base03 color: dismissMa.containsMouse ? Theme.base05 : Theme.base03
font.family: Theme.fontFamily font.family: Theme.iconFont
font.pixelSize: 12 font.pixelSize: 14
MouseArea { MouseArea {
id: dismissMa id: dismissMa
anchors.fill: parent anchors.fill: parent
@ -2604,10 +2787,10 @@ in
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.margins: 8 anchors.margins: 8
text: "\u{f0156}" text: "close"
color: toastDismissMa.containsMouse ? Theme.base05 : Theme.base03 color: toastDismissMa.containsMouse ? Theme.base05 : Theme.base03
font.family: Theme.fontFamily font.family: Theme.iconFont
font.pixelSize: 13 font.pixelSize: 15
MouseArea { MouseArea {
id: toastDismissMa id: toastDismissMa
anchors.fill: parent anchors.fill: parent