notif actions fix

This commit is contained in:
Zacharias-Brohn
2026-02-05 15:47:32 +01:00
parent fa9bdca241
commit 73a3b85141
13 changed files with 225 additions and 624 deletions
-248
View File
@@ -1,248 +0,0 @@
import Quickshell
import Quickshell.Widgets
import QtQuick
import QtQuick.Layouts
import qs.Config
import qs.Daemons
import qs.Helpers
Repeater {
id: groupListView
model: ScriptModel {
id: groupModel
values: groupColumn.isExpanded || groupColumn.shouldShow ? groupColumn.notifications : groupColumn.notifications.slice( 0, 1 )
}
Rectangle {
id: groupHeader
required property int index
required property NotifServer.Notif modelData
property alias notifHeight: groupHeader.height
property bool previewHidden: !groupColumn.shouldShow && index > 0
width: parent.width
height: contentColumn.height + 15
color: Config.useDynamicColors ? DynamicColors.tPalette.m3surfaceContainer : Config.baseBgColor
border.color: "#555555"
border.width: Config.useDynamicColors ? 0 : 1
radius: 8
opacity: previewHidden ? 0 : 1
scale: previewHidden ? 0.7 : 1.0
Component.onCompleted: {
modelData.lock(this);
}
Component.onDestruction: modelData.unlock(this);
MouseArea {
anchors.fill: parent
onClicked: {
if ( groupColumn.isExpanded || groupColumn.notifications.length === 1 ) {
if ( groupHeader.modelData.actions.length === 1 ) {
groupHeader.modelData.actions[0].invoke();
}
} else {
groupColumn.isExpanded = true;
}
}
}
ParallelAnimation {
id: collapseAnim
running: groupColumn.collapseAnimRunning
Anim {
target: groupHeader
property: "opacity"
duration: 100
from: 1
to: index > 0 ? 0 : 1.0
}
Anim {
target: groupHeader
property: "scale"
duration: 100
from: 1
to: index > 0 ? 0.7 : 1.0
}
onFinished: {
groupColumn.isExpanded = false;
groupColumn.shouldShow = false;
groupColumn.collapseAnimRunning = false;
}
}
ParallelAnimation {
running: groupHeader.modelData.closed
onFinished: groupHeader.modelData.unlock(groupHeader)
Anim {
target: groupHeader
property: "opacity"
to: 0
}
Anim {
target: groupHeader
property: "x"
to: groupHeader.x >= 0 ? groupHeader.width : -groupHeader.width
}
}
Behavior on opacity {
Anim {}
}
Behavior on scale {
Anim {}
}
Behavior on x {
Anim {
duration: MaterialEasing.expressiveDefaultSpatialTime
easing.bezierCurve: MaterialEasing.expressiveDefaultSpatial
}
}
Behavior on y {
Anim {
duration: MaterialEasing.expressiveDefaultSpatialTime
easing.bezierCurve: MaterialEasing.expressiveDefaultSpatial
}
}
Column {
id: contentColumn
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 10
anchors.rightMargin: 10
anchors.topMargin: 5
spacing: 10
RowLayout {
id: infoRow
width: parent.width
spacing: 10
property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3secondaryFixed : "#FFFFFF"
IconImage {
source: groupHeader.modelData.image === "" ? Qt.resolvedUrl(groupHeader.modelData.appIcon) : Qt.resolvedUrl(groupHeader.modelData.image)
Layout.preferredWidth: 48
Layout.preferredHeight: 48
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
Layout.topMargin: 5
visible: source !== ""
}
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
TextRender {
text: groupHeader.modelData.summary
color: infoRow.textColor
font.bold: true
font.pointSize: 16
elide: Text.ElideRight
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
}
TextRender {
text: groupHeader.modelData.body
color: infoRow.textColor
font.pointSize: 12
elide: Text.ElideRight
textFormat: Text.MarkdownText
wrapMode: Text.WordWrap
maximumLineCount: 5
linkColor: Config.accentColor.accents.primaryAlt
onLinkActivated: link => {
Quickshell.execDetached(["app2unit", "-O", "--", link]);
}
Layout.fillWidth: true
Layout.fillHeight: true
}
}
TextRender {
text: groupHeader.modelData.timeStr
font.pointSize: 10
color: infoRow.textColor
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
}
RowLayout {
id: actionRow
property NotifServer.Notif notif: groupHeader.modelData
spacing: 2
visible: groupHeader.modelData.actions.length > 1 ? true : false
height: 30
width: parent.width
Repeater {
model: [ ...actionRow.notif.actions ]
Rectangle {
id: actionButton
Layout.fillWidth: true
Layout.preferredHeight: 30
required property var modelData
required property int index
property color btnColor: Config.useDynamicColors ? ( actionButton.index === 0 ? DynamicColors.palette.m3primary : DynamicColors.palette.m3secondary ) : Config.accentColor.accents.primary
property color textColor: Config.useDynamicColors ? ( actionButton.index === 0 ? DynamicColors.palette.m3onPrimaryFixed : DynamicColors.palette.m3onSecondaryFixed ) : "white"
color: buttonArea.containsMouse ? DynamicColors.layer(btnColor, 0) : btnColor
radius: 4
TextRender {
anchors.centerIn: parent
text: actionButton.modelData.text
color: actionButton.textColor
font.pointSize: 12
}
MouseArea {
id: buttonArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
console.log( groupHeader.modelData.actions );
actionButton.modelData.invoke();
}
}
}
}
}
}
Rectangle {
anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: 6
anchors.topMargin: 6
width: 18
height: 18
color: closeArea.containsMouse ? "#FF6077" : "transparent"
radius: 9
TextRender {
anchors.centerIn: parent
text: "✕"
color: closeArea.containsMouse ? "white" : "#888888"
font.pointSize: 12
}
MouseArea {
id: closeArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
groupColumn.isExpanded ? groupHeader.modelData.close() : groupColumn.closeAll();
}
}
}
}
}
-52
View File
@@ -1,52 +0,0 @@
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Services.Notifications
import QtQuick
import qs.Modules
Scope {
id: root
property list<int> notifIds: []
property list<TrackedNotification> notifications;
// NotificationServer {
// id: notificationServer
// imageSupported: true
// actionsSupported: true
// persistenceSupported: true
// bodyImagesSupported: true
// bodySupported: true
// onNotification: notification => {
// notification.tracked = true;
// notification.receivedTime = Date.now();
// root.notifIds.push(notification.id);
// const notif = notificationComponent.createObject(root, { notif: notification, visible: !notificationCenter.doNotDisturb });
// root.notifications.push(notif);
// }
// }
Connections {
target: NotifServer.server
function onNotification() {
notificationComponent.createObject( root, { notif: NotifServer.list[0] });
}
}
Component {
id: notificationComponent
TrackedNotification {
centerX: notificationCenter.posX
notifIndex: root.notifIds
notifList: root.notifications
onNotifDestroy: {
root.notifications.shift();
root.notifIds.shift();
}
}
}
NotificationCenter {
id: notificationCenter
notifications: notificationServer.trackedNotifications.values
}
}
-180
View File
@@ -1,180 +0,0 @@
import Quickshell
import Quickshell.Hyprland
import Quickshell.Io
import Quickshell.Wayland
import QtQuick.Layouts
import QtQuick.Controls.FluentWinUI3
import QtQuick.Effects
import QtQuick
import qs.Config
import qs.Helpers
import qs.Daemons
import qs.Effects
Scope {
Variants {
model: Quickshell.screens
PanelWindow {
id: root
color: "transparent"
anchors {
top: true
right: true
left: true
bottom: true
}
WlrLayershell.namespace: "ZShell-Notifs"
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
property bool centerShown: false
property alias posX: backgroundRect.x
visible: false
mask: Region { item: backgroundRect }
GlobalShortcut {
appid: "zshell-nc"
name: "toggle-nc"
onPressed: {
root.centerShown = !root.centerShown;
}
}
onVisibleChanged: {
if ( root.visible ) {
showAnimation.start();
}
}
onCenterShownChanged: {
if ( !root.centerShown ) {
closeAnimation.start();
closeTimer.start();
} else if ( Hypr.getActiveScreen() === root.screen ) {
root.visible = true;
}
}
Keys.onPressed: {
if ( event.key === Qt.Key_Escape ) {
root.centerShown = false;
event.accepted = true;
}
}
Timer {
id: closeTimer
interval: 300
onTriggered: {
root.visible = false;
}
}
NumberAnimation {
id: showAnimation
target: backgroundRect
property: "x"
to: Math.round(root.screen.width - backgroundRect.implicitWidth - 10)
from: root.screen.width
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
onStopped: {
focusGrab.active = true;
}
}
NumberAnimation {
id: closeAnimation
target: backgroundRect
property: "x"
from: root.screen.width - backgroundRect.implicitWidth - 10
to: root.screen.width
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
HyprlandFocusGrab {
id: focusGrab
active: false
windows: [ root ]
onCleared: {
root.centerShown = false;
}
}
TrackedNotification {
centerShown: root.centerShown
screen: root.screen
}
ShadowRect {
anchors.fill: backgroundRect
radius: backgroundRect.radius
}
Rectangle {
id: backgroundRect
y: 10
x: Screen.width
z: 1
property color backgroundColor: Config.useDynamicColors ? DynamicColors.tPalette.m3surface : Config.baseBgColor
implicitWidth: 400
implicitHeight: root.height - 20
color: backgroundColor
radius: 8
border.color: "#555555"
border.width: Config.useDynamicColors ? 0 : 1
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 10
NotificationCenterHeader { }
Rectangle {
color: "#333333"
Layout.preferredHeight: Config.useDynamicColors ? 0 : 1
Layout.fillWidth: true
}
Flickable {
Layout.fillWidth: true
Layout.fillHeight: true
pixelAligned: true
contentHeight: notificationColumn.implicitHeight
clip: true
Column {
id: notificationColumn
width: parent.width
spacing: 10
add: Transition {
NumberAnimation {
properties: "x";
duration: 300;
easing.type: Easing.OutCubic
}
}
move: Transition {
NumberAnimation {
properties: "x";
duration: 200;
easing.type: Easing.OutCubic
}
}
GroupListView { }
}
}
}
}
}
}
}
-64
View File
@@ -1,64 +0,0 @@
import Quickshell
import Quickshell.Widgets
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls.FluentWinUI3
import qs.Daemons
import qs.Config
RowLayout {
id: root
property color textColor: Config.useDynamicColors ? DynamicColors.palette.m3onSurface : "white"
Layout.fillWidth: true
Switch {
id: dndSwitch
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillWidth: true
text: "Do Not Disturb"
focus: false
activeFocusOnTab: false
focusPolicy: Qt.NoFocus
onCheckedChanged: {
NotifServer.dnd = dndSwitch.checked;
}
}
RowLayout {
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
Text {
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
text: "Clear all"
color: root.textColor
}
Rectangle {
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Layout.preferredWidth: 30
Layout.preferredHeight: 30
color: clearArea.containsMouse ? "#15FFFFFF" : "transparent"
radius: 4
Text {
anchors.centerIn: parent
text: "\ue0b8"
font.family: "Material Symbols Rounded"
font.pointSize: 18
color: root.textColor
}
MouseArea {
id: clearArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
for ( const n of NotifServer.list.filter( n => !n.closed ))
n.close();
}
}
}
}
}
+6 -1
View File
@@ -4,6 +4,7 @@ import qs.Components
import qs.Config
import qs.Modules
import qs.Daemons
import qs.Helpers
import Quickshell
import Quickshell.Widgets
import Quickshell.Services.Notifications
@@ -20,7 +21,7 @@ CustomRect {
property bool expanded: Config.notifs.openExpanded
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3secondaryContainer : DynamicColors.tPalette.m3surfaceContainer
radius: Appearance.rounding.normal
radius: 8
implicitWidth: Config.notifs.sizes.width
implicitHeight: inner.implicitHeight
@@ -113,6 +114,7 @@ CustomRect {
width: Config.notifs.sizes.image
height: Config.notifs.sizes.image
visible: root.hasImage || root.hasAppIcon
asynchronous: true
sourceComponent: ClippingRectangle {
radius: 1000
@@ -138,6 +140,7 @@ CustomRect {
anchors.verticalCenter: root.hasImage ? undefined : image.verticalCenter
anchors.right: root.hasImage ? image.right : undefined
anchors.bottom: root.hasImage ? image.bottom : undefined
asynchronous: true
sourceComponent: CustomRect {
radius: 1000
@@ -151,6 +154,7 @@ CustomRect {
active: root.hasAppIcon
anchors.centerIn: parent
asynchronous: true
width: Math.round(parent.width * 0.6)
height: Math.round(parent.width * 0.6)
@@ -167,6 +171,7 @@ CustomRect {
anchors.centerIn: parent
anchors.horizontalCenterOffset: -18 * 0.02
anchors.verticalCenterOffset: 18 * 0.02
asynchronous: true
sourceComponent: MaterialIcon {
text: Icons.getNotifIcon(root.modelData.summary, root.modelData.urgency)
+1 -1
View File
@@ -129,7 +129,7 @@ CustomRect {
Layout.fillWidth: true
textFormat: Text.MarkdownText
text: root.modelData.body.replace(/(.)\n(?!\n)/g, "$1\n\n") || qsTr("No body here! :/")
text: root.modelData.body.replace(/(.)\n(?!\n)/g, "$1\n\n") || qsTr("No body given")
color: root.modelData.urgency === "critical" ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3onSurface
wrapMode: Text.WordWrap
@@ -17,71 +17,6 @@ Item {
Layout.fillWidth: true
implicitHeight: flickable.contentHeight
layer.enabled: true
layer.smooth: true
layer.effect: OpacityMask {
maskSource: gradientMask
}
Item {
id: gradientMask
anchors.fill: parent
layer.enabled: true
visible: false
Rectangle {
anchors.fill: parent
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop {
position: 0
color: Qt.rgba(0, 0, 0, 0)
}
GradientStop {
position: 0.1
color: Qt.rgba(0, 0, 0, 1)
}
GradientStop {
position: 0.9
color: Qt.rgba(0, 0, 0, 1)
}
GradientStop {
position: 1
color: Qt.rgba(0, 0, 0, 0)
}
}
}
Rectangle {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
implicitWidth: parent.width / 2
opacity: flickable.contentX > 0 ? 0 : 1
Behavior on opacity {
Anim {}
}
}
Rectangle {
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
implicitWidth: parent.width / 2
opacity: flickable.contentX < flickable.contentWidth - parent.width ? 0 : 1
Behavior on opacity {
Anim {}
}
}
}
CustomFlickable {
id: flickable
@@ -113,8 +48,8 @@ Item {
Layout.fillWidth: true
Layout.fillHeight: true
implicitWidth: actionInner.implicitWidth + 10 * 2
implicitHeight: actionInner.implicitHeight + 10 * 2
implicitWidth: actionInner.implicitWidth + 5 * 2
implicitHeight: actionInner.implicitHeight + 5 * 2
Layout.preferredWidth: implicitWidth + (actionStateLayer.pressed ? 18 : 0)
radius: actionStateLayer.pressed ? 6 / 2 : 6
@@ -123,7 +58,7 @@ Item {
Timer {
id: copyTimer
interval: 3000
interval: 1000
onTriggered: actionInner.item.text = "content_copy"
}
@@ -91,13 +91,6 @@ Item {
sourceComponent: ColumnLayout {
spacing: 20
Image {
asynchronous: true
source: Qt.resolvedUrl(`${Quickshell.shellDir}/assets/dino.png`)
fillMode: Image.PreserveAspectFit
sourceSize.width: clipRect.width * 0.8
}
CustomText {
Layout.alignment: Qt.AlignHCenter
text: qsTr("No Notifications")
@@ -35,7 +35,6 @@ Item {
map.set(n.appName, null);
for (const n of NotifServer.list)
map.set(n.appName, null);
console.log(map.keys())
return [...map.keys()];
}
onValuesChanged: root.flagChanged()
@@ -52,8 +51,9 @@ Item {
property int startY
function closeAll(): void {
for (const n of NotifServer.notClosed.filter(n => n.appName === modelData))
for (const n of NotifServer.notClosed.filter(n => n.appName === modelData)) {
n.close();
}
}
y: {