Compare commits

..
2 Commits
4 changed files with 273 additions and 70 deletions
+1 -1
View File
@@ -81,5 +81,5 @@ QtObject {
return maxWidth; return maxWidth;
} }
property string debug: "Notification" property string debug: ""
} }
+59 -34
View File
@@ -16,6 +16,14 @@ Rectangle {
// When used in history overview we don't want auto-timeout or click-to-close // When used in history overview we don't want auto-timeout or click-to-close
property bool isHistory: false property bool isHistory: false
// Helpers to handle summary/body placement
// - "Message:" title is always static (fixed bug where appName replaced it when image existed)
// - If notification only has summary and no body, show summary on the right side
property bool hasBody: notification.body !== undefined && notification.body !== null && String(notification.body).trim() !== ""
property bool hasSummary: notification.summary !== undefined && notification.summary !== null && String(notification.summary).trim() !== ""
property bool showSummaryOnLeft: hasSummary && hasBody
property bool showSummaryOnRight: hasSummary && !hasBody
property real duration: isHistory ? 0 : ( property real duration: isHistory ? 0 : (
notification.expireTimeout > 0 ? notification.expireTimeout*1000 : notification.expireTimeout > 0 ? notification.expireTimeout*1000 :
urgency ? urgency * 2000 : urgency ? urgency * 2000 :
@@ -26,7 +34,8 @@ Rectangle {
implicitWidth: screen.width/7 implicitWidth: screen.width/7
// implicitHeight: screen.height/7 // implicitHeight: screen.height/7
implicitHeight: contentRowRectangle.height+(actionRowRectangle.visible?actionRowRectangle.height:0)+(inlineReplyRectangle.visible?inlineReplyRectangle.height:0)+(progressBar.visible?progressBar.height:0) implicitHeight: contentRowRectangle.height+(actionRowRectangle.visible?actionRowRectangle.height:0)+(inlineReplyRectangle.visible?inlineReplyRectangle.height:0)+(progressBar.visible?progressBar
.height:0)
color: textBGColor color: textBGColor
border.width: Config.border_width border.width: Config.border_width
@@ -82,26 +91,26 @@ Rectangle {
anchors.right: parent.right anchors.right: parent.right
anchors.margins: Config.spacing anchors.margins: Config.spacing
width: closeIconText.implicitHeight + Config.spacing width: closeIconText.implicitHeight
height: closeIconText.implicitHeight + Config.spacing height: closeIconText.implicitHeight
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
radius: Config.radius radius: Config.radius
color: parent.containsMouse ? Colors.errorContainer : "transparent" color: parent.containsMouse ? "red" : "transparent"
border.width: Config.border_width border.width: Config.border_width
border.color: parent.containsMouse ? Colors.error : textColor border.color: parent.containsMouse ? "white" : textColor
Text { Text {
id: closeIconText id: closeIconText
anchors.centerIn: parent anchors.centerIn: parent
text: ""; // or "✕" text: ""; // or "✕"
font.family: Config.font font.family: Config.font
font.pixelSize: Config.screen_height_to_font_small(screen.height) font.pixelSize: Config.screen_height_to_font_medium(screen.height)
color: parent.parent.containsMouse ? Colors.error_fg : textColor color: parent.parent.containsMouse ? "white" : textColor
} }
} }
onClicked: function(mouse){ onClicked: function(mouse){
@@ -153,10 +162,10 @@ Rectangle {
Text { Text {
id: summaryText id: summaryText
// height: implicitHeight + font.pixelSize + Config.spacing
text: root.notification.summary text: root.notification.summary
visible: root.notification.summary ? true : false // Only show summary under icon when both summary and body exist.
// If only summary exists (no body), it will be shown on the right instead.
visible: root.showSummaryOnLeft
width: iconColumn.width width: iconColumn.width
font.family: Config.font font.family: Config.font
font.pixelSize: Config.screen_height_to_font_small(screen.height) font.pixelSize: Config.screen_height_to_font_small(screen.height)
@@ -172,37 +181,54 @@ Rectangle {
Rectangle{ Rectangle{
id: contentRectangle id: contentRectangle
width: root.width - iconColumn.width - contentRow.spacing - contentRow.anchors.margins*2 width: root.width - iconColumn.width - contentRow.spacing - contentRow.anchors.margins*2
height: iconRectangle.height height: Math.max(iconRectangle.height, contentBodyColumn.implicitHeight)
color: "transparent" color: "transparent"
Column { Column {
id: contentBodyColumn id: contentBodyColumn
width: parent.width width: parent.width
height: Math.max(childrenRect.height, iconRectangle.height)
spacing: Config.spacing spacing: Config.spacing
Text { Item {
id: bodyTitleText id: titleTextItem
text: { width: parent.width-closingMouseArea.width
if (notification.image) return notification.appName.charAt(0).toUpperCase() + notification.appName.slice(1) height: closingMouseArea.height
return "Message:" Text {
id: bodyTitleText
anchors.centerIn: parent
text: (!notification.appName || !notification.appName.split(".").pop()
? "Message"
: notification.appName.split(".").pop().charAt(0).toUpperCase()
+ notification.appName.split(".").pop().slice(1))
font.family: Config.font
font.bold: true
font.pixelSize: Config.screen_height_to_font_medium(screen.height)
color: textColor
} }
font.family: Config.font
font.bold: true
font.pixelSize: closingMouseArea.height //Config.screen_height_to_font_small(screen.height)
color: textColor
} }
Rectangle {
width: parent.width
height: Math.max(iconRectangle.height - titleTextItem.height - contentBodyColumn.spacing, 64)
color: textBGColor
radius: Config.screen_big_radius_fun(screen.height)
border.width: Config.border_width
border.color: textColor
Item{
anchors.fill: parent
anchors.margins: Config.spacing
Text {
id: bodyText
// If only summary exists (no body), show summary on the right instead of left
text: root.hasBody ? root.notification.body : (root.showSummaryOnRight ? root.notification.summary : "")
visible: root.hasBody || root.showSummaryOnRight
Text { width: parent.width - 2*parent.anchors.margins
id: bodyText font.family: Config.font
text: root.notification.body font.pixelSize: Config.screen_height_to_font_tiny(screen.height)
visible: root.notification.body ? true : false color: textColor
wrapMode: Text.Wrap
width: contentBodyColumn.width }
font.family: Config.font }
font.pixelSize: Config.screen_height_to_font_tiny(screen.height)
color: textColor
wrapMode: Text.Wrap
} }
} }
} }
@@ -394,8 +420,7 @@ Rectangle {
from: 1.0 from: 1.0
to: 0.0 to: 0.0
duration: root.duration duration: root.duration
paused: mouseArea.containsMouse || closingMouseArea.containsMouse || inlineReplyButtonMouseArea.containsMouse || root._hoveredActionCount > 0 || inlineReplyTextField.hovered || inlineReplyTextField.activeFocus paused: (mouseArea.containsMouse || closingMouseArea.containsMouse || inlineReplyButtonMouseArea.containsMouse || root._hoveredActionCount > 0 || inlineReplyTextField.hovered || inlineReplyTextField.activeFocus) && !isHistory
onFinished: root.closeNotification(false) onFinished: root.closeNotification(false)
} }
} }
+212 -34
View File
@@ -1,4 +1,4 @@
import QtQuick import QtQuick
import Quickshell import Quickshell
import QtQuick.Controls import QtQuick.Controls
import Quickshell.Wayland import Quickshell.Wayland
@@ -10,7 +10,7 @@ import "./notificationServer"
PanelWindow { PanelWindow {
id: root id: root
implicitWidth: screen.width/5 implicitWidth: screen.width/5
implicitHeight: screen.height - Config.barHeight(screen.height)*2 implicitHeight: screen.height - Config.barHeight(screen.height)
color: "transparent" color: "transparent"
visible: true visible: true
focusable: true focusable: true
@@ -101,14 +101,14 @@ PanelWindow {
// text: NotificationS.trackedNotifications.values[0] // text: NotificationS.trackedNotifications.values[0]
// } // }
Component.onCompleted: { // Component.onCompleted: {
for (var notification in NotificationS.trackedNotifications.values) { // for (var notification in NotificationS.trackedNotifications.values) {
console.log("[NotificationHistory]: " + notification.appName + " Notifications") // console.log("[NotificationHistory]: " + notification.appName + " Notifications")
} // }
for (var i =0; i < NotificationS.notificationNum; i++) { // for (var i =0; i < NotificationS.notificationNum; i++) {
console.log("[NotificationHistory]: " + NotificationS.trackedNotifications.values[i].appName + " Notifications") // console.log("[NotificationHistory]: " + NotificationS.trackedNotifications.values[i].appName + " Notifications")
} // }
} // }
Column{ Column{
id: bgColumn id: bgColumn
@@ -119,7 +119,7 @@ PanelWindow {
Rectangle { Rectangle {
id: topBar id: topBar
width: parent.width width: parent.width
height: bigFontSize*1.25 height: bigFontSize*2.5
color: textBGColor color: textBGColor
radius: Config.screen_big_radius_fun(screen.height) radius: Config.screen_big_radius_fun(screen.height)
border.width: Config.border_width border.width: Config.border_width
@@ -135,20 +135,28 @@ PanelWindow {
Text{ Text{
text: "Notifications:" text: "Notifications:"
font.family: Config.font font.family: Config.font
font.pixelSize: bigFontSize font.pixelSize: topBar.height*0.75
color: textColor color: textColor
} }
} }
// Row{
// id: backgroundCenter
// anchors.centerIn: parent
// spacing: Config.screen_small_bar_spacing_fun(screen.height)
// }
Row{ Row{
id: backgroundCenter id: backgroundRight
anchors.centerIn: parent anchors.verticalCenter: parent.verticalCenter
spacing: Config.screen_small_bar_spacing_fun(screen.height) anchors.right: parent.right
anchors.rightMargin: Config.screen_spacing_fun(screen.height)
spacing: Config.screen_spacing_fun(screen.height)
Text{ Text{
text: NotificationS.soundEnabled ? "󰂚" : "󰂛" text: NotificationS.soundEnabled ? "󰂚" : "󰂛"
font.family: Config.font font.family: Config.font
font.pixelSize: bigFontSize font.pixelSize: topBar.height*0.75
color: textColor color: textColor
} }
@@ -156,7 +164,7 @@ PanelWindow {
id: notificationVolumeSwitch id: notificationVolumeSwitch
implicitWidth: notificationVolumeSwitch.height*2 implicitWidth: notificationVolumeSwitch.height*2
implicitHeight: bigFontSize implicitHeight: topBar.height*0.75
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
checked: NotificationS.soundEnabled checked: NotificationS.soundEnabled
@@ -199,19 +207,12 @@ PanelWindow {
console.log("NotificationS.soundEnabled:", NotificationS.soundEnabled) console.log("NotificationS.soundEnabled:", NotificationS.soundEnabled)
} }
} }
}
Row{
id: backgroundRight
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: Config.screen_spacing_fun(screen.height)
spacing: Config.screen_spacing_fun(screen.height)
MouseArea { MouseArea {
id: closeArea id: closeArea
width: bigFontSize width: closeArea.height
height: bigFontSize height: topBar.height*0.75
anchors.verticalCenter: parent.verticalCenter
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
Rectangle{ Rectangle{
@@ -221,15 +222,16 @@ PanelWindow {
border.width: Config.border_width border.width: Config.border_width
border.color: closeArea.containsMouse ? "white" : textColor border.color: closeArea.containsMouse ? "white" : textColor
Text{ Text{
text: "" text: ""
anchors.centerIn: parent anchors.centerIn: parent
font.family: Config.font font.family: Config.font
font.pixelSize: bigFontSize font.pixelSize: closeArea.height - Config.spacing
color: closeArea.containsMouse ? "white" : textColor color: closeArea.containsMouse ? "white" : textColor
} }
} }
onClicked: { onClicked: {
closeNHistory() root.clearAll()
// closeNHistory()
} }
} }
} }
@@ -253,10 +255,10 @@ PanelWindow {
color: textColor color: textColor
} }
Flickable { Flickable {
id: flick id: flick
width: parent.width anchors.fill: parent
// height: Math.min(root.maxContentHeight, contentColumn.implicitHeight) anchors.margins: Config.screen_spacing_fun(screen.height)
contentWidth: width contentWidth: width
contentHeight: contentColumn.implicitHeight contentHeight: contentColumn.implicitHeight
clip: true clip: true
@@ -268,7 +270,183 @@ PanelWindow {
width: flick.width width: flick.width
spacing: Config.spacing spacing: Config.spacing
} // ── Grouped view (only groups with >1 notification are grouped) ──
// Uses root.grouped (sorted by appName). Each app group with
// more than one notification is rendered as a collapsible
// card with a header (appName + count + expand/collapse +
// clear-group). Singletons are rendered directly as a
// NotificationBody without a header.
Repeater {
id: groupedRepeater
model: root.grouped
delegate: Item {
id: delegateRoot
required property var modelData
required property int index
property var group: modelData
property bool isGrouped: group.notes.length > 1
property bool isExpanded: root.expandedGroups[group.appName] === true
width: contentColumn.width
// height adapts to which component is visible
implicitHeight: isGrouped ? groupedCard.implicitHeight : singleBody.implicitHeight
height: implicitHeight
// ── Singleton: plain NotificationBody ──
NotificationBody {
id: singleBody
visible: !delegateRoot.isGrouped
width: parent.width
screen: root.screen
notification: delegateRoot.group.notes[0]
isHistory: true
onDismissed: function(notification, dismiss) {
if (notification && notification.tracked) notification.dismiss()
else if (notification && notification.close) notification.close()
}
}
// ── Grouped card (count > 1) ──
Rectangle {
id: groupedCard
visible: delegateRoot.isGrouped
width: parent.width
implicitHeight: groupColumn.implicitHeight + Config.spacing * 2
color: Qt.alpha(Colors.surfaceContainerHigh, 0.45)
radius: Config.screen_big_radius_fun(root.screen.height)
border.width: Config.border_width
border.color: root.textColor
clip: true
Column {
id: groupColumn
anchors.fill: parent
anchors.margins: Config.spacing
spacing: Config.spacing
Rectangle {
id: groupHeader
width: parent.width
height: Math.max(appNameText.implicitHeight, countText.implicitHeight, expandIcon.implicitHeight, clearGroupIcon.implicitHeight) + Config.spacing * 1.5
color: root.bGColor
radius: Config.radius
border.width: Config.border_width
border.color: root.textColor
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: root.toggleGroup(group.appName)
}
Row {
id: headerLeft
anchors.left: parent.left
anchors.leftMargin: Config.spacing
anchors.verticalCenter: parent.verticalCenter
spacing: Config.spacing
Text {
id: expandIcon
text: delegateRoot.isExpanded ? "󰅀" : "󰅂"
font.family: Config.font
font.pixelSize: root.fontSize
color: root.textColor
anchors.verticalCenter: parent.verticalCenter
}
Text {
id: appNameText
text: group.appName
font.family: Config.font
font.pixelSize: root.fontSize
font.bold: true
color: root.textColor
elide: Text.ElideRight
maximumLineCount: 1
}
Text {
id: countText
text: "(" + group.notes.length + ")"
font.family: Config.font
font.pixelSize: Config.screen_height_to_font_tiny(root.screen.height)
color: Qt.alpha(root.textColor, 0.8)
anchors.verticalCenter: parent.verticalCenter
}
}
Row {
id: headerRight
anchors.right: parent.right
anchors.rightMargin: Config.spacing
anchors.verticalCenter: parent.verticalCenter
spacing: Config.spacing
MouseArea {
id: clearGroupArea
width: bigFontSize + Config.spacing
height: bigFontSize + Config.spacing
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: function(mouse) {
mouse.accepted = true
root.clearGroup(group.appName)
}
Rectangle{
anchors.fill: parent
radius: Config.screen_big_radius_fun(screen.height)
color: clearGroupArea.containsMouse ? "red" : textBGColor
border.width: Config.border_width
border.color: clearGroupArea.containsMouse ? "white" : textColor
Text{
id: clearGroupIcon
anchors.centerIn: parent
text: ""
font.family: Config.font
font.pixelSize: bigFontSize
color: clearGroupArea.containsMouse ? "white" : textColor
}
}
}
}
}
Column {
id: notificationsColumn
width: parent.width
spacing: Config.spacing
visible: delegateRoot.isExpanded
Repeater {
model: group.notes
delegate: NotificationBody {
required property var modelData
required property int index
width: notificationsColumn.width
screen: root.screen
notification: modelData
isHistory: true
onDismissed: function(notification, dismiss) {
if (notification && notification.tracked) notification.dismiss()
else if (notification && notification.close) notification.close()
}
}
}
}
Text {
visible: !delegateRoot.isExpanded && group.notes.length > 1
width: parent.width
text: group.notes.length + " notification" + (group.notes.length > 1 ? "s hidden" : " hidden") + " — click header to expand"
font.family: Config.font
font.pixelSize: Config.screen_height_to_font_tiny(root.screen.height)
color: Qt.alpha(root.textColor, 0.6)
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
}
}
}
}
}
}
} }
} }
} }
@@ -83,7 +83,7 @@ Item {
repeat: false repeat: false
onTriggered: { onTriggered: {
if(!notification.lastGeneration && notificationServerState.soundEnabled){ if((!notification.lastGeneration && notificationServerState.soundEnabled) || Config.debug == "Notification"){
focusedMonitor = Hyprland.focusedMonitor focusedMonitor = Hyprland.focusedMonitor
notificationReceived(notification) notificationReceived(notification)
notificationSound.play() notificationSound.play()