From 3462bcc46ecf74f576ea4397f16492c16bd75b10 Mon Sep 17 00:00:00 2001 From: Nippy Date: Fri, 8 May 2026 19:50:49 +0200 Subject: raveos update --- .../Popup/NotificationPopupManager.qml | 239 +++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 raveos-hyprland-theme/theme-data/dms/Modules/Notifications/Popup/NotificationPopupManager.qml (limited to 'raveos-hyprland-theme/theme-data/dms/Modules/Notifications/Popup/NotificationPopupManager.qml') 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(); + } + } +} -- cgit v1.3