114 lines
3.6 KiB
QML
114 lines
3.6 KiB
QML
import QtQuick
|
|
import QtQuick.Controls
|
|
import Quickshell
|
|
import Quickshell.Wayland
|
|
import ".."
|
|
|
|
PanelWindow {
|
|
id: notificationLayer
|
|
|
|
anchors {
|
|
top: true
|
|
right: true
|
|
}
|
|
|
|
margins {
|
|
top: 24
|
|
right: 24
|
|
}
|
|
|
|
// Top layer keeps notifications above normal windows while still usually
|
|
// below fullscreen overlays. Overlay would be too aggressive here.
|
|
WlrLayershell.layer: WlrLayer.Top
|
|
WlrLayershell.focusable: false
|
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
|
WlrLayershell.namespace: "deltarune-quickshell-notifications"
|
|
|
|
exclusionMode: ExclusionMode.Ignore
|
|
aboveWindows: true
|
|
focusable: false
|
|
visible: true
|
|
color: "#00000000"
|
|
|
|
property int stackWidth: 420
|
|
property int menuReservedHeight: 182
|
|
|
|
readonly property int screenHeight: screen ? screen.height : 1080
|
|
readonly property bool topbarOpen: ShellStateManager.shellOpen
|
|
readonly property int stackOffsetY: topbarOpen ? menuReservedHeight + 24 : 0
|
|
readonly property int maxStackHeight: Math.max(120, screenHeight - 24 - stackOffsetY - 24)
|
|
|
|
implicitWidth: stackWidth
|
|
implicitHeight: stackOffsetY + notificationViewport.height
|
|
|
|
// Input handling decision: the layer itself never grabs focus and is only
|
|
// as large as the stack. This keeps the container effectively click-through
|
|
// outside notification bounds and avoids any global pointer/keyboard grabs.
|
|
mask: Region {
|
|
item: notificationViewport
|
|
}
|
|
|
|
NotificationModel {
|
|
id: notificationModel
|
|
}
|
|
|
|
Flickable {
|
|
id: notificationViewport
|
|
x: 0
|
|
y: notificationLayer.stackOffsetY
|
|
width: notificationLayer.stackWidth
|
|
height: Math.min(notificationColumn.implicitHeight, notificationLayer.maxStackHeight)
|
|
clip: true
|
|
boundsBehavior: Flickable.StopAtBounds
|
|
contentWidth: width
|
|
contentHeight: notificationColumn.implicitHeight
|
|
interactive: contentHeight > height
|
|
flickableDirection: Flickable.VerticalFlick
|
|
|
|
WheelHandler {
|
|
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
|
|
onWheel: function (event) {
|
|
if (!notificationViewport.interactive)
|
|
return;
|
|
const step = event.angleDelta.y / 120 * 60;
|
|
notificationViewport.contentY = Math.max(0, Math.min(notificationViewport.contentHeight - notificationViewport.height, notificationViewport.contentY - step));
|
|
event.accepted = true;
|
|
}
|
|
}
|
|
|
|
ScrollBar.vertical: ScrollBar {
|
|
policy: notificationViewport.interactive ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff
|
|
width: 6
|
|
}
|
|
|
|
Column {
|
|
id: notificationColumn
|
|
width: notificationViewport.width
|
|
spacing: 0
|
|
|
|
Repeater {
|
|
model: notificationModel.notifications
|
|
|
|
NotificationCard {
|
|
notificationId: rowNotificationId
|
|
notifTitle: rowTitle
|
|
notifBody: rowBody
|
|
notifUrgency: rowUrgency
|
|
notifAppName: rowAppName
|
|
notifAppIcon: rowAppIcon
|
|
notifImage: rowImage
|
|
notifTimeoutMs: rowTimeoutMs
|
|
notifCritical: rowCritical
|
|
notifHints: rowHints
|
|
|
|
width: notificationLayer.stackWidth
|
|
|
|
onCloseFinished: function (closedNotificationId, reason) {
|
|
notificationModel.closeById(closedNotificationId, reason);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|