diff --git a/Modules/Dock/Dock.qml b/Modules/Dock/Dock.qml index 0398af5..a045528 100644 --- a/Modules/Dock/Dock.qml +++ b/Modules/Dock/Dock.qml @@ -12,7 +12,7 @@ import qs.Widgets Variants { model: Quickshell.screens - Loader { + delegate: Loader { required property ShellScreen modelData readonly property real scaling: ScalingService.scale(modelData) diff --git a/Modules/Notification/Notification.qml b/Modules/Notification/Notification.qml index 2d968be..707f26f 100644 --- a/Modules/Notification/Notification.qml +++ b/Modules/Notification/Notification.qml @@ -12,12 +12,11 @@ import qs.Widgets Variants { model: Quickshell.screens - PanelWindow { + delegate: Loader { id: root required property ShellScreen modelData - readonly property real scaling: ScalingService.scale(screen) - screen: modelData + readonly property real scaling: ScalingService.scale(modelData) // Access the notification model from the service property ListModel notificationModel: NotificationService.notificationModel @@ -25,197 +24,200 @@ Variants { // Track notifications being removed for animation property var removingNotifications: ({}) - color: Color.transparent - // If no notification display activated in settings, then show them all - visible: modelData ? (Settings.data.notifications.monitors.includes(modelData.name) - || (Settings.data.notifications.monitors.length === 0)) - && (NotificationService.notificationModel.count > 0) : false + active: modelData ? (Settings.data.notifications.monitors.includes(modelData.name) + || (Settings.data.notifications.monitors.length === 0)) + && (NotificationService.notificationModel.count > 0) : false - // Position based on bar location - anchors.top: Settings.data.bar.position === "top" - anchors.bottom: Settings.data.bar.position === "bottom" - anchors.right: true - margins.top: Settings.data.bar.position === "top" ? (Style.barHeight + Style.marginM) * scaling : 0 - margins.bottom: Settings.data.bar.position === "bottom" ? (Style.barHeight + Style.marginM) * scaling : 0 - margins.right: Style.marginM * scaling - implicitWidth: 360 * scaling - implicitHeight: Math.min(notificationStack.implicitHeight, (NotificationService.maxVisible * 120) * scaling) - //WlrLayershell.layer: WlrLayer.Overlay - WlrLayershell.exclusionMode: ExclusionMode.Ignore + sourceComponent: PanelWindow { + screen: modelData + color: Color.transparent - // Connect to animation signal from service - Component.onCompleted: { - NotificationService.animateAndRemove.connect(function (notification, index) { - // Prefer lookup by identity to avoid index mismatches - var delegate = null - if (notificationStack.children && notificationStack.children.length > 0) { - for (var i = 0; i < notificationStack.children.length; i++) { - var child = notificationStack.children[i] - if (child && child.model && child.model.rawNotification === notification) { - delegate = child - break - } - } - } - - // Fallback to index if identity lookup failed - if (!delegate && notificationStack.children && notificationStack.children[index]) { - delegate = notificationStack.children[index] - } - - if (delegate && delegate.animateOut) { - delegate.animateOut() - } else { - // As a last resort, force-remove without animation to avoid stuck popups - NotificationService.forceRemoveNotification(notification) - } - }) - } - - // Main notification container - Column { - id: notificationStack // Position based on bar location - anchors.top: Settings.data.bar.position === "top" ? parent.top : undefined - anchors.bottom: Settings.data.bar.position === "bottom" ? parent.bottom : undefined - anchors.right: parent.right - spacing: Style.marginS * scaling - width: 360 * scaling - visible: true + anchors.top: Settings.data.bar.position === "top" + anchors.bottom: Settings.data.bar.position === "bottom" + anchors.right: true + margins.top: Settings.data.bar.position === "top" ? (Style.barHeight + Style.marginM) * scaling : 0 + margins.bottom: Settings.data.bar.position === "bottom" ? (Style.barHeight + Style.marginM) * scaling : 0 + margins.right: Style.marginM * scaling + implicitWidth: 360 * scaling + implicitHeight: Math.min(notificationStack.implicitHeight, (NotificationService.maxVisible * 120) * scaling) + //WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.exclusionMode: ExclusionMode.Ignore - // Multiple notifications display - Repeater { - model: notificationModel - delegate: Rectangle { - width: 360 * scaling - height: Math.max(80 * scaling, contentColumn.implicitHeight + (Style.marginM * 2 * scaling)) - clip: true - radius: Style.radiusM * scaling - border.color: Color.mPrimary - border.width: Math.max(1, Style.borderS * scaling) - color: Color.mSurface - - // Animation properties - property real scaleValue: 0.8 - property real opacityValue: 0.0 - property bool isRemoving: false - - // Scale and fade-in animation - scale: scaleValue - opacity: opacityValue - - // Animate in when the item is created - Component.onCompleted: { - scaleValue = 1.0 - opacityValue = 1.0 - } - - // Animate out when being removed - function animateOut() { - isRemoving = true - scaleValue = 0.8 - opacityValue = 0.0 - } - - // Timer for delayed removal after animation - Timer { - id: removalTimer - interval: Style.animationSlow - repeat: false - onTriggered: { - NotificationService.forceRemoveNotification(model.rawNotification) + // Connect to animation signal from service + Component.onCompleted: { + NotificationService.animateAndRemove.connect(function (notification, index) { + // Prefer lookup by identity to avoid index mismatches + var delegate = null + if (notificationStack.children && notificationStack.children.length > 0) { + for (var i = 0; i < notificationStack.children.length; i++) { + var child = notificationStack.children[i] + if (child && child.model && child.model.rawNotification === notification) { + delegate = child + break + } } } - // Check if this notification is being removed - onIsRemovingChanged: { - if (isRemoving) { - // Remove from model after animation completes - removalTimer.start() - } + // Fallback to index if identity lookup failed + if (!delegate && notificationStack.children && notificationStack.children[index]) { + delegate = notificationStack.children[index] } - // Animation behaviors - Behavior on scale { - NumberAnimation { - duration: Style.animationSlow - easing.type: Easing.OutExpo - //easing.type: Easing.OutBack looks better but notification get clipped on all sides - } + if (delegate && delegate.animateOut) { + delegate.animateOut() + } else { + // As a last resort, force-remove without animation to avoid stuck popups + NotificationService.forceRemoveNotification(notification) } + }) + } - Behavior on opacity { - NumberAnimation { - duration: Style.animationNormal - easing.type: Easing.OutQuad + // Main notification container + Column { + id: notificationStack + // Position based on bar location + anchors.top: Settings.data.bar.position === "top" ? parent.top : undefined + anchors.bottom: Settings.data.bar.position === "bottom" ? parent.bottom : undefined + anchors.right: parent.right + spacing: Style.marginS * scaling + width: 360 * scaling + visible: true + + // Multiple notifications display + Repeater { + model: notificationModel + delegate: Rectangle { + width: 360 * scaling + height: Math.max(80 * scaling, contentColumn.implicitHeight + (Style.marginM * 2 * scaling)) + clip: true + radius: Style.radiusM * scaling + border.color: Color.mPrimary + border.width: Math.max(1, Style.borderS * scaling) + color: Color.mSurface + + // Animation properties + property real scaleValue: 0.8 + property real opacityValue: 0.0 + property bool isRemoving: false + + // Scale and fade-in animation + scale: scaleValue + opacity: opacityValue + + // Animate in when the item is created + Component.onCompleted: { + scaleValue = 1.0 + opacityValue = 1.0 } - } - Column { - id: contentColumn - anchors.fill: parent - anchors.margins: Style.marginM * scaling - spacing: Style.marginS * scaling + // Animate out when being removed + function animateOut() { + isRemoving = true + scaleValue = 0.8 + opacityValue = 0.0 + } - RowLayout { + // Timer for delayed removal after animation + Timer { + id: removalTimer + interval: Style.animationSlow + repeat: false + onTriggered: { + NotificationService.forceRemoveNotification(model.rawNotification) + } + } + + // Check if this notification is being removed + onIsRemovingChanged: { + if (isRemoving) { + // Remove from model after animation completes + removalTimer.start() + } + } + + // Animation behaviors + Behavior on scale { + NumberAnimation { + duration: Style.animationSlow + easing.type: Easing.OutExpo + //easing.type: Easing.OutBack looks better but notification get clipped on all sides + } + } + + Behavior on opacity { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutQuad + } + } + + Column { + id: contentColumn + anchors.fill: parent + anchors.margins: Style.marginM * scaling spacing: Style.marginS * scaling + + RowLayout { + spacing: Style.marginS * scaling + NText { + text: (model.appName || model.desktopEntry) || "Unknown App" + color: Color.mSecondary + font.pointSize: Style.fontSizeXS * scaling + } + Rectangle { + width: 6 * scaling + height: 6 * scaling + radius: Style.radiusXS * scaling + color: (model.urgency === NotificationUrgency.Critical) ? Color.mError : (model.urgency === NotificationUrgency.Low) ? Color.mOnSurface : Color.mPrimary + Layout.alignment: Qt.AlignVCenter + } + Item { + Layout.fillWidth: true + } + NText { + text: NotificationService.formatTimestamp(model.timestamp) + color: Color.mOnSurface + font.pointSize: Style.fontSizeXS * scaling + } + } + NText { - text: (model.appName || model.desktopEntry) || "Unknown App" - color: Color.mSecondary - font.pointSize: Style.fontSizeXS * scaling - } - Rectangle { - width: 6 * scaling - height: 6 * scaling - radius: Style.radiusXS * scaling - color: (model.urgency === NotificationUrgency.Critical) ? Color.mError : (model.urgency === NotificationUrgency.Low) ? Color.mOnSurface : Color.mPrimary - Layout.alignment: Qt.AlignVCenter - } - Item { - Layout.fillWidth: true - } - NText { - text: NotificationService.formatTimestamp(model.timestamp) + text: model.summary || "No summary" + font.pointSize: Style.fontSizeL * scaling + font.weight: Style.fontWeightBold color: Color.mOnSurface - font.pointSize: Style.fontSizeXS * scaling + wrapMode: Text.Wrap + width: 300 * scaling + maximumLineCount: 3 + elide: Text.ElideRight } + + NText { + text: model.body || "" + font.pointSize: Style.fontSizeXS * scaling + color: Color.mOnSurface + wrapMode: Text.Wrap + width: 300 * scaling + maximumLineCount: 5 + elide: Text.ElideRight + } + + // Actions removed } - NText { - text: model.summary || "No summary" - font.pointSize: Style.fontSizeL * scaling - font.weight: Style.fontWeightBold - color: Color.mOnSurface - wrapMode: Text.Wrap - width: 300 * scaling - maximumLineCount: 3 - elide: Text.ElideRight - } + NIconButton { + icon: "close" + tooltipText: "Close" + sizeMultiplier: 0.8 + anchors.top: parent.top + anchors.right: parent.right + anchors.margins: Style.marginS * scaling - NText { - text: model.body || "" - font.pointSize: Style.fontSizeXS * scaling - color: Color.mOnSurface - wrapMode: Text.Wrap - width: 300 * scaling - maximumLineCount: 5 - elide: Text.ElideRight - } - - // Actions removed - } - - NIconButton { - icon: "close" - tooltipText: "Close" - sizeMultiplier: 0.8 - anchors.top: parent.top - anchors.right: parent.right - anchors.margins: Style.marginS * scaling - - onClicked: { - animateOut() + onClicked: { + animateOut() + } } } } diff --git a/Modules/Notification/NotificationHistoryPanel.qml b/Modules/Notification/NotificationHistoryPanel.qml index 74eb075..b49de08 100644 --- a/Modules/Notification/NotificationHistoryPanel.qml +++ b/Modules/Notification/NotificationHistoryPanel.qml @@ -77,19 +77,19 @@ NPanel { NIcon { text: "notifications_off" font.pointSize: Style.fontSizeXXXL * scaling - color: Color.mOnSurfaceVariant + color: Color.mOnSurface Layout.alignment: Qt.AlignHCenter } NText { text: "No notifications" font.pointSize: Style.fontSizeL * scaling - color: Color.mOnSurfaceVariant + color: Color.mOnSurface Layout.alignment: Qt.AlignHCenter } NText { - text: "Notifications will appear here when you receive them" + text: "Your notifications will show up here as they arrive." font.pointSize: Style.fontSizeNormal * scaling color: Color.mOnSurfaceVariant Layout.alignment: Qt.AlignHCenter