diff --git a/settings/hyprland.nix b/settings/hyprland.nix index 897a40e..a832e26 100644 --- a/settings/hyprland.nix +++ b/settings/hyprland.nix @@ -467,6 +467,7 @@ in keepOnReload: true onNotification: (notification) => { notification.tracked = true; + notifToast.show(notification); } } @@ -1659,6 +1660,160 @@ in } } } + // Notification toast popup + PopupWindow { + id: notifToast + property var currentNotif: null + property bool open: false + + function show(notification) { + currentNotif = notification; + visible = true; + open = true; + _toastTimer.restart(); + } + + function dismiss() { + open = false; + _toastCloseDelay.start(); + } + + anchor.window: bar + anchor.rect.x: bar.width / 2 - 160 + anchor.rect.y: bar.height + anchor.edges: Edges.Top | Edges.Left + anchor.gravity: Edges.Bottom | Edges.Right + visible: false + width: 320 + height: 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(); + } + } + + Rectangle { + id: toastContent + anchors.top: parent.top + anchors.topMargin: 2 + anchors.horizontalCenter: parent.horizontalCenter + width: 316 + height: toastCol.height + 16 + radius: 8 + color: "#E6${c.base00}" + clip: true + + transform: Translate { + y: notifToast.open ? 0 : -(toastContent.height + 10) + Behavior on y { + NumberAnimation { duration: 220; easing.type: Easing.OutCubic } + } + } + + opacity: notifToast.open ? 1.0 : 0.0 + 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.margins: 8 + spacing: 2 + + Text { + width: parent.width + text: notifToast.currentNotif ? (notifToast.currentNotif.summary || notifToast.currentNotif.appName) : "" + color: "#${c.base05}" + font.family: "FiraMono Nerd Font" + font.pixelSize: 12 + font.weight: Font.Medium + elide: Text.ElideRight + } + + Text { + width: parent.width + 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 !== "" + } + + // 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 + Text { + id: toastDismiss + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: 8 + text: "\u{f0156}" + color: toastDismissMa.containsMouse ? "#${c.base05}" : "#${c.base03}" + font.family: "FiraMono Nerd Font" + font.pixelSize: 13 + MouseArea { + id: toastDismissMa + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { notifToast.currentNotif.dismiss(); notifToast.dismiss(); } + } + } + } + } } } '';