import Quickshell import Quickshell.Wayland import QtQuick import QtQuick.Layouts PanelWindow { anchors.top: true anchors.right: true margins.top: Theme.padding margins.right: Theme.padding exclusiveZone: 0 visible: NotificationsState.toasts.count > 0 && !root.zenMode width: 320 height: toastCol.implicitHeight color: "transparent" Column { id: toastCol width: parent.width spacing: 4 Repeater { model: NotificationsState.toasts delegate: Rectangle { required property var model required property int index width: toastCol.width height: toastInner.implicitHeight + Theme.padding * 2 + 3 radius: Theme.radius color: Theme.bg border.color: model.urgency === 2 ? Theme.red : Theme.bg2 border.width: 1 RowLayout { id: toastInner anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right anchors.topMargin: Theme.padding anchors.leftMargin: Theme.padding anchors.rightMargin: Theme.padding spacing: Theme.spacing Rectangle { width: 34 height: 34 radius: 17 color: model.urgency === 2 ? Theme.redN : model.urgency === 0 ? Theme.bg3 : Theme.blueN Layout.alignment: Qt.AlignTop Image { id: tIcon anchors.fill: parent anchors.margins: 5 // Priority: notification image (avatar/screenshot) > app icon. // appIcon can be a path or a theme name; iconPath() resolves theme names. source: { if (model.image) return model.image if (!model.appIcon) return "" if (model.appIcon.startsWith("/")) return model.appIcon return Quickshell.iconPath(model.appIcon, true) || "" } fillMode: Image.PreserveAspectFit visible: status === Image.Ready && source !== "" smooth: true } Text { anchors.centerIn: parent visible: !tIcon.visible text: model.appName.charAt(0).toUpperCase() color: Theme.fg font.pixelSize: Theme.fontSize font.family: Theme.font font.bold: true } } ColumnLayout { Layout.fillWidth: true spacing: 3 RowLayout { Layout.fillWidth: true Text { text: model.summary.length > 0 ? model.summary : model.appName color: Theme.text font.pixelSize: Theme.fontSize font.family: Theme.font font.bold: true elide: Text.ElideRight Layout.fillWidth: true } Text { visible: model.summary.length > 0 text: model.appName color: Theme.subtle font.pixelSize: Theme.fontSizeSmall font.family: Theme.font } } Text { visible: model.body.length > 0 text: model.body textFormat: Text.StyledText color: Theme.fg2 font.pixelSize: Theme.fontSizeSmall font.family: Theme.font wrapMode: Text.WordWrap maximumLineCount: 3 elide: Text.ElideRight Layout.fillWidth: true } } Rectangle { width: 22 height: 22 radius: 11 color: closeBtn.containsMouse ? Theme.bg3 : "transparent" Layout.alignment: Qt.AlignTop Behavior on color { ColorAnimation { duration: 100 } } Text { anchors.centerIn: parent text: "✕" color: closeBtn.containsMouse ? Theme.fg : Theme.subtle font.pixelSize: 11 opacity: closeBtn.containsMouse ? 1.0 : 0.5 Behavior on color { ColorAnimation { duration: 100 } } Behavior on opacity { NumberAnimation { duration: 100 } } } MouseArea { id: closeBtn anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: NotificationsState.dismissToast(model.tid) } } } Rectangle { anchors.bottom: parent.bottom anchors.left: parent.left height: 3 radius: 2 width: parent.width * model.progress color: model.urgency === 2 ? Theme.red : Theme.accent } // Does not extend to the right edge to avoid overlapping the close button MouseArea { anchors.fill: parent anchors.rightMargin: 34 cursorShape: Qt.PointingHandCursor onClicked: { NotificationsState.invokeDefault(model.tid, model.desktopEntry || model.appName) NotificationsState.dismissToast(model.tid) } } } } } }