Files
quickshell/modules/notifications/NotificationIndicator.qml
T
2026-07-17 20:03:04 +00:00

234 lines
8.4 KiB
QML

import QtQuick
import Quickshell
import Quickshell.Services.Notifications
import "../../config"
MouseArea {
id: indicator
required property var manager
height: bar.height
width: indicatorRow.width + Config.spacing_fun(height) * 2
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
property bool popupOpen: false
onClicked: {
popupOpen = !popupOpen
if (popupOpen) {
notifPopup.visible = true
} else {
notifPopup.visible = false
}
}
Rectangle {
anchors.fill: parent
color: indicator.containsMouse || popupOpen ? Colors.tertiaryContainer : "transparent"
radius: Config.radius_fun(indicator.height)
border.width: (indicator.containsMouse || popupOpen) ? Config.border_width : 0
border.color: (indicator.containsMouse || popupOpen) ? Colors.tertiaryContainer_fg : "transparent"
Row {
id: indicatorRow
anchors.centerIn: parent
spacing: Config.spacing_fun(indicator.height) / 2
Item {
id: iconWrap
width: Config.iconSize(indicator.height)
height: Config.iconSize(indicator.height)
anchors.verticalCenter: parent.verticalCenter
Image {
id: notifIcon
anchors.fill: parent
source: (manager.lastNotification && manager.lastNotification.appIcon)
? "file://" + manager.lastNotification.appIcon : ""
sourceSize.width: Config.iconSize(indicator.height)
sourceSize.height: Config.iconSize(indicator.height)
asynchronous: true
visible: status === Image.Ready
}
Text {
anchors.centerIn: parent
text: "\uF0F3"
font.pixelSize: Config.iconSize(indicator.height)
font.family: Config.font
visible: !notifIcon.visible
color: indicator.containsMouse ? Colors.tertiary : Colors.secondary
}
}
Rectangle {
id: countBadge
width: Math.max(badgeText.width + 6, 16)
height: 16
radius: 8
visible: manager.notificationCount > 0
anchors.verticalCenter: parent.verticalCenter
color: manager.hasCritical ? Colors.error : Colors.primary
Text {
id: badgeText
anchors.centerIn: parent
text: manager.notificationCount > 99 ? "99+" : manager.notificationCount.toString()
font.pixelSize: Config.font_size_tiny(indicator.height)
font.family: Config.font
font.bold: true
color: manager.hasCritical ? Colors.error_fg : Colors.primary_fg
}
}
Rectangle {
id: criticalDot
width: 8
height: 8
radius: 4
visible: manager.hasCritical
anchors.verticalCenter: parent.verticalCenter
color: Colors.error
SequentialAnimation on opacity {
loops: Animation.Infinite
PropertyAnimation { from: 1.0; to: 0.4; duration: 800 }
PropertyAnimation { from: 0.4; to: 1.0; duration: 800 }
}
}
}
}
PopupWindow {
id: notifPopup
visible: indicator.popupOpen
color: "transparent"
anchor {
item: indicator
edges: Edges.Top | Edges.Left
gravity: Edges.Bottom | Edges.Right
adjustment: PopupAdjustment.FlipY | PopupAdjustment.SlideX
onAnchoring: {
anchor.rect.x = (indicator.width - notifPopup.width) / 2
anchor.rect.y = indicator.height + 4
}
}
implicitWidth: 280
implicitHeight: Math.max(60, popupContent.height + 24)
Rectangle {
anchors.fill: parent
color: Qt.alpha(Colors.primaryContainer, 0.95)
radius: Config.radius
border {
width: Config.border_width
color: Colors.primaryContainer_fg
}
Column {
id: popupContent
width: parent.width
anchors {
top: parent.top
left: parent.left
right: parent.right
margins: 12
}
spacing: 8
Text {
text: "Notifications"
font.pixelSize: Config.font_size_small(indicator.height)
font.family: Config.font
font.bold: true
color: Colors.primary
anchors.horizontalCenter: parent.horizontalCenter
visible: manager.notificationCount > 0
}
Repeater {
model: manager.notifications
delegate: Rectangle {
width: parent.width
height: delegateText.height + 10
radius: Config.small_radius
color: Qt.alpha(Colors.surfaceContainerHighest, 0.5)
property var notif: modelData
Row {
anchors {
left: parent.left
right: parent.right
margins: 6
verticalCenter: parent.verticalCenter
}
spacing: 6
Rectangle {
width: 6
height: 24
radius: 3
color: {
if (notif.urgency === NotificationUrgency.Critical) return Colors.error
if (notif.urgency === NotificationUrgency.Low) return Colors.outline
return Colors.primary
}
anchors.verticalCenter: parent.verticalCenter
}
Column {
width: parent.width - 24
spacing: 2
Text {
id: delegateText
width: parent.width
text: notif.summary || notif.appName || "Notification"
font.pixelSize: Config.labelSize(indicator.height)
font.family: Config.font
font.bold: true
color: Colors.surface_fg
elide: Text.ElideRight
maximumLineCount: 1
}
Text {
width: parent.width
text: notif.body || ""
visible: text !== ""
font.pixelSize: Config.font_size_tiny(indicator.height)
font.family: Config.font
color: Colors.surfaceVariant_fg
elide: Text.ElideRight
maximumLineCount: 1
}
}
}
}
}
Text {
text: "No notifications"
font.pixelSize: Config.font_size_small(indicator.height)
font.family: Config.font
color: Colors.secondary
anchors.horizontalCenter: parent.horizontalCenter
visible: manager.notificationCount === 0
height: 30
}
}
}
onVisibleChanged: {
if (!visible) indicator.popupOpen = false
}
}
}