256 lines
8.5 KiB
QML
256 lines
8.5 KiB
QML
pragma ComponentBehavior: Bound
|
|
|
|
import Quickshell
|
|
import Quickshell.Services.Notifications
|
|
import Quickshell.Widgets
|
|
import QtQuick
|
|
import QtQuick.Layouts
|
|
import ".."
|
|
|
|
// Notification toast overlay.
|
|
// Toasts slide in from the right, stack downward, and auto-dismiss.
|
|
// Urgency colouring: Low → fgDim stripe, Normal → accent stripe, Critical → critical border + stripe.
|
|
|
|
PanelWindow {
|
|
id: root
|
|
|
|
anchors {
|
|
top: true
|
|
right: true
|
|
}
|
|
exclusionMode: ExclusionMode.Ignore
|
|
color: "transparent"
|
|
|
|
implicitWidth: 356 // 340 toast + 8 left + 8 right margin
|
|
implicitHeight: Math.max(1, (clearAllBtn.visible ? clearAllBtn.height + 6 : 0) + notifList.contentHeight + 16)
|
|
|
|
// ── Notification server ──────────────────────────────────────────────────
|
|
NotificationServer {
|
|
id: server
|
|
|
|
onNotification: function (notif) {
|
|
notifModel.insert(0, {
|
|
"nid": notif.id,
|
|
"app": notif.appName,
|
|
"icon": notif.appIcon,
|
|
"summary": notif.summary !== "" ? notif.summary : notif.appName,
|
|
"body": notif.body,
|
|
"urgency": notif.urgency
|
|
});
|
|
}
|
|
}
|
|
|
|
ListModel {
|
|
id: notifModel
|
|
}
|
|
|
|
function removeById(nid) {
|
|
for (let i = 0; i < notifModel.count; i++) {
|
|
if (notifModel.get(i).nid === nid) {
|
|
notifModel.remove(i);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Toast list ───────────────────────────────────────────────────────────
|
|
// ── Clear all button (visible when 2+ notifications) ────────────────────
|
|
Rectangle {
|
|
id: clearAllBtn
|
|
visible: notifModel.count >= 2
|
|
anchors { top: parent.top; right: parent.right; topMargin: 8; rightMargin: 8 }
|
|
width: 340
|
|
height: 26
|
|
color: clearHover.hovered ? Colors.surface : Colors.bgDark
|
|
border.color: Colors.surface
|
|
border.width: 1
|
|
radius: 6
|
|
|
|
Text {
|
|
anchors.centerIn: parent
|
|
text: "Clear all (" + notifModel.count + ")"
|
|
color: Colors.fgDim
|
|
font.pixelSize: 12
|
|
}
|
|
|
|
HoverHandler { id: clearHover }
|
|
TapHandler { onTapped: notifModel.clear() }
|
|
}
|
|
|
|
ListView {
|
|
id: notifList
|
|
anchors {
|
|
top: clearAllBtn.visible ? clearAllBtn.bottom : parent.top
|
|
right: parent.right
|
|
topMargin: clearAllBtn.visible ? 6 : 8
|
|
rightMargin: 8
|
|
}
|
|
width: 340
|
|
height: contentHeight
|
|
model: notifModel
|
|
spacing: 6
|
|
interactive: false
|
|
|
|
add: Transition {
|
|
NumberAnimation {
|
|
property: "x"
|
|
from: 356
|
|
to: 0
|
|
duration: 220
|
|
easing.type: Easing.OutCubic
|
|
}
|
|
NumberAnimation {
|
|
property: "opacity"
|
|
from: 0
|
|
to: 1
|
|
duration: 180
|
|
}
|
|
}
|
|
|
|
remove: Transition {
|
|
NumberAnimation {
|
|
property: "opacity"
|
|
to: 0
|
|
duration: 130
|
|
easing.type: Easing.InCubic
|
|
}
|
|
NumberAnimation {
|
|
property: "x"
|
|
to: 356
|
|
duration: 180
|
|
easing.type: Easing.InCubic
|
|
}
|
|
}
|
|
|
|
displaced: Transition {
|
|
NumberAnimation {
|
|
properties: "y"
|
|
duration: 220
|
|
easing.type: Easing.OutCubic
|
|
}
|
|
NumberAnimation {
|
|
property: "opacity"
|
|
to: 1
|
|
duration: 220
|
|
}
|
|
}
|
|
|
|
delegate: Item {
|
|
id: toast
|
|
required property int index
|
|
required property int nid
|
|
required property string app
|
|
required property string icon
|
|
required property string summary
|
|
required property string body
|
|
required property int urgency
|
|
|
|
width: 340
|
|
height: toastBg.height
|
|
|
|
// ── Background card ──────────────────────────────────────────────
|
|
Rectangle {
|
|
id: toastBg
|
|
width: parent.width
|
|
height: cardContent.implicitHeight + 20
|
|
color: Colors.bgDark
|
|
border.color: toast.urgency === 2 ? Colors.critical : Colors.surface
|
|
border.width: 1
|
|
radius: 8
|
|
|
|
// Urgency stripe on left edge
|
|
Rectangle {
|
|
anchors {
|
|
left: parent.left
|
|
top: parent.top
|
|
bottom: parent.bottom
|
|
topMargin: 1
|
|
bottomMargin: 1
|
|
leftMargin: 1
|
|
}
|
|
width: 3
|
|
radius: 8
|
|
color: toast.urgency === 0 ? Colors.fgDim : toast.urgency === 2 ? Colors.critical : Colors.accent
|
|
}
|
|
|
|
ColumnLayout {
|
|
id: cardContent
|
|
anchors {
|
|
left: parent.left
|
|
right: parent.right
|
|
top: parent.top
|
|
leftMargin: 14
|
|
rightMargin: 10
|
|
topMargin: 10
|
|
}
|
|
spacing: 4
|
|
|
|
// ── Header row: icon + summary + app name + close button ─
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: 6
|
|
|
|
// App icon (shown when available)
|
|
IconImage {
|
|
visible: toast.icon !== ""
|
|
source: toast.icon
|
|
width: 16
|
|
height: 16
|
|
mipmap: true
|
|
}
|
|
|
|
Text {
|
|
text: toast.summary
|
|
color: toast.urgency === 2 ? Colors.critical : toast.urgency === 0 ? Colors.fgDim : Colors.accent
|
|
font.pixelSize: 13
|
|
font.weight: Font.Medium
|
|
Layout.fillWidth: true
|
|
elide: Text.ElideRight
|
|
}
|
|
|
|
Text {
|
|
text: toast.app
|
|
color: Colors.fgDim
|
|
font.pixelSize: 11
|
|
elide: Text.ElideRight
|
|
Layout.maximumWidth: 90
|
|
}
|
|
|
|
Rectangle {
|
|
width: 18
|
|
height: 18
|
|
radius: 4
|
|
color: closeHover.hovered ? Colors.surface : "transparent"
|
|
HoverHandler {
|
|
id: closeHover
|
|
}
|
|
TapHandler {
|
|
onTapped: root.removeById(toast.nid)
|
|
}
|
|
Text {
|
|
anchors.centerIn: parent
|
|
text: "✕"
|
|
color: Colors.fgDim
|
|
font.pixelSize: 11
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Body text ────────────────────────────────────────────
|
|
Text {
|
|
visible: toast.body !== ""
|
|
text: toast.body
|
|
color: Colors.fg
|
|
font.pixelSize: 12
|
|
Layout.fillWidth: true
|
|
wrapMode: Text.WordWrap
|
|
maximumLineCount: 3
|
|
elide: Text.ElideRight
|
|
bottomPadding: 2
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|