quickshell: fix notification toast as separate Variants popup

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
rope 2026-05-26 20:06:48 +01:00
parent 18ccb266c3
commit 5ba4e7d6cf

View file

@ -458,6 +458,10 @@ in
import Qt5Compat.GraphicalEffects import Qt5Compat.GraphicalEffects
ShellRoot { ShellRoot {
id: root
property var latestNotification: null
signal notificationReceived()
NotificationServer { NotificationServer {
id: notifServer id: notifServer
bodySupported: true bodySupported: true
@ -467,7 +471,8 @@ in
keepOnReload: true keepOnReload: true
onNotification: (notification) => { onNotification: (notification) => {
notification.tracked = true; notification.tracked = true;
notifToast.show(notification); root.latestNotification = notification;
root.notificationReceived();
} }
} }
@ -1660,157 +1665,171 @@ in
} }
} }
} }
// Notification toast popup }
PopupWindow { }
id: notifToast
property var currentNotif: null
property bool open: false
function show(notification) { // Notification toast popup
currentNotif = notification; Variants {
visible = true; model: Quickshell.screens
open = true;
_toastTimer.restart(); PopupWindow {
id: notifToast
required property var modelData
screen: modelData
property var currentNotif: null
property bool open: false
Connections {
target: root
function onNotificationReceived() {
if (notifToast.modelData === Quickshell.screens[0]) {
notifToast.show(root.latestNotification);
}
} }
}
function dismiss() { function show(notification) {
open = false; currentNotif = notification;
_toastCloseDelay.start(); visible = true;
open = true;
_toastTimer.restart();
}
function dismiss() {
open = false;
_toastCloseDelay.start();
}
anchor.rect.x: (screen ? screen.width / 2 : 0) - 160
anchor.rect.y: 30
anchor.edges: Edges.Top | Edges.Left
visible: false
implicitWidth: 320
implicitHeight: toastContent.height + 2
color: "transparent"
Timer {
id: _toastTimer
interval: 5000
onTriggered: notifToast.dismiss()
}
Timer {
id: _toastCloseDelay
interval: 230
onTriggered: { notifToast.visible = false; notifToast.open = false; }
}
HoverHandler {
onHoveredChanged: {
if (hovered) _toastTimer.stop();
else _toastTimer.restart();
} }
}
anchor.window: bar Rectangle {
anchor.rect.x: bar.width / 2 - 160 id: toastContent
anchor.rect.y: bar.height anchors.top: parent.top
anchor.edges: Edges.Top | Edges.Left anchors.topMargin: 2
anchor.gravity: Edges.Bottom | Edges.Right anchors.horizontalCenter: parent.horizontalCenter
visible: false width: 316
width: 320 height: toastCol.height + 16
height: toastContent.height + 2 radius: 8
color: "transparent" color: "#E6${c.base00}"
clip: true
Timer { transform: Translate {
id: _toastTimer y: notifToast.open ? 0 : -(toastContent.height + 10)
interval: 5000 Behavior on y {
onTriggered: notifToast.dismiss() NumberAnimation { duration: 220; easing.type: Easing.OutCubic }
}
Timer {
id: _toastCloseDelay
interval: 230
onTriggered: { notifToast.visible = false; notifToast.open = false; }
}
HoverHandler {
onHoveredChanged: {
if (hovered) _toastTimer.stop();
else _toastTimer.restart();
} }
} }
Rectangle { opacity: notifToast.open ? 1.0 : 0.0
id: toastContent Behavior on opacity {
NumberAnimation { duration: 200; easing.type: Easing.OutCubic }
}
Column {
id: toastCol
anchors.left: parent.left
anchors.right: toastDismiss.left
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 2 anchors.margins: 8
anchors.horizontalCenter: parent.horizontalCenter spacing: 2
width: 316
height: toastCol.height + 16
radius: 8
color: "#E6${c.base00}"
clip: true
transform: Translate { Text {
y: notifToast.open ? 0 : -(toastContent.height + 10) width: parent.width
Behavior on y { text: notifToast.currentNotif ? (notifToast.currentNotif.summary || notifToast.currentNotif.appName) : ""
NumberAnimation { duration: 220; easing.type: Easing.OutCubic } color: "#${c.base05}"
} font.family: "FiraMono Nerd Font"
font.pixelSize: 12
font.weight: Font.Medium
elide: Text.ElideRight
} }
opacity: notifToast.open ? 1.0 : 0.0 Text {
Behavior on opacity { width: parent.width
NumberAnimation { duration: 200; easing.type: Easing.OutCubic } text: notifToast.currentNotif ? (notifToast.currentNotif.body || "") : ""
color: "#${c.base04}"
font.family: "FiraMono Nerd Font"
font.pixelSize: 11
elide: Text.ElideRight
maximumLineCount: 3
wrapMode: Text.Wrap
visible: text !== ""
} }
Column { // Action buttons
id: toastCol Row {
anchors.left: parent.left spacing: 4
anchors.right: toastDismiss.left visible: notifToast.currentNotif && notifToast.currentNotif.actions.length > 0
anchors.top: parent.top Repeater {
anchors.margins: 8 model: notifToast.currentNotif ? notifToast.currentNotif.actions : []
spacing: 2 Rectangle {
required property var modelData
Text { width: toastActionText.width + 12
width: parent.width height: toastActionText.height + 6
text: notifToast.currentNotif ? (notifToast.currentNotif.summary || notifToast.currentNotif.appName) : "" radius: 4
color: "#${c.base05}" color: toastActionMa.containsMouse ? "#${c.base02}" : "#${c.base01}"
font.family: "FiraMono Nerd Font" border.width: 1
font.pixelSize: 12 border.color: "#${c.base02}"
font.weight: Font.Medium Text {
elide: Text.ElideRight id: toastActionText
} anchors.centerIn: parent
text: modelData.text
Text { color: "#${c.base05}"
width: parent.width font.family: "FiraMono Nerd Font"
text: notifToast.currentNotif ? (notifToast.currentNotif.body || "") : "" font.pixelSize: 10
color: "#${c.base04}" }
font.family: "FiraMono Nerd Font" MouseArea {
font.pixelSize: 11 id: toastActionMa
elide: Text.ElideRight anchors.fill: parent
maximumLineCount: 3 hoverEnabled: true
wrapMode: Text.Wrap cursorShape: Qt.PointingHandCursor
visible: text !== "" onClicked: { modelData.invoke(); notifToast.dismiss(); }
}
// Action buttons
Row {
spacing: 4
visible: notifToast.currentNotif && notifToast.currentNotif.actions.length > 0
Repeater {
model: notifToast.currentNotif ? notifToast.currentNotif.actions : []
Rectangle {
required property var modelData
width: toastActionText.width + 12
height: toastActionText.height + 6
radius: 4
color: toastActionMa.containsMouse ? "#${c.base02}" : "#${c.base01}"
border.width: 1
border.color: "#${c.base02}"
Text {
id: toastActionText
anchors.centerIn: parent
text: modelData.text
color: "#${c.base05}"
font.family: "FiraMono Nerd Font"
font.pixelSize: 10
}
MouseArea {
id: toastActionMa
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: { modelData.invoke(); notifToast.dismiss(); }
}
} }
} }
} }
} }
}
// Dismiss X // 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 ? "#${c.base05}" : "#${c.base03}"
font.family: "FiraMono Nerd Font" font.family: "FiraMono Nerd Font"
font.pixelSize: 13 font.pixelSize: 13
MouseArea { MouseArea {
id: toastDismissMa id: toastDismissMa
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { notifToast.currentNotif.dismiss(); notifToast.dismiss(); } onClicked: { notifToast.currentNotif.dismiss(); notifToast.dismiss(); }
}
} }
} }
} }