diff --git a/Modules/Notifications/NotificationPopup.qml b/Modules/Notifications/NotificationPopup.qml
index 8491058a..06e72a38 100644
--- a/Modules/Notifications/NotificationPopup.qml
+++ b/Modules/Notifications/NotificationPopup.qml
@@ -8,32 +8,36 @@ import qs.Services
import qs.Widgets
PanelWindow {
- id: root
+ id: win
- required property var notificationData // Individual notification wrapper
+ required property var notificationData
required property string notificationId
readonly property bool isPopup: notificationData.popup
readonly property int expireTimeout: notificationData.notification.expireTimeout
- property int verticalOffset: notificationData && notificationData.initialOffset || 0
- property bool initialAnimation: true
- property bool fadingOut: false
- property bool slideOut: false
- property bool entering: true
+
+ property int screenY: 0
+ onScreenYChanged: margins.top = Theme.barHeight + 16 + screenY
+ Behavior on screenY {
+ enabled: !exiting
+ NumberAnimation {
+ duration: 220
+ easing.type: Easing.OutCubic
+ }
+ }
+
+ property int rowHeight: 132
+ property bool exiting: false
signal entered()
signal exitFinished()
- visible: isPopup || fadingOut || slideOut
+ visible: true
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
color: "transparent"
implicitWidth: 400
- implicitHeight: 116 // Individual notifications have fixed height
- Component.onCompleted: {
- initialAnimation = false; // kicks the right→left slide-in
- enterDelay.start(); // start TTL after entrance
- }
+ implicitHeight: 116
anchors {
top: true
@@ -41,372 +45,330 @@ PanelWindow {
}
margins {
- top: Theme.barHeight + 16 + verticalOffset
+ top: Theme.barHeight + 16
right: 12
}
- Timer {
- id: enterDelay
-
- interval: Anims.durMed // must match the entrance duration
- repeat: false
- onTriggered: notificationData.timer.start()
- }
-
- Timer {
- id: forceHideTimer
-
- interval: 500 // Force hide after 500ms if stuck
- repeat: false
- onTriggered: {
- console.warn("NotificationPopup: Forcing exit for stuck notification");
- exitFinished();
- }
- }
-
- Connections {
- function onPopupChanged() {
- if (!notificationData.popup) {
- if (notificationData.removedByLimit)
- slideOut = true;
- else
- fadingOut = true;
- // Start force hide timer as safety net
- forceHideTimer.start();
- // When a notification is no longer a popup, we want to remove it from the visible list
- // so that other notifications can move into its place.
- NotificationService.removeFromVisibleNotifications(notificationData);
- }
- }
-
- target: notificationData
- }
-
- Rectangle {
- property var shadowLayers: [shadowLayer1, shadowLayer2, shadowLayer3]
-
+ Item {
+ id: content
anchors.fill: parent
- anchors.margins: 4
- radius: Theme.cornerRadiusLarge
- color: Theme.popupBackground()
- border.color: notificationData.urgency === 2 ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
- border.width: notificationData.urgency === 2 ? 2 : 1
- clip: true
- opacity: (fadingOut || slideOut) ? 0 : 1
- scale: slideOut ? 0.98 : 1
- // Shadow layers - marked for resource cleanup
- Rectangle {
- id: shadowLayer1
-
- anchors.fill: parent
- anchors.margins: -3
- color: "transparent"
- radius: parent.radius + 3
- border.color: Qt.rgba(0, 0, 0, 0.05)
- border.width: 1
- z: -3
- }
-
- Rectangle {
- id: shadowLayer2
-
- anchors.fill: parent
- anchors.margins: -2
- color: "transparent"
- radius: parent.radius + 2
- border.color: Qt.rgba(0, 0, 0, 0.08)
- border.width: 1
- z: -2
- }
-
- Rectangle {
- id: shadowLayer3
-
- anchors.fill: parent
- color: "transparent"
- border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
- border.width: 1
- radius: parent.radius
- z: -1
- }
-
- // Critical notification accent
- Rectangle {
- anchors.fill: parent
- radius: parent.radius
- visible: notificationData.urgency === 2
- opacity: 1
-
- gradient: Gradient {
- orientation: Gradient.Horizontal
-
- GradientStop {
- position: 0
- color: Theme.primary
- }
-
- GradientStop {
- position: 0.02
- color: Theme.primary
- }
-
- GradientStop {
- position: 0.021
- color: "transparent"
- }
-
- }
-
- }
-
- Item {
- id: notificationContent
-
- anchors.top: parent.top
- anchors.left: parent.left
- anchors.right: parent.right
- anchors.topMargin: 12
- anchors.leftMargin: 16
- anchors.rightMargin: 16
- height: 86
-
- Rectangle {
- id: iconContainer
-
- readonly property bool hasNotificationImage: notificationData.image && notificationData.image !== ""
- readonly property bool appIconIsImage: notificationData.appIcon && (notificationData.appIcon.startsWith("file://") || notificationData.appIcon.startsWith("http://") || notificationData.appIcon.startsWith("https://"))
- property alias iconImage: iconImage
-
- width: 55
- height: 55
- radius: 27.5
- color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1)
- border.color: "transparent"
- border.width: 0
- anchors.left: parent.left
- anchors.verticalCenter: parent.verticalCenter
-
- IconImage {
- id: iconImage
-
- anchors.fill: parent
- anchors.margins: 2
- asynchronous: true
- source: {
- if (parent.hasNotificationImage)
- return notificationData.cleanImage;
-
- if (notificationData.appIcon) {
- const appIcon = notificationData.appIcon;
- if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://"))
- return appIcon;
-
- return Quickshell.iconPath(appIcon, "");
- }
- return "";
+ transform: Translate {
+ id: tx
+ x: 400
+ Behavior on x {
+ NumberAnimation {
+ id: xAnim
+ duration: 240
+ easing.type: Easing.OutCubic
+ onRunningChanged: {
+ if (!running && win && !win.exiting && Math.abs(tx.x) < 0.5) win.entered();
+ if (!running && win && win.exiting && Math.abs(tx.x - 96) < 0.5) maybeFinishExit();
}
- visible: status === Image.Ready
}
-
- Text {
- anchors.centerIn: parent
- visible: !parent.hasNotificationImage && (!notificationData.appIcon || notificationData.appIcon === "")
- text: {
- const appName = notificationData.appName || "?";
- return appName.charAt(0).toUpperCase();
- }
- font.pixelSize: 20
- font.weight: Font.Bold
- color: Theme.primaryText
- }
-
- }
-
- Rectangle {
- id: textContainer
-
- anchors.left: iconContainer.right
- anchors.leftMargin: 12
- anchors.right: closeButton.left
- anchors.rightMargin: 8
- anchors.top: parent.top
- anchors.bottom: parent.bottom
- anchors.bottomMargin: 8
- color: "transparent"
-
- Column {
- width: parent.width
- spacing: 2
- anchors.verticalCenter: parent.verticalCenter
-
- Text {
- width: parent.width
- text: {
- if (notificationData.timeStr.length > 0)
- return notificationData.appName + " • " + notificationData.timeStr;
- else
- return notificationData.appName;
- }
- color: Theme.surfaceVariantText
- font.pixelSize: Theme.fontSizeSmall
- font.weight: Font.Medium
- elide: Text.ElideRight
- maximumLineCount: 1
- }
-
- Text {
- text: notificationData.summary
- color: Theme.surfaceText
- font.pixelSize: Theme.fontSizeMedium
- font.weight: Font.Medium
- width: parent.width
- elide: Text.ElideRight
- maximumLineCount: 1
- visible: text.length > 0
- }
-
- Text {
- property bool hasUrls: {
- const urlRegex = /(https?:\/\/[^\s]+)/g;
- return urlRegex.test(notificationData.body);
- }
-
- text: {
- let bodyText = notificationData.body;
- if (bodyText.length > 105)
- bodyText = bodyText.substring(0, 102) + "...";
-
- const urlRegex = /(https?:\/\/[^\s]+)/g;
- return bodyText.replace(urlRegex, '$1');
- }
- color: Theme.surfaceVariantText
- font.pixelSize: Theme.fontSizeSmall
- width: parent.width
- elide: Text.ElideRight
- maximumLineCount: 2
- wrapMode: Text.WordWrap
- visible: text.length > 0
- textFormat: Text.RichText
- onLinkActivated: function(link) {
- Qt.openUrlExternally(link);
- }
- }
-
- }
-
- }
-
- DankActionButton {
- id: closeButton
-
- anchors.right: parent.right
- anchors.top: parent.top
- iconName: "close"
- iconSize: 14
- buttonSize: 20
- z: 15
- onClicked: {
- notificationData.popup = false;
- }
- }
-
- }
-
- // Main hover area for persistence and click handling
- MouseArea {
- id: cardHoverArea
-
- anchors.fill: parent
- hoverEnabled: true
- acceptedButtons: Qt.LeftButton
- propagateComposedEvents: true
- z: 0
- onEntered: {
- notificationData.timer.stop();
- }
- onExited: {
- if (notificationData.popup)
- notificationData.timer.restart();
-
- }
- onClicked: {
- notificationData.popup = false;
}
}
+ opacity: win.exiting ? 0 : 1
Behavior on opacity {
NumberAnimation {
- duration: 180
- easing.type: Easing.BezierSpline
- easing.bezierCurve: Anims.emphasized
- onRunningChanged: {
- if (!running && opacity === 0) {
- forceHideTimer.stop(); // Cancel force hide since animation completed normally
- exitFinished();
- }
- }
+ id: fadeAnim
+ duration: 200
+ easing.type: Easing.OutCubic
+ onRunningChanged: if (!running && win && win.exiting && content && content.opacity === 0) maybeFinishExit()
}
-
}
+ scale: win.exiting ? 0.98 : 1.0
Behavior on scale {
NumberAnimation {
duration: 160
easing.type: Easing.OutCubic
}
-
}
- transform: Translate {
- x: {
- if (initialAnimation)
- return 400;
- // start off-screen right
- if (slideOut)
- return 64;
- // gentle nudge on exit (was 400)
- return 0;
+ layer.enabled: (Math.abs(tx.x) > 0.5) || win.exiting
+ layer.smooth: true
+
+ Rectangle {
+ property var shadowLayers: [shadowLayer1, shadowLayer2, shadowLayer3]
+
+ anchors.fill: parent
+ anchors.margins: 4
+ radius: Theme.cornerRadiusLarge
+ color: Theme.popupBackground()
+ border.color: notificationData.urgency === 2 ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3) : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
+ border.width: notificationData.urgency === 2 ? 2 : 1
+ clip: true
+
+ Rectangle {
+ id: shadowLayer1
+ anchors.fill: parent
+ anchors.margins: -3
+ color: "transparent"
+ radius: parent.radius + 3
+ border.color: Qt.rgba(0, 0, 0, 0.05)
+ border.width: 1
+ z: -3
}
- Behavior on x {
- enabled: initialAnimation || slideOut
+ Rectangle {
+ id: shadowLayer2
+ anchors.fill: parent
+ anchors.margins: -2
+ color: "transparent"
+ radius: parent.radius + 2
+ border.color: Qt.rgba(0, 0, 0, 0.08)
+ border.width: 1
+ z: -2
+ }
- NumberAnimation {
- id: xAnim
+ Rectangle {
+ id: shadowLayer3
+ anchors.fill: parent
+ color: "transparent"
+ border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
+ border.width: 1
+ radius: parent.radius
+ z: -1
+ }
- duration: Anims.durMed
- easing.type: Easing.BezierSpline
- easing.bezierCurve: slideOut ? Anims.emphasized : Anims.emphasizedDecel
- onRunningChanged: {
- if (!running) {
- if (!slideOut) {
- // entrance finished
- entering = false;
- entered();
- } else {
- // exit finished
- forceHideTimer.stop();
- // Cancel force hide since slide completed normally
- exitFinished();
+ Rectangle {
+ anchors.fill: parent
+ radius: parent.radius
+ visible: notificationData.urgency === 2
+ opacity: 1
+
+ gradient: Gradient {
+ orientation: Gradient.Horizontal
+
+ GradientStop {
+ position: 0
+ color: Theme.primary
+ }
+
+ GradientStop {
+ position: 0.02
+ color: Theme.primary
+ }
+
+ GradientStop {
+ position: 0.021
+ color: "transparent"
+ }
+ }
+ }
+
+ Item {
+ id: notificationContent
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.topMargin: 12
+ anchors.leftMargin: 16
+ anchors.rightMargin: 16
+ height: 86
+
+ Rectangle {
+ id: iconContainer
+ readonly property bool hasNotificationImage: notificationData.image && notificationData.image !== ""
+ readonly property bool appIconIsImage: notificationData.appIcon && (notificationData.appIcon.startsWith("file://") || notificationData.appIcon.startsWith("http://") || notificationData.appIcon.startsWith("https://"))
+ property alias iconImage: iconImage
+
+ width: 55
+ height: 55
+ radius: 27.5
+ color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1)
+ border.color: "transparent"
+ border.width: 0
+ anchors.left: parent.left
+ anchors.verticalCenter: parent.verticalCenter
+
+ IconImage {
+ id: iconImage
+ anchors.fill: parent
+ anchors.margins: 2
+ asynchronous: true
+ source: {
+ if (parent.hasNotificationImage)
+ return notificationData.cleanImage;
+
+ if (notificationData.appIcon) {
+ const appIcon = notificationData.appIcon;
+ if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://"))
+ return appIcon;
+
+ return Quickshell.iconPath(appIcon, "");
+ }
+ return "";
+ }
+ visible: status === Image.Ready
+ }
+
+ Text {
+ anchors.centerIn: parent
+ visible: !parent.hasNotificationImage && (!notificationData.appIcon || notificationData.appIcon === "")
+ text: {
+ const appName = notificationData.appName || "?";
+ return appName.charAt(0).toUpperCase();
+ }
+ font.pixelSize: 20
+ font.weight: Font.Bold
+ color: Theme.primaryText
+ }
+ }
+
+ Rectangle {
+ id: textContainer
+ anchors.left: iconContainer.right
+ anchors.leftMargin: 12
+ anchors.right: closeButton.left
+ anchors.rightMargin: 8
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 8
+ color: "transparent"
+
+ Column {
+ width: parent.width
+ spacing: 2
+ anchors.verticalCenter: parent.verticalCenter
+
+ Text {
+ width: parent.width
+ text: {
+ if (notificationData.timeStr.length > 0)
+ return notificationData.appName + " • " + notificationData.timeStr;
+ else
+ return notificationData.appName;
+ }
+ color: Theme.surfaceVariantText
+ font.pixelSize: Theme.fontSizeSmall
+ font.weight: Font.Medium
+ elide: Text.ElideRight
+ maximumLineCount: 1
+ }
+
+ Text {
+ text: notificationData.summary
+ color: Theme.surfaceText
+ font.pixelSize: Theme.fontSizeMedium
+ font.weight: Font.Medium
+ width: parent.width
+ elide: Text.ElideRight
+ maximumLineCount: 1
+ visible: text.length > 0
+ }
+
+ Text {
+ property bool hasUrls: {
+ const urlRegex = /(https?:\/\/[^\s]+)/g;
+ return urlRegex.test(notificationData.body);
+ }
+
+ text: {
+ let bodyText = notificationData.body;
+ if (bodyText.length > 105)
+ bodyText = bodyText.substring(0, 102) + "...";
+
+ const urlRegex = /(https?:\/\/[^\s]+)/g;
+ return bodyText.replace(urlRegex, '$1');
+ }
+ color: Theme.surfaceVariantText
+ font.pixelSize: Theme.fontSizeSmall
+ width: parent.width
+ elide: Text.ElideRight
+ maximumLineCount: 2
+ wrapMode: Text.WordWrap
+ visible: text.length > 0
+ textFormat: Text.RichText
+ onLinkActivated: function(link) {
+ Qt.openUrlExternally(link);
}
}
}
}
+ DankActionButton {
+ id: closeButton
+ anchors.right: parent.right
+ anchors.top: parent.top
+ iconName: "close"
+ iconSize: 14
+ buttonSize: 20
+ z: 15
+ onClicked: {
+ notificationData.popup = false;
+ }
+ }
}
+ MouseArea {
+ id: cardHoverArea
+ anchors.fill: parent
+ hoverEnabled: true
+ acceptedButtons: Qt.LeftButton
+ propagateComposedEvents: true
+ z: 0
+ onEntered: {
+ notificationData.timer.stop();
+ }
+ onExited: {
+ if (notificationData.popup)
+ notificationData.timer.restart();
+ }
+ onClicked: {
+ notificationData.popup = false;
+ }
+ }
}
-
}
- Behavior on verticalOffset {
- NumberAnimation {
- duration: Anims.durMed
- easing.type: Easing.BezierSpline
- easing.bezierCurve: Anims.emphasized
- }
-
+ Component.onCompleted: {
+ enterDelay.start();
+ Qt.callLater(() => { tx.x = 0; });
}
-}
+ Timer {
+ id: enterDelay
+ interval: Anims.durMed
+ repeat: false
+ onTriggered: notificationData.timer.start()
+ }
+
+ Connections {
+ target: notificationData
+ function onPopupChanged() {
+ if (!notificationData.popup && !win.exiting) {
+ win.exiting = true;
+ win.screenY = win.screenY;
+ tx.x = 96;
+ exitWatchdog.restart();
+ forceCleanupTimer.restart();
+ NotificationService.removeFromVisibleNotifications(notificationData);
+ }
+ }
+ }
+
+ Timer {
+ id: exitWatchdog
+ interval: 500
+ repeat: false
+ onTriggered: if (win) win.exitFinished()
+ }
+
+ Timer {
+ id: forceCleanupTimer
+ interval: 2000
+ repeat: false
+ onTriggered: if (win) win.exitFinished()
+ }
+
+ function maybeFinishExit() {
+ if (win && win.exiting && content && Math.abs(tx.x - 96) < 0.5 && content.opacity === 0) {
+ exitWatchdog.stop();
+ forceCleanupTimer.stop();
+ win.exitFinished();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Modules/Notifications/NotificationPopupManager.qml b/Modules/Notifications/NotificationPopupManager.qml
index 80c2e2c7..17ee003d 100644
--- a/Modules/Notifications/NotificationPopupManager.qml
+++ b/Modules/Notifications/NotificationPopupManager.qml
@@ -6,166 +6,100 @@ import qs.Services
QtObject {
id: manager
- property var popupLoaders: []
property int maxTargetNotifications: 3
property int baseNotificationHeight: 132
- property bool dismissalInProgress: false
+ property int topMargin: 0
+ property var popupWindows: []
- property Timer dismissalTimer: Timer {
- interval: 200
- repeat: false
- onTriggered: dismissNextOldest()
- }
-
- property Component popupLoaderComponent: Component {
- Loader {
- id: popupLoader
-
- property var notifWrapper
-
- active: false
- asynchronous: true
-
- sourceComponent: NotificationPopup {
- id: popup
-
- notificationData: popupLoader.notifWrapper
- notificationId: popupLoader.notifWrapper ? popupLoader.notifWrapper.notification.id : ""
- onEntered: manager._onPopupEntered(popupLoader)
- onSlideOutChanged: {
- if (slideOut) {
- manager._onPopupExitStarted(popupLoader);
- }
- }
- onExitFinished: manager._onPopupExitFinished(popupLoader)
- }
+ property Component popupComponent: Component {
+ NotificationPopup {
+ property var wrapper
+ notificationData: wrapper
+ notificationId: wrapper.notification.id
+ rowHeight: manager.baseNotificationHeight
+ onEntered: manager._onPopupEntered(this)
+ onExitFinished: manager._onPopupExitFinished(this)
}
}
property Connections notificationConnections: Connections {
- function onVisibleNotificationsChanged() {
- syncPopupsWithQueue(NotificationService.visibleNotifications);
- }
-
target: NotificationService
- }
-
- function _createPopupLoader(notifWrapper) {
- const L = popupLoaderComponent.createObject(manager, {
- "notifWrapper": notifWrapper
- });
- popupLoaders.push(L);
- return L;
- }
-
- function _destroyPopupLoader(L) {
- const i = popupLoaders.indexOf(L);
- if (i !== -1) {
- popupLoaders.splice(i, 1);
- popupLoaders = popupLoaders.slice();
- }
- L.active = false;
- L.sourceComponent = null;
- }
-
- function _activeItems() {
- return popupLoaders.filter((L) => {
- return L.item && L.item.notificationData && L.item.notificationData.popup;
- });
- }
-
- function _stableItems() {
- return _activeItems().filter((L) => {
- return !L.item.entering;
- });
- }
-
- function repositionAll() {
- const stable = _stableItems();
- for (let i = 0; i < stable.length; ++i) {
- const it = stable[i].item;
- if (it)
- it.verticalOffset = i * baseNotificationHeight;
-
+ function onVisibleNotificationsChanged() {
+ manager._sync(NotificationService.visibleNotifications);
}
}
- function syncPopupsWithQueue(newWrappers) {
+ function _hasWindowFor(w) { return popupWindows.some(p => p && p.notificationData === w); }
+
+ function _sync(newWrappers) {
for (let w of newWrappers) {
- if (!popupLoaders.some((L) => {
- return L.notifWrapper === w;
- })) {
- const L = _createPopupLoader(w);
- const actives = _activeItems().length;
- w.initialOffset = actives * baseNotificationHeight;
- L.active = true;
+ if (!_hasWindowFor(w)) _insertNewestAtTop(w);
+ }
+ for (let p of popupWindows.slice()) {
+ if (newWrappers.indexOf(p.notificationData) === -1 && p && !p.exiting) {
+ p.notificationData.removedByLimit = true;
+ p.notificationData.popup = false;
}
}
- for (let L of popupLoaders.slice()) {
- if (newWrappers.indexOf(L.notifWrapper) === -1)
- _destroyPopupLoader(L);
-
- }
- repositionAll();
}
- function _onPopupEntered(L) {
- repositionAll();
- maybeStartOverflow();
- }
-
- function _onPopupExitStarted(L) {
- const it = L.item;
- if (!it)
- return ;
-
- if (it.shadowLayers) {
- for (let layer of it.shadowLayers) {
- if (layer)
- layer.visible = false;
-
+ function _insertNewestAtTop(wrapper) {
+ for (let p of popupWindows) {
+ if (p && p.notificationData && p.notificationData.popup && !p.exiting) {
+ p.screenY = p.screenY + baseNotificationHeight;
}
}
- if (it.iconContainer && it.iconContainer.iconImage) {
- it.iconContainer.iconImage.source = "";
+
+ const win = popupComponent.createObject(null, { wrapper: wrapper, screenY: topMargin });
+ if (!win) {
+ console.warn("Popup create failed");
+ return;
+ }
+ popupWindows.push(win);
+ _maybeStartOverflow();
+ }
+
+ function _active() {
+ return popupWindows.filter(p => p && p.notificationData && p.notificationData.popup);
+ }
+
+ function _bottom() {
+ let b = null, max = -1;
+ for (let p of _active()) {
+ if (!p.exiting && p.screenY > max) {
+ max = p.screenY;
+ b = p;
+ }
+ }
+ return b;
+ }
+
+ function _maybeStartOverflow() {
+ if (_active().length <= maxTargetNotifications + 1) return;
+ const b = _bottom();
+ if (b && !b.exiting) {
+ b.notificationData.removedByLimit = true;
+ b.notificationData.popup = false;
}
}
- function _onPopupExitFinished(L) {
- NotificationService.releaseWrapper(L.notifWrapper);
- _destroyPopupLoader(L);
- repositionAll();
- maybeStartOverflow();
+ function _onPopupEntered(p) {
+ // Entry completed
}
- function maybeStartOverflow() {
- const active = _activeItems();
- if (dismissalInProgress)
- return ;
-
- if (active.length > maxTargetNotifications)
- startSequentialDismissal();
-
- }
-
- function startSequentialDismissal() {
- dismissalInProgress = true;
- dismissNextOldest();
- }
-
- function dismissNextOldest() {
- const active = _activeItems();
- if (active.length <= maxTargetNotifications) {
- dismissalInProgress = false;
- return ;
- }
- const oldest = active[0].item;
- if (oldest) {
- oldest.notificationData.removedByLimit = true;
- oldest.notificationData.popup = false;
- dismissalTimer.restart();
+ function _onPopupExitFinished(p) {
+ const i = popupWindows.indexOf(p);
+ if (i !== -1) {
+ popupWindows.splice(i, 1);
+ popupWindows = popupWindows.slice();
}
+ if (NotificationService.releaseWrapper) NotificationService.releaseWrapper(p.notificationData);
+ p.destroy();
+
+ const survivors = _active().filter(s => !s.exiting).sort((a,b) => a.screenY - b.screenY);
+ for (let k = 0; k < survivors.length; ++k)
+ survivors[k].screenY = topMargin + k * baseNotificationHeight;
+
+ _maybeStartOverflow();
}
-
-
-}
+}
\ No newline at end of file
diff --git a/Services/NotificationService.qml b/Services/NotificationService.qml
index 9d4366e7..319e5bea 100644
--- a/Services/NotificationService.qml
+++ b/Services/NotificationService.qml
@@ -20,6 +20,7 @@ Singleton {
property int maxVisibleNotifications: 3
property bool addGateBusy: false
property int enterAnimMs: 400
+ property int seqCounter: 0
Timer {
id: addGate
@@ -82,6 +83,7 @@ Singleton {
property bool removedByLimit: false
property bool isPersistent: true
property int initialOffset: 0
+ property int seq: 0
onPopupChanged: {
if (!popup) {
@@ -217,6 +219,7 @@ Singleton {
const [next, ...rest] = notificationQueue;
notificationQueue = rest;
+ next.seq = ++seqCounter;
visibleNotifications = [...visibleNotifications, next];
next.popup = true;