quickshell: modularize QML into separate files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
rope 2026-05-26 20:34:03 +01:00
parent 252d88c758
commit 3a4d8db003

View file

@ -441,21 +441,72 @@ in
Install.WantedBy = [ "hyprland-session.target" ]; Install.WantedBy = [ "hyprland-session.target" ];
}; };
xdg.configFile."quickshell/shell.qml" = { xdg.configFile = let
onChange = '' qsRestart = ''
${pkgs.systemd}/bin/systemctl --user restart quickshell.service 2>/dev/null || true ${pkgs.systemd}/bin/systemctl --user restart quickshell.service 2>/dev/null || true
''; '';
wifiConnectScript = pkgs.writeShellScript "wifi-connect" ''
ssid="$1"
${pkgs.networkmanager}/bin/nmcli device wifi connect "$ssid" 2>/dev/null && exit 0
pw=$(${pkgs.zenity}/bin/zenity --password --title="WiFi Password" 2>/dev/null)
[ -n "$pw" ] && ${pkgs.networkmanager}/bin/nmcli device wifi connect "$ssid" password "$pw"
'';
nmcli = "${pkgs.networkmanager}/bin/nmcli";
powerprofilesctl = "${pkgs.power-profiles-daemon}/bin/powerprofilesctl";
in {
"quickshell/qmldir" = {
onChange = qsRestart;
text = ''
singleton Theme 1.0 Theme.qml
singleton Commands 1.0 Commands.qml
'';
};
"quickshell/Theme.qml" = {
onChange = qsRestart;
text = ''
pragma Singleton
import QtQuick
QtObject {
readonly property color base00: "#${c.base00}"
readonly property color base01: "#${c.base01}"
readonly property color base02: "#${c.base02}"
readonly property color base03: "#${c.base03}"
readonly property color base04: "#${c.base04}"
readonly property color base05: "#${c.base05}"
readonly property color base08: "#${c.base08}"
readonly property color base0A: "#${c.base0A}"
readonly property color base0B: "#${c.base0B}"
readonly property color base0C: "#${c.base0C}"
readonly property color base0D: "#${c.base0D}"
readonly property color barBg: "#D1${c.base00}"
readonly property color toastBg: "#E6${c.base00}"
}
'';
};
"quickshell/Commands.qml" = {
onChange = qsRestart;
text = ''
pragma Singleton
import QtQuick
QtObject {
readonly property string nmcli: "${nmcli}"
readonly property string wifiConnect: "${wifiConnectScript}"
readonly property string powerprofilesctl: "${powerprofilesctl}"
}
'';
};
"quickshell/shell.qml" = {
onChange = qsRestart;
text = '' text = ''
//@ pragma UseQApplication //@ pragma UseQApplication
import Quickshell import Quickshell
import Quickshell.Hyprland
import Quickshell.Services.SystemTray
import Quickshell.Widgets
import Quickshell.Io
import Quickshell.Services.Notifications import Quickshell.Services.Notifications
import QtQuick import QtQuick
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
ShellRoot { ShellRoot {
id: root id: root
@ -479,9 +530,39 @@ in
Variants { Variants {
model: Quickshell.screens model: Quickshell.screens
Bar {
notifServer: notifServer
}
}
Variants {
model: Quickshell.screens
NotificationToast {
shellRoot: root
}
}
}
'';
};
"quickshell/Bar.qml" = {
onChange = qsRestart;
text = ''
import Quickshell
import Quickshell.Hyprland
import Quickshell.Services.SystemTray
import Quickshell.Services.Notifications
import Quickshell.Widgets
import Quickshell.Io
import QtQuick
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
PanelWindow { PanelWindow {
id: bar id: bar
required property var modelData required property var modelData
required property NotificationServer notifServer
screen: modelData screen: modelData
anchors { anchors {
@ -491,7 +572,7 @@ in
} }
implicitHeight: 30 implicitHeight: 30
color: "#D1${c.base00}" color: Theme.barBg
property var activeDropdown: null property var activeDropdown: null
function closeAllDropdowns() { function closeAllDropdowns() {
@ -510,7 +591,6 @@ in
} }
if (setupFn) setupFn(); if (setupFn) setupFn();
if (dd.closing) { if (dd.closing) {
// Cancel close animation, reopen
dd.closing = false; dd.closing = false;
dd.open = true; dd.open = true;
} else { } else {
@ -538,7 +618,7 @@ in
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: modelData.name text: modelData.name
color: modelData.focused ? "#${c.base05}" : "#${c.base03}" color: modelData.focused ? Theme.base05 : Theme.base03
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 13 font.pixelSize: 13
} }
@ -548,7 +628,7 @@ in
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
width: parent.width - 8 width: parent.width - 8
height: 2 height: 2
color: "#${c.base05}" color: Theme.base05
visible: modelData.focused visible: modelData.focused
} }
@ -566,7 +646,7 @@ in
anchors.centerIn: parent anchors.centerIn: parent
property date now: new Date() property date now: new Date()
text: now.toLocaleTimeString(Qt.locale(), "HH:mm") text: now.toLocaleTimeString(Qt.locale(), "HH:mm")
color: "#${c.base05}" color: Theme.base05
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 13 font.pixelSize: 13
font.weight: Font.Medium font.weight: Font.Medium
@ -603,6 +683,14 @@ in
property string netConn: "" property string netConn: ""
property string netType: "" property string netType: ""
property string netIcon: "\u{f0b0}" property string netIcon: "\u{f0b0}"
property var wifiNetworks: []
property string netDevice: ""
property string _pendingState: "disconnected"
property string _pendingConn: ""
property string _pendingType: ""
property string _pendingDevice: ""
property var _pendingNets: []
Timer { Timer {
interval: 5000 interval: 5000
@ -612,11 +700,6 @@ in
onTriggered: netWidget.refreshNet() onTriggered: netWidget.refreshNet()
} }
property string _pendingState: "disconnected"
property string _pendingConn: ""
property string _pendingType: ""
property string _pendingDevice: ""
function refreshNet() { function refreshNet() {
netWidget._pendingState = "disconnected"; netWidget._pendingState = "disconnected";
netWidget._pendingConn = ""; netWidget._pendingConn = "";
@ -626,7 +709,7 @@ in
Process { Process {
id: netProc id: netProc
command: ["${pkgs.networkmanager}/bin/nmcli", "-t", "-f", "DEVICE,TYPE,STATE,CONNECTION", "device"] command: [Commands.nmcli, "-t", "-f", "DEVICE,TYPE,STATE,CONNECTION", "device"]
stdout: SplitParser { stdout: SplitParser {
onRead: data => { onRead: data => {
let fields = data.split(":"); let fields = data.split(":");
@ -663,25 +746,20 @@ in
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: netWidget.netIcon text: netWidget.netIcon
color: "#${c.base05}" color: Theme.base05
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 14 font.pixelSize: 14
} }
property var wifiNetworks: []
property string netDevice: ""
Timer { Timer {
id: netRefreshDelay id: netRefreshDelay
interval: 2000 interval: 2000
onTriggered: netWidget.refreshNet() onTriggered: netWidget.refreshNet()
} }
property var _pendingNets: []
Process { Process {
id: wifiScanProc id: wifiScanProc
command: ["${pkgs.networkmanager}/bin/nmcli", "-t", "-f", "SSID,SIGNAL,SECURITY,IN-USE", "device", "wifi", "list", "--rescan", "auto"] command: [Commands.nmcli, "-t", "-f", "SSID,SIGNAL,SECURITY,IN-USE", "device", "wifi", "list", "--rescan", "auto"]
stdout: SplitParser { stdout: SplitParser {
onRead: data => { onRead: data => {
let fields = data.split(":"); let fields = data.split(":");
@ -708,21 +786,15 @@ in
Process { Process {
id: wifiConnectProc id: wifiConnectProc
property string targetSsid: "" property string targetSsid: ""
command: ["${pkgs.writeShellScript "wifi-connect" '' command: [Commands.wifiConnect, targetSsid]
ssid="$1"
${pkgs.networkmanager}/bin/nmcli device wifi connect "$ssid" 2>/dev/null && exit 0
pw=$(${pkgs.zenity}/bin/zenity --password --title="WiFi Password" 2>/dev/null)
[ -n "$pw" ] && ${pkgs.networkmanager}/bin/nmcli device wifi connect "$ssid" password "$pw"
''}", targetSsid]
} }
Process { Process {
id: netDisconnectProc id: netDisconnectProc
property string targetDevice: "" property string targetDevice: ""
command: ["${pkgs.networkmanager}/bin/nmcli", "device", "disconnect", targetDevice] command: [Commands.nmcli, "device", "disconnect", targetDevice]
} }
function openNetDropdown() { function openNetDropdown() {
bar.toggleDropdown(netDropdown, function() { bar.toggleDropdown(netDropdown, function() {
wifiScanProc.running = true; wifiScanProc.running = true;
@ -793,7 +865,6 @@ in
} else if (lineNum === 5) { } else if (lineNum === 5) {
if (!isNaN(num)) batteryWidget.energyFull = num / 1000000.0; if (!isNaN(num)) batteryWidget.energyFull = num / 1000000.0;
lineNum = 0; lineNum = 0;
// Calculate time remaining
if (batteryWidget.powerDraw > 0.5) { if (batteryWidget.powerDraw > 0.5) {
let hours; let hours;
if (batteryWidget.charging) { if (batteryWidget.charging) {
@ -815,7 +886,7 @@ in
Process { Process {
id: profileProc id: profileProc
command: ["${pkgs.power-profiles-daemon}/bin/powerprofilesctl", "get"] command: [Commands.powerprofilesctl, "get"]
stdout: SplitParser { stdout: SplitParser {
onRead: data => { onRead: data => {
batteryWidget.powerProfile = data.trim(); batteryWidget.powerProfile = data.trim();
@ -826,7 +897,7 @@ in
Process { Process {
id: setProfileProc id: setProfileProc
property string target: "balanced" property string target: "balanced"
command: ["${pkgs.power-profiles-daemon}/bin/powerprofilesctl", "set", target] command: [Commands.powerprofilesctl, "set", target]
} }
Row { Row {
@ -836,9 +907,9 @@ in
Text { Text {
id: batteryText id: batteryText
text: batteryWidget.batteryLevel + "%" text: batteryWidget.batteryLevel + "%"
color: batteryWidget.batteryLevel <= 15 ? "#${c.base08}" color: batteryWidget.batteryLevel <= 15 ? Theme.base08
: batteryWidget.batteryLevel <= 30 ? "#${c.base0A}" : batteryWidget.batteryLevel <= 30 ? Theme.base0A
: "#${c.base05}" : Theme.base05
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 13 font.pixelSize: 13
} }
@ -846,9 +917,9 @@ in
Text { Text {
id: batteryIconText id: batteryIconText
text: batteryWidget.batteryIcon text: batteryWidget.batteryIcon
color: batteryWidget.batteryLevel <= 15 ? "#${c.base08}" color: batteryWidget.batteryLevel <= 15 ? Theme.base08
: batteryWidget.batteryLevel <= 30 ? "#${c.base0A}" : batteryWidget.batteryLevel <= 30 ? Theme.base0A
: "#${c.base05}" : Theme.base05
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 14 font.pixelSize: 14
} }
@ -900,7 +971,7 @@ in
ColorOverlay { ColorOverlay {
anchors.fill: trayIcon anchors.fill: trayIcon
source: trayIcon source: trayIcon
color: "#${c.base05}" color: Theme.base05
} }
MouseArea { MouseArea {
@ -1013,7 +1084,7 @@ in
onPaint: { onPaint: {
var ctx = getContext("2d"); var ctx = getContext("2d");
ctx.clearRect(0, 0, 8, 8); ctx.clearRect(0, 0, 8, 8);
ctx.fillStyle = "#D1${c.base00}"; ctx.fillStyle = Theme.barBg;
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(0, 0); ctx.lineTo(8, 0); ctx.lineTo(8, 8); ctx.moveTo(0, 0); ctx.lineTo(8, 0); ctx.lineTo(8, 8);
ctx.arc(0, 8, 8, 0, -Math.PI / 2, true); ctx.arc(0, 8, 8, 0, -Math.PI / 2, true);
@ -1035,7 +1106,7 @@ in
onPaint: { onPaint: {
var ctx = getContext("2d"); var ctx = getContext("2d");
ctx.clearRect(0, 0, 8, 8); ctx.clearRect(0, 0, 8, 8);
ctx.fillStyle = "#D1${c.base00}"; ctx.fillStyle = Theme.barBg;
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(0, 0); ctx.lineTo(8, 0); ctx.moveTo(0, 0); ctx.lineTo(8, 0);
ctx.arc(8, 8, 8, -Math.PI / 2, Math.PI, true); ctx.arc(8, 8, 8, -Math.PI / 2, Math.PI, true);
@ -1050,7 +1121,7 @@ in
anchors.top: parent.top anchors.top: parent.top
width: dropdown.fullWidth width: dropdown.fullWidth
height: dropdown.open ? dropdown.fullHeight : 0 height: dropdown.open ? dropdown.fullHeight : 0
color: "#D1${c.base00}" color: Theme.barBg
radius: 8 radius: 8
topLeftRadius: 0 topLeftRadius: 0
topRightRadius: 0 topRightRadius: 0
@ -1097,7 +1168,7 @@ in
width: 200 width: 200
height: modelData.isSeparator ? 9 : 28 height: modelData.isSeparator ? 9 : 28
color: !modelData.isSeparator && itemMouse.containsMouse && modelData.enabled color: !modelData.isSeparator && itemMouse.containsMouse && modelData.enabled
? "#${c.base02}" : "transparent" ? Theme.base02 : "transparent"
radius: modelData.isSeparator ? 0 : 4 radius: modelData.isSeparator ? 0 : 4
Rectangle { Rectangle {
@ -1105,7 +1176,7 @@ in
anchors.centerIn: parent anchors.centerIn: parent
width: parent.width - 20 width: parent.width - 20
height: 1 height: 1
color: "#${c.base03}" color: Theme.base03
} }
RowLayout { RowLayout {
@ -1118,7 +1189,7 @@ in
Text { Text {
Layout.fillWidth: true Layout.fillWidth: true
text: modelData.text ?? "" text: modelData.text ?? ""
color: modelData.enabled ? "#${c.base05}" : "#${c.base03}" color: modelData.enabled ? Theme.base05 : Theme.base03
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 12 font.pixelSize: 12
elide: Text.ElideRight elide: Text.ElideRight
@ -1127,7 +1198,7 @@ in
Text { Text {
visible: modelData.buttonType !== QsMenuButtonType.None visible: modelData.buttonType !== QsMenuButtonType.None
text: modelData.checkState === Qt.Checked ? "\u2713" : "" text: modelData.checkState === Qt.Checked ? "\u2713" : ""
color: "#${c.base0D}" color: Theme.base0D
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 12 font.pixelSize: 12
} }
@ -1162,31 +1233,29 @@ in
width: 220 width: 220
spacing: 4 spacing: 4
// Current connection header
Text { Text {
width: parent.width width: parent.width
text: netWidget.netState === "connected" text: netWidget.netState === "connected"
? "\u{f05a9} " + netWidget.netConn ? "\u{f05a9} " + netWidget.netConn
: "\u{f05aa} Not connected" : "\u{f05aa} Not connected"
color: "#${c.base05}" color: Theme.base05
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 13 font.pixelSize: 13
font.weight: Font.Medium font.weight: Font.Medium
elide: Text.ElideRight elide: Text.ElideRight
} }
// Disconnect button (when connected)
Rectangle { Rectangle {
visible: netWidget.netState === "connected" visible: netWidget.netState === "connected"
width: parent.width width: parent.width
height: 28 height: 28
color: disconnectMouse.containsMouse ? "#${c.base02}" : "transparent" color: disconnectMouse.containsMouse ? Theme.base02 : "transparent"
radius: 4 radius: 4
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: "Disconnect" text: "Disconnect"
color: "#${c.base08}" color: Theme.base08
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 12 font.pixelSize: 12
} }
@ -1207,24 +1276,21 @@ in
} }
} }
// Separator
Rectangle { Rectangle {
width: parent.width - 20 width: parent.width - 20
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
height: 1 height: 1
color: "#${c.base03}" color: Theme.base03
} }
// Available networks header
Text { Text {
text: "Available networks" text: "Available networks"
color: "#${c.base03}" color: Theme.base03
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 11 font.pixelSize: 11
topPadding: 2 topPadding: 2
} }
// Network list
Repeater { Repeater {
model: netWidget.wifiNetworks model: netWidget.wifiNetworks
@ -1232,7 +1298,7 @@ in
required property var modelData required property var modelData
width: 220 width: 220
height: 32 height: 32
color: netItemMouse.containsMouse ? "#${c.base02}" : "transparent" color: netItemMouse.containsMouse ? Theme.base02 : "transparent"
radius: 4 radius: 4
Row { Row {
@ -1243,7 +1309,6 @@ in
anchors.rightMargin: 8 anchors.rightMargin: 8
spacing: 8 spacing: 8
// Signal icon
Text { Text {
text: { text: {
let s = modelData.signal; let s = modelData.signal;
@ -1252,16 +1317,15 @@ in
if (s >= 25) return "\u{f05a9}"; if (s >= 25) return "\u{f05a9}";
return "\u{f05aa}"; return "\u{f05aa}";
} }
color: modelData.active ? "#${c.base0B}" : "#${c.base04}" color: modelData.active ? Theme.base0B : Theme.base04
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 13 font.pixelSize: 13
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
// SSID
Text { Text {
text: modelData.ssid text: modelData.ssid
color: modelData.active ? "#${c.base0B}" : "#${c.base05}" color: modelData.active ? Theme.base0B : Theme.base05
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 12 font.pixelSize: 12
elide: Text.ElideRight elide: Text.ElideRight
@ -1269,11 +1333,10 @@ in
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
// Lock icon for secured networks
Text { Text {
visible: modelData.security !== "" && modelData.security !== "--" visible: modelData.security !== "" && modelData.security !== "--"
text: "\u{f0341}" text: "\u{f0341}"
color: "#${c.base03}" color: Theme.base03
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 10 font.pixelSize: 10
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@ -1313,14 +1376,13 @@ in
width: 200 width: 200
spacing: 8 spacing: 8
// Battery status
Row { Row {
width: parent.width width: parent.width
spacing: 8 spacing: 8
Text { Text {
text: batteryWidget.batteryIcon text: batteryWidget.batteryIcon
color: "#${c.base05}" color: Theme.base05
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 18 font.pixelSize: 18
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@ -1330,7 +1392,7 @@ in
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Text { Text {
text: batteryWidget.batteryLevel + "%" + (batteryWidget.charging ? " Charging" : "") text: batteryWidget.batteryLevel + "%" + (batteryWidget.charging ? " Charging" : "")
color: "#${c.base05}" color: Theme.base05
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 13 font.pixelSize: 13
font.weight: Font.Medium font.weight: Font.Medium
@ -1338,30 +1400,27 @@ in
Text { Text {
text: batteryWidget.powerDraw.toFixed(1) + " W" text: batteryWidget.powerDraw.toFixed(1) + " W"
+ (batteryWidget.timeRemaining !== "" ? " \u2022 " + batteryWidget.timeRemaining + (batteryWidget.charging ? " to full" : " left") : "") + (batteryWidget.timeRemaining !== "" ? " \u2022 " + batteryWidget.timeRemaining + (batteryWidget.charging ? " to full" : " left") : "")
color: "#${c.base04}" color: Theme.base04
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 11 font.pixelSize: 11
} }
} }
} }
// Separator
Rectangle { Rectangle {
width: parent.width - 10 width: parent.width - 10
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
height: 1 height: 1
color: "#${c.base03}" color: Theme.base03
} }
// Power profile label
Text { Text {
text: "Power Profile" text: "Power Profile"
color: "#${c.base03}" color: Theme.base03
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 11 font.pixelSize: 11
} }
// Profile buttons
Row { Row {
width: parent.width width: parent.width
spacing: 4 spacing: 4
@ -1379,10 +1438,10 @@ in
height: 36 height: 36
radius: 6 radius: 6
color: batteryWidget.powerProfile === modelData.name color: batteryWidget.powerProfile === modelData.name
? "#${c.base02}" : profMouse.containsMouse ? Theme.base02 : profMouse.containsMouse
? "#${c.base01}" : "transparent" ? Theme.base01 : "transparent"
border.width: batteryWidget.powerProfile === modelData.name ? 1 : 0 border.width: batteryWidget.powerProfile === modelData.name ? 1 : 0
border.color: "#${c.base03}" border.color: Theme.base03
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
@ -1391,14 +1450,14 @@ in
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
text: modelData.label text: modelData.label
color: batteryWidget.powerProfile === modelData.name color: batteryWidget.powerProfile === modelData.name
? "#${c.base0D}" : "#${c.base05}" ? Theme.base0D : Theme.base05
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 14 font.pixelSize: 14
} }
Text { Text {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
text: modelData.tip text: modelData.tip
color: "#${c.base04}" color: Theme.base04
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 9 font.pixelSize: 9
} }
@ -1440,17 +1499,15 @@ in
NumberAnimation { duration: 150; easing.type: Easing.OutCubic } NumberAnimation { duration: 150; easing.type: Easing.OutCubic }
} }
// Date header
Text { Text {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
text: clockText.now.toLocaleDateString(Qt.locale(), "dddd, d MMMM yyyy") text: clockText.now.toLocaleDateString(Qt.locale(), "dddd, d MMMM yyyy")
color: "#${c.base05}" color: Theme.base05
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 14 font.pixelSize: 14
font.weight: Font.Medium font.weight: Font.Medium
} }
// Day headers
Row { Row {
spacing: 0 spacing: 0
Repeater { Repeater {
@ -1460,14 +1517,13 @@ in
width: 28 width: 28
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: modelData text: modelData
color: "#${c.base03}" color: Theme.base03
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 11 font.pixelSize: 11
} }
} }
} }
// Calendar grid
Grid { Grid {
columns: 7 columns: 7
spacing: 0 spacing: 0
@ -1488,7 +1544,7 @@ in
let dayNum = index - startDay + 1; let dayNum = index - startDay + 1;
let daysInMonth = new Date(d.getFullYear(), d.getMonth() + 1, 0).getDate(); let daysInMonth = new Date(d.getFullYear(), d.getMonth() + 1, 0).getDate();
return (dayNum === d.getDate() && dayNum >= 1 && dayNum <= daysInMonth) return (dayNum === d.getDate() && dayNum >= 1 && dayNum <= daysInMonth)
? "#${c.base02}" : "transparent"; ? Theme.base02 : "transparent";
} }
Text { Text {
@ -1506,7 +1562,7 @@ in
let first = new Date(d.getFullYear(), d.getMonth(), 1); let first = new Date(d.getFullYear(), d.getMonth(), 1);
let startDay = (first.getDay() + 6) % 7; let startDay = (first.getDay() + 6) % 7;
let dayNum = parent.index - startDay + 1; let dayNum = parent.index - startDay + 1;
return (dayNum === d.getDate()) ? "#${c.base05}" : "#${c.base04}"; return (dayNum === d.getDate()) ? Theme.base05 : Theme.base04;
} }
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 11 font.pixelSize: 11
@ -1515,19 +1571,17 @@ in
} }
} }
// Separator
Rectangle { Rectangle {
width: 7 * 28 width: 7 * 28
height: 1 height: 1
color: "#${c.base02}" color: Theme.base02
} }
// Notifications header
Row { Row {
width: 7 * 28 width: 7 * 28
Text { Text {
text: "Notifications" text: "Notifications"
color: "#${c.base05}" color: Theme.base05
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 13 font.pixelSize: 13
font.weight: Font.Medium font.weight: Font.Medium
@ -1535,15 +1589,15 @@ in
Item { Layout.fillWidth: true; width: 10 } Item { Layout.fillWidth: true; width: 10 }
Text { Text {
anchors.right: parent.right anchors.right: parent.right
text: notifServer.trackedNotifications.values.length > 0 ? "Clear all" : "" text: bar.notifServer.trackedNotifications.values.length > 0 ? "Clear all" : ""
color: "#${c.base04}" color: Theme.base04
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 11 font.pixelSize: 11
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
let notifs = notifServer.trackedNotifications.values; let notifs = bar.notifServer.trackedNotifications.values;
for (let i = notifs.length - 1; i >= 0; i--) { for (let i = notifs.length - 1; i >= 0; i--) {
notifs[i].dismiss(); notifs[i].dismiss();
} }
@ -1552,22 +1606,21 @@ in
} }
} }
// Notification list
Column { Column {
spacing: 4 spacing: 4
width: 7 * 28 width: 7 * 28
Text { Text {
visible: notifServer.trackedNotifications.values.length === 0 visible: bar.notifServer.trackedNotifications.values.length === 0
text: "No notifications" text: "No notifications"
color: "#${c.base03}" color: Theme.base03
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 11 font.pixelSize: 11
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
} }
Repeater { Repeater {
model: notifServer.trackedNotifications model: bar.notifServer.trackedNotifications
Rectangle { Rectangle {
id: notifItem id: notifItem
@ -1575,7 +1628,7 @@ in
width: 7 * 28 width: 7 * 28
height: notifCol.height + 12 height: notifCol.height + 12
radius: 6 radius: 6
color: "#${c.base01}" color: Theme.base01
Column { Column {
id: notifCol id: notifCol
@ -1588,7 +1641,7 @@ in
Text { Text {
width: parent.width width: parent.width
text: notifItem.modelData.summary || notifItem.modelData.appName text: notifItem.modelData.summary || notifItem.modelData.appName
color: "#${c.base05}" color: Theme.base05
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 11 font.pixelSize: 11
font.weight: Font.Medium font.weight: Font.Medium
@ -1598,7 +1651,7 @@ in
Text { Text {
width: parent.width width: parent.width
text: notifItem.modelData.body || "" text: notifItem.modelData.body || ""
color: "#${c.base04}" color: Theme.base04
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 10 font.pixelSize: 10
elide: Text.ElideRight elide: Text.ElideRight
@ -1607,7 +1660,6 @@ in
visible: text !== "" visible: text !== ""
} }
// Action buttons
Row { Row {
spacing: 4 spacing: 4
visible: notifItem.modelData.actions.length > 0 visible: notifItem.modelData.actions.length > 0
@ -1618,14 +1670,14 @@ in
width: actionText.width + 12 width: actionText.width + 12
height: actionText.height + 4 height: actionText.height + 4
radius: 4 radius: 4
color: actionMa.containsMouse ? "#${c.base02}" : "#${c.base01}" color: actionMa.containsMouse ? Theme.base02 : Theme.base01
border.width: 1 border.width: 1
border.color: "#${c.base02}" border.color: Theme.base02
Text { Text {
id: actionText id: actionText
anchors.centerIn: parent anchors.centerIn: parent
text: modelData.text text: modelData.text
color: "#${c.base05}" color: Theme.base05
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 10 font.pixelSize: 10
} }
@ -1641,14 +1693,13 @@ in
} }
} }
// Dismiss button
Text { Text {
id: dismissBtn id: dismissBtn
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: "\u{f0156}"
color: dismissMa.containsMouse ? "#${c.base05}" : "#${c.base03}" color: dismissMa.containsMouse ? Theme.base05 : Theme.base03
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 12 font.pixelSize: 12
MouseArea { MouseArea {
@ -1665,25 +1716,28 @@ in
} }
} }
} }
} '';
} };
// Notification toast "quickshell/NotificationToast.qml" = {
Variants { onChange = qsRestart;
model: Quickshell.screens text = ''
import Quickshell
import QtQuick
PopupWindow { PopupWindow {
id: notifToast id: notifToast
required property var modelData required property var modelData
required property var shellRoot
screen: modelData screen: modelData
property var currentNotif: null property var currentNotif: null
property bool open: false property bool open: false
Connections { Connections {
target: root target: shellRoot
function onNotificationReceived() { function onNotificationReceived() {
if (notifToast.modelData === Quickshell.screens[0]) { if (notifToast.modelData === Quickshell.screens[0]) {
notifToast.show(root.latestNotification); notifToast.show(shellRoot.latestNotification);
} }
} }
} }
@ -1735,7 +1789,7 @@ in
width: 316 width: 316
height: toastCol.height + 16 height: toastCol.height + 16
radius: 8 radius: 8
color: "#E6${c.base00}" color: Theme.toastBg
clip: true clip: true
transform: Translate { transform: Translate {
@ -1761,7 +1815,7 @@ in
Text { Text {
width: parent.width width: parent.width
text: notifToast.currentNotif ? (notifToast.currentNotif.summary || notifToast.currentNotif.appName) : "" text: notifToast.currentNotif ? (notifToast.currentNotif.summary || notifToast.currentNotif.appName) : ""
color: "#${c.base05}" color: Theme.base05
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 12 font.pixelSize: 12
font.weight: Font.Medium font.weight: Font.Medium
@ -1771,7 +1825,7 @@ in
Text { Text {
width: parent.width width: parent.width
text: notifToast.currentNotif ? (notifToast.currentNotif.body || "") : "" text: notifToast.currentNotif ? (notifToast.currentNotif.body || "") : ""
color: "#${c.base04}" color: Theme.base04
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 11 font.pixelSize: 11
elide: Text.ElideRight elide: Text.ElideRight
@ -1780,7 +1834,6 @@ in
visible: text !== "" visible: text !== ""
} }
// Action buttons
Row { Row {
spacing: 4 spacing: 4
visible: notifToast.currentNotif && notifToast.currentNotif.actions.length > 0 visible: notifToast.currentNotif && notifToast.currentNotif.actions.length > 0
@ -1791,14 +1844,14 @@ in
width: toastActionText.width + 12 width: toastActionText.width + 12
height: toastActionText.height + 6 height: toastActionText.height + 6
radius: 4 radius: 4
color: toastActionMa.containsMouse ? "#${c.base02}" : "#${c.base01}" color: toastActionMa.containsMouse ? Theme.base02 : Theme.base01
border.width: 1 border.width: 1
border.color: "#${c.base02}" border.color: Theme.base02
Text { Text {
id: toastActionText id: toastActionText
anchors.centerIn: parent anchors.centerIn: parent
text: modelData.text text: modelData.text
color: "#${c.base05}" color: Theme.base05
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 10 font.pixelSize: 10
} }
@ -1814,14 +1867,13 @@ in
} }
} }
// Dismiss X
Text { Text {
id: toastDismiss id: toastDismiss
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: "\u{f0156}"
color: toastDismissMa.containsMouse ? "#${c.base05}" : "#${c.base03}" color: toastDismissMa.containsMouse ? Theme.base05 : Theme.base03
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 13 font.pixelSize: 13
MouseArea { MouseArea {
@ -1834,10 +1886,9 @@ in
} }
} }
} }
}
}
''; '';
}; };
};
}; };
}; };