// One app's worth of notifications in the date menu, GNOME-style: a header // carrying the app's identity and a clear button, over a collapsible stack of // cards. // // The header is the disclosure control in its entirety — a 4px chevron is a // poor click target, and GNOME lets you hit the whole row. import QtQuick import Quickshell import Quickshell.Widgets import qs.config import qs.services import qs.modules.notifications import qs.modules.quicksettings import qs.widgets Column { id: root // One entry from Notifs.groups: { app, icon, desktopEntry, items }. required property var group property bool expanded: true signal toggled signal cleared spacing: 4 // The app icon the notifications themselves declared, falling back to the // desktop entry — same resolution order NotificationCard uses. readonly property string iconSource: { if (root.group.icon) return Quickshell.iconPath(root.group.icon, true); const entry = DesktopEntries.byId(root.group.desktopEntry); return entry ? Quickshell.iconPath(entry.icon, true) : ""; } Rectangle { id: header width: parent.width height: 28 radius: Theme.cardRadius - 4 border.width: 0 color: headerMouse.containsMouse ? Theme.alpha(Theme.fg, 0.07) : "transparent" Behavior on color { ColorAnimation { duration: headerMouse.containsMouse ? Theme.durFast : Theme.durNormal } } MouseArea { id: headerMouse anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: root.toggled() } Row { anchors.left: parent.left anchors.leftMargin: 6 anchors.right: clearButton.left anchors.rightMargin: 6 anchors.verticalCenter: parent.verticalCenter spacing: 7 IconImage { anchors.verticalCenter: parent.verticalCenter implicitSize: 15 asynchronous: true source: root.iconSource visible: source !== "" } Text { anchors.verticalCenter: parent.verticalCenter text: root.group.app color: Theme.fgDim elide: Text.ElideRight font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall font.weight: Font.DemiBold } // A bare count next to a collapsed group is the only way to know // how much is hidden, so it is always shown once there is more // than one. Rectangle { anchors.verticalCenter: parent.verticalCenter width: countLabel.implicitWidth + 12 height: 16 radius: Theme.pillRadius border.width: 0 visible: root.group.items.length > 1 color: Theme.alpha(Theme.fg, 0.12) Text { id: countLabel anchors.centerIn: parent text: root.group.items.length color: Theme.fgDim font.family: Theme.fontFamily font.features: Theme.tabularFigures font.pixelSize: Theme.fontSizeSmall } } } IconButton { id: clearButton anchors.right: chevron.left anchors.rightMargin: 2 anchors.verticalCenter: parent.verticalCenter size: 22 iconSize: 12 tint: Theme.fgMuted icon: "edit-clear-all-symbolic" iconFallback: "window-close-symbolic" onClicked: root.cleared() } ThemedIcon { id: chevron anchors.right: parent.right anchors.rightMargin: 6 anchors.verticalCenter: parent.verticalCenter size: 12 icon: "pan-down-symbolic" iconFallback: "go-down-symbolic" tint: Theme.fgMuted // Pointing down when open, right when closed — the standard // disclosure direction, animated only on the click that caused it. rotation: root.expanded ? 0 : -90 Behavior on rotation { NumberAnimation { duration: Theme.durNormal easing.type: Easing.OutCubic } } } } // Section animates its own height and hides itself at zero, which also // collapses the spacing the parent Column would otherwise leave behind. Section { width: parent.width expanded: root.expanded Column { anchors.left: parent.left anchors.right: parent.right spacing: 4 Repeater { model: root.group.items NotificationCard { required property var modelData width: parent.width compact: true notification: modelData onDismissed: Notifs.dismiss(modelData) } } } } }