Files
illogical-impulse/dots/.config/quickshell/ii/modules/waffle/notificationCenter/WSingleNotification.qml
T
2025-12-13 23:07:26 +01:00

267 lines
8.7 KiB
QML

pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Services.Notifications
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.common.functions
import qs.modules.waffle.looks
MouseArea {
id: root
required property var notification
property bool expanded: notification.actions.length > 0
property string groupExpandControlMessage: ""
signal groupExpandToggle
hoverEnabled: true
function dismiss() {
Qt.callLater(() => {
Notifications.discardNotification(root.notification?.notificationId);
});
removeAnimation.start();
}
WNotificationDismissAnim {
id: removeAnimation
target: root
}
implicitHeight: contentItem.implicitHeight
implicitWidth: contentItem.implicitWidth
Behavior on implicitHeight {
animation: Looks.transition.enter.createObject(this)
}
property real dragDismissThreshold: 100
drag {
axis: Drag.XAxis
target: contentItem
minimumX: 0
onActiveChanged: {
if (drag.active)
return;
if (contentItem.x > root.dragDismissThreshold) {
root.dismiss();
} else {
contentItem.x = 0;
}
}
}
Rectangle {
id: contentItem
width: parent.width
color: Looks.colors.bgPanelBody
radius: Looks.radius.medium
property real padding: 12
implicitHeight: notificationContent.implicitHeight + padding * 2
implicitWidth: notificationContent.implicitWidth + padding * 2
border.width: 1
border.color: ColorUtils.applyAlpha(Looks.colors.ambientShadow, 0.1)
Behavior on x {
animation: Looks.transition.enter.createObject(this)
}
ColumnLayout {
id: notificationContent
anchors.fill: parent
anchors.margins: contentItem.padding
spacing: 19
// Header
SingleNotificationHeader {
Layout.fillWidth: true
}
// Content
Item {
id: actualContent
Layout.fillWidth: true
Layout.fillHeight: true
property real spacing: 16
implicitHeight: Math.max(contentColumn.implicitHeight, imageLoader.height)
implicitWidth: contentColumn.implicitWidth
Loader {
id: imageLoader
anchors {
top: parent.top
left: parent.left
}
active: root.notification.image != ""
sourceComponent: StyledImage {
readonly property int size: 48
width: size
height: size
sourceSize.width: size
sourceSize.height: size
source: root.notification.image
fillMode: Image.PreserveAspectFit
}
}
ColumnLayout {
id: contentColumn
anchors {
top: parent.top
left: parent.left
right: parent.right
}
spacing: 3
SummaryText {
id: summaryText
Layout.leftMargin: imageLoader.active ? imageLoader.width + actualContent.spacing : 0
}
BodyText {
Layout.leftMargin: imageLoader.active ? imageLoader.width + actualContent.spacing : 0
// onLineLaidOut: (line) => {
// if (!imageLoader.active) return;
// const dodgeDistance = imageLoader.width + actualContent.spacing;
// // print(line.y, dodgeDistance)
// if (summaryText.height + line.y > dodgeDistance) {
// line.x -= dodgeDistance;
// line.width += dodgeDistance;
// }
// }
}
}
}
// Actions
ActionsRow {
Layout.fillWidth: true
}
// "+1 notifications" button
GroupExpandButton {
Layout.bottomMargin: 2
}
}
}
component SingleNotificationHeader: RowLayout {
ExpandButton {
Layout.topMargin: -2
}
Item {
Layout.fillWidth: true
}
NotificationHeaderButton {
Layout.rightMargin: 4
opacity: root.containsMouse ? 1 : 0
icon.name: "dismiss"
implicitSize: 12
onClicked: root.dismiss()
}
}
component ActionsRow: RowLayout {
visible: root.expanded && root.notification.actions.length > 0
uniformCellSizes: true
Repeater {
id: actionRepeater
model: root.notification.actions
delegate: WBorderedButton {
id: actionButton
Layout.fillHeight: true
required property var modelData
Layout.fillWidth: true
verticalPadding: 16
horizontalPadding: 12
text: modelData.text
implicitHeight: actionButtonText.implicitHeight + verticalPadding * 2
contentItem: WText {
id: actionButtonText
text: actionButton.text
font.pixelSize: Looks.font.pixelSize.large
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.Wrap
}
}
}
}
component SummaryText: WText {
Layout.fillWidth: true
elide: Text.ElideRight
text: root.notification?.summary
font.pixelSize: Looks.font.pixelSize.large
}
component BodyText: WText {
Layout.fillWidth: true
Layout.fillHeight: true
elide: Text.ElideRight
verticalAlignment: Text.AlignTop
wrapMode: Text.Wrap
maximumLineCount: root.expanded ? 100 : 1
text: {
if (root.expanded)
return `<style>img{max-width:${summaryText.width}px; align: right}</style>` + `${NotificationUtils.processNotificationBody(root.notification.body, root.notification.appName || root.notification.summary).replace(/\n/g, "<br/>")}`;
return NotificationUtils.processNotificationBody(root.notification.body, root.notification.appName || root.notification.summary).replace(/\n/g, "<br/>");
}
color: Looks.colors.subfg
textFormat: root.expanded ? Text.RichText : Text.StyledText
onLinkActivated: link => {
Qt.openUrlExternally(link);
GlobalStates.sidebarRightOpen = false;
}
}
component ExpandButton: NotificationHeaderButton {
id: expandButton
implicitWidth: expandButtonContent.implicitWidth
onClicked: root.expanded = !root.expanded
contentItem: Item {
id: expandButtonContent
implicitWidth: expandButtonRow.implicitWidth
implicitHeight: expandButtonRow.implicitHeight
RowLayout {
id: expandButtonRow
anchors.centerIn: parent
spacing: 8
WText {
color: expandButton.colForeground
text: NotificationUtils.getFriendlyNotifTimeString(root.notification?.time)
}
FluentIcon {
Layout.rightMargin: 12
icon: "chevron-down"
implicitSize: 18
rotation: root.expanded ? -180 : 0
color: expandButton.colForeground
Behavior on rotation {
animation: Looks.transition.rotate.createObject(this)
}
}
}
}
}
component GroupExpandButton: AcrylicButton {
id: groupExpandButton
visible: root.groupExpandControlMessage !== ""
horizontalPadding: 10
implicitHeight: 24
implicitWidth: expandButtonText.implicitWidth + horizontalPadding * 2
onClicked: root.groupExpandToggle()
contentItem: Item {
WText {
id: expandButtonText
anchors.centerIn: parent
text: root.groupExpandControlMessage
}
}
}
}