// GNOME's message tray: everything that has arrived, grouped by app. // // Drops from the top centre because that is where GNOME's tray lives and the // muscle memory is the whole point. Layer-shell centres a surface on whichever // axis it is not anchored to, so anchoring only `top` does it. import QtQuick import Quickshell import Quickshell.Wayland import Quickshell.Hyprland import Quickshell.Widgets import qs.config import qs.services import qs.modules.quicksettings import qs.widgets PanelWindow { id: root visible: ShellState.notificationsOpen color: "transparent" anchors.top: true margins.top: Theme.barHeight + Theme.barGap * 2 exclusiveZone: 0 implicitWidth: 440 implicitHeight: surface.implicitHeight WlrLayershell.namespace: "qs-popover-notifications" WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand onVisibleChanged: { if (root.visible) Notifs.markAllRead(); } HyprlandFocusGrab { windows: [root] active: root.visible onCleared: ShellState.close() } Rectangle { id: surface anchors.fill: parent implicitHeight: content.implicitHeight + Theme.popoverPadding * 2 radius: Theme.popoverRadius color: Theme.alpha(Theme.bgPopover, Theme.popoverAlpha) border.width: 1 border.color: Theme.alpha(Theme.fg, 0.08) // The prism edge — see widgets/PrismEdge.qml. Sits just inside the 1px // border so the two don't fight for the same row of pixels. PrismEdge { anchors.top: parent.top anchors.topMargin: 1 anchors.left: parent.left anchors.right: parent.right inset: parent.radius } Item { anchors.fill: parent focus: true Keys.onEscapePressed: ShellState.close() Column { id: content anchors.left: parent.left anchors.right: parent.right anchors.top: parent.top anchors.margins: Theme.popoverPadding spacing: Theme.itemSpacing // ── Header ────────────────────────────────────────────────── Item { width: parent.width height: 32 Text { anchors.left: parent.left anchors.leftMargin: 4 anchors.verticalCenter: parent.verticalCenter text: "Notifications" color: Theme.fg font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeLarge font.weight: Font.DemiBold } Row { anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter spacing: 4 IconButton { size: 30 iconSize: 16 icon: "notifications-disabled-symbolic" tint: Notifs.doNotDisturb ? Theme.accent : Theme.fgDim onClicked: Notifs.doNotDisturb = !Notifs.doNotDisturb } Rectangle { width: clearLabel.implicitWidth + 20 height: 28 radius: Theme.pillRadius border.width: 0 visible: Notifs.hasNotifications color: clearMouse.containsMouse ? Theme.alpha(Theme.fg, 0.16) : Theme.alpha(Theme.fg, 0.08) Behavior on color { ColorAnimation { duration: Theme.durFast } } Text { id: clearLabel anchors.centerIn: parent text: "Clear all" color: Theme.fg font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall font.weight: Font.Medium } MouseArea { id: clearMouse anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: Notifs.dismissAll() } } } } // ── Empty state ───────────────────────────────────────────── Item { width: parent.width height: 120 visible: !Notifs.hasNotifications Text { anchors.centerIn: parent text: Notifs.doNotDisturb ? "Do Not Disturb is on" : "No notifications" color: Theme.fgMuted font.family: Theme.fontFamily font.pixelSize: Theme.fontSize } } // ── Grouped history ───────────────────────────────────────── ScrollColumn { width: parent.width maxHeight: 620 spacing: Theme.itemSpacing visible: Notifs.hasNotifications Repeater { model: Notifs.groups Column { id: group required property var modelData width: parent.width spacing: 4 Item { width: parent.width height: 26 Text { anchors.left: parent.left anchors.leftMargin: 4 anchors.verticalCenter: parent.verticalCenter text: group.modelData.app color: Theme.fgDim font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall font.weight: Font.DemiBold } IconButton { anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter size: 24 iconSize: 13 tint: Theme.fgDim icon: "edit-clear-all-symbolic" iconFallback: "window-close-symbolic" onClicked: Notifs.dismissApp(group.modelData.app) } } Repeater { model: group.modelData.items NotificationCard { required property var modelData width: group.width compact: true notification: modelData onDismissed: Notifs.dismiss(modelData) } } } } } } } } }