117 lines
3.9 KiB
QML
117 lines
3.9 KiB
QML
import QtQuick
|
|
import QtQuick.Controls
|
|
import Quickshell
|
|
import Quickshell.Wayland
|
|
import ".."
|
|
|
|
PanelWindow {
|
|
id: notificationLayer
|
|
|
|
anchors {
|
|
top: true
|
|
right: true
|
|
}
|
|
|
|
margins {
|
|
top: theme.panelMarginTop
|
|
right: theme.panelMarginRight
|
|
}
|
|
|
|
// 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"
|
|
|
|
NotificationTheme {
|
|
id: theme
|
|
}
|
|
|
|
property int stackWidth: theme.stackWidth
|
|
property int menuReservedHeight: theme.topbarReservedHeight
|
|
readonly property bool stackPreviewEnabled: Boolean(ShellStateManager.global("notifications.stackPreviewEnabled", true))
|
|
|
|
readonly property int screenHeight: screen ? screen.height : 1080
|
|
readonly property bool topbarOpen: ShellStateManager.shellOpen
|
|
readonly property int stackOffsetY: topbarOpen ? menuReservedHeight + theme.panelMarginTop : 0
|
|
readonly property int maxStackHeight: Math.max(120, screenHeight - theme.panelMarginTop - stackOffsetY - theme.panelMarginTop)
|
|
|
|
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.groupedNotifications
|
|
|
|
NotificationGroup {
|
|
theme: theme
|
|
groupData: modelData
|
|
expanded: notificationModel.groupIsExpanded(modelData.groupKey)
|
|
peekEnabled: notificationLayer.stackPreviewEnabled
|
|
|
|
width: notificationLayer.stackWidth
|
|
|
|
onExpandRequested: function (groupKey, shouldExpand) {
|
|
notificationModel.setGroupExpanded(groupKey, shouldExpand);
|
|
}
|
|
|
|
onCloseFinished: function (closedNotificationId, reason) {
|
|
notificationModel.closeById(closedNotificationId, reason);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|