pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts import Quickshell import Quickshell.Widgets import ZShell.Config import qs.Components import qs.Effects import qs.Services import qs.Daemons Item { id: root required property NotifServer.Notif notif Layout.fillWidth: true implicitHeight: flickable.contentHeight layer.enabled: true layer.smooth: true layer.effect: Mask { maskSource: gradientMask } Item { id: gradientMask anchors.fill: parent layer.enabled: true visible: false Rectangle { anchors.fill: parent gradient: Gradient { orientation: Gradient.Horizontal GradientStop { color: Qt.rgba(0, 0, 0, 0) position: 0 } GradientStop { color: Qt.rgba(0, 0, 0, 1) position: 0.1 } GradientStop { color: Qt.rgba(0, 0, 0, 1) position: 0.9 } GradientStop { color: Qt.rgba(0, 0, 0, 0) position: 1 } } } Rectangle { anchors.bottom: parent.bottom anchors.left: parent.left anchors.top: parent.top implicitWidth: parent.width / 2 opacity: flickable.contentX > 0 ? 0 : 1 Behavior on opacity { Anim { type: Anim.DefaultEffects } } } Rectangle { anchors.bottom: parent.bottom anchors.right: parent.right anchors.top: parent.top implicitWidth: parent.width / 2 opacity: flickable.contentX < flickable.contentWidth - parent.width ? 0 : 1 Behavior on opacity { Anim { type: Anim.DefaultEffects } } } } CustomFlickable { id: flickable anchors.fill: parent contentHeight: actionList.implicitHeight contentWidth: Math.max(width, actionList.implicitWidth) RowLayout { id: actionList anchors.fill: parent spacing: Tokens.spacing.extraSmall Repeater { model: [ { isClose: true }, ...(root.notif?.actions ?? []), { isCopy: true } ] CustomRect { id: action required property var modelData Layout.fillHeight: true Layout.fillWidth: true Layout.preferredWidth: implicitWidth + (actionStateLayer.pressed ? Tokens.padding.large : 0) color: Colors.layer(Colors.palette.m3surfaceContainerHighest, 4) implicitHeight: actionInner.implicitHeight + Tokens.padding.small implicitWidth: actionInner.implicitWidth + Tokens.padding.medium * 2 radius: actionStateLayer.pressed ? Tokens.rounding.medium / 2 : Tokens.rounding.large Behavior on Layout.preferredWidth { Anim { type: Anim.FastSpatial } } Behavior on radius { Anim { type: Anim.FastSpatial } } Timer { id: copyTimer interval: 3000 onTriggered: actionInner.item.text = "content_copy" } StateLayer { id: actionStateLayer onClicked: { if (action.modelData.isClose) { root.notif.close(); } else if (action.modelData.isCopy) { Quickshell.clipboardText = root.notif.body; actionInner.item.text = "inventory"; copyTimer.start(); } else if (action.modelData.invoke) { action.modelData.invoke(); } else if (!root.notif.resident) { root.notif.close(); } } } Loader { id: actionInner anchors.centerIn: parent sourceComponent: action.modelData.isClose || action.modelData.isCopy ? iconBtn : root.notif?.hasActionIcons ? iconComp : textComp } Component { id: iconBtn MaterialIcon { animate: action.modelData.isCopy ?? false color: Colors.palette.m3onSurfaceVariant text: action.modelData.isCopy ? "content_copy" : "close" } } Component { id: iconComp IconImage { asynchronous: true source: Quickshell.iconPath(action.modelData.identifier) } } Component { id: textComp CustomText { color: Colors.palette.m3onSurfaceVariant text: action.modelData.text } } } } } } }