quickshell: move notification toast into bar surface

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
rope 2026-05-27 16:18:38 +01:00
parent cb3716a1ec
commit 98560bbbef

View file

@ -239,7 +239,6 @@ in
-- Layer rules: blur behind bar and toasts
hl.layer_rule({ match = { namespace = "quickshell-bar" }, blur = true, ignore_alpha = 0.3 })
hl.layer_rule({ match = { namespace = "quickshell-toast" }, blur = true, ignore_alpha = 0.3 })
-- Startup
hl.on("hyprland.start", function()
@ -515,7 +514,6 @@ in
singleton Theme 1.0 Theme.qml
singleton Commands 1.0 Commands.qml
Bar 1.0 Bar.qml
NotificationToast 1.0 NotificationToast.qml
'';
};
@ -590,13 +588,10 @@ in
Bar {
notifServer: _notifServer
}
}
NotificationToast {
shellRoot: root
}
}
}
'';
};
@ -619,6 +614,7 @@ in
id: bar
required property var modelData
required property NotificationServer notifServer
required property var shellRoot
screen: modelData
WlrLayershell.namespace: "quickshell-bar"
@ -640,6 +636,12 @@ in
width: activeDropdown && activeDropdown.visible ? activeDropdown.width : 0
height: activeDropdown && activeDropdown.visible ? activeDropdown.height : 0
}
Region {
x: toastItem.visible ? toastItem.x : 0
y: toastItem.visible ? toastItem.y : 0
width: toastItem.visible ? toastItem.width : 0
height: toastItem.visible ? toastItem.height : 0
}
}
Rectangle {
@ -651,23 +653,35 @@ in
color: Theme.barBg
}
// Bar bottom border left segment (up to dropdown gap)
// The "gap source" for the bar border dropdown takes priority, then toast
property bool hasGap: (activeDropdown && activeDropdown.dropdownHeight > 0)
|| (toastItem.visible && _toastRect.height > 0)
property real gapLeft: activeDropdown && activeDropdown.dropdownHeight > 0
? activeDropdown.x
: toastItem.visible && _toastRect.height > 0
? toastItem.x : 0
property real gapRight: activeDropdown && activeDropdown.dropdownHeight > 0
? activeDropdown.x + activeDropdown.width
: toastItem.visible && _toastRect.height > 0
? toastItem.x + toastItem.width : 0
property bool gapAlignRight: activeDropdown ? activeDropdown.alignRight : false
// Bar bottom border left segment (up to gap)
Rectangle {
id: barBorderLeft
x: 0; y: 30
width: (!activeDropdown || activeDropdown.dropdownHeight <= 0)
? bar.width : activeDropdown.x
width: bar.hasGap ? bar.gapLeft : bar.width
height: 1
color: Theme.base03
}
// Bar bottom border right segment (after dropdown gap)
// Bar bottom border right segment (after gap)
Rectangle {
id: barBorderRight
visible: activeDropdown && activeDropdown.dropdownHeight > 0 && !activeDropdown.alignRight
x: activeDropdown ? activeDropdown.x + activeDropdown.width : 0
visible: bar.hasGap && !bar.gapAlignRight
x: bar.gapRight
y: 30
width: activeDropdown ? bar.width - x : 0
width: bar.width - x
height: 1
color: Theme.base03
}
@ -2186,30 +2200,20 @@ in
}
}
}
}
'';
};
"quickshell/NotificationToast.qml" = {
onChange = qsRestart;
text = ''
import Quickshell
import Quickshell.Wayland
import Quickshell.Io
import QtQuick
PanelWindow {
id: notifToast
required property var shellRoot
screen: Quickshell.screens[0]
// Notification Toast (only on primary screen)
Item {
id: toastItem
visible: false
property var currentNotif: null
property bool open: false
property bool toastOpen: false
readonly property var mutedApps: ["discord", "Discord", "Spotify", "spotify", "vlc", "mpv"]
readonly property bool isPrimary: bar.screen === Quickshell.screens[0]
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.namespace: "quickshell-toast"
exclusionMode: ExclusionMode.Ignore
x: Math.round(bar.width / 2 - width / 2)
y: 30
width: _toastLeftEar.width + _toastRect.width + _toastRightEar.width
height: _toastRect.height + 4
Process {
id: notifSoundProc
@ -2217,16 +2221,18 @@ in
}
Connections {
target: shellRoot
target: bar.shellRoot
function onNotificationReceived() {
notifToast.show(shellRoot.latestNotification);
if (toastItem.isPrimary) {
toastItem.showToast(bar.shellRoot.latestNotification);
}
}
}
function show(notification) {
function showToast(notification) {
currentNotif = notification;
visible = true;
open = true;
toastOpen = true;
_toastTimer.restart();
if (!mutedApps.includes(notification.appName)) {
notifSoundProc.running = true;
@ -2234,27 +2240,20 @@ in
}
function dismiss() {
open = false;
toastOpen = false;
_toastCloseDelay.start();
}
anchors.top: true
margins.top: 30
visible: false
implicitWidth: 320 + 16
implicitHeight: _toastRect.height + 4
color: "transparent"
Timer {
id: _toastTimer
interval: 5000
onTriggered: notifToast.dismiss()
onTriggered: toastItem.dismiss()
}
Timer {
id: _toastCloseDelay
interval: 230
onTriggered: { notifToast.visible = false; notifToast.open = false; }
onTriggered: { toastItem.visible = false; toastItem.toastOpen = false; }
}
HoverHandler {
@ -2266,6 +2265,7 @@ in
// Left inverse corner ear
Item {
id: _toastLeftEar
anchors.right: _toastRect.left
anchors.top: parent.top
width: 8
@ -2294,6 +2294,7 @@ in
// Right inverse corner ear
Item {
id: _toastRightEar
anchors.left: _toastRect.right
anchors.top: parent.top
width: 8
@ -2325,7 +2326,7 @@ in
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
width: 320
height: notifToast.open ? toastCol.height + 16 : 0
height: toastItem.toastOpen ? toastCol.height + 16 : 0
color: Theme.barBg
radius: 8
topLeftRadius: 0
@ -2369,7 +2370,7 @@ in
Text {
width: parent.width
text: notifToast.currentNotif ? (notifToast.currentNotif.summary || notifToast.currentNotif.appName) : ""
text: toastItem.currentNotif ? (toastItem.currentNotif.summary || toastItem.currentNotif.appName) : ""
color: Theme.base05
font.family: "FiraMono Nerd Font"
font.pixelSize: 12
@ -2379,7 +2380,7 @@ in
Text {
width: parent.width
text: notifToast.currentNotif ? (notifToast.currentNotif.body || "") : ""
text: toastItem.currentNotif ? (toastItem.currentNotif.body || "") : ""
color: Theme.base04
font.family: "FiraMono Nerd Font"
font.pixelSize: 11
@ -2391,9 +2392,9 @@ in
Row {
spacing: 4
visible: notifToast.currentNotif && notifToast.currentNotif.actions.length > 0
visible: toastItem.currentNotif && toastItem.currentNotif.actions.length > 0
Repeater {
model: notifToast.currentNotif ? notifToast.currentNotif.actions : []
model: toastItem.currentNotif ? toastItem.currentNotif.actions : []
Rectangle {
required property var modelData
width: toastActionText.width + 12
@ -2415,7 +2416,7 @@ in
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: { modelData.invoke(); notifToast.dismiss(); }
onClicked: { modelData.invoke(); toastItem.dismiss(); }
}
}
}
@ -2436,7 +2437,8 @@ in
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: { notifToast.currentNotif.dismiss(); notifToast.dismiss(); }
onClicked: { toastItem.currentNotif.dismiss(); toastItem.dismiss(); }
}
}
}
}