summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/dms/Modules/Notifications/Popup/NotificationPopupManager.qml
diff options
context:
space:
mode:
authorNippy <nippy@rp1.hu>2026-05-08 19:50:49 +0200
committerNippy <nippy@rp1.hu>2026-05-08 19:50:49 +0200
commit3462bcc46ecf74f576ea4397f16492c16bd75b10 (patch)
treec5ab7aef4498647e057bda8d49c11e5f9dda74c9 /raveos-hyprland-theme/theme-data/dms/Modules/Notifications/Popup/NotificationPopupManager.qml
parent56616f693b4f263cbf0d47da43b67424efa6022d (diff)
downloadRaveOS-PKGBUILD-3462bcc46ecf74f576ea4397f16492c16bd75b10.tar.gz
RaveOS-PKGBUILD-3462bcc46ecf74f576ea4397f16492c16bd75b10.zip
raveos update
Diffstat (limited to 'raveos-hyprland-theme/theme-data/dms/Modules/Notifications/Popup/NotificationPopupManager.qml')
-rw-r--r--raveos-hyprland-theme/theme-data/dms/Modules/Notifications/Popup/NotificationPopupManager.qml239
1 files changed, 239 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/dms/Modules/Notifications/Popup/NotificationPopupManager.qml b/raveos-hyprland-theme/theme-data/dms/Modules/Notifications/Popup/NotificationPopupManager.qml
new file mode 100644
index 0000000..02bfc0f
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/dms/Modules/Notifications/Popup/NotificationPopupManager.qml
@@ -0,0 +1,239 @@
+import QtQuick
+import qs.Common
+import qs.Services
+
+QtObject {
+ id: manager
+
+ property var modelData
+ property int topMargin: 0
+ readonly property bool compactMode: SettingsData.notificationCompactMode
+ readonly property real cardPadding: compactMode ? Theme.notificationCardPaddingCompact : Theme.notificationCardPadding
+ readonly property real popupIconSize: compactMode ? Theme.notificationIconSizeCompact : Theme.notificationIconSizeNormal
+ readonly property real actionButtonHeight: compactMode ? 20 : 24
+ readonly property real contentSpacing: compactMode ? Theme.spacingXS : Theme.spacingS
+ readonly property real popupSpacing: compactMode ? 0 : Theme.spacingXS
+ readonly property real collapsedContentHeight: Math.max(popupIconSize, Theme.fontSizeSmall * 1.2 + Theme.fontSizeMedium * 1.2 + Theme.fontSizeSmall * 1.2 * (compactMode ? 1 : 2))
+ readonly property int baseNotificationHeight: cardPadding * 2 + collapsedContentHeight + actionButtonHeight + contentSpacing + popupSpacing
+ property var popupWindows: []
+ property var destroyingWindows: new Set()
+ property var pendingDestroys: []
+ property int destroyDelayMs: 100
+ property Component popupComponent
+
+ popupComponent: Component {
+ NotificationPopup {
+ onExitFinished: manager._onPopupExitFinished(this)
+ onPopupHeightChanged: manager._onPopupHeightChanged(this)
+ }
+ }
+
+ property Connections notificationConnections
+
+ notificationConnections: Connections {
+ function onVisibleNotificationsChanged() {
+ manager._sync(NotificationService.visibleNotifications);
+ }
+
+ target: NotificationService
+ }
+
+ property Timer sweeper
+
+ property Timer destroyTimer: Timer {
+ interval: destroyDelayMs
+ running: false
+ repeat: false
+ onTriggered: manager._processDestroyQueue()
+ }
+
+ function _processDestroyQueue() {
+ if (pendingDestroys.length === 0)
+ return;
+ const p = pendingDestroys.shift();
+ if (p && p.destroy) {
+ try {
+ p.destroy();
+ } catch (e) {}
+ }
+ if (pendingDestroys.length > 0)
+ destroyTimer.restart();
+ }
+
+ function _scheduleDestroy(p) {
+ if (!p)
+ return;
+ pendingDestroys.push(p);
+ if (!destroyTimer.running)
+ destroyTimer.restart();
+ }
+
+ sweeper: Timer {
+ interval: 500
+ running: false
+ repeat: true
+ onTriggered: {
+ const toRemove = [];
+ for (const p of popupWindows) {
+ if (!p) {
+ toRemove.push(p);
+ continue;
+ }
+ const isZombie = p.status === Component.Null || (!p.visible && !p.exiting) || (!p.notificationData && !p._isDestroying) || (!p.hasValidData && !p._isDestroying);
+ if (isZombie) {
+ toRemove.push(p);
+ if (p.forceExit) {
+ p.forceExit();
+ } else if (p.destroy) {
+ try {
+ p.destroy();
+ } catch (e) {}
+ }
+ }
+ }
+ if (toRemove.length) {
+ popupWindows = popupWindows.filter(p => toRemove.indexOf(p) === -1);
+ _repositionAll();
+ }
+ if (popupWindows.length === 0)
+ sweeper.stop();
+ }
+ }
+
+ function _hasWindowFor(w) {
+ return popupWindows.some(p => p && p.notificationData === w && !p._isDestroying && p.status !== Component.Null);
+ }
+
+ function _isValidWindow(p) {
+ return p && p.status !== Component.Null && !p._isDestroying && p.hasValidData;
+ }
+
+ function _isFocusedScreen() {
+ if (!SettingsData.notificationFocusedMonitor)
+ return true;
+ const focused = CompositorService.getFocusedScreen();
+ return focused && manager.modelData && focused.name === manager.modelData.name;
+ }
+
+ function _sync(newWrappers) {
+ for (const p of popupWindows.slice()) {
+ if (!_isValidWindow(p) || p.exiting)
+ continue;
+ if (p.notificationData && newWrappers.indexOf(p.notificationData) === -1) {
+ p.notificationData.removedByLimit = true;
+ p.notificationData.popup = false;
+ }
+ }
+ for (const w of newWrappers) {
+ if (w && !_hasWindowFor(w) && _isFocusedScreen())
+ _insertAtTop(w);
+ }
+ }
+
+ function _popupHeight(p) {
+ return (p.alignedHeight || p.implicitHeight || (baseNotificationHeight - popupSpacing)) + popupSpacing;
+ }
+
+ function _insertAtTop(wrapper) {
+ if (!wrapper)
+ return;
+ const notificationId = wrapper?.notification ? wrapper.notification.id : "";
+ const win = popupComponent.createObject(null, {
+ "notificationData": wrapper,
+ "notificationId": notificationId,
+ "screenY": topMargin,
+ "screen": manager.modelData
+ });
+ if (!win)
+ return;
+ if (!win.hasValidData) {
+ win.destroy();
+ return;
+ }
+ popupWindows.unshift(win);
+ _repositionAll();
+ if (!sweeper.running)
+ sweeper.start();
+ }
+
+ function _repositionAll() {
+ const active = popupWindows.filter(p => _isValidWindow(p) && p.notificationData?.popup && !p.exiting);
+
+ const pinnedSlots = [];
+ for (const p of active) {
+ if (!p.hovered)
+ continue;
+ pinnedSlots.push({
+ y: p.screenY,
+ end: p.screenY + _popupHeight(p)
+ });
+ }
+ pinnedSlots.sort((a, b) => a.y - b.y);
+
+ let currentY = topMargin;
+ for (const win of active) {
+ if (win.hovered)
+ continue;
+ for (const slot of pinnedSlots) {
+ if (currentY >= slot.y - 1 && currentY < slot.end)
+ currentY = slot.end;
+ }
+ win.screenY = currentY;
+ currentY += _popupHeight(win);
+ }
+ }
+
+ function _onPopupHeightChanged(p) {
+ if (!p || p.exiting || p._isDestroying)
+ return;
+ if (popupWindows.indexOf(p) === -1)
+ return;
+ _repositionAll();
+ }
+
+ function _onPopupExitFinished(p) {
+ if (!p)
+ return;
+ const windowId = p.toString();
+ if (destroyingWindows.has(windowId))
+ return;
+ destroyingWindows.add(windowId);
+ const i = popupWindows.indexOf(p);
+ if (i !== -1) {
+ popupWindows.splice(i, 1);
+ popupWindows = popupWindows.slice();
+ }
+ if (NotificationService.releaseWrapper && p.notificationData)
+ NotificationService.releaseWrapper(p.notificationData);
+ _scheduleDestroy(p);
+ Qt.callLater(() => destroyingWindows.delete(windowId));
+ _repositionAll();
+ }
+
+ function cleanupAllWindows() {
+ sweeper.stop();
+ destroyTimer.stop();
+ pendingDestroys = [];
+ for (const p of popupWindows.slice()) {
+ if (p) {
+ try {
+ if (p.forceExit) {
+ p.forceExit();
+ } else if (p.destroy) {
+ p.destroy();
+ }
+ } catch (e) {}
+ }
+ }
+ popupWindows = [];
+ destroyingWindows.clear();
+ }
+
+ onPopupWindowsChanged: {
+ if (popupWindows.length > 0 && !sweeper.running) {
+ sweeper.start();
+ } else if (popupWindows.length === 0 && sweeper.running) {
+ sweeper.stop();
+ }
+ }
+}