466 lines
17 KiB
QML
Raw Normal View History

2026-03-09 00:07:31 -04:00
import Quickshell
import Quickshell.Hyprland
import Quickshell.Widgets
import Quickshell.Services.SystemTray
import Quickshell.Io
import QtQuick
PanelWindow {
id: bar
required property var walColors
required property string motd
required property int notificationCount
required property var latestNotification
property var notifServer: null
required property SystemStats stats
required property string fontFamily
// --- Notification toast state ---
property int _toastCounter: 0
onLatestNotificationChanged: {
if (latestNotification) {
toastModel.append({
toastId: _toastCounter++,
appName: latestNotification.appName || "",
summary: latestNotification.summary || "",
body: latestNotification.body || ""
})
}
}
ListModel {
id: toastModel
}
anchors {
top: true
left: true
right: true
}
color: "transparent"
exclusionMode: ExclusionMode.Normal
exclusiveZone: 34
implicitHeight: 34
// --- Theme colors ---
property color bgColor: (walColors.special && walColors.special.background) ? walColors.special.background : "#1d160d"
property color fgColor: (walColors.special && walColors.special.foreground) ? walColors.special.foreground : "#d8d8d3"
property color accentColor: (walColors.colors && walColors.colors.color1) ? walColors.colors.color1 : "#946F50"
property color accent2Color: (walColors.colors && walColors.colors.color2) ? walColors.colors.color2 : "#BA9351"
property color accent3Color: (walColors.colors && walColors.colors.color3) ? walColors.colors.color3 : "#BA9351"
property color accent4Color: (walColors.colors && walColors.colors.color4) ? walColors.colors.color4 : "#BA9351"
property color accent5Color: (walColors.colors && walColors.colors.color5) ? walColors.colors.color5 : "#BA9351"
property color accent6Color: (walColors.colors && walColors.colors.color6) ? walColors.colors.color6 : "#BA9351"
property color accent7Color: (walColors.colors && walColors.colors.color7) ? walColors.colors.color7 : "#BA9351"
// --- Hyprland state ---
property var hyprMonitor: Hyprland.monitorFor(screen)
property var monitorWorkspaces: {
let vals = Hyprland.workspaces.values
let result = []
for (let i = 0; i < vals.length; i++) {
if (vals[i].monitor === hyprMonitor && vals[i].id > 0)
result.push(vals[i])
}
return result.sort((a, b) => a.id - b.id)
}
property bool specialWorkspaceVisible: false
Connections {
target: Hyprland
function onRawEvent(event) {
if (event.name === "activespecial") {
let parts = event.data.split(",")
let monName = parts[parts.length - 1]
if (monName === hyprMonitor?.name) {
bar.specialWorkspaceVisible = parts[0] !== ""
}
}
}
}
property string windowTitle: {
2026-03-14 17:32:02 -04:00
const active = Hyprland.activeToplevel
const currentWs = Hyprland.focusedWorkspace?.id
let title = active?.title ?? ""
if (!title || title === "~" || title === "kitty" || title === "fish" || active.workspace?.id !== currentWs)
2026-03-09 00:07:31 -04:00
return motd
return title
}
// --- Clock ---
SystemClock {
id: clock
precision: SystemClock.Minutes
}
// --- Bar layout ---
Item {
anchors.fill: parent
anchors.margins: 2
// Left module group
Rectangle {
id: leftGroup
z: 1
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
height: 30
width: leftContent.width + 8
clip: true
color: bar.bgColor
border.color: bar.accentColor
border.width: 2
radius: 100
Behavior on width {
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
}
Row {
id: leftContent
anchors.left: parent.left
anchors.leftMargin: 4
anchors.verticalCenter: parent.verticalCenter
spacing: 0
Workspaces {
workspaces: bar.monitorWorkspaces
accentColor: bar.accentColor
fgColor: bar.fgColor
fontFamily: bar.fontFamily
anchors.verticalCenter: parent.verticalCenter
}
}
}
// Special workspace badge
Rectangle {
id: specialBadge
z: 0
anchors.verticalCenter: leftGroup.verticalCenter
height: 28
width: specialText.implicitWidth + (bar.specialWorkspaceVisible ? 30 : 10)
topLeftRadius: 0
bottomLeftRadius: 0
topRightRadius: 100
bottomRightRadius: 100
color: bar.bgColor
border.color: bar.accentColor
border.width: 2
visible: bar.specialWorkspaceVisible
x: bar.specialWorkspaceVisible ? leftGroup.x + leftGroup.width - 10 : leftGroup.x + leftGroup.width - specialBadge.width
Behavior on x {
NumberAnimation { duration: 200; easing.type: Easing.InOutCubic }
}
Behavior on width {
NumberAnimation { duration: 200; easing.type: Easing.InOutCubic }
}
Text {
id: specialText
anchors.centerIn: parent
anchors.horizontalCenterOffset: 4
text: "\uf2d2"
color: bar.fgColor
font.pixelSize: 11
font.family: bar.fontFamily
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: Hyprland.dispatch("togglespecialworkspace magic")
}
}
// Center module group
Rectangle {
id: centerGroup
anchors.centerIn: parent
height: 30
width: centerContent.width + 16
clip: true
color: bar.bgColor
border.color: bar.accentColor
border.width: 2
radius: 100
Behavior on width {
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
}
Row {
id: centerContent
anchors.centerIn: parent
spacing: 12
Text {
text: bar.windowTitle
color: bar.fgColor
font.pixelSize: 13
font.family: bar.fontFamily
elide: Text.ElideRight
width: Math.min(implicitWidth, 900)
anchors.verticalCenter: parent.verticalCenter
}
}
}
// Notification toasts
Repeater {
model: toastModel
delegate: NotificationToast {
bgColor: bar.bgColor
fgColor: bar.fgColor
accentColor: bar.accentColor
fontFamily: bar.fontFamily
rightAnchor: rightGroup
sourceModel: toastModel
}
}
// Right module group
Rectangle {
id: rightGroup
z: 1
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
height: 30
width: rightContent.width + 8
clip: true
color: bar.bgColor
border.color: bar.accentColor
border.width: 2
radius: 100
Behavior on x {
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
}
Behavior on width {
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
}
Row {
id: rightContent
anchors.right: parent.right
anchors.rightMargin: 4
anchors.verticalCenter: parent.verticalCenter
spacing: 5
// Notification count
Item {
id: notifBadgeWrapper
property real targetWidth: bar.notificationCount > 0 ? notifCountText.implicitWidth + 16 : 0
height: 22
width: targetWidth
clip: true
anchors.verticalCenter: parent.verticalCenter
visible: width > 0
Behavior on width {
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
}
Rectangle {
anchors.left: parent.left
height: 22
width: notifCountText.implicitWidth + 16
topLeftRadius: 100
bottomLeftRadius: 100
radius: parent.visible ? 4 : 100
color: bar.accent7Color
Behavior on radius {
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
}
Text {
id: notifCountText
anchors.centerIn: parent
text: bar.notificationCount + " \uf0f3"
color: bar.bgColor
font.pixelSize: 13
font.family: bar.fontFamily
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
if (!bar.notifServer) return
let notifs = bar.notifServer.trackedNotifications.values
for (let i = notifs.length - 1; i >= 0; i--)
notifs[i].dismiss()
}
}
}
}
// Updates
BarPill {
id: updatesPill
fontFamily: bar.fontFamily
2026-03-14 17:32:02 -04:00
label: stats.updatesCount + (stats.updatesCount === 0 ? " 󰂪" : " 󱍷")
2026-03-09 00:07:31 -04:00
pillColor: bar.accent6Color
textColor: bar.bgColor
tooltipText: stats.updatesList || "Up to date"
topLeftRadius: bar.notificationCount > 0 ? 4 : 100
bottomLeftRadius: bar.notificationCount > 0 ? 4 : 100
Behavior on topLeftRadius {
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
}
Behavior on bottomLeftRadius {
NumberAnimation { duration: 200; easing.type: Easing.InOutQuad }
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
cursorShape: Qt.PointingHandCursor
onClicked: mouse => {
if (mouse.button === Qt.RightButton)
stats.checkUpdates()
else
updateProc.running = true
}
}
Process {
id: updateProc
command: ["kitty", "--title", "System Update", "-e", "bash", "-c", "yay && echo 'Update completed successfully!' || { echo 'Update failed! Press any key to close...'; read -n 1; }"]
}
}
// CPU
BarPill {
fontFamily: bar.fontFamily
label: stats.cpuUsage + "% \uf2db"
pillColor: bar.accent5Color
textColor: bar.bgColor
}
// Memory
BarPill {
id: memPill
fontFamily: bar.fontFamily
label: stats.memUsage + "% \uf0c9"
pillColor: bar.accent4Color
textColor: bar.bgColor
tooltipText: stats.memInfo
}
// Temperature
BarPill {
id: tempPill
fontFamily: bar.fontFamily
label: {
let t = stats.temperature
let icon = t >= 80 ? "\uf2c7" : "\uf2c9"
return t + "\u00b0C " + icon
}
pillColor: bar.accent3Color
textColor: bar.bgColor
tooltipText: stats.cpuTemps || "No sensors"
}
// Clock
BarPill {
id: clockPill
fontFamily: bar.fontFamily
label: Qt.formatDateTime(clock.date, "hh:mm")
pillColor: bar.accent2Color
textColor: bar.bgColor
tooltipText: Qt.formatDateTime(clock.date, "dddd, MMMM d, yyyy")
}
// System tray
Rectangle {
height: 22
width: trayRow.width + 16
topLeftRadius: 4
bottomLeftRadius: 4
topRightRadius: 100
bottomRightRadius: 100
color: bar.accentColor
anchors.verticalCenter: parent.verticalCenter
Row {
id: trayRow
anchors.centerIn: parent
spacing: 6
Repeater {
model: SystemTray.items
delegate: Item {
id: trayDelegate
required property var modelData
width: 16
height: 16
anchors.verticalCenter: parent.verticalCenter
Image {
anchors.fill: parent
source: modelData.icon
sourceSize.width: 16
sourceSize.height: 16
fillMode: Image.PreserveAspectFit
}
QsMenuAnchor {
id: menuAnchor
menu: modelData.menu
anchor.window: bar
anchor.rect.x: trayDelegate.mapToItem(bar.contentItem, 0, 0).x
anchor.rect.y: trayDelegate.mapToItem(bar.contentItem, 0, 0).y
anchor.rect.width: trayDelegate.width
anchor.rect.height: trayDelegate.height
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: mouse => {
if (mouse.button === Qt.RightButton)
menuAnchor.open()
else
modelData.activate()
}
}
}
}
}
}
}
}
}
// --- Tooltips ---
BarTooltip {
bgColor: bar.bgColor; fgColor: bar.fgColor; borderColor: bar.accentColor; fontFamily: bar.fontFamily
target: updatesPill; text: updatesPill.tooltipText
}
BarTooltip {
bgColor: bar.bgColor; fgColor: bar.fgColor; borderColor: bar.accentColor; fontFamily: bar.fontFamily
target: memPill; text: memPill.tooltipText
}
BarTooltip {
bgColor: bar.bgColor; fgColor: bar.fgColor; borderColor: bar.accentColor; fontFamily: bar.fontFamily
target: tempPill; text: tempPill.tooltipText
}
BarTooltip {
bgColor: bar.bgColor; fgColor: bar.fgColor; borderColor: bar.accentColor; fontFamily: bar.fontFamily
target: clockPill; text: clockPill.tooltipText
}
}